Skip to content

Commit a746b0f

Browse files
committed
feat: Usage page redesign -- matching API Keys premium aesthetic
Redesigned UsagePage to match the new API Keys design language: - Tier badge: same amber/indigo/zinc style as API Keys cards - Resource cards: 3-column grid with large numbers, progress bar on repos - Features: row-based layout with icon boxes, descriptions, and emerald checkmarks for enabled / indigo Pro badge for locked - Full-width layout (removed max-w-4xl) - Section labels: uppercase tracking-wider muted text - Upgrade CTA: button in header + subtle banner, not heavy card - Loading skeleton: matches 3-col + feature list layout - Cost tracking: dashed border placeholder, dimmed Same data, same hooks, same API. Pure visual upgrade.
1 parent 5bec84f commit a746b0f

1 file changed

Lines changed: 189 additions & 132 deletions

File tree

frontend/src/pages/UsagePage.tsx

Lines changed: 189 additions & 132 deletions
Original file line numberDiff line numberDiff line change
@@ -2,25 +2,38 @@
22
* UsagePage -- plan info, resource usage, and feature availability.
33
*
44
* Fetches from GET /users/usage. Shows tier, repo usage,
5-
* limits, and features in a compact two-column layout.
5+
* limits, and features. Premium design matching API Keys page.
66
*/
77

88
import { useAuth } from '@/contexts/AuthContext'
99
import { useUserUsage } from '@/hooks/useCachedQuery'
1010
import {
11-
BarChart3, Package, FunctionSquare, Files, Zap, Search,
12-
Server, Sparkles, Lock, ArrowRight, TrendingUp,
11+
Package, FunctionSquare, Files, Zap, Search,
12+
Server, Sparkles, Lock, ArrowRight, TrendingUp, Check,
1313
} from 'lucide-react'
14-
import { Card, CardContent } from '@/components/ui/card'
1514
import { Button } from '@/components/ui/button'
1615
import { Badge } from '@/components/ui/badge'
17-
import { Skeleton } from '@/components/ui/Skeleton'
1816
import { cn } from '@/lib/utils'
1917

