import { useState, useEffect, useRef, useMemo } from 'react' import { useNavigate, useSearchParams } from 'react-router-dom' import { useAuth } from '@/context/AuthContext' import { Button } from '@/components/ui/button' import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card' import { Input } from '@/components/ui/input' import { Label } from '@/components/ui/label' import { Badge } from '@/components/ui/badge' import { Slider } from '@/components/ui/slider' import { Tabs, TabsList, TabsTrigger, TabsContent } from '@/components/ui/tabs' import { SidePanel, SidePanelContent, SidePanelHeader, SidePanelTitle, SidePanelDescription, SidePanelCloseButton, SidePanelBody, SidePanelFooter, } from '@/components/ui/side-panel' import { api } from '@/lib/api' import { Mail, User, Users, CreditCard, Shield, Settings as SettingsIcon, ArrowLeft, Plus, Trash2, Check, X, ExternalLink, Loader2, Crown, Star, Brain, Building2, Lock, Copy, AlertTriangle, Search, Download, Upload, ChevronDown, ChevronUp, RotateCcw, Play, Eye, Camera, Globe, Clock, Palette, Save, Edit2, } from 'lucide-react' import type { AIControlSettings, CompanyLabel, NameLabel, CategoryInfo, CleanupSettings, CleanupStatus, CategoryAdvanced } from '@/types/settings' import { PrivacySecurity } from '@/components/PrivacySecurity' type TabType = 'profile' | 'accounts' | 'vip' | 'ai-control' | 'name-labels' | 'subscription' | 'privacy' | 'referrals' const HEX_COLOR = /^#([0-9A-Fa-f]{6})$/ function validateLabelImport( imported: unknown, existing: CompanyLabel[] ): { labels: CompanyLabel[]; errors: string[] } { const errors: string[] = [] if (!Array.isArray(imported)) { return { labels: [], errors: ['File must contain a JSON array'] } } const seen = new Set() const labels: CompanyLabel[] = [] imported.forEach((row, i) => { const rowNum = i + 1 if (!row || typeof row !== 'object') { errors.push(`Row ${rowNum}: invalid object`) return } const r = row as Record const name = typeof r.name === 'string' ? r.name.trim() : '' if (!name) { errors.push(`Row ${rowNum}: name is required`) return } if (name.length > 50) { errors.push(`Row ${rowNum}: name must be at most 50 characters`) return } if (r.color != null && r.color !== '') { if (typeof r.color !== 'string' || !HEX_COLOR.test(r.color)) { errors.push(`Row ${rowNum}: color must be a valid #RRGGBB hex`) return } } const key = name.toLowerCase() if (seen.has(key)) { errors.push(`Row ${rowNum}: duplicate name "${name}" in import`) return } seen.add(key) if (existing.some((e) => e.name.trim().toLowerCase() === key)) { errors.push(`Row ${rowNum}: name "${name}" already exists`) return } labels.push({ id: typeof r.id === 'string' && r.id ? r.id : `label_import_${Date.now()}_${i}`, name, condition: typeof r.condition === 'string' ? r.condition : '', enabled: r.enabled !== false, category: typeof r.category === 'string' ? r.category : 'promotions', }) }) if (existing.length + labels.length > 100) { return { labels: [], errors: [`Cannot exceed 100 labels total (have ${existing.length}, importing ${labels.length})`], } } return { labels, errors } } interface EmailAccount { id: string email: string provider: 'gmail' | 'outlook' | 'imap' | 'demo' connected: boolean lastSync?: string isDemo?: boolean } interface VIPSender { email: string name?: string } interface Subscription { status: string plan: string planDisplayName?: string isFreeTier?: boolean currentPeriodEnd?: string cancelAtPeriodEnd?: boolean } function subscriptionTitle(sub: Subscription | null): string { if (!sub) return '' if (sub.planDisplayName) return sub.planDisplayName if (sub.plan === 'free' || sub.isFreeTier) return 'Free plan' if (sub.plan) return sub.plan.charAt(0).toUpperCase() + sub.plan.slice(1) return 'Subscription' } function subscriptionBadge(sub: Subscription | null): { label: string variant: 'success' | 'warning' | 'secondary' } { if (!sub) return { label: '', variant: 'secondary' } if (sub.isFreeTier) return { label: 'Free plan', variant: 'secondary' } if (sub.status === 'active') return { label: 'Active', variant: 'success' } const s = (sub.status || '').toLowerCase() if (s === 'trialing' || s === 'trial') return { label: 'Trial', variant: 'warning' } return { label: sub.status ? sub.status.charAt(0).toUpperCase() + sub.status.slice(1) : 'Inactive', variant: 'warning', } } export function Settings() { const { user } = useAuth() const navigate = useNavigate() const [searchParams, setSearchParams] = useSearchParams() const activeTab = (searchParams.get('tab') as TabType) || 'profile' const [loading, setLoading] = useState(false) const [saving, setSaving] = useState(false) const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null) const [name, setName] = useState(user?.name || '') const [email] = useState(user?.email || '') const [language, setLanguage] = useState('en') const [timezone, setTimezone] = useState(Intl.DateTimeFormat().resolvedOptions().timeZone) const [hasProfileChanges, setHasProfileChanges] = useState(false) const savedProfileRef = useRef<{ name: string; language: string; timezone: string } | null>(null) const [accounts, setAccounts] = useState([]) const [connectingProvider, setConnectingProvider] = useState(null) const [showImapForm, setShowImapForm] = useState(false) const [imapForm, setImapForm] = useState({ email: '', password: '', imapHost: 'imap.porkbun.com', imapPort: 993, imapSecure: true }) const [imapConnecting, setImapConnecting] = useState(false) const [vipSenders, setVipSenders] = useState([]) const [newVipEmail, setNewVipEmail] = useState('') const [subscription, setSubscription] = useState(null) // AI Control state const [aiControlSettings, setAiControlSettings] = useState({ enabledCategories: [], categoryActions: {}, autoDetectCompanies: true, cleanup: { enabled: false, readItems: { enabled: false, action: 'archive_read', gracePeriodDays: 7, }, promotions: { enabled: false, matchCategoriesOrLabels: ['promotions', 'newsletters'], action: 'archive_read', deleteAfterDays: 30, }, safety: { requireConfirmForDelete: true, }, }, }) const [categories, setCategories] = useState([]) const [companyLabels, setCompanyLabels] = useState([]) const [labelImportErrors, setLabelImportErrors] = useState([]) const [isAdmin, setIsAdmin] = useState(false) const [nameLabels, setNameLabels] = useState([]) const [editingNameLabel, setEditingNameLabel] = useState(null) const [showNameLabelPanel, setShowNameLabelPanel] = useState(false) const [referralData, setReferralData] = useState<{ referralCode: string; referralCount: number } | null>(null) const [loadingReferral, setLoadingReferral] = useState(false) // Control Panel Sub-Tabs const [controlPanelTab, setControlPanelTab] = useState<'rules' | 'cleanup' | 'labels'>('rules') // Unsaved changes tracking const [hasUnsavedChanges, setHasUnsavedChanges] = useState(false) const savedSettingsRef = useRef(null) // Search/Filter state const [searchQuery, setSearchQuery] = useState('') const [filterEnabled, setFilterEnabled] = useState<'all' | 'enabled' | 'disabled'>('all') const [labelSort, setLabelSort] = useState<'name' | 'newest'>('name') // Cleanup status const [cleanupStatus, setCleanupStatus] = useState(null) const [cleanupPreview, setCleanupPreview] = useState([]) const [runningCleanup, setRunningCleanup] = useState(false) // Label editor const [editingLabel, setEditingLabel] = useState(null) // Advanced options expanded state const [expandedCategories, setExpandedCategories] = useState>(new Set()) // Side Panel state for category details const [selectedCategory, setSelectedCategory] = useState(null) const [showCategoryPanel, setShowCategoryPanel] = useState(false) // Side Panel state for label editor const [showLabelPanel, setShowLabelPanel] = useState(false) useEffect(() => { loadData() if (user?.$id) { loadReferralData() } }, [user]) // Refetch subscription when opening this tab (fixes JWT timing vs initial loadData) useEffect(() => { if (activeTab !== 'subscription' || !user?.$id) return let cancelled = false ;(async () => { const res = await api.getSubscriptionStatus() if (!cancelled && res.data) setSubscription(res.data) })() return () => { cancelled = true } }, [activeTab, user?.$id]) const loadReferralData = async () => { if (!user?.$id) return setLoadingReferral(true) try { const res = await api.getReferralCode() if (res.data) setReferralData(res.data) } catch (err) { console.error('Failed to load referral data:', err) } finally { setLoadingReferral(false) } } const loadData = async () => { if (!user?.$id) return setLoading(true) try { const [accountsRes, subsRes, prefsRes, aiControlRes, companyLabelsRes, meRes] = await Promise.all([ api.getEmailAccounts(), api.getSubscriptionStatus(), api.getUserPreferences(), api.getAIControlSettings(), api.getCompanyLabels(), user?.$id ? api.getMe() : Promise.resolve({ data: { isAdmin: false } }), ]) if (accountsRes.data) setAccounts(accountsRes.data) if (subsRes.data) setSubscription(subsRes.data) if (meRes.data?.isAdmin) { setIsAdmin(true) const nameLabelsRes = await api.getNameLabels() if (nameLabelsRes.data) setNameLabels(nameLabelsRes.data) } else { setIsAdmin(false) } if (prefsRes.data?.vipSenders) setVipSenders(prefsRes.data.vipSenders) const pdata = prefsRes.data as { profile?: { displayName?: string; timezone?: string; notificationPrefs?: { language?: string } } } | undefined if (pdata?.profile) { const prof = pdata.profile if (prof.displayName != null && prof.displayName !== '') setName(prof.displayName) if (prof.timezone) setTimezone(prof.timezone) if (prof.notificationPrefs?.language) setLanguage(String(prof.notificationPrefs.language)) } if (aiControlRes.data) { // Merge cleanup defaults if not present const raw = aiControlRes.data const defaultCleanup: CleanupSettings = { enabled: false, readItems: { enabled: false, action: 'archive_read', gracePeriodDays: 7, }, promotions: { enabled: false, matchCategoriesOrLabels: ['promotions', 'newsletters'], action: 'archive_read', deleteAfterDays: 30, }, safety: { requireConfirmForDelete: true, dryRun: false, maxDeletesPerRun: 100, }, } const settings: AIControlSettings = { ...raw, cleanup: (raw.cleanup as CleanupSettings | undefined) || defaultCleanup, categoryAdvanced: (raw.categoryAdvanced as Record | undefined) || {}, version: raw.version ?? 1, } setAiControlSettings(settings) savedSettingsRef.current = JSON.parse(JSON.stringify(settings)) // Deep copy setHasUnsavedChanges(false) } if (companyLabelsRes.data) setCompanyLabels(companyLabelsRes.data) // Load categories from API or use defaults const categoryList: CategoryInfo[] = [ { key: 'vip', name: 'Important', description: 'Important emails from known contacts', defaultAction: 'star', color: '#ff0000', enabled: true }, { key: 'customers', name: 'Clients', description: 'Emails from clients and projects', defaultAction: 'inbox', color: '#4285f4', enabled: true }, { key: 'invoices', name: 'Invoices', description: 'Invoices, receipts and financial documents', defaultAction: 'inbox', color: '#0f9d58', enabled: true }, { key: 'newsletters', name: 'Newsletter', description: 'Regular newsletters and updates', defaultAction: 'archive_read', color: '#9c27b0', enabled: true }, { key: 'promotions', name: 'Promotions', description: 'Marketing emails and promotions', defaultAction: 'archive_read', color: '#ff9800', enabled: true }, { key: 'social', name: 'Social', description: 'Social media and platform notifications', defaultAction: 'archive_read', color: '#00bcd4', enabled: true }, { key: 'security', name: 'Security', description: 'Security codes and notifications', defaultAction: 'inbox', color: '#f44336', enabled: true }, { key: 'calendar', name: 'Calendar', description: 'Calendar invites and events', defaultAction: 'inbox', color: '#673ab7', enabled: true }, { key: 'review', name: 'Review', description: 'Emails that need manual review', defaultAction: 'inbox', color: '#607d8b', enabled: true }, ] // Update enabled status from settings const enabledCategories = aiControlRes.data?.enabledCategories || categoryList.map(c => c.key) const updatedCategories = categoryList.map(cat => ({ ...cat, enabled: enabledCategories.includes(cat.key), })) setCategories(updatedCategories) } catch (error) { console.error('Failed to load settings data:', error) } finally { setLoading(false) } } const setTab = (tab: TabType) => { setSearchParams({ tab }) setMessage(null) } const showMessage = (type: 'success' | 'error', text: string) => { setMessage({ type, text }) setTimeout(() => setMessage(null), 5000) } // Track changes for Control Panel useEffect(() => { if (activeTab === 'ai-control' && savedSettingsRef.current) { const current = JSON.stringify(aiControlSettings) const saved = JSON.stringify(savedSettingsRef.current) setHasUnsavedChanges(current !== saved) } }, [aiControlSettings, activeTab]) // Save Control Panel settings const handleSaveControlPanel = async () => { if (!user?.$id) return setSaving(true) try { await api.saveAIControlSettings({ enabledCategories: aiControlSettings.enabledCategories, categoryActions: aiControlSettings.categoryActions, autoDetectCompanies: aiControlSettings.autoDetectCompanies, cleanup: aiControlSettings.cleanup, categoryAdvanced: aiControlSettings.categoryAdvanced, version: aiControlSettings.version || 1, }) savedSettingsRef.current = JSON.parse(JSON.stringify(aiControlSettings)) setHasUnsavedChanges(false) showMessage('success', 'Control Panel settings saved!') } catch { showMessage('error', 'Failed to save settings') } finally { setSaving(false) } } // Discard changes const handleDiscardChanges = () => { if (savedSettingsRef.current) { setAiControlSettings(JSON.parse(JSON.stringify(savedSettingsRef.current))) setHasUnsavedChanges(false) showMessage('success', 'Changes discarded') } } // Load cleanup status const loadCleanupStatus = async () => { const aid = accounts.find((a) => a.provider !== 'demo')?.id if (!aid) return try { const res = await api.getCleanupStatus(aid) if (res.data) setCleanupStatus(res.data) } catch { console.debug('Cleanup status endpoint not available') } } // Load cleanup preview const loadCleanupPreview = async () => { if (!aiControlSettings.cleanup?.enabled) return const aid = accounts.find((a) => a.provider !== 'demo')?.id if (!aid) return try { const res = await api.getCleanupPreview(aid) if (res.data?.messages) setCleanupPreview(res.data.messages) } catch { console.debug('Cleanup preview endpoint not available') } } // Load cleanup status when cleanup tab is active useEffect(() => { if (activeTab === 'ai-control' && controlPanelTab === 'cleanup' && aiControlSettings.cleanup?.enabled) { loadCleanupStatus() if (aiControlSettings.cleanup.safety.dryRun) { loadCleanupPreview() } } }, [activeTab, controlPanelTab, aiControlSettings.cleanup?.enabled, aiControlSettings.cleanup?.safety.dryRun, accounts]) // Run cleanup now const handleRunCleanup = async () => { if (!user?.$id) return setRunningCleanup(true) try { const res = await api.runCleanup() if (res.data) { showMessage('success', `Cleanup completed: ${res.data.emailsProcessed.readItems + res.data.emailsProcessed.promotions} emails processed`) await loadCleanupStatus() } } catch { showMessage('error', 'Failed to run cleanup') } finally { setRunningCleanup(false) } } // Filtered and sorted labels const filteredLabels = useMemo(() => { let filtered = companyLabels // Search filter if (searchQuery) { const query = searchQuery.toLowerCase() filtered = filtered.filter(l => l.name.toLowerCase().includes(query) || l.condition.toLowerCase().includes(query) ) } // Enabled/Disabled filter if (filterEnabled !== 'all') { filtered = filtered.filter(l => filterEnabled === 'enabled' ? l.enabled : !l.enabled ) } // Sort if (labelSort === 'name') { filtered = [...filtered].sort((a, b) => a.name.localeCompare(b.name)) } else if (labelSort === 'newest') { // Reverse order (assuming newer labels are added at the end) filtered = [...filtered].reverse() } return filtered }, [companyLabels, searchQuery, filterEnabled, labelSort]) // Filtered categories const filteredCategories = useMemo(() => { let filtered = categories if (searchQuery) { const query = searchQuery.toLowerCase() filtered = filtered.filter(c => c.name.toLowerCase().includes(query) || c.description.toLowerCase().includes(query) || c.key.toLowerCase().includes(query) ) } if (filterEnabled !== 'all') { filtered = filtered.filter(c => filterEnabled === 'enabled' ? c.enabled : !c.enabled ) } return filtered }, [categories, searchQuery, filterEnabled]) const handleSaveProfile = async () => { if (!user?.$id) return setSaving(true) try { const res = await api.updateProfile({ displayName: name, timezone, notificationPrefs: { language }, }) if (res.error) { showMessage('error', res.error.message || 'Failed to save profile') return } savedProfileRef.current = { name, language, timezone } setHasProfileChanges(false) showMessage('success', 'Profile saved successfully!') } catch { showMessage('error', 'Failed to save profile') } finally { setSaving(false) } } // Track profile changes useEffect(() => { if (savedProfileRef.current) { const hasChanges = name !== savedProfileRef.current.name || language !== savedProfileRef.current.language || timezone !== savedProfileRef.current.timezone setHasProfileChanges(hasChanges) } }, [name, language, timezone]) // Initialize saved profile ref useEffect(() => { if (user && !savedProfileRef.current) { savedProfileRef.current = { name: user.name || '', language: 'en', timezone: Intl.DateTimeFormat().resolvedOptions().timeZone } setName(user.name || '') } }, [user]) const handleConnectAccount = async (provider: 'gmail' | 'outlook') => { if (!user?.$id) return setConnectingProvider(provider) try { const res = await api.getOAuthUrl(provider) if (res.data?.url) { window.location.href = res.data.url } } catch { showMessage('error', `Failed to connect ${provider}`) setConnectingProvider(null) } } const handleDisconnectAccount = async (accountId: string) => { if (!user?.$id) return try { await api.disconnectEmailAccount(accountId) setAccounts(accounts.filter(a => a.id !== accountId)) showMessage('success', 'Account disconnected') } catch { showMessage('error', 'Failed to disconnect') } } const handleConnectImap = async (e: React.FormEvent) => { e.preventDefault() if (!user?.$id || !imapForm.email.trim() || !imapForm.password) return setImapConnecting(true) const res = await api.connectImapAccount({ email: imapForm.email.trim(), password: imapForm.password, imapHost: imapForm.imapHost || undefined, imapPort: imapForm.imapPort || 993, imapSecure: imapForm.imapSecure, }) if (res.error) { const msg = res.error.message || 'Connection failed' showMessage('error', msg.includes('credentials') || msg.includes('auth') || msg.includes('password') ? 'Login failed – check email and password' : msg) setImapConnecting(false) return } const list = await api.getEmailAccounts() setAccounts(list.data ?? []) setShowImapForm(false) setImapForm({ email: '', password: '', imapHost: 'imap.porkbun.com', imapPort: 993, imapSecure: true }) showMessage('success', 'IMAP account connected') setImapConnecting(false) } const handleAddVip = () => { if (!newVipEmail.trim() || !newVipEmail.includes('@')) return if (vipSenders.some(v => v.email === newVipEmail)) { showMessage('error', 'This email is already in the VIP list') return } setVipSenders([...vipSenders, { email: newVipEmail }]) setNewVipEmail('') showMessage('success', 'VIP added') } const handleRemoveVip = (email: string) => { setVipSenders(vipSenders.filter(v => v.email !== email)) } const handleSaveVips = async () => { if (!user?.$id) return setSaving(true) try { await api.saveUserPreferences({ vipSenders }) showMessage('success', 'VIP list saved!') } catch { showMessage('error', 'Failed to save') } finally { setSaving(false) } } const handleManageSubscription = async () => { if (!user?.$id) return try { const res = await api.createPortalSession() if (res.data?.url) { window.location.href = res.data.url } } catch { showMessage('error', 'Failed to open customer portal') } } const handleUpgrade = async (plan: string) => { if (!user?.$id) return try { const res = await api.createSubscriptionCheckout(plan, user.email) if (res.data?.url) { window.location.href = res.data.url } } catch { showMessage('error', 'Failed to start checkout') } } const tabs = useMemo(() => { const base = [ { id: 'profile' as TabType, label: 'Profile', icon: User }, { id: 'accounts' as TabType, label: 'Email Accounts', icon: Mail }, { id: 'vip' as TabType, label: 'VIP List', icon: Star }, { id: 'ai-control' as TabType, label: 'Control Panel', icon: Brain }, ...(isAdmin ? [{ id: 'name-labels' as TabType, label: 'Name Labels (Team)', icon: Users }] : []), { id: 'subscription' as TabType, label: 'Subscription', icon: CreditCard }, { id: 'privacy' as TabType, label: 'Privacy & Security', icon: Lock }, ] return base }, [isAdmin]) return (

