campaign sending prototype
This commit is contained in:
90
webui/src/components/MessageDisplayPanel.tsx
Normal file
90
webui/src/components/MessageDisplayPanel.tsx
Normal file
@@ -0,0 +1,90 @@
|
||||
import { Paperclip } from "lucide-react";
|
||||
|
||||
export type MessageDisplayField = {
|
||||
label: string;
|
||||
value?: string | null;
|
||||
};
|
||||
|
||||
export type MessageDisplayAttachment = {
|
||||
id?: string | null;
|
||||
filename?: string | null;
|
||||
contentType: string;
|
||||
sizeBytes: number;
|
||||
};
|
||||
|
||||
type MessageDisplayPanelProps = {
|
||||
title?: string | null;
|
||||
fields?: MessageDisplayField[];
|
||||
bodyText?: string | null;
|
||||
bodyHtml?: string | null;
|
||||
bodyPreview?: string | null;
|
||||
headers?: Record<string, string>;
|
||||
attachments?: MessageDisplayAttachment[];
|
||||
emptyText?: string;
|
||||
};
|
||||
|
||||
export default function MessageDisplayPanel({
|
||||
title,
|
||||
fields = [],
|
||||
bodyText,
|
||||
bodyHtml,
|
||||
bodyPreview,
|
||||
headers = {},
|
||||
attachments = [],
|
||||
emptyText = "Select an item to inspect its content."
|
||||
}: MessageDisplayPanelProps) {
|
||||
if (!title && fields.length === 0 && !bodyText && !bodyHtml && !bodyPreview && attachments.length === 0 && Object.keys(headers).length === 0) {
|
||||
return <p className="muted">{emptyText}</p>;
|
||||
}
|
||||
|
||||
const body = bodyText || bodyPreview || (bodyHtml ? stripHtml(bodyHtml) : "");
|
||||
|
||||
return (
|
||||
<div className="message-display-panel">
|
||||
<div className="message-display-header">
|
||||
<h3>{title || "(no subject)"}</h3>
|
||||
{fields.filter((field) => field.value).map((field) => (
|
||||
<div key={field.label}>
|
||||
<span>{field.label}</span>
|
||||
<strong>{field.value}</strong>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<pre className="message-display-body">{body || "No readable body content."}</pre>
|
||||
|
||||
{attachments.length ? (
|
||||
<div className="message-display-attachments">
|
||||
<h4>Attachments</h4>
|
||||
{attachments.map((attachment, index) => (
|
||||
<div key={attachment.id || `${attachment.filename || "attachment"}-${index}`}>
|
||||
<Paperclip size={14} aria-hidden="true" />
|
||||
<span>{attachment.filename || "unnamed attachment"}</span>
|
||||
<small>{attachment.contentType} - {formatBytes(attachment.sizeBytes)}</small>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<details className="message-display-headers">
|
||||
<summary>Headers</summary>
|
||||
<dl>
|
||||
{Object.entries(headers).map(([key, value]) => (
|
||||
<div key={key}><dt>{key}</dt><dd>{value}</dd></div>
|
||||
))}
|
||||
</dl>
|
||||
</details>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function formatBytes(value?: number | null): string {
|
||||
if (!value) return "-";
|
||||
if (value < 1024) return `${value} B`;
|
||||
if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`;
|
||||
return `${(value / 1024 / 1024).toFixed(1)} MB`;
|
||||
}
|
||||
|
||||
function stripHtml(value: string): string {
|
||||
return value.replace(/<style[\s\S]*?<\/style>/gi, " ").replace(/<script[\s\S]*?<\/script>/gi, " ").replace(/<[^>]+>/g, " ").replace(/\s+/g, " ").trim();
|
||||
}
|
||||
Reference in New Issue
Block a user