feat: complete postbox access evidence

This commit is contained in:
2026-07-30 14:26:59 +02:00
parent 28b60782de
commit bdc161e889
10 changed files with 858 additions and 96 deletions
+74 -7
View File
@@ -28,6 +28,7 @@ import {
StatusBadge,
ToggleSwitch,
hasScope,
isApiError,
type ApiSettings,
type AuthInfo
} from "@govoplan/core-webui";
@@ -80,6 +81,7 @@ export default function PostboxPage({
const [messages, setMessages] = useState<PostboxMessage[]>([]);
const [selectedMessageId, setSelectedMessageId] = useState("");
const [selectedMessage, setSelectedMessage] = useState<PostboxMessage | null>(null);
const [unavailableSelection, setUnavailableSelection] = useState("");
const [total, setTotal] = useState(0);
const [messageState, setMessageState] = useState<MessageStateFilter>("all");
const [searchDraft, setSearchDraft] = useState("");
@@ -145,6 +147,7 @@ export default function PostboxPage({
setMessages([]);
setSelectedMessageId("");
setSelectedMessage(null);
setUnavailableSelection("");
setTotal(0);
return;
}
@@ -203,15 +206,28 @@ export default function PostboxPage({
const message = await getPostboxMessage(settings, messageId);
if (cancelled) return;
if (!postboxes.some((postbox) => postbox.id === message.postbox_id)) {
setError("The linked message is not available in your current Postbox assignments.");
setSelectedMessageId(messageId);
setSelectedMessage(null);
setUnavailableSelection(
"This message is outside your current Postbox assignments."
);
return;
}
setSelectedScope("all");
setSelectedPostboxId(message.postbox_id);
setSelectedMessageId(message.id);
setSelectedMessage(message);
setUnavailableSelection("");
} catch (loadError) {
if (!cancelled) setError(errorMessage(loadError));
if (!cancelled && isApiError(loadError, 403, 404)) {
setSelectedMessageId(messageId);
setSelectedMessage(null);
setUnavailableSelection(
"This message is no longer available or is outside your current Postbox assignments."
);
} else if (!cancelled) {
setError(errorMessage(loadError));
}
}
})();
return () => {
@@ -222,10 +238,11 @@ export default function PostboxPage({
useEffect(() => {
if (!selectedMessageId) return;
let cancelled = false;
setUnavailableSelection("");
void (async () => {
try {
let message = await getPostboxMessage(settings, selectedMessageId);
if (!message.read_at) {
if (message.availability === "available" && !message.read_at) {
message = await markPostboxMessage(settings, selectedMessageId, "read");
}
if (cancelled) return;
@@ -234,7 +251,14 @@ export default function PostboxPage({
items.map((item) => (item.id === message.id ? message : item))
);
} catch (loadError) {
if (!cancelled) setError(errorMessage(loadError));
if (!cancelled && isApiError(loadError, 403, 404)) {
setSelectedMessage(null);
setUnavailableSelection(
"This message is no longer available or is outside your current Postbox assignments."
);
} else if (!cancelled) {
setError(errorMessage(loadError));
}
}
})();
return () => {
@@ -243,7 +267,13 @@ export default function PostboxPage({
}, [selectedMessageId, settings]);
async function acknowledgeSelected() {
if (!selectedMessage || !canAcknowledge) return;
if (
!selectedMessage ||
selectedMessage.availability !== "available" ||
!canAcknowledge
) {
return;
}
setBusy(true);
setError("");
try {
@@ -268,6 +298,7 @@ export default function PostboxPage({
setPage(1);
setSelectedMessageId("");
setSelectedMessage(null);
setUnavailableSelection("");
}
function selectScope(scopeId: string) {
@@ -276,6 +307,7 @@ export default function PostboxPage({
setPage(1);
setSelectedMessageId("");
setSelectedMessage(null);
setUnavailableSelection("");
}
function openNewGrouping() {
@@ -544,6 +576,12 @@ export default function PostboxPage({
{message.acknowledged_at ? (
<span><CheckCheck size={13} /> Acknowledged</span>
) : null}
{message.availability !== "available" ? (
<StatusBadge
status="warning"
label={message.availability === "withdrawn" ? "Withdrawn" : "Expired"}
/>
) : null}
</span>
</SelectionListItem>
))}
@@ -579,8 +617,20 @@ export default function PostboxPage({
</div>
<Button
onClick={() => void acknowledgeSelected()}
disabled={!selectedMessage || Boolean(selectedMessage.acknowledged_at) || busy}
disabledReason={!canAcknowledge ? "You cannot acknowledge Postbox messages." : undefined}
disabled={
!selectedMessage ||
selectedMessage.availability !== "available" ||
Boolean(selectedMessage.acknowledged_at) ||
busy
}
disabledReason={
!canAcknowledge
? "You cannot acknowledge Postbox messages."
: selectedMessage &&
selectedMessage.availability !== "available"
? "Withdrawn or expired messages cannot be acknowledged."
: undefined
}
>
<CheckCheck size={16} /> Acknowledge
</Button>
@@ -590,6 +640,12 @@ export default function PostboxPage({
message={selectedMessage}
postbox={postboxes.find((item) => item.id === selectedMessage.postbox_id)}
/>
) : unavailableSelection ? (
<div className="postbox-empty postbox-unavailable-message">
<Archive size={24} />
<strong>Message unavailable</strong>
<p>{unavailableSelection}</p>
</div>
) : (
<div className="postbox-empty">
<Inbox size={24} />
@@ -694,6 +750,17 @@ function MessageDetail({
}) {
return (
<div className="postbox-message-detail">
{message.availability !== "available" ? (
<DismissibleAlert
tone="warning"
compact
resetKey={`${message.id}:${message.availability}`}
>
{message.availability === "withdrawn"
? "This message was withdrawn. Its audit metadata remains visible, but its content and actions are unavailable."
: "This message has expired. Its audit metadata remains visible, but its content and actions are unavailable."}
</DismissibleAlert>
) : null}
<header>
<div className="postbox-detail-status">
<StatusBadge status={message.status} />