import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { QRCodeSVG } from "qrcode.react";
import { useEffect, useState } from "react";
import { Link, useLocation, useNavigate, useParams, useSearchParams } from "react-router-dom";
import { RichTextEditor } from "../../components/compose/RichTextEditor";
import { TypedPasswordInput } from "../../components/TypedPasswordInput";
import { ReadingPanePicker } from "../../components/mail/ReadingPanePicker";
import { useT } from "../../i18n";
import { api, ApiError } from "../../lib/api";
import type { CalendarOut, ReminderPref } from "../../lib/types";
import { useMe } from "../../lib/auth";
import { useTheme } from "../../lib/theme";
import { setTimeFormat } from "../../lib/timeFormat";
import { PluginSlot } from "../../plugins/PluginSlot";
import { usePluginSettingsSections } from "../../plugins/manifest";
import { AccountWizard } from "../wizard/AccountWizard";
const SECTIONS = ["profile", "security", "accounts", "calendars", "appearance", "integrations"] as const;
type Section = (typeof SECTIONS)[number];
export function Settings() {
const t = useT();
const params = useParams();
const [searchParams] = useSearchParams();
// Where the back button returns to — set by whoever opened Settings (Mail or a
// plugin like Calendar via ?from=). Only accept internal paths; default to Mail.
const navigate = useNavigate();
const rawFrom = searchParams.get("from") ?? "";
const from = rawFrom.startsWith("/") ? rawFrom : "/mail";
const fromQuery = `?from=${encodeURIComponent(from)}`;
// The add-account wizard renders inside this layout (route /settings/accounts/new),
// with the Accounts nav item highlighted.
const addingAccount = useLocation().pathname.endsWith("/accounts/new");
// Plugins contributing `settings.page` each get their own nav entry, keyed by
// plugin name, rather than being stacked at the bottom of Accounts.
const pluginSections = usePluginSettingsSections();
const isPluginSection = pluginSections.some((b) => b.plugin === params.section);
const section = (
addingAccount
? "accounts"
: SECTIONS.includes(params.section as Section) || isPluginSection
? params.section
: "profile"
) as Section;
return (
← {t("nav.back")}
{t("settings.title")}
{addingAccount && navigate("/settings/accounts")} />}
{!addingAccount && section === "profile" && }
{!addingAccount && section === "security" && }
{!addingAccount && section === "accounts" && }
{section === "calendars" && }
{section === "appearance" && }
{section === "integrations" && }
{/* Plugin-contributed settings UI (SPEC §11.3 settings.page): one section
per plugin, selected from the nav like any built-in section. */}
{isPluginSection && (
)}
);
}
function ProfileSection() {
const t = useT();
const { data } = useMe();
return (
{t("settings.profile")}
);
}
// Whoever controls the recovery address can take the account over with a reset
// link, so adding, changing or removing it asks for the current password.
function RecoveryEmailRow({ current }: { current: string | null }) {
const t = useT();
const queryClient = useQueryClient();
const [editing, setEditing] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const save = useMutation({
mutationFn: () => api.setRecoveryEmail(password, email.trim() || null),
onSuccess: (user) => {
queryClient.setQueryData(["me"], user);
setEditing(false);
setPassword("");
},
});
if (!editing) {
return (
);
}
// Spelling and grammar check in the compose window (LanguageTool on the server).
function SpellcheckSetting() {
const t = useT();
const queryClient = useQueryClient();
const settings = useQuery({ queryKey: ["settings"], queryFn: api.settings });
// Also tells us whether this server has a checker at all (503 when not).
const languages = useQuery({
queryKey: ["spellcheck-languages"],
queryFn: api.spellcheckLanguages,
retry: false,
staleTime: 3_600_000,
});
const patch = useMutation({
mutationFn: api.patchSettings,
onSuccess: (data) => queryClient.setQueryData(["settings"], data),
});
const on = settings.data?.spellcheck_enabled ?? true;
const unavailable = languages.isError;
return (