@coin-voyage/paykit
Version:
Seamless crypto payments. Onboard users from any chain, any coin into your app with one click.
176 lines (173 loc) • 8.52 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
import { useAccount } from "@coin-voyage/crypto/hooks";
import { ChainType, getChainTypeByChainId, PayOrderMode, PayOrderStatus } from "@coin-voyage/shared/common";
import { AnimatePresence, motion } from "framer-motion";
import { useEffect, useMemo, useState } from "react";
import { useChainId, useSwitchChain } from "wagmi";
import { chainToLogo } from "../../../assets/chains";
import { AlertIcon, RetryIconCircle } from "../../../assets/icons";
import useLocales from "../../../hooks/useLocales";
import styled from "../../../styles/styled";
import { ROUTES } from "../../../types/routes";
import usePayContext from "../../contexts/pay";
import { RetryButton, RetryIconContainer } from "../../pay-modal/ConnectWithInjector/styles";
import CircleSpinner from "../../spinners/CircleSpinner";
import { AnimationContainer } from "../../ui/AnimationContainer/styles";
import { ModalBody, ModalContent, ModalH1, PageContent } from "../../ui/Modal/styles";
import Tooltip from "../../ui/Tooltip";
var PayState;
(function (PayState) {
PayState["RequestingPayment"] = "Requesting Payment";
PayState["SwitchingChain"] = "Switching Chain";
PayState["RequestCancelled"] = "Payment Cancelled";
PayState["RequestSuccessful"] = "Payment Successful";
})(PayState || (PayState = {}));
export default function PayWithToken() {
const { triggerResize, paymentState, setRoute, log, allowedWallets } = usePayContext();
const { selectedTokenOption, payWithToken } = paymentState;
const { account } = useAccount();
const [payState, setPayState] = useState(PayState.RequestingPayment);
const locales = useLocales();
const isExpired = paymentState.payOrder?.status === PayOrderStatus.EXPIRED;
const isDeposit = paymentState.payOrder?.mode === PayOrderMode.DEPOSIT;
const chainType = getChainTypeByChainId(paymentState.selectedTokenOption?.chain_id);
const walletChainId = useChainId();
const { switchChainAsync } = useSwitchChain();
const trySwitchingChain = async (option, forceSwitch = false) => {
if (walletChainId !== option.chain_id || forceSwitch) {
const resultChain = await (async () => {
try {
return await switchChainAsync({
chainId: option.chain_id,
});
}
catch (e) {
console.error("Failed to switch chain", e);
return null;
}
})();
if (resultChain?.id !== option.chain_id) {
return false;
}
}
return true;
};
const isRestricted = useMemo(() => {
if (allowedWallets === null)
return false;
const isAddressAllowed = allowedWallets?.find(w => w.address === account.address && w.chainType === account.chainType)?.allowed;
return !isAddressAllowed;
}, [allowedWallets, account]);
const handleTransfer = async (token) => {
if (isRestricted)
return;
if (chainType === ChainType.EVM) {
setPayState(PayState.SwitchingChain);
const switchChain = await trySwitchingChain(token);
if (!switchChain) {
setPayState(PayState.RequestCancelled);
return;
}
}
setPayState(PayState.RequestingPayment);
try {
const txHash = await payWithToken(token);
if (!txHash) {
setPayState(PayState.RequestCancelled);
return;
}
setPayState(PayState.RequestSuccessful);
setTimeout(() => {
setRoute(ROUTES.CONFIRMATION);
}, 200);
}
catch (e) {
if (e?.name === "ConnectorChainMismatchError") {
// Workaround for Rainbow wallet bug -- user is able to switch chain without
// the wallet updating the chain ID for wagmi.
log("Chain mismatch detected, attempting to switch and retry");
const switchSuccessful = await trySwitchingChain(token, true);
if (switchSuccessful) {
try {
await payWithToken(token);
return; // Payment successful after switching chain
}
catch (retryError) {
console.error("Failed to pay with token after switching chain", retryError);
throw retryError;
}
}
}
setPayState(PayState.RequestCancelled);
console.error("Failed to pay with token", e);
}
};
let transferTimeout; // Prevent double-triggering in React dev strict mode.
useEffect(() => {
if (!selectedTokenOption) {
return;
}
// Give user time to see the UI before opening
transferTimeout = setTimeout(() => {
handleTransfer(selectedTokenOption);
}, 100);
return () => {
clearTimeout(transferTimeout);
};
}, []);
useEffect(() => {
triggerResize();
}, [payState]);
const onRetry = () => {
if (isExpired && isDeposit) {
paymentState.copyDepositPayOrder();
setRoute(ROUTES.SELECT_TOKEN);
}
else if (selectedTokenOption) {
handleTransfer(selectedTokenOption);
}
};
return (_jsxs(PageContent, { children: [_jsx(LoadingContainer, { children: _jsxs(AnimationContainer, { "$shake": payState === PayState.RequestCancelled, "$circle": true, children: [_jsx(AnimatePresence, { children: (payState === PayState.RequestCancelled || (isExpired && isDeposit)) ? (_jsx(RetryButton, { "aria-label": "Retry", initial: { opacity: 0, scale: 0.8 }, animate: { opacity: 1, scale: 1 }, exit: { opacity: 0, scale: 0.8 }, whileTap: { scale: 0.9 }, transition: { duration: 0.1 }, onClick: onRetry, children: _jsx(RetryIconContainer, { children: _jsx(Tooltip, { open: payState === PayState.RequestCancelled, message: locales.tryAgainQuestion, xOffset: -6, children: _jsx(RetryIconCircle, {}) }) }) })) : _jsx(ChainLogoContainer, { children: selectedTokenOption &&
chainToLogo[selectedTokenOption.chain_id] }, "ChainLogoContainer") }), _jsx(AnimatePresence, { children: _jsx(CircleSpinner, { logo: _jsx("img", { src: selectedTokenOption?.image_uri, alt: selectedTokenOption?.ticker }, selectedTokenOption?.image_uri), loading: payState === PayState.RequestingPayment && !isExpired, unavailable: false }, "CircleSpinner") })] }) }), !isExpired ? _jsxs(ModalContent, { children: [payState === PayState.RequestCancelled &&
_jsxs(_Fragment, { children: [_jsxs(ModalH1, { "$error": true, children: [_jsx(AlertIcon, {}), locales.injectionScreen_rejected_h1] }), _jsx(ModalBody, { children: locales.injectionScreen_rejected_p })] }), payState === PayState.SwitchingChain &&
_jsx(ModalH1, { children: locales.switchNetworkScreen_heading }), payState === PayState.RequestingPayment && (_jsxs(_Fragment, { children: [_jsx(ModalH1, { children: locales.requesting_payment_h1 }), _jsx(ModalBody, { children: locales.requesting_payment_p })] })), payState === PayState.RequestSuccessful && (_jsx(ModalH1, { children: locales.injectionScreen_connected_h1 }))] }) : _jsxs(ModalContent, { children: [_jsxs(ModalH1, { "$warning": true, children: [_jsx(AlertIcon, {}), locales.payWithTokenScreen_expired_h1] }), _jsx(ModalBody, { children: locales.payWithTokenScreen_expired_p })] })] }));
}
const LoadingContainer = styled(motion.div) `
display: flex;
align-items: center;
justify-content: center;
margin: 10px auto 16px;
height: 120px;
`;
const ChainLogoContainer = styled(motion.div) `
z-index: 10;
position: absolute;
right: 2px;
bottom: 2px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 16px;
overflow: hidden;
color: var(--ck-body-background);
transition: color 200ms ease;
&:before {
z-index: 5;
content: "";
position: absolute;
inset: 0;
opacity: 0;
transition: opacity 200ms ease;
background: var(--ck-body-color);
}
svg {
display: block;
position: relative;
width: 100%;
height: 100%;
}
`;
//# sourceMappingURL=index.js.map