Files
Project_Velocity/app/src/App.tsx
sagnik 4645ff737b feat: Complete code integration of modules (#18)
The complete code integration is done.

Co-authored-by: Sagnik <sagnik7896@gmail.com>
Reviewed-on: sagnik/Project_Velocity#18
2026-04-12 19:20:14 +05:30

256 lines
9.5 KiB
TypeScript

import { useEffect } from 'react';
import { Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { useStore } from '@/store/useStore';
import { Sidebar } from '@/components/layout/Sidebar';
import { LoginScreen } from '@/components/layout/LoginScreen';
import { Dashboard } from '@/components/modules/Dashboard';
import { Oracle } from '@/components/modules/Oracle';
import { Sentinel } from '@/components/modules/Sentinel';
import { Inventory } from '@/components/modules/Inventory';
import { Settings } from '@/components/modules/Settings';
import { Catalyst } from '@/components/modules/Catalyst';
import { NotificationCenter } from '@/components/layout/NotificationCenter';
import { useCrmBootstrap } from '@/hooks/useCrmBootstrap';
import type { ModuleId } from '@/types';
import {
MoreVertical,
LogOut,
Settings2
} from 'lucide-react';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
// ── Route map ─────────────────────────────────────────────────────────────────
// Single source of truth: module id → URL path → page title → component
export const MODULE_ROUTES: Array<{
id: ModuleId;
path: string;
title: string;
component: React.ComponentType;
}> = [
{ id: 'dashboard', path: '/dashboard', title: 'Dashboard', component: Dashboard },
{ id: 'oracle', path: '/oracle', title: 'The Oracle', component: Oracle },
{ id: 'sentinel', path: '/sentinel', title: 'The Sentinel', component: Sentinel },
{ id: 'inventory', path: '/inventory', title: 'Inventory', component: Inventory },
{ id: 'catalyst', path: '/catalyst', title: 'The Catalyst', component: Catalyst },
{ id: 'settings', path: '/settings', title: 'Settings', component: Settings },
];
export const PATH_TO_MODULE = Object.fromEntries(
MODULE_ROUTES.map((r) => [r.path, r.id])
) as Record<string, ModuleId>;
// ── Protected Route guard ─────────────────────────────────────────────────────
function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated } = useStore();
if (!isAuthenticated) return <Navigate to="/login" replace />;
return <>{children}</>;
}
// ── Sync URL → Zustand activeModule ──────────────────────────────────────────
// This keeps the Zustand store in sync with the URL so existing module
// components that read `activeModule` from the store continue to work.
function RouteModuleSync() {
const { pathname } = useLocation();
const { setActiveModule } = useStore();
useEffect(() => {
const moduleId = PATH_TO_MODULE[pathname];
if (moduleId) setActiveModule(moduleId);
}, [pathname, setActiveModule]);
return null;
}
// ── Main authenticated layout ─────────────────────────────────────────────────
function MainLayout() {
const { activeModule, setActiveModule, sidebarExpanded, logout } = useStore();
useCrmBootstrap();
const navigate = useNavigate();
const location = useLocation();
// Current route title
const currentRoute = MODULE_ROUTES.find((r) => r.path === location.pathname);
const pageTitle = currentRoute?.title ?? 'Velocity';
// Navigate to settings from dropdown (keeps router in sync)
const goToSettings = () => {
setActiveModule('settings');
navigate('/settings');
};
return (
<div className="min-h-screen flex" style={{ background: '#000' }}>
{/* Sync URL → store */}
<RouteModuleSync />
{/* Sidebar */}
<Sidebar />
{/* Main Content Area */}
<motion.main
className="flex-1 h-screen flex flex-col overflow-hidden"
initial={{ marginLeft: 72 }}
animate={{ marginLeft: sidebarExpanded ? 232 : 72 }}
transition={{
type: 'spring',
stiffness: 320,
damping: 32,
mass: 0.8
}}
>
{/* Top Bar */}
<header className="flex-none z-40 px-6 py-4">
<div
className="flex items-center justify-between px-5 py-3 rounded-2xl"
style={{
background: 'hsl(var(--surface))',
border: '1px solid hsl(var(--border-subtle))',
}}
>
<motion.div
key={activeModule}
initial={{ opacity: 0, x: -12 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.25 }}
>
<h1 className="text-base font-semibold text-white tracking-tight">{pageTitle}</h1>
<p className="text-xs" style={{ color: 'hsl(var(--muted-fg))' }}>Project Velocity · v.1.1</p>
</motion.div>
{/* User Profile */}
<div className="flex items-center gap-2">
{/* Active Notification Center */}
<NotificationCenter />
<div className="w-px h-6 bg-white/10" />
<div className="text-right">
<p className="text-sm font-medium text-white">Ahmed Al-Farsi</p>
<p className="text-xs" style={{ color: 'hsl(var(--muted-fg))' }}>Sales Director</p>
</div>
<div
className="w-9 h-9 rounded-xl flex items-center justify-center text-sm font-semibold"
style={{
background: 'hsl(var(--accent))',
color: 'hsl(var(--accent-fg))',
}}
>
AA
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<button className="h-9 w-9 flex items-center justify-center rounded-lg hover:bg-white/5 text-zinc-400 hover:text-white transition-colors outline-none">
<MoreVertical className="w-5 h-5" />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-48 bg-[#0A0B10] border-white/10 text-zinc-200">
<DropdownMenuItem
className="cursor-pointer focus:bg-white/5"
onClick={goToSettings}
>
<Settings2 className="mr-2 h-4 w-4 text-zinc-400" />
<span>Settings</span>
</DropdownMenuItem>
<DropdownMenuSeparator className="bg-white/10" />
<DropdownMenuItem className="text-red-400 focus:text-red-400 focus:bg-red-500/10 cursor-pointer" onClick={() => logout()}>
<LogOut className="mr-2 h-4 w-4" />
<span>Log out</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</header>
{/* Module Content — animated on route change */}
<div className="flex-1 overflow-y-auto custom-scrollbar">
<div className="px-8 pb-8 min-h-full relative">
<AnimatePresence mode="wait">
<motion.div
key={location.pathname}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{
duration: 0.3,
ease: [0.4, 0, 0.2, 1]
}}
>
{/* Nested module routes rendered here */}
<Routes>
{MODULE_ROUTES.map(({ path, component: Component }) => (
<Route key={path} path={path} element={<Component />} />
))}
{/* Default: redirect / → /dashboard */}
<Route path="/" element={<Navigate to="/dashboard" replace />} />
{/* Catch-all: any unknown path → dashboard */}
<Route path="*" element={<Navigate to="/dashboard" replace />} />
</Routes>
</motion.div>
</AnimatePresence>
</div>
</div>
</motion.main>
</div>
);
}
// ── Root App ──────────────────────────────────────────────────────────────────
function App() {
const { isAuthenticated } = useStore();
return (
<AnimatePresence mode="wait">
{!isAuthenticated ? (
<motion.div
key="login"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<Routes>
<Route path="/login" element={<LoginScreen />} />
<Route path="*" element={<LoginScreen />} />
</Routes>
</motion.div>
) : (
<motion.div
key="app"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: 0.5 }}
>
<Routes>
<Route
path="/*"
element={
<ProtectedRoute>
<MainLayout />
</ProtectedRoute>
}
/>
</Routes>
</motion.div>
)}
</AnimatePresence>
);
}
export default App;