"use client"; import { useMemo } from 'react'; import { useTranslations } from 'next-intl'; import { Check, Folder } from '@/components/icons'; import { useSettingsStore, type ToolbarPosition, type MailLayout, type InterfaceLayout } from '@/stores/settings-store'; import { SettingsSection, SettingItem, RadioGroup, ToggleSwitch } from './settings-section'; import { cn } from '@/lib/utils'; import { usePolicyStore } from '@/stores/policy-store'; import { useAccountStore } from '@/stores/account-store'; import { useEmailStore } from '@/stores/email-store'; import { hiddenSharedMailIds, setSharedMailAccountHidden } from '@/lib/shared-mail-visibility'; const MAIL_LAYOUT_PREVIEW_ROWS = [ { sender: 'Alice', subject: 'Quarterly roadmap', preview: 'The draft is ready for review.', selected: false }, { sender: 'Nadia', subject: 'Design sync', preview: 'Pushed updated mocks and notes.', selected: true }, { sender: 'Billing', subject: 'Invoice 1042', preview: 'Your receipt is attached.', selected: false }, ]; const MAIL_LAYOUT_PREVIEW_ROWS_FOCUS = [ ...MAIL_LAYOUT_PREVIEW_ROWS, { sender: 'Sam', subject: 'Lunch?', preview: '', selected: false }, { sender: 'Newsletter', subject: 'Weekly digest', preview: '', selected: false }, ]; function MailLayoutPreview({ value, t, }: { value: MailLayout; t: (key: string) => string; }) { return (
{t(`mail_layout.${value}`)}
{t(`mail_layout.${value}_description`)}
{value === 'split' && ( <>
{MAIL_LAYOUT_PREVIEW_ROWS.map((row) => (
{row.sender}
{row.subject}
))}
)} {value === 'focus' && (
{MAIL_LAYOUT_PREVIEW_ROWS_FOCUS.map((row) => (
{row.sender} {row.subject}
))}
)} {value === 'horizontal' && (
{MAIL_LAYOUT_PREVIEW_ROWS.map((row) => (
{row.sender} {row.subject}
))}
)}
); } /** * The Appearance page's Layout section: where the interface, the reading * pane and the toolbars sit. */ export function LayoutSection() { const t = useTranslations('settings.appearance'); const tEmail = useTranslations('settings.email_behavior'); const { toolbarPosition, showToolbarLabels, mailLayout, interfaceLayout, updateSetting } = useSettingsStore(); const { isSettingLocked, isSettingHidden } = usePolicyStore(); const mountainView = interfaceLayout === 'mountain-view'; return ( {/* The interface layout decides where search, navigation and toolbars go - a layout choice, so it leads the section. */} {!isSettingHidden('interfaceLayout') && ( updateSetting('interfaceLayout', value as InterfaceLayout)} options={[ { value: 'default', label: t('interface_layout.default') }, { value: 'mountain-view', label: t('interface_layout.mountain_view') }, ]} /> )} {/* Mountain View always shows the list and then the conversation, and its toolbars are icon-only, so these two would change nothing there. Their saved values stay and apply again under the default layout. */} {!isSettingHidden('mailLayout') && !mountainView && (
updateSetting('mailLayout', value as MailLayout)} options={[ { value: 'split', label: tEmail('mail_layout.split') }, { value: 'focus', label: tEmail('mail_layout.focus') }, { value: 'horizontal', label: tEmail('mail_layout.horizontal') }, ]} />
)} updateSetting('toolbarPosition', value as ToolbarPosition)} options={[ { value: 'top', label: t('toolbar_position.top') }, { value: 'below-subject', label: t('toolbar_position.below_subject') }, ]} /> {!mountainView && ( updateSetting('showToolbarLabels', checked)} /> )}
); } /** The Appearance page's Sidebar section: what the folder sidebar and rail show. */ export function SidebarSection() { const t = useTranslations('settings.appearance'); const { hideAccountSwitcher, showRailAccountList, colorfulSidebarIcons, showFolderTotalCount, updateSetting } = useSettingsStore(); const accounts = useAccountStore(s => s.accounts); const multiAccount = accounts.length > 1; return ( updateSetting('colorfulSidebarIcons', checked)} /> updateSetting('showFolderTotalCount', checked)} /> {/* The account switcher and the rail avatars only mean something with a second account; a single-account user never sees either. */} {multiAccount && ( updateSetting('hideAccountSwitcher', checked)} /> )} {multiAccount && ( updateSetting('showRailAccountList', checked)} /> )} ); } /** * How several accounts and shared mailboxes appear together: the unified * mailbox and its entries, shared mailboxes, and how rows tell accounts * apart. Only rendered once there is something to combine. */ export function MultipleAccountsSection() { const t = useTranslations('settings.appearance'); const { enableUnifiedMailbox, includeGroupInUnified, unifiedCrossAccount, allMailFolderIds, enableCrossUnreadView, enableCrossStarredView, enableCrossAllView, tintListRowsByAccount, showAccountDot, updateSetting } = useSettingsStore(); const { isSettingLocked, isSettingHidden, isFeatureEnabled } = usePolicyStore(); const accounts = useAccountStore(s => s.accounts); const activeAccountId = useAccountStore(s => s.activeAccountId); const mailboxes = useEmailStore(s => s.mailboxes); const hasGroupInboxes = useMemo(() => mailboxes.some(m => m.isShared), [mailboxes]); const showSharedMailAccounts = useSettingsStore(s => s.showSharedMailAccounts); const hiddenSharedMailAccounts = useSettingsStore(s => s.hiddenSharedMailAccounts); const hiddenSharedIds = hiddenSharedMailIds(hiddenSharedMailAccounts, activeAccountId); const sharedMailAccounts = useMemo(() => { const byId = new Map(); for (const m of mailboxes) { if (m.isShared && m.accountId && !byId.has(m.accountId)) byId.set(m.accountId, m.accountName || m.accountId); } return Array.from(byId, ([id, name]) => ({ id, name })); }, [mailboxes]); const connectedAccountCount = useMemo(() => accounts.filter(a => a.isConnected).length, [accounts]); const unifiedCrossAccountAllowed = isFeatureEnabled('unifiedCrossAccountEnabled'); // Unified Mailbox entries (All mail / Unread / Starred), each gated independently // by the admin. Scope (single account vs. cross-account) is governed by // unifiedCrossAccount; the folder picker below narrows which own folders feed them. const crossViews = [ { setting: 'enableCrossUnreadView', value: enableCrossUnreadView, allowed: isFeatureEnabled('crossUnreadViewEnabled'), labelKey: 'cross_unread.label', descKey: 'cross_unread.description' }, { setting: 'enableCrossStarredView', value: enableCrossStarredView, allowed: isFeatureEnabled('crossStarredViewEnabled'), labelKey: 'cross_starred.label', descKey: 'cross_starred.description' }, { setting: 'enableCrossAllView', value: enableCrossAllView, allowed: isFeatureEnabled('crossAllViewEnabled'), labelKey: 'cross_all.label', descKey: 'cross_all.description' }, ] as const; // The folder picker narrows the own folders included in the entries above; show // it once the user has enabled at least one of them. const anyCrossEnabled = enableCrossUnreadView || enableCrossStarredView || enableCrossAllView; const anyCrossAllowed = crossViews.some(c => c.allowed); // Own (non-shared) folders and the active account's All Mail selection. The // selection is per account: a missing entry = never configured, which // defaults to all no-role folders; an explicit [] = no folders. const ownMailboxes = useMemo(() => mailboxes.filter(m => !m.isShared), [mailboxes]); const currentAllMailEntry = activeAccountId ? allMailFolderIds[activeAccountId] : undefined; const allMailSelected = new Set( currentAllMailEntry === undefined ? ownMailboxes.filter(m => !m.role).map(m => m.id) : currentAllMailEntry ); const toggleAllMailFolder = (id: string) => { if (!activeAccountId) return; const next = new Set(allMailSelected); if (next.has(id)) next.delete(id); else next.add(id); updateSetting('allMailFolderIds', { ...allMailFolderIds, [activeAccountId]: ownMailboxes.filter(m => next.has(m.id)).map(m => m.id), }); }; // Name the account the selection applies to, but only when more than one is // logged in (otherwise it's unambiguous). const activeAccount = accounts.find(a => a.id === activeAccountId); const allMailAccountHint = accounts.length > 1 && activeAccount ? t('all_mail.account_hint', { account: activeAccount.displayName || activeAccount.email }) : null; // Nothing to combine: one account, no shared mailboxes, no group inboxes. if (connectedAccountCount < 2 && sharedMailAccounts.length === 0 && !hasGroupInboxes) return null; return ( {!isSettingHidden('enableUnifiedMailbox') && ( updateSetting('enableUnifiedMailbox', v)} /> )} {enableUnifiedMailbox && connectedAccountCount > 1 && unifiedCrossAccountAllowed && !isSettingHidden('unifiedCrossAccount') && ( updateSetting('unifiedCrossAccount', v)} /> )} {enableUnifiedMailbox && hasGroupInboxes && !isSettingHidden('includeGroupInUnified') && ( updateSetting('includeGroupInUnified', v)} /> )} {enableUnifiedMailbox && anyCrossAllowed && crossViews.map(({ setting, value, allowed, labelKey, descKey }) => ( allowed && !isSettingHidden(setting) && ( updateSetting(setting, v)} /> ) ))} {enableUnifiedMailbox && anyCrossAllowed && anyCrossEnabled && (
{t('all_mail.folders_label')}
{t('all_mail.folders_description')}
{allMailAccountHint && (
{allMailAccountHint}
)}
{ownMailboxes.length === 0 ? (

{t('all_mail.no_folders')}

) : (
{ownMailboxes.map((mb) => { const checked = allMailSelected.has(mb.id); return ( ); })}
)}
)} {/* In unified views a row needs to say which account it came from: a coloured dot, or the whole row tinted. One choice, not two toggles that override each other. */} {connectedAccountCount > 1 && ( { updateSetting('tintListRowsByAccount', value === 'tint'); updateSetting('showAccountDot', value === 'dot'); }} options={[ { value: 'dot', label: t('account_marker.dot') }, { value: 'tint', label: t('account_marker.tint') }, { value: 'none', label: t('account_marker.none') }, ]} /> )} {sharedMailAccounts.length > 0 && !isSettingHidden('showSharedMailAccounts') && ( updateSetting('showSharedMailAccounts', v)} /> )} {sharedMailAccounts.length > 0 && showSharedMailAccounts && activeAccountId && !isSettingHidden('showSharedMailAccounts') && (
{sharedMailAccounts.map(({ id, name }) => ( updateSetting( 'hiddenSharedMailAccounts', setSharedMailAccountHidden(hiddenSharedMailAccounts, activeAccountId, id, !v), )} /> ))}
)}
); } /** Experimental: the Pro interface. Advanced, since it swaps the whole shell. */ export function ExperimentalSection() { const t = useTranslations('settings.appearance'); const { proInterface, updateSetting } = useSettingsStore(); return ( updateSetting('proInterface', v)} testId="setting-pro-interface" /> ); }