added ability to copy bot token

This commit is contained in:
onysd 2026-08-05 22:33:33 +03:00
parent bb1f680d3b
commit 8aad71643f
16 changed files with 294 additions and 23 deletions

View file

@ -1,4 +1,4 @@
import { CheckCircle2, CircleAlert, FileJson, Loader2, Play, X } from "lucide-react";
import { Check, CheckCircle2, CircleAlert, Copy, FileJson, Loader2, Play, X } from "lucide-react";
import type { ReactNode } from "react";
import { useMemo, useState } from "react";
import { createPortal } from "react-dom";
@ -17,7 +17,8 @@ export function ActionButton({
tone = "danger",
disabled = false,
onDone,
onError
onError,
secretField
}: {
label: string;
path: string;
@ -34,17 +35,24 @@ export function ActionButton({
// form — an optimistic-locking 409, say — and replace the raw backend text with
// an explanation by returning it.
onError?: (error: unknown) => string | undefined;
// secretField names a key in result.details that holds a one-time secret
// (e.g. a bot token) -- when present, it's pulled out of the generic JSON
// dump and rendered instead as its own copy-to-clipboard callout, so it
// doesn't get lost among the other fields.
secretField?: string;
}) {
const [open, setOpen] = useState(false);
const [reason, setReason] = useState("");
const [result, setResult] = useState<CommandResult | null>(null);
const [error, setError] = useState("");
const [busy, setBusy] = useState(false);
const [secretCopied, setSecretCopied] = useState(false);
function reset() {
setReason("");
setResult(null);
setError("");
setSecretCopied(false);
}
async function run(confirm: boolean) {
@ -78,6 +86,18 @@ export function ActionButton({
}
}, [open, payload]);
const secretValue = secretField && result?.details && typeof result.details[secretField] === "string"
? (result.details[secretField] as string)
: "";
const visibleDetails = secretValue && result?.details
? Object.fromEntries(Object.entries(result.details).filter(([key]) => key !== secretField))
: result?.details;
async function copySecret() {
await navigator.clipboard.writeText(secretValue);
setSecretCopied(true);
}
return (
<>
<button
@ -133,7 +153,19 @@ export function ActionButton({
<div className="result-line"><span>{"Status"}</span><strong>{result.status}</strong></div>
<div className="result-line"><span>{"Dry-run"}</span><strong>{result.dry_run ? "Yes" : "No"}</strong></div>
<div className="result-message">{result.message || result.error}</div>
{result.details && <JsonBlock value={JSON.stringify(result.details, null, 2)} />}
{secretValue && (
<div className="secret-reveal">
<div className="secret-reveal-label">{"One-time secret — copy it now, it won't be shown again"}</div>
<div className="secret-reveal-row">
<code className="secret-reveal-value">{"•".repeat(Math.min(secretValue.length, 40))}</code>
<button className="btn icon-text" type="button" onClick={() => void copySecret()}>
{secretCopied ? <Check size={15} /> : <Copy size={15} />}
{secretCopied ? "Copied" : "Copy"}
</button>
</div>
</div>
)}
{visibleDetails && Object.keys(visibleDetails).length > 0 && <JsonBlock value={JSON.stringify(visibleDetails, null, 2)} />}
</div>
)}
</div>