"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode, } from "react"; import { clearUser, ensureThresholdReachedAt, loadUser, saveUser, sumOrders, type MockOrder, type UserSession, } from "@/lib/auth-types"; import { SPEND_THRESHOLD_USD } from "@/lib/quiz-rules"; import { ApiError, setApiAuthToken } from "@/lib/api"; import { loginWithPassword } from "@/lib/auth-api"; import { isRegisterPasswordValid } from "@/lib/password-rules"; import { registerWithEmail } from "@/lib/register-api"; import { fetchCustomUserInfo } from "@/lib/user-info-api"; type AuthContextValue = { user: UserSession | null; isReady: boolean; login: ( email: string, password: string, ) => Promise<{ ok: boolean; error?: string; message?: string }>; register: (input: { country: string; email: string; password: string; name: string; code: string; }) => Promise<{ ok: boolean; error?: string; message?: string }>; logout: () => void; updateProfile: ( patch: Partial>, ) => void; addMockOrder: (order: Omit) => void; /** 本地演示:将门槛达成时间设为 181 天前,用于验证问答解锁 */ demoUnlockQuizCountdown: () => void; }; const AuthContext = createContext(null); function newUserSession( email: string, name: string, seedOrders: MockOrder[] = [], ): UserSession { const thresholdReachedAt = ensureThresholdReachedAt(seedOrders, null); return { email, name, phone: "", identity: "", thresholdReachedAt, orders: seedOrders, scholarship: {}, }; } export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [isReady, setIsReady] = useState(false); useEffect(() => { /* eslint-disable react-hooks/set-state-in-effect -- hydrate once from localStorage after mount */ setUser(loadUser()); setIsReady(true); /* eslint-enable react-hooks/set-state-in-effect */ }, []); const persist = useCallback((next: UserSession | null) => { setUser(next); if (next) saveUser(next); else clearUser(); }, []); const login = useCallback( async (email: string, password: string) => { if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { return { ok: false, error: "invalid_email" }; } try { const { token, displayName } = await loginWithPassword( email.trim(), password, ); if (token) setApiAuthToken(token); const base = newUserSession(email.trim(), displayName); try { const info = await fetchCustomUserInfo(); persist({ ...base, email: info.email || base.email, name: info.name || base.name, phone: info.phone || base.phone, identity: info.identity || base.identity, }); } catch { persist(base); } return { ok: true }; } catch (e) { const message = e instanceof ApiError ? e.message : "登录失败"; return { ok: false, error: "api", message }; } }, [persist], ); const register = useCallback( async (input: { country: string; email: string; password: string; name: string; code: string; }) => { if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(input.email)) { return { ok: false, error: "invalid_email" }; } if (!isRegisterPasswordValid(input.password)) { return { ok: false, error: "weak_password" }; } if (!input.code.trim()) { return { ok: false, error: "invalid_code" }; } if (!input.country.trim()) { return { ok: false, error: "country_required" }; } try { const { token } = await registerWithEmail({ country: input.country.trim(), name: input.name.trim() || "学员", email: input.email.trim(), emailCode: input.code.trim(), password: input.password, }); if (token) setApiAuthToken(token); const base = newUserSession(input.email.trim(), input.name.trim() || "学员"); try { const info = await fetchCustomUserInfo(); persist({ ...base, email: info.email || base.email, name: info.name || base.name, phone: info.phone || base.phone, identity: info.identity || base.identity, }); } catch { persist(base); } return { ok: true }; } catch (e) { const message = e instanceof ApiError ? e.message : "注册失败"; return { ok: false, error: "api", message }; } }, [persist], ); const logout = useCallback(() => { setApiAuthToken(null); persist(null); }, [persist]); const updateProfile = useCallback( ( patch: Partial< Pick >, ) => { if (!user) return; persist({ ...user, ...patch, name: patch.name ?? user.name, phone: patch.phone ?? user.phone, identity: patch.identity ?? user.identity, scholarship: { ...user.scholarship, ...patch.scholarship }, }); }, [user, persist], ); const addMockOrder = useCallback( (order: Omit) => { if (!user) return; const nextOrder: MockOrder = { ...order, id: `ord_${Date.now()}`, createdAt: new Date().toISOString(), }; const orders = [...user.orders, nextOrder]; const thresholdReachedAt = ensureThresholdReachedAt( orders, user.thresholdReachedAt, ); persist({ ...user, orders, thresholdReachedAt, }); }, [user, persist], ); const demoUnlockQuizCountdown = useCallback(() => { if (!user) return; let orders = user.orders; if (sumOrders(orders) < SPEND_THRESHOLD_USD) { orders = [ ...orders, { id: `ord_demo_${Date.now()}`, amount: SPEND_THRESHOLD_USD, createdAt: new Date().toISOString(), title: "演示订单(验证问答解锁)", }, ]; } const past = new Date(); past.setDate(past.getDate() - 181); persist({ ...user, orders, thresholdReachedAt: past.toISOString(), }); }, [user, persist]); const value = useMemo( () => ({ user, isReady, login, register, logout, updateProfile, addMockOrder, demoUnlockQuizCountdown, }), [ user, isReady, login, register, logout, updateProfile, addMockOrder, demoUnlockQuizCountdown, ], ); return {children}; } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used within AuthProvider"); return ctx; }