Skip to content

Commit 3d5b4d1

Browse files
committed
Makeover for subscription panel
1 parent e29b8cd commit 3d5b4d1

File tree

1 file changed

+55
-112
lines changed

1 file changed

+55
-112
lines changed

web/src/app/profile/components/subscription-section.tsx

Lines changed: 55 additions & 112 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,6 @@ import { SUBSCRIPTION_DISPLAY_NAME } from '@codebuff/common/constants/subscripti
44
import { env } from '@codebuff/common/env'
55
import { useQuery } from '@tanstack/react-query'
66
import {
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

7757
function 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

Comments
 (0)