Skip to content

Commit 970b061

Browse files
committed
refactor: compact two-column layout for Usage page
Major layout improvements: - Tier badge moved to header row (saves a whole card) - Upgrade CTA is a slim banner, not a card-in-card - Two-column grid: Resource Limits left, Features right - Features as simple rows instead of bordered cards - Cost placeholder horizontal (icon + text), not centered block - Thinner progress bar (h-1.5 vs h-2) - Less padding everywhere, more information density - 215 lines, same data, half the vertical space
1 parent f314a0b commit 970b061

1 file changed

Lines changed: 105 additions & 134 deletions

File tree

frontend/src/pages/UsagePage.tsx

Lines changed: 105 additions & 134 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
/**
22
* UsagePage -- plan info, resource usage, and feature availability.
33
*
4-
* Fetches from GET /users/usage. Shows tier, repo usage bars,
5-
* function/file limits, and which features are available on the
6-
* user's current plan.
4+
* Fetches from GET /users/usage. Shows tier, repo usage,
5+
* limits, and features in a compact two-column layout.
76
*/
87

98
import { useAuth } from '@/contexts/AuthContext'
109
import { useUserUsage } from '@/hooks/useCachedQuery'
11-
import { BarChart3, Package, FunctionSquare, Files, Zap, Search, Server, Sparkles, Lock, ArrowRight, TrendingUp } from 'lucide-react'
12-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
10+
import {
11+
BarChart3, Package, FunctionSquare, Files, Zap, Search,
12+
Server, Sparkles, Lock, ArrowRight, TrendingUp,
13+
} from 'lucide-react'
14+
import { Card, CardContent } from '@/components/ui/card'
1315
import { Button } from '@/components/ui/button'
1416
import { Badge } from '@/components/ui/badge'
15-
import { Separator } from '@/components/ui/separator'
1617
import { Skeleton } from '@/components/ui/Skeleton'
1718
import { cn } from '@/lib/utils'
1819

