UNPKG

@plandalf/react

Version:

React checkout components for Plandalf Checkout — an embedded and popup checkout alternative to Stripe Checkout and SamCart, with a built-in billing portal.

1,029 lines (1,013 loc) 68.1 kB
'use strict'; var React = require('react'); var ReactDOM = require('react-dom'); /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __rest(s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; var _a; // Hook to create event handlers that emit to Journey and call user callbacks function useEmbedEvents(journey, callbacks) { return React.useMemo(function () { return ({ onInit: function (checkoutId) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onInit', checkoutId); (_a = callbacks.onInit) === null || _a === void 0 ? void 0 : _a.call(callbacks, checkoutId); }, onPageChange: function (checkoutId, pageId) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onPageChange', checkoutId, pageId); (_a = callbacks.onPageChange) === null || _a === void 0 ? void 0 : _a.call(callbacks, checkoutId, pageId); }, onPaymentInit: function (checkoutId) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onPaymentInit', checkoutId); (_a = callbacks.onPaymentInit) === null || _a === void 0 ? void 0 : _a.call(callbacks, checkoutId); }, onSubmit: function (checkoutId) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onSubmit', checkoutId); (_a = callbacks.onSubmit) === null || _a === void 0 ? void 0 : _a.call(callbacks, checkoutId); }, onSuccess: function (data) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onSuccess', data); (_a = callbacks.onSuccess) === null || _a === void 0 ? void 0 : _a.call(callbacks, data); }, onComplete: function (checkout) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onComplete', checkout); (_a = callbacks.onComplete) === null || _a === void 0 ? void 0 : _a.call(callbacks, checkout); }, onCancel: function (data) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onCancel', data); (_a = callbacks.onCancel) === null || _a === void 0 ? void 0 : _a.call(callbacks, data); }, onClosed: function (data) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onClosed', data); (_a = callbacks.onClosed) === null || _a === void 0 ? void 0 : _a.call(callbacks, data); }, onLineItemChange: function (data) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onLineItemChange', data); (_a = callbacks.onLineItemChange) === null || _a === void 0 ? void 0 : _a.call(callbacks, data); }, onResize: function (data) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onResize', data); (_a = callbacks.onResize) === null || _a === void 0 ? void 0 : _a.call(callbacks, data); }, onError: function (error) { var _a; journey === null || journey === void 0 ? void 0 : journey.emit('onError', error); (_a = callbacks.onError) === null || _a === void 0 ? void 0 : _a.call(callbacks, error); } }); }, [ journey, callbacks.onInit, callbacks.onPageChange, callbacks.onPaymentInit, callbacks.onSubmit, callbacks.onSuccess, callbacks.onComplete, callbacks.onCancel, callbacks.onClosed, callbacks.onLineItemChange, callbacks.onResize, callbacks.onError ]); } var BASE_URL = ((_a = undefined) === null || _a === void 0 ? void 0 : _a.VITE_PLANDALF_BASE_URL) || 'https://checkout.plandalf.dev'; // Generate unique embed ID var generateEmbedId = function () { var min = 1e13; var max = 99999999999999; var randomNumber = Math.floor(Math.random() * (max - min + 1)) + min; return "".concat(randomNumber); }; // Updated hook for iframe functionality function useNumiFrame(_a) { var offerId = _a.offerId, domain = _a.domain, inheritParameters = _a.inheritParameters, parameters = _a.parameters, customer = _a.customer, dynamicResize = _a.dynamicResize; var _b = React.useState(), searchParams = _b[0], setSearchParams = _b[1]; var _c = React.useState(), embedId = _c[0], setEmbedId = _c[1]; React.useEffect(function () { setSearchParams(new URLSearchParams(window.location.search)); setEmbedId(generateEmbedId()); }, []); // Determine origin var origin; if (domain) { if (/^https?:\/\//i.test(domain)) { origin = domain; } else { origin = "https://".concat(domain); } } else { origin = BASE_URL; } var iframeUrl = new URL("".concat(origin, "/o/").concat(offerId)); if (inheritParameters && searchParams) { searchParams.forEach(function (value, key) { iframeUrl.searchParams.set(key, value); }); } // Add regular parameters first if (parameters) { for (var _i = 0, _d = Object.entries(parameters); _i < _d.length; _i++) { var _e = _d[_i], key = _e[0], value = _e[1]; if (value) iframeUrl.searchParams.set(key, value); } } // Add customer information to parameters using encoded format customer[key] if (customer) { if (customer.email) iframeUrl.searchParams.set('customer[email]', customer.email); if (customer.first_name) iframeUrl.searchParams.set('customer[first_name]', customer.first_name); if (customer.last_name) iframeUrl.searchParams.set('customer[last_name]', customer.last_name); if (customer.phone) iframeUrl.searchParams.set('customer[phone]', customer.phone); if (customer.company) iframeUrl.searchParams.set('customer[company]', customer.company); // Handle address information with encoded format customer[address][key] if (customer.address) { if (customer.address.line1) iframeUrl.searchParams.set('customer[address][line1]', customer.address.line1); if (customer.address.line2) iframeUrl.searchParams.set('customer[address][line2]', customer.address.line2); if (customer.address.city) iframeUrl.searchParams.set('customer[address][city]', customer.address.city); if (customer.address.state) iframeUrl.searchParams.set('customer[address][state]', customer.address.state); if (customer.address.postal_code) iframeUrl.searchParams.set('customer[address][postal_code]', customer.address.postal_code); if (customer.address.country) iframeUrl.searchParams.set('customer[address][country]', customer.address.country); } } if (embedId) { iframeUrl.searchParams.set("numi-embed-id", embedId); } if (dynamicResize) { iframeUrl.searchParams.set("numi-embed-dynamic-resize", "true"); } // Memoize the return object to prevent unnecessary re-renders return React.useMemo(function () { return ({ iframeUrl: iframeUrl.toString(), origin: origin, embedId: embedId }); }, [iframeUrl.toString(), origin, embedId]); } // New: billing portal frame hook function useBillingPortalFrame(_a) { var domain = _a.domain, inheritParameters = _a.inheritParameters, parameters = _a.parameters, customerToken = _a.customerToken, returnUrl = _a.returnUrl, dynamicResize = _a.dynamicResize; var _b = React.useState(), searchParams = _b[0], setSearchParams = _b[1]; var _c = React.useState(), embedId = _c[0], setEmbedId = _c[1]; React.useEffect(function () { setSearchParams(new URLSearchParams(window.location.search)); setEmbedId(generateEmbedId()); }, []); // Determine origin var origin; if (domain) { if (/^https?:\/\//i.test(domain)) { origin = domain; } else { origin = "https://".concat(domain); } } else { origin = BASE_URL; } var iframeUrl = new URL("".concat(origin, "/billing/portal")); if (inheritParameters && searchParams) { searchParams.forEach(function (value, key) { iframeUrl.searchParams.set(key, value); }); } if (parameters) { for (var _i = 0, _d = Object.entries(parameters); _i < _d.length; _i++) { var _e = _d[_i], key = _e[0], value = _e[1]; if (value) iframeUrl.searchParams.set(key, value); } } if (customerToken) { iframeUrl.searchParams.set('customer', customerToken); } if (returnUrl) { iframeUrl.searchParams.set('return_url', returnUrl); } if (embedId) { iframeUrl.searchParams.set('numi-embed-id', embedId); } if (dynamicResize) { iframeUrl.searchParams.set('numi-embed-dynamic-resize', 'true'); } return React.useMemo(function () { return ({ iframeUrl: iframeUrl.toString(), origin: origin, embedId: embedId }); }, [iframeUrl.toString(), origin, embedId]); } // Updated message listener hook var useMessageListener = function (embed, eventName, fn, options) { var enabled = !(options === null || options === void 0 ? void 0 : options.disabled); React.useEffect(function () { if (embed && embed.embedId && enabled) { var listener_1 = function (event) { var _a; if (((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === eventName) { try { var sourceMatch = event.data.source === 'plandalf'; var typeMatch = event.data.type === eventName; var embedIdMatch = !event.data.embedId || event.data.embedId === embed.embedId; if (sourceMatch && typeMatch && embedIdMatch) { fn(event.data); } } catch (err) { console.error("Error in message listener:", err); } } }; window.addEventListener("message", listener_1); return function () { window.removeEventListener("message", listener_1); }; } }, [embed, eventName, fn, enabled]); }; // Enhanced events hook with comprehensive v1.js event system function useNumiEvents(embed, events) { // Core lifecycle event handlers var handleInit = React.useCallback(function (data) { if (events.onInit) { events.onInit(data.checkoutId); } }, [events.onInit]); var handlePageChange = React.useCallback(function (data) { if (events.onPageChange) { events.onPageChange(data.checkoutId, data.pageId); } }, [events.onPageChange]); var handlePaymentInit = React.useCallback(function (data) { if (events.onPaymentInit) { events.onPaymentInit(data.checkoutId); } }, [events.onPaymentInit]); var handleSubmit = React.useCallback(function (data) { if (events.onSubmit) { events.onSubmit(data.checkoutId); } }, [events.onSubmit]); var handleSuccess = React.useCallback(function (data) { if (events.onSuccess) { events.onSuccess(data); } }, [events.onSuccess]); var handleComplete = React.useCallback(function (data) { var _a; if (events.onComplete) { // Extract checkout object with proper ID field for Journey var checkoutId = data.checkoutId || data.id || ((_a = data.checkoutSession) === null || _a === void 0 ? void 0 : _a.id); var checkout = __assign(__assign({}, data), { id: checkoutId, checkoutId: checkoutId, checkout_id: checkoutId, session: data.checkoutSession, orderNumber: data.orderNumber }); events.onComplete(checkout); } }, [events.onComplete]); var handleCancel = React.useCallback(function (data) { if (events.onCancel) { events.onCancel(data); } }, [events.onCancel]); var handleClosed = React.useCallback(function (data) { if (events.onClosed) { // If the server sends a redirect URL, handle it if (data.successUrl || data.redirectUrl) { var redirectUrl = data.successUrl || data.redirectUrl; var checkoutId = data.sessionId || data.checkoutId || data.id; if (redirectUrl && typeof window !== 'undefined') { try { var url = new URL(redirectUrl, window.location.origin); // Add or replace checkout_id if we have a valid one // Also replace if existing value is "undefined" or empty var existingCheckoutId = url.searchParams.get('checkout_id'); if (checkoutId && (!existingCheckoutId || existingCheckoutId === 'undefined' || existingCheckoutId === '')) { url.searchParams.set('checkout_id', checkoutId); } window.location.assign(url.toString()); return; } catch (e) { console.error('Failed to parse redirect URL:', e); window.location.assign(redirectUrl); return; } } } events.onClosed(data); } }, [events.onClosed]); // Enhanced interaction event handlers var handleLineItemChange = React.useCallback(function (data) { if (events.onLineItemChange) { events.onLineItemChange(data); } }, [events.onLineItemChange]); var handleResize = React.useCallback(function (data) { if (events.onResize) { events.onResize(data); } }, [events.onResize]); var handleError = React.useCallback(function (data) { if (events.onError) { events.onError(data); } }, [events.onError]); // Memoize the options objects to prevent unnecessary re-renders var initOptions = React.useMemo(function () { return ({ disabled: !events.onInit }); }, [events.onInit]); var pageChangeOptions = React.useMemo(function () { return ({ disabled: !events.onPageChange }); }, [events.onPageChange]); var paymentInitOptions = React.useMemo(function () { return ({ disabled: !events.onPaymentInit }); }, [events.onPaymentInit]); var submitOptions = React.useMemo(function () { return ({ disabled: !events.onSubmit }); }, [events.onSubmit]); var successOptions = React.useMemo(function () { return ({ disabled: !events.onSuccess }); }, [events.onSuccess]); var completeOptions = React.useMemo(function () { return ({ disabled: !events.onComplete }); }, [events.onComplete]); var cancelOptions = React.useMemo(function () { return ({ disabled: !events.onCancel }); }, [events.onCancel]); var closedOptions = React.useMemo(function () { return ({ disabled: !events.onClosed }); }, [events.onClosed]); var lineItemChangeOptions = React.useMemo(function () { return ({ disabled: !events.onLineItemChange }); }, [events.onLineItemChange]); var resizeOptions = React.useMemo(function () { return ({ disabled: !events.onResize }); }, [events.onResize]); var errorOptions = React.useMemo(function () { return ({ disabled: !events.onError }); }, [events.onError]); // Core lifecycle events useMessageListener(embed, "on_init", handleInit, initOptions); useMessageListener(embed, "page_change", handlePageChange, pageChangeOptions); useMessageListener(embed, "payment_init", handlePaymentInit, paymentInitOptions); useMessageListener(embed, "checkout_submit", handleSubmit, submitOptions); useMessageListener(embed, "checkout_success", handleSuccess, successOptions); useMessageListener(embed, "checkout_complete", handleComplete, completeOptions); useMessageListener(embed, "checkout_cancel", handleCancel, cancelOptions); useMessageListener(embed, "checkout_closed", handleClosed, closedOptions); // Also listen for 'close' event (server sends this) useMessageListener(embed, "close", handleClosed, closedOptions); // Enhanced interaction events useMessageListener(embed, "lineitem_change", handleLineItemChange, lineItemChangeOptions); useMessageListener(embed, "form_resize", handleResize, resizeOptions); useMessageListener(embed, "checkout_error", handleError, errorOptions); } // Legacy hook for backward compatibility var useIframeMessage = function (callbacks) { var handleMessage = React.useCallback(function (event) { var _a, _b, _c, _d, _e, _f; console.log('plandalf.useIframeMessage.handleMessage', event); if (!event.data.source || !event.data.source.startsWith('plandalf')) return; if (event.data.type === 'checkout_success') { (_a = callbacks.onSuccess) === null || _a === void 0 ? void 0 : _a.call(callbacks); } else if (event.data.type === 'on_init') { (_b = callbacks.onInit) === null || _b === void 0 ? void 0 : _b.call(callbacks); } else if (event.data.type === 'checkout_cancel') { (_c = callbacks.onCancel) === null || _c === void 0 ? void 0 : _c.call(callbacks); } else if (event.data.type === 'page_change') { (_d = callbacks.onPageChange) === null || _d === void 0 ? void 0 : _d.call(callbacks); } else if (event.data.type === 'payment_init') { (_e = callbacks.onCheckoutInit) === null || _e === void 0 ? void 0 : _e.call(callbacks); } else if (event.data.type === 'checkout_resized') { (_f = callbacks.onCheckoutResized) === null || _f === void 0 ? void 0 : _f.call(callbacks); } }, []); React.useEffect(function () { window.addEventListener('message', handleMessage); return function () { window.removeEventListener('message', handleMessage); }; }, [handleMessage]); }; var JourneyContext = React.createContext(null); var useJourney = function () { var ctx = React.useContext(JourneyContext); if (!ctx) throw new Error('useJourney must be used within a Journey'); return ctx; }; var useJourneyOptional = function () { return React.useContext(JourneyContext); }; // Utility: navigate to success_url with checkoutId var redirectToSuccess = function (successUrl, checkoutId) { try { var url = new URL(successUrl, typeof window !== 'undefined' ? window.location.origin : undefined); if (checkoutId) url.searchParams.set('checkout_id', checkoutId); if (typeof window !== 'undefined') { window.location.assign(url.toString()); } } catch (_a) { if (typeof window !== 'undefined') { var qs = checkoutId ? "?checkout_id=".concat(encodeURIComponent(checkoutId)) : ''; window.location.assign("".concat(successUrl).concat(qs)); } } }; var Journey = function (_a) { var id = _a.id, type = _a.type, options = _a.options, customer = _a.customer, children = _a.children; var _b = React.useState(undefined), lastCheckoutId = _b[0], setLastCheckoutId = _b[1]; var journeyMetaRef = React.useRef({ id: id, type: type }); var registryRef = React.useRef(new Map()); // Core event handlers used by children embeds var handleInit = React.useCallback(function (checkoutId) { setLastCheckoutId(checkoutId); }, []); var handlePageChange = React.useCallback(function (_checkoutId, _pageId) { // no-op for now; reserved for analytics or step tracking }, []); var handlePaymentInit = React.useCallback(function (checkoutId) { setLastCheckoutId(checkoutId); }, []); var handleSubmit = React.useCallback(function (_checkoutId) { // no-op; could mark a step as submitted }, []); var handleSuccess = React.useCallback(function (data) { var checkoutId = (data === null || data === void 0 ? void 0 : data.checkout_id) || (data === null || data === void 0 ? void 0 : data.checkoutId) || (data === null || data === void 0 ? void 0 : data.id); if (checkoutId) setLastCheckoutId(checkoutId); }, []); var handleComplete = React.useCallback(function (checkout) { var checkoutId = (checkout === null || checkout === void 0 ? void 0 : checkout.id) || (checkout === null || checkout === void 0 ? void 0 : checkout.checkout_id) || (checkout === null || checkout === void 0 ? void 0 : checkout.checkoutId); if (checkoutId) setLastCheckoutId(checkoutId); var successUrl = options === null || options === void 0 ? void 0 : options.success_url; if (successUrl) { redirectToSuccess(successUrl, checkoutId); } }, [options === null || options === void 0 ? void 0 : options.success_url]); var handleCancel = React.useCallback(function (_data) { // no-op for now }, []); var handleClosed = React.useCallback(function (data) { // If server sent a successUrl in the event, it takes precedence // The embed's handleClosed will handle the redirect if ((data === null || data === void 0 ? void 0 : data.successUrl) || (data === null || data === void 0 ? void 0 : data.redirectUrl)) { return; } // Otherwise, use Journey's success_url if available var checkoutId = data.sessionId || data.checkoutId || data.id || lastCheckoutId; var successUrl = options === null || options === void 0 ? void 0 : options.success_url; if (successUrl && checkoutId) { redirectToSuccess(successUrl, checkoutId); } }, [options === null || options === void 0 ? void 0 : options.success_url, lastCheckoutId]); var handleLineItemChange = React.useCallback(function (_data) { // no-op for now }, []); var handleResize = React.useCallback(function (_data) { // no-op for now }, []); var handleError = React.useCallback(function (_error) { // optional: surface to user or log }, []); var emit = React.useCallback(function (eventName) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } // Run Journey-level side effects first try { switch (eventName) { case 'onInit': handleInit(args[0]); break; case 'onPaymentInit': handlePaymentInit(args[0]); break; case 'onSuccess': handleSuccess(args[0]); break; case 'onComplete': handleComplete(args[0]); break; case 'onPageChange': handlePageChange(args[0], args[1]); break; case 'onSubmit': handleSubmit(args[0]); break; case 'onCancel': handleCancel(args[0]); break; case 'onClosed': handleClosed(args[0]); break; case 'onLineItemChange': handleLineItemChange(args[0]); break; case 'onResize': handleResize(args[0]); break; case 'onError': handleError(args[0]); break; default: break; } } catch (_) { } // Fan out to all registered callbacks for this event registryRef.current.forEach(function (callbacks) { var cb = callbacks[eventName]; if (typeof cb === 'function') { try { // @ts-expect-error generic call-through cb.apply(void 0, args); } catch (_) { } } }); }, [handleInit, handlePaymentInit, handleSuccess, handleComplete, handlePageChange, handleSubmit, handleCancel, handleClosed, handleLineItemChange, handleResize, handleError]); var registerEmbedEvents = React.useCallback(function (key, callbacks) { registryRef.current.set(key, callbacks || {}); return function () { registryRef.current.delete(key); }; }, []); var value = React.useMemo(function () { return ({ id: journeyMetaRef.current.id, type: journeyMetaRef.current.type, options: options, customer: customer, lastCheckoutId: lastCheckoutId, registerEmbedEvents: registerEmbedEvents, emit: emit }); }, [options, customer, lastCheckoutId, registerEmbedEvents, emit]); // Discover if there are offer children to decide manual fallback visibility var hasOfferChild = React.useMemo(function () { var found = false; var scan = function (node) { if (!node || found) return; if (Array.isArray(node)) return node.forEach(scan); if (typeof node === 'string' || typeof node === 'number' || (node === null || node === void 0 ? void 0 : node.type) === React.Fragment) return; var element = node; if (element && element.props) { var props = element.props; var isOfferEmbed = typeof (props === null || props === void 0 ? void 0 : props.offerId) === 'string' || typeof (props === null || props === void 0 ? void 0 : props.offer) === 'string'; if (isOfferEmbed) { found = true; return; } if (props.children) scan(props.children); } }; scan(children); return found; }, [children]); // Manual mode fallback UI: if there are no offer children, we present a very small // adapter to allow external checkout providers to notify completion. var ManualModeBridge = function () { var complete = function () { // Mock: generate a placeholder checkout id and redirect var mockId = "ext_".concat(Math.random().toString(36).slice(2, 10)); setLastCheckoutId(mockId); if (options === null || options === void 0 ? void 0 : options.success_url) { redirectToSuccess(options.success_url, mockId); } }; return (React.createElement("div", { style: { display: 'none' }, "data-journey-bridge": "true", "data-journey-id": id, "data-journey-type": type }, React.createElement("button", { type: "button", style: { display: 'none' }, onClick: complete, "aria-hidden": true }))); }; return (React.createElement(JourneyContext.Provider, { value: value }, children, !hasOfferChild && React.createElement(ManualModeBridge, null))); }; // Inject styles var styleInject = function (css, _a) { var _b = _a === void 0 ? {} : _a, insertAt = _b.insertAt; if (!css || typeof document === "undefined") return; var head = document.head || document.getElementsByTagName("head")[0]; var style = document.createElement("style"); style.type = "text/css"; if (insertAt === "top") { if (head.firstChild) { head.insertBefore(style, head.firstChild); } else { head.appendChild(style); } } else { head.appendChild(style); } if (style.styleSheet) { style.styleSheet.cssText = css; } else { style.appendChild(document.createTextNode(css)); } }; // Enhanced v1.js mobile-responsive styles with modern animations var mobilePopupStyles = "\n/* Modern popup animation keyframes - gentler fade */\n@keyframes numi-popup-fade-in {\n 0% {\n opacity: 0;\n transform: translate(-50%, -50%) scale(0.95);\n }\n 100% {\n opacity: 1;\n transform: translate(-50%, -50%) scale(1);\n }\n}\n\n@keyframes numi-popup-fade-out {\n 0% {\n opacity: 1;\n transform: translate(-50%, -50%) scale(1);\n }\n 100% {\n opacity: 0;\n transform: translate(-50%, -50%) scale(0.95);\n }\n}\n\n@keyframes numi-backdrop-fade-in {\n 0% {\n opacity: 0;\n backdrop-filter: blur(0px);\n }\n 100% {\n opacity: 1;\n backdrop-filter: blur(4px);\n }\n}\n\n@keyframes numi-backdrop-fade-out {\n 0% {\n opacity: 1;\n backdrop-filter: blur(4px);\n }\n 100% {\n opacity: 0;\n backdrop-filter: blur(0px);\n }\n}\n\n/* Modern popup container styles */\n.numi-embed-popup {\n animation: numi-backdrop-fade-in 0.3s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n}\n\n.numi-embed-popup.closing {\n animation: numi-backdrop-fade-out 0.25s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n}\n\n.numi-embed-iframe-container {\n animation: numi-popup-fade-in 0.3s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards;\n box-shadow: \n 0 25px 50px -12px rgba(0, 0, 0, 0.25),\n 0 10px 25px -5px rgba(0, 0, 0, 0.1);\n}\n\n.numi-embed-iframe-container.closing {\n animation: numi-popup-fade-out 0.25s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n}\n\n@media(max-width: 480px) {\n .numi-embed-popup .numi-embed-iframe-container {\n max-width: 100vw !important;\n width: 100% !important;\n height: calc(100vh - 40px) !important;\n margin-top: 40px !important;\n min-width: unset !important;\n min-height: unset !important;\n animation: numi-popup-fade-in 0.35s cubic-bezier(0.4, 0, 0.2, 1) forwards;\n }\n \n .numi-embed-popup .numi-embed-iframe-container iframe {\n border-radius: 0 !important;\n }\n \n .numi-embed-popup .numi-embed-popup-close-icon {\n color: #fff !important;\n position: absolute !important;\n top: -36px !important;\n right: 16px !important;\n left: unset !important;\n width: 24px !important;\n height: 24px !important;\n cursor: pointer;\n background: #171717 !important;\n border-radius: 50% !important;\n padding: 6px 6px 6px 6px !important;\n box-sizing: content-box !important;\n z-index: 10000000000002 !important;\n box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15) !important;\n }\n}\n\n/* Enhanced loading animation from v1.js */\n@keyframes numi-spin {\n 0% { transform: rotate(0deg); }\n 100% { transform: rotate(360deg); }\n}\n\n.numi-embed-loading {\n border: 4px solid rgba(255, 255, 255, 0.2);\n border-left: 4px solid #ffffff;\n border-radius: 50%;\n width: 40px;\n height: 40px;\n animation: numi-spin 1s linear infinite;\n}\n"; // Inject enhanced v1.js styles styleInject(mobilePopupStyles); // Popup sizing function - exact replica of v1.js sizing logic var getPopupSize$1 = function (size) { if (size === void 0) { size = 'medium'; } if (typeof window === 'undefined') { return { width: '900px', height: '700px' }; } if (size === 'medium') { // v1.js medium size logic (lines 608-615) return { width: window.innerWidth < 1200 ? '80vw' : '900px', height: '700px' }; } else if (size === 'small') { // v1.js small size logic (lines 617-628) var width = window.innerWidth < 600 ? '80vw' : window.innerWidth < 900 ? '60vw' : '560px'; return { width: width, height: 'calc(100% - 80px)' }; } else { // v1.js large size logic (lines 630-633) - default return { width: 'calc(100% - 160px)', height: 'calc(100% - 80px)' }; } }; // Enhanced loading component with v1.js styling var Loading$2 = function () { return (React.createElement("div", { className: "numi-embed-loading" })); }; // Portal component var Portal$2 = function (_a) { var children = _a.children; if (typeof document === 'undefined') { return null; } return ReactDOM.createPortal(children, document.body); }; // Enhanced close button with v1.js beautiful styling and proper z-index var CloseButton$2 = function (_a) { var onClick = _a.onClick; return (React.createElement("button", { onClick: function (e) { e.stopPropagation(); onClick(); }, className: "numi-embed-popup-close-icon", style: { position: "absolute", width: 24, height: 24, textAlign: "center", cursor: "pointer", transition: "all 0.3s ease-in-out", textDecoration: "none", color: "#fff", top: -12, right: -12, background: "#171717", borderRadius: "50%", padding: "6px 6px 6px 6px", border: 0, boxSizing: "content-box", zIndex: 10000000000001, // Higher than popup container boxShadow: "0 2px 8px rgba(0, 0, 0, 0.15)" }, onMouseEnter: function (e) { e.currentTarget.style.transform = "scale(1.1)"; e.currentTarget.style.background = "#000"; }, onMouseLeave: function (e) { e.currentTarget.style.transform = "scale(1)"; e.currentTarget.style.background = "#171717"; } }, React.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: "2", style: { width: "100%", height: "100%" } }, React.createElement("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M6 18L18 6M6 6l12 12" })))); }; // Popup container component with modern animations var PopupContainer = function (_a) { var children = _a.children, isOpen = _a.isOpen; _a.isOpenAnimate; var isClosing = _a.isClosing, onClose = _a.onClose, width = _a.width, height = _a.height, size = _a.size; var dynamicSize = getPopupSize$1(size); return (React.createElement("div", { onClick: onClose, className: "numi-embed-popup ".concat(isClosing ? 'closing' : ''), style: { position: "fixed", top: 0, left: 0, width: "100%", height: "100%", background: "rgba(0, 0, 0, 0.4)", backdropFilter: "blur(4px)", WebkitBackdropFilter: "blur(4px)", // Safari support zIndex: 10000000000000, boxSizing: "border-box", display: isOpen ? "flex" : "none", justifyContent: "center", alignItems: "center" } }, React.createElement("div", { className: "numi-embed-iframe-container ".concat(isClosing ? 'closing' : ''), style: { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", minWidth: 360, minHeight: 360, width: width || dynamicSize.width, height: height || dynamicSize.height, maxWidth: "calc(100vw - 40px)", maxHeight: "calc(100vh - 40px)", borderRadius: "16px", overflow: "visible", // Allow close button to show outside backgroundColor: "#ffffff" }, onClick: function (e) { return e.stopPropagation(); } }, children))); }; // Popup content component var PopupContent = function (_a) { var _b; var offer = _a.offer, domainProp = _a.domain, inheritParameters = _a.inheritParameters, parameters = _a.parameters, customerProp = _a.customer, onClose = _a.onClose, // Convenience props for common events onComplete = _a.onComplete, onSuccess = _a.onSuccess, onError = _a.onError, // All events can be passed via events object _c = _a.events, // All events can be passed via events object eventsProp = _c === void 0 ? {} : _c; var _d = React.useState(true), loading = _d[0], setLoading = _d[1]; var journey = useJourneyOptional(); var embedKey = React.useId(); var domain = domainProp !== null && domainProp !== void 0 ? domainProp : (_b = journey === null || journey === void 0 ? void 0 : journey.options) === null || _b === void 0 ? void 0 : _b.domain; var customer = customerProp !== null && customerProp !== void 0 ? customerProp : journey === null || journey === void 0 ? void 0 : journey.customer; var embed = useNumiFrame({ offerId: offer, domain: domain, inheritParameters: inheritParameters, parameters: parameters, customer: customer }); // Merge individual props with events object (individual props take precedence) var mergedCallbacks = React.useMemo(function () { return (__assign(__assign(__assign(__assign({}, eventsProp), (onComplete && { onComplete: onComplete })), (onSuccess && { onSuccess: onSuccess })), (onError && { onError: onError }))); }, [eventsProp, onComplete, onSuccess, onError]); // Create event handlers that emit to Journey and call user callbacks var events = useEmbedEvents(journey, mergedCallbacks); useNumiEvents(embed && embed.embedId ? embed : null, events); // Register user callbacks with Journey so parent listeners also receive them React.useEffect(function () { if (!journey) return; var unregister = journey.registerEmbedEvents(embedKey, mergedCallbacks); return unregister; }, [journey, embedKey, mergedCallbacks]); return (React.createElement(React.Fragment, null, !loading && React.createElement(CloseButton$2, { onClick: onClose }), embed && embed.embedId && (React.createElement("iframe", { src: embed.iframeUrl, allow: "microphone; camera; geolocation; payment;", title: "Embedded Form", className: "numi-embed-popup-iframe", style: { width: "100%", height: "100%", border: "none", borderRadius: "16px", opacity: !loading ? 1 : 0, transition: "opacity 0.3s cubic-bezier(0.4, 0, 0.2, 1)", display: "block" }, onLoad: function () { return setLoading(false); } })), loading && (React.createElement("div", { style: { position: "fixed", top: 0, left: 0, width: "100%", height: "100%", display: "flex", justifyContent: "center", alignItems: "center" } }, React.createElement(Loading$2, null))))); }; // Main popup component with enhanced animations var OfferPopupEmbed = function (_a) { var _isOpen = _a.isOpen, _onClose = _a.onClose, width = _a.width, height = _a.height, _b = _a.size, size = _b === void 0 ? 'medium' : _b, // Default to medium like v1.js props = __rest(_a, ["isOpen", "onClose", "width", "height", "size"]); var _c = React.useState(false), isOpen = _c[0], setIsOpen = _c[1]; var _d = React.useState(false), isClosing = _d[0], setIsClosing = _d[1]; React.useEffect(function () { if (_isOpen) { setIsClosing(false); setTimeout(function () { return setIsOpen(true); }, 10); } else { setIsOpen(false); setIsClosing(false); } }, [_isOpen]); var onClose = React.useCallback(function () { if (!isOpen || isClosing) return; setIsClosing(true); setIsOpen(false); // Wait for closing animation to complete setTimeout(function () { setIsClosing(false); _onClose(); }, 300); // Slightly longer than animation duration }, [isOpen, isClosing, _onClose]); return (React.createElement(Portal$2, null, React.createElement(PopupContainer, { isOpen: _isOpen, isOpenAnimate: isOpen, isClosing: isClosing, onClose: onClose, width: width, height: height, size: size }, _isOpen && React.createElement(PopupContent, __assign({ onClose: onClose, isOpen: _isOpen }, props))))); }; // Backward-compat export name var NumiPopupEmbed = OfferPopupEmbed; var PLANDALF_HOST = undefined; // Use host from config instead /** @deprecated Use NumiStandardCheckout, NumiPopupEmbed, NumiFullScreenEmbed instead */ var useCheckout = function (offer, config) { var _a = React.useState(false), isOpen = _a[0], setIsOpen = _a[1]; var _b = React.useState([]), items = _b[0], setItems = _b[1]; var host = ((config === null || config === void 0 ? void 0 : config.host) || PLANDALF_HOST || 'https://admin.plandalf.dev').replace(/\/$/, ''); var getCheckoutUrl = React.useCallback(function (checkoutItems, redirectUrl) { var params = new URLSearchParams(); checkoutItems === null || checkoutItems === void 0 ? void 0 : checkoutItems.forEach(function (item, index) { params.append("items[".concat(index, "][lookup_key]"), item.lookup_key); params.append("items[".concat(index, "][quantity]"), item.quantity.toString()); }); if (redirectUrl) { params.append('redirect_url', redirectUrl); } return "".concat(host, "/o/").concat(offer, "?").concat(params.toString()); }, [host, offer]); var checkout = React.useCallback(function (items) { if (items) { setItems(items); } if ((config === null || config === void 0 ? void 0 : config.redirect) && config.redirectUrl) { window.location.href = getCheckoutUrl(items, config.redirectUrl); } else { setIsOpen(true); } }, []); var close = React.useCallback(function () { setIsOpen(false); }, []); var ModalComponent = function () { return (React.createElement(NumiPopupEmbed, { isOpen: isOpen, onClose: close, offer: offer, domain: host, parameters: { items: items // Pass items as parameters if needed } })); }; return { checkout: checkout, isOpen: isOpen, close: close, items: items, Modal: ModalComponent, }; }; // Legacy function for backward compatibility var NumiStandardCheckout = function (_a) { _a.items; var domain = _a.domain, offer = _a.offer, redirectUrl = _a.redirectUrl; var url = "".concat(domain || 'https://checkout.plandalf.dev', "/o/").concat(offer, "?redirect_url=").concat(redirectUrl); window.open(url, '_blank'); }; // Offer standard embed component var OfferStandardEmbed = function (_a) { var _b; var offer = _a.offer, _c = _a.domain, domainProp = _c === void 0 ? undefined : _c, _d = _a.inheritParameters, inheritParameters = _d === void 0 ? false : _d, _e = _a.parameters, parameters = _e === void 0 ? {} : _e, customerProp = _a.customer, _f = _a.dynamicResize, dynamicResize = _f === void 0 ? true : _f, // Convenience props for common events onComplete = _a.onComplete, onSuccess = _a.onSuccess, onError = _a.onError, // All events can be passed via events object _g = _a.events, // All events can be passed via events object eventsProp = _g === void 0 ? {} : _g; var _h = React.useState(true), loading = _h[0], setLoading = _h[1]; var journey = useJourneyOptional(); var embedKey = React.useId(); var domain = domainProp !== null && domainProp !== void 0 ? domainProp : (_b = journey === null || journey === void 0 ? void 0 : journey.options) === null || _b === void 0 ? void 0 : _b.domain; var customer = customerProp !== null && customerProp !== void 0 ? customerProp : journey === null || journey === void 0 ? void 0 : journey.customer; var embed = useNumiFrame({ offerId: offer, domain: domain, inheritParameters: inheritParameters, parameters: parameters, customer: customer, dynamicResize: dynamicResize }); // Merge individual props with events object (individual props take precedence) var mergedCallbacks = React.useMemo(function () { return (__assign(__assign(__assign(__assign({}, eventsProp), (onComplete && { onComplete: onComplete })), (onSuccess && { onSuccess: onSuccess })), (onError && { onError: onError }))); }, [eventsProp, onComplete, onSuccess, onError]); // Create event handlers that emit to Journey and call user callbacks var events = useEmbedEvents(journey, mergedCallbacks); useNumiEvents(embed, events); // Register user callbacks with Journey so non-builtin events also fan out React.useEffect(function () { if (!journey) return; var unregister = journey.registerEmbedEvents(embedKey, mergedCallbacks); return unregister; }, [journey, embedKey, mergedCallbacks]); var _j = React.useState(), height = _j[0], setHeight = _j[1]; // Memoize the form resized handler and options var handleFormResized = React.useCallback(function (data) { var newHeight = data.size; if (typeof newHeight === "number") { setHeight(newHeight); } }, []); var formResizedOptions = React.useMemo(function () { return ({ disabled: !dynamicResize }); }, [dynamicResize]); useMessageListener(embed, "form_resized", handleFormResized, formResizedOptions); if (!(embed === null || embed === void 0 ? void 0 : embed.iframeUrl)) { return React.createElement("div", null, "Loading..."); } var iframeProps = { src: embed.iframeUrl, allow: "microphone; camera; geolocation; payment;", title: "Embedded Form", onLoad: function () { return setLoading(false); }, style: { width: !loading ? "100%" : 0, height: height ? "".concat(height, "px") : "700px", opacity: !loading ? 1 : 0, borderRadius: 10, border: 0, minHeight: 256 } }; return (React.createElement("div", { className: "numi-standard-embed" }, React.createElement("iframe", __assign({}, iframeProps)))); }; // Backward-compat export var NumiStandardEmbed = OfferStandardEmbed; var getCheckoutUrl = function (_a) { var offer = _a.offer, checkoutItems = _a.checkoutItems, hostParam = _a.host, redirectUrl = _a.redirectUrl; var host = (hostParam || 'https://admin.plandalf.dev').replace(/\/$/, ''); var params = new URLSearchParams(); checkoutItems === null || checkoutItems === void 0 ? void 0 : checkoutItems.forEach(function (item, index) { params.append("items[".concat(index, "][lookup_key]"), item.lookup_key); params.append("items[".concat(index, "][quantity]"), item.quantity.toString()); }); if (redirectUrl) { params.append('redirect_url', redirectUrl); } return "".concat(host, "/o/").concat(offer, "?").concat(params.toString()); }; // Loading component var Loading$1 = function () { return (React.createElement("div", { style: { border: "solid 2px #aaa", borderBottomColor: "white", borderRadius: "50%", width: 32, height: 32, animation: "numi-embed-loading 1s infinite linear" } })); }; // Portal component var Portal$1 = function (_a) { var children = _a.children; if (typeof document === 'undefined') { return null; } return ReactDOM.createPortal(children, document.body); }; // Close button component var CloseButton$1 = function (_a) { var onClick = _a.onClick; return (React.createElement("button", { onClick: function (e) { e.stopPropagation(); onClick(); }, style: { position: 'absolute', right: '20px', top: '20px', width: 32,