UNPKG

@coin-voyage/paykit

Version:

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

164 lines 6.64 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { assertNotNull, getChainName, PayOrderStatus } from "@coin-voyage/shared/common"; import { useQuery } from "@tanstack/react-query"; import { AnimatePresence } from "framer-motion"; import { useCallback, useEffect, useRef } from "react"; import useIsMounted from "../../hooks/useIsMounted"; 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 isMounted = useIsMounted(); if (!isMounted) { 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, showPayment, setOpen, setOnOpen, setOnClose } = usePayContext(); // Pre-load payment info in background. // Reload when any of the info changes. const depositParams = "toAddress" in props ? { destination_currency: { address: props.toToken ?? null, chain_id: props.toChain, }, receiving_address: props.toAddress, destination_amount: props.toAmount, metadata: props.metadata, } : null; const payId = "payId" in props ? props.payId : null; useQuery({ queryKey: ["payOrder", JSON.stringify({ depositParams })], queryFn: async () => { if (depositParams == null || !depositParams.receiving_address || !depositParams.destination_currency) return null; await paymentState.createDepositPayOrder(depositParams, (msg) => props.onPaymentCreationError?.({ type: "payorder_creation_error", errorMessage: msg })); return null; }, enabled: depositParams != null, }); useEffect(() => { if (payId != null) { paymentState.setPayId(payId); } }, [payId]); // Payment events: call these three event handlers. const { onPaymentStarted, onPaymentCompleted, onPaymentBounced } = props; const order = paymentState.payOrder; const isStarted = order?.status && [PayOrderStatus.AWAITING_CONFIRMATION, PayOrderStatus.EXECUTING_ORDER, PayOrderStatus.COMPLETED].includes(order.status); const isFinalized = order?.status === PayOrderStatus.COMPLETED || order?.status === PayOrderStatus.REFUNDED; const sentStart = useRef(false); useEffect(() => { if (sentStart.current || !order || !isStarted) return; sentStart.current = true; onPaymentStarted?.({ type: "payorder_confirming", payorder_id: order.id, chain_id: assertNotNull(order.source_currency?.chain_id), chain: getChainName(order.source_currency?.chain_id) ?? "Unknown", source_transaction_hash: order.deposit_tx_hash ?? "", }); }, [order, isStarted]); const sentComplete = useRef(false); useEffect(() => { if (sentComplete.current || !order || !isFinalized) return; if (order.status === PayOrderStatus.COMPLETED) { onPaymentCompleted?.({ type: "payorder_completed", payorder_id: order.id, chain_id: assertNotNull(order.destination_currency?.chain_id), chain: getChainName(order.destination_currency?.chain_id) ?? "Unknown", destination_tx_hash: assertNotNull(order.receiving_tx_hash), }); } else if (order.status === PayOrderStatus.REFUNDED) { onPaymentBounced?.({ type: "payorder_refunded", payorder_id: order.id, refund_address: order.refund_address ?? "", refund_tx_hash: order.refund_tx_hash ?? "", }); } }, [order, isFinalized]); // Set the onOpen and onClose callbacks useEffect(() => { setOnOpen(props.onOpen); return () => setOnOpen(undefined); }, [props.onOpen, setOnOpen]); useEffect(() => { setOnClose(props.onClose); return () => setOnClose(undefined); }, [props.onClose, setOnClose]); // Open the modal by default if the defaultOpen prop is true const hasAutoOpened = useRef(false); useEffect(() => { if (!props.defaultOpen || hasAutoOpened.current) return; if (order == null) return; show(); hasAutoOpened.current = true; }, [order, props.defaultOpen, hasAutoOpened.current]); // Validation if ((payId == null) == (depositParams == null)) { throw new Error("Must specify either payId or depositParams, not both"); } const { children, closeOnSuccess, resetOnSuccess } = props; const show = useCallback(() => { if (order == null) return; const modalOptions = { closeOnSuccess, resetOnSuccess, }; showPayment(modalOptions); }, [order, closeOnSuccess, resetOnSuccess]); const hide = useCallback(() => setOpen(false), []); 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 }) })); } //# sourceMappingURL=index.js.map