@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,046 lines (1,030 loc) • 52.7 kB
JavaScript
'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;
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);
}
}
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 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, _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 = 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) {
if (events.onComplete) {
events.onComplete(data);
}
}, [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) {
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);
// 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]);
};
// 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 = React.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 = React.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 = 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, 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 = React.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 = React.useMemo(function () { return ({
onInit: onInit,
onPageChange: onPageChange,
onSubmit: onSubmit,
onSuccess: onSuccess,
onComplete: onComplete
}); }, [onInit, onPageChange, onSubmit, onSuccess, onComplete]);
useNumiEvents(embed, events);
var _g = React.useState(), height = _g[0], setHeight = _g[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; 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 = React.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 = React.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);
React.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 = React.useMemo(function () { return getCheckoutUrl({ offer: offer, checkoutItems: items, host: domain }); }, [offer, items, domain]);
var modalRef = React.useRef(null);
useIframeMessage({
onSuccess: onSuccess,
onInit: onInit,
onCancel: onCancel,
onPageChange: onPageChange,
onCheckoutInit: onCheckoutInit,
onCheckoutResized: onCheckoutResized,
});
React.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 = React.useState(true), loading = _e[0], setLoading = _e[1];
var _f = React.useState(), height = _f[0], setHeight = _f[1];
var embed = useBillingPortalFrame({
domain: domain,
inheritParameters: inheritParameters,
parameters: parameters,
customerToken: customerToken,
returnUrl: returnUrl,
dynamicResize: dynamicResize
});
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]);
// 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 = React.useState(false), isOpen = _c[0], setIsOpen = _c[1];
var _d = React.useState(false), isClosing = _d[0], setIsClosing = _d[1];
var _e = React.useState(true), loading = _e[0], setLoading = _e[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);
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',
alignI