Compare commits
2 Commits
87eb93512b
...
5cd6f5b96d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5cd6f5b96d | ||
|
|
94bf067c41 |
@@ -70,6 +70,22 @@ h2 {
|
|||||||
font-size: 0.85rem;
|
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 {
|
.back-btn {
|
||||||
padding: 0.4rem 1rem;
|
padding: 0.4rem 1rem;
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
@@ -159,3 +175,13 @@ h2 {
|
|||||||
.actions button.danger:hover {
|
.actions button.danger:hover {
|
||||||
background: #e74c3c;
|
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;
|
||||||
|
}
|
||||||
|
|||||||
@@ -54,6 +54,16 @@ function ProjectList({
|
|||||||
<span className="project-date">
|
<span className="project-date">
|
||||||
{new Date(p.created_at).toLocaleString()}
|
{new Date(p.created_at).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
|
<button
|
||||||
|
className="project-delete"
|
||||||
|
onClick={async (e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
await deleteProject(p.id);
|
||||||
|
await load();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
X
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@@ -190,6 +200,16 @@ function parseHash(): { view: "list" | "project"; projectId: number | null } {
|
|||||||
function App() {
|
function App() {
|
||||||
const [view, setView] = useState<"list" | "project">(parseHash().view);
|
const [view, setView] = useState<"list" | "project">(parseHash().view);
|
||||||
const [projectId, setProjectId] = useState<number | null>(parseHash().projectId);
|
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(() => {
|
useEffect(() => {
|
||||||
const onHashChange = () => {
|
const onHashChange = () => {
|
||||||
@@ -209,6 +229,11 @@ function App() {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (vpnCheck === "loading") return null;
|
||||||
|
if (vpnCheck === "vpn") {
|
||||||
|
return <div className="vpn-block">Выключи VPN</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="app">
|
<div className="app">
|
||||||
{view === "list" ? (
|
{view === "list" ? (
|
||||||
|
|||||||
Reference in New Issue
Block a user