"use client"; import { useState, useRef, useCallback, useMemo } from 'react'; import { useTranslations } from 'next-intl'; import { Check, GripVertical, Plus, Star, AlertCircle, ChevronRight } from 'lucide-react'; import { useAuthStore } from '@/stores/auth-store'; import { useEmailStore } from '@/stores/email-store'; import { useAccountStore, type AccountEntry } from '@/stores/account-store'; import { useManagedAccountStore } from '@/stores/managed-account-store'; import type { SharedAccount } from '@/lib/jmap/types'; import { SettingsSection, SettingItem } from './settings-section'; import { Avatar } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { useRouter } from '@/i18n/navigation'; import { getMaxAccounts } from '@/lib/account-utils'; import { formatFileSize, cn } from '@/lib/utils'; function hostnameOf(serverUrl: string): string { try { return new URL(serverUrl).hostname; } catch { return serverUrl; } } // First scoped settings tab to land on for a shared account, by capability. // Mirrors the scoped-tab gating in the settings page. null = nothing editable. function firstScopedTab(caps: SharedAccount['capabilities']): string | null { if (caps.sieve) return 'filters'; if (caps.mail) return 'vacation'; if (caps.calendars) return 'calendar'; if (caps.contacts) return 'contacts'; return null; } export function AccountSettings() { const t = useTranslations('settings.account'); const router = useRouter(); const { username, serverUrl, isDemoMode, primaryIdentity, authMode, client } = useAuthStore(); const activeAccountId = useAuthStore((s) => s.activeAccountId); const switchAccount = useAuthStore((s) => s.switchAccount); const setManagedAccount = useManagedAccountStore((s) => s.setManagedAccount); // Shared/group accounts delegated to this session (excludes the user's own // primary account). These can be drilled into for scoped settings editing. const sharedAccounts = useMemo( () => (client?.getSharedAccounts() ?? []).filter((a) => !a.isPrimary), [client], ); const { quota } = useEmailStore(); const accounts = useAccountStore((s) => s.accounts); const setDefaultAccount = useAccountStore((s) => s.setDefaultAccount); const reorderAccounts = useAccountStore((s) => s.reorderAccounts); const account = useAccountStore((s) => activeAccountId ? s.getAccountById(activeAccountId) : undefined); const [dragOverIndex, setDragOverIndex] = useState(null); const draggedIndexRef = useRef(null); const quotaPercentage = quota && quota.total > 0 ? Math.min(Math.round((quota.used / quota.total) * 100), 100) : 0; const displayName = primaryIdentity?.name || account?.displayName || (isDemoMode ? 'Demo User' : undefined); const email = primaryIdentity?.email || account?.email || username; const max = getMaxAccounts(); const handleDragStart = useCallback((e: React.DragEvent, index: number) => { draggedIndexRef.current = index; e.dataTransfer.effectAllowed = 'move'; e.dataTransfer.setData('text/plain', String(index)); }, []); const handleDragOver = useCallback((e: React.DragEvent, index: number) => { e.preventDefault(); e.dataTransfer.dropEffect = 'move'; setDragOverIndex(index); }, []); const handleDrop = useCallback((e: React.DragEvent, dropIndex: number) => { e.preventDefault(); setDragOverIndex(null); const fromIndex = draggedIndexRef.current; if (fromIndex === null || fromIndex === dropIndex) return; const next = accounts.map((a) => a.id); const [moved] = next.splice(fromIndex, 1); next.splice(dropIndex, 0, moved); reorderAccounts(next); }, [accounts, reorderAccounts]); const handleDragEnd = useCallback(() => { draggedIndexRef.current = null; setDragOverIndex(null); }, []); const moveAccount = useCallback((from: number, to: number) => { if (to < 0 || to >= accounts.length || from === to) return; const next = accounts.map((a) => a.id); const [moved] = next.splice(from, 1); next.splice(to, 0, moved); reorderAccounts(next); }, [accounts, reorderAccounts]); const handleSwitch = useCallback((id: string) => { if (id === activeAccountId) return; void switchAccount(id); }, [activeAccountId, switchAccount]); const handleAddAccount = useCallback(() => { router.push(`/login?mode=add-account` as never); }, [router]); // Enter scoped settings mode for a shared/group account: set the managed // account, then steer the settings panel to its first editable tab via the // existing 'settings-tab-change' event the page already listens for. const handleManageShared = useCallback((account: SharedAccount) => { const tab = firstScopedTab(account.capabilities); if (!tab) return; setManagedAccount(account); window.dispatchEvent(new CustomEvent('settings-tab-change', { detail: tab })); }, [setManagedAccount]); return (
{/* Display Name */} {displayName || t('../../common.unknown')} {/* Email Address */} {email || t('../../common.unknown')} {/* Username / Login (show when it differs from email) */} {username && username !== email && ( {username} )} {/* Authentication Method */} {authMode === 'oauth' ? t('auth_method_oauth') : t('auth_method_basic')} {/* Server */} {serverUrl || t('../../common.unknown')} {/* Storage */} {quota && quota.total > 0 && (
{t('storage.percentage', { percent: quotaPercentage })}
)} {/* Demo mode indicator */} {isDemoMode && ( {t('demo_account')} )} {/* Logged-in accounts list */} {accounts.length > 0 && (
{accounts.map((a, index) => ( moveAccount(index, index - 1)} onMoveDown={() => moveAccount(index, index + 1)} onSwitch={() => handleSwitch(a.id)} onSetDefault={() => setDefaultAccount(a.id)} labels={{ active: t('accounts.active'), default: t('accounts.default_badge'), setDefault: t('accounts.set_default'), switchTo: t('accounts.switch_to'), moveUp: t('accounts.move_up'), moveDown: t('accounts.move_down'), dragHandle: t('accounts.drag_handle'), }} /> ))} {accounts.length < max && ( )}
)} {/* Shared / group accounts delegated to this session. Clicking one drills into a scoped settings view (filters, vacation, calendars, contacts). */} {sharedAccounts.length > 0 && (
{sharedAccounts.map((acc) => { const editable = firstScopedTab(acc.capabilities) !== null; return ( ); })}
)}
); } interface AccountRowProps { account: AccountEntry; index: number; isActive: boolean; isFirst: boolean; isLast: boolean; isDragOver: boolean; onDragStart: (e: React.DragEvent, index: number) => void; onDragOver: (e: React.DragEvent, index: number) => void; onDrop: (e: React.DragEvent, index: number) => void; onDragEnd: () => void; onMoveUp: () => void; onMoveDown: () => void; onSwitch: () => void; onSetDefault: () => void; labels: { active: string; default: string; setDefault: string; switchTo: string; moveUp: string; moveDown: string; dragHandle: string; }; } function AccountRow({ account, index, isActive, isFirst, isLast, isDragOver, onDragStart, onDragOver, onDrop, onDragEnd, onMoveUp, onMoveDown, onSwitch, onSetDefault, labels, }: AccountRowProps) { return (
onDragStart(e, index)} onDragOver={(e) => onDragOver(e, index)} onDrop={(e) => onDrop(e, index)} onDragEnd={onDragEnd} className={cn( 'flex items-center gap-3 p-3 border rounded-lg transition-colors', isDragOver ? 'border-primary bg-primary/5' : isActive ? 'border-border bg-accent/30' : 'border-border hover:bg-muted/50' )} >
{isActive && (
)}
{!account.isDefault && ( )}
); }