d76866ac3b
Save user data, role, and verification status on login so that page refresh does not lose the session. On mount, AuthBootstrap always verifies against the backend via getCurrentUserForLogin to confirm the cookie JWT is still valid. Remove unused verification store methods (submitVerification, approveVerification, rejectVerification) — the verify page already calls lib/api/users.ts directly.
169 lines
4.6 KiB
TypeScript
169 lines
4.6 KiB
TypeScript
import type { User, UserRole, VerificationStatus } from "@/lib/types"
|
|
import { create } from "zustand"
|
|
|
|
interface NotificationPrefs {
|
|
order: boolean
|
|
community: boolean
|
|
system: boolean
|
|
}
|
|
|
|
const defaultNotificationPrefs: NotificationPrefs = {
|
|
order: true,
|
|
community: true,
|
|
system: false,
|
|
}
|
|
|
|
export type ThemePreference = "light" | "dark" | "system"
|
|
|
|
const STORAGE_KEY = "juwan-auth"
|
|
|
|
interface PersistedAuth {
|
|
user: User
|
|
currentRole: UserRole
|
|
verifiedRoles: UserRole[]
|
|
verificationStatus: Partial<Record<UserRole, VerificationStatus>>
|
|
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
|
|
verifiedRoles: UserRole[]
|
|
verificationStatus: Partial<Record<UserRole, VerificationStatus>>
|
|
verificationReasons: Partial<Record<UserRole, string>>
|
|
notificationPrefs: NotificationPrefs
|
|
themePreference: ThemePreference
|
|
user: User | null
|
|
switchRole: (role: UserRole) => void
|
|
setNotificationPref: (type: keyof NotificationPrefs, enabled: boolean) => void
|
|
setThemePreference: (theme: ThemePreference) => void
|
|
updateProfile: (patch: { nickname?: string; bio?: string; avatar?: string }) => void
|
|
login: (user: User, verifiedRoles?: UserRole[], themePreference?: ThemePreference) => void
|
|
logout: () => void
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>((set, get) => ({
|
|
isAuthenticated: persisted !== null,
|
|
currentRole: persisted?.currentRole ?? "consumer",
|
|
verifiedRoles: persisted?.verifiedRoles ?? ["consumer"],
|
|
verificationStatus: persisted?.verificationStatus ?? { consumer: "approved" },
|
|
verificationReasons: {},
|
|
notificationPrefs: defaultNotificationPrefs,
|
|
themePreference: persisted?.themePreference ?? "system",
|
|
user: persisted?.user ?? null,
|
|
switchRole: (role) => {
|
|
const { verifiedRoles } = get()
|
|
if (verifiedRoles.includes(role)) {
|
|
set({ currentRole: role })
|
|
persistCurrent(get())
|
|
}
|
|
},
|
|
setNotificationPref: (type, enabled) =>
|
|
set((state) => ({
|
|
notificationPrefs: {
|
|
...state.notificationPrefs,
|
|
[type]: enabled,
|
|
},
|
|
})),
|
|
setThemePreference: (theme) => {
|
|
set({ themePreference: theme })
|
|
persistCurrent(get())
|
|
},
|
|
updateProfile: (patch) =>
|
|
set((state) => {
|
|
if (!state.user) return state
|
|
const next = {
|
|
...state,
|
|
user: {
|
|
...state.user,
|
|
nickname: patch.nickname ?? state.user.nickname,
|
|
bio: patch.bio ?? state.user.bio,
|
|
avatar: patch.avatar ?? state.user.avatar,
|
|
},
|
|
}
|
|
persistCurrent(next)
|
|
return next
|
|
}),
|
|
login: (user, verifiedRoles, themePreference) =>
|
|
set((state) => {
|
|
const nextVerifiedRoles = verifiedRoles ?? user.verifiedRoles ?? [user.role]
|
|
const nextVerificationStatus =
|
|
user.verificationStatus ??
|
|
nextVerifiedRoles.reduce<Partial<Record<UserRole, VerificationStatus>>>((acc, role) => {
|
|
acc[role] = "approved"
|
|
return acc
|
|
}, {})
|
|
|
|
const nextTheme = themePreference ?? state.themePreference
|
|
|
|
persist({
|
|
user,
|
|
currentRole: user.role,
|
|
verifiedRoles: nextVerifiedRoles,
|
|
verificationStatus: nextVerificationStatus,
|
|
themePreference: nextTheme,
|
|
})
|
|
|
|
return {
|
|
isAuthenticated: true,
|
|
user,
|
|
currentRole: user.role,
|
|
verifiedRoles: nextVerifiedRoles,
|
|
verificationStatus: nextVerificationStatus,
|
|
verificationReasons: {},
|
|
notificationPrefs: state.notificationPrefs,
|
|
themePreference: nextTheme,
|
|
}
|
|
}),
|
|
logout: () => {
|
|
clearPersisted()
|
|
set({
|
|
isAuthenticated: false,
|
|
currentRole: "consumer",
|
|
verifiedRoles: ["consumer"],
|
|
verificationStatus: { consumer: "approved" },
|
|
verificationReasons: {},
|
|
notificationPrefs: defaultNotificationPrefs,
|
|
themePreference: "system",
|
|
user: null,
|
|
})
|
|
},
|
|
}))
|