'use client'; import { AuthGuard } from '@/components/AuthGuard'; import { DashboardLayout } from '@/components/DashboardLayout'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; import { mailAccountsApi, MailAccount } from '@/lib/api'; import { Plus, Edit2, Trash2, CheckCircle, XCircle, AlertTriangle, Power, RefreshCw } from 'lucide-react'; import { useState } from 'react'; import Image from 'next/image'; import { AddMailAccountModal } from '@/components/AddMailAccountModal'; // Map provider_name (from backend) to the SVG icon filename in /public/providers/ const PROVIDER_ICON_MAP: Record = { 'Gmail': 'gmail', 'GMX': 'gmx', 'WEB.DE': 'webde', 'Outlook / Hotmail': 'outlook', 'Yahoo Mail': 'yahoo', 'AOL Mail': 'aol', 'T-Online': 'tonline', '1&1 / IONOS': 'ionos', 'Freenet': 'freenet', 'Posteo': 'posteo', 'mail.de': 'mailde', 'iCloud Mail': 'icloud', 'Proton Mail': 'protonmail', }; // Fallback: map email domains to SVG icon filenames for accounts without a provider_name const DOMAIN_ICON_MAP: Record = { // Gmail 'gmail.com': 'gmail', 'googlemail.com': 'gmail', // GMX 'gmx.de': 'gmx', 'gmx.net': 'gmx', 'gmx.at': 'gmx', 'gmx.ch': 'gmx', 'gmx.com': 'gmx', // WEB.DE 'web.de': 'webde', // Outlook / Hotmail 'outlook.com': 'outlook', 'hotmail.com': 'outlook', 'live.com': 'outlook', 'msn.com': 'outlook', 'outlook.de': 'outlook', // Yahoo Mail 'yahoo.com': 'yahoo', 'yahoo.de': 'yahoo', 'yahoo.co.uk': 'yahoo', 'ymail.com': 'yahoo', // AOL Mail 'aol.com': 'aol', 'aim.com': 'aol', // T-Online 't-online.de': 'tonline', // 1&1 / IONOS 'online.de': 'ionos', 'onlinehome.de': 'ionos', '1und1.de': 'ionos', // Freenet 'freenet.de': 'freenet', // iCloud Mail 'icloud.com': 'icloud', 'me.com': 'icloud', 'mac.com': 'icloud', // Posteo 'posteo.de': 'posteo', 'posteo.net': 'posteo', // Proton Mail 'proton.me': 'protonmail', 'protonmail.com': 'protonmail', 'protonmail.ch': 'protonmail', 'pm.me': 'protonmail', }; /** * Full-width logo banner rendered at the top of a card. * Uses next/image fill + object-contain so every logo – regardless of its * native aspect ratio (1:1 square up to ~6:1 wordmark) – fits correctly * inside the fixed-height strip without distortion. * * Resolves the icon by first checking provider_name, then falling back to * the email domain so that accounts created without a provider_name still * show the correct logo. */ function ProviderLogoBanner({ providerName, email }: { providerName?: string | null; email?: string | null }) { let icon = providerName ? PROVIDER_ICON_MAP[providerName] : undefined; if (!icon && email) { const atIndex = email.lastIndexOf('@'); const domain = atIndex !== -1 ? email.slice(atIndex + 1).toLowerCase() : undefined; if (domain) icon = DOMAIN_ICON_MAP[domain]; } if (!icon) return null; const label = providerName ?? email?.split('@')[1] ?? 'provider'; return (
{`${label}
); } export default function AccountsPage() { const [isModalOpen, setIsModalOpen] = useState(false); const [editingAccount, setEditingAccount] = useState(null); const [pullingIds, setPullingIds] = useState>(new Set()); const [successIds, setSuccessIds] = useState>(new Set()); const queryClient = useQueryClient(); const { data: accounts, isLoading } = useQuery({ queryKey: ['mail-accounts'], queryFn: mailAccountsApi.list, }); const deleteMutation = useMutation({ mutationFn: mailAccountsApi.delete, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['mail-accounts'] }); }, }); const toggleMutation = useMutation({ mutationFn: mailAccountsApi.toggle, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['mail-accounts'] }); }, }); const handleEdit = (account: MailAccount) => { setEditingAccount(account); setIsModalOpen(true); }; const handleDelete = async (id: number) => { if (confirm('Are you sure you want to delete this mail account?')) { try { await deleteMutation.mutateAsync(id); } catch { alert('Failed to delete account'); } } }; const handleToggle = async (id: number) => { try { await toggleMutation.mutateAsync(id); } catch { alert('Failed to update account'); } }; const handlePullNow = async (id: number) => { setPullingIds((prev) => new Set(prev).add(id)); try { await mailAccountsApi.pullNow(id); setSuccessIds((prev) => new Set(prev).add(id)); setTimeout(() => { setSuccessIds((prev) => { const next = new Set(prev); next.delete(id); return next; }); }, 2000); } catch { alert('Failed to queue pull'); } finally { setPullingIds((prev) => { const next = new Set(prev); next.delete(id); return next; }); } }; const handleCloseModal = () => { setIsModalOpen(false); setEditingAccount(null); }; return (

Mail Accounts

{isLoading ? (
) : accounts && accounts.length > 0 ? (
{accounts.map((account) => (
{/* Provider logo banner – full-width strip that accommodates any aspect ratio */}

{account.name}

{account.email_address}

{account.is_enabled ? ( Enabled ) : ( Disabled )}
Protocol: {account.protocol.toUpperCase()}
Host: {account.host}:{account.port}
SSL: {account.use_ssl ? 'Yes' : 'No'}
Interval: Every {account.check_interval_minutes} min
{account.last_check_at && (
Last checked: {new Date(account.last_check_at).toLocaleString()}
)} {account.last_error_message && (

{account.last_error_message}

)}
))}
) : (

No mail accounts configured yet

)}
{isModalOpen && ( )}
); }