UNPKG

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