Settings

{message && (
{message.type === 'success' ? : } {message.text}
)}
{loading ? (
) : ( <> {activeTab === 'profile' && (
{/* Header with Save Button */}

General Settings

Manage your account and preferences

{hasProfileChanges && ( )}
{/* Profile Section */}
Profile Information
Your personal account details
{/* Avatar Section */}
{name?.charAt(0)?.toUpperCase() || email?.charAt(0)?.toUpperCase() || 'U'}

{name || 'User'}

{email}

Member since {user?.$createdAt ? new Date(user.$createdAt).toLocaleDateString('en-US', { month: 'long', year: 'numeric' }) : 'recently'}

setName(e.target.value)} placeholder="Enter your name" className="mt-2" />

This is how your name appears to others

Email cannot be changed

{/* Preferences Section */}
Preferences
Customize your experience

Choose your preferred language

Set your local timezone

{/* Account Information */}
Account Information
Your account details and security

Account Status

Active and verified

Active

Email Verification

{email}

Verified

Account Created

{user?.$createdAt ? new Date(user.$createdAt).toLocaleDateString('en-US', { year: 'numeric', month: 'long', day: 'numeric' }) : 'Unknown'}

{/* Sticky Save Bar */} {hasProfileChanges && (
You have unsaved changes
)}
)} {activeTab === 'accounts' && (
Connected Email Accounts Connect your email accounts for automatic sorting {accounts.length > 0 ? ( accounts.map((account) => (

{account.email}

{account.provider === 'imap' ? 'IMAP' : account.provider}

{account.connected ? 'Connected' : 'Disconnected'}
)) ) : (

No email accounts connected yet

)}
Add Account Connect a new email account
{showImapForm && (
setImapForm((f) => ({ ...f, email: e.target.value }))} required autoComplete="email" className="bg-white dark:bg-slate-900" />
setImapForm((f) => ({ ...f, password: e.target.value }))} required autoComplete="current-password" className="bg-white dark:bg-slate-900" />
Advanced (host, port, SSL)
setImapForm((f) => ({ ...f, imapHost: e.target.value }))} className="bg-white dark:bg-slate-900 text-sm" />
setImapForm((f) => ({ ...f, imapPort: Number(e.target.value) || 993 }))} className="bg-white dark:bg-slate-900 text-sm" />
)}
)} {activeTab === 'vip' && ( VIP List Emails from these senders will always be marked as important
setNewVipEmail(e.target.value)} onKeyDown={(e) => e.key === 'Enter' && handleAddVip()} />
{vipSenders.length > 0 ? ( vipSenders.map((vip) => (
{vip.email}
)) ) : (

No VIP senders added yet

)}
{vipSenders.length > 0 && ( )}
)} {activeTab === 'ai-control' && (
{/* Header */}

Control Panel

Configure how AI processes your emails and manages your inbox

{/* Sticky Save Bar */} {hasUnsavedChanges && (
Unsaved changes
)} {/* Search/Filter Bar */}
setSearchQuery(e.target.value)} className="pl-10" />
{/* Tabs */} setControlPanelTab((v || 'rules') as 'rules' | 'cleanup' | 'labels')} className="w-full"> Rules Cleanup Labels {/* Rules Tab */} {/* Bulk Actions */}

