fix(panel): implement two-step confirmation for claiming a server to prevent accidental operations
This commit is contained in:
3 files changed
+42
-2
No files matched your search
@@ -19,6 +19,8 @@
|
||||
"online": "online",
|
||||
"console": "Console",
|
||||
"claim": "Claim",
|
||||
"claim_server_title": "Claim Server",
|
||||
"claim_server_desc": "Are you sure you want to claim the server \"{{name}}\"? Once claimed, you will have access to its console, player management, and backups.",
|
||||
"claiming": "Claiming…",
|
||||
"stop": "Stop",
|
||||
"stopping": "Stopping…",
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
"online": "人在线",
|
||||
"console": "控制台",
|
||||
"claim": "认领",
|
||||
"claim_server_title": "认领服务器",
|
||||
"claim_server_desc": "确认认领服务器「{{name}}」吗?认领后你将成为该服务器的所有者,获得控制台、白名单与备份管理权限。",
|
||||
"claiming": "认领中…",
|
||||
"stop": "停止",
|
||||
"stopping": "停止中…",
|
||||
|
||||
@@ -18,6 +18,14 @@ import { useTranslation } from "react-i18next";
|
||||
import { Card, CardContent } from "@/components/ui/card";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogDescription,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from "@/components/ui/dialog";
|
||||
import {
|
||||
Select,
|
||||
SelectTrigger,
|
||||
@@ -433,6 +441,7 @@ function ServerRow({
|
||||
const { t } = useTranslation("ops");
|
||||
const { t: ts } = useTranslation("servers");
|
||||
const [busy, setBusy] = useState<null | "wake" | "stop" | "claim">(null);
|
||||
const [confirmOpen, setConfirmOpen] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
async function act(kind: "wake" | "stop" | "claim", fn: () => Promise<unknown>) {
|
||||
@@ -523,14 +532,41 @@ function ServerRow({
|
||||
size="sm"
|
||||
variant="outline"
|
||||
disabled={busy !== null}
|
||||
onClick={() => act("claim", () => api.claim(server.name))}
|
||||
onClick={() => setConfirmOpen(true)}
|
||||
className="text-primary hover:text-primary hover:bg-primary/5 border-primary/20"
|
||||
>
|
||||
<Hand /> {busy === "claim" ? ts("claiming") : ts("claim")}
|
||||
<Hand /> {ts("claim")}
|
||||
</Button>
|
||||
<Button size="sm" variant="outline" disabled>
|
||||
<Terminal /> {ts("console")}
|
||||
</Button>
|
||||
|
||||
<Dialog open={confirmOpen} onOpenChange={setConfirmOpen}>
|
||||
<DialogContent className="sm:max-w-md">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{ts("claim_server_title")}</DialogTitle>
|
||||
<DialogDescription>
|
||||
{ts("claim_server_desc", { name: server.name })}
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogFooter className="gap-2 sm:gap-0">
|
||||
<Button variant="ghost" onClick={() => setConfirmOpen(false)} disabled={busy !== null}>
|
||||
{ts("access_cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="default"
|
||||
onClick={async () => {
|
||||
await act("claim", () => api.claim(server.name));
|
||||
setConfirmOpen(false);
|
||||
}}
|
||||
disabled={busy !== null}
|
||||
>
|
||||
{busy === "claim" && <RefreshCw className="h-4 w-4 animate-spin mr-1" />}
|
||||
{ts("claim")}
|
||||
</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
|
||||
Reference in new issue
Block a user