← snapshot
2393 bytes
"use client";
import { useRouter } from "next/navigation";
import { useState } from "react";
import { ApiError, api } from "@/lib/api";
import type { ActorView } from "@/lib/types";
import { statementStatus } from "@/lib/validate";
import { VoiceStatementInput } from "./voice-statement-input";
/**
* The one write that matters: a human puts their name to a decision, in
* their own words. The select offers only humans — agents are not merely
* discouraged, they have no constructor.
*/
export function AttestForm({
repo,
decisionId,
humans,
}: {
repo: string;
decisionId: string;
humans: ActorView[];
}) {
const router = useRouter();
const [by, setBy] = useState(humans[0]?.id ?? "");
const [statement, setStatement] = useState("");
const [pending, setPending] = useState(false);
const [error, setError] = useState<string | null>(null);
const valid = statementStatus(statement).valid && by !== "";
async function submit() {
setPending(true);
setError(null);
try {
await api.attest(repo, decisionId, by, statement);
router.refresh();
} catch (e) {
setError(e instanceof ApiError ? e.message : String(e));
} finally {
setPending(false);
}
}
return (
<div style={{ marginTop: 16 }}>
<div className="jac-field">
<label className="jac-label" htmlFor="attest-by">
Signing as
</label>
<select
id="attest-by"
className="jac-select"
value={by}
onChange={(e) => setBy(e.target.value)}
>
{humans.map((h) => (
<option key={h.id} value={h.id}>
{h.display_name ?? h.id} ({h.id})
</option>
))}
</select>
</div>
<div className="jac-field">
<label className="jac-label">Your statement — verbatim</label>
<VoiceStatementInput value={statement} onChange={setStatement} />
</div>
{error ? <div className="jac-error-box">{error}</div> : null}
<div className="jac-cta-row">
<button
type="button"
className="jac-btn jac-btn--primary"
disabled={!valid || pending}
onClick={submit}
>
{pending ? "Signing…" : "Sign in your own words"}
</button>
<span className="jac-small">Recognised, never policed.</span>
</div>
</div>
);
}