Skip to content

Commit 066fed1

Browse files
committed
fix: better billing page
1 parent 4de6a24 commit 066fed1

File tree

2 files changed

+386
-239
lines changed

2 files changed

+386
-239
lines changed

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

Lines changed: 108 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -4,27 +4,23 @@ import {
44
ArrowSquareOutIcon,
55
CalendarIcon,
66
CheckIcon,
7-
ClockIcon,
87
CreditCardIcon,
98
FileTextIcon,
10-
XIcon,
119
} from '@phosphor-icons/react';
1210
import dayjs from 'dayjs';
11+
import { memo } from 'react';
1312
import { useBilling } from '@/app/(main)/billing/hooks/use-billing';
1413
import { Badge } from '@/components/ui/badge';
1514
import { Button } from '@/components/ui/button';
16-
import {
17-
Card,
18-
CardContent,
19-
CardDescription,
20-
CardHeader,
21-
CardTitle,
22-
} from '@/components/ui/card';
23-
import { Separator } from '@/components/ui/separator';
15+
import { Card, CardContent } from '@/components/ui/card';
2416
import { Skeleton } from '@/components/ui/skeleton';
2517
import type { Customer, Invoice } from '../data/billing-data';
2618

27-
function InvoiceCard({ invoice }: { invoice: Invoice }) {
19+
const InvoiceCard = memo(function InvoiceCardComponent({
20+
invoice,
21+
}: {
22+
invoice: Invoice;
23+
}) {
2824
const getStatusBadge = () => {
2925
switch (invoice.status) {
3026
case 'paid':
@@ -84,8 +80,12 @@ function InvoiceCard({ invoice }: { invoice: Invoice }) {
8480
<CardContent className="p-4">
8581
<div className="flex items-center justify-between">
8682
<div className="flex min-w-0 flex-1 items-center gap-3">
87-
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded border">
88-
<FileTextIcon className="text-muted-foreground" size={16} />
83+
<div className="flex h-8 w-8 flex-shrink-0 items-center justify-center rounded border bg-muted">
84+
<FileTextIcon
85+
className="not-dark:text-primary text-muted-foreground"
86+
size={16}
87+
weight="duotone"
88+
/>
8989
</div>
9090
<div className="min-w-0 flex-1">
9191
<div className="mb-1 flex items-center gap-2">
@@ -110,45 +110,55 @@ function InvoiceCard({ invoice }: { invoice: Invoice }) {
110110

111111
{invoice.hosted_invoice_url && (
112112
<Button
113+
aria-label="View invoice details"
113114
className="h-8 cursor-pointer px-2"
114115
onClick={() => {
115116
if (invoice.hosted_invoice_url) {
116117
window.open(invoice.hosted_invoice_url, '_blank');
117118
}
118119
}}
119120
size="sm"
121+
type="button"
120122
variant="ghost"
121123
>
122-
<ArrowSquareOutIcon size={14} />
124+
<ArrowSquareOutIcon
125+
className="not-dark:text-primary"
126+
size={14}
127+
weight="duotone"
128+
/>
123129
</Button>
124130
)}
125131
</div>
126132
</div>
127133
</CardContent>
128134
</Card>
129135
);
130-
}
136+
});
131137

132-
function SubscriptionHistoryCard({ customerData }: { customerData: any }) {
133-
if (!customerData?.products?.length) return null;
138+
const SubscriptionHistoryCard = memo(function SubscriptionHistoryCardComponent({
139+
customerData,
140+
}: {
141+
customerData: Customer;
142+
}) {
143+
if (!customerData?.products?.length) {
144+
return null;
145+
}
134146

135147
return (
136148
<Card>
137-
<CardHeader className="pb-3">
138-
<CardTitle className="flex items-center gap-2 text-base">
139-
<CalendarIcon size={16} />
140-
Subscription History
141-
</CardTitle>
142-
</CardHeader>
143-
<CardContent className="pt-0">
144-
<div className="space-y-3">
145-
{customerData.products.map((product: any) => (
149+
<CardContent className="p-4">
150+
<div className="space-y-2">
151+
{customerData.products.map((product) => (
146152
<div
147153
className="flex items-start gap-2 rounded border p-2 text-sm"
148154
key={product.id}
149155
>
150-
<div className="mt-0.5 flex h-6 w-6 flex-shrink-0 items-center justify-center rounded-full bg-primary/10">
151-
<CheckIcon className="text-primary" size={10} />
156+
<div className="mt-0.5 flex h-5 w-5 flex-shrink-0 items-center justify-center rounded-full bg-muted">
157+
<CheckIcon
158+
className="not-dark:text-primary text-primary"
159+
size={10}
160+
weight="duotone"
161+
/>
152162
</div>
153163
<div className="min-w-0 flex-1">
154164
<div className="mb-1 flex items-center justify-between">
@@ -182,15 +192,15 @@ function SubscriptionHistoryCard({ customerData }: { customerData: any }) {
182192
</CardContent>
183193
</Card>
184194
);
185-
}
195+
});
186196

187197
interface HistoryTabProps {
188198
invoices: Invoice[];
189199
customerData: Customer | null;
190200
isLoading: boolean;
191201
}
192202

193-
export function HistoryTab({
203+
export const HistoryTab = memo(function HistoryTabComponent({
194204
invoices,
195205
customerData,
196206
isLoading,
@@ -206,7 +216,7 @@ export function HistoryTab({
206216
</div>
207217
<div className="grid gap-6">
208218
{Array.from({ length: 3 }).map((_, i) => (
209-
<Skeleton className="h-48 w-full" key={i} />
219+
<Skeleton className="h-48 w-full" key={`skeleton-${i + 1}`} />
210220
))}
211221
</div>
212222
</div>
@@ -215,62 +225,90 @@ export function HistoryTab({
215225

216226
return (
217227
<div className="space-y-6">
228+
{/* Header */}
218229
<div className="flex items-center justify-between">
219230
<div>
220-
<h2 className="font-bold text-xl">Billing History</h2>
221-
<p className="text-muted-foreground text-sm">
222-
View your invoices, payments, and subscription changes
231+
<h1 className="font-bold text-2xl tracking-tight">Billing History</h1>
232+
<p className="mt-1 text-muted-foreground">
233+
View your invoices and subscription changes
223234
</p>
224235
</div>
225-
226236
<Button
227-
className="cursor-pointer"
237+
aria-label="Manage billing settings"
228238
onClick={onManageBilling}
229239
size="sm"
240+
type="button"
230241
variant="outline"
231242
>
232-
<CreditCardIcon className="mr-2" size={14} />
243+
<CreditCardIcon
244+
className="mr-2 not-dark:text-primary"
245+
size={16}
246+
weight="duotone"
247+
/>
233248
Manage Billing
234-
<ArrowSquareOutIcon className="ml-1" size={12} />
249+
<ArrowSquareOutIcon
250+
className="ml-2 not-dark:text-primary"
251+
size={12}
252+
weight="duotone"
253+
/>
235254
</Button>
236255
</div>
237256

238-
<div className="grid gap-6 lg:grid-cols-4">
239-
<div className="space-y-4 lg:col-span-3">
240-
<div>
241-
<h3 className="mb-3 font-semibold text-lg">Recent Invoices</h3>
242-
243-
{invoices.length ? (
244-
<div className="space-y-2">
245-
{invoices
246-
.sort((a: Invoice, b: Invoice) => b.created_at - a.created_at)
247-
.map((invoice: Invoice) => (
248-
<InvoiceCard invoice={invoice} key={invoice.stripe_id} />
249-
))}
250-
</div>
251-
) : (
252-
<Card>
253-
<CardContent className="flex flex-col items-center justify-center py-12">
254-
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded border">
255-
<FileTextIcon className="text-muted-foreground" size={24} />
256-
</div>
257-
<h4 className="mb-2 font-semibold text-lg">
258-
No Invoices Yet
259-
</h4>
260-
<p className="max-w-sm text-center text-muted-foreground text-sm">
261-
Your invoices will appear here once you start using paid
262-
features.
263-
</p>
264-
</CardContent>
265-
</Card>
266-
)}
267-
</div>
257+
{/* Content Grid */}
258+
<div className="grid gap-6 lg:grid-cols-3">
259+
{/* Invoices */}
260+
<div className="lg:col-span-2">
261+
{invoices.length ? (
262+
<div className="space-y-3">
263+
{invoices
264+
.sort((a: Invoice, b: Invoice) => b.created_at - a.created_at)
265+
.map((invoice: Invoice) => (
266+
<InvoiceCard invoice={invoice} key={invoice.stripe_id} />
267+
))}
268+
</div>
269+
) : (
270+
<Card>
271+
<CardContent className="flex flex-col items-center justify-center py-12">
272+
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded border bg-muted">
273+
<FileTextIcon
274+
className="not-dark:text-primary text-muted-foreground"
275+
size={24}
276+
weight="duotone"
277+
/>
278+
</div>
279+
<h3 className="mb-2 font-semibold text-lg">No Invoices Yet</h3>
280+
<p className="text-center text-muted-foreground text-sm">
281+
Your invoices will appear here once you start using paid
282+
features.
283+
</p>
284+
</CardContent>
285+
</Card>
286+
)}
268287
</div>
269288

289+
{/* Subscription History */}
270290
<div className="lg:col-span-1">
271-
<SubscriptionHistoryCard customerData={customerData} />
291+
{customerData ? (
292+
<SubscriptionHistoryCard customerData={customerData} />
293+
) : (
294+
<Card>
295+
<CardContent className="flex flex-col items-center justify-center py-12">
296+
<div className="mb-4 flex h-12 w-12 items-center justify-center rounded border bg-muted">
297+
<CalendarIcon
298+
className="not-dark:text-primary text-muted-foreground"
299+
size={24}
300+
weight="duotone"
301+
/>
302+
</div>
303+
<h3 className="mb-2 font-semibold text-lg">No History</h3>
304+
<p className="text-center text-muted-foreground text-sm">
305+
Your subscription history will appear here.
306+
</p>
307+
</CardContent>
308+
</Card>
309+
)}
272310
</div>
273311
</div>
274312
</div>
275313
);
276-
}
314+
});

0 commit comments

Comments
 (0)