Refine reusable mail profile credential editing
This commit is contained in:
Generated
+3
-104
@@ -13,9 +13,9 @@
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.10",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": ">=7.18.2 <8"
|
||||
"react": ">=19.2.7 <20",
|
||||
"react-dom": ">=19.2.7 <20",
|
||||
"react-router": ">=8.3.0 <9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@govoplan/core-webui": {
|
||||
@@ -23,107 +23,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/cookie": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz",
|
||||
"integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/express"
|
||||
}
|
||||
},
|
||||
"node_modules/lucide-react": {
|
||||
"version": "1.24.0",
|
||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-1.24.0.tgz",
|
||||
"integrity": "sha512-YT6mBD8lGKkg4nM39enlm94/sfJIiW0YKUT60fBy4YK8tai31ylg1VhGNWxkpSKHo9UagfnZqwIff3HTDQwXeA==",
|
||||
"license": "ISC",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz",
|
||||
"integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-dom": {
|
||||
"version": "19.2.7",
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz",
|
||||
"integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.27.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^19.2.7"
|
||||
}
|
||||
},
|
||||
"node_modules/react-router": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz",
|
||||
"integrity": "sha512-aUVMjFm3GAPTTZL7oYr5E7ETiqfQCHRLH+B+5afnICvf0r7kkK4eR6SMuwbSTJw/7t+12khT/Kahij49fqOCIg==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"cookie": "^1.0.1",
|
||||
"set-cookie-parser": "^2.6.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"react-dom": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/react-router-dom": {
|
||||
"version": "7.18.2",
|
||||
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.2.tgz",
|
||||
"integrity": "sha512-AIKJ/jgGlFb3EbfCXk5Gzshiwt+l3mqbCrNjmEWMMjqQxNJ3svBa6bgzFyCC2Sw3RA0VWF1kg3uQf2OFhxb8hw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"react-router": "7.18.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=18",
|
||||
"react-dom": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/scheduler": {
|
||||
"version": "0.27.0",
|
||||
"resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
|
||||
"integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/set-cookie-parser": {
|
||||
"version": "2.7.2",
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz",
|
||||
"integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==",
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.9.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||
|
||||
+3
-3
@@ -16,9 +16,9 @@
|
||||
"peerDependencies": {
|
||||
"@govoplan/core-webui": "^0.1.10",
|
||||
"lucide-react": "^1.23.0",
|
||||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"react-router-dom": ">=7.18.2 <8"
|
||||
"react": ">=19.2.7 <20",
|
||||
"react-dom": ">=19.2.7 <20",
|
||||
"react-router": ">=8.3.0 <9"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@govoplan/core-webui": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
import { AdminSelectionList, ConnectionTree, FieldLabel, LoadingFrame, MailServerSettingsPanel, PolicyLockedHint, PolicyPathHelp, PolicyRow, PolicySourcePath, PolicyTable, StatusBadge, TableActionGroup, ToggleSwitch, hasMailImapSettings, mailImapSettingsPayload, mailServerSecurityOptions, mailSmtpSettingsPayload, mailTextOrNull, mailTransportCredentialsPayload, mergeDeltaRows, normalizeMailServerSecurity, normalizePolicySourcePathItems, useDeltaWatermarks, type ConnectionTreeColumn, type MailServerConnectionTestResult, type MailServerCredentialSettings, type MailServerImapSettings, type MailServerSmtpSettings, type NormalizedPolicySourcePathItem, type PolicySourcePathItem } from "@govoplan/core-webui";
|
||||
import { Link2, Pencil, Plus, Trash2, Unlink } from "lucide-react";
|
||||
import { AdminSelectionList, ConnectionTree, FieldLabel, LoadingFrame, MailServerSettingsPanel, PolicyLockedHint, PolicyPathHelp, PolicyRow, PolicySourcePath, PolicyTable, StageRail, StatusBadge, TableActionGroup, ToggleSwitch, hasMailImapSettings, mailImapSettingsPayload, mailServerSecurityOptions, mailSmtpSettingsPayload, mailTextOrNull, mailTransportCredentialsPayload, mergeDeltaRows, normalizeMailServerSecurity, normalizePolicySourcePathItems, useDeltaWatermarks, type ConnectionTreeColumn, type MailServerConnectionTestResult, type MailServerCredentialSettings, type MailServerImapSettings, type MailServerSmtpSettings, type NormalizedPolicySourcePathItem, type PolicySourcePathItem } from "@govoplan/core-webui";
|
||||
import { ArrowLeft, ArrowRight, Inbox, KeyRound, Link2, Pencil, Plus, Send, Settings2, Trash2, Unlink } from "lucide-react";
|
||||
import type { ApiSettings } from "../../types";
|
||||
import {
|
||||
bindMailServerCredential,
|
||||
@@ -60,7 +60,11 @@ import {
|
||||
mailProfileEditTargetShowsProfileFields,
|
||||
mailProfileEditTargetShowsSettingsPanel,
|
||||
mailProfileEditTargetVisibleSections,
|
||||
mailProfileCreateStageCanContinue,
|
||||
mailProfileCreateStagePanel,
|
||||
mailProfileCreateStages,
|
||||
mailProfileCreateCredentialsPayload,
|
||||
type MailProfileCreateStage,
|
||||
type MailProfileEditTarget,
|
||||
type MailProfileProtocol
|
||||
} from "./mailProfileEditorModel";
|
||||
@@ -179,6 +183,7 @@ export function MailProfileScopeManager({
|
||||
const [selectedTargetId, setSelectedTargetId] = useState(scopeId || targetOptions[0]?.id || "");
|
||||
const [editing, setEditing] = useState<EditingProfile>(null);
|
||||
const [editingTarget, setEditingTarget] = useState<MailProfileEditTarget>({ kind: "create" });
|
||||
const [createStage, setCreateStage] = useState<MailProfileCreateStage>("profile");
|
||||
const [deactivating, setDeactivating] = useState<MailServerProfile | null>(null);
|
||||
const [pendingHierarchyRemoval, setPendingHierarchyRemoval] = useState<PendingHierarchyRemoval>(null);
|
||||
const [availableCredentials, setAvailableCredentials] = useState<MailCredentialEnvelope[]>([]);
|
||||
@@ -204,6 +209,11 @@ export function MailProfileScopeManager({
|
||||
profileDraftKey(draft) !== savedProfileDraftKey
|
||||
|| reuseCredentialId !== savedReuseCredentialId
|
||||
);
|
||||
const createStageIndex = mailProfileCreateStages.indexOf(createStage);
|
||||
const createStageCanContinue = mailProfileCreateStageCanContinue(
|
||||
createStage,
|
||||
draft
|
||||
);
|
||||
|
||||
useUnsavedDraftGuard({
|
||||
dirty: profileDirty,
|
||||
@@ -287,6 +297,7 @@ export function MailProfileScopeManager({
|
||||
setAvailableCredentials([]);
|
||||
setReuseCredentialId("");
|
||||
setSavedReuseCredentialId("");
|
||||
setCreateStage("profile");
|
||||
setEditingTarget({ kind: "create" });
|
||||
setEditing("new");
|
||||
setError("");
|
||||
@@ -338,9 +349,15 @@ export function MailProfileScopeManager({
|
||||
setAvailableCredentials([]);
|
||||
setReuseCredentialId("");
|
||||
setSavedReuseCredentialId("");
|
||||
setCreateStage("profile");
|
||||
setEditingTarget({ kind: "create" });
|
||||
}
|
||||
|
||||
function moveCreateStage(offset: number) {
|
||||
const next = mailProfileCreateStages[createStageIndex + offset];
|
||||
if (next) setCreateStage(next);
|
||||
}
|
||||
|
||||
async function saveProfile(): Promise<boolean> {
|
||||
if (!editing || !scopeReady) return false;
|
||||
setBusy(true);
|
||||
@@ -662,8 +679,52 @@ export function MailProfileScopeManager({
|
||||
onClose={() => !busy && closeProfileEditor()}
|
||||
className="admin-dialog admin-dialog-wide mail-profile-dialog"
|
||||
closeDisabled={busy}
|
||||
footer={<><Button onClick={closeProfileEditor} disabled={busy}>i18n:govoplan-mail.cancel.77dfd213</Button><Button variant="primary" onClick={() => void saveProfile()} disabled={!canWriteProfiles || busy || !scopeReady || !profileEditorCanSave(draft, editing, editingTarget, reuseCredentialId)}>{busy ? "i18n:govoplan-mail.saving.56a2285c" : "i18n:govoplan-mail.save_profile.f597c0e8"}</Button></>}>
|
||||
footer={editing === "new"
|
||||
? <>
|
||||
<Button onClick={closeProfileEditor} disabled={busy}>i18n:govoplan-mail.cancel.77dfd213</Button>
|
||||
{createStageIndex > 0 && (
|
||||
<Button onClick={() => moveCreateStage(-1)} disabled={busy}>
|
||||
<ArrowLeft size={16} />
|
||||
Back
|
||||
</Button>
|
||||
)}
|
||||
{createStageIndex < mailProfileCreateStages.length - 1
|
||||
? (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => moveCreateStage(1)}
|
||||
disabled={!canWriteProfiles || busy || !createStageCanContinue}
|
||||
>
|
||||
Next
|
||||
<ArrowRight size={16} />
|
||||
</Button>
|
||||
)
|
||||
: (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => void saveProfile()}
|
||||
disabled={!canWriteProfiles || busy || !scopeReady || !profileEditorCanSave(draft, editing, editingTarget, reuseCredentialId)}
|
||||
>
|
||||
{busy ? "i18n:govoplan-mail.saving.56a2285c" : "Create profile"}
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
: <><Button onClick={closeProfileEditor} disabled={busy}>i18n:govoplan-mail.cancel.77dfd213</Button><Button variant="primary" onClick={() => void saveProfile()} disabled={!canWriteProfiles || busy || !scopeReady || !profileEditorCanSave(draft, editing, editingTarget, reuseCredentialId)}>{busy ? "i18n:govoplan-mail.saving.56a2285c" : "i18n:govoplan-mail.save_profile.f597c0e8"}</Button></>}>
|
||||
|
||||
{editing === "new" && (
|
||||
<StageRail
|
||||
ariaLabel="Mail profile creation"
|
||||
className="mail-profile-create-rail"
|
||||
items={mailProfileCreateStages.map((stage, index) => ({
|
||||
id: stage,
|
||||
label: mailProfileCreateStageLabel(stage),
|
||||
icon: mailProfileCreateStageIcon(stage),
|
||||
current: stage === createStage,
|
||||
tone: index < createStageIndex ? "success" : stage === createStage ? "active" : "neutral",
|
||||
connectorTone: index < createStageIndex ? "success" : "neutral"
|
||||
}))}
|
||||
/>
|
||||
)}
|
||||
<ProfileForm
|
||||
settings={settings}
|
||||
profiles={profiles}
|
||||
@@ -675,6 +736,7 @@ export function MailProfileScopeManager({
|
||||
canManageCredentials={canManageCredentials}
|
||||
effectivePolicy={profileEffectivePolicy}
|
||||
editTarget={editingTarget}
|
||||
createStage={editing === "new" ? createStage : null}
|
||||
availableCredentials={availableCredentials}
|
||||
reuseCredentialId={reuseCredentialId}
|
||||
setReuseCredentialId={setReuseCredentialId} />
|
||||
@@ -1000,6 +1062,7 @@ function ProfileForm({
|
||||
canManageCredentials,
|
||||
effectivePolicy,
|
||||
editTarget,
|
||||
createStage,
|
||||
availableCredentials,
|
||||
reuseCredentialId,
|
||||
setReuseCredentialId
|
||||
@@ -1014,6 +1077,7 @@ function ProfileForm({
|
||||
canManageCredentials: boolean;
|
||||
effectivePolicy?: MailProfilePolicy | null;
|
||||
editTarget: MailProfileEditTarget;
|
||||
createStage: MailProfileCreateStage | null;
|
||||
availableCredentials: MailCredentialEnvelope[];
|
||||
reuseCredentialId: string;
|
||||
setReuseCredentialId: (credentialId: string) => void;
|
||||
@@ -1027,11 +1091,19 @@ function ProfileForm({
|
||||
const disabled = busy || !canWrite;
|
||||
const credentialDisabled = disabled || !canManageCredentials;
|
||||
const existingProfile = editing !== "new" ? editing : null;
|
||||
const initialSection = mailProfileEditTargetInitialSection(editTarget);
|
||||
const settingsPanelMode = mailProfileEditTargetPanelMode(editTarget);
|
||||
const visibleSections = mailProfileEditTargetVisibleSections(editTarget);
|
||||
const showProfileFields = mailProfileEditTargetShowsProfileFields(editTarget);
|
||||
const showSettingsPanel = mailProfileEditTargetShowsSettingsPanel(editTarget);
|
||||
const createPanel = editTarget.kind === "create" && createStage
|
||||
? mailProfileCreateStagePanel(createStage)
|
||||
: null;
|
||||
const initialSection = createPanel?.initialSection
|
||||
?? mailProfileEditTargetInitialSection(editTarget);
|
||||
const settingsPanelMode = createPanel?.panelMode
|
||||
?? mailProfileEditTargetPanelMode(editTarget);
|
||||
const visibleSections = createPanel?.visibleSections
|
||||
?? mailProfileEditTargetVisibleSections(editTarget);
|
||||
const showProfileFields = createPanel?.showProfileFields
|
||||
?? mailProfileEditTargetShowsProfileFields(editTarget);
|
||||
const showSettingsPanel = createPanel?.showSettingsPanel
|
||||
?? mailProfileEditTargetShowsSettingsPanel(editTarget);
|
||||
const draftHasImap = hasDraftImapSettings(draft);
|
||||
const selectedServerId = editTarget.kind === "server" || editTarget.kind === "credentials" ? editTarget.serverId : undefined;
|
||||
const selectedCredentialId = editTarget.kind === "credentials" ? editTarget.credentialId : undefined;
|
||||
@@ -1289,7 +1361,11 @@ function ProfileForm({
|
||||
</div>
|
||||
}
|
||||
|
||||
{policyMessages.length > 0 &&
|
||||
{policyMessages.length > 0 && (
|
||||
editTarget.kind !== "create"
|
||||
|| createStage === "smtp_server"
|
||||
|| createStage === "imap_server"
|
||||
) &&
|
||||
<DismissibleAlert tone="warning" resetKey={policyMessages.map((item) => `${item.key}:${item.value}`).join("|")} dismissible={false}>
|
||||
<strong>i18n:govoplan-mail.effective_mail_policy_blocks_the_current_profile.1b555820</strong>
|
||||
<ul>{policyMessages.map((item) => <li key={`${item.key}:${item.value}`}>{item.message}</li>)}</ul>
|
||||
@@ -1310,7 +1386,14 @@ function ProfileForm({
|
||||
smtpCredentialDisabled={credentialDisabled}
|
||||
smtpPasswordSaved={Boolean(selectedCredential?.secret_configured || existingProfile?.smtp_password_configured)}
|
||||
imapServerDisabled={disabled}
|
||||
imapCredentialDisabled={credentialDisabled}
|
||||
imapCredentialDisabled={
|
||||
credentialDisabled
|
||||
|| (
|
||||
editTarget.kind === "create"
|
||||
&& createStage === "imap_credentials"
|
||||
&& !draftHasImap
|
||||
)
|
||||
}
|
||||
imapPasswordSaved={Boolean(selectedCredential?.secret_configured || existingProfile?.imap_password_configured)}
|
||||
imapActionDisabled={disabled || !draftHasImap}
|
||||
smtpTestLabel={useSavedSmtpTest ? "i18n:govoplan-mail.test_saved_smtp.008d8054" : "i18n:govoplan-mail.test_smtp.e5697981"}
|
||||
@@ -1409,6 +1492,23 @@ function credentialAvailabilityLabel(credential: MailCredentialEnvelope): string
|
||||
return `${credential.inherit_to_lower_scopes ? "Inherited" : "Current scope"} · ${modules} · ${servers}`;
|
||||
}
|
||||
|
||||
function mailProfileCreateStageLabel(stage: MailProfileCreateStage): string {
|
||||
return {
|
||||
profile: "Profile",
|
||||
smtp_server: "SMTP server",
|
||||
smtp_credentials: "SMTP credential",
|
||||
imap_server: "IMAP server",
|
||||
imap_credentials: "IMAP credential"
|
||||
}[stage];
|
||||
}
|
||||
|
||||
function mailProfileCreateStageIcon(stage: MailProfileCreateStage): ReactNode {
|
||||
if (stage === "profile") return <Settings2 size={17} />;
|
||||
if (stage === "smtp_server") return <Send size={17} />;
|
||||
if (stage === "imap_server") return <Inbox size={17} />;
|
||||
return <KeyRound size={17} />;
|
||||
}
|
||||
|
||||
function profileDialogTitle(editing: EditingProfile, target: MailProfileEditTarget): string {
|
||||
if (editing === "new") return "i18n:govoplan-mail.create_mail_profile.4d2f8f9f";
|
||||
if (target.kind === "server") return `${target.serverId ? "Edit" : "Add"} ${target.protocol.toUpperCase()} server`;
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
export type MailProfileProtocol = "smtp" | "imap";
|
||||
export type MailProfileEditSection = MailProfileProtocol;
|
||||
export type MailProfilePanelMode = "all" | "server" | "credentials";
|
||||
export type MailProfileCreateStage =
|
||||
| "profile"
|
||||
| "smtp_server"
|
||||
| "smtp_credentials"
|
||||
| "imap_server"
|
||||
| "imap_credentials";
|
||||
|
||||
export const mailProfileCreateStages: readonly MailProfileCreateStage[] = [
|
||||
"profile",
|
||||
"smtp_server",
|
||||
"smtp_credentials",
|
||||
"imap_server",
|
||||
"imap_credentials"
|
||||
];
|
||||
|
||||
export type MailProfileCreateStageFields = {
|
||||
name: string;
|
||||
smtpHost: string;
|
||||
imapHost: string;
|
||||
imapUsername: string;
|
||||
imapPassword: string;
|
||||
};
|
||||
|
||||
export type MailProfileEditTarget =
|
||||
| {kind: "create";}
|
||||
@@ -75,6 +97,49 @@ export function mailProfileEditTargetShowsSettingsPanel(target: MailProfileEditT
|
||||
return target.kind !== "profile";
|
||||
}
|
||||
|
||||
export function mailProfileCreateStagePanel(
|
||||
stage: MailProfileCreateStage
|
||||
): {
|
||||
showProfileFields: boolean;
|
||||
showSettingsPanel: boolean;
|
||||
initialSection: MailProfileEditSection;
|
||||
visibleSections: MailProfileEditSection[];
|
||||
panelMode: MailProfilePanelMode | null;
|
||||
} {
|
||||
if (stage === "profile") {
|
||||
return {
|
||||
showProfileFields: true,
|
||||
showSettingsPanel: false,
|
||||
initialSection: "smtp",
|
||||
visibleSections: [],
|
||||
panelMode: null
|
||||
};
|
||||
}
|
||||
const protocol = stage.startsWith("smtp") ? "smtp" : "imap";
|
||||
return {
|
||||
showProfileFields: false,
|
||||
showSettingsPanel: true,
|
||||
initialSection: protocol,
|
||||
visibleSections: [protocol],
|
||||
panelMode: stage.endsWith("credentials") ? "credentials" : "server"
|
||||
};
|
||||
}
|
||||
|
||||
export function mailProfileCreateStageCanContinue(
|
||||
stage: MailProfileCreateStage,
|
||||
fields: MailProfileCreateStageFields
|
||||
): boolean {
|
||||
if (stage === "profile") return Boolean(fields.name.trim());
|
||||
if (stage === "smtp_server") return Boolean(fields.smtpHost.trim());
|
||||
if (
|
||||
stage === "imap_credentials"
|
||||
&& (fields.imapUsername.trim() || fields.imapPassword)
|
||||
) {
|
||||
return Boolean(fields.imapHost.trim());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keep the PATCH authority surface equal to the focused editor. Profile and
|
||||
* server edits must not accidentally replay credential fields merely because
|
||||
|
||||
@@ -19,6 +19,9 @@ import {
|
||||
mailProfileEditTargetShowsProfileFields,
|
||||
mailProfileEditTargetShowsSettingsPanel,
|
||||
mailProfileEditTargetVisibleSections,
|
||||
mailProfileCreateStageCanContinue,
|
||||
mailProfileCreateStagePanel,
|
||||
mailProfileCreateStages,
|
||||
mailProfileCreateCredentialsPayload,
|
||||
mailProfileTargetedUpdatePayload
|
||||
} from "../src/features/mail/mailProfileEditorModel";
|
||||
@@ -48,6 +51,68 @@ assertEqual(mailProfileEditTargetShowsProfileFields({ kind: "profile" }), true);
|
||||
assertEqual(mailProfileEditTargetShowsProfileFields({ kind: "server", protocol: "smtp" }), false);
|
||||
assertEqual(mailProfileEditTargetShowsSettingsPanel({ kind: "profile" }), false);
|
||||
assertEqual(mailProfileEditTargetShowsSettingsPanel({ kind: "create" }), true);
|
||||
assertDeepEqual(
|
||||
mailProfileCreateStages,
|
||||
[
|
||||
"profile",
|
||||
"smtp_server",
|
||||
"smtp_credentials",
|
||||
"imap_server",
|
||||
"imap_credentials"
|
||||
],
|
||||
"profile creation keeps server and credential stages explicit"
|
||||
);
|
||||
assertDeepEqual(
|
||||
mailProfileCreateStagePanel("smtp_credentials"),
|
||||
{
|
||||
showProfileFields: false,
|
||||
showSettingsPanel: true,
|
||||
initialSection: "smtp",
|
||||
visibleSections: ["smtp"],
|
||||
panelMode: "credentials"
|
||||
}
|
||||
);
|
||||
assertDeepEqual(
|
||||
mailProfileCreateStagePanel("imap_server"),
|
||||
{
|
||||
showProfileFields: false,
|
||||
showSettingsPanel: true,
|
||||
initialSection: "imap",
|
||||
visibleSections: ["imap"],
|
||||
panelMode: "server"
|
||||
}
|
||||
);
|
||||
assertEqual(
|
||||
mailProfileCreateStageCanContinue("profile", {
|
||||
name: "",
|
||||
smtpHost: "",
|
||||
imapHost: "",
|
||||
imapUsername: "",
|
||||
imapPassword: ""
|
||||
}),
|
||||
false
|
||||
);
|
||||
assertEqual(
|
||||
mailProfileCreateStageCanContinue("smtp_server", {
|
||||
name: "Profile",
|
||||
smtpHost: "smtp.example.org",
|
||||
imapHost: "",
|
||||
imapUsername: "",
|
||||
imapPassword: ""
|
||||
}),
|
||||
true
|
||||
);
|
||||
assertEqual(
|
||||
mailProfileCreateStageCanContinue("imap_credentials", {
|
||||
name: "Profile",
|
||||
smtpHost: "smtp.example.org",
|
||||
imapHost: "",
|
||||
imapUsername: "imap-user",
|
||||
imapPassword: ""
|
||||
}),
|
||||
false,
|
||||
"IMAP credentials cannot be submitted without an IMAP server"
|
||||
);
|
||||
|
||||
const updateParts = {
|
||||
profile: { name: "Renamed" },
|
||||
|
||||
Reference in New Issue
Block a user