diff --git a/app/[locale]/page.tsx b/app/[locale]/page.tsx index b89c7a40..4997abef 100644 --- a/app/[locale]/page.tsx +++ b/app/[locale]/page.tsx @@ -116,6 +116,7 @@ export default function Home() { markAsRead, toggleStar, moveToMailbox, + moveThreadToMailbox, searchEmails, searchQuery, setSearchQuery, @@ -604,8 +605,8 @@ export default function Home() { } }; - const handleArchive = async () => { - if (!client || !selectedEmail) return; + const handleArchive = async (emailToArchive: Email | null = selectedEmail) => { + if (!client || !emailToArchive) return; // Find archive mailbox const archiveMailbox = mailboxes.find(m => m.role === "archive" || m.name.toLowerCase() === "archive"); @@ -615,10 +616,10 @@ export default function Home() { try { if (archiveMode === 'single') { - await moveToMailbox(client, selectedEmail.id, archiveMailbox.id); + await moveThreadToMailbox(client, emailToArchive.id, archiveMailbox.id); } else { // Determine year/month from the email's received date - const emailDate = new Date(selectedEmail.receivedAt); + const emailDate = new Date(emailToArchive.receivedAt); const year = emailDate.getFullYear().toString(); const month = (emailDate.getMonth() + 1).toString().padStart(2, '0'); const archiveId = archiveMailbox.originalId || archiveMailbox.id; @@ -633,7 +634,7 @@ export default function Home() { } if (archiveMode === 'year') { - await moveToMailbox(client, selectedEmail.id, yearMailbox.id); + await moveThreadToMailbox(client, emailToArchive.id, yearMailbox.id); } else { // archiveMode === 'month' — find or create month subfolder under year const yearId = yearMailbox.originalId || yearMailbox.id; @@ -644,9 +645,16 @@ export default function Home() { monthMailbox = await client.createMailbox(month, yearId); await fetchMailboxes(client); } - await moveToMailbox(client, selectedEmail.id, monthMailbox.id); + await moveThreadToMailbox(client, emailToArchive.id, monthMailbox.id); } } + + if (conversationThread?.threadId === emailToArchive.threadId) { + setConversationThread(null); + setConversationEmails([]); + } + + void fetchMailboxes(client); } catch (error) { console.error("Failed to archive email:", error); } @@ -1453,8 +1461,7 @@ export default function Home() { await handleDelete(); }} onArchive={async (email) => { - selectEmail(email); - await handleArchive(); + await handleArchive(email); }} onSetColorTag={(emailId, color) => { handleSetColorTag(emailId, color); diff --git a/components/settings/themes-settings.tsx b/components/settings/themes-settings.tsx index 40eb3043..6eec790d 100644 --- a/components/settings/themes-settings.tsx +++ b/components/settings/themes-settings.tsx @@ -14,24 +14,31 @@ export function ThemesSettings() { const { installedThemes, activeThemeId, installTheme, uninstallTheme, activateTheme } = useThemeStore(); const [isUploading, setIsUploading] = useState(false); const fileInputRef = useRef(null); - const { isFeatureEnabled, isThemeDisabled, getThemePolicy, isThemeForceEnabled } = usePolicyStore(); + const { isFeatureEnabled, isThemeDisabled, getThemePolicy, getForcedThemeId, isThemeForceEnabled } = usePolicyStore(); const canUpload = isFeatureEnabled('userThemesEnabled'); const themePolicy = getThemePolicy(); + const forcedThemeId = getForcedThemeId(installedThemes.map((theme) => theme.id)); // Filter out themes disabled by admin policy const visibleThemes = installedThemes.filter( theme => !isThemeDisabled(theme.id, !!theme.builtIn) ); + useEffect(() => { + if (forcedThemeId && activeThemeId !== forcedThemeId) { + activateTheme(forcedThemeId); + } + }, [activeThemeId, activateTheme, forcedThemeId]); + // If the active theme was disabled by admin, fall back to default useEffect(() => { if (activeThemeId) { const activeTheme = installedThemes.find(t => t.id === activeThemeId); if (activeTheme && isThemeDisabled(activeThemeId, !!activeTheme.builtIn)) { - activateTheme(null); + activateTheme(forcedThemeId ?? null); } } - }, [activeThemeId, installedThemes, isThemeDisabled, activateTheme]); + }, [activeThemeId, activateTheme, forcedThemeId, installedThemes, isThemeDisabled]); const handleUpload = async (e: React.ChangeEvent) => { const file = e.target.files?.[0]; @@ -58,12 +65,23 @@ export function ThemesSettings() { }; const handleActivate = (id: string | null) => { + if (forcedThemeId && id !== forcedThemeId) { + const forcedTheme = installedThemes.find((theme) => theme.id === forcedThemeId); + toast.info(`Theme "${forcedTheme?.name ?? 'Admin theme'}" is forced by admin and cannot be changed`); + return; + } + activateTheme(id); toast.success(id ? 'Theme activated' : 'Default theme restored'); }; const handleUninstall = (theme: InstalledTheme) => { if (theme.builtIn) return; + if (theme.id === forcedThemeId || theme.forceEnabled || isThemeForceEnabled(theme.id)) { + toast.info(`Theme "${theme.name}" is forced by admin and cannot be removed`); + return; + } + uninstallTheme(theme.id); toast.success('Theme removed'); }; @@ -71,6 +89,12 @@ export function ThemesSettings() { return ( + {forcedThemeId && ( +
+ Theme selection is locked by an administrator. +
+ )} + {/* Theme Grid */}
{/* Default theme card */} @@ -80,25 +104,30 @@ export function ThemesSettings() { isActive={activeThemeId === null} isBuiltIn isDefault={!themePolicy.defaultThemeId} + disabled={Boolean(forcedThemeId)} onActivate={() => handleActivate(null)} /> {/* Installed themes */} - {visibleThemes.map(theme => ( - handleActivate(theme.id)} - onRemove={!theme.builtIn ? () => handleUninstall(theme) : undefined} - /> - ))} + {visibleThemes.map(theme => { + const isForceEnabled = theme.id === forcedThemeId || theme.forceEnabled || isThemeForceEnabled(theme.id); + return ( + handleActivate(theme.id)} + onRemove={!theme.builtIn && !isForceEnabled ? () => handleUninstall(theme) : undefined} + /> + ); + })}
{/* Upload */} @@ -137,20 +166,23 @@ interface ThemeCardProps { isBuiltIn: boolean; isDefault?: boolean; isForceEnabled?: boolean; + disabled?: boolean; variants?: ('light' | 'dark')[]; onActivate: () => void; onRemove?: () => void; } -function ThemeCard({ name, author, preview, isActive, isDefault, isForceEnabled, variants, onActivate, onRemove }: ThemeCardProps) { +function ThemeCard({ name, author, preview, isActive, isDefault, isForceEnabled, disabled, variants, onActivate, onRemove }: ThemeCardProps) { return (