551 lines
30 KiB
TypeScript
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);
|
|
}
|
|
}
|