@@ -4,27 +4,23 @@ import {
44 ArrowSquareOutIcon ,
55 CalendarIcon ,
66 CheckIcon ,
7- ClockIcon ,
87 CreditCardIcon ,
98 FileTextIcon ,
10- XIcon ,
119} from '@phosphor-icons/react' ;
1210import dayjs from 'dayjs' ;
11+ import { memo } from 'react' ;
1312import { useBilling } from '@/app/(main)/billing/hooks/use-billing' ;
1413import { Badge } from '@/components/ui/badge' ;
1514import { 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' ;
2416import { Skeleton } from '@/components/ui/skeleton' ;
2517import 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
187197interface 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