import { useSettings } from "@/store/settings";
import { Switch } from "@/ui/misc";
import { browserTimeZone, listTimeZones } from "@/lib/dates";
import { toast } from "@/ui/toast";
import {
browserLocale,
formatClock,
formatDate,
formatFullDateTime,
getServerLocale,
localeLabel,
localeOptions,
withPrefs,
type DateFormat,
} from "@/lib/datetime";
/** Illustrative instant used for the format previews: 22 Nov 2025, 18:23. */
const SAMPLE = new Date(2025, 10, 22, 18, 23);
const DATE_FORMATS: Array<{ value: DateFormat; label: string }> = [
{ value: "auto", label: "Automatic" },
{ value: "dmy-dot", label: "Day.Month.Year" },
{ value: "dmy-slash", label: "Day/Month/Year" },
{ value: "mdy-slash", label: "Month/Day/Year" },
{ value: "ymd-dash", label: "Year-Month-Day (ISO 8601)" },
];
export function GeneralSettings() {
const s = useSettings((st) => st.settings);
const update = useSettings((st) => st.update);
const reset = useSettings((st) => st.reset);
const exportJson = useSettings((st) => st.exportJson);
const importJson = useSettings((st) => st.importJson);
const serverLocale = getServerLocale();
const autoLocale = serverLocale ?? browserLocale();
return (
General
Reading, sending and list behaviour. Settings are stored in this browser.
Reading
Reading pane
update({ readingPane: e.target.value as typeof s.readingPane })}>
Right of the list
Below the list
Off (open messages full width)
Mark as read
update({ markReadDelay: Number(e.target.value) })}>
Immediately when opened
After 2 seconds
After 5 seconds
Never automatically
After archiving or deleting
update({ autoAdvance: e.target.value as typeof s.autoAdvance })}>
Go back to the list
Open the next (older) conversation
Open the previous (newer) conversation
Remote images
update({ imagePolicy: e.target.value as typeof s.imagePolicy })}>
Ask before showing (recommended)
Show automatically from my contacts
Always show
update({ conversationMode: v })} label="Conversation view" hint="Group messages from the same thread together." />
update({ showPreview: v })} label="Show message snippets" hint="Preview the first line of each message in the list." />
update({ showAvatars: v })} label="Show sender avatars" />
update({ confirmDelete: v })} label="Confirm before deleting" />
Composing
Default format
update({ composeFormat: e.target.value as typeof s.composeFormat })}>
Rich text (HTML)
Plain text
Undo send window
update({ undoSendSeconds: Number(e.target.value) })}>
Off
5 seconds
8 seconds
15 seconds
30 seconds
update({ includeQuote: v })} label="Quote original message in replies" />
update({ signatureAboveQuote: v })} label="Place signature above quoted text" />
update({ attachmentReminder: v })} label="Attachment reminder" hint="Warn when the message mentions an attachment but none is attached." />
update({ requestReadReceipt: v })} label="Always request read receipts" />
update({ spellcheck: v })} label="Spell check while typing" />
Locale
Time zone
update({ timeZone: e.target.value || null })}>
Browser default ({browserTimeZone})
{listTimeZones().map((tz) => {tz} )}
Week starts on
update({ weekStart: Number(e.target.value) as 0 | 1 | 6 })}>
Monday
Sunday
Saturday
Language & region
update({ locale: e.target.value })}>
Automatic ({localeLabel(autoLocale)})
{localeOptions().map((o) => {o.label} — {o.tag} )}
{serverLocale ? `Your mail server reports ${localeLabel(serverLocale)} (${serverLocale}).` : "Your mail server does not report a locale, so the browser's is used."} Dates, times and month names follow this choice.
Date format
update({ dateFormat: e.target.value as DateFormat })}>
{DATE_FORMATS.map((f) => (
{f.label} ({withPrefs({ locale: s.locale, dateFormat: f.value }, () => formatDate(SAMPLE))})
))}
Time format
update({ timeFormat: e.target.value as typeof s.timeFormat })}>
Automatic ({withPrefs({ locale: s.locale, timeFormat: "auto" }, () => formatClock(SAMPLE))})
24-hour clock (18:23)
12-hour clock (6:23 PM)
Preview: {formatFullDateTime(SAMPLE)}
Backup
{ const blob = new Blob([exportJson()], { type: "application/json" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "ihasmail-settings.json"; a.click(); }}>Export settings
Import settings
{ const f = e.target.files?.[0]; if (!f) return; const ok = importJson(await f.text()); toast[ok ? "success" : "error"](ok ? "Settings imported" : "Invalid settings file"); e.target.value = ""; }} />
{ reset(); toast.show("Settings reset to defaults"); }}>Reset to defaults
);
}