export interface Folder { id: number; name: string; system: boolean; messageCount: number; unreadCount: number; } export interface Message { id: number; folderId: number; senderEmail: string; senderName?: string | null; recipientEmail: string; ccEmail?: string | null; subject: string; body: string; html: boolean; read: boolean; flagged: boolean; spam: boolean; messageSize?: number | null; receivedAt: string; hasAttachments: boolean; attachmentCount: number; } export interface Attachment { id: number; filename: string; contentType: string; sizeBytes: number; } export interface Contact { id?: number; name: string; email: string; phone?: string; company?: string; } export interface BlacklistEntry { id: number; senderEmail: string; reason?: string | null; createdAt?: string; } export interface Profile { email: string; displayName: string; usedBytes: number; quotaBytes: number; createdAt: string; admin?: boolean; // may manage mailboxes (Settings → Mailboxes) } /** MailboxAdminController.MailboxView */ export interface Mailbox { id: number; email: string; displayName: string; active: boolean; admin: boolean; trap: boolean; // what it sends stays in its own INBOX, nothing leaves the server (staging@) usedBytes: number; quotaBytes: number; createdAt: string; } /** password — generated by the server, shown once; null when the administrator chose it */ export interface MailboxCreated { mailbox: Mailbox; password: string | null; } export interface ServerConfig { registrationEnabled: boolean; domains: string[]; } export interface Draft { to: string; cc: string; subject: string; body: string; html: boolean; } /** System folders: label and icon in the web client. */ export const FOLDER_VIEW: Record = { INBOX: { label: 'Inbox', icon: 'inbox' }, Drafts: { label: 'Drafts', icon: 'edit_note' }, Sent: { label: 'Sent', icon: 'send' }, Spam: { label: 'Spam', icon: 'report' }, Trash: { label: 'Trash', icon: 'delete' } }; export function folderLabel(folder: Folder): string { return folder.system ? (FOLDER_VIEW[folder.name]?.label ?? folder.name) : folder.name; } export function folderIcon(folder: Folder): string { return folder.system ? (FOLDER_VIEW[folder.name]?.icon ?? 'folder') : 'folder'; } export function formatBytes(bytes: number | null | undefined): string { const value = bytes ?? 0; if (value < 1024) return `${value} B`; if (value < 1024 * 1024) return `${(value / 1024).toFixed(1)} KB`; if (value < 1024 * 1024 * 1024) return `${(value / 1024 / 1024).toFixed(1)} MB`; return `${(value / 1024 / 1024 / 1024).toFixed(2)} GB`; } /** Text of an API error: our {error}, Spring's {message}, or the status. */ export function errorText(error: unknown): string { const e = error as { error?: { error?: string; message?: string; status?: number } | string; status?: number; message?: string }; if (e?.error && typeof e.error === 'object') { // Spring's error body: {status, error: "Bad Request", message: "No such mailbox: …"} — the reason is in message const body = e.error; const springBody = typeof body.status === 'number'; return (springBody ? body.message || body.error : body.error || body.message) || `Error ${e.status ?? ''}`.trim(); } if (typeof e?.error === 'string' && e.error) return e.error; if (e?.status === 0) return 'Server is not reachable'; return e?.message || 'Something went wrong'; }