Merge branch 'main' into feature/scheduled-send

# Conflicts:
#	app/(main)/[locale]/page.tsx
#	components/email/email-list.tsx
#	components/email/email-viewer.tsx
This commit is contained in:
Lucas Gaitzsch
2026-05-26 18:30:45 +02:00
53 changed files with 864 additions and 358 deletions
+1 -26
View File
@@ -4,17 +4,14 @@ import { useTranslations } from 'next-intl';
import { useCalendarStore, CalendarViewMode } from '@/stores/calendar-store';
import { useSettingsStore } from '@/stores/settings-store';
import { usePolicyStore } from '@/stores/policy-store';
import { SettingsSection, SettingItem, Select, RadioGroup, ToggleSwitch } from './settings-section';
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
export function CalendarSettings() {
const t = useTranslations('calendar.settings');
const tViews = useTranslations('calendar.views');
const tDays = useTranslations('calendar.days');
const { viewMode, setViewMode } = useCalendarStore();
const {
timeFormat,
firstDayOfWeek,
showTimeInMonthView,
showWeekNumbers,
enableCalendarTasks,
@@ -40,28 +37,6 @@ export function CalendarSettings() {
/>
</SettingItem>
<SettingItem label={t('week_starts_on')}>
<Select
value={firstDayOfWeek.toString()}
onChange={(value) => updateSetting('firstDayOfWeek', parseInt(value) as 0 | 1)}
options={[
{ value: '1', label: tDays('monday') },
{ value: '0', label: tDays('sunday') },
]}
/>
</SettingItem>
<SettingItem label={t('time_format')}>
<RadioGroup
value={timeFormat}
onChange={(value) => updateSetting('timeFormat', value as '12h' | '24h')}
options={[
{ value: '12h', label: t('time_format_12h') },
{ value: '24h', label: t('time_format_24h') },
]}
/>
</SettingItem>
<SettingItem
label={t('show_time_in_month_view')}
description={t('show_time_in_month_view_desc')}
+3 -2
View File
@@ -6,6 +6,7 @@ import { Folder, FolderOpen, FileText, FileCode, ImageIcon, FileAudio, File, Hom
import { SettingsSection, SettingItem, ToggleSwitch, RadioGroup } from "./settings-section";
import { loadFilesSettings, saveFilesSettings, type FilesSettings, type FolderLayout } from "@/components/files/files-settings-dialog";
import { cn } from "@/lib/utils";
import { withBasePath } from "@/lib/browser-navigation";
interface SampleFile {
name: string;
@@ -95,7 +96,7 @@ function FilesSettingsPreview({ settings }: { settings: FilesSettings }) {
)}
>
{settings.showThumbnails && file.thumbnailUrl ? (
<img src={file.thumbnailUrl} alt="" className="w-4 h-4 rounded object-cover flex-shrink-0" />
<img src={withBasePath(file.thumbnailUrl)} alt="" className="w-4 h-4 rounded object-cover flex-shrink-0" />
) : settings.showIcons ? (
getPreviewIcon(file, settings.coloredIcons, "sm")
) : null}
@@ -125,7 +126,7 @@ function FilesSettingsPreview({ settings }: { settings: FilesSettings }) {
)}
>
{settings.showThumbnails && file.thumbnailUrl ? (
<img src={file.thumbnailUrl} alt="" className="w-8 h-8 rounded object-cover flex-shrink-0" />
<img src={withBasePath(file.thumbnailUrl)} alt="" className="w-8 h-8 rounded object-cover flex-shrink-0" />
) : settings.showIcons ? (
getPreviewIcon(file, settings.coloredIcons, "lg")
) : (
+86 -3
View File
@@ -1,17 +1,100 @@
"use client";
import { useMemo } from 'react';
import { useTranslations } from 'next-intl';
import { LanguageSwitcher } from '@/components/ui/language-switcher';
import { SettingsSection, SettingItem } from './settings-section';
import { useLocaleStore } from '@/stores/locale-store';
import { useSettingsStore } from '@/stores/settings-store';
import type { DateFormat, TimeFormat, FirstDayOfWeek } from '@/stores/settings-store';
import { formatDate } from '@/lib/utils';
import { SettingsSection, SettingItem, Select, RadioGroup } from './settings-section';
export function LanguageSettings() {
const t = useTranslations('settings.appearance');
const t = useTranslations('settings.language_region');
const tDays = useTranslations('calendar.days');
const { dateFormat, timeFormat, firstDayOfWeek, updateSetting } = useSettingsStore();
// Subscribe to locale changes so the preview re-renders on language switch
// (formatDate reads it via getState() and would otherwise stay stale).
const locale = useLocaleStore((s) => s.locale);
const preview = useMemo(() => {
// Build sample timestamps for each bucket so users see what their pick
// will look like in practice. Use offsets relative to "now" so the
// bucketing is stable even though the wall-clock keeps moving.
void locale; void dateFormat; void timeFormat;
const now = new Date();
const today = new Date(now);
today.setHours(15, 31, 0, 0);
const thisWeek = new Date(now);
thisWeek.setDate(now.getDate() - 2);
thisWeek.setHours(15, 31, 0, 0);
const older = new Date(now);
older.setMonth(now.getMonth() - 2);
older.setHours(15, 31, 0, 0);
return {
today: formatDate(today),
thisWeek: formatDate(thisWeek),
older: formatDate(older),
};
}, [locale, dateFormat, timeFormat]);
return (
<SettingsSection title={t('language.label')} description={t('language.description')}>
<SettingsSection title={t('title')} description={t('description')}>
<SettingItem label={t('language.label')} description={t('language.description')}>
<LanguageSwitcher />
</SettingItem>
<SettingItem label={t('date_format.label')} description={t('date_format.description')}>
<div className="flex flex-col items-end gap-2">
<Select
value={dateFormat}
onChange={(value) => updateSetting('dateFormat', value as DateFormat)}
options={[
{ value: 'smart', label: t('date_format.smart') },
{ value: 'relative', label: t('date_format.relative') },
{ value: 'full', label: t('date_format.full') },
]}
/>
<div className="text-xs text-muted-foreground text-right space-y-0.5 font-mono">
<div>
<span className="opacity-70">{t('date_format.preview_today')} </span>
<span className="text-foreground/90">{preview.today}</span>
</div>
<div>
<span className="opacity-70">{t('date_format.preview_this_week')} </span>
<span className="text-foreground/90">{preview.thisWeek}</span>
</div>
<div>
<span className="opacity-70">{t('date_format.preview_older')} </span>
<span className="text-foreground/90">{preview.older}</span>
</div>
</div>
</div>
</SettingItem>
<SettingItem label={t('time_format.label')} description={t('time_format.description')}>
<RadioGroup
value={timeFormat}
onChange={(value) => updateSetting('timeFormat', value as TimeFormat)}
options={[
{ value: '12h', label: t('time_format.12h') },
{ value: '24h', label: t('time_format.24h') },
]}
/>
</SettingItem>
<SettingItem label={t('first_day.label')} description={t('first_day.description')}>
<Select
value={firstDayOfWeek.toString()}
onChange={(value) => updateSetting('firstDayOfWeek', parseInt(value) as FirstDayOfWeek)}
options={[
{ value: '1', label: tDays('monday') },
{ value: '0', label: tDays('sunday') },
]}
/>
</SettingItem>
</SettingsSection>
);
}
+22 -2
View File
@@ -1,11 +1,13 @@
"use client";
import { useMemo } from 'react';
import { useTranslations } from 'next-intl';
import { useSettingsStore, type ToolbarPosition, type MailLayout } 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';
const MAIL_LAYOUT_PREVIEW_ROWS = [
{ sender: 'Alice', subject: 'Quarterly roadmap', preview: 'The draft is ready for review.', selected: false },
@@ -115,9 +117,11 @@ function MailLayoutPreview({
export function LayoutSettings() {
const t = useTranslations('settings.appearance');
const tEmail = useTranslations('settings.email_behavior');
const { toolbarPosition, showToolbarLabels, hideAccountSwitcher, showRailAccountList, enableUnifiedMailbox, colorfulSidebarIcons, mailLayout, proInterface, updateSetting } = useSettingsStore();
const { toolbarPosition, showToolbarLabels, hideAccountSwitcher, showRailAccountList, enableUnifiedMailbox, includeGroupInUnified, colorfulSidebarIcons, mailLayout, proInterface, updateSetting } = useSettingsStore();
const { isSettingLocked, isSettingHidden } = usePolicyStore();
const accounts = useAccountStore(s => s.accounts);
const mailboxes = useEmailStore(s => s.mailboxes);
const hasGroupInboxes = useMemo(() => mailboxes.some(m => m.isShared), [mailboxes]);
return (
<SettingsSection title={t('title')} description={t('description')}>
@@ -177,10 +181,11 @@ export function LayoutSettings() {
/>
</SettingItem>
{accounts.length > 1 && (
{(accounts.length > 1 || hasGroupInboxes) && !isSettingHidden('enableUnifiedMailbox') && (
<SettingItem
label={t('unified_mailbox.label')}
description={t('unified_mailbox.description')}
locked={isSettingLocked('enableUnifiedMailbox')}
>
<ToggleSwitch
checked={enableUnifiedMailbox}
@@ -189,6 +194,21 @@ export function LayoutSettings() {
</SettingItem>
)}
{enableUnifiedMailbox && hasGroupInboxes && !isSettingHidden('includeGroupInUnified') && (
<div className="ml-4 border-l-2 border-border pl-4 -mt-2">
<SettingItem
label={t('unified_mailbox.include_group.label')}
description={t('unified_mailbox.include_group.description')}
locked={isSettingLocked('includeGroupInUnified')}
>
<ToggleSwitch
checked={includeGroupInUnified}
onChange={(v) => updateSetting('includeGroupInUnified', v)}
/>
</SettingItem>
</div>
)}
<SettingItem label={t('pro_interface.label')} description={t('pro_interface.description')}>
<ToggleSwitch
checked={proInterface}