@@ -4,9 +4,6 @@ import { SUBSCRIPTION_DISPLAY_NAME } from '@codebuff/common/constants/subscripti
44import { env } from '@codebuff/common/env'
55import { useQuery } from '@tanstack/react-query'
66import {
7- Zap ,
8- Clock ,
9- CalendarDays ,
107 AlertTriangle ,
118 ExternalLink ,
129 Loader2 ,
@@ -47,45 +44,26 @@ interface SubscriptionApiResponse {
4744 }
4845}
4946
50- function formatRelativeTime ( dateStr : string ) : string {
47+ function formatHours ( dateStr : string ) : string {
5148 const target = new Date ( dateStr )
5249 const now = new Date ( )
5350 const diffMs = target . getTime ( ) - now . getTime ( )
54- if ( diffMs <= 0 ) return 'now'
55- const hours = Math . floor ( diffMs / ( 1000 * 60 * 60 ) )
56- const minutes = Math . floor ( ( diffMs % ( 1000 * 60 * 60 ) ) / ( 1000 * 60 ) )
57- if ( hours > 0 ) return `${ hours } h ${ minutes } m`
58- return `${ minutes } m`
51+ if ( isNaN ( diffMs ) || diffMs <= 0 ) return '0h'
52+ const hours = Math . ceil ( diffMs / ( 1000 * 60 * 60 ) )
53+ return `${ hours } h`
5954}
6055
61- function formatDate ( dateStr : string ) : string {
62- return new Date ( dateStr ) . toLocaleDateString ( 'en-US' , {
63- month : 'short' ,
64- day : 'numeric' ,
65- year : 'numeric' ,
66- } )
67- }
68-
69- function formatShortDate ( dateStr : string ) : string {
70- return new Date ( dateStr ) . toLocaleDateString ( 'en-US' , {
71- weekday : 'short' ,
72- month : 'short' ,
73- day : 'numeric' ,
74- } )
75- }
7656
7757function ProgressBar ( {
78- value,
79- max,
58+ percentAvailable,
8059 label,
8160 className,
8261} : {
83- value : number
84- max : number
62+ percentAvailable : number
8563 label : string
8664 className ?: string
8765} ) {
88- const percent = max > 0 ? Math . min ( 100 , Math . round ( ( value / max ) * 100 ) ) : 0
66+ const percent = Math . min ( 100 , Math . max ( 0 , Math . round ( percentAvailable ) ) )
8967 return (
9068 < div
9169 role = "progressbar"
@@ -101,11 +79,11 @@ function ProgressBar({
10179 < div
10280 className = { cn (
10381 'h-full rounded-full transition-all duration-500' ,
104- percent >= 100
82+ percent <= 0
10583 ? 'bg-red-500'
106- : percent >= 75
84+ : percent <= 25
10785 ? 'bg-yellow-500'
108- : 'bg-indigo -500' ,
86+ : 'bg-green -500' ,
10987 ) }
11088 style = { { width : `${ percent } %` } }
11189 />
@@ -126,23 +104,30 @@ function SubscriptionActive({
126104 const billingPortalUrl = `${ env . NEXT_PUBLIC_STRIPE_CUSTOMER_PORTAL } ?prefilled_email=${ encodeURIComponent ( email ) } `
127105
128106 return (
129- < Card >
130- < CardHeader className = "pb-3 " >
107+ < Card className = "max-w-xl" >
108+ < CardHeader className = "pb-5 " >
131109 < div className = "flex items-center justify-between" >
132- < CardTitle className = "flex items-center gap-2 text-lg" >
133- < Zap className = "h-5 w-5 text-indigo-500" / >
134- { SUBSCRIPTION_DISPLAY_NAME } · $ { subscription ?. tier ?? 200 } /mo
135- </ CardTitle >
136- < span
137- className = { cn (
138- 'inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium' ,
139- isCanceling
140- ? 'bg-muted text-muted-foreground'
141- : 'bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400' ,
110+ < CardTitle className = "flex items-baseline gap-2 text-lg" >
111+ < span > 💪 </ span >
112+ { SUBSCRIPTION_DISPLAY_NAME }
113+ < span className = "text-sm font-normal text-muted-foreground" >
114+ $ { subscription ?. tier ?? 200 } /mo
115+ </ span >
116+ { isCanceling && (
117+ < span className = "inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium bg-muted text-muted-foreground" >
118+ Canceling
119+ </ span >
142120 ) }
121+ </ CardTitle >
122+ < a
123+ href = { billingPortalUrl }
124+ target = "_blank"
125+ rel = "noopener noreferrer"
126+ className = "text-sm text-muted-foreground hover:text-foreground flex items-center gap-1"
143127 >
144- { isCanceling ? 'Canceling' : 'Active' }
145- </ span >
128+ Manage
129+ < ExternalLink className = "h-3.5 w-3.5" />
130+ </ a >
146131 </ div >
147132 </ CardHeader >
148133 < CardContent className = "space-y-5" >
@@ -151,64 +136,40 @@ function SubscriptionActive({
151136 < >
152137 < div className = "space-y-2" >
153138 < div className = "flex items-center justify-between text-sm" >
154- < span className = "flex items-center gap-1.5 font-medium" >
155- < Clock className = "h-3.5 w-3.5 text-muted-foreground" />
156- Current Block
139+ < span className = "font-medium" >
140+ Session
141+ </ span >
142+ < span className = "text-muted-foreground" >
143+ { rateLimit . blockLimit != null && rateLimit . blockUsed != null && rateLimit . blockLimit > 0
144+ ? `${ Math . round ( 100 - ( rateLimit . blockUsed / rateLimit . blockLimit ) * 100 ) } %`
145+ : '100%' }
146+ { rateLimit . blockResetsAt && ` · Resets in ${ formatHours ( rateLimit . blockResetsAt ) } ` }
157147 </ span >
158- { rateLimit . blockResetsAt ? (
159- < span className = "text-muted-foreground" >
160- Resets in { formatRelativeTime ( rateLimit . blockResetsAt ) }
161- </ span >
162- ) : rateLimit . canStartNewBlock ? (
163- < span className = "text-muted-foreground" >
164- Ready for new session
165- </ span >
166- ) : null }
167148 </ div >
168- { rateLimit . blockLimit != null &&
169- rateLimit . blockUsed != null ? (
170- < >
171- < ProgressBar
172- value = { rateLimit . blockUsed }
173- max = { rateLimit . blockLimit }
174- label = "Block usage"
175- />
176- < p className = "text-xs text-muted-foreground" >
177- { rateLimit . blockLimit > 0
178- ? `${ Math . round ( ( rateLimit . blockUsed / rateLimit . blockLimit ) * 100 ) } % used`
179- : '0% used' }
180- </ p >
181- </ >
182- ) : (
183- < >
184- < ProgressBar value = { 0 } max = { 1 } label = "Block usage" />
185- < p className = "text-xs text-muted-foreground" >
186- No active block — a new session will start when you use
187- Codebuff
188- </ p >
189- </ >
190- ) }
149+ < ProgressBar
150+ percentAvailable = {
151+ rateLimit . blockLimit != null && rateLimit . blockUsed != null && rateLimit . blockLimit > 0
152+ ? 100 - ( rateLimit . blockUsed / rateLimit . blockLimit ) * 100
153+ : 100
154+ }
155+ label = "Session usage"
156+ />
191157 </ div >
192158
193159 { /* Weekly usage */ }
194160 < div className = "space-y-2" >
195161 < div className = "flex items-center justify-between text-sm" >
196- < span className = "flex items-center gap-1.5 font-medium" >
197- < CalendarDays className = "h-3.5 w-3.5 text-muted-foreground" />
198- Weekly Usage
162+ < span className = "font-medium" >
163+ Weekly
199164 </ span >
200165 < span className = "text-muted-foreground" >
201- Resets { formatShortDate ( rateLimit . weeklyResetsAt ) }
166+ { 100 - rateLimit . weeklyPercentUsed } % · Resets in { formatHours ( rateLimit . weeklyResetsAt ) }
202167 </ span >
203168 </ div >
204169 < ProgressBar
205- value = { rateLimit . weeklyUsed }
206- max = { rateLimit . weeklyLimit }
170+ percentAvailable = { 100 - rateLimit . weeklyPercentUsed }
207171 label = "Weekly usage"
208172 />
209- < p className = "text-xs text-muted-foreground" >
210- { rateLimit . weeklyPercentUsed } % used
211- </ p >
212173 </ div >
213174
214175 { /* Rate limit warning */ }
@@ -217,33 +178,15 @@ function SubscriptionActive({
217178 < AlertTriangle className = "mt-0.5 h-4 w-4 flex-shrink-0 text-yellow-600 dark:text-yellow-400" />
218179 < p className = "text-sm text-yellow-800 dark:text-yellow-300" >
219180 { rateLimit . reason === 'weekly_limit'
220- ? `Weekly limit reached. Resets ${ formatShortDate ( rateLimit . weeklyResetsAt ) } . You can still use a-la-carte credits.`
221- : `Block exhausted. New block in ${ rateLimit . blockResetsAt ? formatRelativeTime ( rateLimit . blockResetsAt ) : 'soon' } . You can still use a-la-carte credits.` }
181+ ? `Weekly limit reached. Resets in ${ formatHours ( rateLimit . weeklyResetsAt ) } . You can still use a-la-carte credits.`
182+ : `Session exhausted. New session in ${ rateLimit . blockResetsAt ? formatHours ( rateLimit . blockResetsAt ) : 'soon' } . You can still use a-la-carte credits.` }
222183 </ p >
223184 </ div >
224185 ) }
225186 </ >
226187 ) }
227188
228- { /* Billing info & manage */ }
229- < div className = "flex items-center justify-between border-t pt-4" >
230- < p className = "text-sm text-muted-foreground" >
231- { isCanceling
232- ? `Cancels ${ subscription ? formatDate ( subscription . billingPeriodEnd ) : '' } `
233- : `Renews ${ subscription ? formatDate ( subscription . billingPeriodEnd ) : '' } ` }
234- </ p >
235- < Button
236- variant = "ghost"
237- size = "sm"
238- className = "text-muted-foreground"
239- asChild
240- >
241- < a href = { billingPortalUrl } target = "_blank" rel = "noopener noreferrer" >
242- < ExternalLink className = "mr-1.5 h-3.5 w-3.5" />
243- Manage Subscription
244- </ a >
245- </ Button >
246- </ div >
189+
247190 </ CardContent >
248191 </ Card >
249192 )
@@ -255,7 +198,7 @@ function SubscriptionCta() {
255198 < CardContent className = "flex flex-col gap-4 py-5 sm:flex-row sm:items-center sm:justify-between" >
256199 < div className = "flex items-start gap-3" >
257200 < div className = "mt-0.5 rounded-lg bg-indigo-100 p-2 dark:bg-indigo-900/30" >
258- < Zap className = "h-5 w-5 text-indigo-600 dark:text-indigo-400" / >
201+ < span className = "text-xl" > 💪 </ span >
259202 </ div >
260203 < div >
261204 < h3 className = "font-semibold" >
0 commit comments