Wordpress - Front end form to create a custom post with preview

There are many ways you could use to reach your goal. Here's one...

Steps:

  1. Create a shortcode that renders the form.
  2. Create and enqueue a JavaScript file that handles the form submission.
  3. Create PHP function for creating/updating Post/Custom Post.

In your functions.php:

/**
 * @return  string  $html   HTML form.
 */
function shortcode__new_post_form() {
    /**
     * @var     WP_User $current_user
     */
    $current_user = $GLOBALS['current_user'];

    ob_start(); ?>
    <form action="" id="form-new-post">
        <fieldset>
            <legend>NEW AWESOME POST</legend>
            <div class="form-group">
                <input type="text" class="form-control" name="post_title" required/>
            </div>
            <input type="hidden" name="ID" value=""/>
            <input type="hidden" name="post_author" value="<?php echo $current_user->ID; ?>"/>
            <button type="submit"
                    class="submit"
                    data-is-updated="false"
                    data-is-update-text="UPDATE">CREATE
            </button>
        </fieldset>
        <a href=""
           class="preview-link"
           target="_blank"
           style="display: none;"
           rel="nofollow">Preview Link</a>
    </form>
    <?php
    $html = ob_get_clean();

    return $html;
}

function script__new_post_form() {
    wp_enqueue_script(
        'new-post-form',
        // Insert here your JavaScript file URL,
        array( 'jquery' ),
        '1.0.0',
        true
    );

    wp_localize_script(
        'new-post-form',
        'localized_new_post_form',
        array(
            'admin_ajax_url' => admin_url( 'admin-ajax.php' ),
        )
    );
}

function maybe_insert_new_post() {
    /**
     * @var     array $r Initialize response variable.
     */
    $r = array(
        'error'        => '', // Error message.
        'html'         => '', // Any message/HTML you want to output to the logged in user.
        'preview_link' => '', // Preview link URL.
        'post'         => '' // The created/updated post.
    );

    /**
     * @link    https://developer.wordpress.org/reference/functions/wp_insert_post/
     */
    $postarr = array(
        'ID'          => '', // If ID stays empty the post will be created.
        'post_author' => '',
        'post_title'  => 'My name is Optimus Prime',
        'post_status' => 'draft',
        'post_type'   => 'post',
        'meta_input'  => array( // Delete this line if you won't use it!
            'your_NON_acf_meta_field_key' => 'your_NON_acf_meta_field_value'
        )
    );

    parse_str( $_POST['form_data'], $form_data );

    $postarr = array_merge( $postarr, $form_data );

    /**
     * wp_insert_post can either create posts or update existing ones, if ID is passed!
     *
     * @link    https://developer.wordpress.org/reference/functions/wp_insert_post/
     *
     * @param   array $postarr An array of elements that make up a post to update or insert.
     * @param   bool $wp_error Whether to return a WP_Error on failure.
     *
     * @return  int|WP_Error    The post ID on success. The value 0 or WP_Error on failure.
     */
    $new_post = wp_insert_post(
        $postarr,
        true
    );

    // Post was not created/updated, so let's output the error message.
    if ( is_wp_error( $new_post ) ) {
        $r['error'] = $new_post->get_error_message();

        echo json_encode( $r );

        exit;
    }

    $post_id = $new_post; // Just for reference.

    /**
     * To save ACF fields use update_field() function. It doesn't matter if it's text field, repeater field, etc.
     * Make sure the field exists in admin area.
     * Use update_field() as many times as you want.
     *
     * @link    https://www.advancedcustomfields.com/resources/update_field/
     */
    update_field( 'your_acf_meta_key', 'field_value', $post_id );
//  update_field( 'your_acf_meta_key', 'field_value', $post_id );
//  update_field( 'your_acf_meta_key', 'field_value', $post_id );

    /**
     * @link    https://developer.wordpress.org/reference/functions/get_preview_post_link/
     */
    $preview_link = get_preview_post_link( $post_id );

    if ( $preview_link ) {
        $r['preview_link'] = $preview_link;
    }

    // Gets post info in array format as it's easier to debug via console if needed.
    $post_array = get_post( $post_id, ARRAY_A );

    if ( $post_array ) {
        $r['post'] = $post_array;
    }

    echo json_encode( $r );

    exit;
}

// Ads shortcode so you can use the form anywhere you want.
add_shortcode( 'new_post_form', 'shortcode__new_post_form' );

// Use wp_enqueue_scripts action hook so you can correctly localize the script with admin ajax URL.
add_action( 'wp_enqueue_scripts', 'script__new_post_form' );

// Prefix 'wp_ajax_' is mandatory.
add_action( 'wp_ajax_new_post', 'maybe_insert_new_post' );

Create a JavaScript file and write in it (don't forget to put its URL in your functions.php):

(function ($) {

    var el_form = $('#form-new-post'),
        el_form_submit = $('.submit', el_form);

    // Fires when the form is submitted.
    el_form.on('submit', function (e) {
        e.preventDefault();

        el_form_submit.attr('disabled', 'disabled');

        new_post();
    });

    // Ajax request.
    function new_post() {
        $.ajax({
            url: localized_new_post_form.admin_ajax_url,
            type: 'POST',
            dataType: 'json',
            data: {
                action: 'new_post', // Set action without prefix 'wp_ajax_'.
                form_data: el_form.serialize()
            },
            cache: false
        }).done(function (r) {
            if (r.post !== '' && r.preview_link !== '') {
                $('[name="ID"]', el_form).attr('value', r.post.ID);
                $('.preview-link', el_form)
                    .attr('href', r.preview_link)
                    .show();
                el_form_submit.attr('data-is-updated', 'true');
                el_form_submit.text(el_form_submit.data('is-update-text'));
            }

            el_form_submit.removeAttr('disabled');
        });
    }

    // Used to trigger/simulate post submission without user action.
    function trigger_new_post() {
        el_form.trigger('submit');
    }

    // Sets interval so the post the can be updated automatically provided that it was already created.
    setInterval(function () {
        if (el_form_submit.attr('data-is-updated') === 'false') {
            return false;
        }

        trigger_new_post();
    }, 5000); // Set to 5 seconds.

})(jQuery);

Now, create a new page and insert the shortcode [new_post_form] in it. Open the page and test your form.

If it works for you, please accept my answer as your solution.