diff --git a/frontend/src/components/Dashboard.tsx b/frontend/src/components/Dashboard.tsx index 9f46503..4a07ce6 100644 --- a/frontend/src/components/Dashboard.tsx +++ b/frontend/src/components/Dashboard.tsx @@ -2,12 +2,14 @@ import { Routes, Route, Navigate } from 'react-router-dom' import { DashboardLayout } from './dashboard/DashboardLayout' import { DashboardHome } from './dashboard/DashboardHome' import { SettingsPage } from '../pages/SettingsPage' +import { UsagePage } from '../pages/UsagePage' export function Dashboard() { return ( } /> + } /> } /> } /> diff --git a/frontend/src/components/dashboard/Sidebar.tsx b/frontend/src/components/dashboard/Sidebar.tsx index 0753e33..7788f6d 100644 --- a/frontend/src/components/dashboard/Sidebar.tsx +++ b/frontend/src/components/dashboard/Sidebar.tsx @@ -1,6 +1,7 @@ import { Link, useLocation } from 'react-router-dom' import { FolderGit2, + BarChart3, BookOpen, ChevronLeft, ChevronRight, @@ -24,6 +25,7 @@ interface NavItem { const mainNavItems: NavItem[] = [ { name: 'Repositories', href: '/dashboard', icon: }, + { name: 'Usage', href: '/dashboard/usage', icon: }, ] const bottomNavItems: NavItem[] = [ diff --git a/frontend/src/pages/UsagePage.tsx b/frontend/src/pages/UsagePage.tsx new file mode 100644 index 0000000..483e399 --- /dev/null +++ b/frontend/src/pages/UsagePage.tsx @@ -0,0 +1,219 @@ +/** + * UsagePage -- plan info, resource usage, and feature availability. + * + * Fetches from GET /users/usage. Shows tier, repo usage, + * limits, and features in a compact two-column layout. + */ + +import { useAuth } from '@/contexts/AuthContext' +import { useUserUsage } from '@/hooks/useCachedQuery' +import { + BarChart3, Package, FunctionSquare, Files, Zap, Search, + Server, Sparkles, Lock, ArrowRight, TrendingUp, +} from 'lucide-react' +import { Card, CardContent } from '@/components/ui/card' +import { Button } from '@/components/ui/button' +import { Badge } from '@/components/ui/badge' +import { Skeleton } from '@/components/ui/Skeleton' +import { cn } from '@/lib/utils' + +const TIER_COLORS: Record = { + free: 'bg-muted text-muted-foreground', + pro: 'bg-primary/10 text-primary border-primary/20', + enterprise: 'bg-amber-500/10 text-amber-500 border-amber-500/20', +} + +export function UsagePage() { + const { session } = useAuth() + const { data: usage, isLoading } = useUserUsage(session?.access_token, session?.user?.id) + + if (isLoading) return + if (!usage) { + return ( +
+ Failed to load usage data. +
+ ) + } + + const tier = usage.tier || 'free' + const repos = usage.repositories || { current: 0, limit: 3 } + const limits = usage.limits || { max_files_per_repo: 500, max_functions_per_repo: 2000 } + const features = usage.features || { priority_indexing: false, mcp_access: true } + const isFree = tier === 'free' + + return ( +
+ {/* Header */} +
+
+ +
+
+
+

Usage

+ + {tier} + +
+

Plan details and resource limits

+
+
+ + {/* Upgrade CTA for free users */} + {isFree && ( +
+
+

Unlock higher limits and priority indexing

+

+ Pro: 20 repos, 20K functions/repo, Cohere reranking +

+
+ +
+ )} + + {/* Two-column: Usage + Features */} +
+ {/* Left: Resource Usage */} + + +

Resource Limits

+ } + label="Repositories" + value={`${repos.current} / ${repos.limit ?? 'unlimited'}`} + pct={repos.limit ? (repos.current / repos.limit) * 100 : 0} + showBar + /> + } + label="Files / repo" + value={`up to ${limits.max_files_per_repo.toLocaleString()}`} + /> + } + label="Functions / repo" + value={`up to ${limits.max_functions_per_repo.toLocaleString()}`} + /> +
+
+ + {/* Right: Features */} + + +

Features

+ } label="Semantic Code Search" enabled /> + } label="Codebase DNA" enabled /> + } label="MCP Server Access" enabled={features.mcp_access} /> + } label="Priority Indexing" enabled={features.priority_indexing} /> +
+
+
+ + {/* Cost tracking placeholder */} + + +
+ +
+
+

API Cost Tracking

+

+ Token usage, cost breakdown by model, and monthly spend tracking -- coming soon. +

+
+
+
+
+ ) +} + + +function UsageRow({ + icon, + label, + value, + pct, + showBar, +}: { + icon: React.ReactNode + label: string + value: string + pct?: number + showBar?: boolean +}) { + const barColor = (pct ?? 0) > 90 ? 'bg-destructive' : (pct ?? 0) > 70 ? 'bg-amber-500' : 'bg-emerald-500' + + return ( +
+
+
+ {icon} + {label} +
+ {value} +
+ {showBar && pct !== undefined && ( +
+
0 ? Math.max(pct, 2) : 0}%` }} + /> +
+ )} +
+ ) +} + + +function FeatureRow({ + icon, + label, + enabled, +}: { + icon: React.ReactNode + label: string + enabled: boolean +}) { + return ( +
+ + {enabled ? icon : } + + {label} + {!enabled && ( + Pro + )} +
+ ) +} + + +function UsageSkeleton() { + return ( +
+
+ +
+ + +
+
+
+ + +
+
+ ) +}