import { type JSX, useCallback, useState } from "react"; import { Platform, StyleSheet, View } from "react-native"; import { SvgXml } from "react-native-svg"; import type { Chain } from "../../../../chains/types.js"; import type { ThirdwebClient } from "../../../../client/client.js"; import type { MultiStepAuthProviderType } from "../../../../wallets/in-app/core/authentication/types.js"; import type { InAppWalletAuth } from "../../../../wallets/in-app/core/wallet/types.js"; import type { Wallet } from "../../../../wallets/interfaces/wallet.js"; import { parseTheme } from "../../../core/design-system/CustomThemeProvider.js"; import type { Theme } from "../../../core/design-system/index.js"; import { useSiweAuth } from "../../../core/hooks/auth/useSiweAuth.js"; import type { ConnectButtonProps } from "../../../core/hooks/connection/ConnectButtonProps.js"; import type { ConnectEmbedProps } from "../../../core/hooks/connection/ConnectEmbedProps.js"; import { useActiveAccount } from "../../../core/hooks/wallets/useActiveAccount.js"; import { useActiveWallet } from "../../../core/hooks/wallets/useActiveWallet.js"; import { useDisconnect } from "../../../core/hooks/wallets/useDisconnect.js"; import { useIsAutoConnecting } from "../../../core/hooks/wallets/useIsAutoConnecting.js"; import { useConnectionManager } from "../../../core/providers/connection-manager.js"; import { useWalletInfo } from "../../../core/utils/wallet.js"; import { radius, spacing } from "../../design-system/index.js"; import { getDefaultWallets } from "../../wallets/defaultWallets.js"; import { AutoConnect } from "../AutoConnect/AutoConnect.js"; import { ThemedButton, ThemedButtonWithIcon } from "../components/button.js"; import { type ContainerType, Header } from "../components/Header.js"; import { RNImage } from "../components/RNImage.js"; import { Spacer } from "../components/spacer.js"; import { ThemedText } from "../components/text.js"; import { ThemedView } from "../components/view.js"; import { getAuthProviderImage, WalletImage, } from "../components/WalletImage.js"; import { TW_ICON, WALLET_ICON } from "../icons/svgs.js"; import { ErrorView } from "./ErrorView.js"; import { AllWalletsList, ExternalWalletsList } from "./ExternalWalletsList.js"; import { InAppWalletUI, OtpLogin, PasskeyView } from "./InAppWalletUI.js"; import { LoadingView } from "./LoadingView.js"; import WalletLoadingThumbnail from "./WalletLoadingThumbnail.js"; export type ModalState = | { screen: "base" } | { screen: "connecting"; wallet: Wallet; authMethod?: InAppWalletAuth } | { screen: "error"; error: string } | { screen: "otp"; auth: MultiStepAuthProviderType; wallet: Wallet<"inApp"> } | { screen: "passkey"; wallet: Wallet<"inApp"> } | { screen: "external_wallets" } | { screen: "all_wallets" } | { screen: "auth" }; /** * A component that allows the user to connect their wallet. * * it renders the same UI as the [`ConnectButton`](https://portal.thirdweb.com/react/v4/components/ConnectButton) component's modal - but directly on the page instead of being in a modal. * * It only renders UI if wallet is not connected * @example * ```tsx * * ``` * @param props - * The props for the `ConnectEmbed` component. * * Refer to the [`ConnectEmbedProps`](https://portal.thirdweb.com/references/typescript/v5/ConnectEmbedProps) type for more details * @component * @walletConnection */ export function ConnectEmbed(props: ConnectEmbedProps) { const theme = parseTheme(props.theme); const wallet = useActiveWallet(); const account = useActiveAccount(); const siweAuth = useSiweAuth(wallet, account, props.auth); const needsAuth = siweAuth.requiresAuth && !siweAuth.isLoggedIn; const isConnected = wallet && !needsAuth; const adaptedProps = { ...props, connectModal: { ...props }, } as ConnectButtonProps; const isAutoConnecting = useIsAutoConnecting(); const wallets = props.wallets || getDefaultWallets(props); const autoConnectComp = props.autoConnect !== false && ( ); if (isAutoConnecting) { return ; } return isConnected ? ( autoConnectComp ) : ( <> {autoConnectComp} ); } export function ConnectModal( props: ConnectButtonProps & { theme: Theme; onClose?: () => void; containerType: ContainerType; siweAuth: ReturnType; }, ) { const { theme, client, containerType, accountAbstraction, onConnect, onClose, siweAuth, } = props; const wallet = useActiveWallet(); const needsAuth = wallet && siweAuth.requiresAuth && !siweAuth.isLoggedIn; const [modalState, setModalState] = useState( needsAuth ? { screen: "auth" } : { screen: "base" }, ); const wallets = props.wallets || getDefaultWallets(props); const inAppWallet = wallets.find((wallet) => wallet.id === "inApp") as | Wallet<"inApp"> | undefined; const externalWallets = wallets .filter((wallet) => wallet.id !== "inApp") .filter((wallet) => !props.hiddenWallets?.includes(wallet.id)); const showBranding = props.connectModal?.showThirdwebBranding !== false; const connectionManager = useConnectionManager(); const connector = useCallback( async (args: { wallet: Wallet; connectFn: (chain?: Chain) => Promise; authMethod?: InAppWalletAuth; }) => { setModalState({ authMethod: args.authMethod, screen: "connecting", wallet: args.wallet, }); try { const w = await args.connectFn(props.chain); await connectionManager.connect(w, { accountAbstraction, client, onConnect, }); if (siweAuth.requiresAuth && !siweAuth.isLoggedIn) { // if in-app wallet, signin headlessly // TODO (rn) handle signless smart wallets as well if (w.id === "inApp") { await siweAuth.doLogin(); onClose?.(); } else { setModalState({ screen: "auth", }); } } else { onClose?.(); } } catch (error) { setModalState({ error: (error as Error)?.message || "Unknown error", screen: "error", }); } }, [ client, accountAbstraction, onConnect, onClose, siweAuth, connectionManager, props.chain, ], ); let content: JSX.Element; switch (modalState.screen) { case "otp": { content = ( <>
setModalState({ screen: "base" })} onClose={props.onClose} theme={theme} title={props.connectModal?.title || "Sign in"} /> {containerType === "modal" ? ( ) : ( )} ); break; } case "external_wallets": { content = ( <>
setModalState({ screen: "base" })} onClose={props.onClose} theme={theme} title={props.connectModal?.title || "Sign in"} /> setModalState({ screen: "all_wallets" })} showAllWalletsButton={props.showAllWallets !== false} theme={theme} /> ); break; } case "all_wallets": { content = ( <>
inAppWallet ? setModalState({ screen: "external_wallets" }) : setModalState({ screen: "base" }) } onClose={props.onClose} theme={theme} title={props.connectModal?.title || "Select Wallet"} /> ); break; } case "connecting": { content = ( <>
setModalState({ screen: "base" })} onClose={props.onClose} theme={theme} title={props.connectModal?.title || "Sign in"} /> {containerType === "modal" ? ( ) : ( )} {containerType === "modal" ? ( ) : ( )} ); break; } case "passkey": { content = ( <>
setModalState({ screen: "base" })} onClose={props.onClose} theme={theme} title={props.connectModal?.title || "Sign in"} /> {containerType === "modal" ? ( ) : ( )} {containerType === "modal" ? ( ) : ( )} ); break; } case "auth": { content = ( <>
{containerType === "modal" ? ( ) : ( )} setModalState({ screen: "base" })} onError={(error) => setModalState({ error, screen: "error" })} onSignIn={() => props.onClose?.()} siweAuth={siweAuth} theme={theme} /> {containerType === "modal" ? ( ) : ( )} ); break; } case "error": { content = ( <>
setModalState({ screen: "base" })} onClose={props.onClose} theme={theme} title={props.connectModal?.title || "Sign in"} /> {containerType === "modal" ? ( ) : ( )} {containerType === "modal" ? ( ) : ( )} ); break; } default: { content = ( <>
{inAppWallet ? ( <> {containerType === "modal" ? ( ) : ( )} {externalWallets.length > 0 ? ( <> setModalState({ screen: "external_wallets" }) } theme={theme} title="Connect a wallet" /> ) : null} {containerType === "modal" ? ( ) : ( )} ) : externalWallets.length > 0 ? ( <> setModalState({ screen: "all_wallets" }) } showAllWalletsButton={props.showAllWallets !== false} theme={theme} /> ) : null} ); } } return ( {content} {showBranding && } ); } function WalletLoadingView({ theme, wallet, client, authProvider, }: { theme: Theme; wallet: Wallet; client: ThirdwebClient; authProvider?: InAppWalletAuth; }) { const walletInfo = useWalletInfo(wallet.id); return ( {authProvider ? ( ) : ( )} {authProvider ? `Connecting with ${capitalizeFirstLetter(authProvider)}` : "Awaiting confirmation"} {authProvider ? `Signing into your ${capitalizeFirstLetter(authProvider)} account` : `Accept the connection request in ${walletInfo.data?.name}`} ); } function SignInView({ theme, siweAuth, client, onSignIn, onError, onDisconnect, }: { theme: Theme; siweAuth: ReturnType; client: ThirdwebClient; onSignIn: () => void; onError: (error: string) => void; onDisconnect: () => void; }) { const wallet = useActiveWallet(); const walletInfo = useWalletInfo(wallet?.id); const { disconnect } = useDisconnect(); const isSigningIn = siweAuth.isLoggingIn || siweAuth.isLoading; return ( wallet && ( Complete sign in Sign login request in {walletInfo.data?.name} to continue { try { await siweAuth.doLogin(); onSignIn(); } catch (e) { onError((e as Error)?.message || "Unknown error"); } }} style={{ width: "100%" }} theme={theme} variant="accent" > Sign login request { disconnect(wallet); siweAuth.doLogout(); onDisconnect(); }} style={{ width: "100%" }} theme={theme} variant="secondary" > Disconnect ) ); } function OrDivider({ theme }: { theme: Theme }) { return ( OR ); } function PoweredByThirdweb({ theme }: { theme: Theme }) { return ( Powered by thirdweb ); } function capitalizeFirstLetter(str: string): string { if (!str) return str; return str.charAt(0).toUpperCase() + str.slice(1); } const styles = StyleSheet.create({ embedContainer: { backgroundColor: "transparent", flex: 1, flexDirection: "column", width: "100%", }, modalContainer: { borderTopLeftRadius: radius.lg, borderTopRightRadius: radius.lg, flex: 1, flexDirection: "column", width: "100%", }, });