Workflow UI redesign - first draft

This commit is contained in:
2026-06-11 12:30:27 +02:00
parent 03c3f5f5c3
commit fdab7cd362
9 changed files with 704 additions and 577 deletions

View File

@@ -7,6 +7,8 @@ import PageTitle from "../../components/PageTitle";
import LoadingFrame from "../../components/LoadingFrame";
import LockedVersionNotice from "./components/LockedVersionNotice";
import VersionLine from "./components/VersionLine";
import ReviewWorkflowCards from "./components/ReviewWorkflowCards";
import MessagePreviewOverlay, { type MessagePreviewAttachment } from "./components/MessagePreviewOverlay";
import Card from "../../components/Card";
import MetricCard from "../../components/MetricCard";
import StatusBadge from "../../components/StatusBadge";
@@ -157,6 +159,15 @@ export default function SendDataPage({ settings, campaignId }: { settings: ApiSe
<MetricCard label="Failed" value={cards?.failed ?? "—"} tone="danger" detail="SMTP failures" />
</div>
<ReviewWorkflowCards
settings={settings}
version={version}
summary={data.summary}
loading={loading}
reload={reload}
setError={setError}
/>
<Card title="Mock delivery test" collapsible actions={<span className="muted small-note">Temporary sandbox. It never uses the real SMTP/IMAP server and never marks this version sent.</span>}>
<div className="button-row compact-actions">
<Button onClick={() => void runMockFlow(false, false)} disabled={!version || loading || mockBusy}>{mockBusy ? "Working…" : "Review mock steps"}</Button>
@@ -254,22 +265,7 @@ export default function SendDataPage({ settings, campaignId }: { settings: ApiSe
</table>
</div>
{selectedMockMessage && (
<div className="mock-message-detail">
<div className="subsection-heading split">
<h3>{selectedMockMessage.subject || "Mock message"}</h3>
<Button onClick={() => setSelectedMockMessage(null)}>Close details</Button>
</div>
<div className="detail-grid">
<div><span className="muted small-note">From</span><strong>{selectedMockMessage.from_header || selectedMockMessage.envelope_from || "—"}</strong></div>
<div><span className="muted small-note">To</span><strong>{selectedMockMessage.to_header || (selectedMockMessage.envelope_recipients || []).join(", ") || "—"}</strong></div>
<div><span className="muted small-note">Size</span><strong>{selectedMockMessage.size_bytes || 0} bytes</strong></div>
<div><span className="muted small-note">Folder</span><strong>{selectedMockMessage.folder || "—"}</strong></div>
</div>
{selectedMockMessage.body_preview && <pre className="mock-message-preview">{selectedMockMessage.body_preview}</pre>}
{selectedMockMessage.raw_eml && <details><summary>Raw MIME</summary><pre className="mock-message-raw">{selectedMockMessage.raw_eml}</pre></details>}
</div>
)}
</div>
)}
</Card>
@@ -343,6 +339,21 @@ export default function SendDataPage({ settings, campaignId }: { settings: ApiSe
</p>
</Card>
</LoadingFrame>
{selectedMockMessage && (
<MessagePreviewOverlay
title="Captured mock mail"
subject={selectedMockMessage.subject || "Mock message"}
bodyMode="text"
text={selectedMockMessage.body_preview || ""}
recipientLabel={selectedMockMessage.kind === "imap_append" ? "Mock IMAP append" : "Mock SMTP delivery"}
recipientNote={selectedMockMessage.created_at ? new Date(selectedMockMessage.created_at).toLocaleString() : undefined}
metaItems={mockMessageMetaItems(selectedMockMessage)}
attachments={mockMessageAttachments(selectedMockMessage)}
raw={selectedMockMessage.raw_eml}
rawLabel="Raw MIME"
onClose={() => setSelectedMockMessage(null)}
/>
)}
<ConfirmDialog
open={sendConfirmOpen}
title="Send this version now?"
@@ -359,3 +370,23 @@ export default function SendDataPage({ settings, campaignId }: { settings: ApiSe
</div>
);
}
function mockMessageMetaItems(message: MockMailboxMessage) {
return [
{ label: "From", value: message.from_header || message.envelope_from || "—" },
{ label: "To", value: message.to_header || message.envelope_recipients?.join(", ") || "—" },
{ label: "Kind", value: message.kind || "—" },
{ label: "Folder", value: message.folder || "—" },
{ label: "Message-ID", value: message.message_id || "—" },
{ label: "Size", value: `${message.size_bytes || 0} bytes` }
];
}
function mockMessageAttachments(message: MockMailboxMessage): MessagePreviewAttachment[] {
return (message.attachments ?? []).map((attachment, index) => ({
filename: attachment.filename || `Attachment ${index + 1}`,
contentType: attachment.content_type || undefined,
sizeBytes: attachment.size_bytes ?? undefined
}));
}