mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 18:28:15 +00:00
feat: agent detail page, campaign edit slideout, integration config, auth persistence
Agent Detail (/agent/:id): - Individual agent performance page with KPI cards + call log table - Clickable agent names in dashboard table link to detail view - Back button to Team Dashboard Campaign Edit Slideout: - Edit button on each campaign card - Slideout with name, status, budget, dates - Saves via updateCampaign GraphQL mutation Integration Config Slideout: - Configure button on each integration card - Per-integration form fields (Ozonetel, WhatsApp, Facebook, etc.) - Copy webhook URL, OAuth placeholder buttons Auth Persistence: - User data persisted to localStorage on login - Session restored on page refresh — no more logout on F5 - Stale tokens cleaned up automatically Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import type { ReactNode } from 'react';
|
||||
import { createContext, useCallback, useContext, useState } from 'react';
|
||||
import { createContext, useCallback, useContext, useEffect, useState } from 'react';
|
||||
|
||||
export type Role = 'executive' | 'admin' | 'cc-agent';
|
||||
|
||||
@@ -18,6 +18,7 @@ type AuthContextType = {
|
||||
isAdmin: boolean;
|
||||
isCCAgent: boolean;
|
||||
isAuthenticated: boolean;
|
||||
loading: boolean;
|
||||
loginWithUser: (userData: User) => void;
|
||||
login: () => void;
|
||||
logout: () => void;
|
||||
@@ -34,6 +35,21 @@ const DEFAULT_USER: User = {
|
||||
const getInitials = (firstName: string, lastName: string): string =>
|
||||
`${firstName[0] ?? ''}${lastName[0] ?? ''}`.toUpperCase();
|
||||
|
||||
const STORAGE_KEY = 'helix_user';
|
||||
|
||||
const loadPersistedUser = (): User | null => {
|
||||
try {
|
||||
const token = localStorage.getItem('helix_access_token');
|
||||
const stored = localStorage.getItem(STORAGE_KEY);
|
||||
if (token && stored) {
|
||||
return JSON.parse(stored) as User;
|
||||
}
|
||||
} catch {
|
||||
// corrupt data
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
||||
|
||||
export const useAuth = (): AuthContextType => {
|
||||
@@ -49,19 +65,32 @@ interface AuthProviderProps {
|
||||
}
|
||||
|
||||
export const AuthProvider = ({ children }: AuthProviderProps) => {
|
||||
const [user, setUser] = useState<User>(DEFAULT_USER);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
||||
const persisted = loadPersistedUser();
|
||||
const [user, setUser] = useState<User>(persisted ?? DEFAULT_USER);
|
||||
const [isAuthenticated, setIsAuthenticated] = useState(!!persisted);
|
||||
const [loading, setLoading] = useState(!persisted && !!localStorage.getItem('helix_access_token'));
|
||||
|
||||
// If we have a token but no persisted user, try to restore session
|
||||
useEffect(() => {
|
||||
if (!isAuthenticated && localStorage.getItem('helix_access_token') && !persisted) {
|
||||
// Token exists but no user data — could re-fetch profile here
|
||||
// For now, just clear stale token
|
||||
localStorage.removeItem('helix_access_token');
|
||||
localStorage.removeItem('helix_refresh_token');
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
setLoading(false);
|
||||
}
|
||||
}, [isAuthenticated, persisted]);
|
||||
|
||||
const isAdmin = user.role === 'admin';
|
||||
const isCCAgent = user.role === 'cc-agent';
|
||||
|
||||
// Real login — receives user profile from sidecar auth response
|
||||
const loginWithUser = useCallback((userData: User) => {
|
||||
setUser(userData);
|
||||
setIsAuthenticated(true);
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(userData));
|
||||
}, []);
|
||||
|
||||
// Simple login (for backward compat)
|
||||
const login = useCallback(() => {
|
||||
setIsAuthenticated(true);
|
||||
}, []);
|
||||
@@ -71,14 +100,19 @@ export const AuthProvider = ({ children }: AuthProviderProps) => {
|
||||
setIsAuthenticated(false);
|
||||
localStorage.removeItem('helix_access_token');
|
||||
localStorage.removeItem('helix_refresh_token');
|
||||
localStorage.removeItem(STORAGE_KEY);
|
||||
}, []);
|
||||
|
||||
const setRole = useCallback((role: Role) => {
|
||||
setUser(prev => ({ ...prev, role }));
|
||||
setUser(prev => {
|
||||
const updated = { ...prev, role };
|
||||
localStorage.setItem(STORAGE_KEY, JSON.stringify(updated));
|
||||
return updated;
|
||||
});
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<AuthContext.Provider value={{ user, isAdmin, isCCAgent, isAuthenticated, loginWithUser, login, logout, setRole }}>
|
||||
<AuthContext.Provider value={{ user, isAdmin, isCCAgent, isAuthenticated, loading, loginWithUser, login, logout, setRole }}>
|
||||
{children}
|
||||
</AuthContext.Provider>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user