feat: simplify wallet login options
Some checks failed
Deploy Staging (terry-wallet-login) / deploy (push) Failing after 29s

This commit is contained in:
TerryM
2026-06-04 10:52:41 +08:00
parent fb904d3a55
commit 65dee3a37e
5 changed files with 63 additions and 97 deletions

View File

@@ -0,0 +1 @@
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg"><g clip-path="url(#a)"><path d="M512 0H0v512h512V0Z" fill="url(#b)"/><path d="M425.708 148.325c11.246 152.315-86.662 224.305-174.433 231.983-81.602 7.136-158.413-43.005-165.152-120.043-5.558-63.646 33.778-90.743 64.684-93.443 31.787-2.787 58.5 19.134 60.818 45.676 2.231 25.518-13.691 37.134-24.765 38.1-8.758.768-19.776-4.549-20.77-15.965-.854-9.809 2.871-11.145 1.961-21.566-1.62-18.553-17.798-20.714-26.655-19.946-10.719.939-30.167 13.449-27.438 44.611 2.744 31.432 32.882 56.269 72.39 52.814 42.634-3.725 72.318-36.92 74.55-83.478-.021-2.466.499-4.907 1.521-7.152l.014-.056c.459-.975.997-1.912 1.606-2.8.911-1.365 2.077-2.872 3.583-4.522.015-.042.015-.042.043-.042 1.094-1.237 2.417-2.573 3.909-4.009 18.624-17.571 85.696-58.012 149.129-44.89a6.42 6.42 0 0 1 4.163 5.728Z" fill="#fff"/></g><defs><linearGradient id="b" x1="459.192" y1="122.156" x2="22.611" y2="297.091" gradientUnits="userSpaceOnUse"><stop stop-color="#0CC5FF"/><stop offset="1" stop-color="#007FFF"/></linearGradient><clipPath id="a"><path fill="#fff" d="M0 0h512v512H0z"/></clipPath></defs></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -0,0 +1 @@
<svg width="28px" height="28px" viewBox="0 0 28 28" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><title>tokenpocket</title><defs><linearGradient x1="107.511425%" y1="50.0147427%" x2="0.0459570557%" y2="50.0147427%" id="linearGradient-1"><stop stop-color="#FFFFFF" offset="0%"></stop><stop stop-color="#FFFFFF" stop-opacity="0.3233" offset="96.67%"></stop><stop stop-color="#FFFFFF" stop-opacity="0.3" offset="100%"></stop></linearGradient></defs><g id="p1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g id="tokenpocket" fill-rule="nonzero"><polygon id="path" fill="#2980FE" points="27.9874275 0 0 0 0 28 27.9874275 28"></polygon><g id="group" transform="translate(5.107577, 7.574219)"><path d="M6.28678209,4.45186719 L6.29988209,4.45186719 C6.28678209,4.42028824 6.28678209,4.38344613 6.28678209,4.35186719 L6.28678209,4.45186719 Z" id="path" fill="#29AEFF"></path><path d="M13.085927,5.10051172 L9.30493171,5.10051172 L9.30493171,12.2247344 C9.30493171,12.561418 9.56568007,12.8336523 9.88819083,12.8336523 L12.5026417,12.8336523 C12.8251787,12.8336523 13.085927,12.561418 13.085927,12.2247344 L13.085927,5.10051172 Z" id="path" fill="#FFFFFF"></path><path d="M7.47966698,0 L7.35271094,0 L0.583285313,0 C0.260748363,0 0,0.272207031 0,0.608917969 L0,3.08035547 C0,3.41706641 0.260748363,3.68927344 0.583285313,3.68927344 L2.17184659,3.68927344 L2.80316932,3.68927344 L2.80316932,4.41995313 L2.80316932,12.2426445 C2.80316932,12.5793555 3.06391768,12.8515625 3.38642844,12.8515625 L5.87051824,12.8515625 C6.193029,12.8515625 6.45377736,12.5793555 6.45377736,12.2426445 L6.45377736,4.41995313 L6.45377736,4.35192188 L6.45377736,3.68927344 L7.08510009,3.68927344 L7.34241721,3.68927344 L7.46937325,3.68927344 C8.4437942,3.68927344 9.23635921,2.86187891 9.23635921,1.84463672 C9.24665295,0.827394531 8.45408793,0 7.47966698,0 Z" id="path" fill="#FFFFFF"></path><path d="M13.0894107,5.10051172 L13.0894107,10.0720703 C13.2197979,10.1043086 13.3535903,10.1293828 13.49084,10.150875 C13.6829897,10.1795313 13.8819757,10.1974414 14.0809878,10.2010234 C14.0912816,10.2010234 14.1015753,10.2010234 14.1153003,10.2010234 L14.1153003,6.24667969 C13.5423087,6.20727734 13.0894107,5.70940234 13.0894107,5.10051172 Z" id="path" fill="url(#linearGradient-1)"></path><path d="M14.1907091,0 C11.4939345,0 9.30493171,2.28519922 9.30493171,5.10051172 C9.30493171,7.52182812 10.9209429,9.54912109 13.0893583,10.0720703 L13.0893583,5.10051172 C13.0893583,4.46651953 13.5834312,3.95073438 14.1907091,3.95073438 C14.7980131,3.95073438 15.2920861,4.46651953 15.2920861,5.10051172 C15.2920861,5.63420703 14.9455566,6.08193359 14.4720711,6.21085938 C14.3828587,6.23593359 14.2867839,6.25026172 14.1907091,6.25026172 L14.1907091,10.2010234 C14.2867839,10.2010234 14.3794275,10.1974414 14.4720711,10.1938594 C17.0384846,10.039832 19.0765167,7.81910938 19.0765167,5.10051172 C19.0799439,2.28519922 16.8909411,0 14.1907091,0 Z" id="path" fill="#FFFFFF"></path><path d="M14.2117905,10.2010234 L14.2117905,6.25026172 C14.1770295,6.25026172 14.1465846,6.25026172 14.1117905,6.24667969 L14.1117905,10.2010234 C14.1465846,10.2010234 14.1813788,10.2010234 14.2117905,10.2010234 Z" id="path" fill="#FFFFFF"></path></g></g></g></svg>

After

Width:  |  Height:  |  Size: 3.2 KiB

View File

@@ -7,7 +7,6 @@ import {
} from "@rainbow-me/rainbowkit";
import {
imTokenWallet,
metaMaskWallet,
tokenPocketWallet,
} from "@rainbow-me/rainbowkit/wallets";
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
@@ -22,7 +21,7 @@ const connectors = connectorsForWallets(
[
{
groupName: "ARK Library",
wallets: [metaMaskWallet, imTokenWallet, tokenPocketWallet],
wallets: [imTokenWallet, tokenPocketWallet],
},
],
{

View File

@@ -1,11 +1,8 @@
import type { WalletKind } from "./injected";
type Brand = { bg: string; label: string };
const brands: Record<WalletKind, Brand> = {
tokenPocket: { bg: "#2980FE", label: "TP" },
metaMask: { bg: "#F6851B", label: "M" },
imToken: { bg: "#11C4D1", label: "im" },
const logos: Partial<Record<WalletKind, string>> = {
tokenPocket: "/assets/ark-library/wallets/tokenpocket.svg",
imToken: "/assets/ark-library/wallets/imtoken.svg",
};
export function WalletBrandIcon({
@@ -15,14 +12,17 @@ export function WalletBrandIcon({
kind: WalletKind;
size?: number;
}) {
const brand = brands[kind];
const logo = logos[kind];
if (!logo) return null;
return (
<span
<img
src={logo}
alt=""
aria-hidden="true"
style={{ width: size, height: size, backgroundColor: brand.bg }}
className="inline-flex shrink-0 items-center justify-center rounded-lg text-[11px] font-bold leading-none text-white"
>
{brand.label}
</span>
width={size}
height={size}
className="inline-flex shrink-0 rounded-lg"
/>
);
}

View File

@@ -1,11 +1,13 @@
import { QRCodeSVG } from "qrcode.react";
import { LoaderCircle, X } from "lucide-react";
import { useEffect, useState } from "react";
import { useI18n } from "../i18n";
import { walletDeepLink } from "./deepLinks";
import { getInjectedWallet, type WalletKind } from "./injected";
import { useWallet } from "./WalletProvider";
import { useWalletConnectLogin } from "./useWalletConnectLogin";
import {
connectInjectedWallet,
getInjectedWallet,
type WalletKind,
} from "./injected";
import { localWalletToken, useWallet } from "./WalletProvider";
import { WalletBrandIcon } from "./WalletBrandIcon";
const AUTO_LOGIN_PARAM = "autoLogin";
@@ -20,7 +22,9 @@ function buildAutoLoginDappUrl(kind: WalletKind): string {
return url.toString();
}
const wallets: WalletKind[] = ["tokenPocket", "metaMask", "imToken"];
const wallets: WalletKind[] = ["tokenPocket", "imToken"];
type LoginState = "idle" | "connecting";
function isMobileDevice(): boolean {
if (typeof navigator === "undefined") return false;
@@ -31,60 +35,66 @@ function isMobileDevice(): boolean {
export function WalletLoginModal() {
const { t } = useI18n();
const { closeLoginModal, loginModalOpen } = useWallet();
const wc = useWalletConnectLogin();
const { closeLoginModal, completeLogin, loginModalOpen } = useWallet();
const [selected, setSelected] = useState<WalletKind | null>(null);
const [mobileDevice, setMobileDevice] = useState(false);
const resetWalletConnect = wc.reset;
const [state, setState] = useState<LoginState>("idle");
const [error, setError] = useState("");
useEffect(() => {
if (!loginModalOpen) return;
setMobileDevice(isMobileDevice());
setSelected(null);
resetWalletConnect();
}, [loginModalOpen, resetWalletConnect]);
setState("idle");
setError("");
}, [loginModalOpen]);
if (!loginModalOpen) return null;
const walletName = (kind: WalletKind) => t(walletNameKey(kind));
const walletHint = (kind: WalletKind) => {
if (kind === "tokenPocket") {
return mobileDevice
? t("walletTpMobileDesc")
: t("walletTokenPocketQrDesc");
}
return t("walletRainbowFallbackDesc");
};
const busy = wc.state !== "idle";
const walletHint = () =>
mobileDevice ? t("walletChooseMobile") : t("walletDesktopHint");
const busy = state !== "idle";
const close = () => {
closeLoginModal();
setSelected(null);
wc.reset();
setState("idle");
setError("");
};
const selectWallet = (kind: WalletKind) => {
setSelected(kind);
wc.reset();
setError("");
};
const startWalletLogin = (kind: WalletKind, mode: "deeplink" | "qr") => {
const loginInjected = async (kind: WalletKind) => {
setSelected(kind);
void wc.start(kind, mode);
setState("connecting");
setError("");
try {
const address = await connectInjectedWallet(kind);
completeLogin(localWalletToken(address), address);
} catch (err) {
setState("idle");
setError(err instanceof Error ? err.message : t("walletLoginFailed"));
}
};
const openWalletAppDirect = (kind: WalletKind) => {
if (getInjectedWallet(kind)) {
startWalletLogin(kind, "deeplink");
void loginInjected(kind);
return;
}
if (mobileDevice && supportsDirectPull(kind)) {
setSelected(kind);
setError("");
const deeplink = walletDeepLink(kind, buildAutoLoginDappUrl(kind));
window.location.href = deeplink;
return;
}
startWalletLogin(kind, "deeplink");
setSelected(kind);
setError(t("walletInstallSelected").replace("{wallet}", walletName(kind)));
};
return (
@@ -120,8 +130,7 @@ export function WalletLoginModal() {
<div className="mt-5 grid gap-2">
{wallets.map((kind) => {
const active = selected === kind;
const connecting = active && wc.state === "connecting";
const signing = active && wc.state === "signing";
const connecting = active && state === "connecting";
return (
<div
key={kind}
@@ -136,9 +145,9 @@ export function WalletLoginModal() {
onClick={() =>
mobileDevice
? selectWallet(kind)
: startWalletLogin(kind, "qr")
: openWalletAppDirect(kind)
}
disabled={!wc.available || busy}
disabled={busy}
className="flex w-full items-center gap-3 text-left disabled:cursor-wait disabled:opacity-70"
>
<WalletBrandIcon kind={kind} size={32} />
@@ -147,38 +156,24 @@ export function WalletLoginModal() {
{walletName(kind)}
</span>
<span className="mt-1 block text-xs leading-5 text-neutral-400">
{connecting
? t("walletConnecting")
: signing
? t("walletSigning")
: walletHint(kind)}
{connecting ? t("walletConnecting") : walletHint()}
</span>
</span>
{connecting || signing ? (
{connecting ? (
<LoaderCircle className="h-4 w-4 animate-spin text-ark-gold" />
) : null}
</button>
{mobileDevice && active ? (
<div className="mt-3 grid grid-cols-2 gap-2">
<div className="mt-3">
<button
type="button"
onClick={() => openWalletAppDirect(kind)}
disabled={
supportsDirectPull(kind) ? busy : !wc.available || busy
}
className="rounded-full bg-ark-gold px-3 py-2 text-sm font-bold text-black transition hover:bg-ark-gold2 disabled:cursor-wait disabled:opacity-70"
disabled={busy}
className="w-full rounded-full bg-ark-gold px-3 py-2 text-sm font-bold text-black transition hover:bg-ark-gold2 disabled:cursor-wait disabled:opacity-70"
>
{t("walletOpenWalletApp")}
</button>
<button
type="button"
onClick={() => startWalletLogin(kind, "qr")}
disabled={!wc.available || busy}
className="rounded-full border border-ark-gold/50 px-3 py-2 text-sm font-semibold text-ark-gold transition hover:bg-ark-gold/10 disabled:cursor-wait disabled:opacity-70"
>
{t("walletQrLogin")}
</button>
</div>
) : null}
</div>
@@ -186,37 +181,9 @@ export function WalletLoginModal() {
})}
</div>
{!wc.available ? (
<p className="mt-4 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-sm text-neutral-400">
{t("walletRainbowUnavailable")}
</p>
) : null}
{selected && wc.qrUri ? (
<div className="mt-4 grid place-items-center rounded-2xl bg-white p-4 text-center">
<QRCodeSVG value={wc.qrUri} size={180} level="M" />
</div>
) : null}
{wc.error ? (
{error ? (
<p className="mt-4 rounded-2xl border border-red-500/30 bg-red-500/10 px-4 py-3 text-sm text-red-200">
{wc.error}
</p>
) : null}
{selected ? (
<div className="mt-4 rounded-2xl border border-white/10 bg-white/[0.03] px-4 py-3 text-xs leading-5 text-neutral-400">
<p className="font-semibold text-neutral-200">Wallet debug</p>
<p>state: {wc.state}</p>
<p>connected: {wc.isConnected ? "yes" : "no"}</p>
<p className="break-all">address: {wc.address || "-"}</p>
<p>qr: {wc.qrUri ? "received" : "-"}</p>
</div>
) : null}
{selected ? (
<p className="mt-4 text-xs leading-5 text-amber-300/80">
{t("walletNetworkWarning")}
{error}
</p>
) : null}
</div>
@@ -225,7 +192,5 @@ export function WalletLoginModal() {
}
function walletNameKey(kind: WalletKind): string {
if (kind === "tokenPocket") return "walletTokenPocket";
if (kind === "metaMask") return "walletMetaMask";
return "walletImToken";
return kind === "tokenPocket" ? "walletTokenPocket" : "walletImToken";
}