Почтовый кабинет: регистрация, папки, черновики, вложения, грид @jambotron/aria-grid, интерфейс по synthwave-шаблону
Бэкенд: регистрация со входом сразу и /api/config, профиль и смена пароля, свои папки, PATCH письма (прочитано, флажок, перенос; Спам ставит отметку), корзина и очистка, черновики, несколько адресатов и копия, вложения черновика уходят с письмом, вложения и заголовки To/Cc входящих сохраняются, письма заблокированных — в Спам. Исправлено: загрузка файла (415), колонка content_id (500 на любых вложениях), SPA-маршрут с точкой. Фронтенд переписан: все таблицы — грид, оформление — шаблон synthwave-design-showcase.html. α Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
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;
|
||||
}
|
||||
|
||||
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<string, { label: string; icon: string }> = {
|
||||
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';
|
||||
}
|
||||
Reference in New Issue
Block a user