Files
govoplan-campaign/webui/src/features/campaigns/MailSettingsPage.tsx

551 lines
30 KiB
TypeScript

import { useEffect, useMemo, useState } from "react";
import { MailServerSettingsPanel, addressesFromValue, hasMailImapSettings, mailImapSettingsPayload, mailNumberOrDefault, mailNumberOrNull, mailServerSecurityOptions, mailSmtpSettingsPayload, mailTransportCredentialsPayloadFromRecords, usePlatformModuleInstalled, usePlatformUiCapability, type MailProfilesUiCapability, type MailServerConnectionTestResult, type MailServerCredentialSettings, type MailServerFolderLookupResult, type MailServerImapSettings, type MailServerSmtpSettings } from "@govoplan/core-webui";
import type { ApiSettings } from "../../types";
import { Button } from "@govoplan/core-webui";
import { Card } from "@govoplan/core-webui";
import { FormField } from "@govoplan/core-webui";
import { PageTitle } from "@govoplan/core-webui";
import { LoadingFrame } from "@govoplan/core-webui";
import LockedVersionNotice from "./components/LockedVersionNotice";
import VersionLine from "./components/VersionLine";
import { DismissibleAlert, i18nMessage } from "@govoplan/core-webui";
import {
createMailServerProfile,
getMailProfilePolicy,
listImapFolders,
listMailProfileImapFolders,
listMailServerProfiles,
testImapSettings,
testMailProfileImap,
testMailProfileSmtp,
testSmtpSettings,
type MailProfilePolicy,
type MailSecurity,
type MailServerProfile } from
"../../api/mail";
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
import { useCampaignDraftEditor } from "./hooks/useCampaignDraftEditor";
import { asArray, asRecord, isAuditLockedVersion } from "./utils/campaignView";
import { cloneJson, getBool, getNumber, getText } from "./utils/draftEditor";
import { campaignMailSettingsPolicyState } from "./policyUi";
const securityOptions = mailServerSecurityOptions as readonly MailSecurity[];
type MailSettingsView = "settings" | "policy";
type MailSettingsPageProps = {
settings: ApiSettings;
campaignId: string;
view?: MailSettingsView;
};
export default function MailSettingsPage({ settings, campaignId, view = "settings" }: MailSettingsPageProps) {
const mailModuleInstalled = usePlatformModuleInstalled("mail");
const isPolicyView = view === "policy";
const mailProfilesUi = usePlatformUiCapability<MailProfilesUiCapability>("mail.profiles");
const MailProfilePolicyEditor = mailProfilesUi?.MailProfilePolicyEditor ?? null;
const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId);
const [smtpTestResult, setSmtpTestResult] = useState<MailServerConnectionTestResult | null>(null);
const [imapTestResult, setImapTestResult] = useState<MailServerConnectionTestResult | null>(null);
const [folderResult, setFolderResult] = useState<MailServerFolderLookupResult | null>(null);
const [mailActionState, setMailActionState] = useState<"smtp" | "imap" | "folders" | null>(null);
const [mailProfiles, setMailProfiles] = useState<MailServerProfile[]>([]);
const [policyProfiles, setPolicyProfiles] = useState<MailServerProfile[]>([]);
const [effectiveMailPolicy, setEffectiveMailPolicy] = useState<MailProfilePolicy | null>(null);
const [profilesLoading, setProfilesLoading] = useState(false);
const [profileName, setProfileName] = useState("");
const [profileMessage, setProfileMessage] = useState("");
const [profileError, setProfileError] = useState("");
const version = data.currentVersion;
const locked = isAuditLockedVersion(version, data.campaign?.current_version_id);
const { draft, displayDraft, dirty, saveState, localError, setLocalError, patch, saveDraft } = useCampaignDraftEditor({
settings,
campaignId,
version,
locked,
reload,
setError,
currentStep: isPolicyView ? "mail-policy" : "mail-settings",
unsavedTitle: isPolicyView ? "i18n:govoplan-campaign.unsaved_mail_policy_changes.c9327491" : "i18n:govoplan-campaign.unsaved_mail_settings.38e1536b",
unsavedMessage: isPolicyView ?
"i18n:govoplan-campaign.mail_policy_changes_have_unsaved_draft_changes_s.5aee7d4e" :
"i18n:govoplan-campaign.mail_settings_have_unsaved_changes_save_them_bef.52644559",
transformDraftBeforeSave: normalizeMailSettingsBeforeSave
});
const server = asRecord(displayDraft.server);
const smtp = asRecord(server.smtp);
const imap = asRecord(server.imap);
const credentials = asRecord(server.credentials);
const smtpCredentials = asRecord(credentials.smtp);
const imapCredentials = asRecord(credentials.imap);
const delivery = asRecord(displayDraft.delivery);
const imapAppend = asRecord(delivery.imap_append_sent);
const selectedProfileId = mailModuleInstalled ? getText(server, "mail_profile_id") : "";
const selectedProfile = mailModuleInstalled ? mailProfiles.find((profile) => profile.id === selectedProfileId) ?? null : null;
const usingMailProfile = mailModuleInstalled && Boolean(selectedProfileId);
const selectedProfileHasImap = Boolean(selectedProfile?.imap);
const imapUnavailable = usingMailProfile && !selectedProfileHasImap;
const smtpCredentialsInherited = usingMailProfile ? effectiveMailPolicy?.smtp_credentials?.inherit !== false : false;
const imapCredentialsInherited = usingMailProfile ? effectiveMailPolicy?.imap_credentials?.inherit !== false : false;
const effectiveMailPolicyForState: MailProfilePolicy | null = mailModuleInstalled ? effectiveMailPolicy : { allow_campaign_profiles: true };
const mailPolicyState = campaignMailSettingsPolicyState({ effectivePolicy: effectiveMailPolicyForState, selectedProfileId, locked });
const campaignProfilesAllowed = mailPolicyState.campaignProfilesAllowed;
const inlineMailSettingsBlocked = mailPolicyState.inlineMailSettingsBlocked;
const smtpDisabled = locked || usingMailProfile || inlineMailSettingsBlocked;
const smtpCredentialDisabled = locked || inlineMailSettingsBlocked || usingMailProfile && smtpCredentialsInherited;
const imapServerDisabled = locked || inlineMailSettingsBlocked || usingMailProfile;
const imapCredentialDisabled = locked || inlineMailSettingsBlocked || imapUnavailable || usingMailProfile && imapCredentialsInherited;
const imapDisabled = locked || inlineMailSettingsBlocked || imapUnavailable;
const imapAppendEnabled = getBool(imapAppend, "enabled");
const appendTargetFolderDisabled = imapDisabled || !imapAppendEnabled;
const inlinePolicyMessages = useMemo(() => {
const validateMailPolicy = mailProfilesUi?.validateMailPolicy;
if (!mailModuleInstalled || usingMailProfile || !validateMailPolicy || !effectiveMailPolicy) return [];
const recipients = asRecord(displayDraft.recipients);
const fromEmail = firstAddressEmail(recipients.from);
return validateMailPolicy(effectiveMailPolicy, {
smtpHost: getText(smtp, "host"),
imapHost: getText(imap, "host"),
envelopeSender: fromEmail || getText(smtpCredentials, "username", getText(smtp, "username")),
fromHeader: fromEmail,
recipientDomains: collectRecipientDomains(displayDraft)
});
}, [displayDraft, effectiveMailPolicy, imap, mailModuleInstalled, mailProfilesUi, smtp, smtpCredentials, usingMailProfile]);
const displayedSmtp = {
host: usingMailProfile && selectedProfile ? stringOrEmpty(selectedProfile.smtp.host) : getText(smtp, "host"),
port: usingMailProfile && selectedProfile ? selectedProfile.smtp.port ?? 587 : getNumber(smtp, "port", 587),
username: usingMailProfile && smtpCredentialsInherited ? profileUsername(selectedProfile, "smtp") : getText(smtpCredentials, "username", getText(smtp, "username")),
password: usingMailProfile && smtpCredentialsInherited ? "" : getText(smtpCredentials, "password", getText(smtp, "password")),
security: usingMailProfile && selectedProfile ? selectedProfile.smtp.security ?? "starttls" : getText(smtp, "security", "starttls"),
timeout_seconds: usingMailProfile && selectedProfile ? selectedProfile.smtp.timeout_seconds ?? 30 : getNumber(smtp, "timeout_seconds", 30)
};
const displayedImap = {
host: usingMailProfile && selectedProfile?.imap ? stringOrEmpty(selectedProfile.imap.host) : getText(imap, "host"),
port: usingMailProfile && selectedProfile?.imap ? selectedProfile.imap.port ?? 993 : getNumber(imap, "port", 993),
username: usingMailProfile && imapCredentialsInherited ? profileUsername(selectedProfile, "imap") : getText(imapCredentials, "username", getText(imap, "username")),
password: usingMailProfile && imapCredentialsInherited ? "" : getText(imapCredentials, "password", getText(imap, "password")),
security: usingMailProfile && selectedProfile?.imap ? selectedProfile.imap.security ?? "tls" : getText(imap, "security", "tls"),
sent_folder: usingMailProfile && selectedProfile?.imap ? selectedProfile.imap.sent_folder ?? "auto" : getText(imap, "sent_folder", "auto"),
timeout_seconds: usingMailProfile && selectedProfile?.imap ? selectedProfile.imap.timeout_seconds ?? 30 : getNumber(imap, "timeout_seconds", 30)
};
const selectedProfileNeedsLocalCredentials = usingMailProfile && (!smtpCredentialsInherited || selectedProfileHasImap && !imapCredentialsInherited);
useEffect(() => {
if (!mailModuleInstalled) {
setMailProfiles([]);
setPolicyProfiles([]);
setEffectiveMailPolicy({ allow_campaign_profiles: true });
setProfileError("");
setProfilesLoading(false);
return;
}
void refreshMailProfiles();
}, [settings.apiBaseUrl, settings.apiKey, settings.accessToken, campaignId, mailModuleInstalled]);
async function refreshMailProfiles() {
if (!mailModuleInstalled) return;
setProfilesLoading(true);
setProfileError("");
try {
const [allowedProfiles, visibleProfiles, policyResponse] = await Promise.all([
listMailServerProfiles(settings, false, campaignId),
listMailServerProfiles(settings, true),
getMailProfilePolicy(settings, "campaign", campaignId, campaignId)]
);
setMailProfiles(allowedProfiles);
setPolicyProfiles(visibleProfiles);
setEffectiveMailPolicy(policyResponse.effective_policy ?? null);
} catch (err) {
setMailProfiles([]);
setPolicyProfiles([]);
setEffectiveMailPolicy(null);
setProfileError(err instanceof Error ? err.message : String(err));
} finally {
setProfilesLoading(false);
}
}
function normalizeMailSettingsBeforeSave(value: Record<string, unknown>): Record<string, unknown> {
if (mailModuleInstalled === false) return value;
const next = cloneJson(value);
const nextServer = { ...asRecord(next.server) };
const profileId = getText(nextServer, "mail_profile_id");
if (profileId.length === 0) return next;
const nextCredentials = { ...asRecord(nextServer.credentials) };
normalizeProfileCredentialProtocol(nextServer, nextCredentials, "smtp", effectiveMailPolicy?.smtp_credentials?.inherit === false ? false : true);
normalizeProfileCredentialProtocol(nextServer, nextCredentials, "imap", effectiveMailPolicy?.imap_credentials?.inherit === false ? false : true);
nextServer.credentials = nextCredentials;
next.server = nextServer;
return next;
}
function normalizeProfileCredentialProtocol(
serverValue: Record<string, unknown>,
credentialsValue: Record<string, unknown>,
protocol: "smtp" | "imap",
inherit: boolean)
{
serverValue["inherit_" + protocol + "_credentials"] = inherit;
if (inherit === false) return;
const transport = { ...asRecord(serverValue[protocol]) };
delete transport.username;
delete transport.password;
serverValue[protocol] = transport;
credentialsValue[protocol] = {};
}
function selectMailProfile(profileId: string) {
if (!mailModuleInstalled || locked) return;
if (!profileId && !campaignProfilesAllowed) {
setProfileError(mailPolicyState.inlineBlockedMessage);
return;
}
patch(["server", "mail_profile_id"], profileId);
setProfileMessage("");
setProfileError("");
if (profileId) {
patch(["server", "smtp"], {});
patch(["server", "imap"], {});
patch(["server", "credentials"], {});
setSmtpTestResult(null);
setImapTestResult(null);
setFolderResult(null);
}
}
async function saveCurrentSettingsAsProfile() {
if (!mailModuleInstalled || locked || usingMailProfile || !campaignProfilesAllowed) return;
setProfilesLoading(true);
setProfileMessage("");
setProfileError("");
try {
const created = await createMailServerProfile(settings, {
name: profileName.trim() || `${data.campaign?.name || "Campaign"} mail profile`,
scope_type: "campaign",
scope_id: campaignId,
smtp: smtpServerPayload(),
imap: hasInlineImapSettings() ? imapServerPayload() : null,
credentials: mailProfileCredentialsPayload(false),
is_active: true
});
setMailProfiles((current) => [...current.filter((profile) => profile.id !== created.id), created].sort((a, b) => a.name.localeCompare(b.name)));
patch(["server", "mail_profile_id"], created.id);
patch(["server", "smtp"], {});
patch(["server", "imap"], {});
patch(["server", "credentials"], {});
setProfileName("");
setProfileMessage(`Saved profile ${created.name}.`);
} catch (err) {
setProfileError(err instanceof Error ? err.message : String(err));
} finally {
setProfilesLoading(false);
}
}
function patchSmtpSettings(patchValue: Partial<MailServerSmtpSettings>) {
if (patchValue.host !== undefined) patch(["server", "smtp", "host"], String(patchValue.host ?? ""));
if (patchValue.port !== undefined) patch(["server", "smtp", "port"], mailNumberOrNull(patchValue.port));
if (patchValue.security !== undefined) patch(["server", "smtp", "security"], String(patchValue.security || "starttls"));
if (patchValue.timeout_seconds !== undefined) patch(["server", "smtp", "timeout_seconds"], mailNumberOrDefault(patchValue.timeout_seconds, 30));
}
function patchImapSettings(patchValue: Partial<MailServerImapSettings>) {
if (patchValue.host !== undefined) patch(["server", "imap", "host"], String(patchValue.host ?? ""));
if (patchValue.port !== undefined) patch(["server", "imap", "port"], mailNumberOrNull(patchValue.port));
if (patchValue.security !== undefined) patch(["server", "imap", "security"], String(patchValue.security || "tls"));
if (patchValue.sent_folder !== undefined) patch(["server", "imap", "sent_folder"], String(patchValue.sent_folder ?? ""));
if (patchValue.timeout_seconds !== undefined) patch(["server", "imap", "timeout_seconds"], mailNumberOrDefault(patchValue.timeout_seconds, 30));
}
function patchSmtpCredentials(patchValue: Partial<MailServerCredentialSettings>) {
if (patchValue.username !== undefined) patch(["server", "credentials", "smtp", "username"], String(patchValue.username ?? ""));
if (patchValue.password !== undefined) patch(["server", "credentials", "smtp", "password"], String(patchValue.password ?? ""));
}
function patchImapCredentials(patchValue: Partial<MailServerCredentialSettings>) {
if (patchValue.username !== undefined) patch(["server", "credentials", "imap", "username"], String(patchValue.username ?? ""));
if (patchValue.password !== undefined) patch(["server", "credentials", "imap", "password"], String(patchValue.password ?? ""));
}
function profileScopeLabel(profile: MailServerProfile): string {
if (profile.scope_type === "system") return "system";
if (profile.scope_type === "tenant") return "tenant";
if (profile.scope_type === "user") return "user";
if (profile.scope_type === "group") return "group";
return "campaign";
}
function smtpServerPayload() {
return mailSmtpSettingsPayload<MailSecurity>(
{ host: getText(smtp, "host"), port: getNumber(smtp, "port", 587), security: getText(smtp, "security", "starttls"), timeout_seconds: getNumber(smtp, "timeout_seconds", 30) },
{ fallbackSecurity: "starttls", allowedSecurity: securityOptions }
);
}
function imapServerPayload() {
return mailImapSettingsPayload<MailSecurity>(
{ host: getText(imap, "host"), port: getNumber(imap, "port", 993), security: getText(imap, "security", "tls"), sent_folder: getText(imap, "sent_folder", "auto"), timeout_seconds: getNumber(imap, "timeout_seconds", 30) },
{ fallbackSecurity: "tls", allowedSecurity: securityOptions }
);
}
function mailProfileCredentialsPayload(preserveBlankPassword: boolean) {
return {
smtp: mailTransportCredentialsPayloadFromRecords(smtpCredentials, smtp, preserveBlankPassword),
imap: mailTransportCredentialsPayloadFromRecords(imapCredentials, imap, preserveBlankPassword)
};
}
function rawSmtpPayload() {
const serverPayload = selectedProfile && !smtpCredentialsInherited ? selectedProfile.smtp : smtpServerPayload();
return { ...serverPayload, ...mailTransportCredentialsPayloadFromRecords(smtpCredentials, smtp, false) };
}
function rawImapPayload() {
const serverPayload = selectedProfile?.imap && !imapCredentialsInherited ? selectedProfile.imap : imapServerPayload();
return { ...serverPayload, ...mailTransportCredentialsPayloadFromRecords(imapCredentials, imap, false) };
}
function hasInlineImapSettings(): boolean {
return hasMailImapSettings([getText(imap, "host"), getText(imapCredentials, "username", getText(imap, "username")), getText(imapCredentials, "password", getText(imap, "password"))]);
}
function profileUsername(profile: MailServerProfile | null, protocol: "smtp" | "imap"): string {
if (!profile) return "";
if (protocol === "smtp") return stringOrEmpty(profile.credentials?.smtp?.username ?? profile.smtp.username);
return stringOrEmpty(profile.credentials?.imap?.username ?? profile.imap?.username);
}
async function runSmtpTest() {
if (!mailModuleInstalled) {
setSmtpTestResult({ ok: false, protocol: "smtp", message: "i18n:govoplan-campaign.install_and_enable_the_mail_module_to_test_smtp_.a7ce04e1", details: {} });
return;
}
if (locked || inlineMailSettingsBlocked) return;
setMailActionState("smtp");
setLocalError("");
try {
setSmtpTestResult(selectedProfileId && smtpCredentialsInherited ?
await testMailProfileSmtp(settings, selectedProfileId) :
await testSmtpSettings(settings, rawSmtpPayload()));
} catch (err) {
setSmtpTestResult({ ok: false, protocol: "smtp", message: err instanceof Error ? err.message : String(err), details: {} });
} finally {
setMailActionState(null);
}
}
async function runImapTest() {
if (!mailModuleInstalled) {
setImapTestResult({ ok: false, protocol: "imap", message: "i18n:govoplan-campaign.install_and_enable_the_mail_module_to_test_imap_.d6537dfd", details: {} });
return;
}
if (imapDisabled) return;
setMailActionState("imap");
setLocalError("");
try {
setImapTestResult(selectedProfileId && imapCredentialsInherited ?
await testMailProfileImap(settings, selectedProfileId) :
await testImapSettings(settings, rawImapPayload()));
} catch (err) {
setImapTestResult({ ok: false, protocol: "imap", message: err instanceof Error ? err.message : String(err), details: {} });
} finally {
setMailActionState(null);
}
}
async function runFolderLookup() {
if (!mailModuleInstalled) {
setFolderResult({ ok: false, protocol: "imap", message: "i18n:govoplan-campaign.install_and_enable_the_mail_module_to_inspect_im.52535774", folders: [], details: {} });
return;
}
if (appendTargetFolderDisabled) return;
setMailActionState("folders");
setLocalError("");
try {
setFolderResult(selectedProfileId && imapCredentialsInherited ?
await listMailProfileImapFolders(settings, selectedProfileId) :
await listImapFolders(settings, rawImapPayload()));
} 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 || appendTargetFolderDisabled) return;
patch(["delivery", "imap_append_sent", "folder"], folder);
}
return (
<div className="content-pad workspace-data-page">
<div className="page-heading split workspace-heading">
<div>
<PageTitle loading={loading}>{isPolicyView ? "i18n:govoplan-campaign.mail_policy.3eb5d32a" : "i18n:govoplan-campaign.mail_settings.19e07f55"}</PageTitle>
<VersionLine version={version} versions={data.versions} status={saveState} />
</div>
<div className="button-row compact-actions">
<Button onClick={reload} disabled={loading}>i18n:govoplan-campaign.reload.cce71553</Button>
{!isPolicyView && <Button variant="primary" onClick={() => saveDraft("manual")} disabled={!dirty || locked || !draft || inlineMailSettingsBlocked}>{dirty ? "i18n:govoplan-campaign.save_now.3989b7c0" : "i18n:govoplan-campaign.saved.c0ae8f6e"}</Button>}
</div>
</div>
{error && <DismissibleAlert tone="danger" resetKey={error} floating>{error}</DismissibleAlert>}
{localError && <DismissibleAlert tone="danger" resetKey={localError} floating>{localError}</DismissibleAlert>}
{locked && <LockedVersionNotice settings={settings} campaignId={campaignId} version={version} currentVersionId={data.campaign?.current_version_id} reload={reload} message="i18n:govoplan-campaign.this_page_is_read_only_for_the_selected_version.dacf5743" />}
<LoadingFrame loading={loading || !draft} label="i18n:govoplan-campaign.loading_campaign_draft.1cf47e50">
<>
{!mailModuleInstalled &&
<DismissibleAlert tone="info" dismissible={false}>
i18n:govoplan-campaign.the_mail_module_is_not_installed_inline_smtp_and.8e0f802e
</DismissibleAlert>
}
{isPolicyView && mailModuleInstalled && MailProfilePolicyEditor &&
<MailProfilePolicyEditor
settings={settings}
scopeType="campaign"
scopeId={campaignId}
campaignId={campaignId}
profiles={policyProfiles}
ownerUserId={data.campaign?.owner_user_id}
ownerGroupId={data.campaign?.owner_group_id}
canWrite={!locked}
locked={locked}
title="i18n:govoplan-campaign.campaign_local_mail_policy.94f59da0"
description="i18n:govoplan-campaign.campaign_local_mail_limits_applied_after_system_.e7f9bb31"
onSaved={refreshMailProfiles} />
}
{isPolicyView && mailModuleInstalled && !MailProfilePolicyEditor &&
<DismissibleAlert tone="warning" dismissible={false}>i18n:govoplan-campaign.the_mail_module_did_not_expose_profile_managemen.2cdb57e1</DismissibleAlert>
}
{!isPolicyView && mailModuleInstalled &&
<Card
title="i18n:govoplan-campaign.reusable_mail_profile.f9c9aab1"
actions={
<div className="button-row compact-actions">
<Button onClick={() => void refreshMailProfiles()} disabled={profilesLoading}>{profilesLoading ? "i18n:govoplan-campaign.loading.33ce4174" : "i18n:govoplan-campaign.reload_profiles.0fe100d1"}</Button>
<Button variant="primary" onClick={() => void saveCurrentSettingsAsProfile()} disabled={locked || usingMailProfile || profilesLoading || !campaignProfilesAllowed}>{profilesLoading ? "i18n:govoplan-campaign.saving.56a2285c" : "i18n:govoplan-campaign.save_current_settings_as_profile.7578a50b"}</Button>
</div>
}>
<div className="form-grid compact responsive-form-grid">
<FormField label="i18n:govoplan-campaign.profile.ff4fc027">
<select value={selectedProfileId} disabled={locked || profilesLoading} onChange={(event) => selectMailProfile(event.target.value)}>
<option value="" disabled={mailPolicyState.inlineOptionDisabled}>i18n:govoplan-campaign.inline_smtp_imap_settings.cf16c421</option>
{mailProfiles.map((profile) => <option key={profile.id} value={profile.id}>{profile.name} ({profileScopeLabel(profile)})</option>)}
</select>
</FormField>
<FormField label="i18n:govoplan-campaign.new_profile_name.393313b6">
<input value={profileName} disabled={locked || usingMailProfile || profilesLoading || !campaignProfilesAllowed} onChange={(event) => setProfileName(event.target.value)} placeholder={data.campaign?.name ? i18nMessage("i18n:govoplan-campaign.value_campaign_local_profile.70cd9d43", { value0: data.campaign.name }) : "i18n:govoplan-campaign.campaign_local_profile.c24cf2d1"} />
</FormField>
</div>
{!campaignProfilesAllowed && <p className="muted small-note">i18n:govoplan-campaign.campaign_local_mail_settings_are_blocked_by_the_.0b2510aa</p>}
{selectedProfile &&
<p className="muted small-note">i18n:govoplan-campaign.using.c25de2e8 {selectedProfile.name} ({profileScopeLabel(selectedProfile)}i18n:govoplan-campaign.smtp_credentials.10f75c8a {smtpCredentialsInherited ? "i18n:govoplan-campaign.inherited_from_profile.1947c2f3" : "i18n:govoplan-campaign.local_credentials_required.fdc9af1c"}i18n:govoplan-campaign.imap_credentials.7442b238 {selectedProfile.imap ? imapCredentialsInherited ? "i18n:govoplan-campaign.inherited_from_profile.1947c2f3" : "i18n:govoplan-campaign.local_credentials_required.fdc9af1c" : "i18n:govoplan-campaign.not_configured.67f2141f"}.</p>
}
{selectedProfileNeedsLocalCredentials &&
<p className="muted small-note">i18n:govoplan-campaign.the_selected_profile_supplies_the_server_setting.bdc830db</p>
}
{profileMessage && <DismissibleAlert tone="success" resetKey={profileMessage} floating>{profileMessage}</DismissibleAlert>}
{profileError && <DismissibleAlert tone="warning" resetKey={profileError} dismissStorageKey={`campaign:${campaignId}:mail-settings:profile-error`} floating>{profileError}</DismissibleAlert>}
</Card>
}
{!isPolicyView &&
<Card title="i18n:govoplan-campaign.mail_server_settings.6db620b0" collapsible>
{inlinePolicyMessages.length > 0 &&
<DismissibleAlert tone="warning" resetKey={inlinePolicyMessages.map((item) => `${item.key}:${item.value}`).join("|")} dismissible={false}>
<strong>i18n:govoplan-campaign.effective_mail_policy_blocks_the_current_inline_.99c34c6b</strong>
<ul>{inlinePolicyMessages.map((item) => <li key={`${item.key}:${item.value}`}>{item.message}</li>)}</ul>
</DismissibleAlert>
}
<MailServerSettingsPanel
smtp={displayedSmtp}
imap={displayedImap}
onSmtpChange={patchSmtpSettings}
onImapChange={patchImapSettings}
smtpCredentials={{ username: displayedSmtp.username, password: displayedSmtp.password }}
imapCredentials={{ username: displayedImap.username, password: displayedImap.password }}
onSmtpCredentialsChange={patchSmtpCredentials}
onImapCredentialsChange={patchImapCredentials}
smtpDisabled={smtpDisabled}
smtpCredentialDisabled={smtpCredentialDisabled}
smtpPasswordSaved={Boolean(usingMailProfile && smtpCredentialsInherited && selectedProfile?.smtp_password_configured)}
smtpActionDisabled={locked || inlineMailSettingsBlocked || !mailModuleInstalled}
imapServerDisabled={imapServerDisabled}
imapCredentialDisabled={imapCredentialDisabled}
imapPasswordSaved={Boolean(usingMailProfile && imapCredentialsInherited && selectedProfile?.imap_password_configured)}
imapActionDisabled={imapDisabled || !mailModuleInstalled}
append={{
enabled: imapAppendEnabled,
folder: getText(imapAppend, "folder", getText(imap, "sent_folder", "auto")),
disabled: imapDisabled,
folderDisabled: appendTargetFolderDisabled,
onEnabledChange: (checked) => patch(["delivery", "imap_append_sent", "enabled"], checked),
onFolderChange: (folder) => patch(["delivery", "imap_append_sent", "folder"], folder)
}}
smtpTestLabel={usingMailProfile ? "i18n:govoplan-campaign.test_profile_smtp.884a0e66" : "i18n:govoplan-campaign.test_smtp_login.a1359755"}
imapTestLabel={usingMailProfile ? "i18n:govoplan-campaign.test_profile_imap.e1cec0e0" : "i18n:govoplan-campaign.test_imap_login.c32e316e"}
busyAction={mailActionState}
onTestSmtp={runSmtpTest}
onTestImap={runImapTest}
onLookupFolders={runFolderLookup}
smtpTestResult={smtpTestResult}
imapTestResult={imapTestResult}
folderLookupResult={folderResult}
onUseDetectedFolder={useDetectedSentFolder}
useDetectedFolderDisabled={appendTargetFolderDisabled}
floatingResults />
</Card>
}
</>
</LoadingFrame>
</div>);
}
function stringOrEmpty(value: unknown): string {
return value === null || value === undefined ? "" : String(value);
}
function firstAddressEmail(value: unknown): string {
return addressesFromValue(value)[0]?.email ?? "";
}
function collectRecipientDomains(draft: Record<string, unknown>): string[] {
const domains = new Set<string>();
const recipients = asRecord(draft.recipients);
for (const key of ["to", "cc", "bcc"]) addAddressDomains(domains, recipients[key]);
const entries = asArray(asRecord(draft.entries).inline).map(asRecord);
for (const entry of entries) {
for (const key of ["to", "cc", "bcc"]) addAddressDomains(domains, entry[key]);
addAddressDomains(domains, entry.recipient);
addAddressDomains(domains, entry);
}
return [...domains].sort();
}
function addAddressDomains(domains: Set<string>, value: unknown) {
for (const address of addressesFromValue(value)) {
const domain = address.email.split("@").pop()?.trim().toLowerCase();
if (domain) domains.add(domain);
}
}