chore: initialize @limitless/ui git repo + add AppShell

This brings the long-untracked @limitless/ui source tree under version
control. Until now /srv/k8s/templates/limitless-ui has been a plain
file: dependency consumed by gscChronos / gscCRM / gscAdmin, with
copies scattered across web/gsc{Portal,WWW,Aether,Register}/ and
apps/gsc{Meet,Share}/. None were git-tracked.

Treating /srv/k8s/templates/limitless-ui as the canonical going
forward; secondary copies should be replaced with this version
in their consumers' Dockerfiles when they next get touched.

Changes in this initial commit beyond the snapshot:
- Add src/layout/AppShell.tsx — runtime-loaded chrome (header,
  sidebar, footer) backed by gsc-shell-api. Public surface:
    AppShell, ShellProvider, useShell, ShellConfig types
  Framework-agnostic (no Next.js dep). Apps pass appKey + apiUrl +
  getToken; AppShell composes the existing PageShell / Navbar /
  Sidebar / Footer primitives with API data.
- Re-export AppShell from src/index.ts.
- Fix build script: `tsc -p tsconfig.json --noEmit false`. The bare
  `tsc` command was a no-op because tsconfig.json sets noEmit:true
  for typecheck speed. Existing dist/ only existed because of an
  earlier emit; clean rebuilds were silently broken.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude
2026-05-10 09:42:57 +02:00
commit cf068ce4ec
115 changed files with 36542 additions and 0 deletions

813
src/pages/Mail.tsx Normal file
View File