20-
const TIER_COLORS: Record<string, string> = {
21-
free: 'bg-muted text-muted-foreground',
22-
pro: 'bg-primary/10 text-primary border-primary/20',
23-
enterprise: 'bg-amber-500/10 text-amber-500 border-amber-500/20',
18+
const TIER_STYLES: Record<string, { bg: string; text: string; accent: string; label: string }> = {
19+
enterprise: {
20+
bg: 'bg-amber-500/8 border-amber-500/15',
21+
text: 'text-amber-400',
22+
accent: 'bg-amber-400',
23+
label: 'Enterprise',
24+
},
25+
pro: {
26+
bg: 'bg-indigo-500/8 border-indigo-500/15',
27+
text: 'text-indigo-400',
28+
accent: 'bg-indigo-400',
29+
label: 'Pro',
30+
},
31+
free: {
32+
bg: 'bg-zinc-500/8 border-zinc-500/15',
33+
text: 'text-zinc-400',
34+
accent: 'bg-zinc-500',
35+
label: 'Free',
36+
},
2437
}
2538

2639
export function UsagePage() {
@@ -37,138 +50,166 @@ export function UsagePage() {
3750
}
3851

3952
const tier = usage.tier || 'free'
53+
const tierStyle = TIER_STYLES[tier] || TIER_STYLES.free
4054
const repos = usage.repositories || { current: 0, limit: 3 }
4155
const limits = usage.limits || { max_files_per_repo: 500, max_functions_per_repo: 2000 }
4256
const features = usage.features || { priority_indexing: false, mcp_access: true }
4357
const isFree = tier === 'free'
58+
const repoPct = repos.limit ? (repos.current / repos.limit) * 100 : 0
4459

4560
return (
46-
<div className="space-y-4 max-w-4xl">
47-
{/* Header */}
48-
<div className="flex items-center gap-3">
49-
<div className="w-10 h-10 rounded-xl bg-primary/10 border border-primary/20 flex items-center justify-center">
50-
<BarChart3 className="w-5 h-5 text-primary" />
51-
</div>
61+
<div className="space-y-8">
62+
{/* Page header */}
63+
<div className="flex items-start justify-between">
5264
<div>
53-
<div className="flex items-center gap-2">
54-
<h1 className="text-2xl font-bold">Usage</h1>
55-
<Badge variant="outline" className={cn('capitalize', TIER_COLORS[tier])}>
56-
{tier}
65+
<div className="flex items-center gap-3">
66+
<h1 className="text-2xl font-semibold tracking-tight text-foreground">
67+
Usage
68+
</h1>
69+
<Badge
70+
variant="outline"
71+
className={cn(
72+
'text-[10px] uppercase tracking-wider font-medium border',
73+
tierStyle.bg, tierStyle.text
74+
)}
75+
>
76+
{tierStyle.label}
5777
</Badge>
5878
</div>
59-
<p className="text-sm text-muted-foreground">Plan details and resource limits</p>
79+
<p className="text-sm text-muted-foreground mt-1">
80+
Plan details, resource limits, and feature availability.
81+
</p>
6082
</div>
61-
</div>
62-
63-
{/* Upgrade CTA for free users */}
64-
{isFree && (
65-
<div className="flex items-center justify-between rounded-lg bg-primary/5 border border-primary/10 px-4 py-3">
66-
<div>
67-
<p className="text-sm font-medium">Unlock higher limits and priority indexing</p>
68-
<p className="text-xs text-muted-foreground mt-0.5">
69-
Pro: 5 repos, 100K functions/repo, Cohere reranking
70-
</p>
71-
</div>
83+
{isFree && (
7284
<Button
7385
size="sm"
74-
className="ml-4 shrink-0"
86+
className="h-9"
7587
onClick={() => window.open('https://opencodeintel.com/#pricing', '_blank')}
7688
>
77-
Upgrade to Pro
78-
<ArrowRight className="w-3.5 h-3.5 ml-1" />
89+
Upgrade
90+
<ArrowRight className="w-3.5 h-3.5 ml-1.5" />
7991
</Button>
92+
)}
93+
</div>
94+
95+
{/* Upgrade banner for free tier */}
96+
{isFree && (
97+
<div className="rounded-lg border border-indigo-500/15 bg-indigo-500/5 px-5 py-4">
98+
<p className="text-sm font-medium text-foreground">
99+
Unlock higher limits and priority indexing
100+
</p>
101+
<p className="text-xs text-muted-foreground mt-1 leading-relaxed">
102+
Pro gives you 5 repos, 100K functions per repo, Cohere reranking, and priority indexing.
103+
</p>
80104
</div>
81105
)}
82106

83-
{/* Two-column: Usage + Features */}
84-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
85-
{/* Left: Resource Usage */}
86-
<Card>
87-
<CardContent className="pt-5 space-y-4">
88-
<p className="text-sm font-medium text-muted-foreground">Resource Limits</p>
89-
<UsageRow
90-
icon={<Package className="w-4 h-4" />}
91-
label="Repositories"
92-
value={`${repos.current} / ${repos.limit ?? 'unlimited'}`}
93-
pct={repos.limit ? (repos.current / repos.limit) * 100 : 0}
94-
showBar
107+
{/* Resource usage cards */}
108+
<div>
109+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground/60 mb-3">
110+
Resources
111+
</p>
112+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
113+
{/* Repositories */}
114+
<div className="rounded-lg border border-border/60 bg-card/40 px-5 py-4">
115+
<div className="flex items-center justify-between mb-3">
116+
<div className="flex items-center gap-2 text-sm text-muted-foreground">
117+
<Package className="w-4 h-4" />
118+
Repositories
119+
</div>
120+
<span className="text-xs tabular-nums text-muted-foreground/60">
121+
{repos.current} / {repos.limit ?? '---'}
122+
</span>
123+
</div>
124+
<div className="text-2xl font-semibold tabular-nums text-foreground mb-2">
125+
{repos.current}
126+
</div>
127+
{repos.limit && (
128+
<div className="h-1.5 rounded-full bg-muted/60 overflow-hidden">
129+
<div
130+
className={cn(
131+
'h-full rounded-full transition-all duration-700',
132+
repoPct > 90 ? 'bg-red-500' : repoPct > 70 ? 'bg-amber-500' : 'bg-emerald-500'
133+
)}
134+
style={{ width: `${repoPct > 0 ? Math.max(repoPct, 4) : 0}%` }}
135+
/>
136+
</div>
137+
)}
138+
</div>
139+
140+
{/* Files per repo */}
141+
<div className="rounded-lg border border-border/60 bg-card/40 px-5 py-4">
142+
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-3">
143+
<Files className="w-4 h-4" />
144+
Files per repo
145+
</div>
146+
<div className="text-2xl font-semibold tabular-nums text-foreground">
147+
{limits.max_files_per_repo.toLocaleString()}
148+
</div>
149+
<p className="text-[11px] text-muted-foreground/50 mt-1">maximum</p>
150+
</div>
151+
152+
{/* Functions per repo */}
153+
<div className="rounded-lg border border-border/60 bg-card/40 px-5 py-4">
154+
<div className="flex items-center gap-2 text-sm text-muted-foreground mb-3">
155+
<FunctionSquare className="w-4 h-4" />
156+
Functions per repo
157+
</div>
158+
<div className="text-2xl font-semibold tabular-nums text-foreground">
159+
{limits.max_functions_per_repo.toLocaleString()}
160+
</div>
161+
<p className="text-[11px] text-muted-foreground/50 mt-1">maximum</p>
162+
</div>
163+
</div>
164+
</div>
165+
166+
{/* Features */}
167+
<div>
168+
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground/60 mb-3">
169+
Features
170+
</p>
171+
<div className="rounded-lg border border-border/60 bg-card/40 overflow-hidden">
172+
<div className="divide-y divide-border/40">
173+
<FeatureRow
174+
icon={<Search className="w-4 h-4" />}
175+
label="Semantic Code Search"
176+
description="Find code by meaning, not just keywords"
177+
enabled
95178
/>
96-
<UsageRow
97-
icon={<Files className="w-4 h-4" />}
98-
label="Files / repo"
99-
value={`up to ${limits.max_files_per_repo.toLocaleString()}`}
179+
<FeatureRow
180+
icon={<Sparkles className="w-4 h-4" />}
181+
label="Codebase DNA"
182+
description="Extract architectural patterns and conventions"
183+
enabled
100184
/>
101-
<UsageRow
102-
icon={<FunctionSquare className="w-4 h-4" />}
103-
label="Functions / repo"
104-
value={`up to ${limits.max_functions_per_repo.toLocaleString()}`}
185+
<FeatureRow
186+
icon={<Server className="w-4 h-4" />}
187+
label="MCP Server Access"
188+
description="Connect Claude Desktop, Claude Code, and Cursor"
189+
enabled={features.mcp_access}
105190
/>
106-
</CardContent>
107-
</Card>
108-
109-
{/* Right: Features */}
110-
<Card>
111-
<CardContent className="pt-5 space-y-3">
112-
<p className="text-sm font-medium text-muted-foreground">Features</p>
113-
<FeatureRow icon={<Search className="w-4 h-4" />} label="Semantic Code Search" enabled />
114-
<FeatureRow icon={<Sparkles className="w-4 h-4" />} label="Codebase DNA" enabled />
115-
<FeatureRow icon={<Server className="w-4 h-4" />} label="MCP Server Access" enabled={features.mcp_access} />
116-
<FeatureRow icon={<Zap className="w-4 h-4" />} label="Priority Indexing" enabled={features.priority_indexing} />
117-
</CardContent>
118-
</Card>
191+
<FeatureRow
192+
icon={<Zap className="w-4 h-4" />}
193+
label="Priority Indexing"
194+
description="Skip the queue on indexing and re-indexing"
195+
enabled={features.priority_indexing}
196+
/>
197+
</div>
198+
</div>
119199
</div>
120200

121201
{/* Cost tracking placeholder */}
122-
<Card className="border-dashed">
123-
<CardContent className="py-5 flex items-center gap-4">
124-
<div className="w-9 h-9 rounded-full bg-muted flex items-center justify-center shrink-0">
125-
<TrendingUp className="w-4 h-4 text-muted-foreground" />
126-
</div>
127-
<div>
128-
<p className="text-sm font-medium">API Cost Tracking</p>
129-
<p className="text-xs text-muted-foreground">
130-
Token usage, cost breakdown by model, and monthly spend tracking -- coming soon.
131-
</p>
132-
</div>
133-
</CardContent>
134-
</Card>
135-
</div>
136-
)
137-
}
138-
139-
140-
function UsageRow({
141-
icon,
142-
label,
143-
value,
144-
pct,
145-
showBar,
146-
}: {
147-
icon: React.ReactNode
148-
label: string
149-
value: string
150-
pct?: number
151-
showBar?: boolean
152-
}) {
153-
const barColor = (pct ?? 0) > 90 ? 'bg-destructive' : (pct ?? 0) > 70 ? 'bg-amber-500' : 'bg-emerald-500'
154-
155-
return (
156-
<div className="space-y-1.5">
157-
<div className="flex items-center justify-between text-sm">
158-
<div className="flex items-center gap-2">
159-
<span className="text-muted-foreground">{icon}</span>
160-
{label}
202+
<div className="rounded-lg border border-dashed border-border/40 bg-card/20 px-5 py-4 flex items-center gap-4">
203+
<div className="w-9 h-9 rounded-full bg-muted/30 flex items-center justify-center shrink-0">
204+
<TrendingUp className="w-4 h-4 text-muted-foreground/50" />
161205
</div>
162-
<span className="tabular-nums text-muted-foreground">{value}</span>
163-
</div>
164-
{showBar && pct !== undefined && (
165-
<div className="h-1.5 rounded-full bg-muted overflow-hidden">
166-
<div
167-
className={cn('h-full rounded-full transition-all duration-500', barColor)}
168-
style={{ width: `${pct > 0 ? Math.max(pct, 2) : 0}%` }}
169-
/>
206+
<div>
207+
<p className="text-sm font-medium text-muted-foreground">API Cost Tracking</p>
208+
<p className="text-[11px] text-muted-foreground/50 mt-0.5">
209+
Token usage, cost breakdown by model, and monthly spend tracking -- coming soon.
210+
</p>
170211
</div>
171-
)}
212+
</div>
172213
</div>
173214
)
174215
}
@@ -177,23 +218,40 @@ function UsageRow({
177218
function FeatureRow({
178219
icon,
179220
label,
221+
description,
180222
enabled,
181223
}: {
182224
icon: React.ReactNode
183225
label: string
226+
description: string
184227
enabled: boolean
185228
}) {
186229
return (
187230
<div className={cn(
188-
'flex items-center gap-2.5 text-sm py-1',
189-
enabled ? 'text-foreground' : 'text-muted-foreground opacity-50',
231+
'flex items-center gap-4 px-5 py-3.5',
232+
!enabled && 'opacity-40',
190233
)}>
191-
<span className={enabled ? 'text-primary' : ''}>
234+
<div className={cn(
235+
'w-8 h-8 rounded-lg flex items-center justify-center shrink-0',
236+
enabled
237+
? 'bg-primary/10 text-primary'
238+
: 'bg-muted/50 text-muted-foreground'
239+
)}>
192240
{enabled ? icon : <Lock className="w-4 h-4" />}
193-
</span>
194-
<span className="flex-1">{label}</span>
195-
{!enabled && (
196-
<Badge variant="outline" className="text-[10px] px-1.5 py-0">Pro</Badge>
241+
</div>
242+
<div className="flex-1 min-w-0">
243+
<p className="text-sm font-medium text-foreground">{label}</p>
244+
<p className="text-[11px] text-muted-foreground/60 mt-0.5">{description}</p>
245+
</div>
246+
{enabled ? (
247+
<Check className="w-4 h-4 text-emerald-400 shrink-0" />
248+
) : (
249+
<Badge
250+
variant="outline"
251+
className="text-[10px] uppercase tracking-wider border-indigo-500/15 bg-indigo-500/8 text-indigo-400 shrink-0"
252+
>
253+
Pro
254+
</Badge>
197255
)}
198256
</div>
199257
)
@@ -202,18 +260,17 @@ function FeatureRow({
202260

203261
function UsageSkeleton() {
204262
return (
205-
<div className="space-y-4 max-w-4xl">
206-
<div className="flex items-center gap-3">
207-
<Skeleton className="w-10 h-10 rounded-xl" />
208-
<div className="space-y-2">
209-
<Skeleton className="h-6 w-32" />
210-
<Skeleton className="h-4 w-48" />
211-
</div>
263+
<div className="space-y-8">
264+
<div className="space-y-2">
265+
<div className="h-7 w-48 rounded bg-muted/50 animate-pulse" />
266+
<div className="h-4 w-72 rounded bg-muted/30 animate-pulse" />
212267
</div>
213-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
214-
<Skeleton className="h-40 rounded-lg" />
215-
<Skeleton className="h-40 rounded-lg" />
268+
<div className="grid grid-cols-1 md:grid-cols-3 gap-3">
269+
{[1, 2, 3].map((i) => (
270+
<div key={i} className="h-28 rounded-lg bg-muted/20 animate-pulse" />
271+
))}
216272
</div>
273+
<div className="h-52 rounded-lg bg-muted/20 animate-pulse" />
217274
</div>
218275
)
219276
}

0 commit comments

Comments
 (0)