import { useState } from "react"; import type { ApiSettings } from "../../types"; import Button from "../../components/Button"; import Card from "../../components/Card"; import FormField from "../../components/FormField"; import PageTitle from "../../components/PageTitle"; import LoadingFrame from "../../components/LoadingFrame"; import ToggleSwitch from "../../components/ToggleSwitch"; import { listImapFolders, testImapSettings, testSmtpSettings, type MailConnectionTestResponse, type MailImapFolderListResponse, type MailSecurity } from "../../api/mail"; import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData"; 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 [smtpTestResult, setSmtpTestResult] = useState(null); const [imapTestResult, setImapTestResult] = useState(null); const [folderResult, setFolderResult] = useState(null); const [mailActionState, setMailActionState] = useState<"smtp" | "imap" | "folders" | null>(null); const version = data.currentVersion; const locked = isAuditLockedVersion(version); 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); const delivery = asRecord(displayDraft.delivery); const imapAppend = asRecord(delivery.imap_append_sent); const imapEnabled = getBool(imap, "enabled"); const imapDisabled = locked || !imapEnabled; function toggleImap(enabled: boolean) { patch(["server", "imap", "enabled"], enabled); if (!enabled) { patch(["delivery", "imap_append_sent", "enabled"], false); } } function emptyToNull(value: string, trim = true): string | null { const normalized = trim ? value.trim() : value; return normalized ? normalized : null; } function readSecurity(value: string, fallback: MailSecurity): MailSecurity { return securityOptions.includes(value as MailSecurity) ? (value as MailSecurity) : fallback; } function smtpPayload() { return { host: emptyToNull(getText(smtp, "host")), port: getNumber(smtp, "port", 587), username: emptyToNull(getText(smtp, "username")), password: emptyToNull(getText(smtp, "password"), false), security: readSecurity(getText(smtp, "security", "starttls"), "starttls"), timeout_seconds: getNumber(smtp, "timeout_seconds", 30) }; } function imapPayload() { return { enabled: true, host: emptyToNull(getText(imap, "host")), port: getNumber(imap, "port", 993), username: emptyToNull(getText(imap, "username")), password: emptyToNull(getText(imap, "password"), false), security: readSecurity(getText(imap, "security", "tls"), "tls"), sent_folder: emptyToNull(getText(imap, "sent_folder", "auto")), timeout_seconds: getNumber(imap, "timeout_seconds", 30) }; } async function runSmtpTest() { if (locked) return; setMailActionState("smtp"); setLocalError(""); try { setSmtpTestResult(await testSmtpSettings(settings, smtpPayload())); } catch (err) { setSmtpTestResult({ ok: false, protocol: "smtp", message: err instanceof Error ? err.message : String(err), details: {} }); } finally { setMailActionState(null); } } async function runImapTest() { if (imapDisabled) return; setMailActionState("imap"); setLocalError(""); try { setImapTestResult(await testImapSettings(settings, imapPayload())); } catch (err) { setImapTestResult({ ok: false, protocol: "imap", message: err instanceof Error ? err.message : String(err), details: {} }); } finally { setMailActionState(null); } } async function runFolderLookup() { if (imapDisabled) return; setMailActionState("folders"); setLocalError(""); try { setFolderResult(await listImapFolders(settings, imapPayload())); } catch (err) { setFolderResult({ ok: false, protocol: "imap", message: err instanceof Error ? err.message : String(err), folders: [], details: {} }); } finally { setMailActionState(null); } } function useDetectedSentFolder() { const folder = folderResult?.detected_sent_folder; if (!folder || imapDisabled) return; patch(["server", "imap", "sent_folder"], folder); if (!getText(imapAppend, "folder") || getText(imapAppend, "folder") === "auto") { patch(["delivery", "imap_append_sent", "folder"], folder); } } return (
Server settings

Version {version ? `#${version.version_number}` : "—"} · {saveState}

{error &&
{error}
} {localError &&
{localError}
} {locked &&
This version is read-only. {versionLockReason(version)} Copy the campaign before editing server settings.
} <>

SMTP login

patch(["server", "smtp", "host"], event.target.value)} /> patch(["server", "smtp", "port"], Number(event.target.value || 0))} /> patch(["server", "smtp", "username"], event.target.value)} /> patch(["server", "smtp", "password"], event.target.value)} /> patch(["server", "smtp", "timeout_seconds"], Number(event.target.value || 0))} />

IMAP sent-folder append

patch(["server", "imap", "host"], event.target.value)} /> patch(["server", "imap", "port"], Number(event.target.value || 0))} /> patch(["server", "imap", "username"], event.target.value)} /> patch(["server", "imap", "password"], event.target.value)} /> patch(["server", "imap", "sent_folder"], event.target.value)} />
patch(["delivery", "imap_append_sent", "enabled"], checked)} />
patch(["delivery", "imap_append_sent", "folder"], event.target.value)} />

Folder lookup lists visible mailboxes and guesses folders such as Sent, Gesendet or Sent Mail.

); } function MailActionResult({ result }: { result: MailConnectionTestResponse | null }) { if (!result) return null; const authenticated = result.details?.authenticated; return (
{result.message} {result.ok && typeof authenticated === "boolean" && ( Authentication: {authenticated ? "credentials accepted" : "not used"}. )}
); } function FolderLookupResult({ result, disabled, onUseDetected }: { result: MailImapFolderListResponse | null; disabled?: boolean; onUseDetected: () => void }) { if (!result) return null; if (!result.ok) { return
{result.message}
; } return (

{result.message}

Detected Sent folder: {result.detected_sent_folder || "—"}

{result.detected_sent_folder && } {result.folders.length > 0 && (
{result.folders.slice(0, 12).map((folder) => ( {folder.name} ))} {result.folders.length > 12 && +{result.folders.length - 12} more}
)}
); }