@@ -0,0 +1,813 @@
import React, { useState, useCallback } from 'react';
// Types
export interface MailMessage {
id: string;
from: {
name: string;
email: string;
avatar?: string;
};
to?: {
name: string;
email: string;
}[];
subject: string;
preview: string;
body?: string;
date: Date;
isRead?: boolean;
isStarred?: boolean;
hasAttachment?: boolean;
attachments?: MailAttachment[];
labels?: string[];
folder?: string;
}
export interface MailAttachment {
id: string;
name: string;
size: string;
type: string;
url?: string;
}
export interface MailFolder {
id: string;
name: string;
icon?: React.ReactNode;
count?: number;
color?: string;
}
export interface MailLabel {
id: string;
name: string;
color: string;
}
// Mail Layout
export interface MailLayoutProps {
/** Sidebar content (folders, labels) */
sidebar?: React.ReactNode;
/** Main content area */
children: React.ReactNode;
/** Compose button handler */
onCompose?: () => void;
/** Show sidebar */
showSidebar?: boolean;
/** Additional CSS classes */
className?: string;
}
export const MailLayout: React.FC<MailLayoutProps> = ({
sidebar,
children,
onCompose,
showSidebar = true,
className = '',
}) => {
return (
<div className={`ll-mail-layout ${className}`}>
{showSidebar && (
<div className="ll-mail-sidebar">
{onCompose && (
<button className="ll-mail-compose-btn" onClick={onCompose}>
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" />
</svg>
Compose
</button>
)}
{sidebar}
</div>
)}
<div className="ll-mail-content">
{children}
</div>
</div>
);
};
// Mail Sidebar
export interface MailSidebarProps {
/** Folders list */
folders?: MailFolder[];
/** Labels list */
labels?: MailLabel[];
/** Active folder ID */
activeFolder?: string;
/** Folder click handler */
onFolderClick?: (folder: MailFolder) => void;
/** Label click handler */
onLabelClick?: (label: MailLabel) => void;
/** Additional CSS classes */
className?: string;
}
export const MailSidebar: React.FC<MailSidebarProps> = ({
folders = [],
labels = [],
activeFolder,
onFolderClick,
onLabelClick,
className = '',
}) => {
return (
<div className={`ll-mail-sidebar-content ${className}`}>
{folders.length > 0 && (
<div className="ll-mail-folders">
<div className="ll-mail-section-title">Folders</div>
<ul className="ll-mail-folder-list">
{folders.map((folder) => (
<li
key={folder.id}
className={`ll-mail-folder-item ${activeFolder === folder.id ? 'll-mail-folder-active' : ''}`}
onClick={() => onFolderClick?.(folder)}
>
{folder.icon && <span className="ll-mail-folder-icon">{folder.icon}</span>}
<span className="ll-mail-folder-name">{folder.name}</span>
{folder.count !== undefined && folder.count > 0 && (
<span className="ll-mail-folder-count">{folder.count}</span>
)}
</li>
))}
</ul>
</div>
)}
{labels.length > 0 && (
<div className="ll-mail-labels">
<div className="ll-mail-section-title">Labels</div>
<ul className="ll-mail-label-list">
{labels.map((label) => (
<li
key={label.id}
className="ll-mail-label-item"
onClick={() => onLabelClick?.(label)}
>
<span
className="ll-mail-label-dot"
style={{ backgroundColor: label.color }}
/>
<span className="ll-mail-label-name">{label.name}</span>
</li>
))}
</ul>
</div>
)}
</div>
);
};
// Mail List
export interface MailListProps {
/** Messages to display */
messages: MailMessage[];
/** Selected message IDs */
selectedIds?: string[];
/** Selection change handler */
onSelectionChange?: (ids: string[]) => void;
/** Message click handler */
onMessageClick?: (message: MailMessage) => void;
/** Star toggle handler */
onStarToggle?: (message: MailMessage) => void;
/** Show checkboxes */
showCheckboxes?: boolean;
/** Loading state */
loading?: boolean;
/** Empty state message */
emptyMessage?: string;
/** Additional CSS classes */
className?: string;
}
export const MailList: React.FC<MailListProps> = ({
messages,
selectedIds = [],
onSelectionChange,
onMessageClick,
onStarToggle,
showCheckboxes = true,
loading = false,
emptyMessage = 'No messages',
className = '',
}) => {
const handleSelectAll = useCallback(() => {
if (selectedIds.length === messages.length) {
onSelectionChange?.([]);
} else {
onSelectionChange?.(messages.map((m) => m.id));
}
}, [selectedIds, messages, onSelectionChange]);
const handleSelectMessage = useCallback((id: string) => {
if (selectedIds.includes(id)) {
onSelectionChange?.(selectedIds.filter((sid) => sid !== id));
} else {
onSelectionChange?.([...selectedIds, id]);
}
}, [selectedIds, onSelectionChange]);
const formatDate = (date: Date) => {
const now = new Date();
const diff = now.getTime() - date.getTime();
const days = Math.floor(diff / (1000 * 60 * 60 * 24));
if (days === 0) {
return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
} else if (days === 1) {
return 'Yesterday';
} else if (days < 7) {
return date.toLocaleDateString([], { weekday: 'short' });
} else {
return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
}
};
if (loading) {
return (
<div className={`ll-mail-list ll-mail-list-loading ${className}`}>
<div className="ll-mail-loading-spinner" />
</div>
);
}
if (messages.length === 0) {
return (
<div className={`ll-mail-list ll-mail-list-empty ${className}`}>
<div className="ll-mail-empty-icon">
<svg viewBox="0 0 24 24" width="48" height="48" fill="currentColor">
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
</svg>
</div>
<p className="ll-mail-empty-text">{emptyMessage}</p>
</div>
);
}
return (
<div className={`ll-mail-list ${className}`}>
{showCheckboxes && (
<div className="ll-mail-list-header">
<label className="ll-mail-checkbox">
<input
type="checkbox"
checked={selectedIds.length === messages.length && messages.length > 0}
onChange={handleSelectAll}
/>
<span className="ll-mail-checkbox-mark" />
</label>
<span className="ll-mail-list-info">
{selectedIds.length > 0 ? `${selectedIds.length} selected` : `${messages.length} messages`}
</span>
</div>
)}
<div className="ll-mail-messages">
{messages.map((message) => (
<div
key={message.id}
className={`ll-mail-message ${!message.isRead ? 'll-mail-message-unread' : ''} ${
selectedIds.includes(message.id) ? 'll-mail-message-selected' : ''
}`}
>
{showCheckboxes && (
<label className="ll-mail-checkbox" onClick={(e) => e.stopPropagation()}>
<input
type="checkbox"
checked={selectedIds.includes(message.id)}
onChange={() => handleSelectMessage(message.id)}
/>
<span className="ll-mail-checkbox-mark" />
</label>
)}
<button
className={`ll-mail-star ${message.isStarred ? 'll-mail-star-active' : ''}`}
onClick={(e) => {
e.stopPropagation();
onStarToggle?.(message);
}}
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
</svg>
</button>
<div className="ll-mail-message-content" onClick={() => onMessageClick?.(message)}>
<div className="ll-mail-message-avatar">
{message.from.avatar ? (
<img src={message.from.avatar} alt={message.from.name} />
) : (
<span className="ll-mail-message-avatar-placeholder">
{message.from.name.charAt(0).toUpperCase()}
</span>
)}
</div>
<div className="ll-mail-message-info">
<div className="ll-mail-message-header">
<span className="ll-mail-message-sender">{message.from.name}</span>
<span className="ll-mail-message-date">{formatDate(message.date)}</span>
</div>
<div className="ll-mail-message-subject">{message.subject}</div>
<div className="ll-mail-message-preview">{message.preview}</div>
</div>
{message.hasAttachment && (
<div className="ll-mail-message-attachment">
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
</svg>
</div>
)}
</div>
</div>
))}
</div>
</div>
);
};
// Mail Toolbar
export interface MailToolbarProps {
/** Selected count */
selectedCount?: number;
/** Archive handler */
onArchive?: () => void;
/** Delete handler */
onDelete?: () => void;
/** Mark as read handler */
onMarkRead?: () => void;
/** Mark as unread handler */
onMarkUnread?: () => void;
/** Move handler */
onMove?: () => void;
/** Refresh handler */
onRefresh?: () => void;
/** Search value */
searchValue?: string;
/** Search change handler */
onSearchChange?: (value: string) => void;
/** Additional CSS classes */
className?: string;
}
export const MailToolbar: React.FC<MailToolbarProps> = ({
selectedCount = 0,
onArchive,
onDelete,
onMarkRead,
onMarkUnread,
onMove,
onRefresh,
searchValue = '',
onSearchChange,
className = '',
}) => {
return (
<div className={`ll-mail-toolbar ${className}`}>
<div className="ll-mail-toolbar-actions">
{selectedCount > 0 ? (
<>
{onArchive && (
<button className="ll-mail-toolbar-btn" onClick={onArchive} title="Archive">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M20.54 5.23l-1.39-1.68C18.88 3.21 18.47 3 18 3H6c-.47 0-.88.21-1.16.55L3.46 5.23C3.17 5.57 3 6.02 3 6.5V19c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V6.5c0-.48-.17-.93-.46-1.27zM12 17.5L6.5 12H10v-2h4v2h3.5L12 17.5zM5.12 5l.81-1h12l.94 1H5.12z" />
</svg>
</button>
)}
{onDelete && (
<button className="ll-mail-toolbar-btn ll-mail-toolbar-btn-danger" onClick={onDelete} title="Delete">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
</svg>
</button>
)}
{onMarkRead && (
<button className="ll-mail-toolbar-btn" onClick={onMarkRead} title="Mark as read">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z" />
</svg>
</button>
)}
{onMarkUnread && (
<button className="ll-mail-toolbar-btn" onClick={onMarkUnread} title="Mark as unread">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M22 8.98V18c0 1.1-.9 2-2 2H4c-1.1 0-2-.9-2-2V6c0-1.1.9-2 2-2h10.1c-.06.32-.1.66-.1 1 0 1.48.65 2.79 1.67 3.71L12 11 4 6v2l8 5 5.3-3.32c.54.2 1.1.32 1.7.32 1.13 0 2.16-.39 3-1.02zM16 5c0 1.66 1.34 3 3 3s3-1.34 3-3-1.34-3-3-3-3 1.34-3 3z" />
</svg>
</button>
)}
{onMove && (
<button className="ll-mail-toolbar-btn" onClick={onMove} title="Move to">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M20 6h-8l-2-2H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2zm0 12H4V8h16v10z" />
</svg>
</button>
)}
</>
) : (
onRefresh && (
<button className="ll-mail-toolbar-btn" onClick={onRefresh} title="Refresh">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z" />
</svg>
</button>
)
)}
</div>
{onSearchChange && (
<div className="ll-mail-search">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M15.5 14h-.79l-.28-.27C15.41 12.59 16 11.11 16 9.5 16 5.91 13.09 3 9.5 3S3 5.91 3 9.5 5.91 16 9.5 16c1.61 0 3.09-.59 4.23-1.57l.27.28v.79l5 4.99L20.49 19l-4.99-5zm-6 0C7.01 14 5 11.99 5 9.5S7.01 5 9.5 5 14 7.01 14 9.5 11.99 14 9.5 14z" />
</svg>
<input
type="text"
placeholder="Search mail..."
value={searchValue}
onChange={(e) => onSearchChange(e.target.value)}
/>
</div>
)}
</div>
);
};
// Mail Read View
export interface MailReadProps {
/** Message to display */
message: MailMessage;
/** Back button handler */
onBack?: () => void;
/** Reply handler */
onReply?: () => void;
/** Reply all handler */
onReplyAll?: () => void;
/** Forward handler */
onForward?: () => void;
/** Delete handler */
onDelete?: () => void;
/** Star toggle handler */
onStarToggle?: () => void;
/** Additional CSS classes */
className?: string;
}
export const MailRead: React.FC<MailReadProps> = ({
message,
onBack,
onReply,
onReplyAll,
onForward,
onDelete,
onStarToggle,
className = '',
}) => {
const formatDate = (date: Date) => {
return date.toLocaleDateString([], {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
});
};
return (
<div className={`ll-mail-read ${className}`}>
<div className="ll-mail-read-header">
{onBack && (
<button className="ll-mail-read-back" onClick={onBack}>
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<path d="M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z" />
</svg>
</button>
)}
<div className="ll-mail-read-actions">
{onReply && (
<button className="ll-mail-read-btn" onClick={onReply} title="Reply">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M10 9V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z" />
</svg>
</button>
)}
{onReplyAll && (
<button className="ll-mail-read-btn" onClick={onReplyAll} title="Reply All">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M7 8V5l-7 7 7 7v-3l-4-4 4-4zm6 1V5l-7 7 7 7v-4.1c5 0 8.5 1.6 11 5.1-1-5-4-10-11-11z" />
</svg>
</button>
)}
{onForward && (
<button className="ll-mail-read-btn" onClick={onForward} title="Forward">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M14 9V5l7 7-7 7v-4.1c-5 0-8.5 1.6-11 5.1 1-5 4-10 11-11z" />
</svg>
</button>
)}
{onDelete && (
<button className="ll-mail-read-btn ll-mail-read-btn-danger" onClick={onDelete} title="Delete">
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
</svg>
</button>
)}
{onStarToggle && (
<button
className={`ll-mail-read-btn ${message.isStarred ? 'll-mail-read-btn-starred' : ''}`}
onClick={onStarToggle}
title={message.isStarred ? 'Remove star' : 'Add star'}
>
<svg viewBox="0 0 24 24" width="18" height="18" fill="currentColor">
<path d="M12 17.27L18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z" />
</svg>
</button>
)}
</div>
</div>
<div className="ll-mail-read-subject">
<h2>{message.subject}</h2>
{message.labels && message.labels.length > 0 && (
<div className="ll-mail-read-labels">
{message.labels.map((label, index) => (
<span key={index} className="ll-mail-read-label">{label}</span>
))}
</div>
)}
</div>
<div className="ll-mail-read-meta">
<div className="ll-mail-read-avatar">
{message.from.avatar ? (
<img src={message.from.avatar} alt={message.from.name} />
) : (
<span className="ll-mail-read-avatar-placeholder">
{message.from.name.charAt(0).toUpperCase()}
</span>
)}
</div>
<div className="ll-mail-read-info">
<div className="ll-mail-read-sender">
<strong>{message.from.name}</strong>
<span>&lt;{message.from.email}&gt;</span>
</div>
<div className="ll-mail-read-recipients">
to {message.to?.map((r) => r.name).join(', ') || 'me'}
</div>
</div>
<div className="ll-mail-read-date">
{formatDate(message.date)}
</div>
</div>
<div className="ll-mail-read-body">
{message.body || message.preview}
</div>
{message.attachments && message.attachments.length > 0 && (
<div className="ll-mail-read-attachments">
<div className="ll-mail-read-attachments-header">
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M16.5 6v11.5c0 2.21-1.79 4-4 4s-4-1.79-4-4V5c0-1.38 1.12-2.5 2.5-2.5s2.5 1.12 2.5 2.5v10.5c0 .55-.45 1-1 1s-1-.45-1-1V6H10v9.5c0 1.38 1.12 2.5 2.5 2.5s2.5-1.12 2.5-2.5V5c0-2.21-1.79-4-4-4S7 2.79 7 5v12.5c0 3.04 2.46 5.5 5.5 5.5s5.5-2.46 5.5-5.5V6h-1.5z" />
</svg>
<span>{message.attachments.length} attachment{message.attachments.length > 1 ? 's' : ''}</span>
</div>
<div className="ll-mail-read-attachments-list">
{message.attachments.map((attachment) => (
<a
key={attachment.id}
href={attachment.url}
className="ll-mail-attachment"
download
>
<div className="ll-mail-attachment-icon">
<svg viewBox="0 0 24 24" width="24" height="24" fill="currentColor">
<path d="M14 2H6c-1.1 0-1.99.9-1.99 2L4 20c0 1.1.89 2 1.99 2H18c1.1 0 2-.9 2-2V8l-6-6zm2 16H8v-2h8v2zm0-4H8v-2h8v2zm-3-5V3.5L18.5 9H13z" />
</svg>
</div>
<div className="ll-mail-attachment-info">
<span className="ll-mail-attachment-name">{attachment.name}</span>
<span className="ll-mail-attachment-size">{attachment.size}</span>
</div>
</a>
))}
</div>
</div>
)}
</div>
);
};
// Mail Compose
export interface MailComposeProps {
/** Initial values */
initialTo?: string;
initialSubject?: string;
initialBody?: string;
/** Send handler */
onSend?: (data: { to: string; cc?: string; bcc?: string; subject: string; body: string }) => void;
/** Save draft handler */
onSaveDraft?: (data: { to: string; cc?: string; bcc?: string; subject: string; body: string }) => void;
/** Discard handler */
onDiscard?: () => void;
/** Close handler */
onClose?: () => void;
/** Show as modal */
isModal?: boolean;
/** Additional CSS classes */
className?: string;
}
export const MailCompose: React.FC<MailComposeProps> = ({
initialTo = '',
initialSubject = '',
initialBody = '',
onSend,
onSaveDraft,
onDiscard,
onClose,
isModal = false,
className = '',
}) => {
const [to, setTo] = useState(initialTo);
const [cc, setCc] = useState('');
const [bcc, setBcc] = useState('');
const [subject, setSubject] = useState(initialSubject);
const [body, setBody] = useState(initialBody);
const [showCc, setShowCc] = useState(false);
const [showBcc, setShowBcc] = useState(false);
const handleSend = () => {
onSend?.({ to, cc, bcc, subject, body });
};
const handleSaveDraft = () => {
onSaveDraft?.({ to, cc, bcc, subject, body });
};
return (
<div className={`ll-mail-compose ${isModal ? 'll-mail-compose-modal' : ''} ${className}`}>
<div className="ll-mail-compose-header">
<h3>New Message</h3>
{onClose && (
<button className="ll-mail-compose-close" onClick={onClose}>
<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor">
<path d="M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z" />
</svg>
</button>
)}
</div>
<div className="ll-mail-compose-form">
<div className="ll-mail-compose-field">
<label>To</label>
<div className="ll-mail-compose-input-row">
<input
type="email"
value={to}
onChange={(e) => setTo(e.target.value)}
placeholder="Recipients"
/>
<div className="ll-mail-compose-cc-toggle">
{!showCc && (
<button onClick={() => setShowCc(true)}>Cc</button>
)}
{!showBcc && (
<button onClick={() => setShowBcc(true)}>Bcc</button>
)}
</div>
</div>
</div>
{showCc && (
<div className="ll-mail-compose-field">
<label>Cc</label>
<input
type="email"
value={cc}
onChange={(e) => setCc(e.target.value)}
placeholder="Cc recipients"
/>
</div>
)}
{showBcc && (
<div className="ll-mail-compose-field">
<label>Bcc</label>
<input
type="email"
value={bcc}
onChange={(e) => setBcc(e.target.value)}
placeholder="Bcc recipients"
/>
</div>
)}
<div className="ll-mail-compose-field">
<label>Subject</label>
<input
type="text"
value={subject}
onChange={(e) => setSubject(e.target.value)}
placeholder="Subject"
/>
</div>
<div className="ll-mail-compose-body">
<textarea
value={body}
onChange={(e) => setBody(e.target.value)}
placeholder="Write your message..."
/>
</div>
</div>
<div className="ll-mail-compose-footer">
<div className="ll-mail-compose-actions-left">
<button className="ll-mail-compose-btn-primary" onClick={handleSend}>
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
</svg>
Send
</button>
{onSaveDraft && (
<button className="ll-mail-compose-btn" onClick={handleSaveDraft}>
Save Draft
</button>
)}
</div>
<div className="ll-mail-compose-actions-right">
{onDiscard && (
<button className="ll-mail-compose-btn ll-mail-compose-btn-danger" onClick={onDiscard}>
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
</svg>
</button>
)}
</div>
</div>
</div>
);
};
// Default Mail Folders
export const defaultMailFolders: MailFolder[] = [
{
id: 'inbox',
name: 'Inbox',
icon: (
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M19 3H4.99c-1.11 0-1.98.89-1.98 2L3 19c0 1.1.88 2 1.99 2H19c1.1 0 2-.9 2-2V5c0-1.11-.9-2-2-2zm0 12h-4c0 1.66-1.35 3-3 3s-3-1.34-3-3H4.99V5H19v10z" />
</svg>
),
},
{
id: 'sent',
name: 'Sent',
icon: (
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z" />
</svg>
),
},
{
id: 'drafts',
name: 'Drafts',
icon: (
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M21.99 8c0-.72-.37-1.35-.94-1.7L12 1 2.95 6.3C2.38 6.65 2 7.28 2 8v10c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2l-.01-10zM12 13L3.74 7.84 12 3l8.26 4.84L12 13z" />
</svg>
),
},
{
id: 'spam',
name: 'Spam',
icon: (
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z" />
</svg>
),
},
{
id: 'trash',
name: 'Trash',
icon: (
<svg viewBox="0 0 24 24" width="16" height="16" fill="currentColor">
<path d="M6 19c0 1.1.9 2 2 2h8c1.1 0 2-.9 2-2V7H6v12zM19 4h-3.5l-1-1h-5l-1 1H5v2h14V4z" />
</svg>
),
},
];
// Default Mail Labels
export const defaultMailLabels: MailLabel[] = [
{ id: 'work', name: 'Work', color: '#3b82f6' },
{ id: 'personal', name: 'Personal', color: '#10b981' },
{ id: 'important', name: 'Important', color: '#ef4444' },
{ id: 'social', name: 'Social', color: '#8b5cf6' },
];