feat: govern attachment exceptions and ownership transfers

This commit is contained in:
2026-07-30 17:42:10 +02:00
parent cd223cbb95
commit 5f7503598c
28 changed files with 2229 additions and 180 deletions
@@ -1,4 +1,8 @@
import { Button, i18nMessage } from "@govoplan/core-webui";
import {
Button,
FormField,
i18nMessage
} from "@govoplan/core-webui";
import CampaignMessagePreviewOverlay, {
type CampaignMessagePreviewAttachment
@@ -16,6 +20,10 @@ import {
numberOrUndefined,
stringOrUndefined
} from "./reviewFormatters";
import {
messageNeedsExplicitReview,
messageRequiresAttachmentOverrideReason
} from "./builtMessageQuery";
export default function BuiltMessagePreview({
campaignJson,
@@ -24,6 +32,10 @@ export default function BuiltMessagePreview({
index,
canStartSingleMessageSend,
singleMessageSendBusy,
reviewed,
reviewReason,
onReviewReasonChange,
onAcceptReview,
onSelect,
onSendSingle,
onClose
@@ -34,6 +46,10 @@ export default function BuiltMessagePreview({
index: number;
canStartSingleMessageSend: boolean;
singleMessageSendBusy: boolean;
reviewed: boolean;
reviewReason: string;
onReviewReasonChange: (value: string) => void;
onAcceptReview: (reasonRequired: boolean) => void;
onSelect: (index: number) => void;
onSendSingle: (index: number) => void;
onClose: () => void;
@@ -73,6 +89,8 @@ export default function BuiltMessagePreview({
row,
canStartSingleMessageSend
);
const explicitReview = messageNeedsExplicitReview(row);
const reasonRequired = messageRequiresAttachmentOverrideReason(row);
return (
<CampaignMessagePreviewOverlay
@@ -109,14 +127,47 @@ export default function BuiltMessagePreview({
onLast: () => onSelect(rows.length - 1)
}}
actions={
<Button
variant="primary"
disabled={singleMessageSendBusy || Boolean(singleSendDisabledReason)}
title={singleSendDisabledReason || "Test, send, or resend only this built message"}
onClick={() => onSendSingle(index)}
>
{singleMessageSendBusy ? "Sending..." : "Message actions..."}
</Button>
<div className="built-message-review-actions">
{explicitReview && !reviewed ? (
<>
<FormField
label={
reasonRequired
? "Reason for accepting attachment exception"
: "Review note"
}
help={
reasonRequired
? "Required. This reason is stored with the frozen build evidence."
: "Optional note stored with the review decision."
}
>
<input
value={reviewReason}
maxLength={4000}
onChange={(event) =>
onReviewReasonChange(event.target.value)
}
/>
</FormField>
<Button
variant="primary"
disabled={reasonRequired && !reviewReason.trim()}
onClick={() => onAcceptReview(reasonRequired)}
>
Accept review conditions
</Button>
</>
) : null}
<Button
variant={explicitReview && !reviewed ? undefined : "primary"}
disabled={singleMessageSendBusy || Boolean(singleSendDisabledReason)}
title={singleSendDisabledReason || "Test, send, or resend only this built message"}
onClick={() => onSendSingle(index)}
>
{singleMessageSendBusy ? "Sending..." : "Message actions..."}
</Button>
</div>
}
onClose={onClose}
/>