Merge dev into main - version 1.4.8

This commit is contained in:
Linus Rath
2026-03-23 17:08:19 +01:00
47 changed files with 1506 additions and 211 deletions
+24 -9
View File
@@ -3,29 +3,44 @@
import { useTranslations } from 'next-intl';
import { useAuthStore } from '@/stores/auth-store';
import { useEmailStore } from '@/stores/email-store';
import { useAccountStore } from '@/stores/account-store';
import { SettingsSection, SettingItem } from './settings-section';
import { formatFileSize } from '@/lib/utils';
export function AccountSettings() {
const t = useTranslations('settings.account');
const { username, serverUrl, isDemoMode, primaryIdentity } = useAuthStore();
const { username, serverUrl, isDemoMode, primaryIdentity, authMode, activeAccountId } = useAuthStore();
const { quota } = useEmailStore();
const account = useAccountStore((s) => activeAccountId ? s.getAccountById(activeAccountId) : undefined);
const quotaPercentage = quota ? Math.round((quota.used / quota.total) * 100) : 0;
const displayName = primaryIdentity?.name || (isDemoMode ? 'Demo User' : undefined);
const displayName = primaryIdentity?.name || account?.displayName || (isDemoMode ? 'Demo User' : undefined);
const email = primaryIdentity?.email || account?.email || username;
return (
<SettingsSection title={t('title')} description={t('description')}>
{/* Display Name (show in demo mode or when identity has a name) */}
{displayName && (
<SettingItem label={t('name_label')}>
<span className="text-sm text-foreground">{displayName}</span>
</SettingItem>
)}
{/* Display Name */}
<SettingItem label={t('name_label')}>
<span className="text-sm text-foreground">{displayName || t('../../common.unknown')}</span>
</SettingItem>
{/* Email Address */}
<SettingItem label={t('email.label')}>
<span className="text-sm text-foreground">{username || t('../../common.unknown')}</span>
<span className="text-sm text-foreground">{email || t('../../common.unknown')}</span>
</SettingItem>
{/* Username / Login (show when it differs from email) */}
{username && username !== email && (
<SettingItem label={t('username_label')}>
<span className="text-sm text-foreground">{username}</span>
</SettingItem>
)}
{/* Authentication Method */}
<SettingItem label={t('auth_method_label')}>
<span className="text-sm text-foreground">
{authMode === 'oauth' ? t('auth_method_oauth') : t('auth_method_basic')}
</span>
</SettingItem>
{/* Server */}
-34
View File
@@ -16,9 +16,6 @@ export function CalendarSettings() {
firstDayOfWeek,
showTimeInMonthView,
showWeekNumbers,
calendarNotificationsEnabled,
calendarNotificationSound,
calendarInvitationParsingEnabled,
enableCalendarTasks,
showTasksOnCalendar,
updateSetting,
@@ -103,37 +100,6 @@ export function CalendarSettings() {
</SettingItem>
)}
<SettingItem
label={t('notifications_enabled')}
description={t('notifications_enabled_desc')}
>
<ToggleSwitch
checked={calendarNotificationsEnabled}
onChange={(checked) => updateSetting('calendarNotificationsEnabled', checked)}
/>
</SettingItem>
<SettingItem
label={t('notification_sound')}
description={t('notification_sound_desc')}
>
<ToggleSwitch
checked={calendarNotificationSound}
onChange={(checked) => updateSetting('calendarNotificationSound', checked)}
disabled={!calendarNotificationsEnabled}
/>
</SettingItem>
<SettingItem
label={t('invitation_parsing')}
description={t('invitation_parsing_desc')}
>
<ToggleSwitch
checked={calendarInvitationParsingEnabled}
onChange={(checked) => updateSetting('calendarInvitationParsingEnabled', checked)}
/>
</SettingItem>
</SettingsSection>
);
}
+35 -2
View File
@@ -1,7 +1,8 @@
"use client";
import { useState } from 'react';
import { useState, useCallback } from 'react';
import { useTranslations } from 'next-intl';
import { useConfig } from '@/hooks/use-config';
import { useSettingsStore } from '@/stores/settings-store';
import type { ArchiveMode, HoverAction } from '@/stores/settings-store';
import { ALL_HOVER_ACTIONS } from '@/stores/settings-store';
@@ -10,13 +11,26 @@ import { useEmailStore } from '@/stores/email-store';
import { cn } from '@/lib/utils';
import { SettingsSection, SettingItem, Select, ToggleSwitch } from './settings-section';
import { TrustedSendersModal } from '@/components/trusted-senders-modal';
import { ChevronRight, AlertTriangle, FolderSync, Loader2 } from 'lucide-react';
import { ChevronRight, AlertTriangle, FolderSync, Loader2, Mail } from 'lucide-react';
export function EmailSettings() {
const t = useTranslations('settings.email_behavior');
const { appName } = useConfig();
const [showTrustedModal, setShowTrustedModal] = useState(false);
const [isReorganizing, setIsReorganizing] = useState(false);
const [reorganizeResult, setReorganizeResult] = useState<string | null>(null);
const [defaultMailStatus, setDefaultMailStatus] = useState<'idle' | 'success' | 'error'>('idle');
const handleSetDefaultMailProgram = useCallback(() => {
try {
if (typeof navigator !== 'undefined' && navigator.registerProtocolHandler) {
navigator.registerProtocolHandler('mailto', `${window.location.origin}/compose?mailto=%s`);
setDefaultMailStatus('success');
}
} catch {
setDefaultMailStatus('error');
}
}, []);
const {
markAsReadDelay,
@@ -280,6 +294,25 @@ export function EmailSettings() {
/>
</SettingItem>
{/* Default Mail Program */}
<SettingItem label={t('default_mail_program.label')} description={t('default_mail_program.description', { appName: appName || 'Bulwark' })}>
<div className="flex flex-col items-end gap-1">
<button
onClick={handleSetDefaultMailProgram}
className="flex items-center gap-2 px-3 py-1.5 bg-muted hover:bg-accent rounded-md transition-colors"
>
<Mail className="w-4 h-4" />
<span className="text-sm text-foreground">{t('default_mail_program.button')}</span>
</button>
{defaultMailStatus === 'success' && (
<p className="text-xs text-green-600 dark:text-green-400">{t('default_mail_program.success')}</p>
)}
{defaultMailStatus === 'error' && (
<p className="text-xs text-destructive">{t('default_mail_program.error')}</p>
)}
</div>
</SettingItem>
{/* Trusted Senders */}
<SettingItem label={t('trusted_senders.label')} description={t('trusted_senders.description')}>
<button
@@ -0,0 +1,115 @@
"use client";
import { useTranslations } from 'next-intl';
import { useSettingsStore } from '@/stores/settings-store';
import { SettingsSection, SettingItem, ToggleSwitch, Select } from './settings-section';
import { playNotificationSound, NOTIFICATION_SOUNDS } from '@/lib/notification-sound';
import type { NotificationSoundChoice } from '@/lib/notification-sound';
import { Button } from '@/components/ui/button';
import { Volume2 } from 'lucide-react';
export function NotificationSettings() {
const t = useTranslations('settings.notifications');
const {
emailNotificationsEnabled,
emailNotificationSound,
notificationSoundChoice,
calendarNotificationsEnabled,
calendarNotificationSound,
calendarInvitationParsingEnabled,
updateSetting,
} = useSettingsStore();
const soundOptions = NOTIFICATION_SOUNDS.map((s) => ({
value: s.id,
label: t(`sounds.${s.id}`),
}));
return (
<div className="space-y-8">
<SettingsSection title={t('sound_selection.title')} description={t('sound_selection.description')}>
<SettingItem
label={t('sound_selection.choose')}
description={t('sound_selection.choose_desc')}
>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="icon"
className="h-8 w-8"
onClick={() => playNotificationSound(notificationSoundChoice)}
title={t('test_sound')}
>
<Volume2 className="w-4 h-4" />
</Button>
<Select
value={notificationSoundChoice}
onChange={(value) => {
const choice = value as NotificationSoundChoice;
updateSetting('notificationSoundChoice', choice);
playNotificationSound(choice);
}}
options={soundOptions}
/>
</div>
</SettingItem>
</SettingsSection>
<SettingsSection title={t('email.title')} description={t('email.description')}>
<SettingItem
label={t('email.enabled')}
description={t('email.enabled_desc')}
>
<ToggleSwitch
checked={emailNotificationsEnabled}
onChange={(checked) => updateSetting('emailNotificationsEnabled', checked)}
/>
</SettingItem>
<SettingItem
label={t('email.sound')}
description={t('email.sound_desc')}
>
<ToggleSwitch
checked={emailNotificationSound}
onChange={(checked) => updateSetting('emailNotificationSound', checked)}
disabled={!emailNotificationsEnabled}
/>
</SettingItem>
</SettingsSection>
<SettingsSection title={t('calendar.title')} description={t('calendar.description')}>
<SettingItem
label={t('calendar.enabled')}
description={t('calendar.enabled_desc')}
>
<ToggleSwitch
checked={calendarNotificationsEnabled}
onChange={(checked) => updateSetting('calendarNotificationsEnabled', checked)}
/>
</SettingItem>
<SettingItem
label={t('calendar.sound')}
description={t('calendar.sound_desc')}
>
<ToggleSwitch
checked={calendarNotificationSound}
onChange={(checked) => updateSetting('calendarNotificationSound', checked)}
disabled={!calendarNotificationsEnabled}
/>
</SettingItem>
<SettingItem
label={t('calendar.invitation_parsing')}
description={t('calendar.invitation_parsing_desc')}
>
<ToggleSwitch
checked={calendarInvitationParsingEnabled}
onChange={(checked) => updateSetting('calendarInvitationParsingEnabled', checked)}
/>
</SettingItem>
</SettingsSection>
</div>
);
}