import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from 'react' import type { ReactNode } from 'react' import { apiFetch } from '../api' import type { CreationsStore } from '../lib/creations' import { ensureWorkshopSamplesInStore, loadCreations, mergeCreationsStores, replaceCreationsStore, syncCreationsToServerIfAuthed, } from '../lib/creations' export type User = { id: string email: string firstName: string lastName: string } type AuthCtx = { user: User | null token: string | null loading: boolean setAuth: (user: User, token: string) => void signOut: () => void refreshUser: () => Promise } const Ctx = createContext(null) export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null) const [token, setToken] = useState(() => localStorage.getItem('authToken')) const [loading, setLoading] = useState(true) /** Merge server-saved workshop records with localStorage, then upload merged snapshot. */ const hydrateWorkshopCreations = useCallback(async () => { const pr = await apiFetch('/api/users/me/profile') if (!pr.ok) return const profile = (await pr.json()) as { creations?: CreationsStore } const raw = profile.creations const remoteStore: CreationsStore = { personas: Array.isArray(raw?.personas) ? raw!.personas : [], advisorPanels: Array.isArray(raw?.advisorPanels) ? raw!.advisorPanels : [], } const localBefore = loadCreations() const merged = mergeCreationsStores(localBefore, remoteStore) const withSamples = ensureWorkshopSamplesInStore(merged) replaceCreationsStore(withSamples, { skipServerSync: true }) void syncCreationsToServerIfAuthed() }, []) const refreshUser = useCallback(async () => { const t = localStorage.getItem('authToken') if (!t) { setUser(null) setToken(null) return } const r = await apiFetch('/api/auth/me') if (r.ok) { const u = (await r.json()) as User setUser(u) setToken(t) localStorage.setItem('user', JSON.stringify(u)) await hydrateWorkshopCreations() } else { localStorage.removeItem('authToken') localStorage.removeItem('user') setUser(null) setToken(null) } }, [hydrateWorkshopCreations]) useEffect(() => { let cancelled = false ;(async () => { const t = localStorage.getItem('authToken') if (!t) { if (!cancelled) setLoading(false) return } const r = await apiFetch('/api/auth/me') if (cancelled) return if (r.ok) { const u = (await r.json()) as User setUser(u) setToken(t) localStorage.setItem('user', JSON.stringify(u)) await hydrateWorkshopCreations() } else { localStorage.removeItem('authToken') localStorage.removeItem('user') setUser(null) setToken(null) } setLoading(false) })() return () => { cancelled = true } }, [hydrateWorkshopCreations]) const setAuth = useCallback( (u: User, tok: string) => { localStorage.setItem('authToken', tok) localStorage.setItem('user', JSON.stringify(u)) setUser(u) setToken(tok) void hydrateWorkshopCreations() }, [hydrateWorkshopCreations], ) const signOut = useCallback(() => { localStorage.removeItem('authToken') localStorage.removeItem('user') setUser(null) setToken(null) }, []) const value = useMemo( () => ({ user, token, loading, setAuth, signOut, refreshUser }), [user, token, loading, setAuth, signOut, refreshUser], ) return {children} } export function useAuth() { const v = useContext(Ctx) if (!v) throw new Error('useAuth outside AuthProvider') return v }