diff --git a/app/providers.tsx b/app/providers.tsx index 6ea9db5..46533ef 100644 --- a/app/providers.tsx +++ b/app/providers.tsx @@ -12,11 +12,11 @@ import { Toaster } from "sonner" function AuthBootstrap() { const login = useAuthStore((s) => s.login) - const isAuthenticated = useAuthStore((s) => s.isAuthenticated) + const logout = useAuthStore((s) => s.logout) const tried = useRef(false) useEffect(() => { - if (tried.current || isAuthenticated) return + if (tried.current) return tried.current = true getCurrentUserForLogin() @@ -24,9 +24,9 @@ function AuthBootstrap() { login(user, user.verifiedRoles ?? ["consumer"]) }) .catch(() => { - // no valid session — stay logged out + logout() }) - }, [login, isAuthenticated]) + }, [login, logout]) return null } diff --git a/store/auth.ts b/store/auth.ts index beba521..f00e2ea 100644 --- a/store/auth.ts +++ b/store/auth.ts @@ -15,6 +15,53 @@ const defaultNotificationPrefs: NotificationPrefs = { export type ThemePreference = "light" | "dark" | "system" +const STORAGE_KEY = "juwan-auth" + +interface PersistedAuth { + user: User + currentRole: UserRole + verifiedRoles: UserRole[] + verificationStatus: Partial> + themePreference: ThemePreference +} + +function loadPersisted(): PersistedAuth | null { + try { + const raw = localStorage.getItem(STORAGE_KEY) + if (!raw) return null + const parsed = JSON.parse(raw) as PersistedAuth + if (!parsed.user?.id) return null + return parsed + } catch { + return null + } +} + +function persist(state: PersistedAuth) { + try { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)) + } catch {} +} + +function persistCurrent(state: AuthState) { + if (!state.user) return + persist({ + user: state.user, + currentRole: state.currentRole, + verifiedRoles: state.verifiedRoles, + verificationStatus: state.verificationStatus, + themePreference: state.themePreference, + }) +} + +function clearPersisted() { + try { + localStorage.removeItem(STORAGE_KEY) + } catch {} +} + +const persisted = loadPersisted() + interface AuthState { isAuthenticated: boolean currentRole: UserRole @@ -25,9 +72,6 @@ interface AuthState { themePreference: ThemePreference user: User | null switchRole: (role: UserRole) => void - submitVerification: (role: UserRole, materials?: Record) => void - approveVerification: (role: UserRole) => void - rejectVerification: (role: UserRole, reason: string) => void setNotificationPref: (type: keyof NotificationPrefs, enabled: boolean) => void setThemePreference: (theme: ThemePreference) => void updateProfile: (patch: { nickname?: string; bio?: string; avatar?: string }) => void @@ -36,74 +80,21 @@ interface AuthState { } export const useAuthStore = create((set, get) => ({ - isAuthenticated: false, - currentRole: "consumer", - verifiedRoles: ["consumer"], - verificationStatus: { consumer: "approved" }, + isAuthenticated: persisted !== null, + currentRole: persisted?.currentRole ?? "consumer", + verifiedRoles: persisted?.verifiedRoles ?? ["consumer"], + verificationStatus: persisted?.verificationStatus ?? { consumer: "approved" }, verificationReasons: {}, notificationPrefs: defaultNotificationPrefs, - themePreference: "system", - user: null, + themePreference: persisted?.themePreference ?? "system", + user: persisted?.user ?? null, switchRole: (role) => { const { verifiedRoles } = get() if (verifiedRoles.includes(role)) { set({ currentRole: role }) + persistCurrent(get()) } }, - submitVerification: (role, _materials) => - set((state) => { - if (state.verifiedRoles.includes(role)) { - return state - } - - const nextReasons = { ...state.verificationReasons } - delete nextReasons[role] - - return { - verificationStatus: { - ...state.verificationStatus, - [role]: "pending", - }, - verificationReasons: nextReasons, - } - }), - approveVerification: (role) => - set((state) => { - if (state.verifiedRoles.includes(role) && state.verificationStatus[role] === "approved") { - return state - } - - const nextReasons = { ...state.verificationReasons } - delete nextReasons[role] - - return { - verifiedRoles: state.verifiedRoles.includes(role) - ? state.verifiedRoles - : [...state.verifiedRoles, role], - verificationStatus: { - ...state.verificationStatus, - [role]: "approved", - }, - verificationReasons: nextReasons, - } - }), - rejectVerification: (role, reason) => - set((state) => { - if (state.verifiedRoles.includes(role)) { - return state - } - - return { - verificationStatus: { - ...state.verificationStatus, - [role]: "rejected", - }, - verificationReasons: { - ...state.verificationReasons, - [role]: reason.trim() || "认证资料不完整,请补充后重试", - }, - } - }), setNotificationPref: (type, enabled) => set((state) => ({ notificationPrefs: { @@ -111,12 +102,15 @@ export const useAuthStore = create((set, get) => ({ [type]: enabled, }, })), - setThemePreference: (theme) => set({ themePreference: theme }), + setThemePreference: (theme) => { + set({ themePreference: theme }) + persistCurrent(get()) + }, updateProfile: (patch) => set((state) => { if (!state.user) return state - - return { + const next = { + ...state, user: { ...state.user, nickname: patch.nickname ?? state.user.nickname, @@ -124,6 +118,8 @@ export const useAuthStore = create((set, get) => ({ avatar: patch.avatar ?? state.user.avatar, }, } + persistCurrent(next) + return next }), login: (user, verifiedRoles, themePreference) => set((state) => { @@ -135,6 +131,16 @@ export const useAuthStore = create((set, get) => ({ return acc }, {}) + const nextTheme = themePreference ?? state.themePreference + + persist({ + user, + currentRole: user.role, + verifiedRoles: nextVerifiedRoles, + verificationStatus: nextVerificationStatus, + themePreference: nextTheme, + }) + return { isAuthenticated: true, user, @@ -143,10 +149,11 @@ export const useAuthStore = create((set, get) => ({ verificationStatus: nextVerificationStatus, verificationReasons: {}, notificationPrefs: state.notificationPrefs, - themePreference: themePreference ?? state.themePreference, + themePreference: nextTheme, } }), - logout: () => + logout: () => { + clearPersisted() set({ isAuthenticated: false, currentRole: "consumer", @@ -156,5 +163,6 @@ export const useAuthStore = create((set, get) => ({ notificationPrefs: defaultNotificationPrefs, themePreference: "system", user: null, - }), + }) + }, }))