UNPKG

@coin-voyage/paykit

Version:

Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.

227 lines (226 loc) 7.81 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { useIsClient } from "@coin-voyage/shared/hooks"; import { AnimatePresence } from "framer-motion"; import { useCallback, useEffect, useMemo, useRef } from "react"; import { usePaymentLifecycle } from "../../hooks/usePaymentLifecycle"; import { ResetContainer } from "../../styles"; import { ROUTE } from "../../types/routes"; import usePayContext from "../contexts/pay"; import ThemedButton, { ThemeContainer } from "../ui/ThemedButton"; import { SkeletonContainer, TextContainer } from "./styles"; export function PayButton(props) { const { intent, theme, mode, customTheme, style } = props; const context = usePayContext(); const isClient = useIsClient(); if (!isClient) { return _jsx(SkeletonContainer, {}); } return (_jsx(PayButtonCustom, { ...props, children: ({ show }) => (_jsx(ResetContainer, { "$useTheme": theme ?? context.theme, "$useMode": mode ?? context.mode, "$customTheme": customTheme ?? context.customTheme, children: _jsx(ThemeContainer, { disabled: props.disabled, onClick: show, children: _jsx(ThemedButton, { style: style, children: _jsx(ButtonInner, { label: intent ?? "Pay" }) }) }) })) })); } /** Like PayButton, but with custom styling. */ function PayButtonCustom(props) { const { options } = usePayContext(); usePayModalCallbacks(props.onOpen, props.onClose); const { order, show, hide } = usePayButtonController(props); usePaymentLifecycle(order, { onPaymentStarted: props.onPaymentStarted, onPaymentCompleted: props.onPaymentCompleted, onPaymentBounced: props.onPaymentBounced, }, { optimisticConfirmation: options?.optimisticConfirmation, }); useDefaultOpen(props.defaultOpen, show); return props.children({ show, hide }); } PayButtonCustom.displayName = "PayButton.Custom"; PayButton.Custom = PayButtonCustom; const contentVariants = { initial: { zIndex: 2, opacity: 0, x: "-100%", }, animate: { opacity: 1, x: 0.1, transition: { duration: 0.4, ease: [0.25, 1, 0.5, 1], }, }, exit: { zIndex: 1, opacity: 0, x: "-100%", pointerEvents: "none", position: "absolute", transition: { duration: 0.4, ease: [0.25, 1, 0.5, 1], }, }, }; function ButtonInner({ label }) { return (_jsx(AnimatePresence, { initial: false, children: _jsx(TextContainer, { initial: "initial", animate: "animate", exit: "exit", variants: contentVariants, style: { height: 40 }, children: label }) })); } function isDepositParams(props) { return "toAddress" in props; } function isPayIdProps(props) { return "payId" in props; } function useResolvedPaymentInput(props) { const hasDepositParams = isDepositParams(props); const hasPayId = isPayIdProps(props); const toToken = hasDepositParams ? props.toToken : undefined; const toChain = hasDepositParams ? props.toChain : undefined; const toAddress = hasDepositParams ? props.toAddress : undefined; const toAmount = hasDepositParams ? props.toAmount : undefined; const metadata = hasDepositParams ? props.metadata : undefined; const payId = hasPayId ? props.payId : null; const depositParams = useMemo(() => { if (!hasDepositParams) return null; return { intent: { asset: { address: toToken, chain_id: toChain, }, receiving_address: toAddress, amount: { token_amount: toAmount, }, }, metadata, }; }, [hasDepositParams, toToken, toChain, toAddress, toAmount, metadata]); if (payId) { return { kind: "payId", payId, depositParams: null, }; } return { kind: "deposit", payId: null, depositParams: depositParams, }; } function usePayModalCallbacks(onOpen, onClose) { const { setOnOpen, setOnClose } = usePayContext(); useEffect(() => { setOnOpen(onOpen); setOnClose(onClose); return () => { setOnOpen(undefined); setOnClose(undefined); }; }, [onClose, onOpen, setOnClose, setOnOpen]); } function useDefaultOpen(enabled, show) { const hasAutoOpened = useRef(false); useEffect(() => { if (!enabled) return; if (hasAutoOpened.current) return; hasAutoOpened.current = true; show(); }, [enabled, show]); } function useDepositPayOrderLoader(params) { const { paymentState } = usePayContext(); const { depositParams, onPaymentCreationError } = params; const handlePaymentCreationError = useCallback((msg) => { onPaymentCreationError?.({ type: "payorder_creation_error", errorMessage: msg, }); }, [onPaymentCreationError]); return useCallback(async () => { if (!depositParams) return undefined; return paymentState.createDepositPayOrder(depositParams, handlePaymentCreationError); }, [depositParams, handlePaymentCreationError, paymentState]); } function usePayButtonController(props) { const { paymentState, showModal, setOpen, setRoute, open, route, displayError } = usePayContext(); const { payOrder: order, setPayId } = paymentState; const { closeOnSuccess, resetOnSuccess, onPaymentCreationError } = props; const paymentInput = useResolvedPaymentInput(props); const queuedModalOptionsRef = useRef(null); const paymentKind = paymentInput.kind; const handlePaymentCreationError = useCallback((event) => { queuedModalOptionsRef.current = null; displayError(event.errorMessage); onPaymentCreationError?.(event); }, [displayError, onPaymentCreationError]); const loadDepositPayOrder = useDepositPayOrderLoader({ depositParams: paymentInput.depositParams, onPaymentCreationError: handlePaymentCreationError, }); const payId = paymentKind === "payId" ? paymentInput.payId : null; useEffect(() => { if (!payId) return; setPayId(payId); }, [payId, setPayId]); useEffect(() => { const queuedModalOptions = queuedModalOptionsRef.current; if (!queuedModalOptions || !order) return; queuedModalOptionsRef.current = null; showModal(queuedModalOptions); }, [order, showModal]); useEffect(() => { if (paymentKind !== "deposit") return; if (open || route !== ROUTE.PREPARING_PAYMENT) return; queuedModalOptionsRef.current = null; }, [open, paymentKind, route]); const show = useCallback(() => { const nextModalOptions = { closeOnSuccess, resetOnSuccess, }; if (order) { showModal(nextModalOptions); return; } queuedModalOptionsRef.current = nextModalOptions; if (paymentKind === "deposit") { displayError(null); setRoute(ROUTE.PREPARING_PAYMENT); setOpen(true); void loadDepositPayOrder(); return; } if (payId) { void setPayId(payId); return; } }, [ closeOnSuccess, displayError, loadDepositPayOrder, order, payId, paymentKind, resetOnSuccess, setOpen, setPayId, setRoute, showModal, ]); const hide = useCallback(() => { setOpen(false); }, [setOpen]); return { order, show, hide, }; }