"use client"; import { create } from "zustand"; import type { PublicUser, UserSettings, WalletView } from "@spinza/shared"; import { api } from "./api"; export interface SessionState { status: "loading" | "guest" | "authenticated"; user: PublicUser | null; wallet: WalletView | null; settings: UserSettings | null; /** Session start (for break reminders). */ sessionStartedAt: number; refresh: () => Promise; setBalance: (balance: number) => void; setUserXp: (xp: number, level: number) => void; setSettings: (s: Partial) => void; signOut: () => Promise; hydrate: (data: { user: PublicUser; wallet: WalletView; settings: UserSettings } | null) => void; } export const useSession = create((set, get) => ({ status: "loading", user: null, wallet: null, settings: null, sessionStartedAt: Date.now(), hydrate: (data) => { if (data) set({ status: "authenticated", user: data.user, wallet: data.wallet, settings: data.settings }); else set({ status: "guest", user: null, wallet: null, settings: null }); }, refresh: async () => { try { const data = await api<{ user: PublicUser; wallet: WalletView; settings: UserSettings }>("/api/user"); set({ status: "authenticated", user: data.user, wallet: data.wallet, settings: data.settings }); } catch { set({ status: "guest", user: null, wallet: null, settings: null }); } }, setBalance: (balance) => { const w = get().wallet; if (w) set({ wallet: { ...w, balance } }); }, setUserXp: (xp, level) => { const u = get().user; if (u) set({ user: { ...u, xp, level } }); }, setSettings: (s) => { const cur = get().settings; if (cur) set({ settings: { ...cur, ...s } }); api("/api/user/settings", { method: "PATCH", json: s }).catch(() => {}); }, signOut: async () => { await api("/api/auth/logout", { method: "POST" }).catch(() => {}); set({ status: "guest", user: null, wallet: null, settings: null }); }, })); /* ------------------------------------------------------------- toasts */ export interface Toast { id: number; title: string; description?: string; tone?: "default" | "success" | "danger" | "credit"; ttl?: number; } interface ToastState { toasts: Toast[]; push: (t: Omit) => void; dismiss: (id: number) => void; } let toastSeq = 1; export const useToasts = create((set) => ({ toasts: [], push: (t) => { const id = toastSeq++; set((s) => ({ toasts: [...s.toasts, { id, ttl: 4200, ...t }] })); setTimeout(() => set((s) => ({ toasts: s.toasts.filter((x) => x.id !== id) })), t.ttl ?? 4200); }, dismiss: (id) => set((s) => ({ toasts: s.toasts.filter((x) => x.id !== id) })), })); export const toast = (t: Omit) => useToasts.getState().push(t);