Category Rules

Configure how AI handles different email types

{/* Category Cards Grid */}
{filteredCategories.map((category) => { const action = aiControlSettings.categoryActions[category.key] || category.defaultAction const actionLabels: Record = { inbox: 'Keep in Inbox', archive_read: 'Archive & Read', star: 'Star' } return ( { setSelectedCategory(category) setShowCategoryPanel(true) }} >

{category.name}

{category.enabled ? 'Active' : 'Inactive'}

{category.description}

{category.enabled && (
Action {actionLabels[action]}
)} {category.enabled && !aiControlSettings.categoryActions[category.key] && (
Action required
)} ) })}
{/* Cleanup Tab */} {/* Auto Cleanup Read Emails - Large Toggle Card */}

Auto cleanup read emails

Automatically clean up emails that have been read after a grace period

{aiControlSettings.cleanup?.readItems.enabled && (
{/* Action Selection */}
{/* Slider for Days */}
{aiControlSettings.cleanup.readItems.gracePeriodDays} {aiControlSettings.cleanup.readItems.gracePeriodDays === 1 ? 'day' : 'days'}
{ if (!aiControlSettings.cleanup) return setAiControlSettings({ ...aiControlSettings, cleanup: { ...aiControlSettings.cleanup, readItems: { ...aiControlSettings.cleanup.readItems, gracePeriodDays: value, }, }, }) }} min={0} max={90} step={1} />

Emails will be cleaned up after this many days

{/* Warning Banner */} {aiControlSettings.cleanup.readItems.action === 'trash' && (

Delete action selected

Emails moved to trash can be permanently deleted. Use with caution.

)}
)}
{/* Delete Promotions After X Days - Large Toggle Card */}

