export type AuthUser = { id: string; name: string; email: string; role?: "admin" | "supervisor" | "staff" | "user"; phoneNumber?: string | null; notificationPreference?: "email" | "whatsapp" | "both"; twoFactorEnabled?: boolean; twoFactorMethod?: TwoFactorMethod | null; // True for roles that must use 2FA (admin, supervisor) — it can't be turned off. twoFactorRequired?: boolean; }; export type AuthResponse = AuthUser & { token: string; // Only present when the user ticked "Trust this device" on the 2FA step. deviceToken?: string; }; export type TwoFactorMethod = "email" | "whatsapp"; // What the login endpoint returns instead of a token when the account has 2FA on: // a code was sent to `destination` (masked) and must be exchanged for the token. export type TwoFactorChallenge = { twoFactorRequired?: true; challengeId: string; method: TwoFactorMethod; destination: string; // The role requires 2FA and it wasn't on yet: verifying this code turns it on. enrolling?: boolean; }; export function isTwoFactorChallenge(res: unknown): res is TwoFactorChallenge { return !!res && typeof res === "object" && (res as TwoFactorChallenge).twoFactorRequired === true; } const TOKEN_KEY = "hope_events_token"; // Remembers this browser after a 2FA code so the next login can skip it. Kept on // logout on purpose — it identifies the device, not the session. const DEVICE_KEY = "hope_events_device"; export function saveDeviceToken(token: string) { if (typeof window !== "undefined") localStorage.setItem(DEVICE_KEY, token); } export function getDeviceToken(): string | null { if (typeof window === "undefined") return null; return localStorage.getItem(DEVICE_KEY); } export function saveToken(token: string) { if (typeof window !== "undefined") localStorage.setItem(TOKEN_KEY, token); } export function getToken(): string | null { if (typeof window === "undefined") return null; return localStorage.getItem(TOKEN_KEY); } export function clearToken() { if (typeof window !== "undefined") localStorage.removeItem(TOKEN_KEY); }