"use client"; import { ChevronLeftIcon } from "@radix-ui/react-icons"; import { useEffect, useMemo, useRef, useState } from "react"; import type { Chain } from "../../../../chains/types.js"; import type { ThirdwebClient } from "../../../../client/client.js"; import type { InjectedSupportedWalletIds } from "../../../../wallets/__generated__/wallet-ids.js"; import { createWallet } from "../../../../wallets/create-wallet.js"; import { getInstalledWalletProviders } from "../../../../wallets/injected/mipdStore.js"; import type { Wallet } from "../../../../wallets/interfaces/wallet.js"; import type { SmartWalletOptions } from "../../../../wallets/smart/types.js"; import type { WalletId } from "../../../../wallets/wallet-types.js"; import { useCustomTheme } from "../../../core/design-system/CustomThemeProvider.js"; import { fontSize, iconSize, radius, spacing, } from "../../../core/design-system/index.js"; import { useSetSelectionData } from "../../providers/wallet-ui-states-provider.js"; import { sortWallets } from "../../utils/sortWallets.js"; import InAppWalletSelectionUI from "../../wallets/in-app/InAppWalletSelectionUI.js"; import { LAST_USED_BADGE_VERTICAL_RESERVED_SPACE } from "../components/badge.js"; import { Container, Line, ModalHeader, noScrollBar, ScreenBottomContainer, } from "../components/basic.js"; import { Button, IconButton } from "../components/buttons.js"; import { Img } from "../components/Img.js"; import { ModalTitle } from "../components/modalElements.js"; import { Spacer } from "../components/Spacer.js"; import { TextDivider } from "../components/TextDivider.js"; import { Link, Text } from "../components/text.js"; import { StyledDiv, StyledUl } from "../design-system/elements.js"; import { compactModalMaxHeight } from "./constants.js"; import { OutlineWalletIcon } from "./icons/OutlineWalletIcon.js"; import type { ConnectLocale } from "./locale/types.js"; import { SmartConnectUI } from "./Modal/SmartWalletConnectUI.js"; import { useScreenContext } from "./Modal/screen.js"; import { getLastUsedWalletId } from "./Modal/storage.js"; import { TOS } from "./Modal/TOS.js"; import { PoweredByThirdweb } from "./PoweredByTW.js"; import { WalletButtonEl, WalletEntryButton } from "./WalletEntryButton.js"; import { WalletTypeRowButton } from "./WalletTypeRowButton.js"; // const localWalletId = "local"; const inAppWalletId: WalletId = "inApp"; type WalletSelectorProps = { wallets: Wallet[]; selectWallet: (wallet: Wallet) => void; title: string; done: (wallet: Wallet) => void; goBack?: () => void; onShowAll: () => void; setModalVisibility: (value: boolean) => void; accountAbstraction?: SmartWalletOptions; size: "compact" | "wide"; meta: { title?: string; titleIconUrl?: string; showThirdwebBranding?: boolean; termsOfServiceUrl?: string; privacyPolicyUrl?: string; requireApproval?: boolean; }; client: ThirdwebClient; connectLocale: ConnectLocale; recommendedWallets: Wallet[] | undefined; hideHeader: boolean; chain: Chain | undefined; chains: Chain[] | undefined; showAllWallets: boolean | undefined; walletConnect: | { projectId?: string; } | undefined; modalHeader: | { title: string; onBack: () => void; } | undefined; walletIdsToHide: WalletId[] | undefined; disableSelectionDataReset?: boolean; }; /** * @internal */ export function WalletSelector(props: WalletSelectorProps) { const [personalWallet, setPersonalWallet] = useState(null); if (!props.accountAbstraction) { return ; } if (personalWallet) { return ( ); } return ( { setPersonalWallet(w); }} /> ); } /** * @internal */ const WalletSelectorInner: React.FC = (props) => { const { walletIdsToHide } = props; const isCompact = props.size === "compact"; const [isWalletGroupExpanded, setIsWalletGroupExpanded] = useState(false); // This is only used if requireApproval is true const [approvedTOS, setApprovedTOS] = useState(false); const installedWallets = getInstalledWallets(); const lastUsedWalletId = useMemo(() => getLastUsedWalletId(), []); const propsWallets = props.wallets; let _wallets: Wallet[] = [...propsWallets]; for (const iW of installedWallets) { if (!propsWallets.find((w) => w.id === iW.id)) { _wallets.push(iW); } } if (walletIdsToHide) { _wallets = _wallets.filter((w) => !walletIdsToHide?.includes(w.id)); } const localWalletConfig = false; // _wallets.find((w) => w.id === localWalletId); const nonLocalWalletConfigs = _wallets; // _wallets.filter((w) => w.id !== localWalletId); const socialWallets = nonLocalWalletConfigs.filter( (w) => w.id === inAppWalletId, ); const eoaWallets = sortWallets( nonLocalWalletConfigs.filter((w) => w.id !== inAppWalletId), props.recommendedWallets, ); const continueAsGuest = localWalletConfig && ( ); // prevent accidental clicks on the TW icon when clicking on back icon from previous screen const enableTWIconLink = useRef(false); useEffect(() => { setTimeout(() => { enableTWIconLink.current = true; }, 1000); }, []); const twTitle = props.modalHeader ? ( ) : ( {!props.meta.titleIconUrl ? null : ( )} {props.title} ); const handleSelect = async (wallet: Wallet) => { props.selectWallet(wallet); }; const connectAWallet = ( { setIsWalletGroupExpanded(true); }} title={props.connectLocale.connectAWallet} /> ); const newToWallets = ( {props.connectLocale.newToWallets} {props.connectLocale.getStarted} ); const tos = props.meta.requireApproval || props.meta.termsOfServiceUrl || props.meta.privacyPolicyUrl ? ( setApprovedTOS(!approvedTOS)} privacyPolicyUrl={props.meta.privacyPolicyUrl} requireApproval={props.meta.requireApproval} termsOfServiceUrl={props.meta.termsOfServiceUrl} /> ) : undefined; let topSection: React.ReactNode; let bottomSection: React.ReactNode; // wide modal if (!isCompact) { topSection = ( ); if (continueAsGuest) { bottomSection = ( {continueAsGuest} ); } } // compact else { // no social logins if (socialWallets.length === 0) { topSection = ( ); bottomSection = ( <> {newToWallets} {continueAsGuest} {!continueAsGuest && } {tos && ( {tos} )} ); } // social logins else { // not expanded state if (!isWalletGroupExpanded) { topSection = ( {eoaWallets.length > 0 && ( <> )} ); // only social login - no eoa wallets if (eoaWallets.length === 0) { bottomSection = tos || continueAsGuest ? ( <> {continueAsGuest && ( {continueAsGuest} )} {tos && {tos} } ) : ( ); } // social login + eoa wallets else { // social login + More than 1 eoa wallets if (eoaWallets.length > 1) { bottomSection = ( {connectAWallet} {continueAsGuest} {tos ? ( {tos} ) : ( )} ); } // social login + single eoa wallet else { bottomSection = ( <> {continueAsGuest && ( {continueAsGuest} )} {tos ? ( <> {continueAsGuest ? : } {tos} ) : ( continueAsGuest && )} ); } } } // expanded state else { topSection = ( ); bottomSection = ( {newToWallets} ); } } } // hide the header for embed - unless it's customized const showHeader = !props.hideHeader || props.modalHeader; return ( {/* Header */} {showHeader && ( {isWalletGroupExpanded ? ( { setIsWalletGroupExpanded(false); }} title={twTitle} /> ) : ( twTitle )} )} {/* Body */} {!showHeader && isWalletGroupExpanded && ( { setIsWalletGroupExpanded(false); }} style={{ gap: spacing.xxs, paddingBlock: spacing.xxs, paddingRight: spacing.xs, transform: `translateX(-${spacing.xs})`, }} > {props.connectLocale.goBackButton} )} {topSection} {bottomSection} {isCompact && props.meta.showThirdwebBranding !== false && ( )} ); }; let _installedWallets: Wallet[] = []; function getInstalledWallets() { if (_installedWallets.length === 0) { const providers = getInstalledWalletProviders(); const walletIds = providers.map((provider) => provider.info.rdns); _installedWallets = walletIds.map((w) => createWallet(w as InjectedSupportedWalletIds), ); } return _installedWallets; } /** * @internal */ const WalletSelection: React.FC<{ wallets: Wallet[]; selectWallet: (wallet: Wallet) => void; maxHeight?: string; done: (wallet: Wallet) => void; goBack?: () => void; onShowAll?: () => void; recommendedWallets: Wallet[] | undefined; showAllWallets: boolean | undefined; size: "compact" | "wide"; connectLocale: ConnectLocale; client: ThirdwebClient; chain: Chain | undefined; diableSelectionDataReset?: boolean; // If true, all options will be disabled. Used for things like requiring TOS approval. disabled?: boolean; }> = (props) => { const wallets = sortWallets(props.wallets, props.recommendedWallets); const { screen } = useScreenContext(); const setSelectionData = useSetSelectionData(); const lastUsedWalletId = useMemo(() => getLastUsedWalletId(), []); return ( {wallets.map((wallet) => { const isActive = screen ? typeof screen === "object" && screen.id === wallet.id : false; return (
  • {wallet.id === "inApp" && props.size === "compact" ? ( props.done(wallet)} goBack={props.goBack} recommendedWallets={props.recommendedWallets} select={() => props.selectWallet(wallet)} size={props.size} wallet={wallet as Wallet<"inApp">} /> ) : ( { if (!props.diableSelectionDataReset) { setSelectionData({}); } props.selectWallet(wallet); }} wallet={wallet} /> )}
  • ); })} {props.onShowAll && props.showAllWallets !== false && (
    All Wallets 500+ )} ); }; const BadgeText = /* @__PURE__ */ StyledDiv(() => { const theme = useCustomTheme(); return { backgroundColor: theme.colors.secondaryButtonBg, borderRadius: radius.sm, color: theme.colors.secondaryText, fontSize: fontSize.xs, paddingBlock: "3px", paddingInline: spacing.xxs, }; }); const ButtonContainer = /* @__PURE__ */ StyledDiv(() => { const theme = useCustomTheme(); return { "&:hover [data-dot]": { background: theme.colors.primaryText, }, }; }); const ShowAllWalletsIcon = /* @__PURE__ */ StyledDiv(() => { const theme = useCustomTheme(); return { "& div": { background: theme.colors.secondaryText, borderRadius: "50%", height: "10px", transition: "background 200ms ease", width: "10px", }, alignItems: "center", backgroundColor: theme.colors.tertiaryBg, border: `1px solid ${theme.colors.borderColor}`, borderRadius: radius.md, display: "grid", gap: spacing["4xs"], gridTemplateColumns: "1fr 1fr", height: `${iconSize.xl}px`, justifyItems: "center", padding: spacing.xs, width: `${iconSize.xl}px`, }; }); const WalletList = /* @__PURE__ */ StyledUl({ all: "unset", boxSizing: "border-box", display: "flex", flex: 1, flexDirection: "column", gap: "2px", listStyleType: "none", overflowY: "auto", ...noScrollBar, margin: "-2px", marginBottom: 0, // to show the box-shadow of inputs that overflows paddingInline: spacing.md, paddingBottom: spacing.lg, }); const GradientDiv = /* @__PURE__ */ StyledDiv((_) => { const theme = useCustomTheme(); theme.colors.modalBg; return { background: `linear-gradient(to bottom, transparent 0%, ${theme.colors.modalBg} 80%)`, height: spacing.lg, left: 0, pointerEvents: "none", position: "absolute", top: `-${spacing.lg}`, width: "100%", }; });