WebOS completion

This commit is contained in:
Sayan Datta
2026-04-18 18:56:05 +05:30
parent 7e3764a8a4
commit cc04e8505f
459 changed files with 11713 additions and 3853 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect } from 'react';
import { useEffect, useState } from 'react';
import { Routes, Route, Navigate, useNavigate, useLocation } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { useStore } from '@/store/useStore';
@@ -13,6 +13,14 @@ import { Catalyst } from '@/components/modules/Catalyst';
import { NotificationCenter } from '@/components/layout/NotificationCenter';
import { useCrmBootstrap } from '@/hooks/useCrmBootstrap';
import type { ModuleId } from '@/types';
import AdminPage from '@/app/admin/page';
import {
clearVelocityToken,
getVelocityMe,
getVelocityToken,
isAdminRole,
normalizeVelocityRole,
} from '@/lib/velocityPlatformClient';
import {
MoreVertical,
@@ -35,6 +43,7 @@ export const MODULE_ROUTES: Array<{
path: string;
title: string;
component: React.ComponentType;
adminOnly?: boolean;
}> = [
{ id: 'dashboard', path: '/dashboard', title: 'Dashboard', component: Dashboard },
{ id: 'oracle', path: '/oracle', title: 'The Oracle', component: Oracle },
@@ -42,6 +51,7 @@ export const MODULE_ROUTES: Array<{
{ 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 },
{ id: 'admin', path: '/admin', title: 'Admin', component: AdminPage, adminOnly: true },
];
export const PATH_TO_MODULE = Object.fromEntries(
@@ -75,14 +85,23 @@ function RouteModuleSync() {
// ── Main authenticated layout ─────────────────────────────────────────────────
function MainLayout() {
const { activeModule, setActiveModule, sidebarExpanded, logout } = useStore();
const { activeModule, setActiveModule, sidebarExpanded, logout, user } = useStore();
useCrmBootstrap();
const navigate = useNavigate();
const location = useLocation();
const availableRoutes = MODULE_ROUTES.filter((route) => !route.adminOnly || isAdminRole(user?.role));
// Current route title
const currentRoute = MODULE_ROUTES.find((r) => r.path === location.pathname);
const currentRoute = availableRoutes.find((r) => r.path === location.pathname);
const pageTitle = currentRoute?.title ?? 'Velocity';
const roleLabel = formatRoleLabel(user?.role);
const userLabel = user?.name?.trim() || user?.id || 'Authenticated User';
const initials = userLabel
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((part) => part[0]?.toUpperCase() ?? '')
.join('') || 'AU';
// Navigate to settings from dropdown (keeps router in sync)
const goToSettings = () => {
@@ -138,8 +157,8 @@ function MainLayout() {
<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>
<p className="text-sm font-medium text-white">{userLabel}</p>
<p className="text-xs" style={{ color: 'hsl(var(--muted-fg))' }}>{roleLabel}</p>
</div>
<div
className="w-9 h-9 rounded-xl flex items-center justify-center text-sm font-semibold"
@@ -148,7 +167,7 @@ function MainLayout() {
color: 'hsl(var(--accent-fg))',
}}
>
AA
{initials}
</div>
<DropdownMenu>
<DropdownMenuTrigger asChild>
@@ -191,8 +210,12 @@ function MainLayout() {
>
{/* Nested module routes rendered here */}
<Routes>
{MODULE_ROUTES.map(({ path, component: Component }) => (
<Route key={path} path={path} element={<Component />} />
{availableRoutes.map(({ path, component: Component, adminOnly }) => (
<Route
key={path}
path={path}
element={adminOnly && !isAdminRole(user?.role) ? <Navigate to="/dashboard" replace /> : <Component />}
/>
))}
{/* Default: redirect / → /dashboard */}
<Route path="/" element={<Navigate to="/dashboard" replace />} />
@@ -211,7 +234,79 @@ function MainLayout() {
// ── Root App ──────────────────────────────────────────────────────────────────
function App() {
const { isAuthenticated } = useStore();
const { isAuthenticated, login, logout } = useStore();
const [authBootstrapped, setAuthBootstrapped] = useState(false);
useEffect(() => {
let cancelled = false;
const token = getVelocityToken();
if (!token) {
setAuthBootstrapped(true);
if (isAuthenticated) {
logout();
}
return () => {
cancelled = true;
};
}
void getVelocityMe()
.then((me) => {
if (cancelled) return;
login({
id: me.user_id,
name: me.user_id,
role: normalizeVelocityRole(me.role),
});
setAuthBootstrapped(true);
})
.catch(() => {
if (cancelled) return;
clearVelocityToken();
logout();
setAuthBootstrapped(true);
});
return () => {
cancelled = true;
};
}, [isAuthenticated, login, logout]);
useEffect(() => {
if (!isAuthenticated || !authBootstrapped) {
return;
}
let cancelled = false;
void getVelocityMe()
.then((me) => {
if (cancelled) return;
login({
id: me.user_id,
name: me.user_id,
role: normalizeVelocityRole(me.role),
});
})
.catch(() => {
if (cancelled) return;
clearVelocityToken();
logout();
});
return () => {
cancelled = true;
};
}, [authBootstrapped, isAuthenticated, login, logout]);
if (!authBootstrapped) {
return (
<div className="min-h-screen flex items-center justify-center bg-black text-zinc-300 text-sm">
Validating live Velocity session...
</div>
);
}
return (
<AnimatePresence mode="wait">
@@ -253,3 +348,15 @@ function App() {
}
export default App;
function formatRoleLabel(role: string | undefined) {
const normalized = normalizeVelocityRole(role);
if (!normalized) {
return 'Authenticated User';
}
return normalized
.toLowerCase()
.split('_')
.map((part) => part.charAt(0).toUpperCase() + part.slice(1))
.join(' ');
}