SPB Git forge

spb/spinza

Public
8commits 1branches 0releases
1.6 MBsize
maindefault branch
16 days agolast push
TypeScript 97.6% SQL 1.4% JavaScript 0.5%
2.8 KB · 87 lines typescript
Raw Blame History
1"use client";23import { create } from "zustand";4import type { PublicUser, UserSettings, WalletView } from "@spinza/shared";5import { api } from "./api";67export interface SessionState {8  status: "loading" | "guest" | "authenticated";9  user: PublicUser | null;10  wallet: WalletView | null;11  settings: UserSettings | null;12  /** Session start (for break reminders). */13  sessionStartedAt: number;14  refresh: () => Promise<void>;15  setBalance: (balance: number) => void;16  setUserXp: (xp: number, level: number) => void;17  setSettings: (s: Partial<UserSettings>) => void;18  signOut: () => Promise<void>;19  hydrate: (data: { user: PublicUser; wallet: WalletView; settings: UserSettings } | null) => void;20}2122export const useSession = create<SessionState>((set, get) => ({23  status: "loading",24  user: null,25  wallet: null,26  settings: null,27  sessionStartedAt: Date.now(),28  hydrate: (data) => {29    if (data) set({ status: "authenticated", user: data.user, wallet: data.wallet, settings: data.settings });30    else set({ status: "guest", user: null, wallet: null, settings: null });31  },32  refresh: async () => {33    try {34      const data = await api<{ user: PublicUser; wallet: WalletView; settings: UserSettings }>("/api/user");35      set({ status: "authenticated", user: data.user, wallet: data.wallet, settings: data.settings });36    } catch {37      set({ status: "guest", user: null, wallet: null, settings: null });38    }39  },40  setBalance: (balance) => {41    const w = get().wallet;42    if (w) set({ wallet: { ...w, balance } });43  },44  setUserXp: (xp, level) => {45    const u = get().user;46    if (u) set({ user: { ...u, xp, level } });47  },48  setSettings: (s) => {49    const cur = get().settings;50    if (cur) set({ settings: { ...cur, ...s } });51    api("/api/user/settings", { method: "PATCH", json: s }).catch(() => {});52  },53  signOut: async () => {54    await api("/api/auth/logout", { method: "POST" }).catch(() => {});55    set({ status: "guest", user: null, wallet: null, settings: null });56  },57}));5859/* ------------------------------------------------------------- toasts */6061export interface Toast {62  id: number;63  title: string;64  description?: string;65  tone?: "default" | "success" | "danger" | "credit";66  ttl?: number;67}6869interface ToastState {70  toasts: Toast[];71  push: (t: Omit<Toast, "id">) => void;72  dismiss: (id: number) => void;73}7475let toastSeq = 1;76export const useToasts = create<ToastState>((set) => ({77  toasts: [],78  push: (t) => {79    const id = toastSeq++;80    set((s) => ({ toasts: [...s.toasts, { id, ttl: 4200, ...t }] }));81    setTimeout(() => set((s) => ({ toasts: s.toasts.filter((x) => x.id !== id) })), t.ttl ?? 4200);82  },83  dismiss: (id) => set((s) => ({ toasts: s.toasts.filter((x) => x.id !== id) })),84}));8586export const toast = (t: Omit<Toast, "id">) => useToasts.getState().push(t);87