From 94bf067c41e8ff674c3fd27174635b4caf311755 Mon Sep 17 00:00:00 2001 From: Mikhail Kilin Date: Thu, 19 Mar 2026 14:56:55 +0300 Subject: [PATCH] Add VPN detection: show blocker when not on home IP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Check public IP via api.ipify.org on mount and display "Выключи VPN" fullscreen message if it doesn't match the expected home IP. Gracefully falls through on fetch errors. Co-Authored-By: Claude Opus 4.6 (1M context) --- frontend/src/App.css | 10 ++++++++++ frontend/src/App.tsx | 15 +++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/frontend/src/App.css b/frontend/src/App.css index 2355ec8..3475243 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -159,3 +159,13 @@ h2 { .actions button.danger:hover { background: #e74c3c; } + +.vpn-block { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + font-size: 2rem; + font-family: system-ui, -apple-system, sans-serif; + color: #333; +} diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 2b4cb10..8a6a4d8 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -190,6 +190,16 @@ function parseHash(): { view: "list" | "project"; projectId: number | null } { function App() { const [view, setView] = useState<"list" | "project">(parseHash().view); const [projectId, setProjectId] = useState(parseHash().projectId); + const [vpnCheck, setVpnCheck] = useState<"loading" | "ok" | "vpn">("loading"); + + useEffect(() => { + fetch("https://api.ipify.org?format=json") + .then((r) => r.json()) + .then((data: { ip: string }) => { + setVpnCheck(data.ip === "95.165.73.140" ? "ok" : "vpn"); + }) + .catch(() => setVpnCheck("ok")); + }, []); useEffect(() => { const onHashChange = () => { @@ -209,6 +219,11 @@ function App() { } }; + if (vpnCheck === "loading") return null; + if (vpnCheck === "vpn") { + return
Выключи VPN
; + } + return (
{view === "list" ? (