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,051 lines (1,036 loc) 52 kB
import React, { useState, useEffect, useMemo, useCallback, useRef } from 'react'; import ReactDOM from '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; var BASE_URL = ((_a = import.meta.env) === 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 = useState(), searchParams = _b[0], setSearchParams = _b[1]; var _c = useState(), embedId = _c[0], setEmbedId = _c[1]; 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); } } console.log('IFRAME URL', iframeUrl.toString()); 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 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 = useState(), searchParams = _b[0], setSearchParams = _b[1]; var _c = useState(), embedId = _c[0], setEmbedId = _c[1]; 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 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); useEffect(function () { if (embed && embed.embedId && enabled) { var listener_1 = function (event) { var _a, _b, _c, _d; // Only log if this event is relevant to our listener if (((_a = event.data) === null || _a === void 0 ? void 0 : _a.type) === eventName) { console.log("Received message event: ".concat((_b = event.data) === null || _b === void 0 ? void 0 : _b.type, ", source: ").concat((_c = event.data) === null || _c === void 0 ? void 0 : _c.source, ", embedId: ").concat((_d = event.data) === null || _d === void 0 ? void 0 : _d.embedId), { data: event.data, expectedEmbedId: embed.embedId, actualOrigin: event.origin }); try { // Check for plandalf source and correct event type var sourceMatch = event.data.source === 'plandalf'; var typeMatch = event.data.type === eventName; // Embed ID is required to ensure events are routed to the correct component // Temporarily make it optional if server doesn't send it var embedIdMatch = !event.data.embedId || event.data.embedId === embed.embedId; if (sourceMatch && typeMatch && embedIdMatch) { console.log("\u2705 Matched event: ".concat(eventName), event.data); fn(event.data); } else { console.log("\u274C Event mismatch for ".concat(eventName, ":"), { sourceMatch: sourceMatch, typeMatch: typeMatch, embedIdMatch: embedIdMatch, expectedSource: 'plandalf', actualSource: event.data.source, expectedType: eventName, actualType: event.data.type, expectedEmbedId: embed.embedId, actualEmbedId: event.data.embedId }); } } catch (err) { console.error("Error in message listener:", err); } } }; console.log("\uD83C\uDFA7 Mounting listener for event: ".concat(eventName)); window.addEventListener("message", listener_1); return function () { console.log("\uD83D\uDD07 Unmounting listener for event: ".concat(eventName), { embed: embed, eventName: eventName, fn: fn, enabled: enabled }); 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 = useCallback(function (data) { if (events.onInit) { events.onInit(data.checkoutId); } }, [events.onInit]); var handlePageChange = useCallback(function (data) { if (events.onPageChange) { events.onPageChange(data.checkoutId, data.pageId); } }, [events.onPageChange]); var handlePaymentInit = useCallback(function (data) { if (events.onPaymentInit) { events.onPaymentInit(data.checkoutId); } }, [events.onPaymentInit]); var handleSubmit = useCallback(function (data) { if (events.onSubmit) { events.onSubmit(data.checkoutId); } }, [events.onSubmit]); var handleSuccess = useCallback(function (data) { if (events.onSuccess) { events.onSuccess(data); } }, [events.onSuccess]); var handleComplete = useCallback(function (data) { if (events.onComplete) { events.onComplete(data); } }, [events.onComplete]); var handleCancel = useCallback(function (data) { if (events.onCancel) { events.onCancel(data); } }, [events.onCancel]); var handleClosed = useCallback(function (data) { if (events.onClosed) { events.onClosed(data); } }, [events.onClosed]); // Enhanced interaction event handlers var handleLineItemChange = useCallback(function (data) { if (events.onLineItemChange) { events.onLineItemChange(data); } }, [events.onLineItemChange]); var handleResize = useCallback(function (data) { if (events.onResize) { events.onResize(data); } }, [events.onResize]); var handleError = useCallback(function (data) { if (events.onError) { events.onError(data); } }, [events.onError]); // Memoize the options objects to prevent unnecessary re-renders var initOptions = useMemo(function () { return ({ disabled: !events.onInit }); }, [events.onInit]); var pageChangeOptions = useMemo(function () { return ({ disabled: !events.onPageChange }); }, [events.onPageChange]); var paymentInitOptions = useMemo(function () { return ({ disabled: !events.onPaymentInit }); }, [events.onPaymentInit]); var submitOptions = useMemo(function () { return ({ disabled: !events.onSubmit }); }, [events.onSubmit]); var successOptions = useMemo(function () { return ({ disabled: !events.onSuccess }); }, [events.onSuccess]); var completeOptions = useMemo(function () { return ({ disabled: !events.onComplete }); }, [events.onComplete]); var cancelOptions = useMemo(function () { return ({ disabled: !events.onCancel }); }, [events.onCancel]); var closedOptions = useMemo(function () { return ({ disabled: !events.onClosed }); }, [events.onClosed]); var lineItemChangeOptions = useMemo(function () { return ({ disabled: !events.onLineItemChange }); }, [events.onLineItemChange]); var resizeOptions = useMemo(function () { return ({ disabled: !events.onResize }); }, [events.onResize]); var errorOptions = 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); // 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 = 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); } }, []); useEffect(function () { window.addEventListener('message', handleMessage); return function () { window.removeEventListener('message', handleMessage); }; }, [handleMessage]); }; // 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 offerId = _a.offerId, domain = _a.domain, inheritParameters = _a.inheritParameters, parameters = _a.parameters, customer = _a.customer, onClose = _a.onClose, onInit = _a.onInit, onPageChange = _a.onPageChange, onPaymentInit = _a.onPaymentInit, onSubmit = _a.onSubmit, onSuccess = _a.onSuccess, onComplete = _a.onComplete, onCancel = _a.onCancel, onClosed = _a.onClosed, onLineItemChange = _a.onLineItemChange, onResize = _a.onResize, onError = _a.onError; var _b = useState(true), loading = _b[0], setLoading = _b[1]; var embed = useNumiFrame({ offerId: offerId, domain: domain, inheritParameters: inheritParameters, parameters: parameters, customer: customer }); // Memoize the comprehensive events object to prevent unnecessary re-renders var events = useMemo(function () { return ({ onInit: onInit, onPageChange: onPageChange, onPaymentInit: onPaymentInit, onSubmit: onSubmit, onSuccess: onSuccess, onComplete: onComplete, onCancel: onCancel, onClosed: onClosed, onLineItemChange: onLineItemChange, onResize: onResize, onError: onError }); }, [onInit, onPageChange, onPaymentInit, onSubmit, onSuccess, onComplete, onCancel, onClosed, onLineItemChange, onResize, onError]); useNumiEvents(embed && embed.embedId ? embed : null, events); 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; payments;", 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 = useState(false), isOpen = _c[0], setIsOpen = _c[1]; var _d = useState(false), isClosing = _d[0], setIsClosing = _d[1]; useEffect(function () { if (_isOpen) { setIsClosing(false); setTimeout(function () { return setIsOpen(true); }, 10); } else { setIsOpen(false); setIsClosing(false); } }, [_isOpen]); var onClose = 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 = useState(false), isOpen = _a[0], setIsOpen = _a[1]; var _b = 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 = 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 = 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 = useCallback(function () { setIsOpen(false); }, []); var ModalComponent = function () { return (React.createElement(NumiPopupEmbed, { isOpen: isOpen, onClose: close, offerId: 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 offer = _a.offer, onComplete = _a.onComplete, _b = _a.domain, domain = _b === void 0 ? undefined : _b, _c = _a.inheritParameters, inheritParameters = _c === void 0 ? false : _c, _d = _a.parameters, parameters = _d === void 0 ? {} : _d, customer = _a.customer, _e = _a.dynamicResize, dynamicResize = _e === void 0 ? true : _e, onInit = _a.onInit, onPageChange = _a.onPageChange, onSubmit = _a.onSubmit, onSuccess = _a.onSuccess, offerId = _a.offerId; var _f = useState(true), loading = _f[0], setLoading = _f[1]; var embed = useNumiFrame({ offerId: offerId || offer, domain: domain, inheritParameters: inheritParameters, parameters: parameters, customer: customer, dynamicResize: dynamicResize }); // Memoize the events object to prevent unnecessary re-renders var events = useMemo(function () { return ({ onInit: onInit, onPageChange: onPageChange, onSubmit: onSubmit, onSuccess: onSuccess, onComplete: onComplete }); }, [onInit, onPageChange, onSubmit, onSuccess, onComplete]); useNumiEvents(embed, events); var _g = useState(), height = _g[0], setHeight = _g[1]; // Memoize the form resized handler and options var handleFormResized = useCallback(function (data) { var newHeight = data.size; if (typeof newHeight === "number") { setHeight(newHeight); } }, []); var formResizedOptions = 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; payments;", 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, height: 32, background: 'rgba(0, 0, 0, 0.7)', color: 'white', border: 'none', borderRadius: '50%', cursor: 'pointer', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1, fontSize: '18px' } }, "\u00D7")); }; // New fullscreen embed component var OfferFullScreenEmbed = function (_a) { var isOpen = _a.isOpen, onClose = _a.onClose, offerId = _a.offerId, domain = _a.domain, inheritParameters = _a.inheritParameters, parameters = _a.parameters, customer = _a.customer, onInit = _a.onInit, onPageChange = _a.onPageChange, onSubmit = _a.onSubmit, onSuccess = _a.onSuccess, onComplete = _a.onComplete; var _b = useState(true), loading = _b[0], setLoading = _b[1]; var embed = useNumiFrame({ offerId: offerId, domain: domain, inheritParameters: inheritParameters, parameters: parameters, customer: customer }); // Memoize the events object to prevent unnecessary re-renders var events = useMemo(function () { return ({ onInit: onInit, onPageChange: onPageChange, onSubmit: onSubmit, onSuccess: onSuccess, onComplete: onComplete }); }, [onInit, onPageChange, onSubmit, onSuccess, onComplete]); useNumiEvents(embed && embed.embedId ? embed : null, events); useEffect(function () { if (typeof document !== 'undefined') { if (isOpen) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = 'unset'; } } return function () { if (typeof document !== 'undefined') { document.body.style.overflow = 'unset'; } }; }, [isOpen]); if (!isOpen) return null; return (React.createElement(Portal$1, null, React.createElement("div", { style: { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0, 0, 0, 0.9)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1e13, }, onClick: onClose }, React.createElement("div", { style: { backgroundColor: 'white', width: '100%', height: '100%', position: 'relative', }, onClick: function (e) { return e.stopPropagation(); } }, React.createElement(CloseButton$1, { onClick: onClose }), embed && embed.embedId && (React.createElement("iframe", { src: embed.iframeUrl, allow: "microphone; camera; geolocation; payments;", title: "Embedded Form", style: { width: '100%', height: '100%', border: 'none', opacity: !loading ? 1 : 0 }, onLoad: function () { return setLoading(false); } })), loading && (React.createElement("div", { style: { position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', display: 'flex', justifyContent: 'center', alignItems: 'center', backgroundColor: 'white' } }, React.createElement(Loading$1, null))))))); }; // Backward-compat export name var NumiFullScreenEmbed = OfferFullScreenEmbed; // Legacy component for backward compatibility var LegacyNumiFullScreenEmbed = function (_a) { var show = _a.show, onClose = _a.onClose, items = _a.items, domain = _a.domain, offer = _a.offer, onSuccess = _a.onSuccess, onInit = _a.onInit, onCancel = _a.onCancel, onPageChange = _a.onPageChange, onCheckoutInit = _a.onCheckoutInit, onCheckoutResized = _a.onCheckoutResized; console.log('show', show, domain); var url = useMemo(function () { return getCheckoutUrl({ offer: offer, checkoutItems: items, host: domain }); }, [offer, items, domain]); var modalRef = useRef(null); useIframeMessage({ onSuccess: onSuccess, onInit: onInit, onCancel: onCancel, onPageChange: onPageChange, onCheckoutInit: onCheckoutInit, onCheckoutResized: onCheckoutResized, }); useEffect(function () { var handleClickOutside = function (event) { if (modalRef.current && !modalRef.current.contains(event.target)) { onClose(); } }; if (show && typeof document !== 'undefined') { document.addEventListener('mousedown', handleClickOutside); document.body.style.overflow = 'hidden'; } return function () { if (typeof document !== 'undefined') { document.removeEventListener('mousedown', handleClickOutside); document.body.style.overflow = 'unset'; } }; }, [show, onClose]); if (!show) return null; return (React.createElement("div", { style: { position: 'fixed', top: 0, left: 0, right: 0, bottom: 0, backgroundColor: 'rgba(0, 0, 0, 0.5)', display: 'flex', alignItems: 'center', justifyContent: 'center', zIndex: 1000, } }, React.createElement("div", { ref: modalRef, style: { backgroundColor: 'white', width: '100%', height: '100%', position: 'relative', } }, React.createElement("button", { onClick: onClose, style: { position: 'absolute', right: '10px', top: '10px', border: 'none', background: 'none', fontSize: '24px', cursor: 'pointer', zIndex: 1, } }, "\u00D7"), React.createElement("iframe", { src: url, style: { width: '100%', height: '100%', border: 'none', }, title: "Checkout" })))); }; var BillingPortalEmbed = function (_a) { var domain = _a.domain, _b = _a.inheritParameters, inheritParameters = _b === void 0 ? false : _b, _c = _a.parameters, parameters = _c === void 0 ? {} : _c, customerToken = _a.customerToken, returnUrl = _a.returnUrl, _d = _a.dynamicResize, dynamicResize = _d === void 0 ? true : _d, onInit = _a.onInit, onClosed = _a.onClosed, onResize = _a.onResize, onError = _a.onError; var _e = useState(true), loading = _e[0], setLoading = _e[1]; var _f = useState(), height = _f[0], setHeight = _f[1]; var embed = useBillingPortalFrame({ domain: domain, inheritParameters: inheritParameters, parameters: parameters, customerToken: customerToken, returnUrl: returnUrl, dynamicResize: dynamicResize }); var handleFormResized = useCallback(function (data) { var newHeight = data.size; if (typeof newHeight === 'number') { setHeight(newHeight); } }, []); var formResizedOptions = useMemo(function () { return ({ disabled: !dynamicResize }); }, [dynamicResize]); // Core events useMessageListener(embed, 'on_init', function () { return onInit === null || onInit === void 0 ? void 0 : onInit({}); }); // Support both potential closed event names useMessageListener(embed, 'portal_closed', function (d) { return onClosed === null || onClosed === void 0 ? void 0 : onClosed(d); }); useMessageListener(embed, 'checkout_closed', function (d) { return onClosed === null || onClosed === void 0 ? void 0 : onClosed(d); }); // Resize events useMessageListener(embed, 'form_resized', handleFormResized, formResizedOptions); useMessageListener(embed, 'form_resize', function (d) { return onResize === null || onResize === void 0 ? void 0 : onResize(d); }); // Error useMessageListener(embed, 'checkout_error', function (d) { return onError === null || onError === void 0 ? void 0 : onError(d); }); 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; payments;', title: 'Billing Portal', 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-billing-portal-embed" }, React.createElement("iframe", __assign({}, iframeProps)))); }; // Basic loading indicator var Loading = function () { return React.createElement("div", { className: "numi-embed-loading" }); }; var Portal = function (_a) { var children = _a.children; if (typeof document === 'undefined') return null; return ReactDOM.createPortal(children, document.body); }; var CloseButton = 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', color: '#fff', top: -12, right: -12, background: '#171717', borderRadius: '50%', padding: '6px', border: 0, boxSizing: 'content-box', zIndex: 10000000000001, boxShadow: '0 2px 8px rgba(0, 0, 0, 0.15)' } }, 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" })))); }; var getPopupSize = function (size) { if (size === void 0) { size = 'medium'; } if (typeof window === 'undefined') { return { width: '900px', height: '700px' }; } if (size === 'medium') { return { width: window.innerWidth < 1200 ? '80vw' : '900px', height: '700px' }; } else if (size === 'small') { var width = window.innerWidth < 600 ? '80vw' : window.innerWidth < 900 ? '60vw' : '560px'; return { width: width, height: 'calc(100% - 80px)' }; } else { return { width: 'calc(100% - 160px)', height: 'calc(100% - 80px)' }; } }; var BillingPortalPopup = function (_a) { var _isOpen = _a.isOpen, _onClose = _a.onClose, width = _a.width, height = _a.height, _b = _a.size, size = _b === void 0 ? 'medium' : _b, domain = _a.domain, inheritParameters = _a.inheritParameters, parameters = _a.parameters, customerToken = _a.customerToken, returnUrl = _a.returnUrl, onInit = _a.onInit, onClosed = _a.onClosed, onResize = _a.onResize, onError = _a.onError; var _c = useState(false), isOpen = _c[0], setIsOpen = _c[1]; var _d = useState(false), isClosing = _d[0], setIsClosing = _d[1]; var _e = useState(true), loading = _e[0], setLoading = _e[1]; useEffect(function () { if (_isOpen) { setIsClosing(false); setTimeout(function () { return setIsOpen(true); }, 10); } else { setIsOpen(false); setIsClosing(false); } }, [_isOpen]); var onClose = useCallback(function () { if (!isOpen || isClosing) return; setIsClosing(true); setIsOpen(false); setTimeout(function () { setIsClosing(false); _onClose(); }, 300); }, [isOpen, isClosing, _onClose]); var embed = useBillingPortalFrame({ domain: domain, inheritParameters: inheritParameters, parameters: parameters, customerToken: customerToken, returnUrl: returnUrl }); // Events useMessageListener(embed, 'on_init', function () { return onInit === null || onInit === void 0 ? void 0 : onInit({}); }); useMessageListener(embed, 'portal_closed', function (d) { return onClosed === null || onClosed === void 0 ? void 0 : onClosed(d); }); useMessageListener(embed, 'checkout_closed', function (d) { return onClosed === null || onClosed === void 0 ? void 0 : onClosed(d); }); useMessageListener(embed, 'form_resize', function (d) { return onResize === null || onResize === void 0 ? void 0 : onResize(d); }); useMessageListener(embed, 'checkout_error', function (d) { return onError === null || onError === void 0 ? void 0 : onError(d); }); if (!_isOpen) return null; var dynamicSize = getPopupSize(size); return (React.createElement(Portal, null, 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)', 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%)', m