feat: confirm desktop wallet address
Some checks failed
Deploy Staging (terry-wallet-login) / deploy (push) Failing after 32s

This commit is contained in:
TerryM
2026-06-04 11:55:13 +08:00
parent 90f27b050c
commit 4059ec3f20
9 changed files with 171 additions and 28 deletions

View File

@@ -16,6 +16,10 @@ function supportsDirectPull(kind: WalletKind): boolean {
return kind === "tokenPocket" || kind === "imToken";
}
function supportsDesktopExtension(kind: WalletKind): boolean {
return kind === "tokenPocket";
}
function buildAutoLoginDappUrl(kind: WalletKind): string {
const url = new URL(window.location.href);
url.searchParams.set(AUTO_LOGIN_PARAM, kind);
@@ -26,6 +30,11 @@ const wallets: WalletKind[] = ["tokenPocket", "imToken"];
type LoginState = "idle" | "connecting";
type PendingLogin = {
kind: WalletKind;
address: string;
};
type Translate = (key: string) => string;
function walletErrorMessage(error: unknown, t: Translate): string {
@@ -47,6 +56,7 @@ export function WalletLoginModal() {
const [mobileDevice, setMobileDevice] = useState(false);
const [state, setState] = useState<LoginState>("idle");
const [error, setError] = useState("");
const [pendingLogin, setPendingLogin] = useState<PendingLogin | null>(null);
useEffect(() => {
if (!loginModalOpen) return;
@@ -54,13 +64,14 @@ export function WalletLoginModal() {
setSelected(null);
setState("idle");
setError("");
setPendingLogin(null);
}, [loginModalOpen]);
if (!loginModalOpen) return null;
const walletName = (kind: WalletKind) => t(walletNameKey(kind));
const walletText = (key: string, kind: WalletKind) =>
t(key).replace("{wallet}", walletName(kind));
t(key).replaceAll("{wallet}", walletName(kind));
const walletHint = () =>
mobileDevice ? t("walletChooseMobile") : t("walletDesktopHint");
const busy = state !== "idle";
@@ -70,27 +81,49 @@ export function WalletLoginModal() {
setSelected(null);
setState("idle");
setError("");
setPendingLogin(null);
};
const selectWallet = (kind: WalletKind) => {
setSelected(kind);
setError("");
setPendingLogin(null);
};
const loginInjected = async (kind: WalletKind) => {
setSelected(kind);
setState("connecting");
setError("");
setPendingLogin(null);
try {
const address = await connectInjectedWallet(kind);
completeLogin(localWalletToken(address), address);
setPendingLogin({ kind, address });
setState("idle");
} catch (err) {
setState("idle");
setError(walletErrorMessage(err, t));
}
};
const confirmPendingLogin = () => {
if (!pendingLogin) return;
completeLogin(localWalletToken(pendingLogin.address), pendingLogin.address);
};
const cancelPendingLogin = () => {
setPendingLogin(null);
setSelected(null);
setState("idle");
setError("");
};
const openWalletAppDirect = (kind: WalletKind) => {
if (!mobileDevice && !supportsDesktopExtension(kind)) {
setSelected(kind);
setPendingLogin(null);
setError("");
return;
}
if (getInjectedWallet(kind)) {
void loginInjected(kind);
return;
@@ -103,6 +136,7 @@ export function WalletLoginModal() {
return;
}
setSelected(kind);
setPendingLogin(null);
setError(walletText("walletInstallSelected", kind));
};
@@ -192,31 +226,82 @@ export function WalletLoginModal() {
{!mobileDevice && active ? (
<div className="mt-3 rounded-2xl border border-white/10 bg-black/20 p-3 text-sm text-neutral-300">
<p className="font-semibold text-neutral-100">
{walletText("walletDesktopHelpTitle", kind)}
</p>
<ol className="mt-2 list-decimal space-y-1 pl-5 leading-6 text-neutral-400">
<li>{t("walletDesktopHelpUnlock")}</li>
<li>{t("walletDesktopHelpSelect")}</li>
<li>{walletText("walletDesktopHelpRetry", kind)}</li>
</ol>
<div className="mt-3 grid gap-2 sm:grid-cols-2">
<button
type="button"
onClick={() => openWalletAppDirect(kind)}
disabled={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"
>
{walletText("walletReconnectWallet", kind)}
</button>
<button
type="button"
onClick={() => openWalletInstall(kind)}
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"
>
{walletText("walletInstallWallet", kind)}
</button>
</div>
{pendingLogin?.kind === kind ? (
<>
<p className="font-semibold text-neutral-100">
{walletText("walletConfirmAddressTitle", kind)}
</p>
<p className="mt-2 text-xs leading-5 text-neutral-400">
{walletText("walletConfirmAddressDesc", kind)}
</p>
<p
className="mt-3 break-all rounded-xl border border-ark-gold/30 bg-ark-gold/10 px-3 py-2 font-mono text-sm text-ark-gold"
title={pendingLogin.address}
>
{pendingLogin.address}
</p>
<div className="mt-3 grid gap-2 sm:grid-cols-2">
<button
type="button"
onClick={confirmPendingLogin}
className="rounded-full bg-ark-gold px-3 py-2 text-sm font-bold text-black transition hover:bg-ark-gold2"
>
{t("walletConfirmLogin")}
</button>
<button
type="button"
onClick={cancelPendingLogin}
className="rounded-full border border-white/20 px-3 py-2 text-sm font-semibold text-neutral-200 transition hover:border-ark-gold/50 hover:text-ark-gold"
>
{t("walletCancelLogin")}
</button>
</div>
</>
) : supportsDesktopExtension(kind) ? (
<>
<p className="font-semibold text-neutral-100">
{walletText("walletDesktopHelpTitle", kind)}
</p>
<ol className="mt-2 list-decimal space-y-1 pl-5 leading-6 text-neutral-400">
<li>{t("walletDesktopHelpUnlock")}</li>
<li>{t("walletDesktopHelpSelect")}</li>
<li>{walletText("walletDesktopHelpRetry", kind)}</li>
</ol>
<div className="mt-3 grid gap-2 sm:grid-cols-2">
<button
type="button"
onClick={() => openWalletAppDirect(kind)}
disabled={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"
>
{walletText("walletReconnectWallet", kind)}
</button>
<button
type="button"
onClick={() => openWalletInstall(kind)}
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"
>
{walletText("walletInstallWallet", kind)}
</button>
</div>
</>
) : (
<>
<p className="font-semibold text-neutral-100">
{walletText("walletDesktopImTokenTitle", kind)}
</p>
<p className="mt-2 text-sm leading-6 text-neutral-400">
{walletText("walletDesktopImTokenDesc", kind)}
</p>
<button
type="button"
onClick={() => openWalletInstall(kind)}
className="mt-3 w-full rounded-full border border-ark-gold/50 px-3 py-2 text-sm font-semibold text-ark-gold transition hover:bg-ark-gold/10"
>
{walletText("walletDownloadApp", kind)}
</button>
</>
)}
</div>
) : null}
</div>

View File

@@ -31,7 +31,7 @@ export function openWalletDeepLink(kind: WalletKind): void {
}
const downloadUrls: Record<WalletKind, string> = {
tokenPocket: "https://www.tokenpocket.pro/en/download/app",
tokenPocket: "https://extension.tokenpocket.pro/",
metaMask: "https://metamask.io/download/",
imToken: "https://token.im/download",
};