64 lines
1.9 KiB
TypeScript
64 lines
1.9 KiB
TypeScript
|
|
"use client";
|
||
|
|
|
||
|
|
/**
|
||
|
|
* RedTeamRequestButton — POSTs to /api/h/[id]/red-team to enqueue a Schneier
|
||
|
|
* red_team_review job. Shows the job_id + link to /jobs/[id] once submitted.
|
||
|
|
*/
|
||
|
|
import { useState } from "react";
|
||
|
|
import Link from "next/link";
|
||
|
|
import { ArrowUpRight } from "lucide-react";
|
||
|
|
|
||
|
|
export function RedTeamRequestButton({
|
||
|
|
hypothesisId,
|
||
|
|
alreadyReviewed,
|
||
|
|
}: { hypothesisId: string; alreadyReviewed: boolean }) {
|
||
|
|
const [pending, setPending] = useState(false);
|
||
|
|
const [jobId, setJobId] = useState<string | null>(null);
|
||
|
|
const [error, setError] = useState<string | null>(null);
|
||
|
|
|
||
|
|
async function requestReview() {
|
||
|
|
setPending(true);
|
||
|
|
setError(null);
|
||
|
|
try {
|
||
|
|
const r = await fetch(`/api/h/${hypothesisId}/red-team`, { method: "POST" });
|
||
|
|
const d = (await r.json()) as { job_id?: string; error?: string; message?: string };
|
||
|
|
if (!r.ok || !d.job_id) {
|
||
|
|
setError(d.error || d.message || `HTTP ${r.status}`);
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
setJobId(d.job_id);
|
||
|
|
} catch (e) {
|
||
|
|
setError((e as Error).message);
|
||
|
|
} finally {
|
||
|
|
setPending(false);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
if (jobId) {
|
||
|
|
return (
|
||
|
|
<Link
|
||
|
|
href={`/jobs/${jobId}`}
|
||
|
|
target="_blank"
|
||
|
|
className="inline-flex items-center gap-1 text-[11px] font-mono text-[#ff3344] hover:underline"
|
||
|
|
>
|
||
|
|
Schneier em ação — acompanhar <ArrowUpRight size={11} />
|
||
|
|
</Link>
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
return (
|
||
|
|
<div className="flex items-center gap-2">
|
||
|
|
<button
|
||
|
|
onClick={requestReview}
|
||
|
|
disabled={pending}
|
||
|
|
className="px-3 py-1 rounded border border-[#ff3344] text-[11px] font-mono text-[#ff3344] hover:bg-[rgba(255,51,68,0.06)] disabled:opacity-40 disabled:cursor-not-allowed"
|
||
|
|
>
|
||
|
|
{pending ? "Enfileirando…" : alreadyReviewed ? "Re-revisar (Schneier)" : "Acionar Schneier (red-team)"}
|
||
|
|
</button>
|
||
|
|
{error && (
|
||
|
|
<span className="text-[10px] font-mono text-[#ff6ec7]">{error}</span>
|
||
|
|
)}
|
||
|
|
</div>
|
||
|
|
);
|
||
|
|
}
|