Refine reusable mail profile credential editing
This commit is contained in:
Generated
+3
-104
@@ -13,9 +13,9 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.10",
|
"@govoplan/core-webui": "^0.1.10",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
"react": "^19.0.0",
|
"react": ">=19.2.7 <20",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": ">=19.2.7 <20",
|
||||||
"react-router-dom": ">=7.18.2 <8"
|
"react-router": ">=8.3.0 <9"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@govoplan/core-webui": {
|
"@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": {
|
"node_modules/typescript": {
|
||||||
"version": "5.9.3",
|
"version": "5.9.3",
|
||||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
|
||||||
|
|||||||
+3
-3
@@ -16,9 +16,9 @@
|
|||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"@govoplan/core-webui": "^0.1.10",
|
"@govoplan/core-webui": "^0.1.10",
|
||||||
"lucide-react": "^1.23.0",
|
"lucide-react": "^1.23.0",
|
||||||
"react": "^19.0.0",
|
"react": ">=19.2.7 <20",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": ">=19.2.7 <20",
|
||||||
"react-router-dom": ">=7.18.2 <8"
|
"react-router": ">=8.3.0 <9"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"@govoplan/core-webui": {
|
"@govoplan/core-webui": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
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 { 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 { Link2, Pencil, Plus, Trash2, Unlink } from "lucide-react";
|
import { ArrowLeft, ArrowRight, Inbox, KeyRound, Link2, Pencil, Plus, Send, Settings2, Trash2, Unlink } from "lucide-react";
|
||||||
import type { ApiSettings } from "../../types";
|
import type { ApiSettings } from "../../types";
|
||||||
import {
|
import {
|
||||||
bindMailServerCredential,
|
bindMailServerCredential,
|
||||||
@@ -60,7 +60,11 @@ import {
|
|||||||
mailProfileEditTargetShowsProfileFields,
|
mailProfileEditTargetShowsProfileFields,
|
||||||
mailProfileEditTargetShowsSettingsPanel,
|
mailProfileEditTargetShowsSettingsPanel,
|
||||||
mailProfileEditTargetVisibleSections,
|
mailProfileEditTargetVisibleSections,
|
||||||
|
mailProfileCreateStageCanContinue,
|
||||||
|
mailProfileCreateStagePanel,
|
||||||
|
mailProfileCreateStages,
|
||||||
mailProfileCreateCredentialsPayload,
|
mailProfileCreateCredentialsPayload,
|
||||||
|
type MailProfileCreateStage,
|
||||||
type MailProfileEditTarget,
|
type MailProfileEditTarget,
|
||||||
type MailProfileProtocol
|
type MailProfileProtocol
|
||||||
} from "./mailProfileEditorModel";
|
} from "./mailProfileEditorModel";
|
||||||
@@ -179,6 +183,7 @@ export function MailProfileScopeManager({
|
|||||||
const [selectedTargetId, setSelectedTargetId] = useState(scopeId || targetOptions[0]?.id || "");
|
const [selectedTargetId, setSelectedTargetId] = useState(scopeId || targetOptions[0]?.id || "");
|
||||||
const [editing, setEditing] = useState<EditingProfile>(null);
|
const [editing, setEditing] = useState<EditingProfile>(null);
|
||||||
const [editingTarget, setEditingTarget] = useState<MailProfileEditTarget>({ kind: "create" });
|
const [editingTarget, setEditingTarget] = useState<MailProfileEditTarget>({ kind: "create" });
|
||||||
|
const [createStage, setCreateStage] = useState<MailProfileCreateStage>("profile");
|
||||||
const [deactivating, setDeactivating] = useState<MailServerProfile | null>(null);
|
const [deactivating, setDeactivating] = useState<MailServerProfile | null>(null);
|
||||||
const [pendingHierarchyRemoval, setPendingHierarchyRemoval] = useState<PendingHierarchyRemoval>(null);
|
const [pendingHierarchyRemoval, setPendingHierarchyRemoval] = useState<PendingHierarchyRemoval>(null);
|
||||||
const [availableCredentials, setAvailableCredentials] = useState<MailCredentialEnvelope[]>([]);
|
const [availableCredentials, setAvailableCredentials] = useState<MailCredentialEnvelope[]>([]);
|
||||||
@@ -204,6 +209,11 @@ export function MailProfileScopeManager({
|
|||||||
profileDraftKey(draft) !== savedProfileDraftKey
|
profileDraftKey(draft) !== savedProfileDraftKey
|
||||||
|| reuseCredentialId !== savedReuseCredentialId
|
|| reuseCredentialId !== savedReuseCredentialId
|
||||||
);
|
);
|
||||||
|
const createStageIndex = mailProfileCreateStages.indexOf(createStage);
|
||||||
|
const createStageCanContinue = mailProfileCreateStageCanContinue(
|
||||||
|
createStage,
|
||||||
|
draft
|
||||||
|
);
|
||||||
|
|
||||||
useUnsavedDraftGuard({
|
useUnsavedDraftGuard({
|
||||||
dirty: profileDirty,
|
dirty: profileDirty,
|
||||||
@@ -287,6 +297,7 @@ export function MailProfileScopeManager({
|
|||||||
setAvailableCredentials([]);
|
setAvailableCredentials([]);
|
||||||
setReuseCredentialId("");
|
setReuseCredentialId("");
|
||||||
setSavedReuseCredentialId("");
|
setSavedReuseCredentialId("");
|
||||||
|
setCreateStage("profile");
|
||||||
setEditingTarget({ kind: "create" });
|
setEditingTarget({ kind: "create" });
|
||||||
setEditing("new");
|
setEditing("new");
|
||||||
setError("");
|
setError("");
|
||||||
@@ -338,9 +349,15 @@ export function MailProfileScopeManager({
|
|||||||
setAvailableCredentials([]);
|
setAvailableCredentials([]);
|
||||||
setReuseCredentialId("");
|
setReuseCredentialId("");
|
||||||
setSavedReuseCredentialId("");
|
setSavedReuseCredentialId("");
|
||||||
|
setCreateStage("profile");
|
||||||
setEditingTarget({ kind: "create" });
|
setEditingTarget({ kind: "create" });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function moveCreateStage(offset: number) {
|
||||||
|
const next = mailProfileCreateStages[createStageIndex + offset];
|
||||||
|
if (next) setCreateStage(next);
|
||||||
|
}
|
||||||
|
|
||||||
async function saveProfile(): Promise<boolean> {
|
async function saveProfile(): Promise<boolean> {
|
||||||
if (!editing || !scopeReady) return false;
|
if (!editing || !scopeReady) return false;
|
||||||
setBusy(true);
|
setBusy(true);
|
||||||
@@ -662,8 +679,52 @@ export function MailProfileScopeManager({
|
|||||||
onClose={() => !busy && closeProfileEditor()}
|
onClose={() => !busy && closeProfileEditor()}
|
||||||
className="admin-dialog admin-dialog-wide mail-profile-dialog"
|
className="admin-dialog admin-dialog-wide mail-profile-dialog"
|
||||||
closeDisabled={busy}
|
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
|
<ProfileForm
|
||||||
settings={settings}
|
settings={settings}
|
||||||
profiles={profiles}
|
profiles={profiles}
|
||||||
@@ -675,6 +736,7 @@ export function MailProfileScopeManager({
|
|||||||
canManageCredentials={canManageCredentials}
|
canManageCredentials={canManageCredentials}
|
||||||
effectivePolicy={profileEffectivePolicy}
|
effectivePolicy={profileEffectivePolicy}
|
||||||
editTarget={editingTarget}
|
editTarget={editingTarget}
|
||||||
|
createStage={editing === "new" ? createStage : null}
|
||||||
availableCredentials={availableCredentials}
|
availableCredentials={availableCredentials}
|
||||||
reuseCredentialId={reuseCredentialId}
|
reuseCredentialId={reuseCredentialId}
|
||||||
setReuseCredentialId={setReuseCredentialId} />
|
setReuseCredentialId={setReuseCredentialId} />
|
||||||
@@ -1000,6 +1062,7 @@ function ProfileForm({
|
|||||||
canManageCredentials,
|
canManageCredentials,
|
||||||
effectivePolicy,
|
effectivePolicy,
|
||||||
editTarget,
|
editTarget,
|
||||||
|
createStage,
|
||||||
availableCredentials,
|
availableCredentials,
|
||||||
reuseCredentialId,
|
reuseCredentialId,
|
||||||
setReuseCredentialId
|
setReuseCredentialId
|
||||||
@@ -1014,6 +1077,7 @@ function ProfileForm({
|
|||||||
canManageCredentials: boolean;
|
canManageCredentials: boolean;
|
||||||
effectivePolicy?: MailProfilePolicy | null;
|
effectivePolicy?: MailProfilePolicy | null;
|
||||||
editTarget: MailProfileEditTarget;
|
editTarget: MailProfileEditTarget;
|
||||||
|
createStage: MailProfileCreateStage | null;
|
||||||
availableCredentials: MailCredentialEnvelope[];
|
availableCredentials: MailCredentialEnvelope[];
|
||||||
reuseCredentialId: string;
|
reuseCredentialId: string;
|
||||||
setReuseCredentialId: (credentialId: string) => void;
|
setReuseCredentialId: (credentialId: string) => void;
|
||||||
@@ -1027,11 +1091,19 @@ function ProfileForm({
|
|||||||
const disabled = busy || !canWrite;
|
const disabled = busy || !canWrite;
|
||||||
const credentialDisabled = disabled || !canManageCredentials;
|
const credentialDisabled = disabled || !canManageCredentials;
|
||||||
const existingProfile = editing !== "new" ? editing : null;
|
const existingProfile = editing !== "new" ? editing : null;
|
||||||
const initialSection = mailProfileEditTargetInitialSection(editTarget);
|
const createPanel = editTarget.kind === "create" && createStage
|
||||||
const settingsPanelMode = mailProfileEditTargetPanelMode(editTarget);
|
? mailProfileCreateStagePanel(createStage)
|
||||||
const visibleSections = mailProfileEditTargetVisibleSections(editTarget);
|
: null;
|
||||||
const showProfileFields = mailProfileEditTargetShowsProfileFields(editTarget);
|
const initialSection = createPanel?.initialSection
|
||||||
const showSettingsPanel = mailProfileEditTargetShowsSettingsPanel(editTarget);
|
?? 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 draftHasImap = hasDraftImapSettings(draft);
|
||||||
const selectedServerId = editTarget.kind === "server" || editTarget.kind === "credentials" ? editTarget.serverId : undefined;
|
const selectedServerId = editTarget.kind === "server" || editTarget.kind === "credentials" ? editTarget.serverId : undefined;
|
||||||
const selectedCredentialId = editTarget.kind === "credentials" ? editTarget.credentialId : undefined;
|
const selectedCredentialId = editTarget.kind === "credentials" ? editTarget.credentialId : undefined;
|
||||||
@@ -1289,7 +1361,11 @@ function ProfileForm({
|
|||||||
</div>
|
</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}>
|
<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>
|
<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>
|
<ul>{policyMessages.map((item) => <li key={`${item.key}:${item.value}`}>{item.message}</li>)}</ul>
|
||||||
@@ -1310,7 +1386,14 @@ function ProfileForm({
|
|||||||
smtpCredentialDisabled={credentialDisabled}
|
smtpCredentialDisabled={credentialDisabled}
|
||||||
smtpPasswordSaved={Boolean(selectedCredential?.secret_configured || existingProfile?.smtp_password_configured)}
|
smtpPasswordSaved={Boolean(selectedCredential?.secret_configured || existingProfile?.smtp_password_configured)}
|
||||||
imapServerDisabled={disabled}
|
imapServerDisabled={disabled}
|
||||||
imapCredentialDisabled={credentialDisabled}
|
imapCredentialDisabled={
|
||||||
|
credentialDisabled
|
||||||
|
|| (
|
||||||
|
editTarget.kind === "create"
|
||||||
|
&& createStage === "imap_credentials"
|
||||||
|
&& !draftHasImap
|
||||||
|
)
|
||||||
|
}
|
||||||
imapPasswordSaved={Boolean(selectedCredential?.secret_configured || existingProfile?.imap_password_configured)}
|
imapPasswordSaved={Boolean(selectedCredential?.secret_configured || existingProfile?.imap_password_configured)}
|
||||||
imapActionDisabled={disabled || !draftHasImap}
|
imapActionDisabled={disabled || !draftHasImap}
|
||||||
smtpTestLabel={useSavedSmtpTest ? "i18n:govoplan-mail.test_saved_smtp.008d8054" : "i18n:govoplan-mail.test_smtp.e5697981"}
|
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}`;
|
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 {
|
function profileDialogTitle(editing: EditingProfile, target: MailProfileEditTarget): string {
|
||||||
if (editing === "new") return "i18n:govoplan-mail.create_mail_profile.4d2f8f9f";
|
if (editing === "new") return "i18n:govoplan-mail.create_mail_profile.4d2f8f9f";
|
||||||
if (target.kind === "server") return `${target.serverId ? "Edit" : "Add"} ${target.protocol.toUpperCase()} server`;
|
if (target.kind === "server") return `${target.serverId ? "Edit" : "Add"} ${target.protocol.toUpperCase()} server`;
|
||||||
|
|||||||
@@ -1,6 +1,28 @@
|
|||||||
export type MailProfileProtocol = "smtp" | "imap";
|
export type MailProfileProtocol = "smtp" | "imap";
|
||||||
export type MailProfileEditSection = MailProfileProtocol;
|
export type MailProfileEditSection = MailProfileProtocol;
|
||||||
export type MailProfilePanelMode = "all" | "server" | "credentials";
|
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 =
|
export type MailProfileEditTarget =
|
||||||
| {kind: "create";}
|
| {kind: "create";}
|
||||||
@@ -75,6 +97,49 @@ export function mailProfileEditTargetShowsSettingsPanel(target: MailProfileEditT
|
|||||||
return target.kind !== "profile";
|
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
|
* Keep the PATCH authority surface equal to the focused editor. Profile and
|
||||||
* server edits must not accidentally replay credential fields merely because
|
* server edits must not accidentally replay credential fields merely because
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ import {
|
|||||||
mailProfileEditTargetShowsProfileFields,
|
mailProfileEditTargetShowsProfileFields,
|
||||||
mailProfileEditTargetShowsSettingsPanel,
|
mailProfileEditTargetShowsSettingsPanel,
|
||||||
mailProfileEditTargetVisibleSections,
|
mailProfileEditTargetVisibleSections,
|
||||||
|
mailProfileCreateStageCanContinue,
|
||||||
|
mailProfileCreateStagePanel,
|
||||||
|
mailProfileCreateStages,
|
||||||
mailProfileCreateCredentialsPayload,
|
mailProfileCreateCredentialsPayload,
|
||||||
mailProfileTargetedUpdatePayload
|
mailProfileTargetedUpdatePayload
|
||||||
} from "../src/features/mail/mailProfileEditorModel";
|
} from "../src/features/mail/mailProfileEditorModel";
|
||||||
@@ -48,6 +51,68 @@ assertEqual(mailProfileEditTargetShowsProfileFields({ kind: "profile" }), true);
|
|||||||
assertEqual(mailProfileEditTargetShowsProfileFields({ kind: "server", protocol: "smtp" }), false);
|
assertEqual(mailProfileEditTargetShowsProfileFields({ kind: "server", protocol: "smtp" }), false);
|
||||||
assertEqual(mailProfileEditTargetShowsSettingsPanel({ kind: "profile" }), false);
|
assertEqual(mailProfileEditTargetShowsSettingsPanel({ kind: "profile" }), false);
|
||||||
assertEqual(mailProfileEditTargetShowsSettingsPanel({ kind: "create" }), true);
|
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 = {
|
const updateParts = {
|
||||||
profile: { name: "Renamed" },
|
profile: { name: "Renamed" },
|
||||||
|
|||||||
Reference in New Issue
Block a user