@aglyn/plugins-email 1.0.0-beta.218 → 1.0.0-beta.219
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +17 -17
- package/src/lib/components/consent-group-review.js +2 -2
- package/src/lib/components/consent-group-review.js.map +1 -1
- package/src/lib/components/dynamic-list-rule-fields.js +81 -34
- package/src/lib/components/dynamic-list-rule-fields.js.map +1 -1
- package/src/lib/components/emails-console-sections.js +3 -2
- package/src/lib/components/emails-console-sections.js.map +1 -1
- package/src/lib/components/list-import-drawer.js +2 -2
- package/src/lib/components/list-import-drawer.js.map +1 -1
- package/src/lib/list-import.d.ts +149 -0
- package/src/lib/list-import.js +324 -0
- package/src/lib/list-import.js.map +1 -0
- package/src/lib/model/email-record-routes.d.ts +22 -0
- package/src/lib/model/email-record-routes.js +75 -0
- package/src/lib/model/email-record-routes.js.map +1 -0
- package/src/lib/model/template-provenance.d.ts +1 -1
- package/src/lib/model/template-provenance.js +1 -1
- package/src/lib/model/template-provenance.js.map +1 -1
- package/src/lib/plugin.js +5 -1
- package/src/lib/plugin.js.map +1 -1
- package/src/lib/server-email-drafts.js +2 -2
- package/src/lib/server-email-drafts.js.map +1 -1
- package/src/lib/server-list-gate.js +35 -10
- package/src/lib/server-list-gate.js.map +1 -1
- package/src/lib/server-list-import.js +3 -2
- package/src/lib/server-list-import.js.map +1 -1
- package/src/lib/unsubscribe-link.js +1 -1
- package/src/lib/unsubscribe-link.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-email",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.219",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,22 +25,22 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-enums": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-data-types": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-ui-theme": "1.0.0-beta.
|
|
38
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
39
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
40
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
41
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
42
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
43
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.219",
|
|
29
|
+
"@aglyn/shared-data-enums": "1.0.0-beta.219",
|
|
30
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.219",
|
|
31
|
+
"@aglyn/shared-data-types": "1.0.0-beta.219",
|
|
32
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.219",
|
|
33
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.219",
|
|
34
|
+
"@aglyn/shared-ui-jsx-forms": "1.0.0-beta.219",
|
|
35
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.219",
|
|
36
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.219",
|
|
37
|
+
"@aglyn/shared-ui-theme": "1.0.0-beta.219",
|
|
38
|
+
"@aglyn/shared-util-email": "1.0.0-beta.219",
|
|
39
|
+
"@aglyn/shared-util-http": "1.0.0-beta.219",
|
|
40
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.219",
|
|
41
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.219",
|
|
42
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.219",
|
|
43
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.219",
|
|
44
44
|
"@swc/helpers": "0.5.23"
|
|
45
45
|
},
|
|
46
46
|
"peerDependencies": {
|
|
@@ -234,8 +234,8 @@ function title(change) {
|
|
|
234
234
|
});
|
|
235
235
|
}
|
|
236
236
|
const copied = sum(((_ref = preview == null ? void 0 : preview.carries) != null ? _ref : []).map((carry)=>{
|
|
237
|
-
var _ref, _ref1;
|
|
238
|
-
return ((_ref = carry == null ? void 0 : carry.siteSuppressions) != null ? _ref : 0) + ((_ref1 = carry == null ? void 0 : carry.topicOptOuts) != null ? _ref1 : 0);
|
|
237
|
+
var _ref, _ref1, _ref2;
|
|
238
|
+
return ((_ref = carry == null ? void 0 : carry.siteSuppressions) != null ? _ref : 0) + ((_ref1 = carry == null ? void 0 : carry.topicOptOuts) != null ? _ref1 : 0) + ((_ref2 = carry == null ? void 0 : carry.retainedRefusals) != null ? _ref2 : 0);
|
|
239
239
|
})) + ((_lineCount = lineCount(preview, 'crm.refusals')) != null ? _lineCount : 0);
|
|
240
240
|
const paces = sum(((_ref1 = preview == null ? void 0 : preview.carries) != null ? _ref1 : []).map((carry)=>{
|
|
241
241
|
var _ref;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/consent-group-review.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * What the admin is told before a consent group change is applied.\n *\n * A change here decides who may email whom, so the review says it in the\n * terms the admin will be held to rather than in the terms the code uses:\n * what signup forms will say, who stops getting mail, who can still be\n * mailed, and what happens to the CRM's records. Every number comes from the\n * route's preview, which counted it without writing anything; a sentence\n * whose number the preview did not supply is said without one rather than\n * with a guess.\n *\n * Pure, so each wording can be asserted against a preview without rendering.\n */\n\nimport type {\n ConsentGroupChangeEstimate,\n ConsentGroupChangePreview,\n ConsentGroupParticipantLine,\n} from './consent-groups-api'\nimport {\n draftDisclosure,\n type ConsentGroupDraftChange,\n} from './consent-group-editing'\n\n/** One sentence of the review. `warning` is the tone of an irreversible loss. */\nexport interface ConsentGroupReviewItem {\n id: string\n text: string\n tone: 'info' | 'warning'\n}\n\nexport interface ConsentGroupReviewSection {\n id: string\n heading: string | null\n items: ConsentGroupReviewItem[]\n}\n\nexport interface ConsentGroupReview {\n title: string\n /** The primary button, named for what it does. */\n confirmLabel: string\n sections: ConsentGroupReviewSection[]\n}\n\nexport interface ConsentGroupReviewInput {\n change: ConsentGroupDraftChange\n /** `null` while the preview is still being counted. */\n preview: ConsentGroupChangePreview | null\n siteName: (hostId: string) => string\n /** The org's switch: a confirmation one site waits on holds its siblings. */\n awaitsConfirmation: boolean\n}\n\n/*==========================================\n * WORDS\n *=========================================*/\n\nconst listFormat = new Intl.ListFormat('en-US', {\n style: 'long',\n type: 'conjunction',\n})\n\n/** `A`, `A and B`, `A, B, and C`. */\nexport function joinNames(names: readonly string[]): string {\n return listFormat.format(names)\n}\n\nconst count = (n: number) => n.toLocaleString('en-US')\nconst people = (n: number) => `${count(n)} ${n === 1 ? 'person' : 'people'}`\n\nconst sum = (values: readonly number[]) =>\n values.reduce((total, value) => total + (Number.isFinite(value) ? value : 0), 0)\n\nconst dateFormat = new Intl.DateTimeFormat('en-US', {\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n})\n\n/** What an estimate means for the person about to wait on it. */\nexport const CONSENT_GROUP_ESTIMATE_TEXT: Readonly<\n Record<ConsentGroupChangeEstimate, string>\n> = {\n instant: 'This takes effect right away.',\n 'under-a-minute': 'This should take less than a minute.',\n minutes: 'This will take a few minutes.',\n long: 'This will take a while — longer for organizations with many contacts.',\n}\n\n/** What a capture surface is called where a sentence names it. */\nconst CAPTURE_SURFACE_NAMES: Readonly<Record<string, string>> = {\n form: 'signup forms',\n newsletter: 'newsletter signups',\n}\n\n/*==========================================\n * THE PARTICIPANTS' OWN LINES\n *=========================================*/\n\n/**\n * The CRM's counted lines the review writes its own sentences around.\n *\n * The CRM reports what the change does to its records as lines with these\n * ids; the review folds each count into the sentence it belongs to, so the\n * admin reads one account of the change rather than two. A line with any\n * other id is printed as the plugin wrote it.\n */\nconst FOLDED_LINE_IDS = new Set([\n 'crm.combine',\n 'crm.copy',\n 'crm.figures',\n 'crm.refusals',\n 'crm.grants',\n 'crm.visibility',\n])\n\nfunction participantLines(\n preview: ConsentGroupChangePreview | null,\n): ConsentGroupParticipantLine[] {\n return (preview?.participants ?? []).flatMap((participant) =>\n Array.isArray(participant?.lines) ? participant.lines : [],\n )\n}\n\n/** A folded line's count, or `null` when it was not reported or not counted. */\nfunction lineCount(\n preview: ConsentGroupChangePreview | null,\n id: string,\n): number | null {\n const line = participantLines(preview).find((entry) => entry?.id === id)\n return line && typeof line.count === 'number' ? line.count : null\n}\n\n/** Whether the CRM spoke at all — its sentences are said only if it did. */\nfunction crmReported(preview: ConsentGroupChangePreview | null): boolean {\n return participantLines(preview).some((line) =>\n String(line?.id ?? '').startsWith('crm.'),\n )\n}\n\n/*==========================================\n * THE REVIEW\n *=========================================*/\n\nfunction confirmLabel(change: ConsentGroupDraftChange): string {\n if (change.kind === 'create') return 'Create group'\n if (change.kind === 'dissolve') return 'Dissolve group'\n const joining = change.added.length + change.movedIn.length\n const leaving = change.removed.length\n if (change.renamed && !joining && !leaving) return 'Rename group'\n if (!change.renamed && joining && !leaving) {\n return joining === 1 ? 'Add site' : 'Add sites'\n }\n if (!change.renamed && leaving && !joining) {\n return leaving === 1 ? 'Remove site' : 'Remove sites'\n }\n return 'Apply changes'\n}\n\nfunction title(change: ConsentGroupDraftChange): string {\n if (change.kind === 'create') return `Create “${change.name}”`\n if (change.kind === 'dissolve') return `Dissolve “${change.name}”`\n return `Change “${change.previousName ?? change.name}”`\n}\n\n/** Builds every section of the review, in the order it is read. */\nexport function describeConsentGroupReview(\n input: ConsentGroupReviewInput,\n): ConsentGroupReview {\n const { change, preview, siteName, awaitsConfirmation } = input\n const names = (ids: readonly string[]) => joinNames(ids.map(siteName))\n const group = change.name\n const joining = [...change.added, ...change.movedIn.map((move) => move.hostId)]\n const joins = change.kind === 'create' || joining.length > 0\n const dissolving = change.kind === 'dissolve'\n const leaving = dissolving ? change.previousHostIds : change.removed\n const crm = crmReported(preview)\n\n const summary: ConsentGroupReviewItem[] = []\n const forms: ConsentGroupReviewItem[] = []\n const consent: ConsentGroupReviewItem[] = []\n const optOuts: ConsentGroupReviewItem[] = []\n const records: ConsentGroupReviewItem[] = []\n const other: ConsentGroupReviewItem[] = []\n const say = (\n into: ConsentGroupReviewItem[],\n id: string,\n text: string,\n tone: ConsentGroupReviewItem['tone'] = 'info',\n ) => into.push({ id, text, tone })\n\n /*---- What changes ----*/\n if (change.kind === 'create') {\n say(summary, 'summary.create', `Create ${group} with ${names(change.hostIds)}.`)\n }\n if (change.tooFewSites) {\n say(\n summary,\n 'summary.too-few',\n change.unselected.length\n ? `A consent group needs at least two sites. Removing ${names(change.unselected)} dissolves ${group}.`\n : `A consent group needs at least two sites, so this dissolves ${group}.`,\n 'warning',\n )\n } else if (dissolving) {\n say(summary, 'summary.dissolve', `Dissolve ${group}.`)\n }\n if (change.renamed) {\n say(\n summary,\n 'summary.rename',\n `Rename ${change.previousName} to ${group}.`,\n )\n }\n if (change.kind === 'edit' && change.added.length) {\n say(summary, 'summary.add', `Add ${names(change.added)} to ${group}.`)\n }\n for (const move of change.movedIn) {\n say(\n summary,\n `summary.move.${move.hostId}`,\n `Move ${siteName(move.hostId)} from ${move.fromName} to ${group}.`,\n )\n }\n if (change.kind === 'edit' && change.removed.length) {\n say(summary, 'summary.remove', `Remove ${names(change.removed)} from ${group}.`)\n }\n for (const donor of change.donors) {\n if (donor.absorbed) {\n say(\n summary,\n `summary.absorb.${donor.groupId}`,\n `${donor.name} will be merged into ${group}, so ${donor.name} stops existing.`,\n )\n } else if (donor.dissolves) {\n say(\n summary,\n `summary.donor-dissolve.${donor.groupId}`,\n `Moving ${names(donor.losing)} dissolves ${donor.name}, because a consent group needs at least two sites. ${names(donor.remaining)} will send on its own.`,\n 'warning',\n )\n }\n }\n\n /*---- Signup forms ----*/\n if (!dissolving) {\n const disclosure =\n preview?.disclosures?.find(\n (entry) =>\n entry &&\n entry.after &&\n (entry.groupId === change.groupId ||\n (Array.isArray(entry.hostIds) &&\n entry.hostIds.length === change.hostIds.length &&\n entry.hostIds.every((hostId) => change.hostIds.includes(hostId)))),\n )?.after ?? draftDisclosure(group, change.hostIds, change.groupId ?? 'new')\n if (disclosure && (joins || change.renamed || change.removed.length)) {\n say(\n forms,\n 'forms.disclosure',\n change.kind === 'create'\n ? `Signup forms on these sites will say: “${disclosure}”`\n : `Signup forms on ${group}’s sites will say: “${disclosure}”`,\n )\n }\n if (change.renamed) {\n say(\n forms,\n 'forms.rename',\n `Signup forms and preference pages will show ${group} from now on. People who already signed up saw ${change.previousName}; their records keep it.`,\n )\n }\n const surfaces = (preview?.capturesDisclosing ?? [])\n .map((surface) => CAPTURE_SURFACE_NAMES[surface] ?? surface)\n .filter(Boolean)\n if (joins && surfaces.length) {\n say(\n forms,\n 'forms.surfaces',\n `Only ${joinNames(surfaces)} show this name for now. Anywhere else someone signs up, their consent is recorded for the one site they used.`,\n )\n }\n }\n if (leaving.length) {\n say(\n forms,\n 'forms.leave',\n dissolving\n ? `${names(leaving)} will each send on their own again; their signup forms will stop naming ${group}.`\n : leaving.length === 1\n ? `${names(leaving)} will send on its own again; its signup forms will stop naming ${group}.`\n : `${names(leaving)} will each send on their own again; their signup forms will stop naming ${group}.`,\n )\n }\n\n /*---- Who may be emailed: grants ----*/\n if (change.kind === 'create') {\n say(\n consent,\n 'consent.forward',\n 'People who signed up before now agreed to hear from one site. They are not shared; only people who sign up after this change, on a form that shows this name, are.',\n )\n } else if (joins) {\n say(\n consent,\n 'consent.forward',\n `People who signed up before now keep what they agreed to: nobody who signed up on ${names(joining)} is shared with ${group}’s other sites, or the other way around. Only people who sign up after this change, on a form that shows this name, are.`,\n )\n }\n for (const donor of change.donors) {\n if (donor.absorbed) continue\n say(\n consent,\n `consent.kept.${donor.groupId}`,\n `${names(donor.losing)} can still email the people who signed up to ${donor.name} while ${\n donor.losing.length === 1 ? 'it was' : 'they were'\n } part of it.`,\n )\n }\n if (leaving.length) {\n const kept = lineCount(preview, 'crm.grants')\n const who = kept != null ? `the ${people(kept)}` : 'the people'\n say(\n consent,\n 'consent.kept',\n dissolving || leaving.length > 1\n ? `Each site can still email ${who} who signed up to ${group} while it was part of it.`\n : `${names(leaving)} can still email ${who} who signed up to ${group} while it was part of it.`,\n )\n }\n\n /*---- Who stops getting mail: refusals ----*/\n if (joins) {\n const inherited = sum((preview?.inherited ?? []).map((entry) => entry?.refusals ?? 0))\n say(\n optOuts,\n 'opt-outs.inherit',\n `Anyone who unsubscribed from, opted out on, or declined email from any of these sites will stop getting marketing email from all of them${preview ? ` (${count(inherited)} ${inherited === 1 ? 'opt-out' : 'opt-outs'} on record)` : ''}.`,\n )\n if (awaitsConfirmation) {\n say(\n optOuts,\n 'opt-outs.confirmation',\n 'A confirmation one site is waiting for will hold the other sites’ email on that topic.',\n )\n }\n }\n /*\n * Separations: every pair of sites that stops reading each other keeps the\n * other's refusals. The copies are counted once over the whole plan, so the\n * count rides on the sentence when there is one, and on a total when there\n * are several.\n */\n const separations: Array<{ id: string; text: string }> = []\n if (dissolving) {\n separations.push({\n id: 'opt-outs.dissolve',\n text: `Everyone who opted out on any of ${group}’s sites stays opted out on every one of them`,\n })\n } else if (change.removed.length === 1) {\n const site = siteName(change.removed[0])\n separations.push({\n id: 'opt-outs.leave',\n text: `Everyone who opted out on any of ${group}’s sites stays opted out on ${site}, and everyone who opted out on ${site} stays opted out on ${group}’s other sites`,\n })\n } else if (change.removed.length > 1) {\n separations.push({\n id: 'opt-outs.leave',\n text: `Everyone who opted out on any of ${group}’s sites stays opted out on each site that leaves, and everyone who opted out on a site that leaves stays opted out on the rest`,\n })\n }\n for (const donor of change.donors) {\n if (donor.absorbed) continue\n separations.push({\n id: `opt-outs.donor.${donor.groupId}`,\n text: `Everyone who opted out on any of ${donor.name}’s sites stays opted out on ${names(donor.losing)}, and everyone who opted out on ${names(donor.losing)} stays opted out on ${names(donor.remaining)}`,\n })\n }\n const copied =\n sum(\n (preview?.carries ?? []).map(\n (carry) => (carry?.siteSuppressions ?? 0) + (carry?.topicOptOuts ?? 0),\n ),\n ) + (lineCount(preview, 'crm.refusals') ?? 0)\n const paces = sum((preview?.carries ?? []).map((carry) => carry?.paces ?? 0))\n const copiedNote = preview\n ? `${count(copied)} ${copied === 1 ? 'opt-out' : 'opt-outs'} copied${\n paces ? `, and ${count(paces)} email frequency ${paces === 1 ? 'choice' : 'choices'} kept` : ''\n }`\n : ''\n if (separations.length === 1) {\n say(\n optOuts,\n separations[0].id,\n `${separations[0].text}${copiedNote ? ` (${copiedNote})` : ''}.`,\n )\n } else if (separations.length > 1) {\n for (const separation of separations) {\n say(optOuts, separation.id, `${separation.text}.`)\n }\n if (copiedNote) {\n say(optOuts, 'opt-outs.total', `In all: ${copiedNote}.`)\n }\n }\n const holds = sum((preview?.pendingHolds ?? []).map((hold) => hold?.count ?? 0))\n if (holds > 0 && (leaving.length || change.donors.length)) {\n say(\n optOuts,\n 'opt-outs.pending',\n leaving.length === 1 && !dissolving && !change.donors.length\n ? `${people(holds)} waiting to confirm on ${names(leaving)} will no longer hold ${group}’s email, and vice versa.`\n : `${people(holds)} waiting to confirm on one of these sites will no longer hold the email of the sites it separates from.`,\n )\n }\n\n /*---- CRM records ----*/\n if (crm && joins) {\n const combined = lineCount(preview, 'crm.combine')\n say(\n records,\n 'records.combine',\n `The CRM records these sites keep about the same person will be combined into one record, visible to everyone who works on any of these sites. Where two records disagree about one value, such as the owner or stage, ${\n change.kind === 'create'\n ? 'the record from the site that met the person first'\n : `${group}’s record`\n } is kept.${combined != null ? ` This combines records for ${people(combined)}.` : ''}`,\n )\n }\n if (crm && leaving.length) {\n const copies = lineCount(preview, 'crm.copy')\n const met = copies != null ? `the ${people(copies)}` : 'the people'\n say(\n records,\n 'records.copy',\n dissolving || leaving.length > 1\n ? `Each site keeps a copy of the CRM record for ${met} it met itself.`\n : `${names(leaving)} keeps a copy of the CRM record for ${met} it met itself. ${group} keeps its records, including order totals for people who also bought elsewhere.`,\n )\n const seen = lineCount(preview, 'crm.visibility')\n if (seen != null && seen > 0) {\n say(\n records,\n 'records.visibility',\n dissolving || leaving.length > 1\n ? `Each site will still see the ${people(seen)} it met only through ${group}’s other sites, without their CRM details.`\n : `${names(leaving)} will still see ${people(seen)} it met only through ${group}’s other sites, without their CRM details.`,\n )\n }\n }\n if (crm && dissolving) {\n const figures = lineCount(preview, 'crm.figures')\n if (figures == null || figures > 0) {\n say(\n records,\n 'records.figures',\n figures != null\n ? `Order totals for ${people(figures)} who bought on more than one of these sites can’t be split and will be removed.`\n : 'Order totals for people who bought on more than one of these sites can’t be split and will be removed.',\n 'warning',\n )\n }\n }\n if (joins) {\n const partial = Number(preview?.partialAccessMembers ?? 0)\n if (partial > 0) {\n say(\n records,\n 'records.partial-access',\n `${count(partial)} team ${partial === 1 ? 'member' : 'members'} can open only some of these sites. They’ll keep seeing only those sites’ records.`,\n )\n }\n const forward = preview?.forwardPolicyWarning\n if (forward && typeof forward === 'object') {\n const hostIds = Array.isArray(forward.hostIds) ? forward.hostIds : joining\n const before =\n typeof forward.enforceFromMs === 'number'\n ? ` before ${dateFormat.format(new Date(forward.enforceFromMs))}`\n : ''\n say(\n records,\n 'records.forward',\n `${names(hostIds.length ? hostIds : change.hostIds)} will be able to email people these sites captured${before} without a recorded opt-in.`,\n 'warning',\n )\n }\n }\n\n /*---- Anything else the plugins reported, as they wrote it ----*/\n for (const line of participantLines(preview)) {\n if (!line || FOLDED_LINE_IDS.has(line.id) || !line.text) continue\n say(other, `line.${line.id}`, line.text, line.severity === 'warning' ? 'warning' : 'info')\n }\n if ((preview?.participants ?? []).some((participant) => participant && participant.lines === null)) {\n say(\n other,\n 'line.unanswered',\n 'Part of this change couldn’t be counted ahead of time. It still runs, and nobody who opted out starts getting email.',\n )\n }\n const discarded = (preview?.discarded ?? []).filter(\n (id) => typeof id === 'string' && id !== '',\n )\n if (discarded.length) {\n say(\n other,\n 'discarded',\n `${discarded.length === 1 ? 'One saved consent group' : `${count(discarded.length)} saved consent groups`} can’t be used — no name, too few or too many sites, or a site another group also claims — so nothing honors ${discarded.length === 1 ? 'it' : 'them'} today. This change removes ${discarded.length === 1 ? 'it' : 'them'}: ${joinNames(discarded)}.`,\n )\n }\n\n /*---- How long ----*/\n const timing: ConsentGroupReviewItem[] = []\n if (preview?.estimate && CONSENT_GROUP_ESTIMATE_TEXT[preview.estimate]) {\n say(timing, 'timing.estimate', CONSENT_GROUP_ESTIMATE_TEXT[preview.estimate])\n }\n say(timing, 'timing.leave', 'You can leave this page; it finishes on its own.')\n\n return {\n title: title(change),\n confirmLabel: confirmLabel(change),\n sections: [\n { id: 'summary', heading: null, items: summary },\n { id: 'forms', heading: 'Signup forms', items: forms },\n { id: 'consent', heading: 'Who can be emailed', items: consent },\n { id: 'opt-outs', heading: 'Opt-outs', items: optOuts },\n { id: 'records', heading: 'CRM records and access', items: records },\n { id: 'other', heading: 'Also', items: other },\n { id: 'timing', heading: null, items: timing },\n ].filter((section) => section.items.length > 0),\n }\n}\n"],"names":["draftDisclosure","listFormat","Intl","ListFormat","style","type","joinNames","names","format","count","n","toLocaleString","people","sum","values","reduce","total","value","Number","isFinite","dateFormat","DateTimeFormat","month","day","year","CONSENT_GROUP_ESTIMATE_TEXT","instant","minutes","long","CAPTURE_SURFACE_NAMES","form","newsletter","FOLDED_LINE_IDS","Set","participantLines","preview","participants","flatMap","participant","Array","isArray","lines","lineCount","id","line","find","entry","crmReported","some","String","startsWith","confirmLabel","change","kind","joining","added","length","movedIn","leaving","removed","renamed","title","name","previousName","describeConsentGroupReview","input","siteName","awaitsConfirmation","ids","map","group","move","hostId","joins","dissolving","previousHostIds","crm","summary","forms","consent","optOuts","records","other","say","into","text","tone","push","hostIds","tooFewSites","unselected","fromName","donor","donors","absorbed","groupId","dissolves","losing","remaining","disclosure","disclosures","after","every","includes","surfaces","capturesDisclosing","surface","filter","Boolean","kept","who","inherited","refusals","separations","site","copied","carries","carry","siteSuppressions","topicOptOuts","paces","copiedNote","separation","holds","pendingHolds","hold","combined","copies","met","seen","figures","partial","partialAccessMembers","forward","forwardPolicyWarning","before","enforceFromMs","Date","has","severity","discarded","timing","estimate","sections","heading","items","section"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/consent-group-review.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * What the admin is told before a consent group change is applied.\n *\n * A change here decides who may email whom, so the review says it in the\n * terms the admin will be held to rather than in the terms the code uses:\n * what signup forms will say, who stops getting mail, who can still be\n * mailed, and what happens to the CRM's records. Every number comes from the\n * route's preview, which counted it without writing anything; a sentence\n * whose number the preview did not supply is said without one rather than\n * with a guess.\n *\n * Pure, so each wording can be asserted against a preview without rendering.\n */\n\nimport type {\n ConsentGroupChangeEstimate,\n ConsentGroupChangePreview,\n ConsentGroupParticipantLine,\n} from './consent-groups-api'\nimport {\n draftDisclosure,\n type ConsentGroupDraftChange,\n} from './consent-group-editing'\n\n/** One sentence of the review. `warning` is the tone of an irreversible loss. */\nexport interface ConsentGroupReviewItem {\n id: string\n text: string\n tone: 'info' | 'warning'\n}\n\nexport interface ConsentGroupReviewSection {\n id: string\n heading: string | null\n items: ConsentGroupReviewItem[]\n}\n\nexport interface ConsentGroupReview {\n title: string\n /** The primary button, named for what it does. */\n confirmLabel: string\n sections: ConsentGroupReviewSection[]\n}\n\nexport interface ConsentGroupReviewInput {\n change: ConsentGroupDraftChange\n /** `null` while the preview is still being counted. */\n preview: ConsentGroupChangePreview | null\n siteName: (hostId: string) => string\n /** The org's switch: a confirmation one site waits on holds its siblings. */\n awaitsConfirmation: boolean\n}\n\n/*==========================================\n * WORDS\n *=========================================*/\n\nconst listFormat = new Intl.ListFormat('en-US', {\n style: 'long',\n type: 'conjunction',\n})\n\n/** `A`, `A and B`, `A, B, and C`. */\nexport function joinNames(names: readonly string[]): string {\n return listFormat.format(names)\n}\n\nconst count = (n: number) => n.toLocaleString('en-US')\nconst people = (n: number) => `${count(n)} ${n === 1 ? 'person' : 'people'}`\n\nconst sum = (values: readonly number[]) =>\n values.reduce((total, value) => total + (Number.isFinite(value) ? value : 0), 0)\n\nconst dateFormat = new Intl.DateTimeFormat('en-US', {\n month: 'long',\n day: 'numeric',\n year: 'numeric',\n})\n\n/** What an estimate means for the person about to wait on it. */\nexport const CONSENT_GROUP_ESTIMATE_TEXT: Readonly<\n Record<ConsentGroupChangeEstimate, string>\n> = {\n instant: 'This takes effect right away.',\n 'under-a-minute': 'This should take less than a minute.',\n minutes: 'This will take a few minutes.',\n long: 'This will take a while — longer for organizations with many contacts.',\n}\n\n/** What a capture surface is called where a sentence names it. */\nconst CAPTURE_SURFACE_NAMES: Readonly<Record<string, string>> = {\n form: 'signup forms',\n newsletter: 'newsletter signups',\n}\n\n/*==========================================\n * THE PARTICIPANTS' OWN LINES\n *=========================================*/\n\n/**\n * The CRM's counted lines the review writes its own sentences around.\n *\n * The CRM reports what the change does to its records as lines with these\n * ids; the review folds each count into the sentence it belongs to, so the\n * admin reads one account of the change rather than two. A line with any\n * other id is printed as the plugin wrote it.\n */\nconst FOLDED_LINE_IDS = new Set([\n 'crm.combine',\n 'crm.copy',\n 'crm.figures',\n 'crm.refusals',\n 'crm.grants',\n 'crm.visibility',\n])\n\nfunction participantLines(\n preview: ConsentGroupChangePreview | null,\n): ConsentGroupParticipantLine[] {\n return (preview?.participants ?? []).flatMap((participant) =>\n Array.isArray(participant?.lines) ? participant.lines : [],\n )\n}\n\n/** A folded line's count, or `null` when it was not reported or not counted. */\nfunction lineCount(\n preview: ConsentGroupChangePreview | null,\n id: string,\n): number | null {\n const line = participantLines(preview).find((entry) => entry?.id === id)\n return line && typeof line.count === 'number' ? line.count : null\n}\n\n/** Whether the CRM spoke at all — its sentences are said only if it did. */\nfunction crmReported(preview: ConsentGroupChangePreview | null): boolean {\n return participantLines(preview).some((line) =>\n String(line?.id ?? '').startsWith('crm.'),\n )\n}\n\n/*==========================================\n * THE REVIEW\n *=========================================*/\n\nfunction confirmLabel(change: ConsentGroupDraftChange): string {\n if (change.kind === 'create') return 'Create group'\n if (change.kind === 'dissolve') return 'Dissolve group'\n const joining = change.added.length + change.movedIn.length\n const leaving = change.removed.length\n if (change.renamed && !joining && !leaving) return 'Rename group'\n if (!change.renamed && joining && !leaving) {\n return joining === 1 ? 'Add site' : 'Add sites'\n }\n if (!change.renamed && leaving && !joining) {\n return leaving === 1 ? 'Remove site' : 'Remove sites'\n }\n return 'Apply changes'\n}\n\nfunction title(change: ConsentGroupDraftChange): string {\n if (change.kind === 'create') return `Create “${change.name}”`\n if (change.kind === 'dissolve') return `Dissolve “${change.name}”`\n return `Change “${change.previousName ?? change.name}”`\n}\n\n/** Builds every section of the review, in the order it is read. */\nexport function describeConsentGroupReview(\n input: ConsentGroupReviewInput,\n): ConsentGroupReview {\n const { change, preview, siteName, awaitsConfirmation } = input\n const names = (ids: readonly string[]) => joinNames(ids.map(siteName))\n const group = change.name\n const joining = [...change.added, ...change.movedIn.map((move) => move.hostId)]\n const joins = change.kind === 'create' || joining.length > 0\n const dissolving = change.kind === 'dissolve'\n const leaving = dissolving ? change.previousHostIds : change.removed\n const crm = crmReported(preview)\n\n const summary: ConsentGroupReviewItem[] = []\n const forms: ConsentGroupReviewItem[] = []\n const consent: ConsentGroupReviewItem[] = []\n const optOuts: ConsentGroupReviewItem[] = []\n const records: ConsentGroupReviewItem[] = []\n const other: ConsentGroupReviewItem[] = []\n const say = (\n into: ConsentGroupReviewItem[],\n id: string,\n text: string,\n tone: ConsentGroupReviewItem['tone'] = 'info',\n ) => into.push({ id, text, tone })\n\n /*---- What changes ----*/\n if (change.kind === 'create') {\n say(summary, 'summary.create', `Create ${group} with ${names(change.hostIds)}.`)\n }\n if (change.tooFewSites) {\n say(\n summary,\n 'summary.too-few',\n change.unselected.length\n ? `A consent group needs at least two sites. Removing ${names(change.unselected)} dissolves ${group}.`\n : `A consent group needs at least two sites, so this dissolves ${group}.`,\n 'warning',\n )\n } else if (dissolving) {\n say(summary, 'summary.dissolve', `Dissolve ${group}.`)\n }\n if (change.renamed) {\n say(\n summary,\n 'summary.rename',\n `Rename ${change.previousName} to ${group}.`,\n )\n }\n if (change.kind === 'edit' && change.added.length) {\n say(summary, 'summary.add', `Add ${names(change.added)} to ${group}.`)\n }\n for (const move of change.movedIn) {\n say(\n summary,\n `summary.move.${move.hostId}`,\n `Move ${siteName(move.hostId)} from ${move.fromName} to ${group}.`,\n )\n }\n if (change.kind === 'edit' && change.removed.length) {\n say(summary, 'summary.remove', `Remove ${names(change.removed)} from ${group}.`)\n }\n for (const donor of change.donors) {\n if (donor.absorbed) {\n say(\n summary,\n `summary.absorb.${donor.groupId}`,\n `${donor.name} will be merged into ${group}, so ${donor.name} stops existing.`,\n )\n } else if (donor.dissolves) {\n say(\n summary,\n `summary.donor-dissolve.${donor.groupId}`,\n `Moving ${names(donor.losing)} dissolves ${donor.name}, because a consent group needs at least two sites. ${names(donor.remaining)} will send on its own.`,\n 'warning',\n )\n }\n }\n\n /*---- Signup forms ----*/\n if (!dissolving) {\n const disclosure =\n preview?.disclosures?.find(\n (entry) =>\n entry &&\n entry.after &&\n (entry.groupId === change.groupId ||\n (Array.isArray(entry.hostIds) &&\n entry.hostIds.length === change.hostIds.length &&\n entry.hostIds.every((hostId) => change.hostIds.includes(hostId)))),\n )?.after ?? draftDisclosure(group, change.hostIds, change.groupId ?? 'new')\n if (disclosure && (joins || change.renamed || change.removed.length)) {\n say(\n forms,\n 'forms.disclosure',\n change.kind === 'create'\n ? `Signup forms on these sites will say: “${disclosure}”`\n : `Signup forms on ${group}’s sites will say: “${disclosure}”`,\n )\n }\n if (change.renamed) {\n say(\n forms,\n 'forms.rename',\n `Signup forms and preference pages will show ${group} from now on. People who already signed up saw ${change.previousName}; their records keep it.`,\n )\n }\n const surfaces = (preview?.capturesDisclosing ?? [])\n .map((surface) => CAPTURE_SURFACE_NAMES[surface] ?? surface)\n .filter(Boolean)\n if (joins && surfaces.length) {\n say(\n forms,\n 'forms.surfaces',\n `Only ${joinNames(surfaces)} show this name for now. Anywhere else someone signs up, their consent is recorded for the one site they used.`,\n )\n }\n }\n if (leaving.length) {\n say(\n forms,\n 'forms.leave',\n dissolving\n ? `${names(leaving)} will each send on their own again; their signup forms will stop naming ${group}.`\n : leaving.length === 1\n ? `${names(leaving)} will send on its own again; its signup forms will stop naming ${group}.`\n : `${names(leaving)} will each send on their own again; their signup forms will stop naming ${group}.`,\n )\n }\n\n /*---- Who may be emailed: grants ----*/\n if (change.kind === 'create') {\n say(\n consent,\n 'consent.forward',\n 'People who signed up before now agreed to hear from one site. They are not shared; only people who sign up after this change, on a form that shows this name, are.',\n )\n } else if (joins) {\n say(\n consent,\n 'consent.forward',\n `People who signed up before now keep what they agreed to: nobody who signed up on ${names(joining)} is shared with ${group}’s other sites, or the other way around. Only people who sign up after this change, on a form that shows this name, are.`,\n )\n }\n for (const donor of change.donors) {\n if (donor.absorbed) continue\n say(\n consent,\n `consent.kept.${donor.groupId}`,\n `${names(donor.losing)} can still email the people who signed up to ${donor.name} while ${\n donor.losing.length === 1 ? 'it was' : 'they were'\n } part of it.`,\n )\n }\n if (leaving.length) {\n const kept = lineCount(preview, 'crm.grants')\n const who = kept != null ? `the ${people(kept)}` : 'the people'\n say(\n consent,\n 'consent.kept',\n dissolving || leaving.length > 1\n ? `Each site can still email ${who} who signed up to ${group} while it was part of it.`\n : `${names(leaving)} can still email ${who} who signed up to ${group} while it was part of it.`,\n )\n }\n\n /*---- Who stops getting mail: refusals ----*/\n if (joins) {\n const inherited = sum((preview?.inherited ?? []).map((entry) => entry?.refusals ?? 0))\n say(\n optOuts,\n 'opt-outs.inherit',\n `Anyone who unsubscribed from, opted out on, or declined email from any of these sites will stop getting marketing email from all of them${preview ? ` (${count(inherited)} ${inherited === 1 ? 'opt-out' : 'opt-outs'} on record)` : ''}.`,\n )\n if (awaitsConfirmation) {\n say(\n optOuts,\n 'opt-outs.confirmation',\n 'A confirmation one site is waiting for will hold the other sites’ email on that topic.',\n )\n }\n }\n /*\n * Separations: every pair of sites that stops reading each other keeps the\n * other's refusals. The copies are counted once over the whole plan, so the\n * count rides on the sentence when there is one, and on a total when there\n * are several.\n */\n const separations: Array<{ id: string; text: string }> = []\n if (dissolving) {\n separations.push({\n id: 'opt-outs.dissolve',\n text: `Everyone who opted out on any of ${group}’s sites stays opted out on every one of them`,\n })\n } else if (change.removed.length === 1) {\n const site = siteName(change.removed[0])\n separations.push({\n id: 'opt-outs.leave',\n text: `Everyone who opted out on any of ${group}’s sites stays opted out on ${site}, and everyone who opted out on ${site} stays opted out on ${group}’s other sites`,\n })\n } else if (change.removed.length > 1) {\n separations.push({\n id: 'opt-outs.leave',\n text: `Everyone who opted out on any of ${group}’s sites stays opted out on each site that leaves, and everyone who opted out on a site that leaves stays opted out on the rest`,\n })\n }\n for (const donor of change.donors) {\n if (donor.absorbed) continue\n separations.push({\n id: `opt-outs.donor.${donor.groupId}`,\n text: `Everyone who opted out on any of ${donor.name}’s sites stays opted out on ${names(donor.losing)}, and everyone who opted out on ${names(donor.losing)} stays opted out on ${names(donor.remaining)}`,\n })\n }\n const copied =\n sum(\n (preview?.carries ?? []).map(\n (carry) =>\n (carry?.siteSuppressions ?? 0) +\n (carry?.topicOptOuts ?? 0) +\n (carry?.retainedRefusals ?? 0),\n ),\n ) + (lineCount(preview, 'crm.refusals') ?? 0)\n const paces = sum((preview?.carries ?? []).map((carry) => carry?.paces ?? 0))\n const copiedNote = preview\n ? `${count(copied)} ${copied === 1 ? 'opt-out' : 'opt-outs'} copied${\n paces ? `, and ${count(paces)} email frequency ${paces === 1 ? 'choice' : 'choices'} kept` : ''\n }`\n : ''\n if (separations.length === 1) {\n say(\n optOuts,\n separations[0].id,\n `${separations[0].text}${copiedNote ? ` (${copiedNote})` : ''}.`,\n )\n } else if (separations.length > 1) {\n for (const separation of separations) {\n say(optOuts, separation.id, `${separation.text}.`)\n }\n if (copiedNote) {\n say(optOuts, 'opt-outs.total', `In all: ${copiedNote}.`)\n }\n }\n const holds = sum((preview?.pendingHolds ?? []).map((hold) => hold?.count ?? 0))\n if (holds > 0 && (leaving.length || change.donors.length)) {\n say(\n optOuts,\n 'opt-outs.pending',\n leaving.length === 1 && !dissolving && !change.donors.length\n ? `${people(holds)} waiting to confirm on ${names(leaving)} will no longer hold ${group}’s email, and vice versa.`\n : `${people(holds)} waiting to confirm on one of these sites will no longer hold the email of the sites it separates from.`,\n )\n }\n\n /*---- CRM records ----*/\n if (crm && joins) {\n const combined = lineCount(preview, 'crm.combine')\n say(\n records,\n 'records.combine',\n `The CRM records these sites keep about the same person will be combined into one record, visible to everyone who works on any of these sites. Where two records disagree about one value, such as the owner or stage, ${\n change.kind === 'create'\n ? 'the record from the site that met the person first'\n : `${group}’s record`\n } is kept.${combined != null ? ` This combines records for ${people(combined)}.` : ''}`,\n )\n }\n if (crm && leaving.length) {\n const copies = lineCount(preview, 'crm.copy')\n const met = copies != null ? `the ${people(copies)}` : 'the people'\n say(\n records,\n 'records.copy',\n dissolving || leaving.length > 1\n ? `Each site keeps a copy of the CRM record for ${met} it met itself.`\n : `${names(leaving)} keeps a copy of the CRM record for ${met} it met itself. ${group} keeps its records, including order totals for people who also bought elsewhere.`,\n )\n const seen = lineCount(preview, 'crm.visibility')\n if (seen != null && seen > 0) {\n say(\n records,\n 'records.visibility',\n dissolving || leaving.length > 1\n ? `Each site will still see the ${people(seen)} it met only through ${group}’s other sites, without their CRM details.`\n : `${names(leaving)} will still see ${people(seen)} it met only through ${group}’s other sites, without their CRM details.`,\n )\n }\n }\n if (crm && dissolving) {\n const figures = lineCount(preview, 'crm.figures')\n if (figures == null || figures > 0) {\n say(\n records,\n 'records.figures',\n figures != null\n ? `Order totals for ${people(figures)} who bought on more than one of these sites can’t be split and will be removed.`\n : 'Order totals for people who bought on more than one of these sites can’t be split and will be removed.',\n 'warning',\n )\n }\n }\n if (joins) {\n const partial = Number(preview?.partialAccessMembers ?? 0)\n if (partial > 0) {\n say(\n records,\n 'records.partial-access',\n `${count(partial)} team ${partial === 1 ? 'member' : 'members'} can open only some of these sites. They’ll keep seeing only those sites’ records.`,\n )\n }\n const forward = preview?.forwardPolicyWarning\n if (forward && typeof forward === 'object') {\n const hostIds = Array.isArray(forward.hostIds) ? forward.hostIds : joining\n const before =\n typeof forward.enforceFromMs === 'number'\n ? ` before ${dateFormat.format(new Date(forward.enforceFromMs))}`\n : ''\n say(\n records,\n 'records.forward',\n `${names(hostIds.length ? hostIds : change.hostIds)} will be able to email people these sites captured${before} without a recorded opt-in.`,\n 'warning',\n )\n }\n }\n\n /*---- Anything else the plugins reported, as they wrote it ----*/\n for (const line of participantLines(preview)) {\n if (!line || FOLDED_LINE_IDS.has(line.id) || !line.text) continue\n say(other, `line.${line.id}`, line.text, line.severity === 'warning' ? 'warning' : 'info')\n }\n if ((preview?.participants ?? []).some((participant) => participant && participant.lines === null)) {\n say(\n other,\n 'line.unanswered',\n 'Part of this change couldn’t be counted ahead of time. It still runs, and nobody who opted out starts getting email.',\n )\n }\n const discarded = (preview?.discarded ?? []).filter(\n (id) => typeof id === 'string' && id !== '',\n )\n if (discarded.length) {\n say(\n other,\n 'discarded',\n `${discarded.length === 1 ? 'One saved consent group' : `${count(discarded.length)} saved consent groups`} can’t be used — no name, too few or too many sites, or a site another group also claims — so nothing honors ${discarded.length === 1 ? 'it' : 'them'} today. This change removes ${discarded.length === 1 ? 'it' : 'them'}: ${joinNames(discarded)}.`,\n )\n }\n\n /*---- How long ----*/\n const timing: ConsentGroupReviewItem[] = []\n if (preview?.estimate && CONSENT_GROUP_ESTIMATE_TEXT[preview.estimate]) {\n say(timing, 'timing.estimate', CONSENT_GROUP_ESTIMATE_TEXT[preview.estimate])\n }\n say(timing, 'timing.leave', 'You can leave this page; it finishes on its own.')\n\n return {\n title: title(change),\n confirmLabel: confirmLabel(change),\n sections: [\n { id: 'summary', heading: null, items: summary },\n { id: 'forms', heading: 'Signup forms', items: forms },\n { id: 'consent', heading: 'Who can be emailed', items: consent },\n { id: 'opt-outs', heading: 'Opt-outs', items: optOuts },\n { id: 'records', heading: 'CRM records and access', items: records },\n { id: 'other', heading: 'Also', items: other },\n { id: 'timing', heading: null, items: timing },\n ].filter((section) => section.items.length > 0),\n }\n}\n"],"names":["draftDisclosure","listFormat","Intl","ListFormat","style","type","joinNames","names","format","count","n","toLocaleString","people","sum","values","reduce","total","value","Number","isFinite","dateFormat","DateTimeFormat","month","day","year","CONSENT_GROUP_ESTIMATE_TEXT","instant","minutes","long","CAPTURE_SURFACE_NAMES","form","newsletter","FOLDED_LINE_IDS","Set","participantLines","preview","participants","flatMap","participant","Array","isArray","lines","lineCount","id","line","find","entry","crmReported","some","String","startsWith","confirmLabel","change","kind","joining","added","length","movedIn","leaving","removed","renamed","title","name","previousName","describeConsentGroupReview","input","siteName","awaitsConfirmation","ids","map","group","move","hostId","joins","dissolving","previousHostIds","crm","summary","forms","consent","optOuts","records","other","say","into","text","tone","push","hostIds","tooFewSites","unselected","fromName","donor","donors","absorbed","groupId","dissolves","losing","remaining","disclosure","disclosures","after","every","includes","surfaces","capturesDisclosing","surface","filter","Boolean","kept","who","inherited","refusals","separations","site","copied","carries","carry","siteSuppressions","topicOptOuts","retainedRefusals","paces","copiedNote","separation","holds","pendingHolds","hold","combined","copies","met","seen","figures","partial","partialAccessMembers","forward","forwardPolicyWarning","before","enforceFromMs","Date","has","severity","discarded","timing","estimate","sections","heading","items","section"],"mappings":"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"}
|
|
@@ -58,7 +58,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
58
58
|
* nobody has opened.
|
|
59
59
|
*/ import { CONTACT_LIFECYCLE_STAGES, CONTACT_LIFECYCLE_STAGE_LABELS, CONTACT_SOURCE_LABELS, DYNAMIC_LIST_CUSTOM_OPS, normalizeDynamicListRule } from "@aglyn/aglyn";
|
|
60
60
|
import { Autocomplete, Box, Button, Divider, MenuItem, Stack, TextField, Typography } from "@mui/material";
|
|
61
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
61
|
+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
62
62
|
import { useHostCampaigns, useOrgMemberOptions } from "@aglyn/tenant-feature-instance";
|
|
63
63
|
import CampaignPicker from "@aglyn/shared-ui-email-campaigns/components/campaign-picker.component";
|
|
64
64
|
import { useOrgCompanyOptions } from "../hooks/use-org-company-options.js";
|
|
@@ -585,6 +585,53 @@ export const EMPTY_RULE_DRAFT = {
|
|
|
585
585
|
negate: true
|
|
586
586
|
} : {})));
|
|
587
587
|
}
|
|
588
|
+
const companyLabel = (option)=>option.label;
|
|
589
|
+
const sameCompany = (option, chosen)=>option.id === chosen.id;
|
|
590
|
+
// The query already narrowed the hits; a second, client-side filter on the
|
|
591
|
+
// label would hide a match whose stored name differs from its search key.
|
|
592
|
+
const keepHits = (options)=>options;
|
|
593
|
+
const COMPANY_FIELD_SX = {
|
|
594
|
+
minWidth: 260,
|
|
595
|
+
flexGrow: 1,
|
|
596
|
+
maxWidth: 420
|
|
597
|
+
};
|
|
598
|
+
/**
|
|
599
|
+
* The rule's companies, as chips beside a search — MEMOIZED, and every prop it
|
|
600
|
+
* takes is stable while the chosen companies and the search are (AGL-3423).
|
|
601
|
+
*
|
|
602
|
+
* A multiple Autocomplete hands its input a new chip array as
|
|
603
|
+
* `startAdornment` on every render, and MUI's `InputBase` copies that into
|
|
604
|
+
* its `FormControl` from a passive effect: a state update left pending after
|
|
605
|
+
* every commit the field takes part in. React 19 counts each such commit as a
|
|
606
|
+
* nested update, and keystrokes delivered back to back commit one after
|
|
607
|
+
* another with nothing to clear the count, so the fifty-first throws
|
|
608
|
+
* "Maximum update depth exceeded" (#185). Every other field of the rule
|
|
609
|
+
* writes the same draft, so each keystroke in them re-rendered this one;
|
|
610
|
+
* kept out of those renders, it commits only when it changes.
|
|
611
|
+
*/ const RuleCompaniesField = /*#__PURE__*/ memo(function RuleCompaniesField(props) {
|
|
612
|
+
const { onSearch, onChange } = props;
|
|
613
|
+
return /*#__PURE__*/ _jsx(Autocomplete, {
|
|
614
|
+
multiple: true,
|
|
615
|
+
size: "small",
|
|
616
|
+
options: props.hits,
|
|
617
|
+
value: props.value,
|
|
618
|
+
getOptionLabel: companyLabel,
|
|
619
|
+
isOptionEqualToValue: sameCompany,
|
|
620
|
+
filterOptions: keepHits,
|
|
621
|
+
inputValue: props.search,
|
|
622
|
+
onInputChange: (_event, value, reason)=>{
|
|
623
|
+
if (reason !== 'reset') onSearch(value);
|
|
624
|
+
},
|
|
625
|
+
onChange: (_event, value)=>onChange(value.map((option)=>option.id)),
|
|
626
|
+
loading: props.searching,
|
|
627
|
+
noOptionsText: props.search ? 'No company by that name' : 'Type to search companies',
|
|
628
|
+
renderInput: (params)=>/*#__PURE__*/ _jsx(TextField, _extends({}, params, {
|
|
629
|
+
label: "At company",
|
|
630
|
+
helperText: "Any of these companies"
|
|
631
|
+
})),
|
|
632
|
+
sx: COMPANY_FIELD_SX
|
|
633
|
+
});
|
|
634
|
+
});
|
|
588
635
|
export function DynamicListRuleFields(props) {
|
|
589
636
|
var _team_error;
|
|
590
637
|
const { scope, hostId, draft, onChange, listId } = props;
|
|
@@ -753,6 +800,32 @@ export function DynamicListRuleFields(props) {
|
|
|
753
800
|
[key]: value
|
|
754
801
|
}));
|
|
755
802
|
/*
|
|
803
|
+
* `set('companyIds', …)` for the memoized company chips: the same write from
|
|
804
|
+
* the last rendered draft, from a callback that keeps its identity.
|
|
805
|
+
*/ const latest = useRef({
|
|
806
|
+
draft,
|
|
807
|
+
onChange
|
|
808
|
+
});
|
|
809
|
+
latest.current = {
|
|
810
|
+
draft,
|
|
811
|
+
onChange
|
|
812
|
+
};
|
|
813
|
+
const setCompanyIds = useCallback((companyIds)=>latest.current.onChange(_extends({}, latest.current.draft, {
|
|
814
|
+
companyIds
|
|
815
|
+
})), []);
|
|
816
|
+
const companyIds = draft.companyIds;
|
|
817
|
+
const companyNames = companies.names;
|
|
818
|
+
const chosenCompanies = useMemo(()=>companyIds.map((id)=>{
|
|
819
|
+
var _companyNames_id;
|
|
820
|
+
return {
|
|
821
|
+
id,
|
|
822
|
+
label: (_companyNames_id = companyNames[id]) != null ? _companyNames_id : id
|
|
823
|
+
};
|
|
824
|
+
}), [
|
|
825
|
+
companyIds,
|
|
826
|
+
companyNames
|
|
827
|
+
]);
|
|
828
|
+
/*
|
|
756
829
|
* THE CUSTOM-FIELD ROWS. A new row names the first definition with an
|
|
757
830
|
* empty value, so it is on screen and NOT yet a filter — `draftDimensions`
|
|
758
831
|
* hands it to the normalizer, which drops a valued operator with no value
|
|
@@ -1066,39 +1139,13 @@ export function DynamicListRuleFields(props) {
|
|
|
1066
1139
|
children: CONTACT_LIFECYCLE_STAGE_LABELS[stage]
|
|
1067
1140
|
}, stage))
|
|
1068
1141
|
}),
|
|
1069
|
-
/*#__PURE__*/ _jsx(
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
id,
|
|
1077
|
-
label: (_companies_names_id = companies.names[id]) != null ? _companies_names_id : id
|
|
1078
|
-
};
|
|
1079
|
-
}),
|
|
1080
|
-
getOptionLabel: (option)=>option.label,
|
|
1081
|
-
isOptionEqualToValue: (option, chosen)=>option.id === chosen.id,
|
|
1082
|
-
// The query already narrowed the hits; a second, client-side
|
|
1083
|
-
// filter on the label would hide a match whose stored name differs
|
|
1084
|
-
// from its search key.
|
|
1085
|
-
filterOptions: (options)=>options,
|
|
1086
|
-
inputValue: companySearch,
|
|
1087
|
-
onInputChange: (_event, value, reason)=>{
|
|
1088
|
-
if (reason !== 'reset') setCompanySearch(value);
|
|
1089
|
-
},
|
|
1090
|
-
onChange: (_event, value)=>set('companyIds', value.map((option)=>option.id)),
|
|
1091
|
-
loading: companies.searching,
|
|
1092
|
-
noOptionsText: companySearch ? 'No company by that name' : 'Type to search companies',
|
|
1093
|
-
renderInput: (params)=>/*#__PURE__*/ _jsx(TextField, _extends({}, params, {
|
|
1094
|
-
label: "At company",
|
|
1095
|
-
helperText: "Any of these companies"
|
|
1096
|
-
})),
|
|
1097
|
-
sx: {
|
|
1098
|
-
minWidth: 260,
|
|
1099
|
-
flexGrow: 1,
|
|
1100
|
-
maxWidth: 420
|
|
1101
|
-
}
|
|
1142
|
+
/*#__PURE__*/ _jsx(RuleCompaniesField, {
|
|
1143
|
+
hits: companies.hits,
|
|
1144
|
+
value: chosenCompanies,
|
|
1145
|
+
search: companySearch,
|
|
1146
|
+
searching: companies.searching,
|
|
1147
|
+
onSearch: setCompanySearch,
|
|
1148
|
+
onChange: setCompanyIds
|
|
1102
1149
|
}),
|
|
1103
1150
|
/*#__PURE__*/ _jsx(TextField, {
|
|
1104
1151
|
type: "number",
|