540 lines
27 KiB
TypeScript
540 lines
27 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
import {
|
|
Button,
|
|
Card,
|
|
Dialog,
|
|
DismissibleAlert,
|
|
FormField,
|
|
LoadingFrame,
|
|
MailServerFolderLookupResultView,
|
|
MetricCard,
|
|
PageTitle,
|
|
PasswordField,
|
|
ToggleSwitch,
|
|
usePlatformModuleInstalled,
|
|
usePlatformUiCapability,
|
|
type MailCredentialEnvelope,
|
|
type MailProfilesUiCapability,
|
|
type MailServerConnectionTestResult,
|
|
type MailServerEndpoint,
|
|
type MailServerFolderLookupResult
|
|
} from "@govoplan/core-webui";
|
|
import type { ApiSettings } from "../../types";
|
|
import {
|
|
createCampaignMailCredential,
|
|
listMailProfileImapFolders,
|
|
listMailServerProfiles,
|
|
testMailProfileImap,
|
|
testMailProfileSmtp,
|
|
type MailServerProfile
|
|
} from "../../api/mail";
|
|
import { useCampaignWorkspaceData } from "./hooks/useCampaignWorkspaceData";
|
|
import { useCampaignDraftEditor } from "./hooks/useCampaignDraftEditor";
|
|
import LockedVersionNotice from "./components/LockedVersionNotice";
|
|
import VersionLine from "./components/VersionLine";
|
|
import { asRecord, isAuditLockedVersion } from "./utils/campaignView";
|
|
import { getBool, getText } from "./utils/draftEditor";
|
|
import { campaignMailProfileReferenceOnly } from "./utils/mailProfileReference";
|
|
|
|
type MailSettingsView = "settings" | "policy";
|
|
|
|
type MailSettingsPageProps = {
|
|
settings: ApiSettings;
|
|
campaignId: string;
|
|
view?: MailSettingsView;
|
|
};
|
|
|
|
type CampaignCredentialDraft = {
|
|
name: string;
|
|
username: string;
|
|
password: string;
|
|
useSmtp: boolean;
|
|
useImap: boolean;
|
|
};
|
|
|
|
export default function MailSettingsPage({ settings, campaignId, view = "settings" }: MailSettingsPageProps) {
|
|
const mailModuleInstalled = usePlatformModuleInstalled("mail");
|
|
const mailProfilesUi = usePlatformUiCapability<MailProfilesUiCapability>("mail.profiles");
|
|
const MailProfilePolicyEditor = mailProfilesUi?.MailProfilePolicyEditor ?? null;
|
|
const isPolicyView = view === "policy";
|
|
const { data, loading, error, reload, setError } = useCampaignWorkspaceData(settings, campaignId);
|
|
const [mailProfiles, setMailProfiles] = useState<MailServerProfile[]>([]);
|
|
const [policyProfiles, setPolicyProfiles] = useState<MailServerProfile[]>([]);
|
|
const [profilesLoading, setProfilesLoading] = useState(false);
|
|
const [profileError, setProfileError] = useState("");
|
|
const [mailActionState, setMailActionState] = useState<"smtp" | "imap" | "folders" | null>(null);
|
|
const [smtpTestResult, setSmtpTestResult] = useState<MailServerConnectionTestResult | null>(null);
|
|
const [imapTestResult, setImapTestResult] = useState<MailServerConnectionTestResult | null>(null);
|
|
const [folderResult, setFolderResult] = useState<MailServerFolderLookupResult | null>(null);
|
|
const [credentialDialogOpen, setCredentialDialogOpen] = useState(false);
|
|
const [credentialSaving, setCredentialSaving] = useState(false);
|
|
const [credentialDraft, setCredentialDraft] = useState<CampaignCredentialDraft>({
|
|
name: "",
|
|
username: "",
|
|
password: "",
|
|
useSmtp: true,
|
|
useImap: false
|
|
});
|
|
|
|
const version = data.currentVersion;
|
|
const locked = isAuditLockedVersion(version, data.campaign?.current_version_id);
|
|
const migrationRequired = version?.mail_profile_migration_required === true;
|
|
const { draft, displayDraft, dirty, saveState, localError, setLocalError, patch, discardDraft, 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: campaignMailProfileReferenceOnly,
|
|
extraPayload: () => ({
|
|
migrate_legacy_mail_settings: migrationRequired && Boolean(selectedProfileId)
|
|
})
|
|
});
|
|
|
|
const server = asRecord(displayDraft.server);
|
|
const selectedProfileId = getText(server, "mail_profile_id");
|
|
const selectedProfile = mailProfiles.find((profile) => profile.id === selectedProfileId) ?? null;
|
|
const smtpServers = (selectedProfile?.servers ?? []).filter((item) => item.protocol === "smtp" && item.is_active);
|
|
const imapServers = (selectedProfile?.servers ?? []).filter((item) => item.protocol === "imap" && item.is_active);
|
|
const selectedSmtpServer = smtpServers.find((item) => item.id === getText(server, "smtp_server_id"))
|
|
?? smtpServers.find((item) => item.is_default)
|
|
?? smtpServers[0]
|
|
?? null;
|
|
const selectedImapServer = imapServers.find((item) => item.id === getText(server, "imap_server_id"))
|
|
?? imapServers.find((item) => item.is_default)
|
|
?? imapServers[0]
|
|
?? null;
|
|
const selectedSmtpCredential = selectedSmtpServer?.credentials.find((item) => item.id === getText(server, "smtp_credential_id"))
|
|
?? selectedSmtpServer?.credentials.find((item) => item.is_default)
|
|
?? selectedSmtpServer?.credentials[0]
|
|
?? null;
|
|
const selectedImapCredential = selectedImapServer?.credentials.find((item) => item.id === getText(server, "imap_credential_id"))
|
|
?? selectedImapServer?.credentials.find((item) => item.is_default)
|
|
?? selectedImapServer?.credentials[0]
|
|
?? null;
|
|
const delivery = asRecord(displayDraft.delivery);
|
|
const imapAppend = asRecord(delivery.imap_append_sent);
|
|
const imapAppendEnabled = getBool(imapAppend, "enabled");
|
|
const selectedProfileHasImap = Boolean(selectedImapServer);
|
|
const selectedProfileUnavailable = Boolean(selectedProfileId && !profilesLoading && !selectedProfile);
|
|
const canSave = dirty && !locked && Boolean(draft) && (!migrationRequired || Boolean(selectedProfileId));
|
|
|
|
useEffect(() => {
|
|
if (!mailModuleInstalled) {
|
|
setMailProfiles([]);
|
|
setPolicyProfiles([]);
|
|
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] = await Promise.all([
|
|
listMailServerProfiles(settings, false, campaignId),
|
|
listMailServerProfiles(settings, true)
|
|
]);
|
|
setMailProfiles(allowedProfiles);
|
|
setPolicyProfiles(visibleProfiles);
|
|
} catch (err) {
|
|
setMailProfiles([]);
|
|
setPolicyProfiles([]);
|
|
setProfileError(err instanceof Error ? err.message : String(err));
|
|
} finally {
|
|
setProfilesLoading(false);
|
|
}
|
|
}
|
|
|
|
function selectMailProfile(profileId: string) {
|
|
if (!mailModuleInstalled || locked) return;
|
|
const profile = mailProfiles.find((item) => item.id === profileId);
|
|
const smtpServer = profile?.servers?.find((item) => item.protocol === "smtp" && item.is_active && item.is_default)
|
|
?? profile?.servers?.find((item) => item.protocol === "smtp" && item.is_active);
|
|
const imapServer = profile?.servers?.find((item) => item.protocol === "imap" && item.is_active && item.is_default)
|
|
?? profile?.servers?.find((item) => item.protocol === "imap" && item.is_active);
|
|
const smtpCredential = smtpServer?.credentials.find((item) => item.is_active && item.is_default)
|
|
?? smtpServer?.credentials.find((item) => item.is_active);
|
|
const imapCredential = imapServer?.credentials.find((item) => item.is_active && item.is_default)
|
|
?? imapServer?.credentials.find((item) => item.is_active);
|
|
patch(["server"], profile ? mailReference(
|
|
profile.id,
|
|
smtpServer?.id,
|
|
smtpCredential?.id,
|
|
imapServer?.id,
|
|
imapCredential?.id
|
|
) : {});
|
|
setSmtpTestResult(null);
|
|
setImapTestResult(null);
|
|
setFolderResult(null);
|
|
setProfileError("");
|
|
}
|
|
|
|
function selectServer(protocol: "smtp" | "imap", serverId: string) {
|
|
if (!selectedProfile || locked) return;
|
|
const availableServers = protocol === "smtp" ? smtpServers : imapServers;
|
|
const selected = availableServers.find((item) => item.id === serverId) ?? null;
|
|
const credential = selected?.credentials.find((item) => item.is_active && item.is_default)
|
|
?? selected?.credentials.find((item) => item.is_active)
|
|
?? null;
|
|
patch(["server"], mailReference(
|
|
selectedProfile.id,
|
|
protocol === "smtp" ? selected?.id : selectedSmtpServer?.id,
|
|
protocol === "smtp" ? credential?.id : selectedSmtpCredential?.id,
|
|
protocol === "imap" ? selected?.id : selectedImapServer?.id,
|
|
protocol === "imap" ? credential?.id : selectedImapCredential?.id
|
|
));
|
|
if (protocol === "smtp") setSmtpTestResult(null);
|
|
else {
|
|
setImapTestResult(null);
|
|
setFolderResult(null);
|
|
}
|
|
}
|
|
|
|
function selectCredential(protocol: "smtp" | "imap", credentialId: string) {
|
|
if (!selectedProfile || locked) return;
|
|
patch(["server"], mailReference(
|
|
selectedProfile.id,
|
|
selectedSmtpServer?.id,
|
|
protocol === "smtp" ? credentialId : selectedSmtpCredential?.id,
|
|
selectedImapServer?.id,
|
|
protocol === "imap" ? credentialId : selectedImapCredential?.id
|
|
));
|
|
if (protocol === "smtp") setSmtpTestResult(null);
|
|
else {
|
|
setImapTestResult(null);
|
|
setFolderResult(null);
|
|
}
|
|
}
|
|
|
|
function openCredentialDialog() {
|
|
setCredentialDraft({
|
|
name: `${data.campaign?.name || "Campaign"} credential`,
|
|
username: "",
|
|
password: "",
|
|
useSmtp: Boolean(selectedSmtpServer),
|
|
useImap: Boolean(selectedImapServer)
|
|
});
|
|
setCredentialDialogOpen(true);
|
|
setLocalError("");
|
|
}
|
|
|
|
async function saveCampaignCredential() {
|
|
if (!selectedProfile || credentialSaving) return;
|
|
const serverIds = [
|
|
credentialDraft.useSmtp ? selectedSmtpServer?.id : null,
|
|
credentialDraft.useImap ? selectedImapServer?.id : null
|
|
].filter((value): value is string => Boolean(value));
|
|
if (
|
|
!credentialDraft.name.trim() ||
|
|
!credentialDraft.username.trim() ||
|
|
!credentialDraft.password ||
|
|
serverIds.length === 0
|
|
) return;
|
|
setCredentialSaving(true);
|
|
setLocalError("");
|
|
try {
|
|
const credential = await createCampaignMailCredential(
|
|
settings,
|
|
selectedProfile.id,
|
|
campaignId,
|
|
{
|
|
name: credentialDraft.name.trim(),
|
|
username: credentialDraft.username.trim(),
|
|
password: credentialDraft.password,
|
|
server_ids: serverIds
|
|
}
|
|
);
|
|
patch(["server"], mailReference(
|
|
selectedProfile.id,
|
|
selectedSmtpServer?.id,
|
|
credentialDraft.useSmtp ? credential.id : selectedSmtpCredential?.id,
|
|
selectedImapServer?.id,
|
|
credentialDraft.useImap ? credential.id : selectedImapCredential?.id
|
|
));
|
|
setCredentialDialogOpen(false);
|
|
await refreshMailProfiles();
|
|
} catch (err) {
|
|
setLocalError(err instanceof Error ? err.message : String(err));
|
|
} finally {
|
|
setCredentialSaving(false);
|
|
}
|
|
}
|
|
|
|
async function runProfileTest(protocol: "smtp" | "imap") {
|
|
if (!selectedProfileId || locked) return;
|
|
setMailActionState(protocol);
|
|
setLocalError("");
|
|
try {
|
|
if (protocol === "smtp") {
|
|
setSmtpTestResult(await testMailProfileSmtp(
|
|
settings,
|
|
selectedProfileId,
|
|
selectedSmtpServer?.id,
|
|
selectedSmtpCredential?.id,
|
|
campaignId
|
|
));
|
|
} else {
|
|
setImapTestResult(await testMailProfileImap(
|
|
settings,
|
|
selectedProfileId,
|
|
selectedImapServer?.id,
|
|
selectedImapCredential?.id,
|
|
campaignId
|
|
));
|
|
}
|
|
} catch (err) {
|
|
const result = { ok: false, protocol, message: err instanceof Error ? err.message : String(err), details: {} };
|
|
if (protocol === "smtp") setSmtpTestResult(result);
|
|
else setImapTestResult(result);
|
|
} finally {
|
|
setMailActionState(null);
|
|
}
|
|
}
|
|
|
|
async function runFolderLookup() {
|
|
if (!selectedProfileId || locked || !selectedProfileHasImap) return;
|
|
setMailActionState("folders");
|
|
setLocalError("");
|
|
try {
|
|
setFolderResult(await listMailProfileImapFolders(
|
|
settings,
|
|
selectedProfileId,
|
|
selectedImapServer?.id,
|
|
selectedImapCredential?.id,
|
|
campaignId
|
|
));
|
|
} 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 && !locked) 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">
|
|
{!isPolicyView && <Button variant="primary" onClick={() => void saveDraft("manual")} disabled={!canSave}>{dirty ? "i18n:govoplan-campaign.save_now.3989b7c0" : "i18n:govoplan-campaign.saved.c0ae8f6e"}</Button>}
|
|
<Button onClick={() => void discardDraft()} disabled={loading}>i18n:govoplan-campaign.discard.36fff63c</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="warning" dismissible={false}>i18n:govoplan-campaign.install_and_enable_the_mail_module_to_select_a_d.01c75fc4</DismissibleAlert>}
|
|
|
|
{migrationRequired && <DismissibleAlert tone="warning" dismissible={false}>
|
|
i18n:govoplan-campaign.this_version_contains_legacy_campaign_local_mail.44c7a6fd
|
|
</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.mail_policy_limits_which_mail_owned_profiles_thi.824b1b2e"
|
|
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={openCredentialDialog} disabled={locked || profilesLoading || !selectedProfile || !selectedSmtpServer && !selectedImapServer}>Add campaign credential</Button>
|
|
</div>}>
|
|
<p className="muted small-note">The campaign stores stable references to the envelope, selected servers, and credentials.</p>
|
|
<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="">i18n:govoplan-campaign.select_a_mail_profile.76480af0</option>
|
|
{mailProfiles.map((profile) => <option key={profile.id} value={profile.id}>{profile.name} ({profileScopeLabel(profile)})</option>)}
|
|
</select>
|
|
</FormField>
|
|
<FormField label="SMTP server">
|
|
<select value={selectedSmtpServer?.id ?? ""} disabled={locked || profilesLoading || !selectedProfile} onChange={(event) => selectServer("smtp", event.target.value)}>
|
|
<option value="">No SMTP server</option>
|
|
{smtpServers.map((item) => <option key={item.id} value={item.id}>{item.name} ({serverEndpointLabel(item.config)})</option>)}
|
|
</select>
|
|
</FormField>
|
|
<FormField label="SMTP credential">
|
|
<select value={selectedSmtpCredential?.id ?? ""} disabled={locked || profilesLoading || !selectedSmtpServer} onChange={(event) => selectCredential("smtp", event.target.value)}>
|
|
<option value="">No credential</option>
|
|
{selectedSmtpServer?.credentials.filter((item) => item.is_active).map((item) =>
|
|
<option key={item.id} value={item.id}>{credentialLabel(item)}</option>
|
|
)}
|
|
</select>
|
|
</FormField>
|
|
<FormField label="IMAP server">
|
|
<select value={selectedImapServer?.id ?? ""} disabled={locked || profilesLoading || !selectedProfile} onChange={(event) => selectServer("imap", event.target.value)}>
|
|
<option value="">No IMAP server</option>
|
|
{imapServers.map((item) => <option key={item.id} value={item.id}>{item.name} ({serverEndpointLabel(item.config)})</option>)}
|
|
</select>
|
|
</FormField>
|
|
<FormField label="IMAP credential">
|
|
<select value={selectedImapCredential?.id ?? ""} disabled={locked || profilesLoading || !selectedImapServer} onChange={(event) => selectCredential("imap", event.target.value)}>
|
|
<option value="">No credential</option>
|
|
{selectedImapServer?.credentials.filter((item) => item.is_active).map((item) =>
|
|
<option key={item.id} value={item.id}>{credentialLabel(item)}</option>
|
|
)}
|
|
</select>
|
|
</FormField>
|
|
</div>
|
|
{selectedProfileUnavailable && <DismissibleAlert tone="warning" dismissible={false}>i18n:govoplan-campaign.the_referenced_mail_profile_is_inactive_unavaila.abeebe26</DismissibleAlert>}
|
|
{selectedProfile && <div className="metric-grid inside">
|
|
<MetricCard label="i18n:govoplan-campaign.profile.ff4fc027" value={selectedProfile.name} />
|
|
<MetricCard label="i18n:govoplan-campaign.scope.4651a34e" value={profileScopeLabel(selectedProfile)} />
|
|
<MetricCard label="i18n:govoplan-campaign.smtp.efff9cca" value={selectedSmtpServer?.name || "i18n:govoplan-campaign.not_configured.811931bb"} tone={selectedSmtpServer ? "good" : "neutral"} />
|
|
<MetricCard label="i18n:govoplan-campaign.imap.271f9ef2" value={selectedImapServer?.name || "i18n:govoplan-campaign.not_configured.811931bb"} tone={selectedImapServer ? "good" : "neutral"} />
|
|
</div>}
|
|
<div className="button-row compact-actions">
|
|
<Button onClick={() => void runProfileTest("smtp")} disabled={!selectedSmtpServer || locked || Boolean(mailActionState)}>{mailActionState === "smtp" ? "i18n:govoplan-campaign.testing_smtp.8e9f8247" : "i18n:govoplan-campaign.test_profile_smtp.884a0e66"}</Button>
|
|
<Button onClick={() => void runProfileTest("imap")} disabled={!selectedProfileHasImap || locked || Boolean(mailActionState)}>{mailActionState === "imap" ? "i18n:govoplan-campaign.testing_imap.13d255cf" : "i18n:govoplan-campaign.test_profile_imap.e1cec0e0"}</Button>
|
|
</div>
|
|
{smtpTestResult && <DismissibleAlert tone={smtpTestResult.ok ? "success" : "danger"} resetKey={`${smtpTestResult.protocol}:${smtpTestResult.message}`} floating>{smtpTestResult.message}</DismissibleAlert>}
|
|
{imapTestResult && <DismissibleAlert tone={imapTestResult.ok ? "success" : "danger"} resetKey={`${imapTestResult.protocol}:${imapTestResult.message}`} floating>{imapTestResult.message}</DismissibleAlert>}
|
|
{profileError && <DismissibleAlert tone="warning" resetKey={profileError} floating>{profileError}</DismissibleAlert>}
|
|
</Card>}
|
|
|
|
<Dialog
|
|
open={credentialDialogOpen}
|
|
title="Add campaign credential"
|
|
onClose={() => setCredentialDialogOpen(false)}
|
|
closeDisabled={credentialSaving}
|
|
className="admin-dialog admin-dialog-wide adaptive-config-dialog"
|
|
footerClassName="button-row compact-actions"
|
|
footer={
|
|
<>
|
|
<Button onClick={() => setCredentialDialogOpen(false)} disabled={credentialSaving}>Cancel</Button>
|
|
<Button
|
|
variant="primary"
|
|
onClick={() => void saveCampaignCredential()}
|
|
disabled={
|
|
credentialSaving ||
|
|
!credentialDraft.name.trim() ||
|
|
!credentialDraft.username.trim() ||
|
|
!credentialDraft.password ||
|
|
!credentialDraft.useSmtp && !credentialDraft.useImap
|
|
}
|
|
>
|
|
{credentialSaving ? "Saving" : "Save credential"}
|
|
</Button>
|
|
</>
|
|
}
|
|
>
|
|
<div className="adaptive-config-form">
|
|
<section className="adaptive-config-section">
|
|
<header>
|
|
<h3>Campaign login</h3>
|
|
<p>This credential belongs to this campaign and can use only the selected mail servers.</p>
|
|
</header>
|
|
<div className="form-grid two">
|
|
<FormField label="Name">
|
|
<input value={credentialDraft.name} disabled={credentialSaving} onChange={(event) => setCredentialDraft({ ...credentialDraft, name: event.target.value })} autoFocus />
|
|
</FormField>
|
|
<FormField label="Username">
|
|
<input value={credentialDraft.username} disabled={credentialSaving} onChange={(event) => setCredentialDraft({ ...credentialDraft, username: event.target.value })} />
|
|
</FormField>
|
|
<FormField label="Password">
|
|
<PasswordField value={credentialDraft.password} onValueChange={(password) => setCredentialDraft({ ...credentialDraft, password })} disabled={credentialSaving} autoComplete="new-password" />
|
|
</FormField>
|
|
</div>
|
|
<div className="form-grid two">
|
|
{selectedSmtpServer && <ToggleSwitch checked={credentialDraft.useSmtp} disabled={credentialSaving} onChange={(useSmtp) => setCredentialDraft({ ...credentialDraft, useSmtp })} label={`Use for SMTP: ${selectedSmtpServer.name}`} />}
|
|
{selectedImapServer && <ToggleSwitch checked={credentialDraft.useImap} disabled={credentialSaving} onChange={(useImap) => setCredentialDraft({ ...credentialDraft, useImap })} label={`Use for IMAP: ${selectedImapServer.name}`} />}
|
|
</div>
|
|
</section>
|
|
</div>
|
|
</Dialog>
|
|
|
|
{!isPolicyView && <Card title="i18n:govoplan-campaign.imap_append.8c0d9e96" collapsible>
|
|
<div className="form-grid compact responsive-form-grid mail-server-form-grid">
|
|
<div className="mail-server-field-span mail-server-toggle-row mail-server-plain-toggle-row">
|
|
<ToggleSwitch
|
|
label="i18n:govoplan-campaign.append_successful_messages_to_sent_via_imap.dbd1b1d8"
|
|
checked={imapAppendEnabled}
|
|
disabled={locked || !selectedProfileHasImap}
|
|
onChange={(checked) => patch(["delivery", "imap_append_sent", "enabled"], checked)} />
|
|
</div>
|
|
<FormField label="i18n:govoplan-core.append_target_folder.0aaacc0c" help="i18n:govoplan-core.folder_for_sent_message_copies_leave_as_auto_unl.a62586e9">
|
|
<div className="field-with-action mail-server-folder-field">
|
|
<input
|
|
value={getText(imapAppend, "folder", "auto")}
|
|
disabled={locked || !imapAppendEnabled || !selectedProfileHasImap}
|
|
onChange={(event) => patch(["delivery", "imap_append_sent", "folder"], event.target.value)} />
|
|
<Button type="button" variant="primary" onClick={() => void runFolderLookup()} disabled={locked || !imapAppendEnabled || !selectedProfileHasImap || Boolean(mailActionState)}>
|
|
{mailActionState === "folders" ? "i18n:govoplan-core.looking_up.5fc6d2a2" : "i18n:govoplan-core.folders.c603ab65"}
|
|
</Button>
|
|
</div>
|
|
</FormField>
|
|
<MailServerFolderLookupResultView result={folderResult} disabled={locked || !imapAppendEnabled} onUseDetected={useDetectedSentFolder} floatingFailures />
|
|
</div>
|
|
</Card>}
|
|
</>
|
|
</LoadingFrame>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
function profileScopeLabel(profile: MailServerProfile): string {
|
|
if (profile.scope_type === "system") return "i18n:govoplan-campaign.system.bc0792d8";
|
|
if (profile.scope_type === "tenant") return "i18n:govoplan-campaign.tenant.3ca93c78";
|
|
if (profile.scope_type === "user") return "i18n:govoplan-campaign.user.9f8a2389";
|
|
if (profile.scope_type === "group") return "i18n:govoplan-campaign.group.171a0606";
|
|
return "i18n:govoplan-campaign.campaign_scoped_mail_profile.9cbf3505";
|
|
}
|
|
|
|
function mailReference(
|
|
profileId: string,
|
|
smtpServerId?: string | null,
|
|
smtpCredentialId?: string | null,
|
|
imapServerId?: string | null,
|
|
imapCredentialId?: string | null
|
|
): Record<string, string> {
|
|
const value: Record<string, string> = { mail_profile_id: profileId };
|
|
if (smtpServerId) value.smtp_server_id = smtpServerId;
|
|
if (smtpServerId && smtpCredentialId) value.smtp_credential_id = smtpCredentialId;
|
|
if (imapServerId) value.imap_server_id = imapServerId;
|
|
if (imapServerId && imapCredentialId) value.imap_credential_id = imapCredentialId;
|
|
return value;
|
|
}
|
|
|
|
function serverEndpointLabel(config: MailServerEndpoint["config"]): string {
|
|
const host = typeof config.host === "string" && config.host ? config.host : "No host";
|
|
return config.port ? `${host}:${config.port}` : host;
|
|
}
|
|
|
|
function credentialLabel(credential: MailCredentialEnvelope): string {
|
|
const username = credential.public_data?.username;
|
|
return username ? `${credential.name} (${String(username)})` : credential.name;
|
|
}
|