diff --git a/pages/map.tsx b/pages/map.tsx new file mode 100644 index 000000000..b749f17d8 --- /dev/null +++ b/pages/map.tsx @@ -0,0 +1,188 @@ +import fs from 'fs' +import path from 'path' +import { useState, useEffect } from 'react' +import { InferGetStaticPropsType } from 'next' +import { PageSEO } from '@/components/SEO' +import Link from '@/components/Link' +import PublicGoogleSheetsParser from 'public-google-sheets-parser' +import { formatNumber } from '@/components/LifetimeStats' + +const OPENSATS_ORANGE = '#f97316' // tailwind orange-500 + +// ISO 3166-1 alpha-2 codes that match the SVG's path ids (id="US", id="DE", ...) +const GRANTEE_COUNTRY_CODES: string[] = [ + 'US', // USA + 'CA', // Canada + 'DE', // Germany + 'GB', // United Kingdom + 'IT', // Italy + 'JP', // Japan + 'NL', // Netherlands + 'CH', // Switzerland + 'CN', // China + 'BR', // Brazil + 'AR', // Argentina + 'IE', // Ireland + 'HK', // Hong Kong + 'GE', // Georgia + 'SE', // Sweden + 'ES', // Spain + 'PT', // Portugal + 'NO', // Norway + 'GR', // Greece + 'AU', // Australia + 'IN', // India + 'SI', // Slovenia + 'KR', // Republic of Korea + 'FI', // Finland + 'CZ', // Czech Republic + 'UG', // Uganda + 'BE', // Belgium + 'FR', // France + 'VN', // Vietnam + 'UA', // Ukraine + 'TR', // Turkey + 'SV', // El Salvador + 'NZ', // New Zealand + 'HU', // Hungary + 'SK', // Slovakia + 'NG', // Nigeria + 'PA', // Panama + 'RO', // Romania + 'GT', // Guatemala + 'ID', // Indonesia + 'AE', // UAE +] + +export const getStaticProps = async () => { + const svgPath = path.join(process.cwd(), 'public', 'maps', 'world.svg') + let svg = fs.readFileSync(svgPath, 'utf8') + + // Strip XML declaration - it doesn't belong in HTML + svg = svg.replace(/<\?xml[\s\S]*?\?>\s*/i, '').trim() + + // Convert title attributes to child elements for native browser tooltips + svg = svg.replace( + /<path([^>]*)\s+title="([^"]*)"([^>]*)\s*\/>/g, + '<path$1$3><title>$2' + ) + + return { props: { svg } } +} + +export default function MapPage({ + svg, +}: InferGetStaticPropsType) { + const [stats, setStats] = useState<{ label: string; value: number }[]>([]) + + useEffect(() => { + const parser = new PublicGoogleSheetsParser( + '1mLEbHcrJibLN2PKxYq1LHJssq0CGuJRRoaZwot-ncZQ' + ) + parser.parse().then((data) => { + setStats(data) + }) + }, []) + + // Generate CSS selector for highlighted countries (DRY: one selector string) + const highlightSelector = GRANTEE_COUNTRY_CODES.length + ? GRANTEE_COUNTRY_CODES.map((c) => `.grant-map svg #${c}`).join(', ') + : '' + + const highlightCss = highlightSelector + ? ` + ${highlightSelector} { + fill: ${OPENSATS_ORANGE} !important; + } + ` + : '' + + // stats[0] = grants given, stats[1] = USD allocated, stats[2] = sats sent + const grantsGiven = stats[0]?.value ? formatNumber(stats[0].value) : '...' + const usdAllocated = stats[1]?.value + ? Math.round(stats[1].value).toLocaleString() + : '...' + const satsSent = stats[2]?.value + ? formatNumber(stats[2].value).replace('B', 'billion') + : '...' + + return ( + <> + + +
+
+

+ OpenSats has allocated{' '} + + ${usdAllocated} USD + {' '} + to free and open-source projects and sent{' '} + + ~{satsSent} sats + {' '} + to{' '} + + {grantsGiven} grantees + {' '} + in{' '} + + 40+ countries. + +

+
+ +
+
+
+
+ + + + ) +} diff --git a/public/maps/world.svg b/public/maps/world.svg new file mode 100644 index 000000000..612386fcf --- /dev/null +++ b/public/maps/world.svg @@ -0,0 +1,1037 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +