Replace mock dashboard data with real backend data, remove unbuilt pages

Dashboard home was hardcoding fake numbers (2,431/10,000 requests, fake
"Pro Plan", fake activity log, fake random bar chart) despite the real
analytics/logs/keys endpoints already existing and being used correctly
elsewhere (Usage, Logs, Tokens pages). A fresh account with zero real
activity was showing 24% "used" from nobody's data.

Rewired every stat card, the usage chart, and the activity table to real
/api/analytics/*, /api/logs, and /api/keys data. Every Quick Action now
does something real (navigate to the real page, or call a real endpoint)
instead of being a no-op or hardcoded button.

Also removed 5 sidebar/footer links (AI Setup Guides, Tools & Permissions,
Billing, Team, Settings, Support) that pointed at nonexistent routes or
pages with no backend feature behind them at all — no team/multi-user
system, no per-tool permission toggles, no real subscription/billing
system exist in the backend yet. Documented what's needed to build each
one for real in FUTURE_FEATURES.md rather than shipping more fake UI.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
MOHAN 2026-07-02 00:31:36 +05:30
parent 0444ef09aa
commit 739740bb4b
5 changed files with 251 additions and 214 deletions

83
FUTURE_FEATURES.md Normal file
View File

@ -0,0 +1,83 @@
# Removed / Not-Yet-Built Features
These sidebar pages were removed from `src/pages/dashboard/DashboardLayout.tsx`'s
nav because no backend feature exists for them — they were showing fully
hardcoded mock data with no real API behind them. Removed rather than shipped
with fake data, per the "no mock data" rule.
## 1. Billing / Subscriptions
**Was at:** `/dashboard/billing` (deleted: `src/pages/dashboard/Billing.tsx`)
**What it showed (all fake):** hardcoded "Pro Plan $49/month", 3-tier plan
comparison cards, fake invoice history table.
**What's needed to rebuild for real:**
- Backend: `plan`, `subscription_status`, `stripe_customer_id` columns on
`users` table (mentioned as a possibility in `HANDOFF.md` from the original
handoff, never implemented)
- Real Stripe integration — the old Next.js frontend had
`src/app/api/stripe/{checkout,webhook}/route.ts` but they were dead code
(never called, placeholder keys, no backend wiring). Since Vite can't host
secret-key server logic, this needs to live as new endpoints on the FastAPI
backend (`Multi-Tenant-ODOO-MCP`), not the frontend.
- New endpoints: `POST /api/billing/checkout`, `POST /api/billing/webhook`,
`GET /api/billing/invoices`
- Frontend: rebuild the page against those real endpoints
## 2. Team / Multi-User Accounts
**Was at:** `/dashboard/team` (route never existed — dead nav link, 404)
**What's needed:** an entirely new multi-user-per-account concept. Currently
one `users` row = one account = one set of Odoo credentials/API keys. Team
support means either:
- A new `team_members` table linking multiple `users` rows to one billing
account with role-based permissions, or
- An `account_id` grouping concept above `users`
This is a significant schema/auth redesign, not a small addition.
## 3. Tools & Permissions
**Was at:** `/dashboard/tools` (route never existed — dead nav link, 404)
**What's needed:** per-user, per-tool enable/disable toggles for the 47 MCP
tools currently registered in `server.py`'s `TOOLS` list. Would need:
- A new `disabled_tools` table or JSON column on `users`
- A check in `call_tool()` (`server.py`) before dispatch: reject if the tool
is disabled for that user
- Frontend: a page listing all 47 tools grouped by module (connection,
discovery, records, workflow, smart) with toggles, calling a new
`PATCH /api/tools/{name}` endpoint
## 4. AI Setup Guides
**Was at:** `/dashboard/guides` (route never existed — dead nav link, 404)
**What's needed:** this is content, not really a backend feature — step-by-step
setup instructions for Claude, Codex, Cursor, Windsurf. Partially exists
already: `/dashboard/endpoints` already has a working Claude Desktop JSON
config generator with the real API key and MCP URL. This page could be built
as a pure frontend content page (no backend needed) with setup docs for the
other clients, following the same pattern already in `Endpoints.tsx`.
## 5. Settings (account settings / change password while logged in)
**Was at:** `/dashboard/settings` (route never existed — dead nav link, 404)
**What's needed:** currently the only way to change a password is the
forgot-password token-based flow (`/forgot-password``/reset-password`).
There's no "logged in, change my password" endpoint. Would need:
- Backend: `POST /api/account/change-password` (current password + new
password, no token needed since the user is already authenticated)
- Frontend: a settings page with that form, plus maybe email/display-name
fields if those become editable later
## 6. Support link
**Was at:** footer link `/dashboard/support` (route never existed — dead
link, removed entirely from `DashboardLayout.tsx`, not just hidden)
Trivial — just needs a real destination (mailto link, external help site, or
a real support-ticket feature) whenever there's something to point it at.

View File

@ -8,7 +8,6 @@ import ResetPassword from './pages/ResetPassword';
import DashboardLayout from './pages/dashboard/DashboardLayout';
import DashboardHome from './pages/dashboard/DashboardHome';
import Connections from './pages/dashboard/Connections';
import Billing from './pages/dashboard/Billing';
import Endpoints from './pages/dashboard/Endpoints';
import Tokens from './pages/dashboard/Tokens';
import Usage from './pages/dashboard/Usage';
@ -33,7 +32,6 @@ export default function App() {
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<DashboardHome />} />
<Route path="connections" element={<Connections />} />
<Route path="billing" element={<Billing />} />
<Route path="endpoints" element={<Endpoints />} />
<Route path="tokens" element={<Tokens />} />
<Route path="usage" element={<Usage />} />

View File

@ -1,97 +0,0 @@
import { Check } from 'lucide-react';
const PLANS = [
{ name: 'Starter', price: 19, current: false, features: ['1 connection', '1 MCP endpoint', 'Read-only', '1,000 req/mo'] },
{ name: 'Pro', price: 49, current: true, features: ['3 connections', 'Read/Write', 'Access logs', '10,000 req/mo'] },
{ name: 'Agency', price: 149, current: false, features: ['25 workspaces', 'White-label', 'Team members', '50,000 req/mo'] },
];
export default function Billing() {
return (
<div className="max-w-4xl space-y-6">
<div>
<h1 className="text-xl font-bold text-gray-900">Billing</h1>
<p className="text-sm text-gray-500 mt-0.5">Manage your subscription and payment details.</p>
</div>
{/* Current plan */}
<div className="bg-white rounded-xl border border-brand-200 shadow-sm p-5">
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-gray-500 mb-1">Current Plan</p>
<p className="text-lg font-bold text-gray-900">Pro Plan</p>
<p className="text-sm text-gray-500">$49/month · Renews July 29, 2026</p>
</div>
<span className="bg-green-100 text-green-700 text-xs font-medium px-3 py-1.5 rounded-full">Active</span>
</div>
<div className="mt-4 flex gap-3">
<button className="text-sm border border-gray-200 text-gray-600 px-4 py-2 rounded-lg hover:bg-gray-50 transition-colors">
Manage Payment Method
</button>
<button className="text-sm text-red-600 border border-red-200 px-4 py-2 rounded-lg hover:bg-red-50 transition-colors">
Cancel Subscription
</button>
</div>
</div>
{/* Plan comparison */}
<div>
<h2 className="text-sm font-semibold text-gray-900 mb-4">Change Plan</h2>
<div className="grid grid-cols-3 gap-4">
{PLANS.map((plan) => (
<div key={plan.name}
className={`rounded-xl border p-5 ${plan.current ? 'border-brand-400 bg-brand-50' : 'border-gray-200 bg-white'}`}>
<p className="font-semibold text-sm text-gray-900">{plan.name}</p>
<p className="text-2xl font-bold text-gray-900 mt-1 mb-3">${plan.price}<span className="text-sm font-normal text-gray-400">/mo</span></p>
<ul className="space-y-1.5 mb-4">
{plan.features.map((f) => (
<li key={f} className="flex items-center gap-1.5 text-xs text-gray-600">
<Check size={11} className="text-brand-500" /> {f}
</li>
))}
</ul>
{plan.current ? (
<div className="text-center text-xs text-brand-600 font-medium py-2">Current Plan</div>
) : (
<button className="w-full gradient-brand text-white py-2 rounded-lg text-xs font-medium hover:opacity-90 transition-opacity">
Switch to {plan.name}
</button>
)}
</div>
))}
</div>
</div>
{/* Invoice history */}
<div className="bg-white rounded-xl border border-gray-100 shadow-sm p-5">
<h2 className="text-sm font-semibold text-gray-900 mb-4">Invoice History</h2>
<table className="w-full text-xs">
<thead>
<tr className="text-gray-400 border-b border-gray-100">
<th className="text-left pb-2 font-medium">Date</th>
<th className="text-left pb-2 font-medium">Description</th>
<th className="text-left pb-2 font-medium">Amount</th>
<th className="text-left pb-2 font-medium">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-50">
{[
{ date: 'Jun 29, 2026', desc: 'Pro Plan — Monthly', amount: '$49.00', status: 'Paid' },
{ date: 'May 29, 2026', desc: 'Pro Plan — Monthly', amount: '$49.00', status: 'Paid' },
{ date: 'Apr 29, 2026', desc: 'Pro Plan — Monthly', amount: '$49.00', status: 'Paid' },
].map((row, i) => (
<tr key={i}>
<td className="py-2.5 text-gray-500">{row.date}</td>
<td className="py-2.5 text-gray-700">{row.desc}</td>
<td className="py-2.5 text-gray-900 font-medium">{row.amount}</td>
<td className="py-2.5">
<span className="bg-green-50 text-green-600 px-2 py-0.5 rounded-full">{row.status}</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
}

View File

@ -1,30 +1,75 @@
import { useEffect, useState } from 'react';
import { api, session, type MeResult } from '@/lib/api';
import { Cpu, Globe, BarChart2, Copy, RotateCcw, ExternalLink, Activity } from 'lucide-react';
import { useEffect, useState, useCallback } from 'react';
import { useNavigate } from 'react-router-dom';
import {
api, session, type MeResult, type AnalyticsSummary, type TimeseriesBucket, type LogEntry,
} from '@/lib/api';
import {
Cpu, Globe, Copy, RotateCcw, ExternalLink, Activity, CheckCircle2, XCircle, Key,
} from 'lucide-react';
import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer } from 'recharts';
import { clsx } from 'clsx';
const MCP_URL = `${import.meta.env.VITE_API_URL ?? 'https://odoo-mcp.thedomainnest.com'}/mcp/sse`;
const RECENT_ACTIVITY = [
{ time: '2 mins ago', event: 'Connection sync', status: 'Success', detail: 'Odoo connection is healthy' },
{ time: '5 mins ago', event: 'MCP request', status: 'Success', detail: 'search_invoices' },
{ time: '12 mins ago', event: 'Token rotated', status: 'Success', detail: 'New token generated' },
{ time: '1 hour ago', event: 'Connection sync', status: 'Success', detail: 'Odoo connection is healthy' },
];
function isoDaysAgo(days: number): string {
const d = new Date();
d.setUTCDate(d.getUTCDate() - days);
return d.toISOString();
}
export default function DashboardHome() {
const navigate = useNavigate();
const [me, setMe] = useState<MeResult | null>(null);
const [summary, setSummary] = useState<AnalyticsSummary | null>(null);
const [timeseries, setTimeseries] = useState<TimeseriesBucket[]>([]);
const [recentLogs, setRecentLogs] = useState<LogEntry[]>([]);
const [activeKeyCount, setActiveKeyCount] = useState<number | null>(null);
const [loading, setLoading] = useState(true);
const [rotating, setRotating] = useState(false);
useEffect(() => {
const load = useCallback(() => {
const key = session.getKey();
if (!key) return;
api.me(key).then(setMe).catch(console.error).finally(() => setLoading(false));
setLoading(true);
const since = isoDaysAgo(30);
Promise.all([
api.me(key),
api.analyticsSummary(key, since),
api.analyticsTimeseries(key, since, undefined, 'day'),
api.listLogs(key, 1, 5),
api.listApiKeys(key),
])
.then(([meResult, summaryResult, timeseriesResult, logsResult, keysResult]) => {
setMe(meResult);
setSummary(summaryResult);
setTimeseries(timeseriesResult.buckets);
setRecentLogs(logsResult.items);
setActiveKeyCount(keysResult.keys.filter((k) => k.is_active).length);
})
.catch(console.error)
.finally(() => setLoading(false));
}, []);
useEffect(load, [load]);
const instance = me?.odoo_instances?.[0];
const requestsUsed = 2431;
const requestsLimit = 10000;
async function rotateLegacyKey() {
const key = session.getKey();
if (!key) return;
if (!confirm('This regenerates your original signup API key. The old key stops working immediately — any client still using it will lose access. Continue?')) return;
setRotating(true);
try {
const result = await api.regenKey(key);
session.save(result.api_key, me?.email ?? '');
alert('Key rotated. Update any MCP clients using the old key with the new one shown on the API Tokens page.');
load();
} catch (err) {
alert(err instanceof Error ? err.message : 'Failed to rotate key');
} finally {
setRotating(false);
}
}
return (
<div className="max-w-6xl space-y-6">
@ -33,142 +78,156 @@ export default function DashboardHome() {
<p className="text-sm text-gray-500 mt-0.5">Welcome back{me ? `, ${me.email}` : ''}.</p>
</div>
{/* Stat cards */}
{/* Stat cards — all real, from /api/analytics/summary and /api/me */}
<div className="grid grid-cols-2 lg:grid-cols-4 gap-4">
<StatCard
label="Subscription"
value="Pro Plan"
sub="Renews Jul 29, 2026"
icon={<span className="text-xl">💎</span>}
color="bg-purple-50"
label="Requests (30 days)"
value={loading ? '—' : (summary?.total_calls ?? 0).toLocaleString()}
icon={<Activity size={18} className="text-blue-500" />}
color="bg-blue-50"
/>
<StatCard
label="MCP Status"
value={<span className="flex items-center gap-1.5"><span className="w-2 h-2 bg-green-400 rounded-full" />Active</span>}
sub="All systems operational"
icon={<Cpu size={18} className="text-blue-500" />}
color="bg-blue-50"
label="Successful"
value={loading ? '—' : (summary?.success_count ?? 0).toLocaleString()}
icon={<CheckCircle2 size={18} className="text-green-500" />}
color="bg-green-50"
/>
<StatCard
label="Errors"
value={loading ? '—' : (summary?.error_count ?? 0).toLocaleString()}
icon={<XCircle size={18} className="text-red-500" />}
color="bg-red-50"
/>
<StatCard
label="Odoo Connection"
value={instance ? 'Connected' : 'Not connected'}
sub={instance ? 'Healthy' : 'Add a connection'}
icon={<Globe size={18} className="text-green-500" />}
color="bg-green-50"
/>
<StatCard
label="Requests This Month"
value={`${requestsUsed.toLocaleString()} / ${requestsLimit.toLocaleString()}`}
sub={
<div className="mt-1">
<div className="h-1.5 bg-gray-200 rounded-full overflow-hidden">
<div
className="h-full bg-brand-500 rounded-full"
style={{ width: `${(requestsUsed / requestsLimit) * 100}%` }}
/>
</div>
<span className="text-xs text-gray-400">{Math.round((requestsUsed / requestsLimit) * 100)}% used</span>
</div>
}
icon={<BarChart2 size={18} className="text-orange-500" />}
color="bg-orange-50"
icon={<Globe size={18} className="text-purple-500" />}
color="bg-purple-50"
/>
</div>
<div className="grid lg:grid-cols-3 gap-6">
{/* Odoo Connection Health */}
{/* Odoo Connection Health — real data from /api/me */}
<div className="lg:col-span-1 bg-white rounded-xl border border-gray-100 shadow-sm p-5">
<h2 className="text-sm font-semibold text-gray-900 mb-4">Odoo Connection Health</h2>
{instance ? (
<div className="space-y-3 text-xs">
<Row label="Odoo URL" value={instance.url} />
<Row label="Database" value={instance.database} />
<Row label="Last Sync" value="2 minutes ago" />
<Row label="Status" value={<span className="text-green-600 font-medium">Healthy</span>} />
<button className="w-full text-center text-brand-600 text-xs font-medium border border-brand-200 rounded-lg py-2 hover:bg-brand-50 transition-colors mt-2">
View Connection
<Row label="Auth Type" value={<span className="capitalize">{instance.type}</span>} />
<button
onClick={() => navigate('/dashboard/connections')}
className="w-full text-center text-brand-600 text-xs font-medium border border-brand-200 rounded-lg py-2 hover:bg-brand-50 transition-colors mt-2"
>
Manage Connection
</button>
</div>
) : (
<p className="text-xs text-gray-500">No Odoo instance connected yet.{' '}
<a href="/dashboard/connections" className="text-brand-600 hover:underline">Add one </a>
<button onClick={() => navigate('/dashboard/connections')} className="text-brand-600 hover:underline">
Add one
</button>
</p>
)}
</div>
{/* Quick Actions */}
{/* Quick Actions — every action is real, no no-ops */}
<div className="bg-white rounded-xl border border-gray-100 shadow-sm p-5">
<h2 className="text-sm font-semibold text-gray-900 mb-4">Quick Actions</h2>
<div className="space-y-2">
{[
{ label: 'Copy MCP URL', icon: Copy, action: () => navigator.clipboard.writeText(MCP_URL) },
{ label: 'Rotate Token', icon: RotateCcw, action: () => {} },
{ label: 'Open Claude Setup', icon: ExternalLink, action: () => {} },
{ label: 'Open Codex Setup', icon: ExternalLink, action: () => {} },
{ label: 'View Logs', icon: Activity, action: () => {} },
].map(({ label, icon: Icon, action }) => (
<button key={label} onClick={action}
className="w-full flex items-center gap-3 text-sm text-gray-700 hover:text-brand-600 hover:bg-brand-50 px-3 py-2 rounded-lg transition-colors text-left">
<Icon size={14} className="text-gray-400" /> {label}
</button>
))}
<button onClick={() => navigator.clipboard.writeText(MCP_URL)}
className="w-full flex items-center gap-3 text-sm text-gray-700 hover:text-brand-600 hover:bg-brand-50 px-3 py-2 rounded-lg transition-colors text-left">
<Copy size={14} className="text-gray-400" /> Copy MCP URL
</button>
<button onClick={() => navigate('/dashboard/tokens')}
className="w-full flex items-center gap-3 text-sm text-gray-700 hover:text-brand-600 hover:bg-brand-50 px-3 py-2 rounded-lg transition-colors text-left">
<Key size={14} className="text-gray-400" /> Manage / Revoke API Keys
</button>
<button onClick={rotateLegacyKey} disabled={rotating}
className="w-full flex items-center gap-3 text-sm text-gray-700 hover:text-brand-600 hover:bg-brand-50 px-3 py-2 rounded-lg transition-colors text-left disabled:opacity-50">
<RotateCcw size={14} className="text-gray-400" /> {rotating ? 'Rotating…' : 'Rotate Legacy Key'}
</button>
<button onClick={() => navigate('/dashboard/endpoints')}
className="w-full flex items-center gap-3 text-sm text-gray-700 hover:text-brand-600 hover:bg-brand-50 px-3 py-2 rounded-lg transition-colors text-left">
<ExternalLink size={14} className="text-gray-400" /> MCP Client Setup Guide
</button>
<button onClick={() => navigate('/dashboard/logs')}
className="w-full flex items-center gap-3 text-sm text-gray-700 hover:text-brand-600 hover:bg-brand-50 px-3 py-2 rounded-lg transition-colors text-left">
<Cpu size={14} className="text-gray-400" /> View All Logs
</button>
</div>
</div>
{/* Usage Overview */}
{/* Usage Overview — real daily call counts from /api/analytics/timeseries */}
<div className="bg-white rounded-xl border border-gray-100 shadow-sm p-5">
<h2 className="text-sm font-semibold text-gray-900 mb-4">Usage Overview</h2>
<div className="flex items-end gap-1 h-24 mb-2">
{[40, 65, 45, 80, 55, 90, 70, 85, 60, 95, 75, 100, 60, 85].map((h, i) => (
<div key={i} className="flex-1 bg-brand-100 rounded-sm hover:bg-brand-400 transition-colors cursor-pointer"
style={{ height: `${h}%` }} />
))}
</div>
<div className="flex justify-between text-xs text-gray-400">
<span>Jun 1</span><span>Jun 15</span><span>Jun 29</span>
</div>
<p className="text-2xl font-bold text-gray-900 mt-3">{requestsUsed.toLocaleString()}</p>
<p className="text-xs text-gray-500">Requests this month</p>
<h2 className="text-sm font-semibold text-gray-900 mb-4">Usage Overview (30 days)</h2>
{timeseries.length === 0 && !loading ? (
<p className="text-xs text-gray-400 h-24 flex items-center justify-center">No activity yet.</p>
) : (
<ResponsiveContainer width="100%" height={96}>
<BarChart data={timeseries}>
<CartesianGrid strokeDasharray="3 3" stroke="#f0f0f0" />
<XAxis dataKey="period" tick={false} axisLine={false} />
<YAxis hide />
<Tooltip />
<Bar dataKey="calls" fill="#c4b5fd" radius={[2, 2, 0, 0]} />
</BarChart>
</ResponsiveContainer>
)}
<p className="text-2xl font-bold text-gray-900 mt-3">{(summary?.total_calls ?? 0).toLocaleString()}</p>
<p className="text-xs text-gray-500">
Requests in the last 30 days · {activeKeyCount ?? '—'} active API key{activeKeyCount === 1 ? '' : 's'}
</p>
</div>
</div>
{/* Recent Activity */}
{/* Recent Activity — real data from /api/logs */}
<div className="bg-white rounded-xl border border-gray-100 shadow-sm p-5">
<div className="flex items-center justify-between mb-4">
<h2 className="text-sm font-semibold text-gray-900">Recent Activity</h2>
<a href="/dashboard/logs" className="text-xs text-brand-600 hover:underline">View all logs </a>
<button onClick={() => navigate('/dashboard/logs')} className="text-xs text-brand-600 hover:underline">
View all logs
</button>
</div>
<table className="w-full text-xs">
<thead>
<tr className="text-gray-400 border-b border-gray-100">
<th className="text-left pb-2 font-medium">Time</th>
<th className="text-left pb-2 font-medium">Event</th>
<th className="text-left pb-2 font-medium">Status</th>
<th className="text-left pb-2 font-medium">Details</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-50">
{RECENT_ACTIVITY.map((row, i) => (
<tr key={i}>
<td className="py-2.5 text-gray-400">{row.time}</td>
<td className="py-2.5 text-gray-700">{row.event}</td>
<td className="py-2.5">
<span className="inline-flex items-center gap-1 text-green-600 bg-green-50 px-2 py-0.5 rounded-full">
<span className="w-1.5 h-1.5 bg-green-400 rounded-full" />{row.status}
</span>
</td>
<td className="py-2.5 text-gray-500">{row.detail}</td>
{recentLogs.length === 0 && !loading ? (
<p className="text-xs text-gray-400 text-center py-6">No tool calls yet. Connect an MCP client to get started.</p>
) : (
<table className="w-full text-xs">
<thead>
<tr className="text-gray-400 border-b border-gray-100">
<th className="text-left pb-2 font-medium">Time</th>
<th className="text-left pb-2 font-medium">Tool</th>
<th className="text-left pb-2 font-medium">Status</th>
<th className="text-left pb-2 font-medium">Duration</th>
</tr>
))}
</tbody>
</table>
</thead>
<tbody className="divide-y divide-gray-50">
{recentLogs.map((log) => (
<tr key={log.id}>
<td className="py-2.5 text-gray-400">{log.started_at}</td>
<td className="py-2.5 text-gray-700 font-mono">{log.tool_name}</td>
<td className="py-2.5">
<span className={clsx(
'inline-flex items-center gap-1 px-2 py-0.5 rounded-full',
log.success ? 'text-green-600 bg-green-50' : 'text-red-600 bg-red-50',
)}>
<span className={clsx('w-1.5 h-1.5 rounded-full', log.success ? 'bg-green-400' : 'bg-red-400')} />
{log.success ? 'Success' : 'Error'}
</span>
</td>
<td className="py-2.5 text-gray-500">{log.duration_ms} ms</td>
</tr>
))}
</tbody>
</table>
)}
</div>
</div>
);
}
function StatCard({ label, value, sub, icon, color }: {
label: string; value: React.ReactNode; sub: React.ReactNode; icon: React.ReactNode; color: string;
function StatCard({ label, value, icon, color }: {
label: string; value: React.ReactNode; icon: React.ReactNode; color: string;
}) {
return (
<div className="bg-white rounded-xl border border-gray-100 shadow-sm p-4">
@ -177,7 +236,6 @@ function StatCard({ label, value, sub, icon, color }: {
<div className={clsx('w-8 h-8 rounded-lg flex items-center justify-center', color)}>{icon}</div>
</div>
<p className="text-sm font-semibold text-gray-900">{value}</p>
<div className="text-xs text-gray-400 mt-1">{sub}</div>
</div>
);
}

View File

@ -2,23 +2,22 @@ import { useEffect, useState } from 'react';
import { useNavigate, useLocation, Link, Outlet } from 'react-router-dom';
import { session } from '@/lib/api';
import {
LayoutDashboard, Globe, Cpu, BookOpen, Shield, BarChart2,
FileText, CreditCard, Key, Users, Settings, HelpCircle, LogOut, Menu, X,
LayoutDashboard, Globe, Cpu, BarChart2, FileText, Key, LogOut, Menu,
} from 'lucide-react';
import { clsx } from 'clsx';
// NOTE: AI Setup Guides, Tools & Permissions, Billing, Team, and Settings were
// removed from this nav because no backend feature exists for them yet
// (no team/multi-user system, no per-tool permission toggles, no real
// subscription/billing system). See FUTURE_FEATURES.md for what to rebuild
// and how, once those backend features are designed.
const NAV = [
{ label: 'Dashboard', href: '/dashboard', icon: LayoutDashboard },
{ label: 'Odoo Connections', href: '/dashboard/connections', icon: Globe },
{ label: 'MCP Endpoints', href: '/dashboard/endpoints', icon: Cpu },
{ label: 'AI Setup Guides', href: '/dashboard/guides', icon: BookOpen },
{ label: 'Tools & Permissions', href: '/dashboard/tools', icon: Shield },
{ label: 'Usage', href: '/dashboard/usage', icon: BarChart2 },
{ label: 'Logs', href: '/dashboard/logs', icon: FileText },
{ label: 'Billing', href: '/dashboard/billing', icon: CreditCard },
{ label: 'API Tokens', href: '/dashboard/tokens', icon: Key },
{ label: 'Team', href: '/dashboard/team', icon: Users },
{ label: 'Settings', href: '/dashboard/settings', icon: Settings },
];
export default function DashboardLayout() {
@ -77,10 +76,6 @@ export default function DashboardLayout() {
{/* Bottom */}
<div className="px-3 pb-4 border-t border-gray-800 pt-3 space-y-0.5">
<Link to="/dashboard/support"
className="flex items-center gap-3 px-3 py-2 rounded-lg text-sm hover:bg-gray-800 hover:text-white transition-colors">
<HelpCircle size={16} /> Support
</Link>
<button
onClick={logout}
className="w-full flex items-center gap-3 px-3 py-2 rounded-lg text-sm hover:bg-gray-800 hover:text-white transition-colors">