modcomms/frontend/contexts/UserContext.tsx
Vadym Samoilenko 0432635153 Grant oversight_admin write access to campaigns and proofs
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>
2026-03-03 13:08:54 +00:00

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>;
};