Delete promotions after X days

Automatically clean up promotional emails and newsletters after a set period

{aiControlSettings.cleanup?.promotions.enabled && (
{/* Action Selection */}
{/* Preset Buttons */}
{[7, 14, 30].map((days) => ( ))}
{ if (!aiControlSettings.cleanup) return setAiControlSettings({ ...aiControlSettings, cleanup: { ...aiControlSettings.cleanup, promotions: { ...aiControlSettings.cleanup.promotions, deleteAfterDays: value, }, }, }) }} min={1} max={90} step={1} className="flex-1 mr-4" /> {aiControlSettings.cleanup.promotions.deleteAfterDays} {aiControlSettings.cleanup.promotions.deleteAfterDays === 1 ? 'day' : 'days'}
{/* Match Categories */}
{categories.filter(c => ['promotions', 'newsletters', 'social'].includes(c.key)).map((category) => ( ))}
{/* Warning Banner */} {aiControlSettings.cleanup.promotions.action === 'trash' && (

Delete action selected

Promotional emails will be permanently deleted. Use with caution.

)}
)}
{/* Preview Section */} {((cleanupPreview && cleanupPreview.length > 0) || cleanupStatus) && (

Preview

{(cleanupPreview && cleanupPreview.length > 0) ? `${cleanupPreview.length} emails affected in the last 7 days` : cleanupStatus?.lastRun ? `Last run: ${new Date(cleanupStatus.lastRun).toLocaleDateString()}` : 'No preview data available'}

{cleanupPreview && cleanupPreview.length > 0 && (
{cleanupPreview.slice(0, 10).map((item) => (

{item.subject}

{item.from} • {new Date(item.date).toLocaleDateString()}

{item.reason}
))}
)}
)} {/* Run Cleanup Now */}

