@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.
Files changed (50) hide show
  1. package/package.json +17 -17
  2. package/src/lib/components/consent-group-access.d.ts +33 -0
  3. package/src/lib/components/consent-group-access.js +73 -0
  4. package/src/lib/components/consent-group-access.js.map +1 -0
  5. package/src/lib/components/consent-group-change-progress.d.ts +55 -0
  6. package/src/lib/components/consent-group-change-progress.js +282 -0
  7. package/src/lib/components/consent-group-change-progress.js.map +1 -0
  8. package/src/lib/components/consent-group-confirmation-card.d.ts +6 -16
  9. package/src/lib/components/consent-group-confirmation-card.js +7 -50
  10. package/src/lib/components/consent-group-confirmation-card.js.map +1 -1
  11. package/src/lib/components/consent-group-dialog.d.ts +53 -0
  12. package/src/lib/components/consent-group-dialog.js +495 -0
  13. package/src/lib/components/consent-group-dialog.js.map +1 -0
  14. package/src/lib/components/consent-group-editing.d.ts +127 -0
  15. package/src/lib/components/consent-group-editing.js +225 -0
  16. package/src/lib/components/consent-group-editing.js.map +1 -0
  17. package/src/lib/components/consent-group-review.d.ts +62 -0
  18. package/src/lib/components/consent-group-review.js +353 -0
  19. package/src/lib/components/consent-group-review.js.map +1 -0
  20. package/src/lib/components/consent-group-site-picker.d.ts +35 -0
  21. package/src/lib/components/consent-group-site-picker.js +190 -0
  22. package/src/lib/components/consent-group-site-picker.js.map +1 -0
  23. package/src/lib/components/consent-groups-api.d.ts +158 -0
  24. package/src/lib/components/consent-groups-api.js +250 -0
  25. package/src/lib/components/consent-groups-api.js.map +1 -0
  26. package/src/lib/components/consent-groups-card.d.ts +47 -0
  27. package/src/lib/components/consent-groups-card.js +351 -0
  28. package/src/lib/components/consent-groups-card.js.map +1 -0
  29. package/src/lib/components/email-screens-card.js +1 -1
  30. package/src/lib/components/email-screens-card.js.map +1 -1
  31. package/src/lib/components/emails-console-page.d.ts +1 -1
  32. package/src/lib/components/emails-console-page.js +62 -27
  33. package/src/lib/components/emails-console-page.js.map +1 -1
  34. package/src/lib/components/emails-console-sections.d.ts +1 -1
  35. package/src/lib/components/emails-console-sections.js +14 -1
  36. package/src/lib/components/emails-console-sections.js.map +1 -1
  37. package/src/lib/components/org-email-templates-card.js +1 -1
  38. package/src/lib/components/org-email-templates-card.js.map +1 -1
  39. package/src/lib/components/site-consent-group-card.d.ts +23 -0
  40. package/src/lib/components/site-consent-group-card.js +80 -0
  41. package/src/lib/components/site-consent-group-card.js.map +1 -0
  42. package/src/lib/components/suppressions-card.js +28 -7
  43. package/src/lib/components/suppressions-card.js.map +1 -1
  44. package/src/lib/server-console.js +4 -2
  45. package/src/lib/server-console.js.map +1 -1
  46. package/src/lib/server-list-gate.d.ts +9 -0
  47. package/src/lib/server-list-gate.js +23 -9
  48. package/src/lib/server-list-gate.js.map +1 -1
  49. package/src/lib/server-list-import.js +5 -2
  50. 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":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAGA,SAASA,8BAA8B,QAAQ,wCAAuC;AACtF,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SACEC,KAAK,EACLC,GAAG,EACHC,MAAM,EACNC,gBAAgB,EAChBC,MAAM,EACNC,aAAa,EACbC,aAAa,EACbC,WAAW,EACXC,KAAK,EACLC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACzE,SACEC,4BAA4B,EAC5BC,yBAAyB,EACzBC,eAAe,EACfC,qBAAqB,EACrBC,iBAAiB,EACjBC,yBAAyB,QAGpB,6BAAyB;AAChC,SAASC,0BAA0B,EAAEC,SAAS,QAAQ,4BAAwB;AAC9E,OAAOC,4BAA4B,iCAA6B;AAChE,SACEC,sBAAsB,EACtBC,wBAAwB,EACxBC,iBAAiB,QAMZ,0BAAsB;AAoC7B,MAAMC,YAAoB;IAAEC,SAAS,IAAIC;IAAOC,SAAS,EAAE;AAAC;AAE5D,MAAMC,aAAa,IAAIF,IAAI;IAAC;IAAc;IAAiB;CAAiB;AAC5E,MAAMG,aAAa,IAAIH,IAAI;IACzB;IACA;IACA;IACA;IACA;CACD;AAED;;;;;;;;;;;;;;;;;;;;;;;;;CAyBC,GACD,OAAO,SAASI,mBAAmBC,KAA8B;QAqO7CC,cAEhBA;QAqBeC;IA3PjB,MAAM,EAAEC,IAAI,EAAEC,IAAI,EAAEC,OAAO,EAAEC,GAAG,EAAEC,KAAK,EAAEC,KAAK,EAAEC,OAAO,EAAEC,SAAS,EAAE,GAAGV;IACvE,MAAM,EAAEW,MAAMC,IAAI,EAAE,GAAGhD;IAEvB,MAAM,CAACsC,UAAUW,YAAY,GAAGjC,SAAmB;QAAEkC,UAAU;QAAMC,QAAQ,EAAE;IAAC;IAChF,MAAM,CAACC,MAAMC,QAAQ,GAAGrC,SAAS;IACjC,MAAM,CAACc,SAASwB,WAAW,GAAGtC,SAAmB,EAAE;IACnD,MAAM,CAACuC,MAAMC,QAAQ,GAAGxC,SAA4B;IACpD,MAAM,CAACyC,SAASC,WAAW,GAAG1C,SAA2C;IACzE,MAAM,CAAC2C,MAAMC,QAAQ,GAAG5C,SAAqC;IAC7D,MAAM,CAAC6C,OAAOC,SAAS,GAAG9C,SAGhB;IACV,MAAM,CAACqB,QAAQ0B,UAAU,GAAG/C,SAAiBa;IAC7C,MAAM,CAACmC,SAASC,WAAW,GAAGjD,SAAS;IACvC;;;;;GAKC,GACD,MAAM,CAACkD,aAAaC,eAAe,GAAGnD,SAAS;IAC/C,sEAAsE,GACtE,MAAMoD,UAAUrD,OAAO;IAEvB,MAAMsD,SAAStD,OAAO2B;IACtB2B,OAAOC,OAAO,GAAG5B;IAEjB;;;;;;;GAOC,GACD,MAAM6B,OAAO3D,YACX,CAAC4D,KAAqCC,YAAY,KAAK;YAIdJ;QAHvC,qEAAqE;QACrE,oEAAoE;QACpE,6CAA6C;QAC7C,MAAMlB,SAAS9B,kBAAkB,cAAMgD,kBAAAA,OAAOC,OAAO,YAAdD,kBAAkB,CAAC;YAAIK,eAAeF;;QAC7EvB,YAAY;YAAEC,UAAUsB;YAAKrB;QAAO;QACpCO,WAAW;QACXK,UAAUlC;QACV,IAAI4C,WAAW,OAAOtB;QACtB,MAAMwB,QAAQlC,UAAUU,OAAOyB,IAAI,CAAC,CAACC,QAAUA,MAAMC,EAAE,KAAKrC,WAAW;QACvEY,QAAQb,SAAS,WAAW,aAAMmC,yBAAAA,MAAOvB,IAAI,mBAAI;QACjDE,WAAWd,SAAS,WAAW,EAAE,GAAG;wBAAKmC,yBAAAA,MAAO7C,OAAO,oBAAI,EAAE;SAAE;QAC/DmC,WAAW;QACX,OAAOd;IACT,GACA;QAACV;QAASD;KAAK;IAGjB,MAAMuC,QAA2BjE,QAC/B,IAAO,CAAA;YAAE0B;YAAMC,SAASD,SAAS,WAAW,OAAOC;YAASW;YAAMtB;QAAQ,CAAA,GAC1E;QAACU;QAAMC;QAASW;QAAMtB;KAAQ;IAEhC,MAAMkD,SAASlE,QACb,IAAMI,0BAA0BoB,SAASa,MAAM,EAAE4B,QACjD;QAACzC,SAASa,MAAM;QAAE4B;KAAM;IAE1B,MAAME,cAAcnE,QAClB,IAAMQ,0BAA0BgB,SAASa,MAAM,EAAE4B,QACjD;QAACzC,SAASa,MAAM;QAAE4B;KAAM;IAE1B,MAAMG,cACJ1C,SAAS,YAAYF,SAASa,MAAM,CAACgC,IAAI,CAAC,CAACR,QAAUA,MAAMG,EAAE,KAAKrC;IAEpE,MAAM2C,WAAWxE,YACf,CAACyE;QACC,MAAMC,OAAO1C,MAAMgC,IAAI,CAAC,CAACC,QAAUA,MAAMC,EAAE,KAAKO;QAChD,OAAOC,CAAAA,wBAAAA,KAAMlC,IAAI,MAAIkC,wBAAAA,KAAMC,SAAS,KAAIF;IAC1C,GACA;QAACzC;KAAM;IAGT,sEAAsE,GACtE,MAAM4C,cAAc5E,YAClB,CAAC6E,QAAuCC;QACtC,MAAMC,SAAiB;YAAE7D,SAAS,IAAIC;YAAOC,SAAS,EAAE;QAAC;QACzD,MAAM4D,SAAS3E,6BAA6BqB,SAASa,MAAM,EAAE4B,OAAO5B,MAAM;QAC1E,KAAK,MAAM0C,SAASJ,OAAQ;YAC1B,MAAMK,OAAOD,MAAMC,IAAI;YACvB,MAAMC,UAAUpE,yBAAyBkE,OAAOT;YAChD,wEAAwE;YACxE,MAAMY,OACJN,eAAe,QACdG,CAAAA,MAAMI,UAAU,IAAI,QAAQJ,MAAMI,UAAU,KAAKP,WAAU;YAC9D,IAAIG,MAAMR,MAAM,EAAEM,OAAO7D,OAAO,CAACoE,GAAG,CAACL,MAAMR,MAAM;YACjD,IAAIW,QAAQ/D,WAAWkE,GAAG,CAACL,OAAO;oBAChCH,SAAAA;iBAAAA,QAAAA,CAAAA,UAAAA,QAAOvC,IAAI,YAAXuC,QAAAA,QAAOvC,IAAI,GAAK2C;YAClB,OAAO,IAAIC,QAAQ9D,WAAWiE,GAAG,CAACL,OAAO;oBACvCH,UAAAA;iBAAAA,SAAAA,CAAAA,WAAAA,QAAO/C,KAAK,YAAZ+C,SAAAA,SAAO/C,KAAK,GAAKmD;YACnB,OAAO;oBAEwBH;gBAD7B,MAAMQ,QACJP,MAAMI,UAAU,IAAI,QAAOL,2BAAAA,MAAM,CAACC,MAAMI,UAAU,CAAC,qBAAxBL,yBAA0BxC,IAAI,GAAGiD;gBAC9DV,OAAO3D,OAAO,CAACsE,IAAI,CAACF,QAAQ,GAAGA,MAAM,EAAE,EAAEL,SAAS,GAAGA;YACvD;QACF;QACAhC,UAAU4B;IACZ,GACA;QAACrD,SAASa,MAAM;QAAE4B;QAAOK;KAAS;IAGpC,0DAA0D,GAC1D,MAAMmB,gBAAgB3F,YACpB,CAAC4F,SAA+Bd;QAC9B,IAAIc,QAAQC,IAAI,KAAK,WAAW;YAC9BjB,YAAYgB,QAAQf,MAAM,EAAEC;YAC5B5B,SACE0C,QAAQf,MAAM,CAACiB,MAAM,GACjB,OACA;gBAAEC,UAAU;gBAASZ,SAASS,QAAQT,OAAO;YAAC;YAEpD,IAAIvD,SAAS,YAAYgB,QAAQ;YACjC;QACF;QACA,IAAIgD,QAAQC,IAAI,KAAK,SAAS;gBAG1BD;YAFF,0DAA0D;YAC1D,MAAMrD,SAASoB,MACbiC,mBAAAA,QAAQlC,OAAO,YAAfkC,mBAAmBpF,sBAAsBiD,OAAOC,OAAO,GACvD9B,SAAS;YAEX,MAAMoE,aACJpE,SAAS,YAAYW,OAAOgC,IAAI,CAAC,CAACR,QAAUA,MAAMG,EAAE,KAAKrC;YAC3DqB,SAAS;gBACP6C,UAAU;gBACVZ,SAASa,aACL,GAAGJ,QAAQT,OAAO,CAAC,mEAAmE,CAAC,GACvF,GAAGS,QAAQT,OAAO,CAAC,qCAAqC,CAAC;YAC/D;YACAvC,QAAQhB,SAAS,aAAa,WAAW;YACzC,IAAIA,SAAS,cAAcoE,YAAYzC,eAAe;YACtD;QACF;QACAL,SAAS;YACP6C,UAAUH,QAAQC,IAAI,KAAK,cAAc,SAAS;YAClDV,SAASS,QAAQT,OAAO;QAC1B;IACF,GACA;QAACP;QAAajB;QAAM/B;QAAMC;KAAQ;IAGpC,MAAMoE,iBAAiBjG,YAAY;QACjC,IAAI,CAACoC,MAAM;QACX,MAAM8D,SAAS1C,QAAQE,OAAO;QAC9B,MAAM,EAAEnB,MAAM,EAAEuC,WAAW,EAAE,GAAGzE,6BAC9BqB,SAASa,MAAM,EACf4B;QAEFnB,QAAQ;QACRE,SAAS;QACTC,UAAUlC;QACV6B,WAAW;QACXF,QAAQ;QACR,MAAMuD,SAAS,MAAMnF,kBAAgDoB,MAAM;YACzEL;YACAqE,QAAQ;YACR9D,UAAUZ,SAASY,QAAQ;YAC3BC;QACF;QACA,IAAI2D,WAAW1C,QAAQE,OAAO,EAAE;QAChCV,QAAQ;QACR,IAAImD,OAAOE,EAAE,EAAE;gBACFF;YAAXrD,YAAWqD,uBAAAA,OAAOG,IAAI,CAACzD,OAAO,YAAnBsD,uBAAuB;YAClC;QACF;QACAR,cAAcQ,OAAOP,OAAO,EAAEd;IAChC,GAAG;QAAC1C;QAAMV;QAAUyC;QAAOpC;QAAO4D;KAAc;IAEhD,MAAMY,QAAQvG,YAAY;QACxB,IAAI,CAACoC,QAAQ,CAACS,SAAS;QACvB,MAAMqD,SAAS1C,QAAQE,OAAO;QAC9B,MAAM,EAAEnB,MAAM,EAAEuC,WAAW,EAAE,GAAGzE,6BAC9BqB,SAASa,MAAM,EACf4B;QAEFnB,QAAQ;QACRE,SAAS;QACT,MAAMiD,SAAS,MAAMnF,kBAAiDoB,MAAM;YAC1EL;YACAqE,QAAQ;YACR9D,UAAUZ,SAASY,QAAQ;YAC3BC;QACF;QACA,IAAI2D,WAAW1C,QAAQE,OAAO,EAAE;QAChCV,QAAQ;QACR,IAAImD,OAAOE,EAAE,EAAE;YACbnE,UAAU;gBAAEsE,UAAUL,OAAOG,IAAI,CAACE,QAAQ;gBAAEC,MAAMN,OAAOG,IAAI,CAACG,IAAI,KAAK;YAAK;YAC5E;QACF;QACAd,cAAcQ,OAAOP,OAAO,EAAEd;IAChC,GAAG;QAAC1C;QAAMS;QAASnB;QAAUyC;QAAOpC;QAAOG;QAAWyD;KAAc;IAEpE;;;GAGC,GACD1F,UAAU;QACR,IAAI,CAAC0B,MAAM;QACX6B,QAAQE,OAAO,IAAI;QACnBR,SAAS;QACTF,QAAQ;QACRJ,QAAQhB,SAAS,aAAa,WAAW;QACzC+B,KAAKnD,sBAAsBiD,OAAOC,OAAO;QACzCH,eAAe3B,SAAS;IAC1B,GAAG;QAACD;QAAMC;QAAMC;QAAS8B;KAAK;IAC9B1D,UAAU;QACR,IAAI,CAAC0B,QAAQ,CAAC2B,aAAa;QAC3BC,eAAe;QACf,KAAK0C;IACP,GAAG;QAACtE;QAAM2B;QAAa2C;KAAe;IAEtC,MAAMS,SAASxG,QACb,IACEyC,SAAS,WACLhC,2BAA2B;YACzByD;YACAvB;YACA2B;YACAmC,oBAAoBxH,+BAA+B2C;QACrD,KACA,MACN;QAACa;QAAMyB;QAAQvB;QAAS2B;QAAU1C;KAAI;IAGxC,MAAM8E,aAAYnF,eAAAA,OAAOe,IAAI,YAAXf,eAAgB2B,UAAUiB,YAAY7B,IAAI,GAAGiD;IAC/D,MAAMoB,cACJpF,gBAAAA,OAAOO,KAAK,YAAZP,gBACC2B,WAAWgB,OAAOyB,IAAI,KAAK,aAAaxB,YAAYrC,KAAK,GAAGyD;IAC/D,MAAMqB,aAAavG,gBAAgBiC,MAAMtB;IACzC,MAAM6F,aAAa3C,OAAOyB,IAAI,KAAK;IACnC,MAAMmB,YACJ,CAACjE,QACDuB,eACA,CAACF,OAAO6C,KAAK,IACZF,CAAAA,cAAe,CAAC1C,YAAY7B,IAAI,IAAI,CAAC6B,YAAYrC,KAAK;IAEzD,MAAMkF,QAAQ;QACZ,IAAInE,SAAS,SAAS;QACtBS,QAAQE,OAAO,IAAI;QACnBzB;IACF;IAEA,MAAMkF,QACJxE,SAAS,YAAY+D,SACjBA,OAAOS,KAAK,GACZvF,SAAS,WACP,sBACA,CAAC,MAAM,WAAEF,wBAAAA,SAASa,MAAM,CAACyB,IAAI,CAAC,CAACD,QAAUA,MAAMG,EAAE,KAAKrC,6BAA7CH,sBAAuDc,IAAI,mBAAIA,KAAK,CAAC,CAAC;IAEvF,qBACE,MAAC/C;QAAOkC,MAAMA;QAAMM,SAASiF;QAAOE,UAAS;QAAKC,SAAS;;0BACzD,KAACzH;0BAAauH;;0BACd,KAACxH;gBAAc2H,QAAQ;0BACrB,cAAA,MAACzH;oBAAM0H,SAAS;;wBACbtE,sBAAQ,KAAC5D;4BAAM0G,UAAU9C,MAAM8C,QAAQ;sCAAG9C,MAAMkC,OAAO;6BAAY;wBACnE1D,OAAOL,OAAO,CAACoG,GAAG,CAAC,CAACrC,wBACnB,KAAC9F;gCAAoB0G,UAAS;0CAC3BZ;+BADSA;wBAIbxC,SAAS,uBACR;;8CACE,KAAC7C;oCACC2H,OAAM;oCACNC,OAAOlF;oCACPmF,UAAU,CAACC;wCACTnF,QAAQmF,MAAMC,MAAM,CAACH,KAAK;wCAC1BvE,UAAU,CAACO,UAAa,aAAKA;gDAASlB,MAAMiD;;oCAC9C;oCACAqC,QAAQ,IAAMzE,WAAW;oCACzB0E,OAAOC,QAAQpB;oCACfqB,UAAU,EACRrB,oBAAAA,YACA;oCAEFsB,WAAW;wCAAEC,WAAW;4CAAEC,WAAWtH,yBAAyB;wCAAG;oCAAE;oCACnEuH,WAAWzG,SAAS;oCACpB0G,QAAQ;oCACRjB,SAAS;;8CAEX,MAAC/H;;sDACC,KAACS;4CAAWwI,SAAQ;sDAAa;;sDACjC,KAACxI;4CACCwI,SAAQ;4CACRC,OAAO1B,aAAa,iBAAiB;4CACrC2B,eAAY;sDAEX3B,qBAAAA,aAAc;;;;8CAGnB,KAACjG;oCACCmB,OAAOA;oCACPO,QAAQb,SAASa,MAAM;oCACvBV,SAASD,SAAS,WAAW,OAAOC;oCACpC6G,UAAUxH;oCACVyG,UAAU,CAACgB;wCACTjG,WAAWiG;wCACXtF,WAAW;wCACXF,UAAU,CAACO,UAAa,aACnBA;gDACH1B,OAAOyD;gDACPvE,SAAS,IAAIC;;oCAEjB;oCACA4G,OAAOlB;oCACP+B,gBAAgBnH,OAAOP,OAAO;;gCAE/B6F,cAAc3C,OAAOyB,IAAI,KAAK,cAAczB,OAAOyE,WAAW,iBAC7D,KAACxJ;oCAAM0G,UAAS;8CACb,CAAC,0CAA0C,EAC1C3B,OAAO0E,UAAU,CAAChD,MAAM,GACpB,CAAC,SAAS,EAAElF,UAAUwD,OAAO0E,UAAU,CAACtB,GAAG,CAAChD,WAAW,WAAW,EAAEJ,OAAO5B,IAAI,CAAC,CAAC,CAAC,GAClF,CAAC,eAAe,EAAE4B,OAAO5B,IAAI,CAAC,CAAC,CAAC,CACrC,oCAAoC,CAAC;qCAEtC;;2CAGN;;gCACGO,SAAS,0BACR,MAAClD;oCAAMkJ,WAAU;oCAAMxB,SAAS;oCAAGyB,IAAI;wCAAEC,YAAY;oCAAS;;sDAC5D,KAACzJ;4CAAiB0J,MAAM;;sDACxB,KAACnJ;4CAAWwI,SAAQ;4CAAQC,OAAM;sDAC/B;;;qCAGH;gCACH9B,UAAU7D,UACP6D,OAAOyC,QAAQ,CAAC3B,GAAG,CAAC,CAAC4B,wBACnB,MAAC9J;wCAAqB+J,WAAU;;4CAC7BD,QAAQE,OAAO,iBACd,KAACvJ;gDAAWwI,SAAQ;gDAAYS,IAAI;oDAAEO,IAAI;gDAAI;0DAC3CH,QAAQE,OAAO;iDAEhB;0DACJ,KAACzJ;gDAAM0H,SAAS;0DACb6B,QAAQI,KAAK,CAAChC,GAAG,CAAC,CAACiC,OAClBA,KAAKC,IAAI,KAAK,0BACZ,KAACrK;wDAAoB0G,UAAS;wDAAUiD,IAAI;4DAAEW,IAAI;wDAAE;kEACjDF,KAAKG,IAAI;uDADAH,KAAKvF,EAAE,kBAInB,KAACnE;wDAAyBwI,SAAQ;kEAC/BkB,KAAKG,IAAI;uDADKH,KAAKvF,EAAE;;;uCAbtBkF,QAAQlF,EAAE,KAqBtB;;;;;;0BAKZ,MAACxE;;oBACEiD,SAAS,YAAYf,SAAS,2BAC7B,KAACrC;wBACCsK,SAAS;4BACPjH,QAAQ;4BACRM,SAAS;wBACX;wBACA4G,UAAU9B,QAAQjF;kCAEjB;uCAGH,KAACxD;wBAAOsK,SAAS3C;wBAAO4C,UAAU/G,SAAS;kCACxC;;oBAGJJ,SAAS,uBACR,KAACpD;wBACCgJ,SAAQ;wBACRsB,SAAS;4BACPxG,WAAW;4BACX,IAAI2D,WAAW,KAAKf;wBACtB;wBACA6D,UAAU,CAAC9C;kCAEVD,aAAa,sBAAsB;uCAGtC,KAACxH;wBACCgJ,SAAQ;wBACRC,OAAOzB,aAAa,UAAU;wBAC9B8C,SAAS,IAAM,KAAKtD;wBACpBuD,UAAU,CAACjH,WAAWmF,QAAQjF,SAAS,CAACuB;wBACxCyF,WAAWhH,SAAS,wBAAU,KAACvD;4BAAiB0J,MAAM;6BAASzD;2CAE9DiB,0BAAAA,OAAQsD,YAAY,oBAAI;;;;;;AAMrC;AACAzI,mBAAmB0I,WAAW,GAAG;AAEjC,eAAe1I,mBAAkB"}