Code cleanup and deduplication

This commit is contained in:
2026-06-10 17:26:00 +02:00
parent fcc46b06fe
commit be793fb3e7
27 changed files with 474 additions and 851 deletions

View File

@@ -1,4 +1,4 @@
import { useEffect, useState } from "react";
import { useState } from "react";
import type { ApiSettings } from "../../types";
import Button from "../../components/Button";
import Card from "../../components/Card";
@@ -6,21 +6,16 @@ import FormField from "../../components/FormField";
import PageTitle from "../../components/PageTitle";
import LoadingFrame from "../../components/LoadingFrame";
import ToggleSwitch from "../../components/ToggleSwitch";
import { autosaveCampaignVersion } from "../../api/campaigns";
import { listImapFolders, testImapSettings, testSmtpSettings, type MailConnectionTestResponse, type MailImapFolderListResponse, type MailSecurity } from "../../api/mail";
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
import { asRecord, formatDateTime, getCampaignJson, isAuditLockedVersion, versionLockReason } from "./utils/campaignView";
import { ensureCampaignDraft, getBool, getNumber, getText, updateNested } from "./utils/draftEditor";
import { useRegisterCampaignUnsavedChanges } from "./context/UnsavedChangesContext";
import { useCampaignDraftEditor } from "./hooks/useCampaignDraftEditor";
import { asRecord, isAuditLockedVersion, versionLockReason } from "./utils/campaignView";
import { getBool, getNumber, getText } from "./utils/draftEditor";
const securityOptions = ["plain", "tls", "starttls"];
export default function MailSettingsPage({ settings, campaignId }: { settings: ApiSettings; campaignId: string }) {
const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId);
const [draft, setDraft] = useState<Record<string, unknown> | null>(null);
const [dirty, setDirty] = useState(false);
const [saveState, setSaveState] = useState("Loaded");
const [localError, setLocalError] = useState("");
const [smtpTestResult, setSmtpTestResult] = useState<MailConnectionTestResponse | null>(null);
const [imapTestResult, setImapTestResult] = useState<MailConnectionTestResponse | null>(null);
const [folderResult, setFolderResult] = useState<MailImapFolderListResponse | null>(null);
@@ -28,7 +23,17 @@ export default function MailSettingsPage({ settings, campaignId }: { settings: A
const version = data.currentVersion;
const locked = isAuditLockedVersion(version);
const displayDraft = draft ?? ensureCampaignDraft(null);
const { draft, displayDraft, dirty, saveState, localError, setLocalError, patch, saveDraft } = useCampaignDraftEditor({
settings,
campaignId,
version,
locked,
reload,
setError,
currentStep: "mail-settings",
unsavedTitle: "Unsaved server settings",
unsavedMessage: "Server settings have unsaved changes. Save them before leaving, or discard them and continue."
});
const server = asRecord(displayDraft.server);
const smtp = asRecord(server.smtp);
const imap = asRecord(server.imap);
@@ -37,46 +42,8 @@ export default function MailSettingsPage({ settings, campaignId }: { settings: A
const imapEnabled = getBool(imap, "enabled");
const imapDisabled = locked || !imapEnabled;
useEffect(() => {
if (!version) return;
setDraft(ensureCampaignDraft(version));
setDirty(false);
setSaveState(version.autosaved_at ? `Loaded saved draft ${formatDateTime(version.autosaved_at)}` : "Loaded");
}, [version]);
function patch(path: string[], value: unknown) {
if (locked) return;
setDraft((current) => updateNested(current ?? {}, path, value));
setDirty(true);
setLocalError("");
}
async function saveDraft(mode: "auto" | "manual" = "manual"): Promise<boolean> {
if (!draft || !version || locked) return false;
setSaveState("Saving…");
setError("");
setLocalError("");
try {
const saved = await autosaveCampaignVersion(settings, campaignId, version.id, {
campaign_json: draft,
current_flow: "manual",
current_step: "mail-settings",
workflow_state: "editing",
is_complete: false
});
setDraft(getCampaignJson(saved));
setDirty(false);
setSaveState(`Saved ${formatDateTime(saved.autosaved_at ?? saved.updated_at)}`);
await reload();
return true;
} catch (err) {
const text = err instanceof Error ? err.message : String(err);
setLocalError(text);
setSaveState("Save failed");
return false;
}
}
function toggleImap(enabled: boolean) {
patch(["server", "imap", "enabled"], enabled);
@@ -85,12 +52,6 @@ export default function MailSettingsPage({ settings, campaignId }: { settings: A
}
}
useRegisterCampaignUnsavedChanges(dirty && !locked ? {
title: "Unsaved server settings",
message: "Server settings have unsaved changes. Save them before leaving, or discard them and continue.",
onSave: () => saveDraft("manual"),
onDiscard: () => setDirty(false)
} : null);
function emptyToNull(value: string, trim = true): string | null {
const normalized = trim ? value.trim() : value;