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%",
},
});