39910-vm/src/components/AppLayout.tsx
2026-05-06 09:19:27 +00:00

337 lines
16 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import { useAppContext } from '@/contexts/AppContext';
import { useAuth } from '@/contexts/AuthContext';
import { useIsMobile } from '@/hooks/use-mobile';
import { UserRole, ModuleId } from '@/lib/types';
import { MODULES } from '@/lib/appData';
import Sidebar from '@/components/frameworks/Sidebar';
import TopBar from '@/components/frameworks/TopBar';
import Dashboard from '@/components/frameworks/Dashboard';
import FrameModule from '@/components/frameworks/FrameModule';
import ClassroomSupport from '@/components/frameworks/ClassroomSupport';
import ClassroomTimer from '@/components/frameworks/ClassroomTimer';
import QBSSafety from '@/components/frameworks/QBSSafety';
import EmotionalIntelligence from '@/components/frameworks/EmotionalIntelligence';
import ZonesOfRegulation from '@/components/frameworks/ZonesOfRegulation';
import SignLanguage from '@/components/frameworks/SignLanguage';
import DirectorDashboard from '@/components/frameworks/DirectorDashboard';
import HandbookPolicy from '@/components/frameworks/HandbookPolicy';
import SignInModal from '@/components/frameworks/SignInModal';
import CommunityService from '@/components/frameworks/CommunityService';
import VocationalOpportunities from '@/components/frameworks/VocationalOpportunities';
import ESAFunding from '@/components/frameworks/ESAFunding';
import WalkThroughCheckIn from '@/components/frameworks/WalkThroughCheckIn';
import CampusAttendance from '@/components/frameworks/CampusAttendance';
import {
ParentCommModule,
InternalCommModule,
SafetyProtocolsModule,
} from '@/components/frameworks/MoreModules';
import {
Loader2, LogIn, CheckCircle2, Eye, ChevronDown
} from 'lucide-react';
const AppLayout: React.FC = () => {
const { sidebarOpen, toggleSidebar } = useAppContext();
const { isAuthenticated, profile, loading: authLoading } = useAuth();
const isMobile = useIsMobile();
const [currentModule, setCurrentModule] = useState<ModuleId>('dashboard');
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
const [mobileSidebarOpen, setMobileSidebarOpen] = useState(false);
const [zoneCheckIn, setZoneCheckIn] = useState<string | null>(null);
const [showSignInModal, setShowSignInModal] = useState(false);
// Demo role switcher for unauthenticated users
const [demoRole, setDemoRole] = useState<UserRole>('teacher');
const [showDemoRolePicker, setShowDemoRolePicker] = useState(false);
// Use real role from profile when authenticated, otherwise use demo role
const userRole: UserRole = isAuthenticated && profile?.role ? profile.role : demoRole;
const userName = isAuthenticated && profile?.full_name ? profile.full_name : 'Guest User';
const userCampus = isAuthenticated && profile?.campus ? profile.campus : 'Tigers';
const hasAccess = MODULES.find(m => m.id === currentModule)?.roles.includes(userRole);
const activeModule = hasAccess ? currentModule : 'dashboard';
const handleSetModule = (id: ModuleId) => {
setCurrentModule(id);
if (isMobile) setMobileSidebarOpen(false);
};
// When demo role changes, check if current module is still accessible
useEffect(() => {
const mod = MODULES.find(m => m.id === currentModule);
if (mod && !mod.roles.includes(userRole)) {
setCurrentModule('dashboard');
}
}, [userRole, currentModule]);
const renderModule = () => {
switch (activeModule) {
case 'dashboard':
return <Dashboard userRole={userRole} userName={userName} setCurrentModule={handleSetModule} zoneCheckIn={zoneCheckIn} setZoneCheckIn={setZoneCheckIn} />;
case 'frame':
return <FrameModule userRole={userRole} />;
case 'classroom':
return <ClassroomSupport />;
case 'timer':
return <ClassroomTimer />;
case 'qbs':
return <QBSSafety userRole={userRole} />;
case 'ei':
return <EmotionalIntelligence userRole={userRole} userName={userName} userCampus={userCampus} />;
case 'zones':
return <ZonesOfRegulation />;
case 'signs':
return <SignLanguage />;
case 'attendance':
return <CampusAttendance userRole={userRole} userCampus={userCampus} userName={userName} />;
case 'parent-comm':
return <ParentCommModule />;
case 'internal-comm':
return <InternalCommModule userRole={userRole} />;
case 'safety':
return <SafetyProtocolsModule />;
case 'handbook':
return <HandbookPolicy userRole={userRole} />;
case 'director':
return <DirectorDashboard setCurrentModule={handleSetModule} />;
case 'community':
return <CommunityService />;
case 'vocational':
return <VocationalOpportunities />;
case 'esa':
return <ESAFunding />;
case 'walkthrough':
return <WalkThroughCheckIn />;
default:
return <Dashboard userRole={userRole} userName={userName} setCurrentModule={handleSetModule} zoneCheckIn={zoneCheckIn} setZoneCheckIn={setZoneCheckIn} />;
}
};
// Loading state
if (authLoading) {
return (
<div className="h-screen w-full flex items-center justify-center bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950">
<div className="text-center">
<div className="w-16 h-16 rounded-2xl bg-gradient-to-br from-violet-500 to-amber-400 flex items-center justify-center mx-auto mb-4 shadow-lg shadow-violet-500/30 animate-pulse">
<span className="text-white font-bold text-2xl">F</span>
</div>
<Loader2 size={24} className="animate-spin text-violet-400 mx-auto mb-3" />
<p className="text-slate-400 text-sm">Loading FRAMEworks...</p>
</div>
</div>
);
}
const demoRoles: { value: UserRole; label: string; color: string }[] = [
{ value: 'teacher', label: 'Teacher', color: 'text-emerald-400 bg-emerald-500/15 border-emerald-500/30' },
{ value: 'para', label: 'Support Staff', color: 'text-blue-400 bg-blue-500/15 border-blue-500/30' },
{ value: 'office', label: 'Office Manager', color: 'text-amber-400 bg-amber-500/15 border-amber-500/30' },
{ value: 'director', label: 'Director', color: 'text-purple-400 bg-purple-500/15 border-purple-500/30' },
{ value: 'superintendent', label: 'Superintendent', color: 'text-rose-400 bg-rose-500/15 border-rose-500/30' },
];
const currentDemoRole = demoRoles.find(r => r.value === demoRole)!;
// Always show the full app — authenticated or guest
return (
<div className="h-screen w-full flex bg-gradient-to-br from-slate-950 via-slate-900 to-slate-950 overflow-hidden">
{/* Mobile sidebar overlay */}
{mobileSidebarOpen && isMobile && (
<div className="fixed inset-0 bg-black/50 backdrop-blur-sm z-30" onClick={() => setMobileSidebarOpen(false)} />
)}
{/* Sidebar */}
<div className={`${isMobile ? `fixed left-0 top-0 h-full z-40 transform transition-transform duration-300 ${mobileSidebarOpen ? 'translate-x-0' : '-translate-x-full'}` : 'relative'}`}>
<Sidebar currentModule={activeModule} setCurrentModule={handleSetModule} userRole={userRole} collapsed={isMobile ? false : sidebarCollapsed} setCollapsed={setSidebarCollapsed} userCampus={userCampus} />
</div>
{/* Main content area */}
<div className="flex-1 flex flex-col min-w-0 overflow-hidden">
<TopBar
userRole={userRole}
userName={userName}
userCampus={userCampus}
toggleSidebar={() => isMobile ? setMobileSidebarOpen(!mobileSidebarOpen) : setSidebarCollapsed(!sidebarCollapsed)}
/>
<main className="flex-1 overflow-y-auto p-4 md:p-6 lg:p-8">
{/* Guest Mode Banner — only when NOT authenticated */}
{!isAuthenticated && (
<div className="max-w-7xl mx-auto mb-5">
<div className="bg-gradient-to-r from-violet-600/10 via-amber-500/10 to-emerald-500/10 rounded-2xl border border-violet-500/20 px-4 py-3 flex flex-col sm:flex-row items-start sm:items-center justify-between gap-3">
<div className="flex items-center gap-3">
<div className="w-9 h-9 rounded-xl bg-gradient-to-br from-violet-500/20 to-amber-500/20 border border-violet-500/30 flex items-center justify-center flex-shrink-0">
<Eye size={18} className="text-violet-400" />
</div>
<div>
<p className="text-sm font-semibold text-white">
You're browsing as a Guest
</p>
<p className="text-xs text-slate-400">
Explore all modules freely. Sign in to save your progress and get a personalized experience.
</p>
</div>
</div>
<div className="flex items-center gap-2 flex-shrink-0">
{/* Demo Role Picker */}
<div className="relative">
<button
onClick={() => setShowDemoRolePicker(!showDemoRolePicker)}
className={`flex items-center gap-1.5 px-3 py-1.5 rounded-xl text-xs font-semibold border transition-all ${currentDemoRole.color}`}
>
<span>View as: {currentDemoRole.label}</span>
<ChevronDown size={12} />
</button>
{showDemoRolePicker && (
<>
<div className="fixed inset-0 z-30" onClick={() => setShowDemoRolePicker(false)} />
<div className="absolute right-0 top-full mt-1.5 w-48 bg-slate-800 rounded-xl shadow-2xl shadow-black/40 border border-slate-700/50 py-1.5 z-40">
<p className="px-3 py-1.5 text-[10px] text-slate-500 uppercase tracking-wider font-semibold">Switch Demo Role</p>
{demoRoles.map(r => (
<button
key={r.value}
onClick={() => {
setDemoRole(r.value);
setShowDemoRolePicker(false);
}}
className={`w-full text-left px-3 py-2 text-sm transition-colors flex items-center justify-between ${
demoRole === r.value
? 'bg-violet-500/10 text-white'
: 'text-slate-300 hover:bg-slate-700/50'
}`}
>
<span className="font-medium">{r.label}</span>
{demoRole === r.value && <CheckCircle2 size={14} className="text-violet-400" />}
</button>
))}
</div>
</>
)}
</div>
<button
onClick={() => setShowSignInModal(true)}
className="flex items-center gap-1.5 px-4 py-1.5 bg-gradient-to-r from-violet-500 to-amber-500 hover:from-violet-600 hover:to-amber-600 text-white font-semibold rounded-xl transition-all duration-200 shadow-lg shadow-violet-500/25 hover:shadow-violet-500/40 text-xs"
>
<LogIn size={14} />
Sign In
</button>
</div>
</div>
</div>
)}
{/* Module Content */}
<div className="max-w-7xl mx-auto">{renderModule()}</div>
{/* Footer */}
<footer className="mt-12 pb-6">
<div className="max-w-7xl mx-auto">
<div className="bg-slate-800/40 backdrop-blur-sm rounded-2xl border border-slate-700/40 p-6 md:p-8">
<div className="grid grid-cols-1 md:grid-cols-4 gap-8">
<div className="md:col-span-1">
<div className="flex items-center gap-2 mb-3">
<div className="w-8 h-8 rounded-lg bg-gradient-to-br from-violet-500 to-amber-400 flex items-center justify-center shadow-lg shadow-violet-500/20">
<span className="text-white font-bold text-sm">F</span>
</div>
<span className="font-bold text-lg bg-gradient-to-r from-violet-400 to-amber-400 bg-clip-text text-transparent">FRAMEworks</span>
</div>
<p className="text-xs text-slate-500 leading-relaxed">A modular, role-based school operations platform designed for autism-focused educational environments.</p>
</div>
<div>
<h4 className="font-semibold text-sm text-slate-300 mb-3">Core Modules</h4>
<ul className="space-y-1.5">
{[
{ label: 'Classroom Support', mod: 'classroom' as ModuleId },
{ label: 'Classroom Timer', mod: 'timer' as ModuleId },
{ label: 'De-escalation Strategies', mod: 'qbs' as ModuleId },
{ label: 'Regulate your Zone', mod: 'zones' as ModuleId },
{ label: 'Sign Language', mod: 'signs' as ModuleId },
].map(item => (
<li key={item.label}>
<button
onClick={() => handleSetModule(item.mod)}
className="text-xs text-slate-500 hover:text-violet-400 transition-colors"
>
{item.label}
</button>
</li>
))}
</ul>
</div>
<div>
<h4 className="font-semibold text-sm text-slate-300 mb-3">Operations</h4>
<ul className="space-y-1.5">
{[
{ label: 'F.R.A.M.E. Weekly', mod: 'frame' as ModuleId },
{ label: 'Attendance', mod: 'attendance' as ModuleId },
{ label: 'Handbook & Policies', mod: 'handbook' as ModuleId },
{ label: 'Safety Protocols', mod: 'safety' as ModuleId },
{ label: 'Community & Partnerships', mod: 'community' as ModuleId },
{ label: 'Vocational Opportunities', mod: 'vocational' as ModuleId },
{ label: 'ESA Funding Info', mod: 'esa' as ModuleId },
].map(item => (
<li key={item.label}>
<button
onClick={() => handleSetModule(item.mod)}
className="text-xs text-slate-500 hover:text-violet-400 transition-colors"
>
{item.label}
</button>
</li>
))}
</ul>
</div>
<div>
<h4 className="font-semibold text-sm text-slate-300 mb-3">Platform</h4>
<ul className="space-y-1.5">
{['Role-Based Access', 'Secure Auth', 'ADP Integration', 'FERPA Compliant'].map(item => (
<li key={item} className="text-xs text-slate-500">{item}</li>
))}
</ul>
</div>
</div>
<div className="mt-6 pt-4 border-t border-slate-700/40 flex flex-col md:flex-row items-center justify-between gap-2">
<p className="text-[10px] text-slate-600">FRAMEworks © 2026 — Built for autism-focused school communities</p>
<div className="flex items-center gap-2">
<CheckCircle2 size={10} className={isAuthenticated ? 'text-emerald-500' : 'text-slate-600'} />
<span className="text-[10px] text-slate-600">
{isAuthenticated
? `Signed in as ${userName} (${userRole})`
: `Browsing as Guest (${currentDemoRole.label})`
}
</span>
</div>
</div>
</div>
</div>
</footer>
</main>
</div>
{/* Sign In Modal */}
<SignInModal isOpen={showSignInModal} onClose={() => setShowSignInModal(false)} />
</div>
);
};
export default AppLayout;