Skip to content

Commit 6c57887

Browse files
committed
fix: billing page
1 parent e12c9d0 commit 6c57887

File tree

2 files changed

+115
-138
lines changed

2 files changed

+115
-138
lines changed

apps/dashboard/app/(main)/billing/components/overview-tab.tsx

Lines changed: 81 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
WarningIcon,
1717
} from '@phosphor-icons/react';
1818
import type { Product } from 'autumn-js';
19+
import dayjs from 'dayjs';
1920
import React, { memo, useMemo } from 'react';
2021
import { useBilling } from '@/app/(main)/billing/hooks/use-billing';
2122
import { Badge } from '@/components/ui/badge';
@@ -38,10 +39,14 @@ const UsageCard = memo(function UsageCardComponent({
3839
feature,
3940
onUpgrade,
4041
}: UsageCardProps) {
41-
const percentage =
42-
feature.limit > 0 ? Math.min((feature.used / feature.limit) * 100, 100) : 0;
43-
const isNearLimit = !feature.unlimited && percentage > 80;
44-
const isOverLimit = !feature.unlimited && percentage >= 100;
42+
const percentage = feature.unlimited
43+
? 0
44+
: feature.limit > 0
45+
? Math.min((feature.used / feature.limit) * 100, 100)
46+
: 0;
47+
48+
const isNearLimit = !feature.unlimited && (percentage > 80 || feature.balance < feature.limit * 0.2);
49+
const isOverLimit = !feature.unlimited && (percentage >= 100 || feature.balance <= 0);
4550

4651
const getIcon = () => {
4752
if (feature.name.toLowerCase().includes('event')) {
@@ -60,19 +65,17 @@ const UsageCard = memo(function UsageCardComponent({
6065
};
6166

6267
const getIntervalText = () => {
63-
if (!feature.interval) {
64-
return `Resets ${feature.nextReset}`;
65-
}
66-
switch (feature.interval) {
67-
case 'day':
68-
return 'Resets daily';
69-
case 'month':
70-
return 'Resets monthly';
71-
case 'year':
72-
return 'Resets yearly';
73-
default:
74-
return `Resets ${feature.nextReset}`;
68+
const intervals: Record<string, string> = {
69+
day: 'Resets daily',
70+
month: 'Resets monthly',
71+
year: 'Resets yearly',
72+
};
73+
74+
if (feature.interval && intervals[feature.interval]) {
75+
return intervals[feature.interval];
7576
}
77+
78+
return feature.nextReset ? `Resets ${feature.nextReset}` : 'No reset scheduled';
7679
};
7780

7881
const getUsageTextColor = () => {
@@ -189,61 +192,45 @@ const PlanStatusCard = memo(function PlanStatusCardComponent({
189192
if (isCanceled) {
190193
return (
191194
<Badge variant="destructive">
192-
<WarningIcon
193-
className="mr-1 font-bold not-dark:text-primary"
194-
size={12}
195-
weight="duotone"
196-
/>
195+
<WarningIcon className="mr-1" size={12} weight="duotone" />
197196
Cancelled
198197
</Badge>
199198
);
200199
}
201200
if (isScheduled) {
202201
return (
203202
<Badge variant="secondary">
204-
<CalendarIcon
205-
className="mr-1 font-bold not-dark:text-primary"
206-
size={12}
207-
weight="duotone"
208-
/>
203+
<CalendarIcon className="mr-1" size={12} weight="duotone" />
209204
Scheduled
210205
</Badge>
211206
);
212207
}
213208
return (
214209
<Badge>
215-
<CheckIcon
216-
className="mr-1 text-white dark:text-black"
217-
size={12}
218-
weight="bold"
219-
/>
210+
<CheckIcon className="mr-1" size={12} weight="bold" />
220211
Active
221212
</Badge>
222213
);
223214
};
224215

225216
const getFeatureText = (item: Product['items'][0]) => {
226-
let mainText = item.display?.primary_text || '';
217+
let text = item.display?.primary_text ?? '';
218+
const intervals: Record<string, string> = {
219+
day: ' per day',
220+
month: ' per month',
221+
year: ' per year',
222+
};
223+
227224
if (
228225
item.interval &&
229-
!mainText.toLowerCase().includes('per ') &&
230-
!mainText.toLowerCase().includes('/')
226+
intervals[item.interval] &&
227+
!text.toLowerCase().includes('per ') &&
228+
!text.toLowerCase().includes('/')
231229
) {
232-
switch (item.interval) {
233-
case 'day':
234-
mainText += ' per day';
235-
break;
236-
case 'month':
237-
mainText += ' per month';
238-
break;
239-
case 'year':
240-
mainText += ' per year';
241-
break;
242-
default:
243-
break;
244-
}
230+
text += intervals[item.interval];
245231
}
246-
return mainText;
232+
233+
return text;
247234
};
248235

249236
return (
@@ -279,16 +266,16 @@ const PlanStatusCard = memo(function PlanStatusCardComponent({
279266
</div>
280267
</div>
281268

282-
<div className="flex-shrink-0 text-right">
283-
<div className="font-bold text-2xl sm:text-3xl">
284-
{isFree
285-
? 'Free'
286-
: plan?.items[0]?.display?.primary_text || 'Free'}
287-
</div>
288-
<div className="text-muted-foreground text-sm">
289-
{!isFree && plan?.items[0]?.display?.secondary_text}
269+
<div className="flex-shrink-0 text-right">
270+
<div className="font-bold text-2xl sm:text-3xl">
271+
{isFree
272+
? 'Free'
273+
: plan?.items[0]?.display?.primary_text || 'Free'}
274+
</div>
275+
<div className="text-muted-foreground text-sm">
276+
{!isFree && plan?.items[0]?.display?.secondary_text}
277+
</div>
290278
</div>
291-
</div>
292279
</div>
293280
</CardHeader>
294281

@@ -391,7 +378,7 @@ interface OverviewTabProps {
391378
export const OverviewTab = memo(function OverviewTabComponent({
392379
onNavigateToPlans,
393380
}: OverviewTabProps) {
394-
const { products, usage, customer, isLoading, refetch } = useBillingData();
381+
const { products, usage, customer, isLoading, error, refetch } = useBillingData();
395382
const {
396383
onCancelClick,
397384
onCancelConfirm,
@@ -405,44 +392,36 @@ export const OverviewTab = memo(function OverviewTabComponent({
405392
} = useBilling(refetch);
406393

407394
const { currentPlan, usageStats, statusDetails } = useMemo(() => {
408-
const activePlan = products?.find(
409-
(p: Product) =>
410-
p.scenario !== 'upgrade' &&
411-
p.scenario !== 'downgrade' &&
412-
p.scenario !== 'new'
413-
);
414-
const featureUsage = usage?.features || [];
395+
const activeCustomerProduct = customer?.products?.find((p) => {
396+
if (p.canceled_at && p.current_period_end) {
397+
return dayjs(p.current_period_end).isAfter(dayjs());
398+
}
399+
return !p.canceled_at || p.status === 'scheduled';
400+
});
415401

416-
const customerProduct = activePlan
417-
? customer?.products?.find((p) => p.id === activePlan.id)
418-
: undefined;
402+
const activePlan = activeCustomerProduct
403+
? products?.find((p: Product) => p.id === activeCustomerProduct.id)
404+
: products?.find((p: Product) => !p.scenario || (p.scenario !== 'upgrade' && p.scenario !== 'downgrade'));
419405

420-
const planStatusDetails = customerProduct
406+
const planStatusDetails = activeCustomerProduct
421407
? getSubscriptionStatusDetails(
422-
customerProduct as unknown as Parameters<
408+
activeCustomerProduct as unknown as Parameters<
423409
typeof getSubscriptionStatusDetails
424410
>[0]
425411
)
426412
: '';
427413

428414
return {
429415
currentPlan: activePlan,
430-
usageStats: featureUsage,
416+
usageStats: usage?.features ?? [],
431417
statusDetails: planStatusDetails,
432418
};
433-
}, [
434-
products,
435-
usage?.features,
436-
customer?.products,
437-
getSubscriptionStatusDetails,
438-
]);
419+
}, [products, usage?.features, customer?.products, getSubscriptionStatusDetails]);
439420

440421
if (isLoading) {
441422
return (
442423
<div className="space-y-8">
443-
{/* Header Section Skeleton */}
444424
<div className="grid gap-8 lg:grid-cols-3">
445-
{/* Usage Overview Header Skeleton */}
446425
<div className="lg:col-span-2">
447426
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
448427
<div className="space-y-2">
@@ -453,7 +432,6 @@ export const OverviewTab = memo(function OverviewTabComponent({
453432
</div>
454433
</div>
455434

456-
{/* Current Plan Header Skeleton */}
457435
<div className="lg:col-span-1">
458436
<div className="space-y-2">
459437
<Skeleton className="h-8 w-32" />
@@ -462,14 +440,11 @@ export const OverviewTab = memo(function OverviewTabComponent({
462440
</div>
463441
</div>
464442

465-
{/* Main Content Grid Skeleton */}
466443
<div className="grid gap-8 lg:grid-cols-3">
467-
{/* Usage Overview Section Skeleton */}
468444
<div className="space-y-6 lg:col-span-2">
469445
<Skeleton className="h-96 w-full" />
470446
</div>
471447

472-
{/* Current Plan Section Skeleton */}
473448
<div className="space-y-6 lg:col-span-1">
474449
<Skeleton className="h-96 w-full" />
475450
</div>
@@ -478,6 +453,29 @@ export const OverviewTab = memo(function OverviewTabComponent({
478453
);
479454
}
480455

456+
if (error) {
457+
return (
458+
<Card className="h-full">
459+
<CardContent className="flex h-full flex-col items-center justify-center py-16">
460+
<div className="mb-6 flex h-16 w-16 items-center justify-center rounded border bg-destructive/10">
461+
<WarningIcon
462+
className="text-destructive"
463+
size={32}
464+
weight="duotone"
465+
/>
466+
</div>
467+
<h3 className="mb-2 font-semibold text-xl">Error Loading Billing Data</h3>
468+
<p className="mb-4 max-w-sm text-center text-muted-foreground">
469+
{error instanceof Error ? error.message : 'Failed to load customer data. Please try again.'}
470+
</p>
471+
<Button onClick={() => refetch()} size="lg" type="button">
472+
Retry
473+
</Button>
474+
</CardContent>
475+
</Card>
476+
);
477+
}
478+
481479
return (
482480
<>
483481
<NoPaymentMethodDialog
@@ -496,9 +494,7 @@ export const OverviewTab = memo(function OverviewTabComponent({
496494
/>
497495

498496
<div className="space-y-8">
499-
{/* Header Section */}
500497
<div className="grid gap-8 lg:grid-cols-3">
501-
{/* Usage Overview Header */}
502498
<div className="lg:col-span-2">
503499
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
504500
<div>
@@ -516,7 +512,6 @@ export const OverviewTab = memo(function OverviewTabComponent({
516512
</div>
517513
</div>
518514

519-
{/* Current Plan Header */}
520515
<div className="lg:col-span-1">
521516
<div>
522517
<h2 className="font-bold text-2xl tracking-tight">
@@ -529,9 +524,7 @@ export const OverviewTab = memo(function OverviewTabComponent({
529524
</div>
530525
</div>
531526

532-
{/* Main Content Grid */}
533527
<div className="grid gap-8 lg:grid-cols-3">
534-
{/* Usage Overview Section */}
535528
<div className="space-y-6 lg:col-span-2">
536529
{usageStats.length === 0 ? (
537530
<Card className="h-full">
@@ -562,7 +555,6 @@ export const OverviewTab = memo(function OverviewTabComponent({
562555
)}
563556
</div>
564557

565-
{/* Current Plan Section */}
566558
<div className="space-y-6 lg:col-span-1">
567559
<div className="h-full">
568560
<PlanStatusCard

0 commit comments

Comments
 (0)