@@ -27,7 +28,6 @@ export function UsagePage() {
2728
const { data: usage, isLoading } = useUserUsage(session?.access_token, session?.user?.id)
2829

2930
if (isLoading) return <UsageSkeleton />
30-
3131
if (!usage) {
3232
return (
3333
<div className="flex items-center justify-center min-h-[300px] text-muted-foreground">
@@ -40,76 +40,91 @@ export function UsagePage() {
4040
const repos = usage.repositories || { current: 0, limit: 3 }
4141
const limits = usage.limits || { max_files_per_repo: 500, max_functions_per_repo: 2000 }
4242
const features = usage.features || { priority_indexing: false, mcp_access: true }
43+
const isFree = tier === 'free'
4344

4445
return (
45-
<div className="space-y-6 max-w-3xl">
46+
<div className="space-y-4 max-w-4xl">
47+
{/* Header */}
4648
<div className="flex items-center gap-3">
4749
<div className="w-10 h-10 rounded-xl bg-primary/10 border border-primary/20 flex items-center justify-center">
4850
<BarChart3 className="w-5 h-5 text-primary" />
4951
</div>
5052
<div>
51-
<h1 className="text-2xl font-bold">Usage</h1>
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}
57+
</Badge>
58+
</div>
5259
<p className="text-sm text-muted-foreground">Plan details and resource limits</p>
5360
</div>
5461
</div>
5562

56-
<PlanCard tier={tier} />
57-
58-
<Card>
59-
<CardHeader className="pb-3">
60-
<CardTitle className="text-base">Resource Usage</CardTitle>
61-
</CardHeader>
62-
<CardContent className="space-y-5">
63-
<UsageBar
64-
icon={<Package className="w-4 h-4" />}
65-
label="Repositories"
66-
current={repos.current}
67-
limit={repos.limit}
68-
/>
69-
<Separator />
70-
<UsageBar
71-
icon={<Files className="w-4 h-4" />}
72-
label="Files per repository"
73-
current={null}
74-
limit={limits.max_files_per_repo}
75-
/>
76-
<Separator />
77-
<UsageBar
78-
icon={<FunctionSquare className="w-4 h-4" />}
79-
label="Functions per repository"
80-
current={null}
81-
limit={limits.max_functions_per_repo}
82-
/>
83-
</CardContent>
84-
</Card>
85-
86-
<Card>
87-
<CardHeader className="pb-3">
88-
<CardTitle className="text-base">Features</CardTitle>
89-
</CardHeader>
90-
<CardContent>
91-
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
92-
<FeatureItem icon={<Search className="w-4 h-4" />} label="Semantic Code Search" enabled />
93-
<FeatureItem icon={<Sparkles className="w-4 h-4" />} label="Codebase DNA" enabled />
94-
<FeatureItem icon={<Server className="w-4 h-4" />} label="MCP Server Access" enabled={features.mcp_access} />
95-
<FeatureItem icon={<Zap className="w-4 h-4" />} label="Priority Indexing" enabled={features.priority_indexing} />
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: 20 repos, 20K functions/repo, Cohere reranking
70+
</p>
9671
</div>
97-
</CardContent>
98-
</Card>
72+
<Button size="sm" className="ml-4 shrink-0">
73+
Upgrade to Pro
74+
<ArrowRight className="w-3.5 h-3.5 ml-1" />
75+
</Button>
76+
</div>
77+
)}
9978

79+
{/* Two-column: Usage + Features */}
80+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
81+
{/* Left: Resource Usage */}
82+
<Card>
83+
<CardContent className="pt-5 space-y-4">
84+
<p className="text-sm font-medium text-muted-foreground">Resource Limits</p>
85+
<UsageRow
86+
icon={<Package className="w-4 h-4" />}
87+
label="Repositories"
88+
value={`${repos.current} / ${repos.limit ?? 'unlimited'}`}
89+
pct={repos.limit ? (repos.current / repos.limit) * 100 : 0}
90+
showBar
91+
/>
92+
<UsageRow
93+
icon={<Files className="w-4 h-4" />}
94+
label="Files / repo"
95+
value={`up to ${limits.max_files_per_repo.toLocaleString()}`}
96+
/>
97+
<UsageRow
98+
icon={<FunctionSquare className="w-4 h-4" />}
99+
label="Functions / repo"
100+
value={`up to ${limits.max_functions_per_repo.toLocaleString()}`}
101+
/>
102+
</CardContent>
103+
</Card>
104+
105+
{/* Right: Features */}
106+
<Card>
107+
<CardContent className="pt-5 space-y-3">
108+
<p className="text-sm font-medium text-muted-foreground">Features</p>
109+
<FeatureRow icon={<Search className="w-4 h-4" />} label="Semantic Code Search" enabled />
110+
<FeatureRow icon={<Sparkles className="w-4 h-4" />} label="Codebase DNA" enabled />
111+
<FeatureRow icon={<Server className="w-4 h-4" />} label="MCP Server Access" enabled={features.mcp_access} />
112+
<FeatureRow icon={<Zap className="w-4 h-4" />} label="Priority Indexing" enabled={features.priority_indexing} />
113+
</CardContent>
114+
</Card>
115+
</div>
116+
117+
{/* Cost tracking placeholder */}
100118
<Card className="border-dashed">
101-
<CardContent className="py-8">
102-
<div className="flex flex-col items-center text-center gap-3">
103-
<div className="w-10 h-10 rounded-full bg-muted flex items-center justify-center">
104-
<TrendingUp className="w-5 h-5 text-muted-foreground" />
105-
</div>
106-
<div>
107-
<p className="text-sm font-medium">API Cost Tracking</p>
108-
<p className="text-xs text-muted-foreground mt-1 max-w-xs">
109-
Token usage, cost breakdown by model (OpenAI, Voyage, Cohere),
110-
and monthly spend tracking -- coming soon.
111-
</p>
112-
</div>
119+
<CardContent className="py-5 flex items-center gap-4">
120+
<div className="w-9 h-9 rounded-full bg-muted flex items-center justify-center shrink-0">
121+
<TrendingUp className="w-4 h-4 text-muted-foreground" />
122+
</div>
123+
<div>
124+
<p className="text-sm font-medium">API Cost Tracking</p>
125+
<p className="text-xs text-muted-foreground">
126+
Token usage, cost breakdown by model, and monthly spend tracking -- coming soon.
127+
</p>
113128
</div>
114129
</CardContent>
115130
</Card>
@@ -118,77 +133,34 @@ export function UsagePage() {
118133
}
119134

120135

121-
function PlanCard({ tier }: { tier: string }) {
122-
const isFree = tier === 'free'
123-
124-
return (
125-
<Card className={isFree ? 'border-primary/20' : ''}>
126-
<CardContent className="py-5 space-y-4">
127-
<div className="flex items-center justify-between">
128-
<div className="flex items-center gap-3">
129-
<span className="text-sm text-muted-foreground">Current plan</span>
130-
<Badge
131-
variant="outline"
132-
className={cn('capitalize text-sm px-3 py-1', TIER_COLORS[tier])}
133-
>
134-
{tier}
135-
</Badge>
136-
</div>
137-
{!isFree && (
138-
<span className="text-xs text-muted-foreground">Active</span>
139-
)}
140-
</div>
141-
{isFree && (
142-
<div className="flex items-center justify-between rounded-lg bg-primary/5 border border-primary/10 px-4 py-3">
143-
<div>
144-
<p className="text-sm font-medium">Unlock higher limits and priority indexing</p>
145-
<p className="text-xs text-muted-foreground mt-0.5">
146-
Pro: 20 repos, 20K functions/repo, Cohere reranking
147-
</p>
148-
</div>
149-
<Button size="sm" className="ml-4 shrink-0">
150-
Upgrade to Pro
151-
<ArrowRight className="w-3.5 h-3.5 ml-1" />
152-
</Button>
153-
</div>
154-
)}
155-
</CardContent>
156-
</Card>
157-
)
158-
}
159-
160-
161-
function UsageBar({
136+
function UsageRow({
162137
icon,
163138
label,
164-
current,
165-
limit,
139+
value,
140+
pct,
141+
showBar,
166142
}: {
167143
icon: React.ReactNode
168144
label: string
169-
current: number | null
170-
limit: number | null
145+
value: string
146+
pct?: number
147+
showBar?: boolean
171148
}) {
172-
const hasBar = current !== null && limit !== null && limit > 0
173-
const pct = hasBar ? Math.min((current / limit!) * 100, 100) : 0
174-
const barColor = pct > 90 ? 'bg-destructive' : pct > 70 ? 'bg-amber-500' : 'bg-emerald-500'
149+
const barColor = (pct ?? 0) > 90 ? 'bg-destructive' : (pct ?? 0) > 70 ? 'bg-amber-500' : 'bg-emerald-500'
175150

176151
return (
177-
<div className="space-y-2">
178-
<div className="flex items-center justify-between">
179-
<div className="flex items-center gap-2 text-sm">
152+
<div className="space-y-1.5">
153+
<div className="flex items-center justify-between text-sm">
154+
<div className="flex items-center gap-2">
180155
<span className="text-muted-foreground">{icon}</span>
181156
{label}
182157
</div>
183-
<span className="text-sm tabular-nums text-muted-foreground">
184-
{current !== null ? `${current.toLocaleString()} / ` : 'up to '}
185-
{limit !== null ? limit.toLocaleString() : 'unlimited'}
186-
</span>
158+
<span className="tabular-nums text-muted-foreground">{value}</span>
187159
</div>
188-
{hasBar && (
189-
<div className="h-2 rounded-full bg-muted overflow-hidden">
160+
{showBar && pct !== undefined && (
161+
<div className="h-1.5 rounded-full bg-muted overflow-hidden">
190162
<div
191-
className={cn('h-full rounded-full transition-all duration-500 ease-out', barColor)}
163+
className={cn('h-full rounded-full transition-all duration-500', barColor)}
192164
style={{ width: `${Math.max(pct, 2)}%` }}
193165
/>
194166
</div>
@@ -198,7 +170,7 @@ function UsageBar({
198170
}
199171

200172

201-
function FeatureItem({
173+
function FeatureRow({
202174
icon,
203175
label,
204176
enabled,
@@ -209,17 +181,15 @@ function FeatureItem({
209181
}) {
210182
return (
211183
<div className={cn(
212-
'flex items-center gap-2.5 rounded-lg border px-3 py-2.5 text-sm',
213-
enabled
214-
? 'border-border text-foreground'
215-
: 'border-border/50 text-muted-foreground opacity-60',
184+
'flex items-center gap-2.5 text-sm py-1',
185+
enabled ? 'text-foreground' : 'text-muted-foreground opacity-50',
216186
)}>
217-
<span className={enabled ? 'text-primary' : 'text-muted-foreground'}>
187+
<span className={enabled ? 'text-primary' : ''}>
218188
{enabled ? icon : <Lock className="w-4 h-4" />}
219189
</span>
220-
{label}
190+
<span className="flex-1">{label}</span>
221191
{!enabled && (
222-
<Badge variant="outline" className="ml-auto text-[10px] px-1.5 py-0">Pro</Badge>
192+
<Badge variant="outline" className="text-[10px] px-1.5 py-0">Pro</Badge>
223193
)}
224194
</div>
225195
)
@@ -228,17 +198,18 @@ function FeatureItem({
228198

229199
function UsageSkeleton() {
230200
return (
231-
<div className="space-y-6 max-w-3xl">
201+
<div className="space-y-4 max-w-4xl">
232202
<div className="flex items-center gap-3">
233203
<Skeleton className="w-10 h-10 rounded-xl" />
234204
<div className="space-y-2">
235-
<Skeleton className="h-6 w-24" />
205+
<Skeleton className="h-6 w-32" />
236206
<Skeleton className="h-4 w-48" />
237207
</div>
238208
</div>
239-
<Skeleton className="h-20 rounded-lg" />
240-
<Skeleton className="h-48 rounded-lg" />
241-
<Skeleton className="h-32 rounded-lg" />
209+
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
210+
<Skeleton className="h-40 rounded-lg" />
211+
<Skeleton className="h-40 rounded-lg" />
212+
</div>
242213
</div>
243214
)
244215
}

0 commit comments

Comments
 (0)