| 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<void>
|
| }
|
|
|
| const Ctx = createContext<AuthCtx | null>(null)
|
|
|
| export function AuthProvider({ children }: { children: ReactNode }) {
|
| const [user, setUser] = useState<User | null>(null)
|
| const [token, setToken] = useState<string | null>(() => localStorage.getItem('authToken'))
|
| const [loading, setLoading] = useState(true)
|
|
|
|
|
| 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 <Ctx.Provider value={value}>{children}</Ctx.Provider>
|
| }
|
|
|
| export function useAuth() {
|
| const v = useContext(Ctx)
|
| if (!v) throw new Error('useAuth outside AuthProvider')
|
| return v
|
| }
|
|
|