1- import type { MiniChartDataPoint , Website } from '@databuddy/shared' ;
1+ import type { Website , ProcessedMiniChartData } from '@databuddy/shared' ;
22import {
33 ArrowRightIcon ,
4- GlobeIcon ,
54 MinusIcon ,
65 TrendDownIcon ,
76 TrendUpIcon ,
87} from '@phosphor-icons/react' ;
8+ import dynamic from 'next/dynamic' ;
99import Link from 'next/link' ;
10- import { memo , useMemo } from 'react' ;
11- import {
12- Area ,
13- AreaChart ,
14- ResponsiveContainer ,
15- Tooltip ,
16- XAxis ,
17- YAxis ,
18- } from 'recharts' ;
10+ import { memo , Suspense } from 'react' ;
1911import { FaviconImage } from '@/components/analytics/favicon-image' ;
2012import {
2113 Card ,
@@ -28,7 +20,7 @@ import { Skeleton } from '@/components/ui/skeleton';
2820
2921interface WebsiteCardProps {
3022 website : Website ;
31- chartData ?: MiniChartDataPoint [ ] ;
23+ chartData ?: ProcessedMiniChartData ;
3224 isLoadingChart ?: boolean ;
3325}
3426
@@ -38,97 +30,17 @@ const formatNumber = (num: number) => {
3830 return num . toString ( ) ;
3931} ;
4032
41- const getTrend = ( data : MiniChartDataPoint [ ] ) => {
42- if ( ! data || data . length === 0 ) return null ;
43-
44- const mid = Math . floor ( data . length / 2 ) ;
45- const [ first , second ] = [ data . slice ( 0 , mid ) , data . slice ( mid ) ] ;
46-
47- const avg = ( arr : MiniChartDataPoint [ ] ) =>
48- arr . length > 0 ? arr . reduce ( ( sum , p ) => sum + p . value , 0 ) / arr . length : 0 ;
49- const [ prevAvg , currAvg ] = [ avg ( first ) , avg ( second ) ] ;
50-
51- if ( prevAvg === 0 )
52- return currAvg > 0
53- ? { type : 'up' , value : 100 }
54- : { type : 'neutral' , value : 0 } ;
55-
56- const change = ( ( currAvg - prevAvg ) / prevAvg ) * 100 ;
57- let type : 'up' | 'down' | 'neutral' = 'neutral' ;
58- if ( change > 5 ) type = 'up' ;
59- else if ( change < - 5 ) type = 'down' ;
60- return { type, value : Math . abs ( change ) } ;
61- } ;
62-
63- const Chart = memo (
64- ( { data, id } : { data : MiniChartDataPoint [ ] ; id : string } ) => (
65- < div className = "chart-container" >
66- < ResponsiveContainer height = { 50 } width = "100%" >
67- < AreaChart
68- data = { data }
69- margin = { { top : 5 , right : 0 , left : 0 , bottom : 0 } }
70- >
71- < defs >
72- < linearGradient id = { `gradient-${ id } ` } x1 = "0" x2 = "0" y1 = "0" y2 = "1" >
73- < stop
74- offset = "5%"
75- stopColor = "var(--chart-color)"
76- stopOpacity = { 0.8 }
77- />
78- < stop
79- offset = "95%"
80- stopColor = "var(--chart-color)"
81- stopOpacity = { 0.1 }
82- />
83- </ linearGradient >
84- </ defs >
85- < XAxis dataKey = "date" hide />
86- < YAxis domain = { [ 'dataMin - 5' , 'dataMax + 5' ] } hide />
87- < Tooltip
88- content = { ( { active, payload, label } ) =>
89- active && payload ?. [ 0 ] && typeof payload [ 0 ] . value === 'number' ? (
90- < div className = "rounded-lg border bg-background p-2 text-sm shadow-lg" >
91- < p className = "font-medium" >
92- { new Date ( label ) . toLocaleDateString ( 'en-US' , {
93- month : 'short' ,
94- day : 'numeric' ,
95- } ) }
96- </ p >
97- < p className = "text-primary" >
98- { formatNumber ( payload [ 0 ] . value ) } views
99- </ p >
100- </ div >
101- ) : null
102- }
103- />
104- < Area
105- dataKey = "value"
106- dot = { false }
107- fill = { `url(#gradient-${ id } )` }
108- stroke = "var(--chart-color)"
109- strokeWidth = { 2.5 }
110- type = "monotone"
111- />
112- </ AreaChart >
113- </ ResponsiveContainer >
114- </ div >
115- )
33+ // Lazy load the chart component to improve initial page load
34+ const MiniChart = dynamic (
35+ ( ) => import ( './mini-chart' ) . then ( mod => mod . default ) ,
36+ {
37+ loading : ( ) => < Skeleton className = "h-12 w-full rounded" /> ,
38+ ssr : false ,
39+ }
11640) ;
11741
118- Chart . displayName = 'Chart' ;
119-
12042export const WebsiteCard = memo (
12143 ( { website, chartData, isLoadingChart } : WebsiteCardProps ) => {
122- const data = chartData || [ ] ;
123-
124- const { totalViews, trend } = useMemo (
125- ( ) => ( {
126- totalViews : data . reduce ( ( sum , point ) => sum + point . value , 0 ) ,
127- trend : getTrend ( data ) ,
128- } ) ,
129- [ data ]
130- ) ;
131-
13244 return (
13345 < Link
13446 className = "group block"
@@ -173,15 +85,15 @@ export const WebsiteCard = memo(
17385 < Skeleton className = "h-12 w-full rounded" />
17486 </ div >
17587 ) : chartData ? (
176- data . length > 0 ? (
88+ chartData . data . length > 0 ? (
17789 < div className = "space-y-2" >
17890 < div className = "flex items-center justify-between" >
17991 < span className = "font-medium text-muted-foreground text-xs" >
180- { formatNumber ( totalViews ) } views
92+ { formatNumber ( chartData . totalViews ) } views
18193 </ span >
182- { trend && (
94+ { chartData . trend && (
18395 < div className = "flex items-center gap-1 font-medium text-xs" >
184- { trend . type === 'up' ? (
96+ { chartData . trend . type === 'up' ? (
18597 < >
18698 < TrendUpIcon
18799 aria-hidden = "true"
@@ -193,10 +105,10 @@ export const WebsiteCard = memo(
193105 className = "!text-success"
194106 style = { { color : 'var(--tw-success, #22c55e)' } }
195107 >
196- +{ trend . value . toFixed ( 0 ) } %
108+ +{ chartData . trend . value . toFixed ( 0 ) } %
197109 </ span >
198110 </ >
199- ) : trend . type === 'down' ? (
111+ ) : chartData . trend . type === 'down' ? (
200112 < >
201113 < TrendDownIcon
202114 aria-hidden = "true"
@@ -212,7 +124,7 @@ export const WebsiteCard = memo(
212124 color : 'var(--tw-destructive, #ef4444)' ,
213125 } }
214126 >
215- -{ trend . value . toFixed ( 0 ) } %
127+ -{ chartData . trend . value . toFixed ( 0 ) } %
216128 </ span >
217129 </ >
218130 ) : (
@@ -229,7 +141,9 @@ export const WebsiteCard = memo(
229141 ) }
230142 </ div >
231143 < div className = "transition-colors duration-300 [--chart-color:theme(colors.primary.DEFAULT)] group-hover:[--chart-color:theme(colors.primary.600)]" >
232- < Chart data = { data } id = { website . id } />
144+ < Suspense fallback = { < Skeleton className = "h-12 w-full rounded" /> } >
145+ < MiniChart data = { chartData . data } id = { website . id } />
146+ </ Suspense >
233147 </ div >
234148 </ div >
235149 ) : (
@@ -263,4 +177,4 @@ export function WebsiteCardSkeleton() {
263177 </ CardContent >
264178 </ Card >
265179 ) ;
266- }
180+ }
0 commit comments