Project Setup
Complete project configuration files including package manifests, TypeScript config, Vite build config, HTML entry point, CSS theme, and application entry points.
Table of contents
- dashboard/package.json
- dashboard/tsconfig.json
- dashboard/tsconfig.node.json
- dashboard/vite.config.ts
- dashboard/index.html
- dashboard/src/index.css
- dashboard/src/main.tsx
- dashboard/src/App.tsx
- dashboard/src/vite-env.d.ts
- dashboard/public/favicon.svg
dashboard/package.json
{
"name": "@metaforge/dashboard",
"private": true,
"version": "0.1.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "tsc -b && vite build",
"preview": "vite preview",
"lint": "eslint ."
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^7.1.0",
"@tanstack/react-query": "^5.62.0",
"@tanstack/react-table": "^8.20.0",
"zustand": "^5.0.0",
"axios": "^1.7.9",
"@react-three/fiber": "^8.17.0",
"@react-three/drei": "^9.114.0",
"three": "^0.170.0",
"recharts": "^2.15.0",
"lucide-react": "^0.468.0",
"clsx": "^2.1.1",
"tailwind-merge": "^2.6.0",
"class-variance-authority": "^0.7.1",
"@radix-ui/react-dialog": "^1.1.4",
"@radix-ui/react-dropdown-menu": "^2.1.4",
"@radix-ui/react-select": "^2.1.4",
"@radix-ui/react-tabs": "^1.1.3",
"@radix-ui/react-tooltip": "^1.1.6",
"@radix-ui/react-scroll-area": "^1.2.2",
"@radix-ui/react-separator": "^1.1.1",
"@radix-ui/react-label": "^2.1.1",
"@radix-ui/react-progress": "^1.1.1",
"@radix-ui/react-slot": "^1.1.1",
"cmdk": "^1.0.4",
"sonner": "^1.7.1",
"date-fns": "^4.1.0"
},
"devDependencies": {
"@types/react": "^18.3.14",
"@types/react-dom": "^18.3.3",
"@types/three": "^0.170.0",
"@vitejs/plugin-react": "^4.3.4",
"typescript": "^5.6.3",
"vite": "^6.0.5",
"@tailwindcss/vite": "^4.0.0",
"tailwindcss": "^4.0.0",
"msw": "^2.7.0"
}
}
dashboard/tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
"jsx": "react-jsx",
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"forceConsistentCasingInFileNames": true,
/* Path aliases */
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
}
dashboard/tsconfig.node.json
{
"compilerOptions": {
"target": "ES2022",
"lib": ["ES2023"],
"module": "ESNext",
"skipLibCheck": true,
/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,
/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["vite.config.ts"]
}
dashboard/vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import path from 'path';
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 5173,
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true,
},
'/ws': {
target: 'ws://localhost:3000',
ws: true,
},
},
},
build: {
target: 'ES2020',
sourcemap: true,
rollupOptions: {
output: {
manualChunks: {
vendor: ['react', 'react-dom', 'react-router-dom'],
three: ['three', '@react-three/fiber', '@react-three/drei'],
query: ['@tanstack/react-query'],
charts: ['recharts'],
},
},
},
},
});
dashboard/index.html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="MetaForge Digital Twin Dashboard" />
<title>MetaForge Dashboard</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
dashboard/src/index.css
@import "tailwindcss";
/* ------------------------------------------------------------------ */
/* Theme tokens — shadcn/ui compatible */
/* ------------------------------------------------------------------ */
@theme {
--color-background: oklch(1 0 0);
--color-foreground: oklch(0.145 0 0);
--color-card: oklch(1 0 0);
--color-card-foreground: oklch(0.145 0 0);
--color-popover: oklch(1 0 0);
--color-popover-foreground: oklch(0.145 0 0);
--color-primary: oklch(0.205 0.064 270.94);
--color-primary-foreground: oklch(0.985 0 0);
--color-secondary: oklch(0.97 0 0);
--color-secondary-foreground: oklch(0.205 0.064 270.94);
--color-muted: oklch(0.97 0 0);
--color-muted-foreground: oklch(0.556 0 0);
--color-accent: oklch(0.97 0 0);
--color-accent-foreground: oklch(0.205 0.064 270.94);
--color-destructive: oklch(0.577 0.245 27.33);
--color-destructive-foreground: oklch(0.985 0 0);
--color-border: oklch(0.922 0 0);
--color-input: oklch(0.922 0 0);
--color-ring: oklch(0.708 0.165 254.62);
--color-chart-1: oklch(0.646 0.222 41.12);
--color-chart-2: oklch(0.6 0.118 184.71);
--color-chart-3: oklch(0.398 0.07 227.39);
--color-chart-4: oklch(0.828 0.189 84.43);
--color-chart-5: oklch(0.769 0.188 70.08);
--color-sidebar-background: oklch(0.985 0 0);
--color-sidebar-foreground: oklch(0.145 0 0);
--color-sidebar-primary: oklch(0.205 0.064 270.94);
--color-sidebar-primary-foreground: oklch(0.985 0 0);
--color-sidebar-accent: oklch(0.97 0 0);
--color-sidebar-accent-foreground: oklch(0.205 0.064 270.94);
--color-sidebar-border: oklch(0.922 0 0);
--color-sidebar-ring: oklch(0.708 0.165 254.62);
--radius: 0.625rem;
}
/* ------------------------------------------------------------------ */
/* Dark mode */
/* ------------------------------------------------------------------ */
.dark {
--color-background: oklch(0.145 0 0);
--color-foreground: oklch(0.985 0 0);
--color-card: oklch(0.205 0 0);
--color-card-foreground: oklch(0.985 0 0);
--color-popover: oklch(0.205 0 0);
--color-popover-foreground: oklch(0.985 0 0);
--color-primary: oklch(0.922 0 0);
--color-primary-foreground: oklch(0.205 0.064 270.94);
--color-secondary: oklch(0.269 0 0);
--color-secondary-foreground: oklch(0.985 0 0);
--color-muted: oklch(0.269 0 0);
--color-muted-foreground: oklch(0.708 0 0);
--color-accent: oklch(0.269 0 0);
--color-accent-foreground: oklch(0.985 0 0);
--color-destructive: oklch(0.704 0.191 22.22);
--color-destructive-foreground: oklch(0.985 0 0);
--color-border: oklch(0.352 0 0);
--color-input: oklch(0.352 0 0);
--color-ring: oklch(0.556 0.165 254.62);
--color-chart-1: oklch(0.488 0.243 264.05);
--color-chart-2: oklch(0.696 0.17 162.48);
--color-chart-3: oklch(0.769 0.188 70.08);
--color-chart-4: oklch(0.627 0.265 303.9);
--color-chart-5: oklch(0.645 0.246 16.44);
--color-sidebar-background: oklch(0.205 0 0);
--color-sidebar-foreground: oklch(0.985 0 0);
--color-sidebar-primary: oklch(0.488 0.243 264.05);
--color-sidebar-primary-foreground: oklch(0.985 0 0);
--color-sidebar-accent: oklch(0.269 0 0);
--color-sidebar-accent-foreground: oklch(0.985 0 0);
--color-sidebar-border: oklch(0.352 0 0);
--color-sidebar-ring: oklch(0.556 0.165 254.62);
}
/* ------------------------------------------------------------------ */
/* Base styles */
/* ------------------------------------------------------------------ */
*,
*::before,
*::after {
border-color: var(--color-border);
}
body {
background-color: var(--color-background);
color: var(--color-foreground);
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji",
"Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* ------------------------------------------------------------------ */
/* Scrollbar */
/* ------------------------------------------------------------------ */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: var(--color-border);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background-color: var(--color-muted-foreground);
}
/* Firefox */
* {
scrollbar-width: thin;
scrollbar-color: var(--color-border) transparent;
}
/* ------------------------------------------------------------------ */
/* Utility classes */
/* ------------------------------------------------------------------ */
/* Hide scrollbar but allow scrolling */
.scrollbar-hidden {
-ms-overflow-style: none;
scrollbar-width: none;
}
.scrollbar-hidden::-webkit-scrollbar {
display: none;
}
/* Fade-in animation */
@keyframes fade-in {
from {
opacity: 0;
transform: translateY(4px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in {
animation: fade-in 0.2s ease-out;
}
/* Pulse dot for live indicators */
@keyframes pulse-dot {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.4;
}
}
.animate-pulse-dot {
animation: pulse-dot 2s ease-in-out infinite;
}
dashboard/src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { BrowserRouter } from 'react-router-dom';
import { App } from '@/App';
import '@/index.css';
const queryClient = new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000,
retry: 1,
refetchOnWindowFocus: false,
},
mutations: {
retry: 0,
},
},
});
async function enableMocking(): Promise<void> {
if (import.meta.env.VITE_MOCK_API !== 'true') {
return;
}
const { worker } = await import('@/mocks/mock-server');
await worker.start({
onUnhandledRequest: 'bypass',
quiet: false,
});
console.info('[MSW] Mock service worker started');
}
enableMocking().then(() => {
const root = document.getElementById('root');
if (!root) {
throw new Error('Root element not found. Ensure index.html has a <div id="root">.');
}
createRoot(root).render(
<StrictMode>
<QueryClientProvider client={queryClient}>
<BrowserRouter>
<App />
</BrowserRouter>
</QueryClientProvider>
</StrictMode>,
);
});
dashboard/src/App.tsx
import { lazy, Suspense } from 'react';
import { Routes, Route } from 'react-router-dom';
import { Toaster } from 'sonner';
import { RootLayout } from '@/components/layout/root-layout';
import { LoadingSpinner } from '@/components/shared/loading-spinner';
/* ----- Lazy-loaded pages ----- */
const OverviewPage = lazy(() =>
import('@/pages/overview/overview-page').then((m) => ({ default: m.OverviewPage })),
);
const SessionsPage = lazy(() =>
import('@/pages/sessions/sessions-page').then((m) => ({ default: m.SessionsPage })),
);
const SessionDetailPage = lazy(() =>
import('@/pages/sessions/session-detail-page').then((m) => ({ default: m.SessionDetailPage })),
);
const AgentsPage = lazy(() =>
import('@/pages/agents/agents-page').then((m) => ({ default: m.AgentsPage })),
);
const DigitalTwinPage = lazy(() =>
import('@/pages/digital-twin/digital-twin-page').then((m) => ({ default: m.DigitalTwinPage })),
);
const BomPage = lazy(() =>
import('@/pages/bom/bom-page').then((m) => ({ default: m.BomPage })),
);
const CompliancePage = lazy(() =>
import('@/pages/compliance/compliance-page').then((m) => ({ default: m.CompliancePage })),
);
const TestingPage = lazy(() =>
import('@/pages/testing/testing-page').then((m) => ({ default: m.TestingPage })),
);
const SupplyChainPage = lazy(() =>
import('@/pages/supply-chain/supply-chain-page').then((m) => ({ default: m.SupplyChainPage })),
);
const ApprovalsPage = lazy(() =>
import('@/pages/approvals/approvals-page').then((m) => ({ default: m.ApprovalsPage })),
);
const SettingsPage = lazy(() =>
import('@/pages/settings/settings-page').then((m) => ({ default: m.SettingsPage })),
);
/* ----- Page-level loading fallback ----- */
function PageLoader() {
return (
<div className="flex h-[calc(100vh-4rem)] items-center justify-center">
<LoadingSpinner size="lg" label="Loading page..." />
</div>
);
}
/* ----- Application component ----- */
export function App() {
return (
<>
<Routes>
<Route element={<RootLayout />}>
<Route
index
element={
<Suspense fallback={<PageLoader />}>
<OverviewPage />
</Suspense>
}
/>
<Route
path="sessions"
element={
<Suspense fallback={<PageLoader />}>
<SessionsPage />
</Suspense>
}
/>
<Route
path="sessions/:id"
element={
<Suspense fallback={<PageLoader />}>
<SessionDetailPage />
</Suspense>
}
/>
<Route
path="agents"
element={
<Suspense fallback={<PageLoader />}>
<AgentsPage />
</Suspense>
}
/>
<Route
path="digital-twin"
element={
<Suspense fallback={<PageLoader />}>
<DigitalTwinPage />
</Suspense>
}
/>
<Route
path="bom"
element={
<Suspense fallback={<PageLoader />}>
<BomPage />
</Suspense>
}
/>
<Route
path="compliance"
element={
<Suspense fallback={<PageLoader />}>
<CompliancePage />
</Suspense>
}
/>
<Route
path="testing"
element={
<Suspense fallback={<PageLoader />}>
<TestingPage />
</Suspense>
}
/>
<Route
path="supply-chain"
element={
<Suspense fallback={<PageLoader />}>
<SupplyChainPage />
</Suspense>
}
/>
<Route
path="approvals"
element={
<Suspense fallback={<PageLoader />}>
<ApprovalsPage />
</Suspense>
}
/>
<Route
path="settings"
element={
<Suspense fallback={<PageLoader />}>
<SettingsPage />
</Suspense>
}
/>
</Route>
</Routes>
<Toaster
position="bottom-right"
toastOptions={{
classNames: {
toast: 'bg-card text-card-foreground border-border',
title: 'text-foreground',
description: 'text-muted-foreground',
},
}}
/>
</>
);
}
dashboard/src/vite-env.d.ts
/// <reference types="vite/client" />
interface ImportMetaEnv {
/** Enable MSW mock API ('true' to enable) */
readonly VITE_MOCK_API: string;
/** Gateway API base URL (default: http://localhost:3000) */
readonly VITE_API_URL: string;
/** WebSocket URL (default: ws://localhost:3000/ws) */
readonly VITE_WS_URL: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}
dashboard/public/favicon.svg
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" fill="none">
<!-- Hexagonal anvil / forge icon -->
<path
d="M32 4L56.785 18.5V47.5L32 62L7.215 47.5V18.5L32 4Z"
fill="#1a1a4e"
stroke="#4a5cd6"
stroke-width="2"
/>
<!-- Anvil body -->
<path
d="M20 38H44V42C44 43.1 43.1 44 42 44H22C20.9 44 20 43.1 20 42V38Z"
fill="#a0aec0"
/>
<!-- Anvil top surface -->
<path
d="M18 34H46V38H18V34Z"
fill="#cbd5e0"
/>
<!-- Anvil horn -->
<path
d="M14 34L18 34V38L14 36V34Z"
fill="#a0aec0"
/>
<!-- Spark 1 -->
<circle cx="28" cy="26" r="1.5" fill="#f6ad55" opacity="0.9" />
<!-- Spark 2 -->
<circle cx="36" cy="24" r="1" fill="#fc8181" opacity="0.8" />
<!-- Spark 3 -->
<circle cx="32" cy="22" r="1.2" fill="#fbd38d" opacity="0.85" />
<!-- Hammer -->
<rect x="30" y="18" width="4" height="14" rx="1" fill="#718096" transform="rotate(-15 32 25)" />
<rect x="27" y="15" width="10" height="5" rx="1" fill="#4a5568" transform="rotate(-15 32 17.5)" />
</svg>