Compare commits

...

2 Commits

Author SHA1 Message Date
Mikhail Kilin
5cd6f5b96d Add delete button to project list on main page
All checks were successful
ci/woodpecker/push/build Pipeline was successful
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-19 15:22:37 +03:00
Mikhail Kilin
94bf067c41 Add VPN detection: show blocker when not on home IP
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) <noreply@anthropic.com>
2026-03-19 14:56:55 +03:00
2 changed files with 51 additions and 0 deletions

View File

@@ -70,6 +70,22 @@ h2 {
font-size: 0.85rem;
}
.project-delete {
background: #c0392b;
color: white;
border: none;
border-radius: 4px;
padding: 0.2rem 0.5rem;
font-size: 0.8rem;
cursor: pointer;
margin-left: 0.5rem;
flex-shrink: 0;
}
.project-delete:hover {
background: #e74c3c;
}
.back-btn {
padding: 0.4rem 1rem;
font-size: 0.9rem;
@@ -159,3 +175,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;
}

View File

@@ -54,6 +54,16 @@ function ProjectList({
<span className="project-date">
{new Date(p.created_at).toLocaleString()}
</span>
<button
className="project-delete"
onClick={async (e) => {
e.stopPropagation();
await deleteProject(p.id);
await load();
}}
>
X
</button>
</div>
))}
</div>
@@ -190,6 +200,16 @@ function parseHash(): { view: "list" | "project"; projectId: number | null } {
function App() {
const [view, setView] = useState<"list" | "project">(parseHash().view);
const [projectId, setProjectId] = useState<number | null>(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 +229,11 @@ function App() {
}
};
if (vpnCheck === "loading") return null;
if (vpnCheck === "vpn") {
return <div className="vpn-block">Выключи VPN</div>;
}
return (
<div className="app">
{view === "list" ? (