@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,040 lines (1,026 loc) • 67.2 kB
JavaScript
import React, { useMemo, useState, useEffect, useCallback, createContext, useContext, useRef, useId } 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;
// Hook to create event handlers that emit to Journey and call user callbacks
function useEmbedEvents(journey, callbacks) {
return 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 = 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);
}
}
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;
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 = 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) {
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 = useCallback(function (data) {
if (events.onCancel) {
events.onCancel(data);
}
}, [events.onCancel]);
var handleClosed = 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 = 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);
// 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 = 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]);
};
var JourneyContext = createContext(null);
var useJourney = function () {
var ctx = useContext(JourneyContext);
if (!ctx)
throw new Error('useJourney must be used within a Journey');
return ctx;
};
var useJourneyOptional = function () { return 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 = useState(undefined), lastCheckoutId = _b[0], setLastCheckoutId = _b[1];
var journeyMetaRef = useRef({ id: id, type: type });
var registryRef = useRef(new Map());
// Core event handlers used by children embeds
var handleInit = useCallback(function (checkoutId) {
setLastCheckoutId(checkoutId);
}, []);
var handlePageChange = useCallback(function (_checkoutId, _pageId) {
// no-op for now; reserved for analytics or step tracking
}, []);
var handlePaymentInit = useCallback(function (checkoutId) {
setLastCheckoutId(checkoutId);
}, []);
var handleSubmit = useCallback(function (_checkoutId) {
// no-op; could mark a step as submitted
}, []);
var handleSuccess = 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 = 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 = useCallback(function (_data) {
// no-op for now
}, []);
var handleClosed = 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 = useCallback(function (_data) {
// no-op for now
}, []);
var handleResize = useCallback(function (_data) {
// no-op for now
}, []);
var handleError = useCallback(function (_error) {
// optional: surface to user or log
}, []);
var emit = 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 = useCallback(function (key, callbacks) {
registryRef.current.set(key, callbacks || {});
return function () {
registryRef.current.delete(key);
};
}, []);
var value = 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 = 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 = useState(true), loading = _d[0], setLoading = _d[1];
var journey = useJourneyOptional();
var embedKey = 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 = 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
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 = 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, 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 = useState(true), loading = _h[0], setLoading = _h[1];
var journey = useJourneyOptional();
var embedKey = 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 = 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
useEffect(function () {
if (!journey)
return;
var unregister = journey.registerEmbedEvents(embedKey, mergedCallbacks);
return unregister;
}, [journey, embedKey, mergedCallbacks]);
var _j = useState(), height = _j[0], setHeight = _j[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; 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,
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 fullsc