'use client'; import { useEffect, useState } from 'react'; import { RefreshCw } from 'lucide-react'; const BUILD_ID = process.env.NEXT_PUBLIC_BUILD_ID ?? ''; const RELOADED_FOR = '1driver_reloaded_for'; const CHECK_EVERY_MS = 10 * 60 * 1000; /** The build the server is serving now, or '' when unknown (offline, dev). */ async function liveBuildId(): Promise { try { const res = await fetch(`/version.json?t=${Date.now()}`, { cache: 'no-store' }); if (!res.ok) return ''; const body = (await res.json()) as { id?: unknown }; return typeof body.id === 'string' ? body.id : ''; } catch { return ''; } } /** * Drop the cached pages (not the hashed /_next/static files) so the service * worker fetches the new HTML, then reload. Without this the first reload * after a deploy still shows the old cached page. */ async function loadNewVersion(): Promise { try { for (const name of await caches.keys()) { const cache = await caches.open(name); for (const req of await cache.keys()) { const url = new URL(req.url); if (url.origin === location.origin && !url.pathname.startsWith('/_next/static/')) { await cache.delete(req); } } } const reg = await navigator.serviceWorker?.getRegistration(); await reg?.update(); } catch { // Reload anyway; the worst case is one more stale open. } location.reload(); } // Keeps drivers on the latest build. On launch a new build loads straight // away (once per build, so a stale server can't cause a reload loop). While // the app stays open it only shows an Update banner: a reload then could cut // off a camera photo or an upload in progress. export default function UpdateWatcher() { const [ready, setReady] = useState(false); const [busy, setBusy] = useState(false); useEffect(() => { if (process.env.NODE_ENV !== 'production' || !BUILD_ID) return; let stopped = false; const check = async (onLaunch: boolean) => { const live = await liveBuildId(); if (stopped || !live || live === BUILD_ID) return; let alreadyTried = false; try { alreadyTried = sessionStorage.getItem(RELOADED_FOR) === live; if (onLaunch && !alreadyTried) sessionStorage.setItem(RELOADED_FOR, live); } catch { alreadyTried = true; // no storage: never auto-reload, the banner is enough } if (onLaunch && !alreadyTried) void loadNewVersion(); else setReady(true); }; void check(true); const onVisible = () => { if (document.visibilityState === 'visible') void check(false); }; document.addEventListener('visibilitychange', onVisible); const timer = setInterval(() => void check(false), CHECK_EVERY_MS); return () => { stopped = true; document.removeEventListener('visibilitychange', onVisible); clearInterval(timer); }; }, []); if (!ready) return null; return (

A new version of the app is ready.

); }