From f278aada7ae2d57fdb5d32bf83c88f9be8cf17cb Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sat, 14 Mar 2026 21:35:26 +0100 Subject: [PATCH] fix: use flag icons, show per-datacenter dots on map, format tooltip as bytes --- app/sites/[id]/cdn/page.tsx | 42 ++++++++++++++---------------- components/dashboard/DottedMap.tsx | 6 +++-- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/app/sites/[id]/cdn/page.tsx b/app/sites/[id]/cdn/page.tsx index 3ace75a..267b6c7 100644 --- a/app/sites/[id]/cdn/page.tsx +++ b/app/sites/[id]/cdn/page.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from 'react' import dynamic from 'next/dynamic' import { useParams } from 'next/navigation' import Link from 'next/link' +import * as Flags from 'country-flag-icons/react/3x2' const DottedMap = dynamic(() => import('@/components/dashboard/DottedMap'), { ssr: false }) import { getDateRange, formatDate, Select } from '@ciphera-net/ui' @@ -56,29 +57,24 @@ function extractCity(datacenter: string): string { return afterColon.split(',')[0]?.trim() || datacenter } -/** Convert ISO country code to flag emoji */ -function countryFlag(code: string): string { - try { - return code - .toUpperCase() - .split('') - .map(c => String.fromCodePoint(0x1F1E6 + c.charCodeAt(0) - 65)) - .join('') - } catch { - return '' - } +/** Get flag icon component for a country code */ +function getFlagIcon(code: string) { + if (!code) return null + const FlagComponent = (Flags as Record>)[code] + return FlagComponent ? : null } -/** Aggregate bandwidth by ISO country code for the map */ -function aggregateByCountry(data: Array<{ country_code: string; bandwidth: number }>): Array<{ country: string; pageviews: number }> { - const byCountry = new Map() - for (const row of data) { - const cc = extractCountryCode(row.country_code) - if (cc) { - byCountry.set(cc, (byCountry.get(cc) || 0) + row.bandwidth) - } - } - return Array.from(byCountry, ([country, pageviews]) => ({ country, pageviews })) +/** + * Map each datacenter entry to its country's centroid for the dotted map. + * Each datacenter gets its own dot (sized by bandwidth) at the country's position. + */ +function mapToCountryCentroids(data: Array<{ country_code: string; bandwidth: number }>): Array<{ country: string; pageviews: number }> { + return data + .map((row) => ({ + country: extractCountryCode(row.country_code), + pageviews: row.bandwidth, + })) + .filter((d) => d.country !== '') } function formatBytes(bytes: number): string { @@ -472,7 +468,7 @@ export default function CDNPage() { {countries.length > 0 ? ( <>
- +
{countries.map((row) => { @@ -482,7 +478,7 @@ export default function CDNPage() { return (
- {cc && {countryFlag(cc)}} + {cc && getFlagIcon(cc)}
{city}
diff --git a/components/dashboard/DottedMap.tsx b/components/dashboard/DottedMap.tsx index 8d6ecf6..2c7055b 100644 --- a/components/dashboard/DottedMap.tsx +++ b/components/dashboard/DottedMap.tsx @@ -57,6 +57,8 @@ const BASE_DOTS_PATH = (() => { interface DottedMapProps { data: Array<{ country: string; pageviews: number }> className?: string + /** Custom formatter for tooltip values. Defaults to formatNumber. */ + formatValue?: (value: number) => string } function getCountryName(code: string): string { @@ -68,7 +70,7 @@ function getCountryName(code: string): string { } } -export default function DottedMap({ data, className }: DottedMapProps) { +export default function DottedMap({ data, className, formatValue = formatNumber }: DottedMapProps) { const [tooltip, setTooltip] = useState<{ x: number; y: number; country: string; pageviews: number } | null>(null) const markerData = useMemo(() => { @@ -152,7 +154,7 @@ export default function DottedMap({ data, className }: DottedMapProps) { style={{ left: tooltip.x, top: tooltip.y }} > {getCountryName(tooltip.country)} - {formatNumber(tooltip.pageviews)} + {formatValue(tooltip.pageviews)}
)}