Skip to content

Commit d5b7818

Browse files
Merge pull request #259 from TheAngryRaven/claude/migration-banner
Migration banner on hackthetrack.net → LapWing (emergency, into main)
2 parents acb5bd7 + 9c05d19 commit d5b7818

4 files changed

Lines changed: 136 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
> from git history and grouped by theme rather than exhaustive per-commit
1212
> detail.
1313
14+
## [2.7.2] - 2026-06-20
15+
16+
### Added
17+
- **Migration notice (hackthetrack.net).** The site is moving to **LapWing** at
18+
**lapwingdata.com**. Because files are stored only in your browser per-domain,
19+
they don't carry to the new address — so a banner on hackthetrack.net warns of
20+
the **July 20, 2026** shutdown and offers a one-click data export and (where
21+
accounts are enabled) a "create a free account" path so your data follows you
22+
via cloud sync. The banner only appears on the old domain.
23+
1424
## [2.7.1] - 2026-06-19
1525

1626
### Changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "doves-dataviewer",
33
"private": true,
4-
"version": "2.7.1",
4+
"version": "2.7.2",
55
"description": "Open-source, offline-first motorsport telemetry viewer (Dove's DataViewer / HackTheTrack).",
66
"license": "GPL-3.0-or-later",
77
"author": "TheAngryRaven",

src/App.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { BrowserRouter, Routes, Route } from "react-router-dom";
99
import { I18nextProvider } from "react-i18next";
1010
import i18n from "@/lib/i18n";
1111
import { AuthProvider } from "@/contexts/AuthContext";
12+
import { MigrationBanner } from "@/components/MigrationBanner";
1213
import Index from "./pages/Index";
1314
import NotFound from "./pages/NotFound";
1415

@@ -59,6 +60,9 @@ const App = () => {
5960
<FileLoadingOverlay />
6061
<DebugConsole />
6162
<BrowserRouter>
63+
{/* Old-domain-only migration notice (hackthetrack.net → lapwingdata.com).
64+
Renders nothing on the new site. Inside the router so it can navigate. */}
65+
<MigrationBanner />
6266
<Suspense fallback={null}>
6367
{enableCloud && <PendingCheckoutRedirect />}
6468
<Routes>

src/components/MigrationBanner.tsx

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import { useState } from "react";
2+
import { useNavigate } from "react-router-dom";
3+
import { AlertTriangle, Download, Loader2, X } from "lucide-react";
4+
import { toast } from "sonner";
5+
import { Button } from "@/components/ui/button";
6+
7+
// Emergency migration notice for the OLD domain. HackTheTrack is moving to
8+
// LapWing (lapwingdata.com), which is a different origin — so local-only files
9+
// (IndexedDB/localStorage) do NOT carry over. This banner warns users on the old
10+
// host to create an account (cloud sync follows them) or export a copy before the
11+
// old site is shut down.
12+
//
13+
// Host-gated: only renders on hackthetrack.net. The new domain never shows it.
14+
// `?migrate=preview` forces it on any host for testing.
15+
16+
const NEW_SITE = "https://lapwingdata.com";
17+
// The day the old site (hackthetrack.net) is taken down. Local-only data is lost
18+
// after this unless exported or moved to an account.
19+
const KILL_DATE = new Date("2026-07-20T00:00:00Z");
20+
const KILL_DATE_LABEL = "July 20, 2026";
21+
const DISMISS_KEY = "htt-migration-dismissed"; // session-only, so it returns next visit
22+
23+
const enableCloud = import.meta.env.VITE_ENABLE_CLOUD === "true";
24+
25+
/** True on the legacy domain (or when explicitly previewed). */
26+
function onOldSite(): boolean {
27+
if (typeof window === "undefined") return false;
28+
const host = window.location.hostname.toLowerCase();
29+
const isOld = host === "hackthetrack.net" || host === "www.hackthetrack.net";
30+
const preview = window.location.search.includes("migrate=preview");
31+
return isOld || preview;
32+
}
33+
34+
function daysUntilKill(now: number = Date.now()): number {
35+
return Math.max(0, Math.ceil((KILL_DATE.getTime() - now) / 86_400_000));
36+
}
37+
38+
export function MigrationBanner() {
39+
const navigate = useNavigate();
40+
const [dismissed, setDismissed] = useState(() => {
41+
try {
42+
return sessionStorage.getItem(DISMISS_KEY) === "1";
43+
} catch {
44+
return false;
45+
}
46+
});
47+
const [exporting, setExporting] = useState(false);
48+
49+
if (!onOldSite() || dismissed) return null;
50+
51+
const days = daysUntilKill();
52+
const closed = days <= 0;
53+
54+
const dismiss = () => {
55+
try {
56+
sessionStorage.setItem(DISMISS_KEY, "1");
57+
} catch {
58+
/* ignore */
59+
}
60+
setDismissed(true);
61+
};
62+
63+
const runExport = async () => {
64+
setExporting(true);
65+
try {
66+
// Dynamic import keeps the cloud-sync/export + Supabase chunk off the
67+
// eager graph; the export gathers local data even when signed out.
68+
const { downloadAccountExport } = await import("@/plugins/cloud-sync/accountExport");
69+
await downloadAccountExport();
70+
toast.success("Your data export has started downloading.");
71+
} catch {
72+
toast.error("Couldn't export here — open Profile → Data & privacy to download your data.");
73+
} finally {
74+
setExporting(false);
75+
}
76+
};
77+
78+
return (
79+
<div className="w-full border-b border-warning/60 bg-warning/10 px-4 py-3 text-warning">
80+
<div className="mx-auto flex w-full max-w-4xl items-start gap-3">
81+
<AlertTriangle className="mt-0.5 h-5 w-5 shrink-0" />
82+
<div className="min-w-0 flex-1 space-y-2">
83+
<p className="text-sm font-medium">
84+
HackTheTrack is moving to <span className="font-bold">LapWing</span> at{" "}
85+
<span className="font-bold">lapwingdata.com</span>.{" "}
86+
{closed
87+
? "This site (hackthetrack.net) is closing."
88+
: `This site (hackthetrack.net) shuts down on ${KILL_DATE_LABEL} (${days} day${days === 1 ? "" : "s"} left).`}
89+
</p>
90+
<p className="text-xs">
91+
Your files are saved only in this browser and <strong>won't carry over</strong> to the
92+
new site. Keep them by {enableCloud ? "creating a free account (your data syncs to the new site) or " : ""}
93+
exporting a copy now.
94+
</p>
95+
<div className="flex flex-wrap gap-2 pt-0.5">
96+
{enableCloud && (
97+
<Button size="sm" onClick={() => navigate("/register")}>
98+
Create free account
99+
</Button>
100+
)}
101+
<Button size="sm" variant="outline" disabled={exporting} onClick={() => void runExport()}>
102+
{exporting ? <Loader2 className="mr-1.5 h-4 w-4 animate-spin" /> : <Download className="mr-1.5 h-4 w-4" />}
103+
Export my data
104+
</Button>
105+
<Button size="sm" variant="ghost" asChild>
106+
<a href={NEW_SITE} target="_blank" rel="noopener noreferrer">Go to LapWing →</a>
107+
</Button>
108+
</div>
109+
</div>
110+
<button
111+
type="button"
112+
onClick={dismiss}
113+
aria-label="Dismiss"
114+
className="shrink-0 rounded p-1 text-warning/80 transition-colors hover:bg-warning/20 hover:text-warning"
115+
>
116+
<X className="h-4 w-4" />
117+
</button>
118+
</div>
119+
</div>
120+
);
121+
}

0 commit comments

Comments
 (0)