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