Source: create-upsell-app.js

import { createApp, watch, watchEffect } from "vue";

import * as Sentry from "@sentry/vue";

import UpsellView from "./components/UpsellView.vue";

/**
 * Creates a Vue app, providing the UpsellStore for state management.
 * Loads the UI component files asynchronously, then mounts the app.
 * Exposes the global {@link createOfferView} function
 * @function createUpsellApp
 * @param {UpsellStore} store - store object provided to the app for state management
 */
export default function createUpsellApp(store) {
    if (!store.flow) {
        showThankYouPage();
        return;
    }

    const app = createApp(UpsellView);
    app.provide("upsellsStore", store);

    Sentry.init({
        app,
        dsn: "https://edbac16706954e6bba8f616ed9a366bf@o4504920595890176.ingest.sentry.io/4505557581496320",
        integrations: [
            new Sentry.BrowserTracing(),
            new Sentry.Replay(),
        ],
        // Capture performance data for 5% of all sessions
        tracesSampleRate: 0.05,
        // Capture performance data for /upsell API calls
        tracePropagationTargets: ["localhost", "cf.earthbreezedev.com/upsell"],
        // Capture Replay for 1% of all sessions,
        // plus for 10% of sessions with an error
        replaysSessionSampleRate: 0.01,
        replaysOnErrorSampleRate: 0.1,
    });

    /**
     * Registers a UI component with the app, marking it "loaded" in the UpsellStore state.
     * This ensures all components are registered with the app before it mounts.
     * @function createOfferView
     * @param {String} name - Name of the component
     * @param {Component} component - Vue Component to be registered
     * @returns {void}
     * @global
     */
    window.createOfferView = (name, component) => {
        app.component(name, component);
        store.loadView(name);
    };

    let isMounted = false;
    watch(store, ({ allViewsLoaded }) => {
        if (allViewsLoaded && !isMounted) {
            mount(app);
            isMounted = true;
        }
    });

    /**
     * Script tags are used for backward compatibility.
     * Dynamic imports would be simpler, but they are not supported in older browsers.
     */
    store.views.forEach(addAsyncScriptTag);

    watchEffect(() => {
        if (store.flow === null) {
            showThankYouPage();
        }
    });
}

/**
 * Asynchronously loads and executes a JavaScript file.
 * @function addAsyncScriptTag
 * @param {String} src 
 * @returns {void}
 * @private
 */
function addAsyncScriptTag(src) {
    const script = document.createElement('script');
    script.src = src;
    script.async = true;
    /**
     * type="module" is helpful for local development with ES modules,
     * but production code won't contain any ES syntax.
     */
    script.type = "module";
    document.head.appendChild(script);
    return script;
}

/**
 * Adds a div element to the document body and mounts a Vue app onto it.
 * This way we don't need to edit the page HTML ahead of time.
 * @private
 * @function mount
 * @param {App} app - Vue App instance
 * @returns {void}
 */
function mount(app) {
    const target = document.createElement('div');
    document.body.insertBefore(target, document.body.firstChild);
    app.mount(target);
    hideThankYouPage();
    removeLoadingSpinner();
}

function stopLoading() {
    const loader = document.querySelector("div.upsell-container");
    if (loader) loader.style.display = "none";
}

/**
 * Hides the static "Thank You" page content, if it exists.
 * @private
 */
function hideThankYouPage() {
    stopLoading();
    const header = document.querySelector("div.header");
    if (header) header.style.display = "none";

    const main = document.getElementById("main");
    if (main) main.style.display = "none";
}

/**
 * Shows the static "Thank You" page content, if it exists.
 * @private
 */
export function showThankYouPage() {
    stopLoading();
    const header = document.querySelector("div.header");
    if (header) header.style.display = "block";

    const main = document.getElementById("main");
    if (main) main.style.display = "block";

    removeLoadingSpinner();
}

/**
 * Removes the loading spinner from the page.
 * This is necessary so that the thank you page can display properly.
 * @private
 */
function removeLoadingSpinner() {
    const checkoutWrapper = document.querySelector('#main .wrap');
    if (checkoutWrapper) checkoutWrapper.style.display = "block";

    const loadingSpinner = document.querySelector('div.loader');
    if (loadingSpinner) loadingSpinner.style.display = "none";
}