feat: implement account center for user profile management, payments, and wallet binding.

This commit is contained in:
2569718930@qq.com
2026-03-13 11:51:01 +08:00
parent c031d9caeb
commit 04fda60fb4
+58 -11
View File
@@ -246,24 +246,47 @@ export function AccountCenter() {
const supabaseReady = hasSupabasePublicEnv(); const supabaseReady = hasSupabasePublicEnv();
/**
* Returns a valid access token, refreshing the session if the stored one
* is missing or close to expiry. Throws if the user is not authenticated.
*/
const getValidAccessToken = useCallback(async (): Promise<string> => {
if (!supabaseReady) throw new Error("Supabase 未配置,无法获取登录凭证。");
const client = getSupabaseBrowserClient();
// First try the cached session.
const {
data: { session: cached },
} = await client.auth.getSession();
const cachedToken = String(cached?.access_token || "").trim();
if (cachedToken) return cachedToken;
// Session missing or expired — force a refresh.
const {
data: { session: refreshed },
error,
} = await client.auth.refreshSession();
const refreshedToken = String(refreshed?.access_token || "").trim();
if (refreshedToken) return refreshedToken;
throw new Error(
error?.message
? `登录会话已失效 (${error.message}),请退出后重新登录。`
: "登录会话已失效,请退出后重新登录。",
);
}, [supabaseReady]);
const buildAuthedHeaders = useCallback( const buildAuthedHeaders = useCallback(
async (withJson = false): Promise<Record<string, string>> => { async (withJson = false): Promise<Record<string, string>> => {
const headers: Record<string, string> = {}; const headers: Record<string, string> = {};
if (withJson) headers["Content-Type"] = "application/json"; if (withJson) headers["Content-Type"] = "application/json";
if (!supabaseReady) return headers; if (!supabaseReady) return headers;
try { try {
const client = getSupabaseBrowserClient(); const token = await getValidAccessToken();
const { headers.Authorization = `Bearer ${token}`;
data: { session },
} = await client.auth.getSession();
const accessToken = String(session?.access_token || "").trim();
if (accessToken) headers.Authorization = `Bearer ${accessToken}`;
} catch { } catch {
// no-op // Non-authenticated page load — silently skip.
} }
return headers; return headers;
}, },
[supabaseReady], [supabaseReady, getValidAccessToken],
); );
const loadPaymentSnapshot = useCallback(async () => { const loadPaymentSnapshot = useCallback(async () => {
@@ -590,14 +613,26 @@ export function AccountCenter() {
setPaymentBusy(true); setPaymentBusy(true);
try { try {
// Ensure we have a valid token BEFORE opening the wallet modal.
let accessToken: string;
try {
accessToken = await getValidAccessToken();
} catch (tokenErr) {
setPaymentError(String(tokenErr));
setPaymentBusy(false);
return;
}
const authHeaders: Record<string, string> = {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
};
const accounts = (await eth.request({ const accounts = (await eth.request({
method: "eth_requestAccounts", method: "eth_requestAccounts",
})) as string[]; })) as string[];
const address = String(accounts?.[0] || "").toLowerCase(); const address = String(accounts?.[0] || "").toLowerCase();
if (!address) throw new Error("钱包账户为空"); if (!address) throw new Error("钱包账户为空");
const authHeaders = await buildAuthedHeaders(true);
setWalletAddress(address); setWalletAddress(address);
const challengeRes = await fetch("/api/payments/wallets/challenge", { const challengeRes = await fetch("/api/payments/wallets/challenge", {
method: "POST", method: "POST",
@@ -667,7 +702,19 @@ export function AccountCenter() {
setPaymentBusy(true); setPaymentBusy(true);
try { try {
const authHeaders = await buildAuthedHeaders(true); // Ensure we have a valid token BEFORE switching chain / sending tx.
let accessToken: string;
try {
accessToken = await getValidAccessToken();
} catch (tokenErr) {
setPaymentError(String(tokenErr));
setPaymentBusy(false);
return;
}
const authHeaders: Record<string, string> = {
"Content-Type": "application/json",
Authorization: `Bearer ${accessToken}`,
};
const targetChainId = Number(paymentConfig.chain_id || 137); const targetChainId = Number(paymentConfig.chain_id || 137);
await ensureTargetChain(eth, targetChainId); await ensureTargetChain(eth, targetChainId);