feat: complete postbox access evidence
This commit is contained in:
@@ -64,6 +64,7 @@ export type PostboxMessage = {
|
||||
subject: string;
|
||||
body_text?: string | null;
|
||||
status: string;
|
||||
availability: "available" | "withdrawn" | "expired";
|
||||
classification: string;
|
||||
sender_label?: string | null;
|
||||
delivered_at: string;
|
||||
|
||||
@@ -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} />
|
||||
|
||||
Reference in New Issue
Block a user