feat/#24 WebOS Completion (#25)
#24 WebOS Completion Co-authored-by: Sayan Datta <sayan@Sayans-MacBook-Air.local> Reviewed-on: #25
This commit was merged in pull request #25.
This commit is contained in:
125
app/src/App.tsx
125
app/src/App.tsx
@@ -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(' ');
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user