"use client"; import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from "react"; import { api, ApiError } from "./api"; import { components } from "./generated/api"; // Permission constants - must match backend/models.py Permission enum. // Backend exposes these via GET /api/meta/constants for validation. // TODO: Generate this from the backend endpoint at build time. export const Permission = { VIEW_COUNTER: "view_counter", INCREMENT_COUNTER: "increment_counter", USE_SUM: "use_sum", VIEW_AUDIT: "view_audit", MANAGE_OWN_PROFILE: "manage_own_profile", MANAGE_INVITES: "manage_invites", VIEW_OWN_INVITES: "view_own_invites", // Booking permissions (regular users) BOOK_APPOINTMENT: "book_appointment", VIEW_OWN_APPOINTMENTS: "view_own_appointments", CANCEL_OWN_APPOINTMENT: "cancel_own_appointment", // Availability/Appointments permissions (admin) MANAGE_AVAILABILITY: "manage_availability", VIEW_ALL_APPOINTMENTS: "view_all_appointments", CANCEL_ANY_APPOINTMENT: "cancel_any_appointment", } as const; export type PermissionType = (typeof Permission)[keyof typeof Permission]; // Use generated type from OpenAPI schema type User = components["schemas"]["UserResponse"]; interface AuthContextType { user: User | null; isLoading: boolean; login: (email: string, password: string) => Promise; register: (email: string, password: string, inviteIdentifier: string) => Promise; logout: () => Promise; hasPermission: (permission: PermissionType) => boolean; hasRole: (role: string) => boolean; } const AuthContext = createContext(null); export function AuthProvider({ children }: { children: ReactNode }) { const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); useEffect(() => { checkAuth(); }, []); const checkAuth = async () => { try { const userData = await api.get("/api/auth/me"); setUser(userData); } catch { // Not authenticated } finally { setIsLoading(false); } }; const login = async (email: string, password: string) => { try { const userData = await api.post("/api/auth/login", { email, password }); setUser(userData); } catch (err) { if (err instanceof ApiError) { const data = err.data as { detail?: string }; throw new Error(data?.detail || "Login failed"); } throw err; } }; const register = async (email: string, password: string, inviteIdentifier: string) => { try { const userData = await api.post("/api/auth/register", { email, password, invite_identifier: inviteIdentifier, }); setUser(userData); } catch (err) { if (err instanceof ApiError) { const data = err.data as { detail?: string }; throw new Error(data?.detail || "Registration failed"); } throw err; } }; const logout = async () => { try { await api.post("/api/auth/logout"); } catch { // Ignore errors on logout } setUser(null); }; const hasPermission = useCallback( (permission: PermissionType): boolean => { return user?.permissions.includes(permission) ?? false; }, [user] ); const hasRole = useCallback( (role: string): boolean => { return user?.roles.includes(role) ?? false; }, [user] ); return ( {children} ); } export function useAuth() { const context = useContext(AuthContext); if (!context) { throw new Error("useAuth must be used within an AuthProvider"); } return context; }