Run Cleanup Now

Manually trigger cleanup for your account

{cleanupStatus?.lastRun && (

Last run: {new Date(cleanupStatus.lastRun).toLocaleString()} {cleanupStatus.lastRunCounts && ( ({cleanupStatus.lastRunCounts.readItems + cleanupStatus.lastRunCounts.promotions} emails processed) )}

)}
{/* Labels Tab */} {/* Header with Actions */}

Company Labels

Automatically label emails from specific companies

{labelImportErrors.length > 0 && (

Import issues

    {labelImportErrors.map((err, i) => (
  • {err}
  • ))}
)}
{/* Auto-Detection Toggle */}

Auto-Detect Known Companies

Automatically detect and label emails from Amazon, Google, Microsoft, etc.

{/* Labels Table */} {filteredLabels.length > 0 ? (
{filteredLabels.map((label) => ( ))}
Name Status Category Actions

{label.name}

{label.condition}

{label.enabled ? 'Enabled' : 'Disabled'} {label.category && ( {label.category} )}
{label.enabled ? 'Enabled' : 'Disabled'} {label.category && ( {label.category} )}
) : (

{searchQuery ? 'No labels match your search' : 'No labels yet'}

{searchQuery ? 'Try adjusting your search query' : 'Create your first company label to get started'}

{!searchQuery && ( )}
)}
{/* Category Details Side Panel */} {selectedCategory && ( <>
{selectedCategory.name}
{selectedCategory.description} )} {selectedCategory && (
{/* Enable/Disable Toggle */}

Process emails in this category

{/* Action Selection */} {selectedCategory.enabled && (
{!aiControlSettings.categoryActions[selectedCategory.key] && (
Please select an action
)}
)} {/* Advanced Options */} {selectedCategory.enabled && (
{expandedCategories.has(selectedCategory.key) && (
{ const keywords = e.target.value.split(',').map(k => k.trim()).filter(k => k) const newAdvanced = { ...aiControlSettings.categoryAdvanced } if (!newAdvanced[selectedCategory.key]) newAdvanced[selectedCategory.key] = {} newAdvanced[selectedCategory.key] = { ...newAdvanced[selectedCategory.key], excludeKeywords: keywords } setAiControlSettings({ ...aiControlSettings, categoryAdvanced: newAdvanced }) }} placeholder="spam, test, noreply" className="mt-1" />

Comma-separated keywords to exclude

)}
)}
)} {/* Label Editor Side Panel */} {editingLabel?.id ? 'Edit Label' : 'Add New Label'} {editingLabel?.id ? 'Update the company label settings' : 'Create a new company label to automatically categorize emails'} {editingLabel && (
{/* Company Name */}
setEditingLabel({ ...editingLabel, name: e.target.value })} className="w-full" />
{/* Condition */}
setEditingLabel({ ...editingLabel, condition: e.target.value })} className="w-full font-mono text-sm" />

