"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react"; import { getMe, getTenant, refreshTokens, } from "@/lib/meshkee/api"; import { ACCESS_TOKEN_KEY, REFRESH_TOKEN_KEY, } from "@/lib/meshkee/config"; import type { AuthUser } from "@/lib/meshkee/types"; type AuthContextValue = { user: AuthUser | null; isAuthenticated: boolean; ready: boolean; businessId: string | null; isWebsiteAdmin: boolean; getAccessToken: () => string | null; logout: () => void; refreshUser: () => Promise; }; const AuthContext = createContext(null); function readToken(key: string) { if (typeof window === "undefined") return null; return localStorage.getItem(key); } function writeTokens(accessToken: string, refreshToken: string) { localStorage.setItem(ACCESS_TOKEN_KEY, accessToken); localStorage.setItem(REFRESH_TOKEN_KEY, refreshToken); } function clearTokens() { localStorage.removeItem(ACCESS_TOKEN_KEY); localStorage.removeItem(REFRESH_TOKEN_KEY); } export function AuthProvider({ children }: { children: React.ReactNode }) { const [user, setUser] = useState(null); const [ready, setReady] = useState(false); const [businessId, setBusinessId] = useState(null); const logout = useCallback(() => { clearTokens(); setUser(null); }, []); const getAccessToken = useCallback(() => readToken(ACCESS_TOKEN_KEY), []); const refreshUser = useCallback(async () => { let accessToken = readToken(ACCESS_TOKEN_KEY); const refreshToken = readToken(REFRESH_TOKEN_KEY); if (!accessToken && !refreshToken) { setUser(null); return; } try { if (!accessToken && refreshToken) { const tokens = await refreshTokens(refreshToken); writeTokens(tokens.accessToken, tokens.refreshToken); accessToken = tokens.accessToken; } if (!accessToken) { setUser(null); return; } const me = await getMe(accessToken); setUser(me); } catch { if (refreshToken) { try { const tokens = await refreshTokens(refreshToken); writeTokens(tokens.accessToken, tokens.refreshToken); const me = await getMe(tokens.accessToken); setUser(me); return; } catch { clearTokens(); setUser(null); } } else { clearTokens(); setUser(null); } } }, []); useEffect(() => { let cancelled = false; (async () => { try { try { const tenant = await getTenant(); if (!cancelled) setBusinessId(String(tenant.id)); } catch { if (!cancelled) setBusinessId(null); } await refreshUser(); } catch { if (!cancelled) { clearTokens(); setUser(null); } } finally { if (!cancelled) setReady(true); } })().catch(() => { // swallow unexpected bootstrap rejections (e.g. aborted HMR) }); return () => { cancelled = true; }; }, [refreshUser]); const isWebsiteAdmin = useMemo(() => { if (!user || !businessId) return false; if (user.isSuperAdmin || user.roles?.includes("super_admin")) return true; return Boolean( user.businesses?.some( (biz) => String(biz.id) === String(businessId) && (biz.isOwner || Boolean(biz.teamRole)), ), ); }, [user, businessId]); const value = useMemo( () => ({ user, isAuthenticated: Boolean(user), ready, businessId, isWebsiteAdmin, getAccessToken, logout, refreshUser, }), [ user, ready, businessId, isWebsiteAdmin, getAccessToken, logout, refreshUser, ], ); return {children}; } export function useAuth() { const ctx = useContext(AuthContext); if (!ctx) throw new Error("useAuth must be used within AuthProvider"); return ctx; }