@aglyn/plugins-outreach 1.0.0-beta.218 → 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 +302 -180
- package/src/lib/components/compliance-section.js.map +1 -1
- package/src/lib/components/enroll-dialog.d.ts +12 -2
- package/src/lib/components/enroll-dialog.js +72 -18
- package/src/lib/components/enroll-dialog.js.map +1 -1
- package/src/lib/components/enrollment-detail.d.ts +5 -1
- package/src/lib/components/enrollment-detail.js +14 -8
- package/src/lib/components/enrollment-detail.js.map +1 -1
- package/src/lib/components/outreach-console-sections.d.ts +1 -1
- package/src/lib/components/outreach-console-sections.js +1 -1
- package/src/lib/components/outreach-console-sections.js.map +1 -1
- package/src/lib/components/sequence-editor.d.ts +13 -0
- package/src/lib/components/sequence-editor.js +586 -335
- 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/components/use-outreach-api.d.ts +2 -1
- package/src/lib/components/use-outreach-api.js +5 -3
- package/src/lib/components/use-outreach-api.js.map +1 -1
- package/src/lib/engine/compose.d.ts +1 -1
- package/src/lib/engine/compose.js +10 -6
- 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-activity.d.ts +9 -1
- package/src/lib/engine/enrollment-activity.js +15 -2
- package/src/lib/engine/enrollment-activity.js.map +1 -1
- package/src/lib/engine/enrollment-state.d.ts +33 -2
- package/src/lib/engine/enrollment-state.js +68 -9
- 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/sending-capacity.d.ts +1 -1
- package/src/lib/engine/sending-capacity.js +1 -1
- package/src/lib/engine/sending-capacity.js.map +1 -1
- package/src/lib/engine/sequence-validation.d.ts +20 -3
- package/src/lib/engine/sequence-validation.js +32 -6
- 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/enroll-people.js +6 -4
- package/src/lib/enrollment/enroll-people.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/enrollment/step-overrides.d.ts +4 -2
- package/src/lib/enrollment/step-overrides.js +11 -4
- package/src/lib/enrollment/step-overrides.js.map +1 -1
- package/src/lib/mailboxes/mailbox-api.d.ts +5 -1
- package/src/lib/mailboxes/mailbox-api.js.map +1 -1
- package/src/lib/mailboxes/mailbox-routes.d.ts +5 -2
- package/src/lib/mailboxes/mailbox-routes.js +10 -7
- package/src/lib/mailboxes/mailbox-routes.js.map +1 -1
- package/src/lib/mailboxes/mailbox-settings.d.ts +2 -2
- package/src/lib/mailboxes/mailbox-settings.js +1 -1
- package/src/lib/mailboxes/mailbox-settings.js.map +1 -1
- package/src/lib/model/enrollment-timeline.d.ts +1 -1
- package/src/lib/model/enrollment-timeline.js +22 -3
- package/src/lib/model/enrollment-timeline.js.map +1 -1
- package/src/lib/model/outreach-api.d.ts +19 -1
- package/src/lib/model/outreach-api.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +26 -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/step-preview.d.ts +2 -0
- package/src/lib/model/step-preview.js +11 -7
- package/src/lib/model/step-preview.js.map +1 -1
- package/src/lib/model/stored-records.js +28 -2
- package/src/lib/model/stored-records.js.map +1 -1
- package/src/lib/plugin.js +1 -1
- package/src/lib/plugin.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.d.ts +6 -0
- package/src/lib/routes/enroll-routes.js +31 -19
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/preview-routes.js +14 -5
- package/src/lib/routes/preview-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/routes/step-test-routes.js +3 -3
- package/src/lib/routes/step-test-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/src/lib/testing/stand-in-crm-record-routes.d.ts +25 -0
- package/src/lib/testing/stand-in-crm-record-routes.js +46 -0
- package/src/lib/testing/stand-in-crm-record-routes.js.map +1 -0
- package/src/lib/transport/gmail-errors.d.ts +27 -0
- package/src/lib/transport/gmail-errors.js +56 -0
- package/src/lib/transport/gmail-errors.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 {
|
|
@@ -21,7 +21,7 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
|
21
21
|
import { CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
22
22
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
23
23
|
import { Alert, Autocomplete, Button, Chip, Paper, Stack, TextField, Typography } from "@mui/material";
|
|
24
|
-
import { useEffect, useMemo, useRef, useState } from "react";
|
|
24
|
+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
25
25
|
import { composeOutreachFooter } from "../engine/compose.js";
|
|
26
26
|
import { outreachComplianceSettingsEqual, outreachCountryLabel, outreachCountryOptions, OUTREACH_BRAND_NAME_MAX, OUTREACH_LEGAL_NAME_MAX, OUTREACH_POSTAL_ADDRESS_MAX, validateOutreachComplianceSettings } from "../model/compliance-settings.js";
|
|
27
27
|
import { OutreachDoNotContactDomainsCard } from "./do-not-contact-domains.js";
|
|
@@ -66,6 +66,219 @@ function pickCard(settings, card) {
|
|
|
66
66
|
function cardEqual(card, a, b) {
|
|
67
67
|
return outreachComplianceSettingsEqual(_extends({}, b, pickCard(a, card)), b);
|
|
68
68
|
}
|
|
69
|
+
const countryName = (option)=>option.name;
|
|
70
|
+
const sameCountry = (option, value)=>option.code === value.code;
|
|
71
|
+
/**
|
|
72
|
+
* The allowed countries, as chips — MEMOIZED, and every prop it takes is
|
|
73
|
+
* stable while the countries are (AGL-3423).
|
|
74
|
+
*
|
|
75
|
+
* A multiple Autocomplete hands its input a new chip array as
|
|
76
|
+
* `startAdornment` on every render, and MUI's `InputBase` copies that into
|
|
77
|
+
* its `FormControl` from a passive effect: a state update left pending after
|
|
78
|
+
* every commit the field takes part in. React 19 counts each such commit as a
|
|
79
|
+
* nested update, and keystrokes delivered back to back commit one after
|
|
80
|
+
* another with nothing to clear the count, so the fifty-first throws
|
|
81
|
+
* "Maximum update depth exceeded" (#185) — the sequence editor's countries
|
|
82
|
+
* field did exactly that. The legal name and address share one form with
|
|
83
|
+
* this list, so each keystroke in them re-rendered it; kept out of those
|
|
84
|
+
* renders, it commits only when it changes.
|
|
85
|
+
*/ const ComplianceCountriesField = /*#__PURE__*/ memo(function ComplianceCountriesField(props) {
|
|
86
|
+
const { onChange } = props;
|
|
87
|
+
return /*#__PURE__*/ _jsx(Autocomplete, {
|
|
88
|
+
multiple: true,
|
|
89
|
+
options: props.options,
|
|
90
|
+
value: props.value,
|
|
91
|
+
getOptionLabel: countryName,
|
|
92
|
+
isOptionEqualToValue: sameCountry,
|
|
93
|
+
onChange: (_event, next)=>onChange(next),
|
|
94
|
+
renderValue: (value, getItemProps)=>value.map((option, index)=>{
|
|
95
|
+
const _getItemProps = getItemProps({
|
|
96
|
+
index
|
|
97
|
+
}), { key } = _getItemProps, item = _object_without_properties_loose(_getItemProps, [
|
|
98
|
+
"key"
|
|
99
|
+
]);
|
|
100
|
+
return /*#__PURE__*/ _jsx(Chip, _extends({
|
|
101
|
+
size: "small",
|
|
102
|
+
label: option.name
|
|
103
|
+
}, item), key);
|
|
104
|
+
}),
|
|
105
|
+
renderInput: (params)=>/*#__PURE__*/ _jsx(TextField, _extends({}, params, {
|
|
106
|
+
label: "Countries",
|
|
107
|
+
error: Boolean(props.issue),
|
|
108
|
+
helperText: props.issue
|
|
109
|
+
}))
|
|
110
|
+
});
|
|
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
|
+
});
|
|
69
282
|
/**
|
|
70
283
|
* Sequences → Compliance (AGL-2980): who every email says sent it, the
|
|
71
284
|
* countries a sequence may send to at all, and — below the settings, with
|
|
@@ -77,8 +290,12 @@ function cardEqual(card, a, b) {
|
|
|
77
290
|
* `composeOutreachFooter`, so the page shows the lines every email will end
|
|
78
291
|
* with — or, while the legal name or the address is empty, the sentence
|
|
79
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.
|
|
80
298
|
*/ export function OutreachComplianceSection(props) {
|
|
81
|
-
var _fieldIssue, _fieldIssue1, _fieldIssue2;
|
|
82
299
|
const { orgId } = props;
|
|
83
300
|
const api = useOutreachApi(orgId);
|
|
84
301
|
const loaded = useOutreachComplianceSettings(api, orgId);
|
|
@@ -118,16 +335,25 @@ function cardEqual(card, a, b) {
|
|
|
118
335
|
const cardDirty = (card)=>stored ? !outreachComplianceSettingsEqual(stored, candidate(card).settings) : false;
|
|
119
336
|
const cardBlocked = (card)=>candidate(card).issues.some((issue)=>CARDS[card].fields.includes(issue.field));
|
|
120
337
|
const footer = composeOutreachFooter(normalized);
|
|
121
|
-
const set = (field
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
+
]);
|
|
131
357
|
const clearIssues = (card)=>setServerIssues((previous)=>{
|
|
132
358
|
const next = _extends({}, previous);
|
|
133
359
|
for (const field of CARDS[card].fields){
|
|
@@ -135,6 +361,31 @@ function cardEqual(card, a, b) {
|
|
|
135
361
|
}
|
|
136
362
|
return next;
|
|
137
363
|
});
|
|
364
|
+
// Stable, for the memoized countries field: an edit clears the card's
|
|
365
|
+
// issues and replaces the list in whatever form it lands on.
|
|
366
|
+
const setCountries = useCallback((next)=>{
|
|
367
|
+
setServerIssues((previous)=>{
|
|
368
|
+
const cleared = _extends({}, previous);
|
|
369
|
+
for (const field of CARDS.countries.fields){
|
|
370
|
+
delete cleared[field];
|
|
371
|
+
}
|
|
372
|
+
return cleared;
|
|
373
|
+
});
|
|
374
|
+
setForm((previous)=>_extends({}, previous, {
|
|
375
|
+
allowedCountries: next.map((option)=>option.code)
|
|
376
|
+
}));
|
|
377
|
+
}, []);
|
|
378
|
+
const allowedCountries = form.allowedCountries;
|
|
379
|
+
const selectedCountries = useMemo(()=>allowedCountries.map((code)=>{
|
|
380
|
+
var _countries_find;
|
|
381
|
+
return (_countries_find = countries.find((option)=>option.code === code)) != null ? _countries_find : {
|
|
382
|
+
code,
|
|
383
|
+
name: outreachCountryLabel(code)
|
|
384
|
+
};
|
|
385
|
+
}), [
|
|
386
|
+
allowedCountries,
|
|
387
|
+
countries
|
|
388
|
+
]);
|
|
138
389
|
const discard = (card)=>{
|
|
139
390
|
clearIssues(card);
|
|
140
391
|
if (stored) setForm((previous)=>_extends({}, previous, pickCard(stored, card)));
|
|
@@ -164,6 +415,18 @@ function cardEqual(card, a, b) {
|
|
|
164
415
|
setSaving(null);
|
|
165
416
|
}
|
|
166
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), []);
|
|
167
430
|
if (loaded.status === 'loading' || !orgId && !stored) {
|
|
168
431
|
return /*#__PURE__*/ _jsx(OutreachLoading, {
|
|
169
432
|
label: "Loading compliance settings…"
|
|
@@ -177,179 +440,38 @@ function cardEqual(card, a, b) {
|
|
|
177
440
|
onRetry: loaded.reload
|
|
178
441
|
});
|
|
179
442
|
}
|
|
180
|
-
const selectedCountries = form.allowedCountries.map((code)=>{
|
|
181
|
-
var _countries_find;
|
|
182
|
-
return (_countries_find = countries.find((option)=>option.code === code)) != null ? _countries_find : {
|
|
183
|
-
code,
|
|
184
|
-
name: outreachCountryLabel(code)
|
|
185
|
-
};
|
|
186
|
-
});
|
|
187
|
-
const cardActions = (card)=>{
|
|
188
|
-
const dirty = cardDirty(card);
|
|
189
|
-
return /*#__PURE__*/ _jsxs(Stack, {
|
|
190
|
-
direction: "row",
|
|
191
|
-
spacing: 1,
|
|
192
|
-
children: [
|
|
193
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
194
|
-
size: "small",
|
|
195
|
-
variant: "text",
|
|
196
|
-
disabled: !dirty || saving !== null,
|
|
197
|
-
onClick: ()=>discard(card),
|
|
198
|
-
children: "Discard changes"
|
|
199
|
-
}),
|
|
200
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
201
|
-
size: "small",
|
|
202
|
-
variant: "contained",
|
|
203
|
-
disabled: !dirty || saving !== null || cardBlocked(card),
|
|
204
|
-
onClick: ()=>void save(card),
|
|
205
|
-
children: saving === card ? 'Saving…' : 'Save'
|
|
206
|
-
})
|
|
207
|
-
]
|
|
208
|
-
});
|
|
209
|
-
};
|
|
210
443
|
return /*#__PURE__*/ _jsxs(Stack, {
|
|
211
444
|
spacing: 2,
|
|
212
445
|
children: [
|
|
213
|
-
/*#__PURE__*/ _jsx(
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
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.'
|
|
230
|
-
}),
|
|
231
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
232
|
-
label: "Legal name",
|
|
233
|
-
value: form.legalName,
|
|
234
|
-
onChange: (event)=>set('legalName')(event.target.value),
|
|
235
|
-
error: Boolean(fieldIssue('legalName')),
|
|
236
|
-
helperText: (_fieldIssue = fieldIssue('legalName')) != null ? _fieldIssue : 'Your organization’s legal name, as the footer prints it.',
|
|
237
|
-
slotProps: {
|
|
238
|
-
htmlInput: {
|
|
239
|
-
maxLength: OUTREACH_LEGAL_NAME_MAX + 20
|
|
240
|
-
}
|
|
241
|
-
},
|
|
242
|
-
fullWidth: true
|
|
243
|
-
}),
|
|
244
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
245
|
-
label: "Brand name",
|
|
246
|
-
value: form.brandName,
|
|
247
|
-
onChange: (event)=>set('brandName')(event.target.value),
|
|
248
|
-
error: Boolean(fieldIssue('brandName')),
|
|
249
|
-
helperText: (_fieldIssue1 = fieldIssue('brandName')) != null ? _fieldIssue1 : 'The name the solicitation sentence uses. Leave empty to use the legal name.',
|
|
250
|
-
slotProps: {
|
|
251
|
-
htmlInput: {
|
|
252
|
-
maxLength: OUTREACH_BRAND_NAME_MAX + 20
|
|
253
|
-
}
|
|
254
|
-
},
|
|
255
|
-
fullWidth: true
|
|
256
|
-
}),
|
|
257
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
258
|
-
label: "Postal address",
|
|
259
|
-
value: form.postalAddress,
|
|
260
|
-
onChange: (event)=>set('postalAddress')(event.target.value),
|
|
261
|
-
error: Boolean(fieldIssue('postalAddress')),
|
|
262
|
-
helperText: (_fieldIssue2 = fieldIssue('postalAddress')) != null ? _fieldIssue2 : OUTREACH_POSTAL_ADDRESS_HELP,
|
|
263
|
-
slotProps: {
|
|
264
|
-
htmlInput: {
|
|
265
|
-
maxLength: OUTREACH_POSTAL_ADDRESS_MAX + 40
|
|
266
|
-
}
|
|
267
|
-
},
|
|
268
|
-
multiline: true,
|
|
269
|
-
minRows: 3,
|
|
270
|
-
fullWidth: true
|
|
271
|
-
}),
|
|
272
|
-
footer.footer ? /*#__PURE__*/ _jsxs(Stack, {
|
|
273
|
-
spacing: 0.5,
|
|
274
|
-
children: [
|
|
275
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
276
|
-
variant: "subtitle2",
|
|
277
|
-
children: "Every email ends with"
|
|
278
|
-
}),
|
|
279
|
-
/*#__PURE__*/ _jsx(Paper, {
|
|
280
|
-
variant: "outlined",
|
|
281
|
-
sx: {
|
|
282
|
-
p: 1.5
|
|
283
|
-
},
|
|
284
|
-
children: /*#__PURE__*/ _jsx(Typography, {
|
|
285
|
-
variant: "body2",
|
|
286
|
-
sx: {
|
|
287
|
-
whiteSpace: 'pre-wrap',
|
|
288
|
-
wordBreak: 'break-word'
|
|
289
|
-
},
|
|
290
|
-
"data-testid": "outreach-footer-preview",
|
|
291
|
-
children: footer.footer
|
|
292
|
-
})
|
|
293
|
-
})
|
|
294
|
-
]
|
|
295
|
-
}) : /*#__PURE__*/ _jsx(Alert, {
|
|
296
|
-
severity: "warning",
|
|
297
|
-
children: 'No sequence can be activated, and no email sent, until you add your organization’s legal ' + 'name and postal address.'
|
|
298
|
-
})
|
|
299
|
-
]
|
|
300
|
-
})
|
|
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
|
|
301
462
|
}),
|
|
302
|
-
/*#__PURE__*/ _jsx(
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
313
|
-
spacing: 2,
|
|
314
|
-
children: [
|
|
315
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
316
|
-
variant: "body2",
|
|
317
|
-
color: "text.secondary",
|
|
318
|
-
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.'
|
|
319
|
-
}),
|
|
320
|
-
/*#__PURE__*/ _jsx(Autocomplete, {
|
|
321
|
-
multiple: true,
|
|
322
|
-
options: countries,
|
|
323
|
-
value: selectedCountries,
|
|
324
|
-
getOptionLabel: (option)=>option.name,
|
|
325
|
-
isOptionEqualToValue: (option, value)=>option.code === value.code,
|
|
326
|
-
onChange: (_event, next)=>{
|
|
327
|
-
clearIssues('countries');
|
|
328
|
-
setForm((previous)=>_extends({}, previous, {
|
|
329
|
-
allowedCountries: next.map((option)=>option.code)
|
|
330
|
-
}));
|
|
331
|
-
},
|
|
332
|
-
renderValue: (value, getItemProps)=>value.map((option, index)=>{
|
|
333
|
-
const _getItemProps = getItemProps({
|
|
334
|
-
index
|
|
335
|
-
}), { key } = _getItemProps, item = _object_without_properties_loose(_getItemProps, [
|
|
336
|
-
"key"
|
|
337
|
-
]);
|
|
338
|
-
return /*#__PURE__*/ _jsx(Chip, _extends({
|
|
339
|
-
size: "small",
|
|
340
|
-
label: option.name
|
|
341
|
-
}, item), key);
|
|
342
|
-
}),
|
|
343
|
-
renderInput: (params)=>/*#__PURE__*/ _jsx(TextField, _extends({}, params, {
|
|
344
|
-
label: "Countries",
|
|
345
|
-
error: Boolean(fieldIssue('allowedCountries')),
|
|
346
|
-
helperText: fieldIssue('allowedCountries')
|
|
347
|
-
}))
|
|
348
|
-
})
|
|
349
|
-
]
|
|
350
|
-
})
|
|
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
|
|
351
473
|
}),
|
|
352
|
-
/*#__PURE__*/ _jsx(
|
|
474
|
+
/*#__PURE__*/ _jsx(MemoDoNotContactDomainsCard, {
|
|
353
475
|
orgId: orgId
|
|
354
476
|
})
|
|
355
477
|
]
|