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";
}