Oversight admins can now create campaigns, upload proofs, and flag/resolve issues when they have an agency assigned. They retain all existing cross-agency read access for analytics, auditing, and user management. Oversight admins without an agency see a read-only campaigns view. Changes: - Add oversight_admin to canWrite permission in UserContext - Guard readOnly for oversight_admin without agency in App.tsx - Remove oversight_admin block from require_write_access dependency - Remove WebSocket oversight_admin upload block in main.py - Require agency for oversight_admin campaign creation in routes.py Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
89 lines
3 KiB
TypeScript
89 lines
3 KiB
TypeScript
import React, { createContext, useContext, useState, useEffect } from 'react';
|
|
import apiService from '../services/apiService';
|
|
import type { UserRole, AppUser } from '../types';
|
|
|
|
interface UserContextValue {
|
|
user: AppUser | null;
|
|
isLoading: boolean;
|
|
/** Convenience booleans derived from user.role */
|
|
isSuperAdmin: boolean;
|
|
isOversightAdmin: boolean;
|
|
canWrite: boolean;
|
|
canSeeAnalytics: boolean;
|
|
canSeeAuditing: boolean;
|
|
canSeeKnowledgeBase: boolean;
|
|
canSeeSettings: boolean;
|
|
canSeeUserManagement: boolean;
|
|
canEditSettings: boolean;
|
|
/** True when the user exists but has no agency and is not an admin */
|
|
isUnassigned: boolean;
|
|
/** Re-fetch user from backend (e.g. after role change) */
|
|
refresh: () => Promise<void>;
|
|
}
|
|
|
|
const UserContext = createContext<UserContextValue>({
|
|
user: null,
|
|
isLoading: true,
|
|
isSuperAdmin: false,
|
|
isOversightAdmin: false,
|
|
canWrite: false,
|
|
canSeeAnalytics: false,
|
|
canSeeAuditing: false,
|
|
canSeeKnowledgeBase: false,
|
|
canSeeSettings: false,
|
|
canSeeUserManagement: false,
|
|
canEditSettings: false,
|
|
isUnassigned: false,
|
|
refresh: async () => {},
|
|
});
|
|
|
|
export const useUser = () => useContext(UserContext);
|
|
|
|
export const UserProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
|
const [user, setUser] = useState<AppUser | null>(null);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
|
|
const fetchUser = async () => {
|
|
try {
|
|
const me = await apiService.getMe();
|
|
setUser({
|
|
id: me.id,
|
|
email: me.email,
|
|
name: me.name,
|
|
role: me.role as UserRole,
|
|
agencyId: me.agency_id,
|
|
agencyName: me.agency_name,
|
|
});
|
|
} catch (error) {
|
|
console.error('Failed to fetch current user:', error);
|
|
setUser(null);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchUser();
|
|
}, []);
|
|
|
|
const role = user?.role;
|
|
|
|
const value: UserContextValue = {
|
|
user,
|
|
isLoading,
|
|
isSuperAdmin: role === 'super_admin',
|
|
isOversightAdmin: role === 'oversight_admin',
|
|
canWrite: role === 'super_admin' || role === 'oversight_admin' || role === 'agency_admin' || role === 'basic_user',
|
|
canSeeAnalytics: role === 'super_admin' || role === 'oversight_admin' || role === 'agency_admin',
|
|
canSeeAuditing: role === 'super_admin' || role === 'oversight_admin',
|
|
canSeeKnowledgeBase: role === 'super_admin',
|
|
canSeeSettings: role === 'super_admin' || role === 'oversight_admin' || role === 'agency_admin',
|
|
canSeeUserManagement: role === 'super_admin' || role === 'oversight_admin',
|
|
canEditSettings: role === 'super_admin',
|
|
isUnassigned: user != null && user.agencyId == null
|
|
&& role !== 'super_admin' && role !== 'oversight_admin',
|
|
refresh: fetchUser,
|
|
};
|
|
|
|
return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
|
|
};
|