Skip to content

Commit 445d622

Browse files
committed
fix: top countries UI
1 parent e233d72 commit 445d622

File tree

1 file changed

+109
-64
lines changed
  • apps/dashboard/app/(main)/websites/[id]/map

1 file changed

+109
-64
lines changed

apps/dashboard/app/(main)/websites/[id]/map/page.tsx

Lines changed: 109 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,74 @@ const MapComponent = dynamic(
3333
}
3434
);
3535

36+
interface CountryData {
37+
country: string;
38+
country_code?: string;
39+
visitors: number;
40+
pageviews: number;
41+
}
42+
43+
interface CountryRowProps {
44+
country: CountryData;
45+
totalVisitors: number;
46+
onCountrySelect: (countryCode: string) => void;
47+
}
48+
49+
function CountryRow({
50+
country,
51+
totalVisitors,
52+
onCountrySelect,
53+
}: CountryRowProps) {
54+
const percentage =
55+
totalVisitors > 0 ? (country.visitors / totalVisitors) * 100 : 0;
56+
const getColor = (pct: number) =>
57+
pct >= 50
58+
? ['rgba(34, 197, 94, 0.08)', 'rgba(34, 197, 94, 0.8)']
59+
: pct >= 25
60+
? ['rgba(59, 130, 246, 0.08)', 'rgba(59, 130, 246, 0.8)']
61+
: pct >= 10
62+
? ['rgba(245, 158, 11, 0.08)', 'rgba(245, 158, 11, 0.8)']
63+
: ['rgba(107, 114, 128, 0.06)', 'rgba(107, 114, 128, 0.7)'];
64+
const [bgColor, accentColor] = getColor(percentage);
65+
66+
return (
67+
<button
68+
className="flex w-full cursor-pointer items-center gap-2.5 px-1.5 py-1.5 text-left transition-all hover:opacity-80"
69+
onClick={() =>
70+
onCountrySelect(
71+
country.country_code?.toUpperCase() || country.country.toUpperCase()
72+
)
73+
}
74+
style={{
75+
background: percentage > 0 ? bgColor : undefined,
76+
boxShadow:
77+
percentage > 0 ? `inset 2px 0 0 0 ${accentColor}` : undefined,
78+
}}
79+
type="button"
80+
>
81+
<div className="relative h-4 w-5 flex-shrink-0 overflow-hidden border border-border/20 shadow-sm">
82+
<Image
83+
alt={`${country.country} flag`}
84+
className="object-cover"
85+
fill
86+
sizes="32p"
87+
src={`https://purecatamphetamine.github.io/country-flag-icons/3x2/${country.country_code?.toUpperCase() || country.country.toUpperCase()}.svg`}
88+
/>
89+
</div>
90+
<div className="min-w-0 flex-1">
91+
<div className="flex items-center justify-between">
92+
<div className="truncate font-medium text-xs">{country.country}</div>
93+
<span className="ml-1 font-semibold text-primary text-xs">
94+
{country.visitors > 999
95+
? `${(country.visitors / 1000).toFixed(0)}k`
96+
: country.visitors.toString()}
97+
</span>
98+
</div>
99+
</div>
100+
</button>
101+
);
102+
}
103+
36104
function WebsiteMapPage() {
37105
const { id } = useParams<{ id: string }>();
38106
const [mode] = useState<'total' | 'perCapita'>('total');
@@ -101,14 +169,14 @@ function WebsiteMapPage() {
101169
}
102170

103171
return (
104-
<div
105-
className="h-screen overflow-hidden"
106-
style={{
172+
<div
173+
className="h-screen overflow-hidden"
174+
style={{
107175
width: 'calc(100% + 3rem)',
108176
marginTop: '-1.5rem',
109177
marginLeft: '-1.5rem',
110178
marginRight: '-1.5rem',
111-
marginBottom: '-1.5rem'
179+
marginBottom: '-1.5rem',
112180
}}
113181
>
114182
<div className="relative h-full w-full">
@@ -124,85 +192,62 @@ function WebsiteMapPage() {
124192

125193
{/* Top 5 Countries Overlay */}
126194
<div className="absolute top-2 right-2 z-20">
127-
<Card className="border-sidebar-border bg-background/90 backdrop-blur-md shadow-xl w-60">
128-
<CardHeader className="pb-2 pt-3 px-3">
129-
<CardTitle className="flex items-center gap-1.5 text-xs font-medium">
130-
<GlobeIcon className="h-3 w-3 text-primary" weight="duotone" />
131-
Top 5 Countries
195+
<Card className="w-56 max-w-[90vw] gap-0 border-sidebar-border bg-background/95 py-0 shadow-xl backdrop-blur-md sm:w-64">
196+
<CardHeader className="px-3 pt-2.5 pb-2">
197+
<CardTitle className="flex items-center gap-1.5 font-semibold text-xs">
198+
<GlobeIcon
199+
className="h-3.5 w-3.5 text-primary"
200+
weight="duotone"
201+
/>
202+
Top Countries
132203
</CardTitle>
133204
</CardHeader>
134-
<CardContent className="p-0 pb-1">
205+
<CardContent className="p-0">
135206
{isLoading ? (
136207
<div className="space-y-1 px-3 pb-2">
137208
{new Array(5).fill(0).map((_, i) => (
138209
<div
139-
className="flex items-center justify-between py-1"
210+
className="flex items-center gap-2.5 py-1.5"
140211
key={`country-skeleton-${i + 1}`}
141212
>
142-
<div className="flex items-center gap-1.5">
143-
<Skeleton className="h-2.5 w-4 rounded" />
144-
<Skeleton className="h-2.5 w-12" />
145-
</div>
146-
<Skeleton className="h-2.5 w-6" />
213+
<Skeleton className="h-2.5 w-4" />
214+
<Skeleton className="h-2.5 flex-1" />
215+
<Skeleton className="h-2.5 w-8" />
147216
</div>
148217
))}
149218
</div>
150219
) : topCountries.length > 0 ? (
151-
<div className="px-3 pb-2">
152-
{topCountries.map((country) => {
153-
const percentage =
154-
totalVisitors > 0
155-
? (country.visitors / totalVisitors) * 100
156-
: 0;
157-
return (
158-
<button
159-
className="flex w-full cursor-pointer items-center justify-between py-1.5 text-left transition-colors hover:bg-primary/5 rounded-sm"
160-
key={country.country}
161-
onClick={() =>
162-
handleCountrySelect(
163-
country.country_code?.toUpperCase() ||
164-
country.country.toUpperCase()
165-
)
166-
}
167-
type="button"
168-
>
169-
<div className="flex items-center gap-1.5 min-w-0 flex-1">
170-
<div className="relative h-2.5 w-4 flex-shrink-0 overflow-hidden rounded shadow-sm">
171-
<Image
172-
alt={`${country.country} flag`}
173-
className="object-cover"
174-
fill
175-
sizes="16px"
176-
src={`https://purecatamphetamine.github.io/country-flag-icons/3x2/${country.country_code?.toUpperCase() || country.country.toUpperCase()}.svg`}
177-
/>
178-
</div>
179-
<div className="min-w-0 flex-1">
180-
<div className="truncate font-medium text-xs">
181-
{country.country}
182-
</div>
183-
</div>
184-
</div>
185-
<div className="flex items-center gap-1.5 text-right">
186-
<div className="text-muted-foreground text-xs">
187-
{percentage.toFixed(0)}%
188-
</div>
189-
<div className="font-semibold text-xs min-w-0 text-primary">
190-
{country.visitors > 999 ? `${(country.visitors / 1000).toFixed(0)}k` : country.visitors.toString()}
191-
</div>
192-
</div>
193-
</button>
194-
);
195-
})}
220+
<div className="space-y-0.5">
221+
{topCountries.map((country) => (
222+
<CountryRow
223+
country={country}
224+
key={country.country}
225+
onCountrySelect={handleCountrySelect}
226+
totalVisitors={totalVisitors}
227+
/>
228+
))}
229+
230+
{/* Total visitors summary */}
231+
<div className="border-border/50 border-t px-2 pt-1.5 pb-1.5">
232+
<div className="flex items-center justify-between text-xs">
233+
<span className="text-muted-foreground">Total</span>
234+
<span className="font-semibold text-primary">
235+
{totalVisitors > 999
236+
? `${(totalVisitors / 1000).toFixed(0)}k`
237+
: totalVisitors.toLocaleString()}
238+
</span>
239+
</div>
240+
</div>
196241
</div>
197242
) : (
198-
<div className="flex flex-col items-center justify-center py-6 text-center px-3">
199-
<div className="flex h-6 w-6 items-center justify-center rounded bg-muted/20 mb-1">
243+
<div className="flex flex-col items-center justify-center px-3 py-6 text-center">
244+
<div className="mb-1.5 flex h-6 w-6 items-center justify-center bg-muted/20">
200245
<GlobeIcon
201246
className="h-3 w-3 text-muted-foreground/50"
202247
weight="duotone"
203248
/>
204249
</div>
205-
<p className="text-muted-foreground text-xs">
250+
<p className="font-medium text-muted-foreground text-xs">
206251
No data
207252
</p>
208253
</div>

0 commit comments

Comments
 (0)