import React, { createContext, useContext, useCallback, useMemo } from 'react';
import { useQuery, useQueryClient, type UseQueryResult } from '@tanstack/react-query';
import { authApi } from '@/api/auth';
import type { User, AuthState, SignupData, CompanyRole, CompanyPermission } from '@/types';
// ─── Role hierarchy ───────────────────────────────────────────────────
const ROLE_LEVEL: Record<CompanyRole, number> = {
owner: 5,
admin: 4,
manager: 3,
rep: 2,
viewer: 1,
};
// ─── Permission matrix ────────────────────────────────────────────────
// Maps each permission to the minimum role level required.
const PERMISSION_MIN_LEVEL: Record<CompanyPermission, number> = {
create_org: ROLE_LEVEL.owner, // Only owner
manage_team: ROLE_LEVEL.manager, // owner, admin, manager
view_analytics: ROLE_LEVEL.manager, // owner, admin, manager
edit_integrations: ROLE_LEVEL.admin, // owner, admin
view_reports: ROLE_LEVEL.viewer, // Everyone (rep+ for own, viewer+ for read-only)
edit_settings: ROLE_LEVEL.admin, // owner, admin
};
// ─── Check if role meets minimum level ─────────────────────────────────
function roleMeetsMinimum(role: CompanyRole | undefined, minLevel: number): boolean {
if (!role) return false;
return (ROLE_LEVEL[role] ?? 0) >= minLevel;
}
// ─── Context value ─────────────────────────────────────────────────────
const AuthContext = createContext<AuthState | undefined>(undefined);
export function AuthProvider({ children }: { children: React.ReactNode }) {
const queryClient = useQueryClient();
// ─── User data managed by React Query ────────────────────────────────
// staleTime of 5 min means repeated renders won't trigger refetches;
// any mutation that changes user data invalidates the cache.
const query: UseQueryResult<User | null, Error> = useQuery({
queryKey: ['auth-user'],
queryFn: authApi.getCurrentUser,
staleTime: 5 * 60 * 1000,
retry: false,
});
const user = query.data ?? null;
const isLoading = query.isLoading;
const login = useCallback(async (email: string, password: string) => {
await authApi.login(email, password);
// Invalidate so the cached user refetches with the new session
await queryClient.invalidateQueries({ queryKey: ['auth-user'] });
}, [queryClient]);
const logout = useCallback(async () => {
await authApi.logout();
// Clear cached user data so all consumers immediately see logged-out state
queryClient.setQueryData(['auth-user'], null);
}, [queryClient]);
const signup = useCallback(async (data: SignupData) => {
await authApi.signup(data);
await queryClient.invalidateQueries({ queryKey: ['auth-user'] });
}, [queryClient]);
const updateProfile = useCallback(async (full_name: string) => {
await authApi.updateProfile(full_name);
await queryClient.invalidateQueries({ queryKey: ['auth-user'] });
}, [queryClient]);
const changePassword = useCallback(async (currentPassword: string, newPassword: string) => {
return await authApi.changePassword(currentPassword, newPassword);
}, []);
const isRole = useCallback((role: string): boolean => {
return user?.role === role;
}, [user]);
const hasRole = useCallback((...roles: string[]): boolean => {
return !!user?.role && roles.includes(user.role);
}, [user]);
// ─── Company role helpers ─────────────────────────────────────────────
const getCompanyRole = useCallback((): CompanyRole | undefined => {
return user?.companyRole;
}, [user]);
const getRoleLevel = useCallback((): number => {
return ROLE_LEVEL[user?.companyRole as CompanyRole] ?? 0;
}, [user]);
const hasCompanyPermission = useCallback((permission: CompanyPermission): boolean => {
const required = PERMISSION_MIN_LEVEL[permission];
return roleMeetsMinimum(user?.companyRole as CompanyRole, required);
}, [user]);
const hasRoleAtLeast = useCallback((minRole: CompanyRole): boolean => {
const required = ROLE_LEVEL[minRole];
return roleMeetsMinimum(user?.companyRole as CompanyRole, required);
}, [user]);
const value: AuthState = useMemo(() => ({
user,
isAuthenticated: !!user,
isLoading,
login,
logout,
signup,
updateProfile,
changePassword,
isRole,
hasRole,
// Company role enforcement
companyRole: getCompanyRole(),
roleLevel: getRoleLevel(),
hasCompanyPermission,
hasRoleAtLeast,
}), [user, isLoading, login, logout, signup, updateProfile, changePassword, isRole, hasRole,
getCompanyRole, getRoleLevel, hasCompanyPermission, hasRoleAtLeast]);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
}
export function useAuth(): AuthState {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}