@aglyn/plugins-email 1.0.0-beta.196 → 1.0.0-beta.197
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-access.d.ts +33 -0
- package/src/lib/components/consent-group-access.js +73 -0
- package/src/lib/components/consent-group-access.js.map +1 -0
- package/src/lib/components/consent-group-change-progress.d.ts +55 -0
- package/src/lib/components/consent-group-change-progress.js +282 -0
- package/src/lib/components/consent-group-change-progress.js.map +1 -0
- package/src/lib/components/consent-group-confirmation-card.d.ts +6 -16
- package/src/lib/components/consent-group-confirmation-card.js +7 -50
- package/src/lib/components/consent-group-confirmation-card.js.map +1 -1
- package/src/lib/components/consent-group-dialog.d.ts +53 -0
- package/src/lib/components/consent-group-dialog.js +495 -0
- package/src/lib/components/consent-group-dialog.js.map +1 -0
- package/src/lib/components/consent-group-editing.d.ts +127 -0
- package/src/lib/components/consent-group-editing.js +225 -0
- package/src/lib/components/consent-group-editing.js.map +1 -0
- package/src/lib/components/consent-group-review.d.ts +62 -0
- package/src/lib/components/consent-group-review.js +353 -0
- package/src/lib/components/consent-group-review.js.map +1 -0
- package/src/lib/components/consent-group-site-picker.d.ts +35 -0
- package/src/lib/components/consent-group-site-picker.js +190 -0
- package/src/lib/components/consent-group-site-picker.js.map +1 -0
- package/src/lib/components/consent-groups-api.d.ts +158 -0
- package/src/lib/components/consent-groups-api.js +250 -0
- package/src/lib/components/consent-groups-api.js.map +1 -0
- package/src/lib/components/consent-groups-card.d.ts +47 -0
- package/src/lib/components/consent-groups-card.js +351 -0
- package/src/lib/components/consent-groups-card.js.map +1 -0
- package/src/lib/components/email-screens-card.js +1 -1
- package/src/lib/components/email-screens-card.js.map +1 -1
- package/src/lib/components/emails-console-page.d.ts +1 -1
- package/src/lib/components/emails-console-page.js +62 -27
- package/src/lib/components/emails-console-page.js.map +1 -1
- package/src/lib/components/emails-console-sections.d.ts +1 -1
- package/src/lib/components/emails-console-sections.js +14 -1
- package/src/lib/components/emails-console-sections.js.map +1 -1
- package/src/lib/components/org-email-templates-card.js +1 -1
- package/src/lib/components/org-email-templates-card.js.map +1 -1
- package/src/lib/components/site-consent-group-card.d.ts +23 -0
- package/src/lib/components/site-consent-group-card.js +80 -0
- package/src/lib/components/site-consent-group-card.js.map +1 -0
- package/src/lib/components/suppressions-card.js +28 -7
- package/src/lib/components/suppressions-card.js.map +1 -1
- package/src/lib/server-console.js +4 -2
- package/src/lib/server-console.js.map +1 -1
- package/src/lib/server-list-gate.d.ts +9 -0
- package/src/lib/server-list-gate.js +23 -9
- package/src/lib/server-list-gate.js.map +1 -1
- package/src/lib/server-list-import.js +5 -2
- package/src/lib/server-list-import.js.map +1 -1
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
19
|
+
import { consentGroupsAwaitConfirmation } from "@aglyn/aglyn/app-utils/consent-groups";
|
|
20
|
+
import { useUser } from "@aglyn/tenant-feature-instance";
|
|
21
|
+
import { Alert, Box, Button, CircularProgress, Dialog, DialogActions, DialogContent, DialogTitle, Stack, TextField, Typography } from "@mui/material";
|
|
22
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
23
|
+
import { buildConsentGroupDeclaration, describeConsentGroupDraft, draftDisclosure, expectedConsentGroups, listConsentGroups, validateConsentGroupDraft } from "./consent-group-editing.js";
|
|
24
|
+
import { describeConsentGroupReview, joinNames } from "./consent-group-review.js";
|
|
25
|
+
import ConsentGroupSitePicker from "./consent-group-site-picker.js";
|
|
26
|
+
import { CONSENT_GROUP_NAME_MAX, consentGroupIssueMessage, postConsentGroups } from "./consent-groups-api.js";
|
|
27
|
+
const NO_ISSUES = {
|
|
28
|
+
hostIds: new Set(),
|
|
29
|
+
general: []
|
|
30
|
+
};
|
|
31
|
+
const NAME_CODES = new Set([
|
|
32
|
+
'name-empty',
|
|
33
|
+
'name-too-long',
|
|
34
|
+
'name-duplicate'
|
|
35
|
+
]);
|
|
36
|
+
const SITE_CODES = new Set([
|
|
37
|
+
'too-few-sites',
|
|
38
|
+
'too-many-sites',
|
|
39
|
+
'group-replaced',
|
|
40
|
+
'unknown-site',
|
|
41
|
+
'site-in-two-groups'
|
|
42
|
+
]);
|
|
43
|
+
/**
|
|
44
|
+
* Declare, change or dissolve one consent group (AGL-3320).
|
|
45
|
+
*
|
|
46
|
+
* ## Two steps, because the second one is the consequence
|
|
47
|
+
*
|
|
48
|
+
* The EDIT step is the declaration: a name, which every signup form on the
|
|
49
|
+
* group's sites will show, and the sites. The REVIEW step is what that
|
|
50
|
+
* declaration does to people — who stops getting mail, who can still be
|
|
51
|
+
* mailed, what happens to the CRM's records — counted by the route before
|
|
52
|
+
* anything is written. Nothing changes until the review's button is pressed,
|
|
53
|
+
* and that button is named for what it does.
|
|
54
|
+
*
|
|
55
|
+
* ## The whole declaration is sent, against the one that was read
|
|
56
|
+
*
|
|
57
|
+
* The route takes the org's complete next declaration and the raw one the
|
|
58
|
+
* dialog was opened against, so a change somebody else made meanwhile is
|
|
59
|
+
* refused rather than overwritten. The dialog snapshots that declaration
|
|
60
|
+
* when it opens and edits against the snapshot; when the route says it is
|
|
61
|
+
* stale, the dialog reloads the current one and asks again.
|
|
62
|
+
*
|
|
63
|
+
* ## Dissolving is where a group with too few sites ends up
|
|
64
|
+
*
|
|
65
|
+
* A group of one site is not a group, so unticking all but one site of an
|
|
66
|
+
* existing group does not produce an invalid form — it becomes the dissolve,
|
|
67
|
+
* and the dialog says so before anything is reviewed.
|
|
68
|
+
*/ export function ConsentGroupDialog(props) {
|
|
69
|
+
var _issues_name, _issues_sites, _ref, _ref1;
|
|
70
|
+
var _snapshot_groups_find;
|
|
71
|
+
const { open, mode, groupId, org, orgId, sites, onClose, onApplied } = props;
|
|
72
|
+
const { data: user } = useUser();
|
|
73
|
+
const [snapshot, setSnapshot] = useState({
|
|
74
|
+
expected: null,
|
|
75
|
+
groups: []
|
|
76
|
+
});
|
|
77
|
+
const [name, setName] = useState('');
|
|
78
|
+
const [hostIds, setHostIds] = useState([]);
|
|
79
|
+
const [step, setStep] = useState('edit');
|
|
80
|
+
const [preview, setPreview] = useState(null);
|
|
81
|
+
const [busy, setBusy] = useState(null);
|
|
82
|
+
const [alert, setAlert] = useState(null);
|
|
83
|
+
const [issues, setIssues] = useState(NO_ISSUES);
|
|
84
|
+
const [touched, setTouched] = useState(false);
|
|
85
|
+
/**
|
|
86
|
+
* Set when a review should be asked for as soon as the snapshot it depends
|
|
87
|
+
* on has rendered: a dissolve on open, and a dissolve reloaded after a
|
|
88
|
+
* stale answer. State rather than a call, because the call made in the
|
|
89
|
+
* same pass as the load would post the declaration from before it.
|
|
90
|
+
*/ const [autoPreview, setAutoPreview] = useState(false);
|
|
91
|
+
/** Bumped on every open, so an answer to a closed dialog is dropped. */ const session = useRef(0);
|
|
92
|
+
const orgRef = useRef(org);
|
|
93
|
+
orgRef.current = org;
|
|
94
|
+
/**
|
|
95
|
+
* Starts (or restarts, after a stale answer) from a declaration.
|
|
96
|
+
*
|
|
97
|
+
* A new group's draft survives a restart — the name and the sites mean the
|
|
98
|
+
* same against the declaration as it now stands, and any site somebody
|
|
99
|
+
* else grouped meanwhile shows it on its badge. An edit's draft does not:
|
|
100
|
+
* it was made to a group that has since changed.
|
|
101
|
+
*/ const load = useCallback((raw, keepDraft = false)=>{
|
|
102
|
+
var _orgRef_current, _ref, _ref1;
|
|
103
|
+
// The org as it stands with `raw` as its declaration, so the listing
|
|
104
|
+
// refuses exactly what the route's reader refuses — a group id that
|
|
105
|
+
// is also one of the org's sites among them.
|
|
106
|
+
const groups = listConsentGroups(_extends({}, (_orgRef_current = orgRef.current) != null ? _orgRef_current : {}, {
|
|
107
|
+
consentGroups: raw
|
|
108
|
+
}));
|
|
109
|
+
setSnapshot({
|
|
110
|
+
expected: raw,
|
|
111
|
+
groups
|
|
112
|
+
});
|
|
113
|
+
setPreview(null);
|
|
114
|
+
setIssues(NO_ISSUES);
|
|
115
|
+
if (keepDraft) return groups;
|
|
116
|
+
const group = groupId ? groups.find((entry)=>entry.id === groupId) : null;
|
|
117
|
+
setName(mode === 'create' ? '' : (_ref = group == null ? void 0 : group.name) != null ? _ref : '');
|
|
118
|
+
setHostIds(mode === 'create' ? [] : [
|
|
119
|
+
...(_ref1 = group == null ? void 0 : group.hostIds) != null ? _ref1 : []
|
|
120
|
+
]);
|
|
121
|
+
setTouched(false);
|
|
122
|
+
return groups;
|
|
123
|
+
}, [
|
|
124
|
+
groupId,
|
|
125
|
+
mode
|
|
126
|
+
]);
|
|
127
|
+
const draft = useMemo(()=>({
|
|
128
|
+
mode,
|
|
129
|
+
groupId: mode === 'create' ? null : groupId,
|
|
130
|
+
name,
|
|
131
|
+
hostIds
|
|
132
|
+
}), [
|
|
133
|
+
mode,
|
|
134
|
+
groupId,
|
|
135
|
+
name,
|
|
136
|
+
hostIds
|
|
137
|
+
]);
|
|
138
|
+
const change = useMemo(()=>describeConsentGroupDraft(snapshot.groups, draft), [
|
|
139
|
+
snapshot.groups,
|
|
140
|
+
draft
|
|
141
|
+
]);
|
|
142
|
+
const localErrors = useMemo(()=>validateConsentGroupDraft(snapshot.groups, draft), [
|
|
143
|
+
snapshot.groups,
|
|
144
|
+
draft
|
|
145
|
+
]);
|
|
146
|
+
const groupExists = mode === 'create' || snapshot.groups.some((group)=>group.id === groupId);
|
|
147
|
+
const siteName = useCallback((hostId)=>{
|
|
148
|
+
const site = sites.find((entry)=>entry.id === hostId);
|
|
149
|
+
return (site == null ? void 0 : site.name) || (site == null ? void 0 : site.subdomain) || hostId;
|
|
150
|
+
}, [
|
|
151
|
+
sites
|
|
152
|
+
]);
|
|
153
|
+
/** Sorts a validation answer onto the name, the sites or the banner. */ const placeIssues = useCallback((errors, editedIndex)=>{
|
|
154
|
+
const placed = {
|
|
155
|
+
hostIds: new Set(),
|
|
156
|
+
general: []
|
|
157
|
+
};
|
|
158
|
+
const posted = buildConsentGroupDeclaration(snapshot.groups, draft).groups;
|
|
159
|
+
for (const issue of errors){
|
|
160
|
+
const code = issue.code;
|
|
161
|
+
const message = consentGroupIssueMessage(issue, siteName);
|
|
162
|
+
// A dissolve has no fields on screen, so everything goes to the banner.
|
|
163
|
+
const ours = editedIndex != null && (issue.groupIndex == null || issue.groupIndex === editedIndex);
|
|
164
|
+
if (issue.hostId) placed.hostIds.add(issue.hostId);
|
|
165
|
+
if (ours && NAME_CODES.has(code)) {
|
|
166
|
+
var _placed, _name;
|
|
167
|
+
(_name = (_placed = placed).name) != null ? _name : _placed.name = message;
|
|
168
|
+
} else if (ours && SITE_CODES.has(code)) {
|
|
169
|
+
var _placed1, _sites;
|
|
170
|
+
(_sites = (_placed1 = placed).sites) != null ? _sites : _placed1.sites = message;
|
|
171
|
+
} else {
|
|
172
|
+
var _posted_issue_groupIndex;
|
|
173
|
+
const other = issue.groupIndex != null ? (_posted_issue_groupIndex = posted[issue.groupIndex]) == null ? void 0 : _posted_issue_groupIndex.name : undefined;
|
|
174
|
+
placed.general.push(other ? `${other}: ${message}` : message);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
setIssues(placed);
|
|
178
|
+
}, [
|
|
179
|
+
snapshot.groups,
|
|
180
|
+
draft,
|
|
181
|
+
siteName
|
|
182
|
+
]);
|
|
183
|
+
/** What every refused call does, whichever step made it. */ const handleFailure = useCallback((failure, editedIndex)=>{
|
|
184
|
+
if (failure.kind === 'invalid') {
|
|
185
|
+
placeIssues(failure.errors, editedIndex);
|
|
186
|
+
setAlert(failure.errors.length ? null : {
|
|
187
|
+
severity: 'error',
|
|
188
|
+
message: failure.message
|
|
189
|
+
});
|
|
190
|
+
if (mode !== 'dissolve') setStep('edit');
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
if (failure.kind === 'stale') {
|
|
194
|
+
var _failure_current;
|
|
195
|
+
// Reload what is there now, and let the admin look again.
|
|
196
|
+
const groups = load((_failure_current = failure.current) != null ? _failure_current : expectedConsentGroups(orgRef.current), mode === 'create');
|
|
197
|
+
const stillThere = mode === 'create' || groups.some((group)=>group.id === groupId);
|
|
198
|
+
setAlert({
|
|
199
|
+
severity: 'warning',
|
|
200
|
+
message: stillThere ? `${failure.message} This shows what they saved; check your change and review it again.` : `${failure.message} This consent group no longer exists.`
|
|
201
|
+
});
|
|
202
|
+
setStep(mode === 'dissolve' ? 'review' : 'edit');
|
|
203
|
+
if (mode === 'dissolve' && stillThere) setAutoPreview(true);
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
setAlert({
|
|
207
|
+
severity: failure.kind === 'in-flight' ? 'info' : 'error',
|
|
208
|
+
message: failure.message
|
|
209
|
+
});
|
|
210
|
+
}, [
|
|
211
|
+
placeIssues,
|
|
212
|
+
load,
|
|
213
|
+
mode,
|
|
214
|
+
groupId
|
|
215
|
+
]);
|
|
216
|
+
const requestPreview = useCallback(async ()=>{
|
|
217
|
+
if (!user) return;
|
|
218
|
+
const ticket = session.current;
|
|
219
|
+
const { groups, editedIndex } = buildConsentGroupDeclaration(snapshot.groups, draft);
|
|
220
|
+
setBusy('preview');
|
|
221
|
+
setAlert(null);
|
|
222
|
+
setIssues(NO_ISSUES);
|
|
223
|
+
setPreview(null);
|
|
224
|
+
setStep('review');
|
|
225
|
+
const result = await postConsentGroups(user, {
|
|
226
|
+
orgId,
|
|
227
|
+
action: 'preview',
|
|
228
|
+
expected: snapshot.expected,
|
|
229
|
+
groups
|
|
230
|
+
});
|
|
231
|
+
if (ticket !== session.current) return;
|
|
232
|
+
setBusy(null);
|
|
233
|
+
if (result.ok) {
|
|
234
|
+
var _result_body_preview;
|
|
235
|
+
setPreview((_result_body_preview = result.body.preview) != null ? _result_body_preview : null);
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
handleFailure(result.failure, editedIndex);
|
|
239
|
+
}, [
|
|
240
|
+
user,
|
|
241
|
+
snapshot,
|
|
242
|
+
draft,
|
|
243
|
+
orgId,
|
|
244
|
+
handleFailure
|
|
245
|
+
]);
|
|
246
|
+
const apply = useCallback(async ()=>{
|
|
247
|
+
if (!user || !preview) return;
|
|
248
|
+
const ticket = session.current;
|
|
249
|
+
const { groups, editedIndex } = buildConsentGroupDeclaration(snapshot.groups, draft);
|
|
250
|
+
setBusy('apply');
|
|
251
|
+
setAlert(null);
|
|
252
|
+
const result = await postConsentGroups(user, {
|
|
253
|
+
orgId,
|
|
254
|
+
action: 'apply',
|
|
255
|
+
expected: snapshot.expected,
|
|
256
|
+
groups
|
|
257
|
+
});
|
|
258
|
+
if (ticket !== session.current) return;
|
|
259
|
+
setBusy(null);
|
|
260
|
+
if (result.ok) {
|
|
261
|
+
onApplied({
|
|
262
|
+
changeId: result.body.changeId,
|
|
263
|
+
done: result.body.done === true
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
handleFailure(result.failure, editedIndex);
|
|
268
|
+
}, [
|
|
269
|
+
user,
|
|
270
|
+
preview,
|
|
271
|
+
snapshot,
|
|
272
|
+
draft,
|
|
273
|
+
orgId,
|
|
274
|
+
onApplied,
|
|
275
|
+
handleFailure
|
|
276
|
+
]);
|
|
277
|
+
/*
|
|
278
|
+
* Open: snapshot the declaration as it stands, and start from the group as
|
|
279
|
+
* it is. A dissolve has nothing to edit, so it asks for its review at once.
|
|
280
|
+
*/ useEffect(()=>{
|
|
281
|
+
if (!open) return;
|
|
282
|
+
session.current += 1;
|
|
283
|
+
setAlert(null);
|
|
284
|
+
setBusy(null);
|
|
285
|
+
setStep(mode === 'dissolve' ? 'review' : 'edit');
|
|
286
|
+
load(expectedConsentGroups(orgRef.current));
|
|
287
|
+
setAutoPreview(mode === 'dissolve');
|
|
288
|
+
}, [
|
|
289
|
+
open,
|
|
290
|
+
mode,
|
|
291
|
+
groupId,
|
|
292
|
+
load
|
|
293
|
+
]);
|
|
294
|
+
useEffect(()=>{
|
|
295
|
+
if (!open || !autoPreview) return;
|
|
296
|
+
setAutoPreview(false);
|
|
297
|
+
void requestPreview();
|
|
298
|
+
}, [
|
|
299
|
+
open,
|
|
300
|
+
autoPreview,
|
|
301
|
+
requestPreview
|
|
302
|
+
]);
|
|
303
|
+
const review = useMemo(()=>step === 'review' ? describeConsentGroupReview({
|
|
304
|
+
change,
|
|
305
|
+
preview,
|
|
306
|
+
siteName,
|
|
307
|
+
awaitsConfirmation: consentGroupsAwaitConfirmation(org)
|
|
308
|
+
}) : null, [
|
|
309
|
+
step,
|
|
310
|
+
change,
|
|
311
|
+
preview,
|
|
312
|
+
siteName,
|
|
313
|
+
org
|
|
314
|
+
]);
|
|
315
|
+
const nameError = (_issues_name = issues.name) != null ? _issues_name : touched ? localErrors.name : undefined;
|
|
316
|
+
const sitesError = (_issues_sites = issues.sites) != null ? _issues_sites : touched && change.kind !== 'dissolve' ? localErrors.sites : undefined;
|
|
317
|
+
const disclosure = draftDisclosure(name, hostIds);
|
|
318
|
+
const dissolving = change.kind === 'dissolve';
|
|
319
|
+
const canReview = !busy && groupExists && !change.empty && (dissolving || !localErrors.name && !localErrors.sites);
|
|
320
|
+
const close = ()=>{
|
|
321
|
+
if (busy === 'apply') return;
|
|
322
|
+
session.current += 1;
|
|
323
|
+
onClose();
|
|
324
|
+
};
|
|
325
|
+
const title = step === 'review' && review ? review.title : mode === 'create' ? 'New consent group' : `Edit “${(_ref = (_snapshot_groups_find = snapshot.groups.find((group)=>group.id === groupId)) == null ? void 0 : _snapshot_groups_find.name) != null ? _ref : name}”`;
|
|
326
|
+
return /*#__PURE__*/ _jsxs(Dialog, {
|
|
327
|
+
open: open,
|
|
328
|
+
onClose: close,
|
|
329
|
+
maxWidth: "sm",
|
|
330
|
+
fullWidth: true,
|
|
331
|
+
children: [
|
|
332
|
+
/*#__PURE__*/ _jsx(DialogTitle, {
|
|
333
|
+
children: title
|
|
334
|
+
}),
|
|
335
|
+
/*#__PURE__*/ _jsx(DialogContent, {
|
|
336
|
+
dividers: true,
|
|
337
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
338
|
+
spacing: 2,
|
|
339
|
+
children: [
|
|
340
|
+
alert ? /*#__PURE__*/ _jsx(Alert, {
|
|
341
|
+
severity: alert.severity,
|
|
342
|
+
children: alert.message
|
|
343
|
+
}) : null,
|
|
344
|
+
issues.general.map((message)=>/*#__PURE__*/ _jsx(Alert, {
|
|
345
|
+
severity: "error",
|
|
346
|
+
children: message
|
|
347
|
+
}, message)),
|
|
348
|
+
step === 'edit' ? /*#__PURE__*/ _jsxs(_Fragment, {
|
|
349
|
+
children: [
|
|
350
|
+
/*#__PURE__*/ _jsx(TextField, {
|
|
351
|
+
label: "Name",
|
|
352
|
+
value: name,
|
|
353
|
+
onChange: (event)=>{
|
|
354
|
+
setName(event.target.value);
|
|
355
|
+
setIssues((current)=>_extends({}, current, {
|
|
356
|
+
name: undefined
|
|
357
|
+
}));
|
|
358
|
+
},
|
|
359
|
+
onBlur: ()=>setTouched(true),
|
|
360
|
+
error: Boolean(nameError),
|
|
361
|
+
helperText: nameError != null ? nameError : 'Signup forms on these sites show it, so use the name people know you by.',
|
|
362
|
+
slotProps: {
|
|
363
|
+
htmlInput: {
|
|
364
|
+
maxLength: CONSENT_GROUP_NAME_MAX + 20
|
|
365
|
+
}
|
|
366
|
+
},
|
|
367
|
+
autoFocus: mode === 'create',
|
|
368
|
+
required: true,
|
|
369
|
+
fullWidth: true
|
|
370
|
+
}),
|
|
371
|
+
/*#__PURE__*/ _jsxs(Box, {
|
|
372
|
+
children: [
|
|
373
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
374
|
+
variant: "subtitle2",
|
|
375
|
+
children: 'What signup forms will say'
|
|
376
|
+
}),
|
|
377
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
378
|
+
variant: "body2",
|
|
379
|
+
color: disclosure ? 'text.primary' : 'text.secondary',
|
|
380
|
+
"data-testid": "consent-group-disclosure",
|
|
381
|
+
children: disclosure != null ? disclosure : 'Choose a name and at least two sites to see it.'
|
|
382
|
+
})
|
|
383
|
+
]
|
|
384
|
+
}),
|
|
385
|
+
/*#__PURE__*/ _jsx(ConsentGroupSitePicker, {
|
|
386
|
+
sites: sites,
|
|
387
|
+
groups: snapshot.groups,
|
|
388
|
+
groupId: mode === 'create' ? null : groupId,
|
|
389
|
+
selected: hostIds,
|
|
390
|
+
onChange: (next)=>{
|
|
391
|
+
setHostIds(next);
|
|
392
|
+
setTouched(true);
|
|
393
|
+
setIssues((current)=>_extends({}, current, {
|
|
394
|
+
sites: undefined,
|
|
395
|
+
hostIds: new Set()
|
|
396
|
+
}));
|
|
397
|
+
},
|
|
398
|
+
error: sitesError,
|
|
399
|
+
refusedHostIds: issues.hostIds
|
|
400
|
+
}),
|
|
401
|
+
dissolving && change.kind === 'dissolve' && change.tooFewSites ? /*#__PURE__*/ _jsx(Alert, {
|
|
402
|
+
severity: "warning",
|
|
403
|
+
children: `A consent group needs at least two sites. ${change.unselected.length ? `Removing ${joinNames(change.unselected.map(siteName))} dissolves ${change.name}.` : `This dissolves ${change.name}.`} Review it to see what that changes.`
|
|
404
|
+
}) : null
|
|
405
|
+
]
|
|
406
|
+
}) : /*#__PURE__*/ _jsxs(_Fragment, {
|
|
407
|
+
children: [
|
|
408
|
+
busy === 'preview' ? /*#__PURE__*/ _jsxs(Stack, {
|
|
409
|
+
direction: "row",
|
|
410
|
+
spacing: 1,
|
|
411
|
+
sx: {
|
|
412
|
+
alignItems: 'center'
|
|
413
|
+
},
|
|
414
|
+
children: [
|
|
415
|
+
/*#__PURE__*/ _jsx(CircularProgress, {
|
|
416
|
+
size: 16
|
|
417
|
+
}),
|
|
418
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
419
|
+
variant: "body2",
|
|
420
|
+
color: "text.secondary",
|
|
421
|
+
children: 'Counting what this changes — nothing is written yet…'
|
|
422
|
+
})
|
|
423
|
+
]
|
|
424
|
+
}) : null,
|
|
425
|
+
review && preview ? review.sections.map((section)=>/*#__PURE__*/ _jsxs(Box, {
|
|
426
|
+
component: "section",
|
|
427
|
+
children: [
|
|
428
|
+
section.heading ? /*#__PURE__*/ _jsx(Typography, {
|
|
429
|
+
variant: "subtitle2",
|
|
430
|
+
sx: {
|
|
431
|
+
mb: 0.5
|
|
432
|
+
},
|
|
433
|
+
children: section.heading
|
|
434
|
+
}) : null,
|
|
435
|
+
/*#__PURE__*/ _jsx(Stack, {
|
|
436
|
+
spacing: 0.75,
|
|
437
|
+
children: section.items.map((item)=>item.tone === 'warning' ? /*#__PURE__*/ _jsx(Alert, {
|
|
438
|
+
severity: "warning",
|
|
439
|
+
sx: {
|
|
440
|
+
py: 0
|
|
441
|
+
},
|
|
442
|
+
children: item.text
|
|
443
|
+
}, item.id) : /*#__PURE__*/ _jsx(Typography, {
|
|
444
|
+
variant: "body2",
|
|
445
|
+
children: item.text
|
|
446
|
+
}, item.id))
|
|
447
|
+
})
|
|
448
|
+
]
|
|
449
|
+
}, section.id)) : null
|
|
450
|
+
]
|
|
451
|
+
})
|
|
452
|
+
]
|
|
453
|
+
})
|
|
454
|
+
}),
|
|
455
|
+
/*#__PURE__*/ _jsxs(DialogActions, {
|
|
456
|
+
children: [
|
|
457
|
+
step === 'review' && mode !== 'dissolve' ? /*#__PURE__*/ _jsx(Button, {
|
|
458
|
+
onClick: ()=>{
|
|
459
|
+
setStep('edit');
|
|
460
|
+
setAlert(null);
|
|
461
|
+
},
|
|
462
|
+
disabled: Boolean(busy),
|
|
463
|
+
children: 'Back'
|
|
464
|
+
}) : /*#__PURE__*/ _jsx(Button, {
|
|
465
|
+
onClick: close,
|
|
466
|
+
disabled: busy === 'apply',
|
|
467
|
+
children: 'Cancel'
|
|
468
|
+
}),
|
|
469
|
+
step === 'edit' ? /*#__PURE__*/ _jsx(Button, {
|
|
470
|
+
variant: "contained",
|
|
471
|
+
onClick: ()=>{
|
|
472
|
+
setTouched(true);
|
|
473
|
+
if (canReview) void requestPreview();
|
|
474
|
+
},
|
|
475
|
+
disabled: !canReview,
|
|
476
|
+
children: dissolving ? 'Review dissolving' : 'Review'
|
|
477
|
+
}) : /*#__PURE__*/ _jsx(Button, {
|
|
478
|
+
variant: "contained",
|
|
479
|
+
color: dissolving ? 'error' : 'primary',
|
|
480
|
+
onClick: ()=>void apply(),
|
|
481
|
+
disabled: !preview || Boolean(busy) || !groupExists,
|
|
482
|
+
startIcon: busy === 'apply' ? /*#__PURE__*/ _jsx(CircularProgress, {
|
|
483
|
+
size: 16
|
|
484
|
+
}) : undefined,
|
|
485
|
+
children: (_ref1 = review == null ? void 0 : review.confirmLabel) != null ? _ref1 : 'Apply'
|
|
486
|
+
})
|
|
487
|
+
]
|
|
488
|
+
})
|
|
489
|
+
]
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
ConsentGroupDialog.displayName = 'ConsentGroupDialog';
|
|
493
|
+
export default ConsentGroupDialog;
|
|
494
|
+
|
|
495
|
+
//# sourceMappingURL=consent-group-dialog.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/consent-group-dialog.tsx"],"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'use client'\n\nimport type { ConsolePluginOrgHost } from '@aglyn/aglyn'\nimport { consentGroupsAwaitConfirmation } from '@aglyn/aglyn/app-utils/consent-groups'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Box,\n Button,\n CircularProgress,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n buildConsentGroupDeclaration,\n describeConsentGroupDraft,\n draftDisclosure,\n expectedConsentGroups,\n listConsentGroups,\n validateConsentGroupDraft,\n type ConsentGroupDraft,\n type ListedConsentGroup,\n} from './consent-group-editing'\nimport { describeConsentGroupReview, joinNames } from './consent-group-review'\nimport ConsentGroupSitePicker from './consent-group-site-picker'\nimport {\n CONSENT_GROUP_NAME_MAX,\n consentGroupIssueMessage,\n postConsentGroups,\n type ConsentGroupChangePreview,\n type ConsentGroupsFailure,\n type ConsentGroupsPreviewResponse,\n type ConsentGroupsProgressResponse,\n type ConsentGroupValidationIssue,\n} from './consent-groups-api'\n\nexport type ConsentGroupDialogMode = 'create' | 'edit' | 'dissolve'\n\nexport interface ConsentGroupDialogProps {\n open: boolean\n mode: ConsentGroupDialogMode\n /** The group to edit or dissolve; ignored when creating. */\n groupId: string | null\n /** The org document, kept live by the shell. */\n org: Record<string, unknown> | null | undefined\n orgId: string\n /** Every site the organization has. */\n sites: readonly ConsolePluginOrgHost[]\n onClose: () => void\n /**\n * The route accepted the change. `done` when it already finished — a\n * rename moves no data, so it can complete inside the request.\n */\n onApplied: (result: { changeId: string; done: boolean }) => void\n}\n\n/** The declaration the dialog was opened against, held until it is applied. */\ninterface Snapshot {\n expected: Record<string, unknown> | null\n groups: ListedConsentGroup[]\n}\n\n/** The route's refusals, sorted onto the fields they belong to. */\ninterface Issues {\n name?: string\n sites?: string\n hostIds: Set<string>\n general: string[]\n}\n\nconst NO_ISSUES: Issues = { hostIds: new Set(), general: [] }\n\nconst NAME_CODES = new Set(['name-empty', 'name-too-long', 'name-duplicate'])\nconst SITE_CODES = new Set([\n 'too-few-sites',\n 'too-many-sites',\n 'group-replaced',\n 'unknown-site',\n 'site-in-two-groups',\n])\n\n/**\n * Declare, change or dissolve one consent group (AGL-3320).\n *\n * ## Two steps, because the second one is the consequence\n *\n * The EDIT step is the declaration: a name, which every signup form on the\n * group's sites will show, and the sites. The REVIEW step is what that\n * declaration does to people — who stops getting mail, who can still be\n * mailed, what happens to the CRM's records — counted by the route before\n * anything is written. Nothing changes until the review's button is pressed,\n * and that button is named for what it does.\n *\n * ## The whole declaration is sent, against the one that was read\n *\n * The route takes the org's complete next declaration and the raw one the\n * dialog was opened against, so a change somebody else made meanwhile is\n * refused rather than overwritten. The dialog snapshots that declaration\n * when it opens and edits against the snapshot; when the route says it is\n * stale, the dialog reloads the current one and asks again.\n *\n * ## Dissolving is where a group with too few sites ends up\n *\n * A group of one site is not a group, so unticking all but one site of an\n * existing group does not produce an invalid form — it becomes the dissolve,\n * and the dialog says so before anything is reviewed.\n */\nexport function ConsentGroupDialog(props: ConsentGroupDialogProps) {\n const { open, mode, groupId, org, orgId, sites, onClose, onApplied } = props\n const { data: user } = useUser()\n\n const [snapshot, setSnapshot] = useState<Snapshot>({ expected: null, groups: [] })\n const [name, setName] = useState('')\n const [hostIds, setHostIds] = useState<string[]>([])\n const [step, setStep] = useState<'edit' | 'review'>('edit')\n const [preview, setPreview] = useState<ConsentGroupChangePreview | null>(null)\n const [busy, setBusy] = useState<'preview' | 'apply' | null>(null)\n const [alert, setAlert] = useState<{\n severity: 'error' | 'warning' | 'info'\n message: string\n } | null>(null)\n const [issues, setIssues] = useState<Issues>(NO_ISSUES)\n const [touched, setTouched] = useState(false)\n /**\n * Set when a review should be asked for as soon as the snapshot it depends\n * on has rendered: a dissolve on open, and a dissolve reloaded after a\n * stale answer. State rather than a call, because the call made in the\n * same pass as the load would post the declaration from before it.\n */\n const [autoPreview, setAutoPreview] = useState(false)\n /** Bumped on every open, so an answer to a closed dialog is dropped. */\n const session = useRef(0)\n\n const orgRef = useRef(org)\n orgRef.current = org\n\n /**\n * Starts (or restarts, after a stale answer) from a declaration.\n *\n * A new group's draft survives a restart — the name and the sites mean the\n * same against the declaration as it now stands, and any site somebody\n * else grouped meanwhile shows it on its badge. An edit's draft does not:\n * it was made to a group that has since changed.\n */\n const load = useCallback(\n (raw: Record<string, unknown> | null, keepDraft = false) => {\n // The org as it stands with `raw` as its declaration, so the listing\n // refuses exactly what the route's reader refuses — a group id that\n // is also one of the org's sites among them.\n const groups = listConsentGroups({ ...(orgRef.current ?? {}), consentGroups: raw })\n setSnapshot({ expected: raw, groups })\n setPreview(null)\n setIssues(NO_ISSUES)\n if (keepDraft) return groups\n const group = groupId ? groups.find((entry) => entry.id === groupId) : null\n setName(mode === 'create' ? '' : (group?.name ?? ''))\n setHostIds(mode === 'create' ? [] : [...(group?.hostIds ?? [])])\n setTouched(false)\n return groups\n },\n [groupId, mode],\n )\n\n const draft: ConsentGroupDraft = useMemo(\n () => ({ mode, groupId: mode === 'create' ? null : groupId, name, hostIds }),\n [mode, groupId, name, hostIds],\n )\n const change = useMemo(\n () => describeConsentGroupDraft(snapshot.groups, draft),\n [snapshot.groups, draft],\n )\n const localErrors = useMemo(\n () => validateConsentGroupDraft(snapshot.groups, draft),\n [snapshot.groups, draft],\n )\n const groupExists =\n mode === 'create' || snapshot.groups.some((group) => group.id === groupId)\n\n const siteName = useCallback(\n (hostId: string) => {\n const site = sites.find((entry) => entry.id === hostId)\n return site?.name || site?.subdomain || hostId\n },\n [sites],\n )\n\n /** Sorts a validation answer onto the name, the sites or the banner. */\n const placeIssues = useCallback(\n (errors: ConsentGroupValidationIssue[], editedIndex: number | null) => {\n const placed: Issues = { hostIds: new Set(), general: [] }\n const posted = buildConsentGroupDeclaration(snapshot.groups, draft).groups\n for (const issue of errors) {\n const code = issue.code\n const message = consentGroupIssueMessage(issue, siteName)\n // A dissolve has no fields on screen, so everything goes to the banner.\n const ours =\n editedIndex != null &&\n (issue.groupIndex == null || issue.groupIndex === editedIndex)\n if (issue.hostId) placed.hostIds.add(issue.hostId)\n if (ours && NAME_CODES.has(code)) {\n placed.name ??= message\n } else if (ours && SITE_CODES.has(code)) {\n placed.sites ??= message\n } else {\n const other =\n issue.groupIndex != null ? posted[issue.groupIndex]?.name : undefined\n placed.general.push(other ? `${other}: ${message}` : message)\n }\n }\n setIssues(placed)\n },\n [snapshot.groups, draft, siteName],\n )\n\n /** What every refused call does, whichever step made it. */\n const handleFailure = useCallback(\n (failure: ConsentGroupsFailure, editedIndex: number | null) => {\n if (failure.kind === 'invalid') {\n placeIssues(failure.errors, editedIndex)\n setAlert(\n failure.errors.length\n ? null\n : { severity: 'error', message: failure.message },\n )\n if (mode !== 'dissolve') setStep('edit')\n return\n }\n if (failure.kind === 'stale') {\n // Reload what is there now, and let the admin look again.\n const groups = load(\n failure.current ?? expectedConsentGroups(orgRef.current),\n mode === 'create',\n )\n const stillThere =\n mode === 'create' || groups.some((group) => group.id === groupId)\n setAlert({\n severity: 'warning',\n message: stillThere\n ? `${failure.message} This shows what they saved; check your change and review it again.`\n : `${failure.message} This consent group no longer exists.`,\n })\n setStep(mode === 'dissolve' ? 'review' : 'edit')\n if (mode === 'dissolve' && stillThere) setAutoPreview(true)\n return\n }\n setAlert({\n severity: failure.kind === 'in-flight' ? 'info' : 'error',\n message: failure.message,\n })\n },\n [placeIssues, load, mode, groupId],\n )\n\n const requestPreview = useCallback(async () => {\n if (!user) return\n const ticket = session.current\n const { groups, editedIndex } = buildConsentGroupDeclaration(\n snapshot.groups,\n draft,\n )\n setBusy('preview')\n setAlert(null)\n setIssues(NO_ISSUES)\n setPreview(null)\n setStep('review')\n const result = await postConsentGroups<ConsentGroupsPreviewResponse>(user, {\n orgId,\n action: 'preview',\n expected: snapshot.expected,\n groups,\n })\n if (ticket !== session.current) return\n setBusy(null)\n if (result.ok) {\n setPreview(result.body.preview ?? null)\n return\n }\n handleFailure(result.failure, editedIndex)\n }, [user, snapshot, draft, orgId, handleFailure])\n\n const apply = useCallback(async () => {\n if (!user || !preview) return\n const ticket = session.current\n const { groups, editedIndex } = buildConsentGroupDeclaration(\n snapshot.groups,\n draft,\n )\n setBusy('apply')\n setAlert(null)\n const result = await postConsentGroups<ConsentGroupsProgressResponse>(user, {\n orgId,\n action: 'apply',\n expected: snapshot.expected,\n groups,\n })\n if (ticket !== session.current) return\n setBusy(null)\n if (result.ok) {\n onApplied({ changeId: result.body.changeId, done: result.body.done === true })\n return\n }\n handleFailure(result.failure, editedIndex)\n }, [user, preview, snapshot, draft, orgId, onApplied, handleFailure])\n\n /*\n * Open: snapshot the declaration as it stands, and start from the group as\n * it is. A dissolve has nothing to edit, so it asks for its review at once.\n */\n useEffect(() => {\n if (!open) return\n session.current += 1\n setAlert(null)\n setBusy(null)\n setStep(mode === 'dissolve' ? 'review' : 'edit')\n load(expectedConsentGroups(orgRef.current))\n setAutoPreview(mode === 'dissolve')\n }, [open, mode, groupId, load])\n useEffect(() => {\n if (!open || !autoPreview) return\n setAutoPreview(false)\n void requestPreview()\n }, [open, autoPreview, requestPreview])\n\n const review = useMemo(\n () =>\n step === 'review'\n ? describeConsentGroupReview({\n change,\n preview,\n siteName,\n awaitsConfirmation: consentGroupsAwaitConfirmation(org),\n })\n : null,\n [step, change, preview, siteName, org],\n )\n\n const nameError = issues.name ?? (touched ? localErrors.name : undefined)\n const sitesError =\n issues.sites ??\n (touched && change.kind !== 'dissolve' ? localErrors.sites : undefined)\n const disclosure = draftDisclosure(name, hostIds)\n const dissolving = change.kind === 'dissolve'\n const canReview =\n !busy &&\n groupExists &&\n !change.empty &&\n (dissolving || (!localErrors.name && !localErrors.sites))\n\n const close = () => {\n if (busy === 'apply') return\n session.current += 1\n onClose()\n }\n\n const title =\n step === 'review' && review\n ? review.title\n : mode === 'create'\n ? 'New consent group'\n : `Edit “${snapshot.groups.find((group) => group.id === groupId)?.name ?? name}”`\n\n return (\n <Dialog open={open} onClose={close} maxWidth=\"sm\" fullWidth>\n <DialogTitle>{title}</DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2}>\n {alert ? <Alert severity={alert.severity}>{alert.message}</Alert> : null}\n {issues.general.map((message) => (\n <Alert key={message} severity=\"error\">\n {message}\n </Alert>\n ))}\n {step === 'edit' ? (\n <>\n <TextField\n label=\"Name\"\n value={name}\n onChange={(event) => {\n setName(event.target.value)\n setIssues((current) => ({ ...current, name: undefined }))\n }}\n onBlur={() => setTouched(true)}\n error={Boolean(nameError)}\n helperText={\n nameError ??\n 'Signup forms on these sites show it, so use the name people know you by.'\n }\n slotProps={{ htmlInput: { maxLength: CONSENT_GROUP_NAME_MAX + 20 } }}\n autoFocus={mode === 'create'}\n required\n fullWidth\n />\n <Box>\n <Typography variant=\"subtitle2\">{'What signup forms will say'}</Typography>\n <Typography\n variant=\"body2\"\n color={disclosure ? 'text.primary' : 'text.secondary'}\n data-testid=\"consent-group-disclosure\"\n >\n {disclosure ?? 'Choose a name and at least two sites to see it.'}\n </Typography>\n </Box>\n <ConsentGroupSitePicker\n sites={sites}\n groups={snapshot.groups}\n groupId={mode === 'create' ? null : groupId}\n selected={hostIds}\n onChange={(next) => {\n setHostIds(next)\n setTouched(true)\n setIssues((current) => ({\n ...current,\n sites: undefined,\n hostIds: new Set(),\n }))\n }}\n error={sitesError}\n refusedHostIds={issues.hostIds}\n />\n {dissolving && change.kind === 'dissolve' && change.tooFewSites ? (\n <Alert severity=\"warning\">\n {`A consent group needs at least two sites. ${\n change.unselected.length\n ? `Removing ${joinNames(change.unselected.map(siteName))} dissolves ${change.name}.`\n : `This dissolves ${change.name}.`\n } Review it to see what that changes.`}\n </Alert>\n ) : null}\n </>\n ) : (\n <>\n {busy === 'preview' ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <CircularProgress size={16} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Counting what this changes — nothing is written yet…'}\n </Typography>\n </Stack>\n ) : null}\n {review && preview\n ? review.sections.map((section) => (\n <Box key={section.id} component=\"section\">\n {section.heading ? (\n <Typography variant=\"subtitle2\" sx={{ mb: 0.5 }}>\n {section.heading}\n </Typography>\n ) : null}\n <Stack spacing={0.75}>\n {section.items.map((item) =>\n item.tone === 'warning' ? (\n <Alert key={item.id} severity=\"warning\" sx={{ py: 0 }}>\n {item.text}\n </Alert>\n ) : (\n <Typography key={item.id} variant=\"body2\">\n {item.text}\n </Typography>\n ),\n )}\n </Stack>\n </Box>\n ))\n : null}\n </>\n )}\n </Stack>\n </DialogContent>\n <DialogActions>\n {step === 'review' && mode !== 'dissolve' ? (\n <Button\n onClick={() => {\n setStep('edit')\n setAlert(null)\n }}\n disabled={Boolean(busy)}\n >\n {'Back'}\n </Button>\n ) : (\n <Button onClick={close} disabled={busy === 'apply'}>\n {'Cancel'}\n </Button>\n )}\n {step === 'edit' ? (\n <Button\n variant=\"contained\"\n onClick={() => {\n setTouched(true)\n if (canReview) void requestPreview()\n }}\n disabled={!canReview}\n >\n {dissolving ? 'Review dissolving' : 'Review'}\n </Button>\n ) : (\n <Button\n variant=\"contained\"\n color={dissolving ? 'error' : 'primary'}\n onClick={() => void apply()}\n disabled={!preview || Boolean(busy) || !groupExists}\n startIcon={busy === 'apply' ? <CircularProgress size={16} /> : undefined}\n >\n {review?.confirmLabel ?? 'Apply'}\n </Button>\n )}\n </DialogActions>\n </Dialog>\n )\n}\nConsentGroupDialog.displayName = 'ConsentGroupDialog'\n\nexport default ConsentGroupDialog\n"],"names":["consentGroupsAwaitConfirmation","useUser","Alert","Box","Button","CircularProgress","Dialog","DialogActions","DialogContent","DialogTitle","Stack","TextField","Typography","useCallback","useEffect","useMemo","useRef","useState","buildConsentGroupDeclaration","describeConsentGroupDraft","draftDisclosure","expectedConsentGroups","listConsentGroups","validateConsentGroupDraft","describeConsentGroupReview","joinNames","ConsentGroupSitePicker","CONSENT_GROUP_NAME_MAX","consentGroupIssueMessage","postConsentGroups","NO_ISSUES","hostIds","Set","general","NAME_CODES","SITE_CODES","ConsentGroupDialog","props","issues","snapshot","open","mode","groupId","org","orgId","sites","onClose","onApplied","data","user","setSnapshot","expected","groups","name","setName","setHostIds","step","setStep","preview","setPreview","busy","setBusy","alert","setAlert","setIssues","touched","setTouched","autoPreview","setAutoPreview","session","orgRef","current","load","raw","keepDraft","consentGroups","group","find","entry","id","draft","change","localErrors","groupExists","some","siteName","hostId","site","subdomain","placeIssues","errors","editedIndex","placed","posted","issue","code","message","ours","groupIndex","add","has","other","undefined","push","handleFailure","failure","kind","length","severity","stillThere","requestPreview","ticket","result","action","ok","body","apply","changeId","done","review","awaitsConfirmation","nameError","sitesError","disclosure","dissolving","canReview","empty","close","title","maxWidth","fullWidth","dividers","spacing","map","label","value","onChange","event","target","onBlur","error","Boolean","helperText","slotProps","htmlInput","maxLength","autoFocus","required","variant","color","data-testid","selected","next","refusedHostIds","tooFewSites","unselected","direction","sx","alignItems","size","sections","section","component","heading","mb","items","item","tone","py","text","onClick","disabled","startIcon","confirmLabel","displayName"],"mappings":"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"}
|