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