From 97c08250f4e3ef1ea53177cbf12b7f2054e325ba Mon Sep 17 00:00:00 2001 From: Manan Tank Date: Tue, 13 Jan 2026 22:25:24 +0530 Subject: [PATCH] [MNY-233] Add Last Used badge in Connect UI --- .changeset/three-fans-flow.md | 5 ++ .../web/ui/ConnectWallet/Modal/storage.ts | 30 +++++++++ .../ui/ConnectWallet/WalletEntryButton.tsx | 1 - .../web/ui/ConnectWallet/WalletSelector.tsx | 64 +++++++++++-------- .../ui/ConnectWallet/WalletTypeRowButton.tsx | 4 ++ .../screens/WalletConnectReceiverScreen.tsx | 1 + .../src/react/web/ui/components/badge.tsx | 49 ++++++++++++++ .../src/react/web/utils/sortWallets.ts | 12 ++++ .../wallets/in-app/InputSelectionUI.test.tsx | 3 + .../web/wallets/in-app/InputSelectionUI.tsx | 4 ++ .../shared/ConnectWalletSocialOptions.tsx | 53 ++++++++++++++- .../stories/ConnectButton/others.stories.tsx | 34 ++++++++++ .../stories/ConnectButton/themes.stories.tsx | 5 +- .../thirdweb/src/wallets/manager/index.ts | 3 + 14 files changed, 239 insertions(+), 29 deletions(-) create mode 100644 .changeset/three-fans-flow.md create mode 100644 packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/storage.ts create mode 100644 packages/thirdweb/src/react/web/ui/components/badge.tsx create mode 100644 packages/thirdweb/src/stories/ConnectButton/others.stories.tsx diff --git a/.changeset/three-fans-flow.md b/.changeset/three-fans-flow.md new file mode 100644 index 00000000000..6164bd59152 --- /dev/null +++ b/.changeset/three-fans-flow.md @@ -0,0 +1,5 @@ +--- +"thirdweb": patch +--- + +Add Last Used badge in Connect UI to highlight the last used sign-in method diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/storage.ts b/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/storage.ts new file mode 100644 index 00000000000..5de925993f4 --- /dev/null +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/Modal/storage.ts @@ -0,0 +1,30 @@ +import type { AuthArgsType } from "../../../../../wallets/in-app/core/authentication/types.js"; +import { LAST_USED_WALLET_ID } from "../../../../../wallets/manager/index.js"; +import type { WalletId } from "../../../../../wallets/wallet-types.js"; +import { LAST_AUTH_PROVIDER_STORAGE_KEY } from "../../../../core/utils/storage.js"; + +export function getLastUsedWalletId() { + try { + if (typeof window !== "undefined" && window.localStorage) { + return window.localStorage.getItem( + LAST_USED_WALLET_ID, + ) as WalletId | null; + } + } catch { + // ignore + } + return null; +} + +export function getLastUsedSocialAuth() { + try { + if (typeof window !== "undefined" && window.localStorage) { + return window.localStorage.getItem(LAST_AUTH_PROVIDER_STORAGE_KEY) as + | (AuthArgsType["strategy"] & string) + | null; + } + } catch { + // ignore + } + return null; +} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletEntryButton.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletEntryButton.tsx index 9edbf7dd9fd..38cd981cd13 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletEntryButton.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletEntryButton.tsx @@ -122,7 +122,6 @@ export const WalletButtonEl = /* @__PURE__ */ StyledButton((_) => { '&[data-active="true"]': { backgroundColor: theme.colors.tertiaryBg, }, - alignItems: "center", borderRadius: radius.md, boxSizing: "border-box", diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx index 35f25aee4fa..7b7e64089c7 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/WalletSelector.tsx @@ -1,6 +1,6 @@ "use client"; import { ChevronLeftIcon } from "@radix-ui/react-icons"; -import { useEffect, useRef, useState } from "react"; +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"; @@ -19,6 +19,7 @@ import { 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, @@ -38,6 +39,7 @@ 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"; @@ -136,6 +138,7 @@ const WalletSelectorInner: React.FC = (props) => { const [approvedTOS, setApprovedTOS] = useState(false); const installedWallets = getInstalledWallets(); + const lastUsedWalletId = useMemo(() => getLastUsedWalletId(), []); const propsWallets = props.wallets; let _wallets: Wallet[] = [...propsWallets]; @@ -219,6 +222,11 @@ const WalletSelectorInner: React.FC = (props) => { const connectAWallet = ( = (props) => { > {/* Header */} {showHeader && ( - + {isWalletGroupExpanded ? ( { @@ -514,12 +522,11 @@ const WalletSelectorInner: React.FC = (props) => { {/* Body */} = (props) => { } > {!showHeader && isWalletGroupExpanded && ( - - { - setIsWalletGroupExpanded(false); - }} + + - - {props.connectLocale.goBackButton} - + { + setIsWalletGroupExpanded(false); + }} + style={{ + gap: spacing.xxs, + paddingBlock: spacing.xxs, + paddingRight: spacing.xs, + transform: `translateX(-${spacing.xs})`, + }} + > + + {props.connectLocale.goBackButton} + + )} @@ -602,11 +611,16 @@ const WalletSelection: React.FC<{ const wallets = sortWallets(props.wallets, props.recommendedWallets); const { screen } = useScreenContext(); const setSelectionData = useSetSelectionData(); + + const lastUsedWalletId = useMemo(() => getLastUsedWalletId(), []); + return ( {wallets.map((wallet) => { @@ -634,7 +648,7 @@ const WalletSelection: React.FC<{ ) : ( + {props.lastUsedBadge && } {props.title} diff --git a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/WalletConnectReceiverScreen.tsx b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/WalletConnectReceiverScreen.tsx index cf394ef64e0..c320ed2038c 100644 --- a/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/WalletConnectReceiverScreen.tsx +++ b/packages/thirdweb/src/react/web/ui/ConnectWallet/screens/WalletConnectReceiverScreen.tsx @@ -153,6 +153,7 @@ export function WalletConnectReceiverScreen(props: { + + {props.text} + + + ); +} + +export function LastUsedBadge() { + return ( +
+ +
+ ); +} + +export const LAST_USED_BADGE_VERTICAL_RESERVED_SPACE = 12; diff --git a/packages/thirdweb/src/react/web/utils/sortWallets.ts b/packages/thirdweb/src/react/web/utils/sortWallets.ts index 74ccaa1b7a4..8cfd26ac35e 100644 --- a/packages/thirdweb/src/react/web/utils/sortWallets.ts +++ b/packages/thirdweb/src/react/web/utils/sortWallets.ts @@ -1,5 +1,6 @@ import { getInstalledWalletProviders } from "../../../wallets/injected/mipdStore.js"; import type { WalletId } from "../../../wallets/wallet-types.js"; +import { getLastUsedWalletId } from "../ui/ConnectWallet/Modal/storage.js"; /** * @@ -10,6 +11,7 @@ export function sortWallets( recommendedWallets?: { id: WalletId }[], ): T[] { const providers = getInstalledWalletProviders(); + const lastUsedWalletId = getLastUsedWalletId(); return ( wallets // show the installed wallets first @@ -38,6 +40,16 @@ export function sortWallets( } return 0; }) + // show the last used wallet even before recommended wallets + .sort((a, b) => { + if (a.id === lastUsedWalletId) { + return -1; + } + if (b.id === lastUsedWalletId) { + return 1; + } + return 0; + }) // show in-app wallets first .sort((a, b) => { const aIsInApp = a.id === "inApp" || a.id === "embedded"; diff --git a/packages/thirdweb/src/react/web/wallets/in-app/InputSelectionUI.test.tsx b/packages/thirdweb/src/react/web/wallets/in-app/InputSelectionUI.test.tsx index 8150b67ae5a..1ea2eac525f 100644 --- a/packages/thirdweb/src/react/web/wallets/in-app/InputSelectionUI.test.tsx +++ b/packages/thirdweb/src/react/web/wallets/in-app/InputSelectionUI.test.tsx @@ -15,6 +15,7 @@ describe("InputSelectionUI", () => { render( { it('should initialize countryCodeInfo with "US +1" if defaultSmsCountryCode is not provided', () => { render( { render( + {props.lastUsedBadge && } + {props.format === "phone" && ( void; const themeObj = useCustomTheme(); + const lastUsedSocialAuth = useMemo(() => getLastUsedSocialAuth(), []); + const lastUsedWalletId = useMemo(() => getLastUsedWalletId(), []); + const optionalImageMetadata = useMemo( () => props.wallet.id === "inApp" @@ -134,10 +142,26 @@ export const ConnectWalletSocialOptions = ( queryKey: ["auth-options", wallet.id], retry: false, }); - const authOptions = isEcosystemWallet(wallet) + const _authOptions = isEcosystemWallet(wallet) ? (ecosystemAuthOptions ?? defaultAuthOptions) : (wallet.getConfig()?.auth?.options ?? defaultAuthOptions); + const authOptions = useMemo(() => { + if (!lastUsedSocialAuth || wallet.id !== lastUsedWalletId) { + return _authOptions; + } + + return [..._authOptions].sort((a, b) => { + if (lastUsedSocialAuth === a) { + return -1; + } + if (lastUsedSocialAuth === b) { + return 1; + } + return 0; + }); + }, [_authOptions, lastUsedSocialAuth, wallet.id, lastUsedWalletId]); + const emailIndex = authOptions.indexOf("email"); const isEmailEnabled = emailIndex !== -1; const phoneIndex = authOptions.indexOf("phone"); @@ -369,9 +393,13 @@ export const ConnectWalletSocialOptions = ( }} style={{ flexGrow: socialLogins.length < 7 ? 1 : 0, + position: "relative", }} variant="outline" > + {lastUsedWalletId === wallet.id && + lastUsedSocialAuth === loginMethod && } + { @@ -417,6 +448,9 @@ export const ConnectWalletSocialOptions = ( /> ) : ( ))} + {isPhoneEnabled && (inputMode === "phone" ? ( { @@ -463,6 +501,9 @@ export const ConnectWalletSocialOptions = ( /> ) : ( ; + +type Story = StoryObj; + +export const WideModal: Story = { + args: { + connectModal: { + size: "wide", + }, + }, +}; + +export const CompactModal: Story = { + args: { + connectModal: { + size: "compact", + }, + }, +}; + +export default meta; diff --git a/packages/thirdweb/src/stories/ConnectButton/themes.stories.tsx b/packages/thirdweb/src/stories/ConnectButton/themes.stories.tsx index 8e426726bd5..290cdc7e6e1 100644 --- a/packages/thirdweb/src/stories/ConnectButton/themes.stories.tsx +++ b/packages/thirdweb/src/stories/ConnectButton/themes.stories.tsx @@ -28,11 +28,12 @@ export const Light: Story = { }, }; -export const CustomBlack: Story = { +export const Custom: Story = { args: { theme: darkTheme({ colors: { - modalBg: "black", + modalBg: "#0c0a2e", + borderColor: "#2f2987", }, }), }, diff --git a/packages/thirdweb/src/wallets/manager/index.ts b/packages/thirdweb/src/wallets/manager/index.ts index 74e4905f729..ce84e5584e3 100644 --- a/packages/thirdweb/src/wallets/manager/index.ts +++ b/packages/thirdweb/src/wallets/manager/index.ts @@ -24,6 +24,7 @@ export type ConnectionStatus = const CONNECTED_WALLET_IDS = "thirdweb:connected-wallet-ids"; const LAST_ACTIVE_EOA_ID = "thirdweb:active-wallet-id"; const LAST_ACTIVE_CHAIN = "thirdweb:active-chain"; +export const LAST_USED_WALLET_ID = "thirdweb:last-used-wallet-id"; export type ConnectionManager = ReturnType; export type ConnectManagerOptions = { @@ -148,6 +149,7 @@ export function createConnectionManager(storage: AsyncStorage) { })(); await storage.setItem(LAST_ACTIVE_EOA_ID, wallet.id); + await storage.setItem(LAST_USED_WALLET_ID, wallet.id); // add personal wallet to connected wallets list even if it's not the active one addConnectedWallet(wallet); @@ -218,6 +220,7 @@ export function createConnectionManager(storage: AsyncStorage) { // do not set smart wallet as last active EOA if (activeWallet.id !== "smart") { await storage.setItem(LAST_ACTIVE_EOA_ID, activeWallet.id); + await storage.setItem(LAST_USED_WALLET_ID, activeWallet.id); } };