Files
helix-engage/src/components/layout/app-shell.tsx
saridsa2 7af1ccb713 feat: wizard step indicator, wider dialog, campaigns in admin sidebar, clear leads shortcut
- Import wizard: added step indicator (numbered circles), widened to max-w-5xl
- Admin sidebar: added Marketing → Campaigns nav link
- Clear campaign leads: Ctrl+Shift+C shortcut with campaign picker modal (test-only)
- Test CSV data for all 3 campaigns
- Defect fixing plan + CSV import spec docs
- Session memory update

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 12:45:05 +05:30

88 lines
4.5 KiB
TypeScript

import { useEffect, type ReactNode } from 'react';
import { useLocation } from 'react-router';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faWifi, faWifiSlash } from '@fortawesome/pro-duotone-svg-icons';
import { Sidebar } from './sidebar';
import { SipProvider } from '@/providers/sip-provider';
import { useSip } from '@/providers/sip-provider';
import { CallWidget } from '@/components/call-desk/call-widget';
import { MaintOtpModal } from '@/components/modals/maint-otp-modal';
import { ClearCampaignLeadsModal } from '@/components/modals/clear-campaign-leads-modal';
import { AgentStatusToggle } from '@/components/call-desk/agent-status-toggle';
import { NotificationBell } from './notification-bell';
import { useAuth } from '@/providers/auth-provider';
import { useMaintShortcuts } from '@/hooks/use-maint-shortcuts';
import { useNetworkStatus } from '@/hooks/use-network-status';
import { cx } from '@/utils/cx';
interface AppShellProps {
children: ReactNode;
}
export const AppShell = ({ children }: AppShellProps) => {
const { pathname } = useLocation();
const { isCCAgent, isAdmin } = useAuth();
const { isOpen, activeAction, close } = useMaintShortcuts();
const { connectionStatus, isRegistered } = useSip();
const networkQuality = useNetworkStatus();
const hasAgentConfig = !!localStorage.getItem('helix_agent_config');
// Heartbeat: keep agent session alive in Redis (CC agents only)
useEffect(() => {
if (!isCCAgent) return;
const beat = () => {
const token = localStorage.getItem('helix_access_token');
if (token) {
const apiUrl = import.meta.env.VITE_API_URL ?? 'http://localhost:4100';
fetch(`${apiUrl}/auth/heartbeat`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}` },
}).catch(() => {});
}
};
const interval = setInterval(beat, 5 * 60 * 1000);
return () => clearInterval(interval);
}, [isCCAgent]);
return (
<SipProvider>
<div className="flex h-screen bg-primary">
<Sidebar activeUrl={pathname} />
<div className="flex flex-1 flex-col overflow-hidden">
{/* Persistent top bar — visible on all pages */}
{(hasAgentConfig || isAdmin) && (
<div className="flex shrink-0 items-center justify-end gap-2 border-b border-secondary px-4 py-2">
{isAdmin && <NotificationBell />}
{hasAgentConfig && (
<>
<div className={cx(
'flex items-center gap-1.5 rounded-full px-2.5 py-1 text-xs font-medium',
networkQuality === 'good'
? 'bg-success-primary text-success-primary'
: networkQuality === 'offline'
? 'bg-error-secondary text-error-primary'
: 'bg-warning-secondary text-warning-primary',
)}>
<FontAwesomeIcon
icon={networkQuality === 'offline' ? faWifiSlash : faWifi}
className="size-3"
/>
{networkQuality === 'good' ? 'Connected' : networkQuality === 'offline' ? 'No connection' : 'Unstable'}
</div>
<AgentStatusToggle isRegistered={isRegistered} connectionStatus={connectionStatus} />
</>
)}
</div>
)}
<main className="flex flex-1 flex-col overflow-hidden">{children}</main>
</div>
{isCCAgent && pathname !== '/' && pathname !== '/call-desk' && <CallWidget />}
</div>
<MaintOtpModal isOpen={isOpen && activeAction?.endpoint !== '__client__clear-campaign-leads'} onOpenChange={(open) => !open && close()} action={activeAction} />
<ClearCampaignLeadsModal isOpen={isOpen && activeAction?.endpoint === '__client__clear-campaign-leads'} onOpenChange={(open) => !open && close()} />
</SipProvider>
);
};