@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
JavaScript
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