From c9759e094b974933324b20f0255903724db09ddd Mon Sep 17 00:00:00 2001 From: Max Ghenis Date: Fri, 8 May 2026 20:21:54 -0500 Subject: [PATCH] Default home view to Global, auto-switch to visitor's country on mount - SSR renders the Global leaderboard so the cached static HTML is the same for every visitor (no per-request rendering, keeps the page static-prerendered). - After hydration, App.tsx checks the visitor's IANA timezone and navigator.languages and switches selectedView to "us" or "uk" when it can confidently identify the country. Anything that doesn't match (Europe outside the UK, Asia, Africa, Latin America, etc.) stays on Global. - The auto-switch only fires once per session and respects subsequent manual selections via the country pill. --- app/src/App.tsx | 56 ++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 53 insertions(+), 3 deletions(-) diff --git a/app/src/App.tsx b/app/src/App.tsx index b006413..ad5662c 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -38,12 +38,61 @@ function getAvailableViews(dashboard: DashboardBundle): ViewKey[] { return countryViews; } +/** Map IANA timezone or BCP-47 language to a benchmark country, when we can tell. */ +function detectVisitorCountry( + availableViews: readonly ViewKey[], +): CountryCode | null { + if (typeof window === "undefined" || typeof navigator === "undefined") { + return null; + } + let timezone = ""; + try { + timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ?? ""; + } catch { + timezone = ""; + } + const langs = (navigator.languages ?? [navigator.language ?? ""]) + .map((value) => value.toLowerCase()); + const matchesUS = + timezone.startsWith("America/") || + timezone === "Pacific/Honolulu" || + timezone === "Pacific/Pago_Pago" || + langs.some((lang) => lang === "en-us" || lang.endsWith("-us")); + const matchesUK = + timezone === "Europe/London" || + timezone === "Europe/Belfast" || + timezone === "Europe/Guernsey" || + timezone === "Europe/Isle_of_Man" || + timezone === "Europe/Jersey" || + langs.some((lang) => + ["en-gb", "cy-gb", "gd-gb", "en-uk"].includes(lang), + ); + if (matchesUS && availableViews.includes("us")) return "us"; + if (matchesUK && availableViews.includes("uk")) return "uk"; + return null; +} + export default function App() { const availableViews = useMemo(() => getAvailableViews(dashboard), []); - const defaultView = availableViews.includes("uk") - ? "uk" + // Default to the global leaderboard. After mount we try to switch to the + // visitor's country (US or UK) when we can detect it from timezone or + // navigator.language; if neither matches we stay on Global. + const initialView: ViewKey = availableViews.includes("global") + ? "global" : (availableViews[0] ?? "global"); - const [selectedView, setSelectedView] = useState(defaultView); + const [selectedView, setSelectedView] = useState(initialView); + const [hasUserPickedView, setHasUserPickedView] = useState(false); + + useEffect(() => { + if (hasUserPickedView) return; + const detected = detectVisitorCountry(availableViews); + if (detected && detected !== selectedView) { + setSelectedView(detected); + } + // We only want this auto-pick to run once per session; further changes + // come from the user clicking the country selector. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); const [activeNav, setActiveNav] = useState("models"); const observerRef = useRef(null); @@ -54,6 +103,7 @@ export default function App() { const navItems = isGlobal ? GLOBAL_NAV_ITEMS : COUNTRY_NAV_ITEMS; const handleSelectView = (view: ViewKey) => { setSelectedView(view); + setHasUserPickedView(true); setActiveNav("models"); };