Files
ihasmail/web/src/views/settings/SettingsView.tsx
T
jcoffey-dev c17887e48e ihasmail 2.0: rebuild as Stalwart-first JMAP webmail
Replace the FastAPI/HTMX prototype with a Node/Hono session proxy and a
React 19/Vite SPA. Mail (conversation view, search operators, labels,
sanitised HTML, privacy image proxy, invites, undo send, templates),
calendar (month/week/day/agenda, invites, free/busy, categories,
context menus), contacts (JSContact, groups, vCard), files, Sieve filter
builder (incl. filter-from-message with retroactive apply), vacation,
identities with default + Reply-To, PWA/mobile layout, push via SSE,
in-memory mock Stalwart for dev, Docker + CI.
2026-08-23 01:07:13 -07:00

63 lines
3.8 KiB
TypeScript

import { lazy, Suspense, type ReactNode } from "react";
import { Link, useLocation } from "wouter";
import { ArrowLeft, Bell, Filter, Folder, Info, Keyboard, LayoutTemplate, Palette, PenLine, Plane, Settings as SettingsIcon, ShieldCheck, Tag, Users, Calendar } from "lucide-react";
import { Spinner } from "@/ui/misc";
import { GeneralSettings } from "./GeneralSettings";
import { AppearanceSettings } from "./AppearanceSettings";
import { IdentitiesSettings } from "./IdentitiesSettings";
import { FoldersSettings } from "./FoldersSettings";
import { LabelsSettings } from "./LabelsSettings";
import { TemplatesSettings } from "./TemplatesSettings";
import { NotificationsSettings } from "./NotificationsSettings";
import { SecuritySettings } from "./SecuritySettings";
import { AboutSettings } from "./AboutSettings";
import { ShortcutsSettings } from "./ShortcutsSettings";
import { CalendarSettings } from "./CalendarSettings";
const FiltersSettings = lazy(() => import("./FiltersSettings").then((m) => ({ default: m.FiltersSettings })));
const VacationSettings = lazy(() => import("./VacationSettings").then((m) => ({ default: m.VacationSettings })));
const SECTIONS: Array<{ id: string; label: string; icon: ReactNode; el: ReactNode }> = [
{ id: "general", label: "General", icon: <SettingsIcon size={18} />, el: <GeneralSettings /> },
{ id: "appearance", label: "Appearance", icon: <Palette size={18} />, el: <AppearanceSettings /> },
{ id: "identities", label: "Identities & signatures", icon: <PenLine size={18} />, el: <IdentitiesSettings /> },
{ id: "filters", label: "Filters & rules", icon: <Filter size={18} />, el: <FiltersSettings /> },
{ id: "vacation", label: "Out of office", icon: <Plane size={18} />, el: <VacationSettings /> },
{ id: "folders", label: "Folders", icon: <Folder size={18} />, el: <FoldersSettings /> },
{ id: "labels", label: "Labels", icon: <Tag size={18} />, el: <LabelsSettings /> },
{ id: "templates", label: "Templates", icon: <LayoutTemplate size={18} />, el: <TemplatesSettings /> },
{ id: "calendar", label: "Calendar & contacts", icon: <Calendar size={18} />, el: <CalendarSettings /> },
{ id: "notifications", label: "Notifications", icon: <Bell size={18} />, el: <NotificationsSettings /> },
{ id: "security", label: "Security & sessions", icon: <ShieldCheck size={18} />, el: <SecuritySettings /> },
{ id: "shortcuts", label: "Keyboard shortcuts", icon: <Keyboard size={18} />, el: <ShortcutsSettings /> },
{ id: "about", label: "About", icon: <Info size={18} />, el: <AboutSettings /> },
];
export function SettingsView({ section }: { section?: string }) {
const [, navigate] = useLocation();
const current = SECTIONS.find((s) => s.id === section);
return (
<div className={`settings-layout ${section ? "section" : "root"}`}>
<nav className="settings-nav" aria-label="Settings">
<div className="nav-section" style={{ paddingLeft: 8 }}><span>Settings</span></div>
{SECTIONS.map((s) => (
<Link key={s.id} href={`/settings/${s.id}`} className={`nav-item ${section === s.id ? "active" : ""}`}>
{s.icon}
<span className="nav-label">{s.label}</span>
</Link>
))}
<div className="nav-section" style={{ paddingLeft: 8 }}><span>Shortcuts</span></div>
<Link href="/contacts" className="nav-item"><Users size={18} /><span className="nav-label">Address books</span></Link>
</nav>
<div className="settings-content">
{section && (
<button className="btn btn-ghost btn-sm" style={{ marginBottom: 8, marginLeft: -8 }} onClick={() => navigate("/settings")}>
<ArrowLeft size={16} /> All settings
</button>
)}
<Suspense fallback={<Spinner />}>{current ? current.el : <GeneralSettings />}</Suspense>
</div>
</div>
);
}