Initial Switch to V2. Completely Overhauled Backend, Frontend and General Structure.

This commit is contained in:
2026-04-17 14:37:36 +03:00
parent eb773c5531
commit 0a8a42d69b
447 changed files with 70696 additions and 492 deletions

View File

@@ -0,0 +1,206 @@
// frontend/src/router/index.jsx
// Root router for v2. All routes start from /.
import { Routes, Route, Navigate } from 'react-router-dom'
import { useAuth } from '@/hooks/useAuth'
import MainLayout from '@/components/layout/MainLayout'
import LoginPage from '@/pages/auth/LoginPage'
import DashboardPage from '@/pages/dashboard/DashboardPage'
import DeviceList from '@/pages/bellcloud/devices/DeviceList'
import DeviceDetail from '@/pages/bellcloud/devices/DeviceDetail'
import DeviceMapPage from '@/pages/bellcloud/devices/DeviceMapPage'
import StyleGuide from '@/pages/dev/StyleGuide'
import CardFontSample from '@/pages/dev/CardFontSample'
import UserList from '@/pages/bellcloud/users/UserList'
import UserDetail from '@/pages/bellcloud/users/UserDetail'
import UserForm from '@/pages/bellcloud/users/UserForm'
import MelodyList from '@/pages/bellcloud/melodies/MelodyList'
import ArchetypeList from '@/pages/bellcloud/melodies/archetypes/ArchetypeList'
import ArchetypeForm from '@/pages/bellcloud/melodies/archetypes/ArchetypeForm'
import MelodyComposer from '@/pages/bellcloud/melodies/MelodyComposer'
import MelodySettings from '@/pages/bellcloud/melodies/MelodySettings'
import MelodyDetail from '@/pages/bellcloud/melodies/MelodyDetail'
import MelodyForm from '@/pages/bellcloud/melodies/MelodyForm'
import MailPage from '@/pages/crm/comms/mail/MailPage'
import CommsPage from '@/pages/crm/comms/CommsPage'
import ProductList from '@/pages/crm/products/ProductList'
import ProductForm from '@/pages/crm/products/ProductForm'
import QuotationList from '@/pages/crm/quotations/QuotationList'
import QuotationForm from '@/pages/crm/quotations/QuotationForm'
import StaffList from '@/pages/settings/staff/StaffList'
import StaffDetail from '@/pages/settings/staff/StaffDetail'
import StaffForm from '@/pages/settings/staff/StaffForm'
import PublicFeaturesSettings from '@/pages/settings/PublicFeaturesSettings'
import AutomationsPage from '@/pages/settings/automations/AutomationsPage'
import ApiReferencePage from '@/pages/engineering/developer/ApiReferencePage'
import CustomerList from '@/pages/crm/customers/CustomerList'
import CustomerDetail from '@/pages/crm/customers/CustomerDetail'
import CustomerForm from '@/pages/crm/customers/CustomerForm'
import OrderList from '@/pages/crm/orders/OrderList'
import OrderDetail from '@/pages/crm/orders/OrderDetail'
import DeviceInventory from '@/pages/engineering/manufacturing/DeviceInventory'
import DeviceInventoryDetail from '@/pages/engineering/manufacturing/DeviceInventoryDetail'
import FlashAssetManager from '@/pages/engineering/manufacturing/FlashAssetManager'
import FirmwareManagerPage from '@/pages/engineering/firmware/FirmwareManagerPage'
import ProvisioningWizard from '@/pages/engineering/manufacturing/ProvisioningWizard'
import HelpdeskPage from '@/pages/crm/comms/helpdesk/HelpdeskPage'
// ---------------------------------------------------------------------------
// Coming Soon placeholder
// ---------------------------------------------------------------------------
function ComingSoon() {
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 'var(--space-16)', gap: 'var(--space-4)' }}>
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M12 2v4M12 18v4M4.93 4.93l2.83 2.83M16.24 16.24l2.83 2.83M2 12h4M18 12h4M4.93 19.07l2.83-2.83M16.24 7.76l2.83-2.83" />
</svg>
<p style={{ fontSize: 'var(--font-size-lg)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-secondary)' }}>Coming Soon</p>
<p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>This page is being built.</p>
</div>
)
}
// ---------------------------------------------------------------------------
// ProtectedRoute
// ---------------------------------------------------------------------------
function ProtectedRoute() {
const { user, loading } = useAuth()
if (loading) {
return (
<div style={{ minHeight: '100vh', display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'var(--color-bg-base)' }}>
<p style={{ color: 'var(--color-text-muted)', fontSize: 'var(--font-size-sm)' }}>Loading</p>
</div>
)
}
if (!user) return <Navigate to="/login" replace />
return <MainLayout />
}
// ---------------------------------------------------------------------------
// PermissionGate
// ---------------------------------------------------------------------------
function PermissionGate({ section, action = 'view', children }) {
const { hasPermission } = useAuth()
if (!hasPermission(section, action)) return <AccessDenied />
return children
}
// ---------------------------------------------------------------------------
// RoleGate
// ---------------------------------------------------------------------------
function RoleGate({ roles, children }) {
const { hasRole } = useAuth()
if (!hasRole(...roles)) return <AccessDenied />
return children
}
// ---------------------------------------------------------------------------
// AccessDenied
// ---------------------------------------------------------------------------
function AccessDenied() {
return (
<div style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 'var(--space-16)' }}>
<div style={{ backgroundColor: 'var(--color-bg-surface)', border: '1px solid var(--color-border)', borderRadius: 'var(--radius-lg)', padding: 'var(--space-8)', textAlign: 'center', maxWidth: '400px' }}>
<h2 style={{ fontSize: 'var(--font-size-lg)', fontWeight: 'var(--font-weight-semibold)', color: 'var(--color-text-primary)', marginBottom: 'var(--space-2)' }}>Access Denied</h2>
<p style={{ fontSize: 'var(--font-size-sm)', color: 'var(--color-text-muted)' }}>You don't have permission to access this feature.</p>
</div>
</div>
)
}
// ---------------------------------------------------------------------------
// Root router
// ---------------------------------------------------------------------------
export default function V2Router() {
return (
<div className="app">
<Routes>
{/* ── Public routes ─────────────────────────────────────────────── */}
<Route path="/login" element={<LoginPage />} />
<Route path="/cloudflash" element={<ComingSoon />} />
<Route path="/serial-monitor" element={<ComingSoon />} />
<Route path="/dev/styleguide" element={<StyleGuide />} />
<Route path="/dev/card-fonts" element={<CardFontSample />} />
{/* ── Protected routes ──────────────────────────────────────────── */}
<Route element={<ProtectedRoute />}>
<Route index element={<DashboardPage />} />
{/* Melodies */}
<Route path="melodies" element={<PermissionGate section="melodies"><MelodyList /></PermissionGate>} />
<Route path="melodies/settings" element={<PermissionGate section="melodies"><MelodySettings /></PermissionGate>} />
<Route path="melodies/composer" element={<PermissionGate section="melodies" action="edit"><MelodyComposer /></PermissionGate>} />
<Route path="melodies/new" element={<PermissionGate section="melodies" action="add"><MelodyForm /></PermissionGate>} />
<Route path="melodies/archetypes" element={<PermissionGate section="melodies" action="edit"><ArchetypeList /></PermissionGate>} />
<Route path="melodies/archetypes/new" element={<PermissionGate section="melodies" action="edit"><ArchetypeForm /></PermissionGate>} />
<Route path="melodies/archetypes/:id" element={<PermissionGate section="melodies" action="edit"><ArchetypeForm /></PermissionGate>} />
<Route path="melodies/:id" element={<PermissionGate section="melodies"><MelodyDetail /></PermissionGate>} />
<Route path="melodies/:id/edit" element={<PermissionGate section="melodies" action="edit"><MelodyForm /></PermissionGate>} />
{/* Devices */}
<Route path="devices" element={<PermissionGate section="devices"><DeviceList /></PermissionGate>} />
<Route path="devices/new" element={<PermissionGate section="devices" action="add"><ComingSoon /></PermissionGate>} />
{/* Map preview — temporary unlisted route while map view is under construction */}
<Route path="devices/map-preview" element={<PermissionGate section="devices"><DeviceMapPage /></PermissionGate>} />
<Route path="devices/:id" element={<PermissionGate section="devices"><DeviceDetail /></PermissionGate>} />
<Route path="devices/:id/edit" element={<PermissionGate section="devices" action="edit"><ComingSoon /></PermissionGate>} />
{/* App Users */}
<Route path="users" element={<PermissionGate section="app_users"><UserList /></PermissionGate>} />
<Route path="users/new" element={<PermissionGate section="app_users" action="add"><UserForm /></PermissionGate>} />
<Route path="users/:id" element={<PermissionGate section="app_users"><UserDetail /></PermissionGate>} />
<Route path="users/:id/edit" element={<PermissionGate section="app_users" action="edit"><UserForm /></PermissionGate>} />
{/* MQTT */}
<Route path="mqtt" element={<PermissionGate section="mqtt"><ComingSoon /></PermissionGate>} />
<Route path="mqtt/commands" element={<PermissionGate section="mqtt"><ComingSoon /></PermissionGate>} />
<Route path="mqtt/logs" element={<PermissionGate section="mqtt"><ComingSoon /></PermissionGate>} />
{/* Manufacturing */}
<Route path="manufacturing" element={<PermissionGate section="manufacturing"><DeviceInventory /></PermissionGate>} />
<Route path="manufacturing/provision" element={<PermissionGate section="manufacturing" action="edit"><ProvisioningWizard /></PermissionGate>} />
<Route path="manufacturing/devices/:sn" element={<PermissionGate section="manufacturing"><DeviceInventoryDetail /></PermissionGate>} />
<Route path="firmware" element={<PermissionGate section="manufacturing"><FirmwareManagerPage /></PermissionGate>} />
{/* Mail */}
<Route path="mail" element={<PermissionGate section="mail"><MailPage /></PermissionGate>} />
{/* Helpdesk */}
<Route path="crm/comms/helpdesk" element={<PermissionGate section="crm"><HelpdeskPage /></PermissionGate>} />
{/* CRM */}
<Route path="crm/comms" element={<PermissionGate section="crm_customers" action="comms_view"><CommsPage /></PermissionGate>} />
<Route path="crm/products" element={<PermissionGate section="crm_products"><ProductList /></PermissionGate>} />
<Route path="crm/products/new" element={<PermissionGate section="crm_products" action="add"><ProductForm /></PermissionGate>} />
<Route path="crm/products/:id" element={<PermissionGate section="crm_products"><ProductForm /></PermissionGate>} />
<Route path="crm/customers" element={<PermissionGate section="crm_customers"><CustomerList /></PermissionGate>} />
<Route path="crm/customers/new" element={<PermissionGate section="crm_customers" action="add"><CustomerForm /></PermissionGate>} />
<Route path="crm/customers/:id" element={<PermissionGate section="crm_customers"><CustomerDetail /></PermissionGate>} />
<Route path="crm/customers/:id/edit" element={<PermissionGate section="crm_customers" action="full_access"><CustomerForm /></PermissionGate>} />
<Route path="crm/orders" element={<PermissionGate section="crm_orders"><OrderList /></PermissionGate>} />
<Route path="crm/orders/:id" element={<PermissionGate section="crm_orders"><OrderDetail /></PermissionGate>} />
<Route path="crm/quotations" element={<PermissionGate section="crm_customers" action="quotations_view"><QuotationList /></PermissionGate>} />
<Route path="crm/quotations/new" element={<PermissionGate section="crm_customers" action="quotations_edit"><QuotationForm /></PermissionGate>} />
<Route path="crm/quotations/:id" element={<PermissionGate section="crm_customers" action="quotations_edit"><QuotationForm /></PermissionGate>} />
{/* Developer */}
<Route path="developer/api" element={<RoleGate roles={['sysadmin', 'admin']}><ApiReferencePage /></RoleGate>} />
{/* Settings */}
<Route path="settings/staff" element={<RoleGate roles={['sysadmin', 'admin']}><StaffList /></RoleGate>} />
<Route path="settings/staff/new" element={<RoleGate roles={['sysadmin', 'admin']}><StaffForm /></RoleGate>} />
<Route path="settings/staff/:id" element={<RoleGate roles={['sysadmin', 'admin']}><StaffDetail /></RoleGate>} />
<Route path="settings/staff/:id/edit" element={<RoleGate roles={['sysadmin', 'admin']}><StaffForm /></RoleGate>} />
<Route path="settings/public-features" element={<RoleGate roles={['sysadmin', 'admin']}><PublicFeaturesSettings /></RoleGate>} />
<Route path="settings/automations" element={<RoleGate roles={['sysadmin', 'admin']}><AutomationsPage /></RoleGate>} />
<Route path="settings/serial-logs" element={<RoleGate roles={['sysadmin', 'admin']}><ComingSoon /></RoleGate>} />
{/* Catch-all */}
<Route path="*" element={<Navigate to="/" replace />} />
</Route>
</Routes>
</div>
)
}