@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.
Files changed (59) hide show
  1. package/package.json +15 -15
  2. package/src/lib/components/compliance-section.d.ts +5 -0
  3. package/src/lib/components/compliance-section.js +233 -149
  4. package/src/lib/components/compliance-section.js.map +1 -1
  5. package/src/lib/components/enrollment-detail.js +2 -2
  6. package/src/lib/components/enrollment-detail.js.map +1 -1
  7. package/src/lib/components/sequence-editor.d.ts +13 -0
  8. package/src/lib/components/sequence-editor.js +508 -302
  9. package/src/lib/components/sequence-editor.js.map +1 -1
  10. package/src/lib/components/sequence-preview.js +25 -10
  11. package/src/lib/components/sequence-preview.js.map +1 -1
  12. package/src/lib/components/sequence-step-card.d.ts +10 -0
  13. package/src/lib/components/sequence-step-card.js +320 -202
  14. package/src/lib/components/sequence-step-card.js.map +1 -1
  15. package/src/lib/engine/compose.js +2 -2
  16. package/src/lib/engine/compose.js.map +1 -1
  17. package/src/lib/engine/delivery-status.js +1 -1
  18. package/src/lib/engine/delivery-status.js.map +1 -1
  19. package/src/lib/engine/enrollment-state.js +2 -2
  20. package/src/lib/engine/enrollment-state.js.map +1 -1
  21. package/src/lib/engine/gates.d.ts +2 -2
  22. package/src/lib/engine/gates.js.map +1 -1
  23. package/src/lib/engine/opt-out-intent.js +1 -1
  24. package/src/lib/engine/opt-out-intent.js.map +1 -1
  25. package/src/lib/engine/sequence-validation.js +2 -2
  26. package/src/lib/engine/sequence-validation.js.map +1 -1
  27. package/src/lib/engine/thread-classification.js +3 -3
  28. package/src/lib/engine/thread-classification.js.map +1 -1
  29. package/src/lib/enrollment/gate-lookups.js +5 -4
  30. package/src/lib/enrollment/gate-lookups.js.map +1 -1
  31. package/src/lib/model/outreach.types.d.ts +1 -1
  32. package/src/lib/model/outreach.types.js.map +1 -1
  33. package/src/lib/model/sales-topic.d.ts +27 -0
  34. package/src/lib/model/sales-topic.js +27 -0
  35. package/src/lib/model/sales-topic.js.map +1 -0
  36. package/src/lib/model/sequence-draft.d.ts +1 -1
  37. package/src/lib/model/sequence-draft.js +2 -2
  38. package/src/lib/model/sequence-draft.js.map +1 -1
  39. package/src/lib/model/stored-records.js +2 -2
  40. package/src/lib/model/stored-records.js.map +1 -1
  41. package/src/lib/routes/curate-routes.js +7 -7
  42. package/src/lib/routes/curate-routes.js.map +1 -1
  43. package/src/lib/routes/enroll-routes.js +10 -13
  44. package/src/lib/routes/enroll-routes.js.map +1 -1
  45. package/src/lib/routes/sequence-routes.js +9 -9
  46. package/src/lib/routes/sequence-routes.js.map +1 -1
  47. package/src/lib/runtime/campaign-credit.js +2 -2
  48. package/src/lib/runtime/campaign-credit.js.map +1 -1
  49. package/src/lib/runtime/campaign-detach.js +3 -2
  50. package/src/lib/runtime/campaign-detach.js.map +1 -1
  51. package/src/lib/runtime/lead-records.d.ts +6 -1
  52. package/src/lib/runtime/lead-records.js +25 -6
  53. package/src/lib/runtime/lead-records.js.map +1 -1
  54. package/src/lib/runtime/platform-runtime-deps.js +3 -3
  55. package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
  56. package/src/lib/runtime/send-job.js +20 -4
  57. package/src/lib/runtime/send-job.js.map +1 -1
  58. package/src/lib/runtime/sync-job.js +3 -3
  59. 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.219",
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.219",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.219",
30
- "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.219",
31
- "@aglyn/shared-ui-jsx": "1.0.0-beta.219",
32
- "@aglyn/shared-ui-next": "1.0.0-beta.219",
33
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.219",
34
- "@aglyn/shared-util-email": "1.0.0-beta.219",
35
- "@aglyn/shared-util-http": "1.0.0-beta.219",
36
- "@aglyn/shared-util-timestamp": "1.0.0-beta.219",
37
- "@aglyn/shared-util-tools": "1.0.0-beta.219",
38
- "@aglyn/tenant-data-admin": "1.0.0-beta.219",
39
- "@aglyn/tenant-feature-instance": "1.0.0-beta.219",
40
- "@aglyn/tenant-runtime": "1.0.0-beta.219",
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)=>(value)=>{
165
- setServerIssues((previous)=>{
166
- const next = _extends({}, previous);
167
- delete next[field];
168
- return next;
169
- });
170
- setForm((previous)=>_extends({}, previous, {
171
- [field]: value
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(CardDisplay, {
275
- header: "Sender identity",
276
- help: pluginDocsHelp('sequences', {
277
- anchor: '#compliance-settings'
278
- }),
279
- HeaderProps: {
280
- action: cardActions('identity')
281
- },
282
- contentGutterX: true,
283
- contentGutterY: true,
284
- children: /*#__PURE__*/ _jsxs(Stack, {
285
- spacing: 2,
286
- children: [
287
- /*#__PURE__*/ _jsx(Typography, {
288
- variant: "body2",
289
- color: "text.secondary",
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(CardDisplay, {
364
- header: "Allowed countries",
365
- help: pluginDocsHelp('sequences', {
366
- anchor: '#allowed-countries'
367
- }),
368
- HeaderProps: {
369
- action: cardActions('countries')
370
- },
371
- contentGutterX: true,
372
- contentGutterY: true,
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(OutreachDoNotContactDomainsCard, {
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":"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"}