Use "from:domain.com" or "subject:keyword" syntax

{/* Category */}
{/* Enabled Toggle */}

This label will be active and applied to matching emails

)}
)} {activeTab === 'name-labels' && isAdmin && (
Name Labels (Team)
Personal labels for each team member. The AI will assign emails to a worker when they are clearly for that person (e.g. "für Max", "an Anna", subject/body mentions).
{nameLabels.length > 0 ? (
{nameLabels.map((label) => (

{label.name}

{label.email && (

{label.email}

)} {label.keywords?.length ? (

Keywords: {label.keywords.join(', ')}

) : null}
))}
) : (

No name labels yet

Add team members so the AI can assign emails to the right person

)}
{/* Name Label Editor Side Panel */} {editingNameLabel?.id ? 'Edit Name Label' : 'Add Team Member'} {editingNameLabel?.id ? 'Update the name label' : 'Add a team member. The AI will assign emails to this person when they are clearly for them (e.g. "für Max", subject mentions).'} {editingNameLabel && (
setEditingNameLabel({ ...editingNameLabel, name: e.target.value })} className="mt-2" />
setEditingNameLabel({ ...editingNameLabel, email: e.target.value || undefined })} className="mt-2" />
setEditingNameLabel({ ...editingNameLabel, keywords: e.target.value.split(',').map(k => k.trim()).filter(Boolean), })} className="mt-2" />

Hints for the AI to recognize emails for this person

This label will be used when sorting

)}
)} {activeTab === 'referrals' && ( Referrals Share MailFlow and earn rewards {loadingReferral ? (
) : (

Your referral code

{referralData?.referralCode || 'USER-XXXXXX'}

People referred

{referralData?.referralCount || 0}

)}
)} {activeTab === 'privacy' && ( Privacy & Security Understand what data we access, what we store, and how to manage your privacy ({ id: a.id, email: a.email, provider: a.provider }))} onDisconnect={async (accountId) => { if (!user?.$id) return try { const result = await api.disconnectEmailAccount(accountId) if (result.data) { setAccounts(accounts.filter(a => a.id !== accountId)) showMessage('success', 'Account disconnected') } } catch { showMessage('error', 'Failed to disconnect account') } }} onDeleteAccount={async () => { if (!user?.$id) return if (!confirm('Are you absolutely sure? This cannot be undone.')) return try { const result = await api.deleteAccount() if (result.data) { showMessage('success', 'Account deleted. Redirecting...') setTimeout(() => { window.location.href = '/' }, 2000) } } catch { showMessage('error', 'Failed to delete account') } }} /> )} {activeTab === 'subscription' && (
Current Subscription Manage your MailFlow subscription {loading ? (

Loading subscription…

) : !subscription ? (

Subscription status could not be loaded. Make sure you are signed in and the API is running.

) : (

{subscriptionTitle(subscription)}

{(() => { const b = subscriptionBadge(subscription) return b.label ? ( {b.label} ) : null })()}
{subscription.currentPeriodEnd && (

Next billing:{' '} {new Date(subscription.currentPeriodEnd).toLocaleDateString('en-US')}

)}
)}
Available Plans Choose the plan that fits you
{[ { id: 'basic', name: 'Basic', price: '9', features: ['1 email account', '500 emails/day', 'Standard support'] }, { id: 'pro', name: 'Pro', price: '19', features: ['3 email accounts', 'Unlimited emails', 'Historical sorting', 'Priority support'], popular: true }, { id: 'business', name: 'Business', price: '49', features: ['10 email accounts', 'Unlimited emails', 'Team features', 'API access', '24/7 support'] }, ].map((plan) => (
{plan.popular && (
Popular
)}

{plan.name}

${plan.price} /month
    {plan.features.map((feature, fi) => (
  • {feature}
  • ))}
))}
)} )}
) }