UNPKG

@coin-voyage/paykit

Version:

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

147 lines (146 loc) 5.18 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { useIsClient } from "@coin-voyage/shared/hooks"; import { useQuery } from "@tanstack/react-query"; import { AnimatePresence } from "framer-motion"; import { useCallback, useEffect, useMemo, useRef } from "react"; import { usePaymentLifecycle } from "../../hooks/usePaymentLifecycle"; import { ResetContainer } from "../../styles"; 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 { paymentState, showModal, setOpen, setOnOpen, setOnClose } = usePayContext(); const { onPaymentStarted, onPaymentCompleted, onPaymentBounced } = props; const { payOrder: order, setPayId } = paymentState; const { children, closeOnSuccess, resetOnSuccess } = props; const hasDepositParams = "toAddress" in props; const hasPayId = "payId" in props; const depositParams = useMemo(() => { if (!hasDepositParams) return null; return { intent: { asset: { address: props.toToken, chain_id: props.toChain, }, receiving_address: props.toAddress, amount: { token_amount: props.toAmount, }, }, metadata: props.metadata, }; }, [props]); const payId = hasPayId ? props.payId : null; usePaymentLifecycle(order, { onPaymentStarted, onPaymentCompleted, onPaymentBounced, }); const metadataKey = useMemo(() => JSON.stringify(depositParams?.metadata ?? {}), [depositParams?.metadata]); // Preload payOrder useQuery({ queryKey: [ "payOrder", depositParams?.intent.asset?.address, depositParams?.intent.asset?.chain_id, depositParams?.intent.receiving_address, depositParams?.intent.amount.token_amount, metadataKey, ], enabled: hasDepositParams, staleTime: Infinity, queryFn: async () => { if (!depositParams) return null; await paymentState.createDepositPayOrder(depositParams, (msg) => props.onPaymentCreationError?.({ type: "payorder_creation_error", errorMessage: msg, })); return null; }, }); // Load payment by ID when using payId useEffect(() => { if (!payId) return; setPayId(payId); }, [payId, setPayId]); // Register open/close handlers useEffect(() => { setOnOpen(props.onOpen); setOnClose(props.onClose); return () => { setOnOpen(undefined); setOnClose(undefined); }; }, [props.onOpen, props.onClose, setOnOpen, setOnClose]); const show = useCallback(() => { if (!order) return; showModal({ closeOnSuccess, resetOnSuccess, }); }, [order, closeOnSuccess, resetOnSuccess, showModal]); const hide = useCallback(() => { setOpen(false); }, [setOpen]); // Auto-open modal const hasAutoOpened = useRef(false); useEffect(() => { if (!props.defaultOpen) return; if (hasAutoOpened.current) return; if (!order) return; show(); hasAutoOpened.current = true; }, [order, props.defaultOpen, show]); return 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 }) })); }