@aglyn/plugins-outreach 1.0.0-beta.219 → 1.0.0-beta.220
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 +15 -15
- package/src/lib/components/compliance-section.d.ts +5 -0
- package/src/lib/components/compliance-section.js +233 -149
- package/src/lib/components/compliance-section.js.map +1 -1
- package/src/lib/components/enrollment-detail.js +2 -2
- package/src/lib/components/enrollment-detail.js.map +1 -1
- package/src/lib/components/sequence-editor.d.ts +13 -0
- package/src/lib/components/sequence-editor.js +508 -302
- package/src/lib/components/sequence-editor.js.map +1 -1
- package/src/lib/components/sequence-preview.js +25 -10
- package/src/lib/components/sequence-preview.js.map +1 -1
- package/src/lib/components/sequence-step-card.d.ts +10 -0
- package/src/lib/components/sequence-step-card.js +320 -202
- package/src/lib/components/sequence-step-card.js.map +1 -1
- package/src/lib/engine/compose.js +2 -2
- package/src/lib/engine/compose.js.map +1 -1
- package/src/lib/engine/delivery-status.js +1 -1
- package/src/lib/engine/delivery-status.js.map +1 -1
- package/src/lib/engine/enrollment-state.js +2 -2
- package/src/lib/engine/enrollment-state.js.map +1 -1
- package/src/lib/engine/gates.d.ts +2 -2
- package/src/lib/engine/gates.js.map +1 -1
- package/src/lib/engine/opt-out-intent.js +1 -1
- package/src/lib/engine/opt-out-intent.js.map +1 -1
- package/src/lib/engine/sequence-validation.js +2 -2
- package/src/lib/engine/sequence-validation.js.map +1 -1
- package/src/lib/engine/thread-classification.js +3 -3
- package/src/lib/engine/thread-classification.js.map +1 -1
- package/src/lib/enrollment/gate-lookups.js +5 -4
- package/src/lib/enrollment/gate-lookups.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +1 -1
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/model/sales-topic.d.ts +27 -0
- package/src/lib/model/sales-topic.js +27 -0
- package/src/lib/model/sales-topic.js.map +1 -0
- package/src/lib/model/sequence-draft.d.ts +1 -1
- package/src/lib/model/sequence-draft.js +2 -2
- package/src/lib/model/sequence-draft.js.map +1 -1
- package/src/lib/model/stored-records.js +2 -2
- package/src/lib/model/stored-records.js.map +1 -1
- package/src/lib/routes/curate-routes.js +7 -7
- package/src/lib/routes/curate-routes.js.map +1 -1
- package/src/lib/routes/enroll-routes.js +10 -13
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/sequence-routes.js +9 -9
- package/src/lib/routes/sequence-routes.js.map +1 -1
- package/src/lib/runtime/campaign-credit.js +2 -2
- package/src/lib/runtime/campaign-credit.js.map +1 -1
- package/src/lib/runtime/campaign-detach.js +3 -2
- package/src/lib/runtime/campaign-detach.js.map +1 -1
- package/src/lib/runtime/lead-records.d.ts +6 -1
- package/src/lib/runtime/lead-records.js +25 -6
- package/src/lib/runtime/lead-records.js.map +1 -1
- package/src/lib/runtime/platform-runtime-deps.js +3 -3
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
- package/src/lib/runtime/send-job.js +20 -4
- package/src/lib/runtime/send-job.js.map +1 -1
- package/src/lib/runtime/sync-job.js +3 -3
- package/src/lib/runtime/sync-job.js.map +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aglyn/plugins-outreach",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.220",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"homepage": "https://aglyn.com",
|
|
6
6
|
"repository": {
|
|
@@ -25,26 +25,26 @@
|
|
|
25
25
|
"./package.json": "./package.json"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@aglyn/aglyn": "1.0.0-beta.
|
|
29
|
-
"@aglyn/shared-data-mdi": "1.0.0-beta.
|
|
30
|
-
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.
|
|
31
|
-
"@aglyn/shared-ui-jsx": "1.0.0-beta.
|
|
32
|
-
"@aglyn/shared-ui-next": "1.0.0-beta.
|
|
33
|
-
"@aglyn/shared-ui-snackstack": "1.0.0-beta.
|
|
34
|
-
"@aglyn/shared-util-email": "1.0.0-beta.
|
|
35
|
-
"@aglyn/shared-util-http": "1.0.0-beta.
|
|
36
|
-
"@aglyn/shared-util-timestamp": "1.0.0-beta.
|
|
37
|
-
"@aglyn/shared-util-tools": "1.0.0-beta.
|
|
38
|
-
"@aglyn/tenant-data-admin": "1.0.0-beta.
|
|
39
|
-
"@aglyn/tenant-feature-instance": "1.0.0-beta.
|
|
40
|
-
"@aglyn/tenant-runtime": "1.0.0-beta.
|
|
28
|
+
"@aglyn/aglyn": "1.0.0-beta.220",
|
|
29
|
+
"@aglyn/shared-data-mdi": "1.0.0-beta.220",
|
|
30
|
+
"@aglyn/shared-ui-email-campaigns": "1.0.0-beta.220",
|
|
31
|
+
"@aglyn/shared-ui-jsx": "1.0.0-beta.220",
|
|
32
|
+
"@aglyn/shared-ui-next": "1.0.0-beta.220",
|
|
33
|
+
"@aglyn/shared-ui-snackstack": "1.0.0-beta.220",
|
|
34
|
+
"@aglyn/shared-util-email": "1.0.0-beta.220",
|
|
35
|
+
"@aglyn/shared-util-http": "1.0.0-beta.220",
|
|
36
|
+
"@aglyn/shared-util-timestamp": "1.0.0-beta.220",
|
|
37
|
+
"@aglyn/shared-util-tools": "1.0.0-beta.220",
|
|
38
|
+
"@aglyn/tenant-data-admin": "1.0.0-beta.220",
|
|
39
|
+
"@aglyn/tenant-feature-instance": "1.0.0-beta.220",
|
|
40
|
+
"@aglyn/tenant-runtime": "1.0.0-beta.220",
|
|
41
41
|
"@swc/helpers": "0.5.23"
|
|
42
42
|
},
|
|
43
43
|
"peerDependencies": {
|
|
44
44
|
"@mui/material": "^9.3.1",
|
|
45
45
|
"firebase": "^12.18.0",
|
|
46
46
|
"firebase-admin": "^14.3.0",
|
|
47
|
-
"next": "16.3.3",
|
|
47
|
+
"next": "^16.3.3",
|
|
48
48
|
"react": "^19.2.8"
|
|
49
49
|
},
|
|
50
50
|
"sideEffects": false,
|
|
@@ -15,6 +15,11 @@ export declare const OUTREACH_POSTAL_ADDRESS_HELP: string;
|
|
|
15
15
|
* `composeOutreachFooter`, so the page shows the lines every email will end
|
|
16
16
|
* with — or, while the legal name or the address is empty, the sentence
|
|
17
17
|
* that explains why nothing can be sent and no sequence activated.
|
|
18
|
+
*
|
|
19
|
+
* Both settings cards are drawn from the one form, and each is memoized and
|
|
20
|
+
* handed only what it shows, through handlers that keep their identity
|
|
21
|
+
* (AGL-3423): a letter typed into the sender identity redraws its field and
|
|
22
|
+
* the footer, not the other fields, the countries or the domain list.
|
|
18
23
|
*/
|
|
19
24
|
export declare function OutreachComplianceSection(props: OutreachComplianceSectionProps): import("react").JSX.Element;
|
|
20
25
|
export declare namespace OutreachComplianceSection {
|
|
@@ -109,6 +109,176 @@ const sameCountry = (option, value)=>option.code === value.code;
|
|
|
109
109
|
}))
|
|
110
110
|
});
|
|
111
111
|
});
|
|
112
|
+
/** A text field that redraws only when what it shows changes (AGL-3423). */ const MemoTextField = /*#__PURE__*/ memo(TextField);
|
|
113
|
+
/** Its own list and listener: nothing typed in the cards above it reaches it. */ const MemoDoNotContactDomainsCard = /*#__PURE__*/ memo(OutreachDoNotContactDomainsCard);
|
|
114
|
+
const LEGAL_NAME_SLOT_PROPS = {
|
|
115
|
+
htmlInput: {
|
|
116
|
+
maxLength: OUTREACH_LEGAL_NAME_MAX + 20
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
const BRAND_NAME_SLOT_PROPS = {
|
|
120
|
+
htmlInput: {
|
|
121
|
+
maxLength: OUTREACH_BRAND_NAME_MAX + 20
|
|
122
|
+
}
|
|
123
|
+
};
|
|
124
|
+
const POSTAL_ADDRESS_SLOT_PROPS = {
|
|
125
|
+
htmlInput: {
|
|
126
|
+
maxLength: OUTREACH_POSTAL_ADDRESS_MAX + 40
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const FOOTER_PAPER = {
|
|
130
|
+
p: 1.5
|
|
131
|
+
};
|
|
132
|
+
const FOOTER_TEXT = {
|
|
133
|
+
whiteSpace: 'pre-wrap',
|
|
134
|
+
wordBreak: 'break-word'
|
|
135
|
+
};
|
|
136
|
+
/** A settings card's header actions: discard its edits, or save its own fields. */ const ComplianceCardActions = /*#__PURE__*/ memo(function ComplianceCardActions(props) {
|
|
137
|
+
const { card, dirty, saving, onDiscard, onSave } = props;
|
|
138
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
139
|
+
direction: "row",
|
|
140
|
+
spacing: 1,
|
|
141
|
+
children: [
|
|
142
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
143
|
+
size: "small",
|
|
144
|
+
variant: "text",
|
|
145
|
+
disabled: !dirty || saving !== null,
|
|
146
|
+
onClick: ()=>onDiscard(card),
|
|
147
|
+
children: "Discard changes"
|
|
148
|
+
}),
|
|
149
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
150
|
+
size: "small",
|
|
151
|
+
variant: "contained",
|
|
152
|
+
disabled: !dirty || saving !== null || props.blocked,
|
|
153
|
+
onClick: ()=>onSave(card),
|
|
154
|
+
children: saving === card ? 'Saving…' : 'Save'
|
|
155
|
+
})
|
|
156
|
+
]
|
|
157
|
+
});
|
|
158
|
+
});
|
|
159
|
+
/**
|
|
160
|
+
* Who every email says sent it, and the footer that says it. Each field is
|
|
161
|
+
* memoized with a handler that keeps its identity, so a letter typed into one
|
|
162
|
+
* redraws that field and the footer it changes, not the other two.
|
|
163
|
+
*/ const ComplianceIdentityCard = /*#__PURE__*/ memo(function ComplianceIdentityCard(props) {
|
|
164
|
+
var _props_legalNameIssue, _props_brandNameIssue, _props_postalAddressIssue;
|
|
165
|
+
const { footer } = props;
|
|
166
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
167
|
+
header: "Sender identity",
|
|
168
|
+
help: pluginDocsHelp('sequences', {
|
|
169
|
+
anchor: '#compliance-settings'
|
|
170
|
+
}),
|
|
171
|
+
HeaderProps: {
|
|
172
|
+
action: /*#__PURE__*/ _jsx(ComplianceCardActions, {
|
|
173
|
+
card: "identity",
|
|
174
|
+
dirty: props.dirty,
|
|
175
|
+
blocked: props.blocked,
|
|
176
|
+
saving: props.saving,
|
|
177
|
+
onDiscard: props.onDiscard,
|
|
178
|
+
onSave: props.onSave
|
|
179
|
+
})
|
|
180
|
+
},
|
|
181
|
+
contentGutterX: true,
|
|
182
|
+
contentGutterY: true,
|
|
183
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
184
|
+
spacing: 2,
|
|
185
|
+
children: [
|
|
186
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
187
|
+
variant: "body2",
|
|
188
|
+
color: "text.secondary",
|
|
189
|
+
children: 'Every email a sequence sends ends with a footer naming your organization, its postal ' + 'address, that the email is a sales email, and how to stop more of them. The law ' + 'requires these on commercial email, so every email gets the footer automatically.'
|
|
190
|
+
}),
|
|
191
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
192
|
+
label: "Legal name",
|
|
193
|
+
value: props.legalName,
|
|
194
|
+
onChange: props.onLegalName,
|
|
195
|
+
error: Boolean(props.legalNameIssue),
|
|
196
|
+
helperText: (_props_legalNameIssue = props.legalNameIssue) != null ? _props_legalNameIssue : 'Your organization’s legal name, as the footer prints it.',
|
|
197
|
+
slotProps: LEGAL_NAME_SLOT_PROPS,
|
|
198
|
+
fullWidth: true
|
|
199
|
+
}),
|
|
200
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
201
|
+
label: "Brand name",
|
|
202
|
+
value: props.brandName,
|
|
203
|
+
onChange: props.onBrandName,
|
|
204
|
+
error: Boolean(props.brandNameIssue),
|
|
205
|
+
helperText: (_props_brandNameIssue = props.brandNameIssue) != null ? _props_brandNameIssue : 'The name the solicitation sentence uses. Leave empty to use the legal name.',
|
|
206
|
+
slotProps: BRAND_NAME_SLOT_PROPS,
|
|
207
|
+
fullWidth: true
|
|
208
|
+
}),
|
|
209
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
210
|
+
label: "Postal address",
|
|
211
|
+
value: props.postalAddress,
|
|
212
|
+
onChange: props.onPostalAddress,
|
|
213
|
+
error: Boolean(props.postalAddressIssue),
|
|
214
|
+
helperText: (_props_postalAddressIssue = props.postalAddressIssue) != null ? _props_postalAddressIssue : OUTREACH_POSTAL_ADDRESS_HELP,
|
|
215
|
+
slotProps: POSTAL_ADDRESS_SLOT_PROPS,
|
|
216
|
+
multiline: true,
|
|
217
|
+
minRows: 3,
|
|
218
|
+
fullWidth: true
|
|
219
|
+
}),
|
|
220
|
+
footer ? /*#__PURE__*/ _jsxs(Stack, {
|
|
221
|
+
spacing: 0.5,
|
|
222
|
+
children: [
|
|
223
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
224
|
+
variant: "subtitle2",
|
|
225
|
+
children: "Every email ends with"
|
|
226
|
+
}),
|
|
227
|
+
/*#__PURE__*/ _jsx(Paper, {
|
|
228
|
+
variant: "outlined",
|
|
229
|
+
sx: FOOTER_PAPER,
|
|
230
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
231
|
+
variant: "body2",
|
|
232
|
+
sx: FOOTER_TEXT,
|
|
233
|
+
"data-testid": "outreach-footer-preview",
|
|
234
|
+
children: footer
|
|
235
|
+
})
|
|
236
|
+
})
|
|
237
|
+
]
|
|
238
|
+
}) : /*#__PURE__*/ _jsx(Alert, {
|
|
239
|
+
severity: "warning",
|
|
240
|
+
children: 'No sequence can be activated, and no email sent, until you add your organization’s legal ' + 'name and postal address.'
|
|
241
|
+
})
|
|
242
|
+
]
|
|
243
|
+
})
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
/** The countries a sequence may send to at all. */ const ComplianceCountriesCard = /*#__PURE__*/ memo(function ComplianceCountriesCard(props) {
|
|
247
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
248
|
+
header: "Allowed countries",
|
|
249
|
+
help: pluginDocsHelp('sequences', {
|
|
250
|
+
anchor: '#allowed-countries'
|
|
251
|
+
}),
|
|
252
|
+
HeaderProps: {
|
|
253
|
+
action: /*#__PURE__*/ _jsx(ComplianceCardActions, {
|
|
254
|
+
card: "countries",
|
|
255
|
+
dirty: props.dirty,
|
|
256
|
+
blocked: props.blocked,
|
|
257
|
+
saving: props.saving,
|
|
258
|
+
onDiscard: props.onDiscard,
|
|
259
|
+
onSave: props.onSave
|
|
260
|
+
})
|
|
261
|
+
},
|
|
262
|
+
contentGutterX: true,
|
|
263
|
+
contentGutterY: true,
|
|
264
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
265
|
+
spacing: 2,
|
|
266
|
+
children: [
|
|
267
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
268
|
+
variant: "body2",
|
|
269
|
+
color: "text.secondary",
|
|
270
|
+
children: 'Sequences may send only to people in these countries, and every sequence only to the ones ' + 'it and this list both allow. The United States is the default: Canada, the United Kingdom and most ' + 'of the European Union require a consent basis that an email to someone who never contacted ' + 'you does not have, so a cold email never goes outside the United States whatever this list ' + 'says. Add another country only for people who came to you first.'
|
|
271
|
+
}),
|
|
272
|
+
/*#__PURE__*/ _jsx(ComplianceCountriesField, {
|
|
273
|
+
options: props.options,
|
|
274
|
+
value: props.value,
|
|
275
|
+
issue: props.issue,
|
|
276
|
+
onChange: props.onChange
|
|
277
|
+
})
|
|
278
|
+
]
|
|
279
|
+
})
|
|
280
|
+
});
|
|
281
|
+
});
|
|
112
282
|
/**
|
|
113
283
|
* Sequences → Compliance (AGL-2980): who every email says sent it, the
|
|
114
284
|
* countries a sequence may send to at all, and — below the settings, with
|
|
@@ -120,8 +290,12 @@ const sameCountry = (option, value)=>option.code === value.code;
|
|
|
120
290
|
* `composeOutreachFooter`, so the page shows the lines every email will end
|
|
121
291
|
* with — or, while the legal name or the address is empty, the sentence
|
|
122
292
|
* that explains why nothing can be sent and no sequence activated.
|
|
293
|
+
*
|
|
294
|
+
* Both settings cards are drawn from the one form, and each is memoized and
|
|
295
|
+
* handed only what it shows, through handlers that keep their identity
|
|
296
|
+
* (AGL-3423): a letter typed into the sender identity redraws its field and
|
|
297
|
+
* the footer, not the other fields, the countries or the domain list.
|
|
123
298
|
*/ export function OutreachComplianceSection(props) {
|
|
124
|
-
var _fieldIssue, _fieldIssue1, _fieldIssue2;
|
|
125
299
|
const { orgId } = props;
|
|
126
300
|
const api = useOutreachApi(orgId);
|
|
127
301
|
const loaded = useOutreachComplianceSettings(api, orgId);
|
|
@@ -161,16 +335,25 @@ const sameCountry = (option, value)=>option.code === value.code;
|
|
|
161
335
|
const cardDirty = (card)=>stored ? !outreachComplianceSettingsEqual(stored, candidate(card).settings) : false;
|
|
162
336
|
const cardBlocked = (card)=>candidate(card).issues.some((issue)=>CARDS[card].fields.includes(issue.field));
|
|
163
337
|
const footer = composeOutreachFooter(normalized);
|
|
164
|
-
const set = (field
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
338
|
+
const set = useCallback((field, value)=>{
|
|
339
|
+
setServerIssues((previous)=>{
|
|
340
|
+
const next = _extends({}, previous);
|
|
341
|
+
delete next[field];
|
|
342
|
+
return next;
|
|
343
|
+
});
|
|
344
|
+
setForm((previous)=>_extends({}, previous, {
|
|
345
|
+
[field]: value
|
|
346
|
+
}));
|
|
347
|
+
}, []);
|
|
348
|
+
const setLegalName = useCallback((event)=>set('legalName', event.target.value), [
|
|
349
|
+
set
|
|
350
|
+
]);
|
|
351
|
+
const setBrandName = useCallback((event)=>set('brandName', event.target.value), [
|
|
352
|
+
set
|
|
353
|
+
]);
|
|
354
|
+
const setPostalAddress = useCallback((event)=>set('postalAddress', event.target.value), [
|
|
355
|
+
set
|
|
356
|
+
]);
|
|
174
357
|
const clearIssues = (card)=>setServerIssues((previous)=>{
|
|
175
358
|
const next = _extends({}, previous);
|
|
176
359
|
for (const field of CARDS[card].fields){
|
|
@@ -232,6 +415,18 @@ const sameCountry = (option, value)=>option.code === value.code;
|
|
|
232
415
|
setSaving(null);
|
|
233
416
|
}
|
|
234
417
|
};
|
|
418
|
+
// The discard and save as last drawn, behind handlers that keep their
|
|
419
|
+
// identity: both read the form and the stored settings of that render.
|
|
420
|
+
const drawn = useRef({
|
|
421
|
+
discard,
|
|
422
|
+
save
|
|
423
|
+
});
|
|
424
|
+
drawn.current = {
|
|
425
|
+
discard,
|
|
426
|
+
save
|
|
427
|
+
};
|
|
428
|
+
const onDiscard = useCallback((card)=>drawn.current.discard(card), []);
|
|
429
|
+
const onSave = useCallback((card)=>void drawn.current.save(card), []);
|
|
235
430
|
if (loaded.status === 'loading' || !orgId && !stored) {
|
|
236
431
|
return /*#__PURE__*/ _jsx(OutreachLoading, {
|
|
237
432
|
label: "Loading compliance settings…"
|
|
@@ -245,149 +440,38 @@ const sameCountry = (option, value)=>option.code === value.code;
|
|
|
245
440
|
onRetry: loaded.reload
|
|
246
441
|
});
|
|
247
442
|
}
|
|
248
|
-
const cardActions = (card)=>{
|
|
249
|
-
const dirty = cardDirty(card);
|
|
250
|
-
return /*#__PURE__*/ _jsxs(Stack, {
|
|
251
|
-
direction: "row",
|
|
252
|
-
spacing: 1,
|
|
253
|
-
children: [
|
|
254
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
255
|
-
size: "small",
|
|
256
|
-
variant: "text",
|
|
257
|
-
disabled: !dirty || saving !== null,
|
|
258
|
-
onClick: ()=>discard(card),
|
|
259
|
-
children: "Discard changes"
|
|
260
|
-
}),
|
|
261
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
262
|
-
size: "small",
|
|
263
|
-
variant: "contained",
|
|
264
|
-
disabled: !dirty || saving !== null || cardBlocked(card),
|
|
265
|
-
onClick: ()=>void save(card),
|
|
266
|
-
children: saving === card ? 'Saving…' : 'Save'
|
|
267
|
-
})
|
|
268
|
-
]
|
|
269
|
-
});
|
|
270
|
-
};
|
|
271
443
|
return /*#__PURE__*/ _jsxs(Stack, {
|
|
272
444
|
spacing: 2,
|
|
273
445
|
children: [
|
|
274
|
-
/*#__PURE__*/ _jsx(
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
children: 'Every email a sequence sends ends with a footer naming your organization, its postal ' + 'address, that the email is a sales email, and how to stop more of them. The law ' + 'requires these on commercial email, so every email gets the footer automatically.'
|
|
291
|
-
}),
|
|
292
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
293
|
-
label: "Legal name",
|
|
294
|
-
value: form.legalName,
|
|
295
|
-
onChange: (event)=>set('legalName')(event.target.value),
|
|
296
|
-
error: Boolean(fieldIssue('legalName')),
|
|
297
|
-
helperText: (_fieldIssue = fieldIssue('legalName')) != null ? _fieldIssue : 'Your organization’s legal name, as the footer prints it.',
|
|
298
|
-
slotProps: {
|
|
299
|
-
htmlInput: {
|
|
300
|
-
maxLength: OUTREACH_LEGAL_NAME_MAX + 20
|
|
301
|
-
}
|
|
302
|
-
},
|
|
303
|
-
fullWidth: true
|
|
304
|
-
}),
|
|
305
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
306
|
-
label: "Brand name",
|
|
307
|
-
value: form.brandName,
|
|
308
|
-
onChange: (event)=>set('brandName')(event.target.value),
|
|
309
|
-
error: Boolean(fieldIssue('brandName')),
|
|
310
|
-
helperText: (_fieldIssue1 = fieldIssue('brandName')) != null ? _fieldIssue1 : 'The name the solicitation sentence uses. Leave empty to use the legal name.',
|
|
311
|
-
slotProps: {
|
|
312
|
-
htmlInput: {
|
|
313
|
-
maxLength: OUTREACH_BRAND_NAME_MAX + 20
|
|
314
|
-
}
|
|
315
|
-
},
|
|
316
|
-
fullWidth: true
|
|
317
|
-
}),
|
|
318
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
319
|
-
label: "Postal address",
|
|
320
|
-
value: form.postalAddress,
|
|
321
|
-
onChange: (event)=>set('postalAddress')(event.target.value),
|
|
322
|
-
error: Boolean(fieldIssue('postalAddress')),
|
|
323
|
-
helperText: (_fieldIssue2 = fieldIssue('postalAddress')) != null ? _fieldIssue2 : OUTREACH_POSTAL_ADDRESS_HELP,
|
|
324
|
-
slotProps: {
|
|
325
|
-
htmlInput: {
|
|
326
|
-
maxLength: OUTREACH_POSTAL_ADDRESS_MAX + 40
|
|
327
|
-
}
|
|
328
|
-
},
|
|
329
|
-
multiline: true,
|
|
330
|
-
minRows: 3,
|
|
331
|
-
fullWidth: true
|
|
332
|
-
}),
|
|
333
|
-
footer.footer ? /*#__PURE__*/ _jsxs(Stack, {
|
|
334
|
-
spacing: 0.5,
|
|
335
|
-
children: [
|
|
336
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
337
|
-
variant: "subtitle2",
|
|
338
|
-
children: "Every email ends with"
|
|
339
|
-
}),
|
|
340
|
-
/*#__PURE__*/ _jsx(Paper, {
|
|
341
|
-
variant: "outlined",
|
|
342
|
-
sx: {
|
|
343
|
-
p: 1.5
|
|
344
|
-
},
|
|
345
|
-
children: /*#__PURE__*/ _jsx(Typography, {
|
|
346
|
-
variant: "body2",
|
|
347
|
-
sx: {
|
|
348
|
-
whiteSpace: 'pre-wrap',
|
|
349
|
-
wordBreak: 'break-word'
|
|
350
|
-
},
|
|
351
|
-
"data-testid": "outreach-footer-preview",
|
|
352
|
-
children: footer.footer
|
|
353
|
-
})
|
|
354
|
-
})
|
|
355
|
-
]
|
|
356
|
-
}) : /*#__PURE__*/ _jsx(Alert, {
|
|
357
|
-
severity: "warning",
|
|
358
|
-
children: 'No sequence can be activated, and no email sent, until you add your organization’s legal ' + 'name and postal address.'
|
|
359
|
-
})
|
|
360
|
-
]
|
|
361
|
-
})
|
|
446
|
+
/*#__PURE__*/ _jsx(ComplianceIdentityCard, {
|
|
447
|
+
legalName: form.legalName,
|
|
448
|
+
brandName: form.brandName,
|
|
449
|
+
postalAddress: form.postalAddress,
|
|
450
|
+
legalNameIssue: fieldIssue('legalName'),
|
|
451
|
+
brandNameIssue: fieldIssue('brandName'),
|
|
452
|
+
postalAddressIssue: fieldIssue('postalAddress'),
|
|
453
|
+
footer: footer.footer,
|
|
454
|
+
onLegalName: setLegalName,
|
|
455
|
+
onBrandName: setBrandName,
|
|
456
|
+
onPostalAddress: setPostalAddress,
|
|
457
|
+
dirty: cardDirty('identity'),
|
|
458
|
+
blocked: cardBlocked('identity'),
|
|
459
|
+
saving: saving,
|
|
460
|
+
onDiscard: onDiscard,
|
|
461
|
+
onSave: onSave
|
|
362
462
|
}),
|
|
363
|
-
/*#__PURE__*/ _jsx(
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
374
|
-
spacing: 2,
|
|
375
|
-
children: [
|
|
376
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
377
|
-
variant: "body2",
|
|
378
|
-
color: "text.secondary",
|
|
379
|
-
children: 'Sequences may send only to people in these countries, and every sequence only to the ones ' + 'it and this list both allow. The United States is the default: Canada, the United Kingdom and most ' + 'of the European Union require a consent basis that an email to someone who never contacted ' + 'you does not have, so a cold email never goes outside the United States whatever this list ' + 'says. Add another country only for people who came to you first.'
|
|
380
|
-
}),
|
|
381
|
-
/*#__PURE__*/ _jsx(ComplianceCountriesField, {
|
|
382
|
-
options: countries,
|
|
383
|
-
value: selectedCountries,
|
|
384
|
-
issue: fieldIssue('allowedCountries'),
|
|
385
|
-
onChange: setCountries
|
|
386
|
-
})
|
|
387
|
-
]
|
|
388
|
-
})
|
|
463
|
+
/*#__PURE__*/ _jsx(ComplianceCountriesCard, {
|
|
464
|
+
options: countries,
|
|
465
|
+
value: selectedCountries,
|
|
466
|
+
issue: fieldIssue('allowedCountries'),
|
|
467
|
+
onChange: setCountries,
|
|
468
|
+
dirty: cardDirty('countries'),
|
|
469
|
+
blocked: cardBlocked('countries'),
|
|
470
|
+
saving: saving,
|
|
471
|
+
onDiscard: onDiscard,
|
|
472
|
+
onSave: onSave
|
|
389
473
|
}),
|
|
390
|
-
/*#__PURE__*/ _jsx(
|
|
474
|
+
/*#__PURE__*/ _jsx(MemoDoNotContactDomainsCard, {
|
|
391
475
|
orgId: orgId
|
|
392
476
|
})
|
|
393
477
|
]
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/compliance-section.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 { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Autocomplete,\n Button,\n Chip,\n Paper,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { composeOutreachFooter } from '../engine/compose'\nimport {\n outreachComplianceSettingsEqual,\n outreachCountryLabel,\n outreachCountryOptions,\n OUTREACH_BRAND_NAME_MAX,\n OUTREACH_LEGAL_NAME_MAX,\n OUTREACH_POSTAL_ADDRESS_MAX,\n validateOutreachComplianceSettings,\n type OutreachComplianceField,\n type OutreachCountryOption,\n} from '../model/compliance-settings'\nimport type { OutreachComplianceSettings } from '../model/outreach.types'\nimport { OutreachDoNotContactDomainsCard } from './do-not-contact-domains'\nimport { OutreachLoading, OutreachLoadProblem } from './outreach-ui'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachComplianceSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n}\n\n/** What CAN-SPAM accepts as the postal address, as the field says it. */\nexport const OUTREACH_POSTAL_ADDRESS_HELP =\n 'A street address, a post office box registered with the US Postal Service, or a private mailbox ' +\n 'registered with the US Postal Service. Every email prints it in its footer.'\n\nconst EMPTY: OutreachComplianceSettings = {\n legalName: '',\n brandName: '',\n postalAddress: '',\n allowedCountries: ['US'],\n}\n\ntype ComplianceCardKey = 'identity' | 'countries'\n\n/**\n * The page's cards and the settings each one owns. Every card saves only its\n * own fields onto what is stored, so saving the countries never commits a\n * half-edited sender identity, and the other way round.\n */\nconst CARDS: Record<\n ComplianceCardKey,\n { fields: readonly (keyof OutreachComplianceSettings)[]; saved: string }\n> = {\n identity: {\n fields: ['legalName', 'brandName', 'postalAddress'],\n saved: 'Sender identity saved.',\n },\n countries: {\n fields: ['allowedCountries'],\n saved: 'Allowed countries saved.',\n },\n}\n\nfunction pickCard(\n settings: OutreachComplianceSettings,\n card: ComplianceCardKey,\n): Partial<OutreachComplianceSettings> {\n return Object.fromEntries(\n CARDS[card].fields.map((field) => [field, settings[field]]),\n )\n}\n\nfunction cardEqual(\n card: ComplianceCardKey,\n a: OutreachComplianceSettings,\n b: OutreachComplianceSettings,\n): boolean {\n return outreachComplianceSettingsEqual({ ...b, ...pickCard(a, card) }, b)\n}\n\nconst countryName = (option: OutreachCountryOption): string => option.name\nconst sameCountry = (\n option: OutreachCountryOption,\n value: OutreachCountryOption,\n): boolean => option.code === value.code\n\n/**\n * The allowed countries, as chips — MEMOIZED, and every prop it takes is\n * stable while the countries are (AGL-3423).\n *\n * A multiple Autocomplete hands its input a new chip array as\n * `startAdornment` on every render, and MUI's `InputBase` copies that into\n * its `FormControl` from a passive effect: a state update left pending after\n * every commit the field takes part in. React 19 counts each such commit as a\n * nested update, and keystrokes delivered back to back commit one after\n * another with nothing to clear the count, so the fifty-first throws\n * \"Maximum update depth exceeded\" (#185) — the sequence editor's countries\n * field did exactly that. The legal name and address share one form with\n * this list, so each keystroke in them re-rendered it; kept out of those\n * renders, it commits only when it changes.\n */\nconst ComplianceCountriesField = memo(function ComplianceCountriesField(props: {\n options: readonly OutreachCountryOption[]\n value: readonly OutreachCountryOption[]\n issue?: string\n onChange(next: OutreachCountryOption[]): void\n}) {\n const { onChange } = props\n return (\n <Autocomplete<OutreachCountryOption, true>\n multiple\n options={props.options}\n value={props.value as OutreachCountryOption[]}\n getOptionLabel={countryName}\n isOptionEqualToValue={sameCountry}\n onChange={(_event, next) => onChange(next)}\n renderValue={(value, getItemProps) =>\n value.map((option, index) => {\n const { key, ...item } = getItemProps({ index })\n return <Chip key={key} size=\"small\" label={option.name} {...item} />\n })\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Countries\"\n error={Boolean(props.issue)}\n helperText={props.issue}\n />\n )}\n />\n )\n})\n\n/**\n * Sequences → Compliance (AGL-2980): who every email says sent it, the\n * countries a sequence may send to at all, and — below the settings, with\n * no Save of its own — the domains no sequence emails (AGL-3244). Each\n * settings card saves its own fields from its header, the console's place\n * for a card's actions (AGL-3333).\n *\n * The footer is previewed from what is typed, by the engine's own\n * `composeOutreachFooter`, so the page shows the lines every email will end\n * with — or, while the legal name or the address is empty, the sentence\n * that explains why nothing can be sent and no sequence activated.\n */\nexport function OutreachComplianceSection(\n props: OutreachComplianceSectionProps,\n) {\n const { orgId } = props\n const api = useOutreachApi(orgId)\n const loaded = useOutreachComplianceSettings(api, orgId)\n const { enqueueSnackbar } = useSnackbar()\n const [form, setForm] = useState<OutreachComplianceSettings>(EMPTY)\n const [saving, setSaving] = useState<ComplianceCardKey | null>(null)\n const [serverIssues, setServerIssues] = useState<\n Partial<Record<OutreachComplianceField, string>>\n >({})\n const countries = useMemo(() => outreachCountryOptions(), [])\n\n const stored = loaded.settings\n const previousStored = useRef<OutreachComplianceSettings | null>(null)\n useEffect(() => {\n if (!stored) return\n const before = previousStored.current\n previousStored.current = stored\n // A fresh read replaces every card the member has not touched; a card\n // with unsaved edits keeps them, so saving one card never wipes another.\n setForm((current) => {\n if (!before) return { ...stored }\n let next: OutreachComplianceSettings = { ...stored }\n for (const card of Object.keys(CARDS) as ComplianceCardKey[]) {\n if (!cardEqual(card, current, before)) {\n next = { ...next, ...pickCard(current, card) }\n }\n }\n return next\n })\n }, [stored])\n\n const { settings: normalized, issues } =\n validateOutreachComplianceSettings(form)\n const fieldIssue = (field: OutreachComplianceField) =>\n serverIssues[field] ??\n issues.find((issue) => issue.field === field)?.message\n /** What saving one card would store: its own fields over the stored rest. */\n const candidate = (card: ComplianceCardKey) =>\n validateOutreachComplianceSettings(\n stored ? { ...stored, ...pickCard(form, card) } : form,\n )\n const cardDirty = (card: ComplianceCardKey) =>\n stored\n ? !outreachComplianceSettingsEqual(stored, candidate(card).settings)\n : false\n const cardBlocked = (card: ComplianceCardKey) =>\n candidate(card).issues.some((issue) =>\n (CARDS[card].fields as readonly string[]).includes(issue.field),\n )\n const footer = composeOutreachFooter(normalized)\n const set = (field: keyof OutreachComplianceSettings) => (value: string) => {\n setServerIssues((previous) => {\n const next = { ...previous }\n delete next[field as OutreachComplianceField]\n return next\n })\n setForm((previous) => ({ ...previous, [field]: value }))\n }\n\n const clearIssues = (card: ComplianceCardKey) =>\n setServerIssues((previous) => {\n const next = { ...previous }\n for (const field of CARDS[card].fields) {\n delete next[field as OutreachComplianceField]\n }\n return next\n })\n\n // Stable, for the memoized countries field: an edit clears the card's\n // issues and replaces the list in whatever form it lands on.\n const setCountries = useCallback((next: OutreachCountryOption[]) => {\n setServerIssues((previous) => {\n const cleared = { ...previous }\n for (const field of CARDS.countries.fields) {\n delete cleared[field as OutreachComplianceField]\n }\n return cleared\n })\n setForm((previous) => ({\n ...previous,\n allowedCountries: next.map((option) => option.code),\n }))\n }, [])\n const allowedCountries = form.allowedCountries\n const selectedCountries = useMemo(\n () =>\n allowedCountries.map(\n (code) =>\n countries.find((option) => option.code === code) ?? {\n code,\n name: outreachCountryLabel(code),\n },\n ),\n [allowedCountries, countries],\n )\n\n const discard = (card: ComplianceCardKey) => {\n clearIssues(card)\n if (stored)\n setForm((previous) => ({ ...previous, ...pickCard(stored, card) }))\n }\n\n const save = async (card: ComplianceCardKey) => {\n setSaving(card)\n try {\n const answer = await api.saveSettings(candidate(card).settings)\n clearIssues(card)\n setForm((previous) => ({\n ...previous,\n ...pickCard(answer.settings, card),\n }))\n loaded.reload()\n enqueueSnackbar(answer.changed ? CARDS[card].saved : 'Nothing changed.', {\n variant: answer.changed ? 'success' : 'info',\n })\n } catch (error) {\n if (error instanceof OutreachRouteError && error.issues.length) {\n setServerIssues((previous) => ({\n ...previous,\n ...Object.fromEntries(\n error.issues\n .filter(\n (\n issue,\n ): issue is {\n field: OutreachComplianceField\n message: string\n } => 'field' in issue,\n )\n .map((issue) => [issue.field, issue.message]),\n ),\n }))\n }\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(null)\n }\n }\n\n if (loaded.status === 'loading' || (!orgId && !stored)) {\n return <OutreachLoading label=\"Loading compliance settings…\" />\n }\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return (\n <OutreachLoadProblem\n status={loaded.status}\n what=\"compliance settings\"\n message={loaded.message}\n onRetry={loaded.reload}\n />\n )\n }\n\n const cardActions = (card: ComplianceCardKey) => {\n const dirty = cardDirty(card)\n return (\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n variant=\"text\"\n disabled={!dirty || saving !== null}\n onClick={() => discard(card)}\n >\n Discard changes\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={!dirty || saving !== null || cardBlocked(card)}\n onClick={() => void save(card)}\n >\n {saving === card ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n )\n }\n\n return (\n <Stack spacing={2}>\n <CardDisplay\n header=\"Sender identity\"\n help={pluginDocsHelp('sequences', { anchor: '#compliance-settings' })}\n HeaderProps={{ action: cardActions('identity') }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Every email a sequence sends ends with a footer naming your organization, its postal ' +\n 'address, that the email is a sales email, and how to stop more of them. The law ' +\n 'requires these on commercial email, so every email gets the footer automatically.'}\n </Typography>\n <TextField\n label=\"Legal name\"\n value={form.legalName}\n onChange={(event) => set('legalName')(event.target.value)}\n error={Boolean(fieldIssue('legalName'))}\n helperText={\n fieldIssue('legalName') ??\n 'Your organization’s legal name, as the footer prints it.'\n }\n slotProps={{\n htmlInput: { maxLength: OUTREACH_LEGAL_NAME_MAX + 20 },\n }}\n fullWidth\n />\n <TextField\n label=\"Brand name\"\n value={form.brandName}\n onChange={(event) => set('brandName')(event.target.value)}\n error={Boolean(fieldIssue('brandName'))}\n helperText={\n fieldIssue('brandName') ??\n 'The name the solicitation sentence uses. Leave empty to use the legal name.'\n }\n slotProps={{\n htmlInput: { maxLength: OUTREACH_BRAND_NAME_MAX + 20 },\n }}\n fullWidth\n />\n <TextField\n label=\"Postal address\"\n value={form.postalAddress}\n onChange={(event) => set('postalAddress')(event.target.value)}\n error={Boolean(fieldIssue('postalAddress'))}\n helperText={\n fieldIssue('postalAddress') ?? OUTREACH_POSTAL_ADDRESS_HELP\n }\n slotProps={{\n htmlInput: { maxLength: OUTREACH_POSTAL_ADDRESS_MAX + 40 },\n }}\n multiline\n minRows={3}\n fullWidth\n />\n {footer.footer ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">Every email ends with</Typography>\n <Paper variant=\"outlined\" sx={{ p: 1.5 }}>\n <Typography\n variant=\"body2\"\n sx={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}\n data-testid=\"outreach-footer-preview\"\n >\n {footer.footer}\n </Typography>\n </Paper>\n </Stack>\n ) : (\n <Alert severity=\"warning\">\n {'No sequence can be activated, and no email sent, until you add your organization’s legal ' +\n 'name and postal address.'}\n </Alert>\n )}\n </Stack>\n </CardDisplay>\n\n <CardDisplay\n header=\"Allowed countries\"\n help={pluginDocsHelp('sequences', { anchor: '#allowed-countries' })}\n HeaderProps={{ action: cardActions('countries') }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Sequences may send only to people in these countries, and every sequence only to the ones ' +\n 'it and this list both allow. The United States is the default: Canada, the United Kingdom and most ' +\n 'of the European Union require a consent basis that an email to someone who never contacted ' +\n 'you does not have, so a cold email never goes outside the United States whatever this list ' +\n 'says. Add another country only for people who came to you first.'}\n </Typography>\n <ComplianceCountriesField\n options={countries}\n value={selectedCountries}\n issue={fieldIssue('allowedCountries')}\n onChange={setCountries}\n />\n </Stack>\n </CardDisplay>\n\n {/* Its own list, saved as it is edited (AGL-3244): it has no Save. */}\n <OutreachDoNotContactDomainsCard orgId={orgId} />\n </Stack>\n )\n}\nOutreachComplianceSection.displayName = 'OutreachComplianceSection'\n\nexport default OutreachComplianceSection\n"],"names":["pluginDocsHelp","CardDisplay","useSnackbar","Alert","Autocomplete","Button","Chip","Paper","Stack","TextField","Typography","memo","useCallback","useEffect","useMemo","useRef","useState","composeOutreachFooter","outreachComplianceSettingsEqual","outreachCountryLabel","outreachCountryOptions","OUTREACH_BRAND_NAME_MAX","OUTREACH_LEGAL_NAME_MAX","OUTREACH_POSTAL_ADDRESS_MAX","validateOutreachComplianceSettings","OutreachDoNotContactDomainsCard","OutreachLoading","OutreachLoadProblem","OutreachRouteError","useOutreachApi","useOutreachComplianceSettings","OUTREACH_POSTAL_ADDRESS_HELP","EMPTY","legalName","brandName","postalAddress","allowedCountries","CARDS","identity","fields","saved","countries","pickCard","settings","card","Object","fromEntries","map","field","cardEqual","a","b","countryName","option","name","sameCountry","value","code","ComplianceCountriesField","props","onChange","multiple","options","getOptionLabel","isOptionEqualToValue","_event","next","renderValue","getItemProps","index","key","item","size","label","renderInput","params","error","Boolean","issue","helperText","OutreachComplianceSection","fieldIssue","orgId","api","loaded","enqueueSnackbar","form","setForm","saving","setSaving","serverIssues","setServerIssues","stored","previousStored","before","current","keys","normalized","issues","find","message","candidate","cardDirty","cardBlocked","some","includes","footer","set","previous","clearIssues","setCountries","cleared","selectedCountries","discard","save","answer","saveSettings","reload","changed","variant","length","filter","allowDuplicate","status","what","onRetry","cardActions","dirty","direction","spacing","disabled","onClick","header","help","anchor","HeaderProps","action","contentGutterX","contentGutterY","color","event","target","slotProps","htmlInput","maxLength","fullWidth","multiline","minRows","sx","p","whiteSpace","wordBreak","data-testid","severity","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/compliance-section.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 { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Autocomplete,\n Button,\n Chip,\n Paper,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { composeOutreachFooter } from '../engine/compose'\nimport {\n outreachComplianceSettingsEqual,\n outreachCountryLabel,\n outreachCountryOptions,\n OUTREACH_BRAND_NAME_MAX,\n OUTREACH_LEGAL_NAME_MAX,\n OUTREACH_POSTAL_ADDRESS_MAX,\n validateOutreachComplianceSettings,\n type OutreachComplianceField,\n type OutreachCountryOption,\n} from '../model/compliance-settings'\nimport type { OutreachComplianceSettings } from '../model/outreach.types'\nimport { OutreachDoNotContactDomainsCard } from './do-not-contact-domains'\nimport { OutreachLoading, OutreachLoadProblem } from './outreach-ui'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachComplianceSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n}\n\n/** What CAN-SPAM accepts as the postal address, as the field says it. */\nexport const OUTREACH_POSTAL_ADDRESS_HELP =\n 'A street address, a post office box registered with the US Postal Service, or a private mailbox ' +\n 'registered with the US Postal Service. Every email prints it in its footer.'\n\nconst EMPTY: OutreachComplianceSettings = {\n legalName: '',\n brandName: '',\n postalAddress: '',\n allowedCountries: ['US'],\n}\n\ntype ComplianceCardKey = 'identity' | 'countries'\n\n/**\n * The page's cards and the settings each one owns. Every card saves only its\n * own fields onto what is stored, so saving the countries never commits a\n * half-edited sender identity, and the other way round.\n */\nconst CARDS: Record<\n ComplianceCardKey,\n { fields: readonly (keyof OutreachComplianceSettings)[]; saved: string }\n> = {\n identity: {\n fields: ['legalName', 'brandName', 'postalAddress'],\n saved: 'Sender identity saved.',\n },\n countries: {\n fields: ['allowedCountries'],\n saved: 'Allowed countries saved.',\n },\n}\n\nfunction pickCard(\n settings: OutreachComplianceSettings,\n card: ComplianceCardKey,\n): Partial<OutreachComplianceSettings> {\n return Object.fromEntries(\n CARDS[card].fields.map((field) => [field, settings[field]]),\n )\n}\n\nfunction cardEqual(\n card: ComplianceCardKey,\n a: OutreachComplianceSettings,\n b: OutreachComplianceSettings,\n): boolean {\n return outreachComplianceSettingsEqual({ ...b, ...pickCard(a, card) }, b)\n}\n\nconst countryName = (option: OutreachCountryOption): string => option.name\nconst sameCountry = (\n option: OutreachCountryOption,\n value: OutreachCountryOption,\n): boolean => option.code === value.code\n\n/**\n * The allowed countries, as chips — MEMOIZED, and every prop it takes is\n * stable while the countries are (AGL-3423).\n *\n * A multiple Autocomplete hands its input a new chip array as\n * `startAdornment` on every render, and MUI's `InputBase` copies that into\n * its `FormControl` from a passive effect: a state update left pending after\n * every commit the field takes part in. React 19 counts each such commit as a\n * nested update, and keystrokes delivered back to back commit one after\n * another with nothing to clear the count, so the fifty-first throws\n * \"Maximum update depth exceeded\" (#185) — the sequence editor's countries\n * field did exactly that. The legal name and address share one form with\n * this list, so each keystroke in them re-rendered it; kept out of those\n * renders, it commits only when it changes.\n */\nconst ComplianceCountriesField = memo(function ComplianceCountriesField(props: {\n options: readonly OutreachCountryOption[]\n value: readonly OutreachCountryOption[]\n issue?: string\n onChange(next: OutreachCountryOption[]): void\n}) {\n const { onChange } = props\n return (\n <Autocomplete<OutreachCountryOption, true>\n multiple\n options={props.options}\n value={props.value as OutreachCountryOption[]}\n getOptionLabel={countryName}\n isOptionEqualToValue={sameCountry}\n onChange={(_event, next) => onChange(next)}\n renderValue={(value, getItemProps) =>\n value.map((option, index) => {\n const { key, ...item } = getItemProps({ index })\n return <Chip key={key} size=\"small\" label={option.name} {...item} />\n })\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Countries\"\n error={Boolean(props.issue)}\n helperText={props.issue}\n />\n )}\n />\n )\n})\n\n/** A text field that redraws only when what it shows changes (AGL-3423). */\nconst MemoTextField = memo(TextField) as typeof TextField\n/** Its own list and listener: nothing typed in the cards above it reaches it. */\nconst MemoDoNotContactDomainsCard = memo(OutreachDoNotContactDomainsCard)\n\ntype FieldChange = { target: { value: string } }\n\nconst LEGAL_NAME_SLOT_PROPS = {\n htmlInput: { maxLength: OUTREACH_LEGAL_NAME_MAX + 20 },\n}\nconst BRAND_NAME_SLOT_PROPS = {\n htmlInput: { maxLength: OUTREACH_BRAND_NAME_MAX + 20 },\n}\nconst POSTAL_ADDRESS_SLOT_PROPS = {\n htmlInput: { maxLength: OUTREACH_POSTAL_ADDRESS_MAX + 40 },\n}\nconst FOOTER_PAPER = { p: 1.5 } as const\nconst FOOTER_TEXT = { whiteSpace: 'pre-wrap', wordBreak: 'break-word' } as const\n\n/** How a card's header actions stand: whether it has edits, and can save them. */\ninterface ComplianceCardState {\n dirty: boolean\n blocked: boolean\n saving: ComplianceCardKey | null\n onDiscard(card: ComplianceCardKey): void\n onSave(card: ComplianceCardKey): void\n}\n\n/** A settings card's header actions: discard its edits, or save its own fields. */\nconst ComplianceCardActions = memo(function ComplianceCardActions(\n props: ComplianceCardState & { card: ComplianceCardKey },\n) {\n const { card, dirty, saving, onDiscard, onSave } = props\n return (\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n variant=\"text\"\n disabled={!dirty || saving !== null}\n onClick={() => onDiscard(card)}\n >\n Discard changes\n </Button>\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={!dirty || saving !== null || props.blocked}\n onClick={() => onSave(card)}\n >\n {saving === card ? 'Saving…' : 'Save'}\n </Button>\n </Stack>\n )\n})\n\n/**\n * Who every email says sent it, and the footer that says it. Each field is\n * memoized with a handler that keeps its identity, so a letter typed into one\n * redraws that field and the footer it changes, not the other two.\n */\nconst ComplianceIdentityCard = memo(function ComplianceIdentityCard(\n props: ComplianceCardState & {\n legalName: string\n brandName: string\n postalAddress: string\n legalNameIssue?: string\n brandNameIssue?: string\n postalAddressIssue?: string\n /** The footer every email ends with, or `null` while it cannot be composed. */\n footer: string | null\n onLegalName(event: FieldChange): void\n onBrandName(event: FieldChange): void\n onPostalAddress(event: FieldChange): void\n },\n) {\n const { footer } = props\n return (\n <CardDisplay\n header=\"Sender identity\"\n help={pluginDocsHelp('sequences', { anchor: '#compliance-settings' })}\n HeaderProps={{\n action: (\n <ComplianceCardActions\n card=\"identity\"\n dirty={props.dirty}\n blocked={props.blocked}\n saving={props.saving}\n onDiscard={props.onDiscard}\n onSave={props.onSave}\n />\n ),\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Every email a sequence sends ends with a footer naming your organization, its postal ' +\n 'address, that the email is a sales email, and how to stop more of them. The law ' +\n 'requires these on commercial email, so every email gets the footer automatically.'}\n </Typography>\n <MemoTextField\n label=\"Legal name\"\n value={props.legalName}\n onChange={props.onLegalName}\n error={Boolean(props.legalNameIssue)}\n helperText={\n props.legalNameIssue ??\n 'Your organization’s legal name, as the footer prints it.'\n }\n slotProps={LEGAL_NAME_SLOT_PROPS}\n fullWidth\n />\n <MemoTextField\n label=\"Brand name\"\n value={props.brandName}\n onChange={props.onBrandName}\n error={Boolean(props.brandNameIssue)}\n helperText={\n props.brandNameIssue ??\n 'The name the solicitation sentence uses. Leave empty to use the legal name.'\n }\n slotProps={BRAND_NAME_SLOT_PROPS}\n fullWidth\n />\n <MemoTextField\n label=\"Postal address\"\n value={props.postalAddress}\n onChange={props.onPostalAddress}\n error={Boolean(props.postalAddressIssue)}\n helperText={props.postalAddressIssue ?? OUTREACH_POSTAL_ADDRESS_HELP}\n slotProps={POSTAL_ADDRESS_SLOT_PROPS}\n multiline\n minRows={3}\n fullWidth\n />\n {footer ? (\n <Stack spacing={0.5}>\n <Typography variant=\"subtitle2\">Every email ends with</Typography>\n <Paper variant=\"outlined\" sx={FOOTER_PAPER}>\n <Typography\n variant=\"body2\"\n sx={FOOTER_TEXT}\n data-testid=\"outreach-footer-preview\"\n >\n {footer}\n </Typography>\n </Paper>\n </Stack>\n ) : (\n <Alert severity=\"warning\">\n {'No sequence can be activated, and no email sent, until you add your organization’s legal ' +\n 'name and postal address.'}\n </Alert>\n )}\n </Stack>\n </CardDisplay>\n )\n})\n\n/** The countries a sequence may send to at all. */\nconst ComplianceCountriesCard = memo(function ComplianceCountriesCard(\n props: ComplianceCardState & {\n options: readonly OutreachCountryOption[]\n value: readonly OutreachCountryOption[]\n issue?: string\n onChange(next: OutreachCountryOption[]): void\n },\n) {\n return (\n <CardDisplay\n header=\"Allowed countries\"\n help={pluginDocsHelp('sequences', { anchor: '#allowed-countries' })}\n HeaderProps={{\n action: (\n <ComplianceCardActions\n card=\"countries\"\n dirty={props.dirty}\n blocked={props.blocked}\n saving={props.saving}\n onDiscard={props.onDiscard}\n onSave={props.onSave}\n />\n ),\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Sequences may send only to people in these countries, and every sequence only to the ones ' +\n 'it and this list both allow. The United States is the default: Canada, the United Kingdom and most ' +\n 'of the European Union require a consent basis that an email to someone who never contacted ' +\n 'you does not have, so a cold email never goes outside the United States whatever this list ' +\n 'says. Add another country only for people who came to you first.'}\n </Typography>\n <ComplianceCountriesField\n options={props.options}\n value={props.value}\n issue={props.issue}\n onChange={props.onChange}\n />\n </Stack>\n </CardDisplay>\n )\n})\n\n/**\n * Sequences → Compliance (AGL-2980): who every email says sent it, the\n * countries a sequence may send to at all, and — below the settings, with\n * no Save of its own — the domains no sequence emails (AGL-3244). Each\n * settings card saves its own fields from its header, the console's place\n * for a card's actions (AGL-3333).\n *\n * The footer is previewed from what is typed, by the engine's own\n * `composeOutreachFooter`, so the page shows the lines every email will end\n * with — or, while the legal name or the address is empty, the sentence\n * that explains why nothing can be sent and no sequence activated.\n *\n * Both settings cards are drawn from the one form, and each is memoized and\n * handed only what it shows, through handlers that keep their identity\n * (AGL-3423): a letter typed into the sender identity redraws its field and\n * the footer, not the other fields, the countries or the domain list.\n */\nexport function OutreachComplianceSection(\n props: OutreachComplianceSectionProps,\n) {\n const { orgId } = props\n const api = useOutreachApi(orgId)\n const loaded = useOutreachComplianceSettings(api, orgId)\n const { enqueueSnackbar } = useSnackbar()\n const [form, setForm] = useState<OutreachComplianceSettings>(EMPTY)\n const [saving, setSaving] = useState<ComplianceCardKey | null>(null)\n const [serverIssues, setServerIssues] = useState<\n Partial<Record<OutreachComplianceField, string>>\n >({})\n const countries = useMemo(() => outreachCountryOptions(), [])\n\n const stored = loaded.settings\n const previousStored = useRef<OutreachComplianceSettings | null>(null)\n useEffect(() => {\n if (!stored) return\n const before = previousStored.current\n previousStored.current = stored\n // A fresh read replaces every card the member has not touched; a card\n // with unsaved edits keeps them, so saving one card never wipes another.\n setForm((current) => {\n if (!before) return { ...stored }\n let next: OutreachComplianceSettings = { ...stored }\n for (const card of Object.keys(CARDS) as ComplianceCardKey[]) {\n if (!cardEqual(card, current, before)) {\n next = { ...next, ...pickCard(current, card) }\n }\n }\n return next\n })\n }, [stored])\n\n const { settings: normalized, issues } =\n validateOutreachComplianceSettings(form)\n const fieldIssue = (field: OutreachComplianceField) =>\n serverIssues[field] ??\n issues.find((issue) => issue.field === field)?.message\n /** What saving one card would store: its own fields over the stored rest. */\n const candidate = (card: ComplianceCardKey) =>\n validateOutreachComplianceSettings(\n stored ? { ...stored, ...pickCard(form, card) } : form,\n )\n const cardDirty = (card: ComplianceCardKey) =>\n stored\n ? !outreachComplianceSettingsEqual(stored, candidate(card).settings)\n : false\n const cardBlocked = (card: ComplianceCardKey) =>\n candidate(card).issues.some((issue) =>\n (CARDS[card].fields as readonly string[]).includes(issue.field),\n )\n const footer = composeOutreachFooter(normalized)\n const set = useCallback(\n (field: keyof OutreachComplianceSettings, value: string) => {\n setServerIssues((previous) => {\n const next = { ...previous }\n delete next[field as OutreachComplianceField]\n return next\n })\n setForm((previous) => ({ ...previous, [field]: value }))\n },\n [],\n )\n const setLegalName = useCallback(\n (event: FieldChange) => set('legalName', event.target.value),\n [set],\n )\n const setBrandName = useCallback(\n (event: FieldChange) => set('brandName', event.target.value),\n [set],\n )\n const setPostalAddress = useCallback(\n (event: FieldChange) => set('postalAddress', event.target.value),\n [set],\n )\n\n const clearIssues = (card: ComplianceCardKey) =>\n setServerIssues((previous) => {\n const next = { ...previous }\n for (const field of CARDS[card].fields) {\n delete next[field as OutreachComplianceField]\n }\n return next\n })\n\n // Stable, for the memoized countries field: an edit clears the card's\n // issues and replaces the list in whatever form it lands on.\n const setCountries = useCallback((next: OutreachCountryOption[]) => {\n setServerIssues((previous) => {\n const cleared = { ...previous }\n for (const field of CARDS.countries.fields) {\n delete cleared[field as OutreachComplianceField]\n }\n return cleared\n })\n setForm((previous) => ({\n ...previous,\n allowedCountries: next.map((option) => option.code),\n }))\n }, [])\n const allowedCountries = form.allowedCountries\n const selectedCountries = useMemo(\n () =>\n allowedCountries.map(\n (code) =>\n countries.find((option) => option.code === code) ?? {\n code,\n name: outreachCountryLabel(code),\n },\n ),\n [allowedCountries, countries],\n )\n\n const discard = (card: ComplianceCardKey) => {\n clearIssues(card)\n if (stored)\n setForm((previous) => ({ ...previous, ...pickCard(stored, card) }))\n }\n\n const save = async (card: ComplianceCardKey) => {\n setSaving(card)\n try {\n const answer = await api.saveSettings(candidate(card).settings)\n clearIssues(card)\n setForm((previous) => ({\n ...previous,\n ...pickCard(answer.settings, card),\n }))\n loaded.reload()\n enqueueSnackbar(answer.changed ? CARDS[card].saved : 'Nothing changed.', {\n variant: answer.changed ? 'success' : 'info',\n })\n } catch (error) {\n if (error instanceof OutreachRouteError && error.issues.length) {\n setServerIssues((previous) => ({\n ...previous,\n ...Object.fromEntries(\n error.issues\n .filter(\n (\n issue,\n ): issue is {\n field: OutreachComplianceField\n message: string\n } => 'field' in issue,\n )\n .map((issue) => [issue.field, issue.message]),\n ),\n }))\n }\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(null)\n }\n }\n\n // The discard and save as last drawn, behind handlers that keep their\n // identity: both read the form and the stored settings of that render.\n const drawn = useRef({ discard, save })\n drawn.current = { discard, save }\n const onDiscard = useCallback(\n (card: ComplianceCardKey) => drawn.current.discard(card),\n [],\n )\n const onSave = useCallback(\n (card: ComplianceCardKey) => void drawn.current.save(card),\n [],\n )\n\n if (loaded.status === 'loading' || (!orgId && !stored)) {\n return <OutreachLoading label=\"Loading compliance settings…\" />\n }\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return (\n <OutreachLoadProblem\n status={loaded.status}\n what=\"compliance settings\"\n message={loaded.message}\n onRetry={loaded.reload}\n />\n )\n }\n\n return (\n <Stack spacing={2}>\n <ComplianceIdentityCard\n legalName={form.legalName}\n brandName={form.brandName}\n postalAddress={form.postalAddress}\n legalNameIssue={fieldIssue('legalName')}\n brandNameIssue={fieldIssue('brandName')}\n postalAddressIssue={fieldIssue('postalAddress')}\n footer={footer.footer}\n onLegalName={setLegalName}\n onBrandName={setBrandName}\n onPostalAddress={setPostalAddress}\n dirty={cardDirty('identity')}\n blocked={cardBlocked('identity')}\n saving={saving}\n onDiscard={onDiscard}\n onSave={onSave}\n />\n\n <ComplianceCountriesCard\n options={countries}\n value={selectedCountries}\n issue={fieldIssue('allowedCountries')}\n onChange={setCountries}\n dirty={cardDirty('countries')}\n blocked={cardBlocked('countries')}\n saving={saving}\n onDiscard={onDiscard}\n onSave={onSave}\n />\n\n {/* Its own list, saved as it is edited (AGL-3244): it has no Save. */}\n <MemoDoNotContactDomainsCard orgId={orgId} />\n </Stack>\n )\n}\nOutreachComplianceSection.displayName = 'OutreachComplianceSection'\n\nexport default OutreachComplianceSection\n"],"names":["pluginDocsHelp","CardDisplay","useSnackbar","Alert","Autocomplete","Button","Chip","Paper","Stack","TextField","Typography","memo","useCallback","useEffect","useMemo","useRef","useState","composeOutreachFooter","outreachComplianceSettingsEqual","outreachCountryLabel","outreachCountryOptions","OUTREACH_BRAND_NAME_MAX","OUTREACH_LEGAL_NAME_MAX","OUTREACH_POSTAL_ADDRESS_MAX","validateOutreachComplianceSettings","OutreachDoNotContactDomainsCard","OutreachLoading","OutreachLoadProblem","OutreachRouteError","useOutreachApi","useOutreachComplianceSettings","OUTREACH_POSTAL_ADDRESS_HELP","EMPTY","legalName","brandName","postalAddress","allowedCountries","CARDS","identity","fields","saved","countries","pickCard","settings","card","Object","fromEntries","map","field","cardEqual","a","b","countryName","option","name","sameCountry","value","code","ComplianceCountriesField","props","onChange","multiple","options","getOptionLabel","isOptionEqualToValue","_event","next","renderValue","getItemProps","index","key","item","size","label","renderInput","params","error","Boolean","issue","helperText","MemoTextField","MemoDoNotContactDomainsCard","LEGAL_NAME_SLOT_PROPS","htmlInput","maxLength","BRAND_NAME_SLOT_PROPS","POSTAL_ADDRESS_SLOT_PROPS","FOOTER_PAPER","p","FOOTER_TEXT","whiteSpace","wordBreak","ComplianceCardActions","dirty","saving","onDiscard","onSave","direction","spacing","variant","disabled","onClick","blocked","ComplianceIdentityCard","footer","header","help","anchor","HeaderProps","action","contentGutterX","contentGutterY","color","onLegalName","legalNameIssue","slotProps","fullWidth","onBrandName","brandNameIssue","onPostalAddress","postalAddressIssue","multiline","minRows","sx","data-testid","severity","ComplianceCountriesCard","OutreachComplianceSection","orgId","api","loaded","enqueueSnackbar","form","setForm","setSaving","serverIssues","setServerIssues","stored","previousStored","before","current","keys","normalized","issues","fieldIssue","find","message","candidate","cardDirty","cardBlocked","some","includes","set","previous","setLegalName","event","target","setBrandName","setPostalAddress","clearIssues","setCountries","cleared","selectedCountries","discard","save","answer","saveSettings","reload","changed","length","filter","allowDuplicate","drawn","status","what","onRetry","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;;AAEA,SAASA,cAAc,QAAQ,eAAc;AAC7C,SAASC,WAAW,QAAQ,uBAAsB;AAClD,SAASC,WAAW,QAAQ,8BAA6B;AACzD,SACEC,KAAK,EACLC,YAAY,EACZC,MAAM,EACNC,IAAI,EACJC,KAAK,EACLC,KAAK,EACLC,SAAS,EACTC,UAAU,QACL,gBAAe;AACtB,SAASC,IAAI,EAAEC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAC/E,SAASC,qBAAqB,QAAQ,uBAAmB;AACzD,SACEC,+BAA+B,EAC/BC,oBAAoB,EACpBC,sBAAsB,EACtBC,uBAAuB,EACvBC,uBAAuB,EACvBC,2BAA2B,EAC3BC,kCAAkC,QAG7B,kCAA8B;AAErC,SAASC,+BAA+B,QAAQ,8BAA0B;AAC1E,SAASC,eAAe,EAAEC,mBAAmB,QAAQ,mBAAe;AACpE,SAASC,kBAAkB,EAAEC,cAAc,QAAQ,wBAAoB;AACvE,SAASC,6BAA6B,QAAQ,6BAAyB;AAOvE,uEAAuE,GACvE,OAAO,MAAMC,+BACX,qGACA,8EAA6E;AAE/E,MAAMC,QAAoC;IACxCC,WAAW;IACXC,WAAW;IACXC,eAAe;IACfC,kBAAkB;QAAC;KAAK;AAC1B;AAIA;;;;CAIC,GACD,MAAMC,QAGF;IACFC,UAAU;QACRC,QAAQ;YAAC;YAAa;YAAa;SAAgB;QACnDC,OAAO;IACT;IACAC,WAAW;QACTF,QAAQ;YAAC;SAAmB;QAC5BC,OAAO;IACT;AACF;AAEA,SAASE,SACPC,QAAoC,EACpCC,IAAuB;IAEvB,OAAOC,OAAOC,WAAW,CACvBT,KAAK,CAACO,KAAK,CAACL,MAAM,CAACQ,GAAG,CAAC,CAACC,QAAU;YAACA;YAAOL,QAAQ,CAACK,MAAM;SAAC;AAE9D;AAEA,SAASC,UACPL,IAAuB,EACvBM,CAA6B,EAC7BC,CAA6B;IAE7B,OAAOjC,gCAAgC,aAAKiC,GAAMT,SAASQ,GAAGN,QAASO;AACzE;AAEA,MAAMC,cAAc,CAACC,SAA0CA,OAAOC,IAAI;AAC1E,MAAMC,cAAc,CAClBF,QACAG,QACYH,OAAOI,IAAI,KAAKD,MAAMC,IAAI;AAExC;;;;;;;;;;;;;;CAcC,GACD,MAAMC,yCAA2B/C,KAAK,SAAS+C,yBAAyBC,KAKvE;IACC,MAAM,EAAEC,QAAQ,EAAE,GAAGD;IACrB,qBACE,KAACvD;QACCyD,QAAQ;QACRC,SAASH,MAAMG,OAAO;QACtBN,OAAOG,MAAMH,KAAK;QAClBO,gBAAgBX;QAChBY,sBAAsBT;QACtBK,UAAU,CAACK,QAAQC,OAASN,SAASM;QACrCC,aAAa,CAACX,OAAOY,eACnBZ,MAAMT,GAAG,CAAC,CAACM,QAAQgB;gBACjB,MAAyBD,gBAAAA,aAAa;oBAAEC;gBAAM,IAAxC,EAAEC,GAAG,EAAW,GAAGF,eAATG,wCAASH;;;gBACzB,qBAAO,KAAC9D;oBAAekE,MAAK;oBAAQC,OAAOpB,OAAOC,IAAI;mBAAMiB,OAA1CD;YACpB;QAEFI,aAAa,CAACC,uBACZ,KAAClE,wBACKkE;gBACJF,OAAM;gBACNG,OAAOC,QAAQlB,MAAMmB,KAAK;gBAC1BC,YAAYpB,MAAMmB,KAAK;;;AAKjC;AAEA,0EAA0E,GAC1E,MAAME,8BAAgBrE,KAAKF;AAC3B,+EAA+E,GAC/E,MAAMwE,4CAA8BtE,KAAKc;AAIzC,MAAMyD,wBAAwB;IAC5BC,WAAW;QAAEC,WAAW9D,0BAA0B;IAAG;AACvD;AACA,MAAM+D,wBAAwB;IAC5BF,WAAW;QAAEC,WAAW/D,0BAA0B;IAAG;AACvD;AACA,MAAMiE,4BAA4B;IAChCH,WAAW;QAAEC,WAAW7D,8BAA8B;IAAG;AAC3D;AACA,MAAMgE,eAAe;IAAEC,GAAG;AAAI;AAC9B,MAAMC,cAAc;IAAEC,YAAY;IAAYC,WAAW;AAAa;AAWtE,iFAAiF,GACjF,MAAMC,sCAAwBjF,KAAK,SAASiF,sBAC1CjC,KAAwD;IAExD,MAAM,EAAEf,IAAI,EAAEiD,KAAK,EAAEC,MAAM,EAAEC,SAAS,EAAEC,MAAM,EAAE,GAAGrC;IACnD,qBACE,MAACnD;QAAMyF,WAAU;QAAMC,SAAS;;0BAC9B,KAAC7F;gBACCmE,MAAK;gBACL2B,SAAQ;gBACRC,UAAU,CAACP,SAASC,WAAW;gBAC/BO,SAAS,IAAMN,UAAUnD;0BAC1B;;0BAGD,KAACvC;gBACCmE,MAAK;gBACL2B,SAAQ;gBACRC,UAAU,CAACP,SAASC,WAAW,QAAQnC,MAAM2C,OAAO;gBACpDD,SAAS,IAAML,OAAOpD;0BAErBkD,WAAWlD,OAAO,YAAY;;;;AAIvC;AAEA;;;;CAIC,GACD,MAAM2D,uCAAyB5F,KAAK,SAAS4F,uBAC3C5C,KAYC;QAkCSA,uBAYAA,uBAWUA;IAvDpB,MAAM,EAAE6C,MAAM,EAAE,GAAG7C;IACnB,qBACE,KAAC1D;QACCwG,QAAO;QACPC,MAAM1G,eAAe,aAAa;YAAE2G,QAAQ;QAAuB;QACnEC,aAAa;YACXC,sBACE,KAACjB;gBACChD,MAAK;gBACLiD,OAAOlC,MAAMkC,KAAK;gBAClBS,SAAS3C,MAAM2C,OAAO;gBACtBR,QAAQnC,MAAMmC,MAAM;gBACpBC,WAAWpC,MAAMoC,SAAS;gBAC1BC,QAAQrC,MAAMqC,MAAM;;QAG1B;QACAc,cAAc;QACdC,cAAc;kBAEd,cAAA,MAACvG;YAAM0F,SAAS;;8BACd,KAACxF;oBAAWyF,SAAQ;oBAAQa,OAAM;8BAC/B,0FACC,qFACA;;8BAEJ,KAAChC;oBACCP,OAAM;oBACNjB,OAAOG,MAAM1B,SAAS;oBACtB2B,UAAUD,MAAMsD,WAAW;oBAC3BrC,OAAOC,QAAQlB,MAAMuD,cAAc;oBACnCnC,UAAU,GACRpB,wBAAAA,MAAMuD,cAAc,YAApBvD,wBACA;oBAEFwD,WAAWjC;oBACXkC,SAAS;;8BAEX,KAACpC;oBACCP,OAAM;oBACNjB,OAAOG,MAAMzB,SAAS;oBACtB0B,UAAUD,MAAM0D,WAAW;oBAC3BzC,OAAOC,QAAQlB,MAAM2D,cAAc;oBACnCvC,UAAU,GACRpB,wBAAAA,MAAM2D,cAAc,YAApB3D,wBACA;oBAEFwD,WAAW9B;oBACX+B,SAAS;;8BAEX,KAACpC;oBACCP,OAAM;oBACNjB,OAAOG,MAAMxB,aAAa;oBAC1ByB,UAAUD,MAAM4D,eAAe;oBAC/B3C,OAAOC,QAAQlB,MAAM6D,kBAAkB;oBACvCzC,UAAU,GAAEpB,4BAAAA,MAAM6D,kBAAkB,YAAxB7D,4BAA4B5B;oBACxCoF,WAAW7B;oBACXmC,SAAS;oBACTC,SAAS;oBACTN,SAAS;;gBAEVZ,uBACC,MAAChG;oBAAM0F,SAAS;;sCACd,KAACxF;4BAAWyF,SAAQ;sCAAY;;sCAChC,KAAC5F;4BAAM4F,SAAQ;4BAAWwB,IAAIpC;sCAC5B,cAAA,KAAC7E;gCACCyF,SAAQ;gCACRwB,IAAIlC;gCACJmC,eAAY;0CAEXpB;;;;mCAKP,KAACrG;oBAAM0H,UAAS;8BACb,8FACC;;;;;AAMd;AAEA,iDAAiD,GACjD,MAAMC,wCAA0BnH,KAAK,SAASmH,wBAC5CnE,KAKC;IAED,qBACE,KAAC1D;QACCwG,QAAO;QACPC,MAAM1G,eAAe,aAAa;YAAE2G,QAAQ;QAAqB;QACjEC,aAAa;YACXC,sBACE,KAACjB;gBACChD,MAAK;gBACLiD,OAAOlC,MAAMkC,KAAK;gBAClBS,SAAS3C,MAAM2C,OAAO;gBACtBR,QAAQnC,MAAMmC,MAAM;gBACpBC,WAAWpC,MAAMoC,SAAS;gBAC1BC,QAAQrC,MAAMqC,MAAM;;QAG1B;QACAc,cAAc;QACdC,cAAc;kBAEd,cAAA,MAACvG;YAAM0F,SAAS;;8BACd,KAACxF;oBAAWyF,SAAQ;oBAAQa,OAAM;8BAC/B,+FACC,wGACA,gGACA,gGACA;;8BAEJ,KAACtD;oBACCI,SAASH,MAAMG,OAAO;oBACtBN,OAAOG,MAAMH,KAAK;oBAClBsB,OAAOnB,MAAMmB,KAAK;oBAClBlB,UAAUD,MAAMC,QAAQ;;;;;AAKlC;AAEA;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,SAASmE,0BACdpE,KAAqC;IAErC,MAAM,EAAEqE,KAAK,EAAE,GAAGrE;IAClB,MAAMsE,MAAMpG,eAAemG;IAC3B,MAAME,SAASpG,8BAA8BmG,KAAKD;IAClD,MAAM,EAAEG,eAAe,EAAE,GAAGjI;IAC5B,MAAM,CAACkI,MAAMC,QAAQ,GAAGrH,SAAqCgB;IAC7D,MAAM,CAAC8D,QAAQwC,UAAU,GAAGtH,SAAmC;IAC/D,MAAM,CAACuH,cAAcC,gBAAgB,GAAGxH,SAEtC,CAAC;IACH,MAAMyB,YAAY3B,QAAQ,IAAMM,0BAA0B,EAAE;IAE5D,MAAMqH,SAASP,OAAOvF,QAAQ;IAC9B,MAAM+F,iBAAiB3H,OAA0C;IACjEF,UAAU;QACR,IAAI,CAAC4H,QAAQ;QACb,MAAME,SAASD,eAAeE,OAAO;QACrCF,eAAeE,OAAO,GAAGH;QACzB,sEAAsE;QACtE,yEAAyE;QACzEJ,QAAQ,CAACO;YACP,IAAI,CAACD,QAAQ,OAAO,aAAKF;YACzB,IAAIvE,OAAmC,aAAKuE;YAC5C,KAAK,MAAM7F,QAAQC,OAAOgG,IAAI,CAACxG,OAA+B;gBAC5D,IAAI,CAACY,UAAUL,MAAMgG,SAASD,SAAS;oBACrCzE,OAAO,aAAKA,MAASxB,SAASkG,SAAShG;gBACzC;YACF;YACA,OAAOsB;QACT;IACF,GAAG;QAACuE;KAAO;IAEX,MAAM,EAAE9F,UAAUmG,UAAU,EAAEC,MAAM,EAAE,GACpCvH,mCAAmC4G;IACrC,MAAMY,aAAa,CAAChG;YAClBuF;YACAQ;gBADAR,sBAAAA,YAAY,CAACvF,MAAM,YAAnBuF,uBACAQ,eAAAA,OAAOE,IAAI,CAAC,CAACnE,QAAUA,MAAM9B,KAAK,KAAKA,2BAAvC+F,aAA+CG,OAAO;;IACxD,2EAA2E,GAC3E,MAAMC,YAAY,CAACvG,OACjBpB,mCACEiH,SAAS,aAAKA,QAAW/F,SAAS0F,MAAMxF,SAAUwF;IAEtD,MAAMgB,YAAY,CAACxG,OACjB6F,SACI,CAACvH,gCAAgCuH,QAAQU,UAAUvG,MAAMD,QAAQ,IACjE;IACN,MAAM0G,cAAc,CAACzG,OACnBuG,UAAUvG,MAAMmG,MAAM,CAACO,IAAI,CAAC,CAACxE,QAC3B,AAACzC,KAAK,CAACO,KAAK,CAACL,MAAM,CAAuBgH,QAAQ,CAACzE,MAAM9B,KAAK;IAElE,MAAMwD,SAASvF,sBAAsB6H;IACrC,MAAMU,MAAM5I,YACV,CAACoC,OAAyCQ;QACxCgF,gBAAgB,CAACiB;YACf,MAAMvF,OAAO,aAAKuF;YAClB,OAAOvF,IAAI,CAAClB,MAAiC;YAC7C,OAAOkB;QACT;QACAmE,QAAQ,CAACoB,WAAc,aAAKA;gBAAU,CAACzG,MAAM,EAAEQ;;IACjD,GACA,EAAE;IAEJ,MAAMkG,eAAe9I,YACnB,CAAC+I,QAAuBH,IAAI,aAAaG,MAAMC,MAAM,CAACpG,KAAK,GAC3D;QAACgG;KAAI;IAEP,MAAMK,eAAejJ,YACnB,CAAC+I,QAAuBH,IAAI,aAAaG,MAAMC,MAAM,CAACpG,KAAK,GAC3D;QAACgG;KAAI;IAEP,MAAMM,mBAAmBlJ,YACvB,CAAC+I,QAAuBH,IAAI,iBAAiBG,MAAMC,MAAM,CAACpG,KAAK,GAC/D;QAACgG;KAAI;IAGP,MAAMO,cAAc,CAACnH,OACnB4F,gBAAgB,CAACiB;YACf,MAAMvF,OAAO,aAAKuF;YAClB,KAAK,MAAMzG,SAASX,KAAK,CAACO,KAAK,CAACL,MAAM,CAAE;gBACtC,OAAO2B,IAAI,CAAClB,MAAiC;YAC/C;YACA,OAAOkB;QACT;IAEF,sEAAsE;IACtE,6DAA6D;IAC7D,MAAM8F,eAAepJ,YAAY,CAACsD;QAChCsE,gBAAgB,CAACiB;YACf,MAAMQ,UAAU,aAAKR;YACrB,KAAK,MAAMzG,SAASX,MAAMI,SAAS,CAACF,MAAM,CAAE;gBAC1C,OAAO0H,OAAO,CAACjH,MAAiC;YAClD;YACA,OAAOiH;QACT;QACA5B,QAAQ,CAACoB,WAAc,aAClBA;gBACHrH,kBAAkB8B,KAAKnB,GAAG,CAAC,CAACM,SAAWA,OAAOI,IAAI;;IAEtD,GAAG,EAAE;IACL,MAAMrB,mBAAmBgG,KAAKhG,gBAAgB;IAC9C,MAAM8H,oBAAoBpJ,QACxB,IACEsB,iBAAiBW,GAAG,CAClB,CAACU;gBACChB;oBAAAA,kBAAAA,UAAUwG,IAAI,CAAC,CAAC5F,SAAWA,OAAOI,IAAI,KAAKA,iBAA3ChB,kBAAoD;gBAClDgB;gBACAH,MAAMnC,qBAAqBsC;YAC7B;YAEN;QAACrB;QAAkBK;KAAU;IAG/B,MAAM0H,UAAU,CAACvH;QACfmH,YAAYnH;QACZ,IAAI6F,QACFJ,QAAQ,CAACoB,WAAc,aAAKA,UAAa/G,SAAS+F,QAAQ7F;IAC9D;IAEA,MAAMwH,OAAO,OAAOxH;QAClB0F,UAAU1F;QACV,IAAI;YACF,MAAMyH,SAAS,MAAMpC,IAAIqC,YAAY,CAACnB,UAAUvG,MAAMD,QAAQ;YAC9DoH,YAAYnH;YACZyF,QAAQ,CAACoB,WAAc,aAClBA,UACA/G,SAAS2H,OAAO1H,QAAQ,EAAEC;YAE/BsF,OAAOqC,MAAM;YACbpC,gBAAgBkC,OAAOG,OAAO,GAAGnI,KAAK,CAACO,KAAK,CAACJ,KAAK,GAAG,oBAAoB;gBACvE2D,SAASkE,OAAOG,OAAO,GAAG,YAAY;YACxC;QACF,EAAE,OAAO5F,OAAO;YACd,IAAIA,iBAAiBhD,sBAAsBgD,MAAMmE,MAAM,CAAC0B,MAAM,EAAE;gBAC9DjC,gBAAgB,CAACiB,WAAc,aAC1BA,UACA5G,OAAOC,WAAW,CACnB8B,MAAMmE,MAAM,CACT2B,MAAM,CACL,CACE5F,QAIG,WAAWA,OAEjB/B,GAAG,CAAC,CAAC+B,QAAU;4BAACA,MAAM9B,KAAK;4BAAE8B,MAAMoE,OAAO;yBAAC;YAGpD;YACAf,gBAAgB,AAACvD,MAAgBsE,OAAO,EAAE;gBACxC/C,SAAS;gBACTwE,gBAAgB;YAClB;QACF,SAAU;YACRrC,UAAU;QACZ;IACF;IAEA,sEAAsE;IACtE,uEAAuE;IACvE,MAAMsC,QAAQ7J,OAAO;QAAEoJ;QAASC;IAAK;IACrCQ,MAAMhC,OAAO,GAAG;QAAEuB;QAASC;IAAK;IAChC,MAAMrE,YAAYnF,YAChB,CAACgC,OAA4BgI,MAAMhC,OAAO,CAACuB,OAAO,CAACvH,OACnD,EAAE;IAEJ,MAAMoD,SAASpF,YACb,CAACgC,OAA4B,KAAKgI,MAAMhC,OAAO,CAACwB,IAAI,CAACxH,OACrD,EAAE;IAGJ,IAAIsF,OAAO2C,MAAM,KAAK,aAAc,CAAC7C,SAAS,CAACS,QAAS;QACtD,qBAAO,KAAC/G;YAAgB+C,OAAM;;IAChC;IACA,IAAIyD,OAAO2C,MAAM,KAAK,WAAW3C,OAAO2C,MAAM,KAAK,WAAW;QAC5D,qBACE,KAAClJ;YACCkJ,QAAQ3C,OAAO2C,MAAM;YACrBC,MAAK;YACL5B,SAAShB,OAAOgB,OAAO;YACvB6B,SAAS7C,OAAOqC,MAAM;;IAG5B;IAEA,qBACE,MAAC/J;QAAM0F,SAAS;;0BACd,KAACK;gBACCtE,WAAWmG,KAAKnG,SAAS;gBACzBC,WAAWkG,KAAKlG,SAAS;gBACzBC,eAAeiG,KAAKjG,aAAa;gBACjC+E,gBAAgB8B,WAAW;gBAC3B1B,gBAAgB0B,WAAW;gBAC3BxB,oBAAoBwB,WAAW;gBAC/BxC,QAAQA,OAAOA,MAAM;gBACrBS,aAAayC;gBACbrC,aAAawC;gBACbtC,iBAAiBuC;gBACjBjE,OAAOuD,UAAU;gBACjB9C,SAAS+C,YAAY;gBACrBvD,QAAQA;gBACRC,WAAWA;gBACXC,QAAQA;;0BAGV,KAAC8B;gBACChE,SAASrB;gBACTe,OAAO0G;gBACPpF,OAAOkE,WAAW;gBAClBpF,UAAUoG;gBACVnE,OAAOuD,UAAU;gBACjB9C,SAAS+C,YAAY;gBACrBvD,QAAQA;gBACRC,WAAWA;gBACXC,QAAQA;;0BAIV,KAACf;gBAA4B+C,OAAOA;;;;AAG1C;AACAD,0BAA0BiD,WAAW,GAAG;AAExC,eAAejD,0BAAyB"}
|