'use client'; import { createContext, useContext, useState, useCallback, type ReactNode } from 'react'; import { createApiClient } from '@bytelyst/api-client'; import type { AuthConfig, AuthContextValue, BaseUser } from './types.js'; /** * Create a typed auth provider + hook for a specific user type. * * @example * ```tsx * const { AuthProvider, useAuth } = createAuthProvider({ * storagePrefix: "admin", * loginEndpoint: "/auth/login", * mapLoginResponse: (data) => ({ * user: data.user, * accessToken: data.accessToken, * refreshToken: data.refreshToken, * }), * }); * ``` */ export function createAuthProvider(config: AuthConfig) { const { storagePrefix, loginEndpoint, mapLoginResponse, onLoginFallback, onLogout } = config; const USER_KEY = `${storagePrefix}_auth_user`; const TOKEN_KEY = `${storagePrefix}_access_token`; const REFRESH_KEY = `${storagePrefix}_refresh_token`; const AuthContext = createContext | null>(null); function getStoredUser(): TUser | null { if (typeof window === 'undefined') return null; try { const stored = localStorage.getItem(USER_KEY); return stored ? JSON.parse(stored) : null; } catch { return null; } } function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(getStoredUser); const isLoading = false; const api = createApiClient({ baseUrl: '/api', getToken: () => (typeof window !== 'undefined' ? localStorage.getItem(TOKEN_KEY) : null), }); const login = useCallback( async (email: string, password: string) => { const { data, error } = await api.safeFetch(loginEndpoint, { method: 'POST', body: JSON.stringify({ email, password }), }); if (data && !error) { const mapped = mapLoginResponse(data); setUser(mapped.user); localStorage.setItem(USER_KEY, JSON.stringify(mapped.user)); localStorage.setItem(TOKEN_KEY, mapped.accessToken); localStorage.setItem(REFRESH_KEY, mapped.refreshToken); return true; } // Try fallback (e.g. mock credentials) when API is unavailable if (error && onLoginFallback) { const fallback = await onLoginFallback(email, password, error); if (fallback) { setUser(fallback.user); localStorage.setItem(USER_KEY, JSON.stringify(fallback.user)); localStorage.setItem(TOKEN_KEY, fallback.accessToken); localStorage.setItem(REFRESH_KEY, fallback.refreshToken); return true; } } return false; }, [api] ); const logout = useCallback(() => { setUser(null); localStorage.removeItem(USER_KEY); localStorage.removeItem(TOKEN_KEY); localStorage.removeItem(REFRESH_KEY); onLogout?.(); }, []); return ( {children} ); } function useAuth(): AuthContextValue { const ctx = useContext(AuthContext); if (!ctx) { throw new Error('useAuth must be used within an AuthProvider'); } return ctx; } return { AuthProvider, useAuth }; }