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
98import { useAuth } from '@/contexts/AuthContext'
109import { 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'
1315import { Button } from '@/components/ui/button'
1416import { Badge } from '@/components/ui/badge'
15- import { Separator } from '@/components/ui/separator'
1617import { Skeleton } from '@/components/ui/Skeleton'
1718import { 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
229199function 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