@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
JavaScript
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 }) }));
}