UNPKG

@turnkey/react-wallet-kit

Version:

The easiest and most powerful way to integrate Turnkey's Embedded Wallets into your React applications.

409 lines (405 loc) 17.5 kB
'use strict'; var jsxRuntime = require('react/jsx-runtime'); var Buttons = require('../design/Buttons.js'); var Hook = require('../../providers/modal/Hook.js'); var Svg = require('../design/Svg.js'); var reactFontawesome = require('@fortawesome/react-fontawesome'); var freeSolidSvgIcons = require('@fortawesome/free-solid-svg-icons'); var react = require('react'); var clsx = require('clsx'); var qrcode_react = require('qrcode.react'); var Success = require('../design/Success.js'); var core = require('@turnkey/core'); var Hook$1 = require('../../providers/client/Hook.js'); function WalletAuthButton(props) { const { onContinue } = props; const [isLoading, setIsLoading] = react.useState(false); const handleContinue = async () => { setIsLoading(true); try { await Promise.resolve(onContinue()); } finally { setIsLoading(false); } }; return jsxRuntime.jsx("div", { className: "flex flex-col w-full", children: jsxRuntime.jsx(Buttons.ActionButton, { onClick: handleContinue, loading: isLoading, className: "w-full text-inherit bg-button-light dark:bg-button-dark", children: "Continue with wallet" }) }); } const canUnlink = (provider, shouldShowUnlink) => { return shouldShowUnlink && provider.connectedAddresses && provider.connectedAddresses.length > 0; }; function ExternalWalletChainSelector(props) { const { providers, onSelect, onUnlink } = props; const { isMobile } = Hook.useModal(); const shouldShowUnlink = onUnlink !== undefined; const handleSelect = provider => { if (canUnlink(provider, shouldShowUnlink)) { onUnlink(provider); } else { onSelect(provider); } }; return jsxRuntime.jsxs("div", { className: clsx("flex flex-col w-72 gap-4 mt-11 items-center justify-center", isMobile ? "w-full" : "w-72"), children: [jsxRuntime.jsx("img", { src: providers[0]?.info.icon, className: "size-14 rounded-full" }), jsxRuntime.jsxs("span", { className: "text-sm text-center text-icon-text-light dark:text-icon-text-dark", children: [providers[0]?.info.name ?? "This wallet provider", " supports multiple chains. Select which chain you would like to use."] }), jsxRuntime.jsx("div", { className: "w-full flex flex-col gap-2", children: providers.map(p => { const [isHovering, setIsHovering] = react.useState(false); return jsxRuntime.jsxs(Buttons.ActionButton, { onClick: () => handleSelect(p), onMouseEnter: () => setIsHovering(true), onMouseLeave: () => setIsHovering(false), className: "relative overflow-hidden flex items-center justify-start gap-2 w-full text-inherit bg-button-light dark:bg-button-dark", children: [core.isEthereumProvider(p) ? jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(Svg.EthereumLogo, { className: "size-5" }), canUnlink(p, shouldShowUnlink) && jsxRuntime.jsx(ConnectedIndicator, { isPinging: true })] }) : jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx(Svg.SolanaLogo, { className: "size-5" }), canUnlink(p, shouldShowUnlink) && jsxRuntime.jsx(ConnectedIndicator, { isPinging: true })] }), jsxRuntime.jsxs("div", { className: "flex flex-col items-start", children: [core.isEthereumProvider(p) ? "EVM" : "Solana", canUnlink(p, shouldShowUnlink) && jsxRuntime.jsxs("span", { className: "text-xs text-icon-text-light dark:text-icon-text-dark", children: ["Connected: ", p.connectedAddresses[0]?.slice(0, 4), "...", p.connectedAddresses[0]?.slice(-3)] })] }), jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, { className: clsx(`absolute transition-all duration-200`, isHovering ? "right-4" : "-right-4", canUnlink(p, shouldShowUnlink) ? "text-danger-light dark:text-danger-dark" : "text-icon-text-light dark:text-icon-text-dark"), size: canUnlink(p, shouldShowUnlink) ? "lg" : "1x", icon: canUnlink(p, shouldShowUnlink) ? freeSolidSvgIcons.faClose : freeSolidSvgIcons.faChevronRight })] }, p.chainInfo.namespace); }) })] }); } function ExternalWalletSelector(props) { const { providers, onUnlink, onSelect } = props; const { pushPage, popPage, isMobile } = Hook.useModal(); const shouldShowUnlink = onUnlink !== undefined; // Group providers by name const grouped = providers.reduce((acc, provider) => { const name = provider.info.name; if (!acc[name]) acc[name] = []; acc[name].push(provider); return acc; }, {}); const handleSelectGroup = group => { if (group.length === 1) { if (canUnlink(group[0], shouldShowUnlink)) { onUnlink(group[0]); } else { onSelect(group[0]); } } else { pushPage({ key: `Select chain`, content: jsxRuntime.jsx(ExternalWalletChainSelector, { providers: group, onUnlink: onUnlink, onSelect: onSelect }) }); } }; react.useEffect(() => { // Don't show the selector if there's only one wallet provider const groupedValues = Object.values(grouped); if (groupedValues.length === 1) { popPage(); // Remove this page from the stack so user doesn't get stuck in an infinite loop when pressing back handleSelectGroup(groupedValues[0]); } }, [grouped]); return Object.keys(grouped).length === 0 ? jsxRuntime.jsxs("div", { className: clsx("flex flex-col h-40 mt-4 gap-2 justify-center items-center text-xs text-center text-icon-text-light dark:text-icon-text-dark", isMobile ? "w-full" : "w-72"), children: [jsxRuntime.jsx("span", { className: "text-sm font-medium", children: "No wallet providers available." }), jsxRuntime.jsx("span", { children: "Only wallets supporting EIP-1193 (Ethereum) or the Solana wallet standard are supported." })] }) : jsxRuntime.jsx("div", { className: clsx("w-72 min-h-42 max-h-64 mt-12 overflow-y-auto tk-scrollbar p-0.5", isMobile ? "w-full" : "w-72"), children: jsxRuntime.jsx("div", { className: "flex flex-col gap-2", children: Object.entries(grouped).map(([name, group]) => { const [isHovering, setIsHovering] = react.useState(false); const first = group[0]; return jsxRuntime.jsxs(Buttons.ActionButton, { onMouseEnter: () => setIsHovering(true), onMouseLeave: () => setIsHovering(false), onClick: () => handleSelectGroup(group), className: "relative flex items-center justify-between w-full text-inherit bg-button-light dark:bg-button-dark overflow-hidden", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2 overflow-hidden", children: [jsxRuntime.jsx("img", { src: first?.info.icon, alt: first?.info.name, className: "size-6 rounded-full" }), first?.info.name] }), jsxRuntime.jsx("div", { className: clsx(`flex items-center transition-all gap-1`), children: group.map((c, idx) => { const Logo = core.isEthereumProvider(c) ? Svg.EthereumLogo : Svg.SolanaLogo; const delay = 50 + idx * 30; // Staggered delay: leftmost has largest return jsxRuntime.jsxs("div", { style: { transitionDelay: `${delay}ms` }, className: clsx("relative", "size-4", "transition-all duration-200", isHovering ? "-translate-x-8" : "translate-x-0"), children: [jsxRuntime.jsx(Logo, { className: "size-4" }), canUnlink(c, shouldShowUnlink) && jsxRuntime.jsx(ConnectedIndicator, { isPinging: isHovering })] }, c.chainInfo.namespace); }) }), jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, { className: clsx(`absolute transition-all duration-200`, isHovering ? "right-4" : "-right-4", group.length === 1 && canUnlink(group[0], shouldShowUnlink) ? "text-danger-light dark:text-danger-dark" : "text-icon-text-light dark:text-icon-text-dark"), size: group.length === 1 && canUnlink(group[0], shouldShowUnlink) ? "lg" : "1x", icon: group.length === 1 && canUnlink(group[0], shouldShowUnlink) ? freeSolidSvgIcons.faClose : freeSolidSvgIcons.faChevronRight })] }, name); }) }) }); } function UnlinkWalletScreen(props) { const { provider, onUnlink } = props; const { isMobile } = Hook.useModal(); const [isLoading, setIsLoading] = react.useState(false); const [hasError, setHasError] = react.useState(false); const handleUnlink = async () => { setIsLoading(true); setHasError(false); try { await onUnlink(provider); } catch (err) { setHasError(true); } finally { setIsLoading(false); } }; return jsxRuntime.jsxs("div", { className: clsx("mt-8", isMobile ? "w-full" : "w-96"), children: [jsxRuntime.jsxs("div", { className: "mt-6 mb-5 flex flex-col items-center gap-3", children: [jsxRuntime.jsx("img", { src: provider.info.icon ?? "", className: "size-14 rounded-full" }), jsxRuntime.jsx("div", { className: clsx("text-2xl font-bold text-center", hasError && "text-danger-light dark:text-danger-dark"), children: hasError ? "You can't unlink this wallet!" : `Unlink ${provider.info.name}` }), jsxRuntime.jsx("div", { className: "text-icon-text-light dark:text-icon-text-dark text-center !p-0", children: hasError ? `Try unlinking directly from the ${provider.info.name} app` : "You can always link this wallet again later." })] }), jsxRuntime.jsx("div", { className: "flex my-2 mt-0", children: jsxRuntime.jsx(Buttons.ActionButton, { onClick: handleUnlink, loading: isLoading, className: clsx("w-full max-w-md bg-danger-light dark:bg-danger-dark text-primary-text-light dark:text-primary-text-dark", hasError && "animate-shake opacity-50"), spinnerClassName: "text-primary-danger-text-light dark:text-primary-danger-text-dark", children: "Unlink Wallet" }) })] }); } function ConnectedIndicator(props) { const { isPinging = false } = props; return jsxRuntime.jsxs("div", { className: "flex absolute top-[-2px] right-0", children: [isPinging && jsxRuntime.jsx("div", { className: "absolute animate-ping size-[6px] bg-success-light dark:bg-success-dark rounded-full border border-modal-background-light dark:border-modal-background-dark" }), jsxRuntime.jsx("div", { className: "size-[6px] bg-success-light dark:bg-success-dark rounded-full border border-modal-background-light dark:border-modal-background-dark" })] }); } function WalletConnectScreen(props) { const { provider, successPageDuration, onAction, onDisconnect } = props; const { pushPage, closeModal, isMobile } = Hook.useModal(); const { getWalletProviders } = Hook$1.useTurnkey(); const hasRan = react.useRef(false); const [walletConnectProvider, setWalletConnectProvider] = react.useState(); const connectedAccount = walletConnectProvider?.connectedAddresses[0]; react.useEffect(() => { setWalletConnectProvider(provider); }, [provider]); const [isUnlinking, setIsUnlinking] = react.useState(false); const [unlinkError, setUnlinkError] = react.useState(false); // kick off authentication/pairing or signing on mount or when URI changes react.useMemo(() => { (async () => { if (hasRan.current) return; try { await onAction(walletConnectProvider ?? provider); hasRan.current = true; pushPage({ key: "Link Success", content: jsxRuntime.jsx(Success.SuccessPage, { text: "Successfully linked WalletConnect!", onComplete: closeModal, duration: successPageDuration }), preventBack: true, showTitle: false }); } catch (e) {} })(); }, [walletConnectProvider?.uri]); const handleUnlink = async () => { setIsUnlinking(true); setUnlinkError(false); try { await onDisconnect?.(walletConnectProvider ?? provider); const newProviders = await getWalletProviders(); setWalletConnectProvider(newProviders.find(p => p.interfaceType === provider.interfaceType)); } catch (err) { setUnlinkError(true); } finally { setIsUnlinking(false); } }; return jsxRuntime.jsx("div", { className: "p-3 flex flex-col items-center", children: connectedAccount ? jsxRuntime.jsxs("div", { className: clsx("mt-8 flex flex-col items-center gap-3", isMobile ? "w-full" : "w-96"), children: [jsxRuntime.jsxs("div", { className: "w-full justify-between flex items-center flex-1", children: [jsxRuntime.jsx("div", { className: clsx("flex items-center justify-center bg-icon-background-light dark:bg-icon-background-dark rounded-full p-2 text-icon-text-light dark:text-icon-text-dark", isMobile ? "size-18" : "size-24"), children: jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faMobileScreen, size: isMobile ? "3x" : "4x" }) }), jsxRuntime.jsxs("div", { className: "flex flex-row items-center justify-center space-x-2.5 font-medium text-icon-text-light dark:text-icon-text-dark", children: [jsxRuntime.jsxs("div", { className: "flex items-center justify-center", children: [jsxRuntime.jsx("img", { className: "size-5", src: walletConnectProvider.info.icon, alt: "Wallet connect logo" }), jsxRuntime.jsx("img", { className: "size-5 absolute animate-ping", src: walletConnectProvider.info.icon, alt: "Wallet connect logo" })] }), jsxRuntime.jsxs("span", { children: [connectedAccount?.slice(0, 3), "...", connectedAccount?.slice(-3)] })] }), jsxRuntime.jsx("div", { className: clsx("flex items-center justify-center bg-icon-background-light dark:bg-icon-background-dark rounded-full p-2 text-icon-text-light dark:text-icon-text-dark", isMobile ? "size-18" : "size-24"), children: jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, { icon: freeSolidSvgIcons.faLaptop, size: isMobile ? "3x" : "4x" }) })] }), jsxRuntime.jsx("div", { className: clsx("flex flex-row items-center mt-5 text-2xl font-bold text-center"), children: "Already connected" }), jsxRuntime.jsxs("div", { className: "text-icon-text-light dark:text-icon-text-dark text-center text-xs flex flex-col space-y-2 !p-0", children: [jsxRuntime.jsx("span", { children: "Please open the wallet app on your phone to sign the message." }), isUnlinking ? jsxRuntime.jsx("span", { className: "text-danger-light dark:text-danger-dark opacity-50", children: "Unlinking..." }) : unlinkError ? jsxRuntime.jsx("span", { className: "text-danger-light dark:text-danger-dark opacity-50", children: "Error unlinking wallet." }) : jsxRuntime.jsxs("span", { children: ["Need to connect a different wallet?", " ", jsxRuntime.jsx("span", { className: "text-danger-light dark:text-danger-dark cursor-pointer underline", onClick: handleUnlink, children: "Unlink" }), " ", "this wallet first."] })] })] }) : jsxRuntime.jsxs("div", { className: clsx("mt-8 flex flex-col items-center gap-3", isMobile ? "w-full" : "w-96"), children: [walletConnectProvider?.uri && // @ts-expect-error: qrcode.react uses a different React type version jsxRuntime.jsx(qrcode_react.QRCodeSVG, { className: " \n border border-modal-background-dark/20 dark:border-modal-background-light/20\n shadow-[0_0_42px] shadow-primary-light/50\n dark:shadow-[0_0_42px] dark:shadow-primary-dark/50", value: walletConnectProvider.uri, imageSettings: { src: walletConnectProvider.info.icon ?? "", width: 24, height: 24, excavate: true }, size: 200 }), jsxRuntime.jsx("div", { className: clsx("text-2xl font-bold text-center"), children: "Use your phone" }), jsxRuntime.jsx("div", { className: "text-icon-text-light dark:text-icon-text-dark text-center !p-0", children: "Scan this QR code with your WalletConnect-compatible wallet to link" })] }) }); } exports.ConnectedIndicator = ConnectedIndicator; exports.ExternalWalletChainSelector = ExternalWalletChainSelector; exports.ExternalWalletSelector = ExternalWalletSelector; exports.UnlinkWalletScreen = UnlinkWalletScreen; exports.WalletAuthButton = WalletAuthButton; exports.WalletConnectScreen = WalletConnectScreen; //# sourceMappingURL=Wallet.js.map