@aglyn/plugins-outreach 1.0.0-beta.219 → 1.0.0-beta.221
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +15 -15
- package/src/lib/components/compliance-section.d.ts +5 -0
- package/src/lib/components/compliance-section.js +233 -149
- package/src/lib/components/compliance-section.js.map +1 -1
- package/src/lib/components/enrollment-detail.js +2 -2
- package/src/lib/components/enrollment-detail.js.map +1 -1
- package/src/lib/components/sequence-editor.d.ts +13 -0
- package/src/lib/components/sequence-editor.js +508 -302
- package/src/lib/components/sequence-editor.js.map +1 -1
- package/src/lib/components/sequence-preview.js +25 -10
- package/src/lib/components/sequence-preview.js.map +1 -1
- package/src/lib/components/sequence-step-card.d.ts +10 -0
- package/src/lib/components/sequence-step-card.js +320 -202
- package/src/lib/components/sequence-step-card.js.map +1 -1
- package/src/lib/engine/compose.js +2 -2
- package/src/lib/engine/compose.js.map +1 -1
- package/src/lib/engine/delivery-status.js +1 -1
- package/src/lib/engine/delivery-status.js.map +1 -1
- package/src/lib/engine/enrollment-state.js +2 -2
- package/src/lib/engine/enrollment-state.js.map +1 -1
- package/src/lib/engine/gates.d.ts +2 -2
- package/src/lib/engine/gates.js.map +1 -1
- package/src/lib/engine/opt-out-intent.js +1 -1
- package/src/lib/engine/opt-out-intent.js.map +1 -1
- package/src/lib/engine/sequence-validation.js +2 -2
- package/src/lib/engine/sequence-validation.js.map +1 -1
- package/src/lib/engine/thread-classification.js +3 -3
- package/src/lib/engine/thread-classification.js.map +1 -1
- package/src/lib/enrollment/gate-lookups.js +5 -4
- package/src/lib/enrollment/gate-lookups.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +1 -1
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/model/sales-topic.d.ts +27 -0
- package/src/lib/model/sales-topic.js +27 -0
- package/src/lib/model/sales-topic.js.map +1 -0
- package/src/lib/model/sequence-draft.d.ts +1 -1
- package/src/lib/model/sequence-draft.js +2 -2
- package/src/lib/model/sequence-draft.js.map +1 -1
- package/src/lib/model/stored-records.js +2 -2
- package/src/lib/model/stored-records.js.map +1 -1
- package/src/lib/routes/curate-routes.js +7 -7
- package/src/lib/routes/curate-routes.js.map +1 -1
- package/src/lib/routes/enroll-routes.js +10 -13
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/sequence-routes.js +9 -9
- package/src/lib/routes/sequence-routes.js.map +1 -1
- package/src/lib/runtime/campaign-credit.js +2 -2
- package/src/lib/runtime/campaign-credit.js.map +1 -1
- package/src/lib/runtime/campaign-detach.js +3 -2
- package/src/lib/runtime/campaign-detach.js.map +1 -1
- package/src/lib/runtime/lead-records.d.ts +6 -1
- package/src/lib/runtime/lead-records.js +25 -6
- package/src/lib/runtime/lead-records.js.map +1 -1
- package/src/lib/runtime/platform-runtime-deps.js +3 -3
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
- package/src/lib/runtime/send-job.js +20 -4
- package/src/lib/runtime/send-job.js.map +1 -1
- package/src/lib/runtime/sync-job.js +3 -3
- package/src/lib/runtime/sync-job.js.map +1 -1
|
@@ -21,11 +21,11 @@ import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
|
21
21
|
import { consentGroupForHost } from "@aglyn/aglyn/app-utils/consent-groups";
|
|
22
22
|
import { mdiPlus } from "@aglyn/shared-data-mdi";
|
|
23
23
|
import { CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
|
|
24
|
-
import
|
|
24
|
+
import ContainerPicker from "@aglyn/tenant-feature-instance/components/container-picker";
|
|
25
25
|
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
26
|
-
import {
|
|
26
|
+
import { useOrgContainerOptions, useUser } from "@aglyn/tenant-feature-instance";
|
|
27
27
|
import { Alert, Autocomplete, Box, Button, Chip, FormControlLabel, Grid, Menu, MenuItem, Stack, Switch, TextField, Typography } from "@mui/material";
|
|
28
|
-
import { memo, useCallback, useEffect, useMemo, useState } from "react";
|
|
28
|
+
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
29
29
|
import { isInThreadEmailStep, OUTREACH_SEQUENCE_NAME_MAX, validateOutreachSequence } from "../engine/sequence-validation.js";
|
|
30
30
|
import { outreachCountryLabel } from "../model/compliance-settings.js";
|
|
31
31
|
import { OUTREACH_MAX_EMAIL_STEPS, OUTREACH_MAX_STEPS, OUTREACH_TASK_KIND_LABELS, OUTREACH_TASK_KINDS } from "../model/outreach.types.js";
|
|
@@ -91,6 +91,362 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
91
91
|
}
|
|
92
92
|
});
|
|
93
93
|
});
|
|
94
|
+
/**
|
|
95
|
+
* Each step's issues, the SAME record for a step while what it holds reads
|
|
96
|
+
* the same (AGL-3423), so a step a keystroke did not touch keeps its props.
|
|
97
|
+
*/ function useStepIssues(issues, count) {
|
|
98
|
+
const held = useRef([]);
|
|
99
|
+
return useMemo(()=>{
|
|
100
|
+
const found = Array.from({
|
|
101
|
+
length: count
|
|
102
|
+
}, ()=>({}));
|
|
103
|
+
for (const issue of issues){
|
|
104
|
+
const key = stepIssueKey(issue.path);
|
|
105
|
+
const record = key ? found[key.index] : undefined;
|
|
106
|
+
if (key && record && !record[key.field]) record[key.field] = issue.message;
|
|
107
|
+
}
|
|
108
|
+
held.current = found.map((value, index)=>{
|
|
109
|
+
const key = JSON.stringify(value);
|
|
110
|
+
const before = held.current[index];
|
|
111
|
+
return (before == null ? void 0 : before.key) === key ? before : {
|
|
112
|
+
key,
|
|
113
|
+
value
|
|
114
|
+
};
|
|
115
|
+
});
|
|
116
|
+
return held.current.map((entry)=>entry.value);
|
|
117
|
+
}, [
|
|
118
|
+
issues,
|
|
119
|
+
count
|
|
120
|
+
]);
|
|
121
|
+
}
|
|
122
|
+
/** A text field that redraws only when what it shows changes (AGL-3423). */ const MemoTextField = /*#__PURE__*/ memo(TextField);
|
|
123
|
+
/*
|
|
124
|
+
* The sections below are drawn from the one draft, and each redraws only
|
|
125
|
+
* when what it shows changes (AGL-3423).
|
|
126
|
+
*/ const MemoMailboxPicker = /*#__PURE__*/ memo(OutreachSequenceMailboxPicker);
|
|
127
|
+
const MemoContainerPicker = /*#__PURE__*/ memo(ContainerPicker);
|
|
128
|
+
const MemoSendWindowFields = /*#__PURE__*/ memo(OutreachSendWindowFields);
|
|
129
|
+
const MemoSequencePreview = /*#__PURE__*/ memo(OutreachSequencePreview);
|
|
130
|
+
const NAME_SLOT_PROPS = {
|
|
131
|
+
htmlInput: {
|
|
132
|
+
maxLength: OUTREACH_SEQUENCE_NAME_MAX + 20
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
const ADD_STEP_ROW = {
|
|
136
|
+
flexWrap: 'wrap',
|
|
137
|
+
rowGap: 1
|
|
138
|
+
};
|
|
139
|
+
const PREVIEW_COLUMN = {
|
|
140
|
+
position: {
|
|
141
|
+
md: 'sticky'
|
|
142
|
+
},
|
|
143
|
+
top: {
|
|
144
|
+
md: 16
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
const SequenceSiteField = /*#__PURE__*/ memo(function SequenceSiteField(props) {
|
|
148
|
+
const { hosts, value, issue } = props;
|
|
149
|
+
return /*#__PURE__*/ _jsx(TextField, {
|
|
150
|
+
select: true,
|
|
151
|
+
label: "Site",
|
|
152
|
+
value: hosts.some((host)=>host.id === value) ? value : '',
|
|
153
|
+
onChange: props.onChange,
|
|
154
|
+
disabled: props.disabled || !hosts.length,
|
|
155
|
+
error: Boolean(issue),
|
|
156
|
+
helperText: issue != null ? issue : 'The site whose contacts this sequence emails.',
|
|
157
|
+
fullWidth: true,
|
|
158
|
+
children: hosts.map((host)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
159
|
+
value: host.id,
|
|
160
|
+
children: host.name
|
|
161
|
+
}, host.id))
|
|
162
|
+
});
|
|
163
|
+
});
|
|
164
|
+
/** The sequence card's header actions: Cancel, and the save of the whole sequence. */ const SequenceSaveActions = /*#__PURE__*/ memo(function SequenceSaveActions(props) {
|
|
165
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
166
|
+
direction: "row",
|
|
167
|
+
spacing: 1,
|
|
168
|
+
children: [
|
|
169
|
+
props.onCancel ? /*#__PURE__*/ _jsx(Button, {
|
|
170
|
+
size: "small",
|
|
171
|
+
variant: "text",
|
|
172
|
+
onClick: props.onCancel,
|
|
173
|
+
disabled: props.saving,
|
|
174
|
+
children: "Cancel"
|
|
175
|
+
}) : null,
|
|
176
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
177
|
+
size: "small",
|
|
178
|
+
variant: "contained",
|
|
179
|
+
onClick: props.onSave,
|
|
180
|
+
disabled: props.saving,
|
|
181
|
+
children: props.saving ? 'Saving…' : props.saveLabel
|
|
182
|
+
})
|
|
183
|
+
]
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
/** The sequence's own card: its name, site, mailbox and campaigns. */ const SequenceCard = /*#__PURE__*/ memo(function SequenceCard(props) {
|
|
187
|
+
var _props_campaignsIssue;
|
|
188
|
+
const { archived } = props;
|
|
189
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
190
|
+
header: "Sequence",
|
|
191
|
+
help: pluginDocsHelp('sequences', {
|
|
192
|
+
anchor: '#build-a-sequence'
|
|
193
|
+
}),
|
|
194
|
+
// The whole sequence (steps and sending rules included) saves
|
|
195
|
+
// from its first card's header, where the console puts a card's
|
|
196
|
+
// actions (AGL-3333).
|
|
197
|
+
HeaderProps: {
|
|
198
|
+
action: archived ? undefined : /*#__PURE__*/ _jsx(SequenceSaveActions, {
|
|
199
|
+
saving: props.saving,
|
|
200
|
+
saveLabel: props.saveLabel,
|
|
201
|
+
onCancel: props.onCancel,
|
|
202
|
+
onSave: props.onSave
|
|
203
|
+
})
|
|
204
|
+
},
|
|
205
|
+
contentGutterX: true,
|
|
206
|
+
contentGutterY: true,
|
|
207
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
208
|
+
spacing: 2,
|
|
209
|
+
children: [
|
|
210
|
+
/*#__PURE__*/ _jsx(MemoTextField, {
|
|
211
|
+
label: "Name",
|
|
212
|
+
value: props.name,
|
|
213
|
+
onChange: props.onName,
|
|
214
|
+
disabled: archived,
|
|
215
|
+
error: Boolean(props.nameIssue),
|
|
216
|
+
helperText: props.nameIssue,
|
|
217
|
+
slotProps: NAME_SLOT_PROPS,
|
|
218
|
+
fullWidth: true
|
|
219
|
+
}),
|
|
220
|
+
/*#__PURE__*/ _jsx(SequenceSiteField, {
|
|
221
|
+
hosts: props.hosts,
|
|
222
|
+
value: props.hostId,
|
|
223
|
+
issue: props.hostIssue,
|
|
224
|
+
disabled: archived,
|
|
225
|
+
onChange: props.onHost
|
|
226
|
+
}),
|
|
227
|
+
/*#__PURE__*/ _jsx(MemoMailboxPicker, {
|
|
228
|
+
orgId: props.orgId,
|
|
229
|
+
uid: props.uid,
|
|
230
|
+
mailboxes: props.mailboxes,
|
|
231
|
+
mailboxesPath: props.mailboxesPath,
|
|
232
|
+
value: props.mailboxId,
|
|
233
|
+
onChange: props.onMailbox,
|
|
234
|
+
error: props.mailboxIssue,
|
|
235
|
+
disabled: archived
|
|
236
|
+
}),
|
|
237
|
+
/*#__PURE__*/ _jsx(MemoContainerPicker, {
|
|
238
|
+
kind: "campaign",
|
|
239
|
+
options: props.campaignOptions,
|
|
240
|
+
value: props.campaignIds,
|
|
241
|
+
onChange: props.onCampaigns,
|
|
242
|
+
label: "Campaigns",
|
|
243
|
+
helperText: (_props_campaignsIssue = props.campaignsIssue) != null ? _props_campaignsIssue : 'The campaigns this sequence is part of. Everyone enrolled joins them, and its sends, replies, meetings and conversions count on their pages.',
|
|
244
|
+
disabled: archived,
|
|
245
|
+
empty: props.noCampaigns,
|
|
246
|
+
emptyText: "There are no campaigns yet. Create one from Marketing to file this sequence under it."
|
|
247
|
+
})
|
|
248
|
+
]
|
|
249
|
+
})
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
/**
|
|
253
|
+
* One step's card, bound to its position. Memoized, with handlers that keep
|
|
254
|
+
* their identity, so a keystroke in one step leaves the others undrawn.
|
|
255
|
+
*/ const SequenceStepItem = /*#__PURE__*/ memo(function SequenceStepItem(props) {
|
|
256
|
+
const { index, canSendTest, onStepChange, onStepMove, onStepRemove, onStepTest } = props, card = _object_without_properties_loose(props, [
|
|
257
|
+
"index",
|
|
258
|
+
"canSendTest",
|
|
259
|
+
"onStepChange",
|
|
260
|
+
"onStepMove",
|
|
261
|
+
"onStepRemove",
|
|
262
|
+
"onStepTest"
|
|
263
|
+
]);
|
|
264
|
+
const onChange = useCallback((step)=>onStepChange(index, step), [
|
|
265
|
+
index,
|
|
266
|
+
onStepChange
|
|
267
|
+
]);
|
|
268
|
+
const onMove = useCallback((direction)=>onStepMove(index, direction), [
|
|
269
|
+
index,
|
|
270
|
+
onStepMove
|
|
271
|
+
]);
|
|
272
|
+
const onRemove = useCallback(()=>onStepRemove(index), [
|
|
273
|
+
index,
|
|
274
|
+
onStepRemove
|
|
275
|
+
]);
|
|
276
|
+
const onSendTest = useCallback(()=>onStepTest(index), [
|
|
277
|
+
index,
|
|
278
|
+
onStepTest
|
|
279
|
+
]);
|
|
280
|
+
return /*#__PURE__*/ _jsx(OutreachStepCard, _extends({}, card, {
|
|
281
|
+
index: index,
|
|
282
|
+
onChange: onChange,
|
|
283
|
+
onMove: onMove,
|
|
284
|
+
onRemove: onRemove,
|
|
285
|
+
onSendTest: canSendTest ? onSendTest : undefined
|
|
286
|
+
}));
|
|
287
|
+
});
|
|
288
|
+
/** Adds an email, or a task of a kind picked from its menu. */ const SequenceAddStep = /*#__PURE__*/ memo(function SequenceAddStep(props) {
|
|
289
|
+
const { onAddTask } = props;
|
|
290
|
+
const [taskAnchor, setTaskAnchor] = useState(null);
|
|
291
|
+
return /*#__PURE__*/ _jsxs(Stack, {
|
|
292
|
+
direction: "row",
|
|
293
|
+
spacing: 1,
|
|
294
|
+
sx: ADD_STEP_ROW,
|
|
295
|
+
children: [
|
|
296
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
297
|
+
variant: "outlined",
|
|
298
|
+
startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
299
|
+
path: mdiPlus.path,
|
|
300
|
+
fontSize: "small"
|
|
301
|
+
}),
|
|
302
|
+
disabled: props.emailDisabled,
|
|
303
|
+
onClick: props.onAddEmail,
|
|
304
|
+
children: "Add email"
|
|
305
|
+
}),
|
|
306
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
307
|
+
variant: "outlined",
|
|
308
|
+
startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
309
|
+
path: mdiPlus.path,
|
|
310
|
+
fontSize: "small"
|
|
311
|
+
}),
|
|
312
|
+
disabled: props.taskDisabled,
|
|
313
|
+
onClick: (event)=>setTaskAnchor(event.currentTarget),
|
|
314
|
+
"aria-haspopup": "menu",
|
|
315
|
+
children: "Add task"
|
|
316
|
+
}),
|
|
317
|
+
/*#__PURE__*/ _jsx(Menu, {
|
|
318
|
+
anchorEl: taskAnchor,
|
|
319
|
+
open: Boolean(taskAnchor),
|
|
320
|
+
onClose: ()=>setTaskAnchor(null),
|
|
321
|
+
children: OUTREACH_TASK_KINDS.map((kind)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
322
|
+
onClick: ()=>{
|
|
323
|
+
setTaskAnchor(null);
|
|
324
|
+
onAddTask(kind);
|
|
325
|
+
},
|
|
326
|
+
children: OUTREACH_TASK_KIND_LABELS[kind]
|
|
327
|
+
}, kind))
|
|
328
|
+
})
|
|
329
|
+
]
|
|
330
|
+
});
|
|
331
|
+
});
|
|
332
|
+
/** Who the sequence sends to, and when: its countries, switches and hours. */ const SequenceSendingCard = /*#__PURE__*/ memo(function SequenceSendingCard(props) {
|
|
333
|
+
const { settings, disabled, onSettings } = props;
|
|
334
|
+
const setAllowCustomers = useCallback((event)=>onSettings({
|
|
335
|
+
allowCustomers: event.target.checked
|
|
336
|
+
}), [
|
|
337
|
+
onSettings
|
|
338
|
+
]);
|
|
339
|
+
const setTrackClicks = useCallback((event)=>onSettings({
|
|
340
|
+
trackClicks: event.target.checked
|
|
341
|
+
}), [
|
|
342
|
+
onSettings
|
|
343
|
+
]);
|
|
344
|
+
const setCountOpens = useCallback((event)=>onSettings({
|
|
345
|
+
countOpens: event.target.checked
|
|
346
|
+
}), [
|
|
347
|
+
onSettings
|
|
348
|
+
]);
|
|
349
|
+
const setListUnsubscribe = useCallback((event)=>onSettings({
|
|
350
|
+
listUnsubscribe: event.target.checked
|
|
351
|
+
}), [
|
|
352
|
+
onSettings
|
|
353
|
+
]);
|
|
354
|
+
const setWindow = useCallback((window)=>onSettings({
|
|
355
|
+
window
|
|
356
|
+
}), [
|
|
357
|
+
onSettings
|
|
358
|
+
]);
|
|
359
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
360
|
+
header: "Who it sends to, and when",
|
|
361
|
+
help: pluginDocsHelp('sequences', {
|
|
362
|
+
anchor: '#build-a-sequence',
|
|
363
|
+
excerpt: 'A sequence sends only to the countries both it and your organization allow, and in its mailbox’s sending hours unless it has its own.'
|
|
364
|
+
}),
|
|
365
|
+
contentGutterX: true,
|
|
366
|
+
contentGutterY: true,
|
|
367
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
368
|
+
spacing: 2,
|
|
369
|
+
children: [
|
|
370
|
+
/*#__PURE__*/ _jsx(OutreachCountriesField, {
|
|
371
|
+
options: props.countryOptions,
|
|
372
|
+
value: settings.allowedCountries,
|
|
373
|
+
onChange: props.onCountries,
|
|
374
|
+
issue: props.countriesIssue,
|
|
375
|
+
disabled: disabled
|
|
376
|
+
}),
|
|
377
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
378
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
379
|
+
checked: settings.allowCustomers,
|
|
380
|
+
onChange: setAllowCustomers,
|
|
381
|
+
disabled: disabled
|
|
382
|
+
}),
|
|
383
|
+
label: "Include customers"
|
|
384
|
+
}),
|
|
385
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
386
|
+
spacing: 0,
|
|
387
|
+
children: [
|
|
388
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
389
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
390
|
+
checked: settings.trackClicks,
|
|
391
|
+
onChange: setTrackClicks,
|
|
392
|
+
disabled: disabled
|
|
393
|
+
}),
|
|
394
|
+
label: "Count link clicks"
|
|
395
|
+
}),
|
|
396
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
397
|
+
variant: "caption",
|
|
398
|
+
color: "text.secondary",
|
|
399
|
+
children: "Links in these emails are replaced with links of ours that forward to the same page, so clicks can be counted. The recipient sees the replacement, not your address. The emails stay plain text."
|
|
400
|
+
})
|
|
401
|
+
]
|
|
402
|
+
}),
|
|
403
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
404
|
+
spacing: 0,
|
|
405
|
+
children: [
|
|
406
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
407
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
408
|
+
checked: settings.countOpens,
|
|
409
|
+
onChange: setCountOpens,
|
|
410
|
+
disabled: disabled
|
|
411
|
+
}),
|
|
412
|
+
label: "Count opens"
|
|
413
|
+
}),
|
|
414
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
415
|
+
variant: "caption",
|
|
416
|
+
color: "text.secondary",
|
|
417
|
+
children: "Each email also goes out as HTML — the same words — with a tiny tracking image, so opens can be counted. The plain-text version is unchanged. It makes the email look less like one written by hand, and some spam filters score HTML with a remote image against you, so turn it on to measure a slice of your sends rather than for every sequence. Gmail opens count; Apple Mail loads every image on arrival, so its loads are counted separately."
|
|
418
|
+
})
|
|
419
|
+
]
|
|
420
|
+
}),
|
|
421
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
422
|
+
spacing: 0,
|
|
423
|
+
children: [
|
|
424
|
+
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
425
|
+
control: /*#__PURE__*/ _jsx(Switch, {
|
|
426
|
+
checked: settings.listUnsubscribe,
|
|
427
|
+
onChange: setListUnsubscribe,
|
|
428
|
+
disabled: disabled
|
|
429
|
+
}),
|
|
430
|
+
label: "Add a mail-client unsubscribe button"
|
|
431
|
+
}),
|
|
432
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
433
|
+
variant: "caption",
|
|
434
|
+
color: "text.secondary",
|
|
435
|
+
children: "Adds the List-Unsubscribe header, so the recipient’s mail app shows its own Unsubscribe button. It’s the easiest way out for them, and fewer people mark you as spam when they have it, but mail apps then label the email as coming from a mailing list. Off, the footer’s “reply ‘no’” line is the way out, and it’s always there."
|
|
436
|
+
})
|
|
437
|
+
]
|
|
438
|
+
}),
|
|
439
|
+
/*#__PURE__*/ _jsx(MemoSendWindowFields, {
|
|
440
|
+
window: settings.window,
|
|
441
|
+
onChange: setWindow,
|
|
442
|
+
daysIssue: props.daysIssue,
|
|
443
|
+
hoursIssue: props.hoursIssue,
|
|
444
|
+
disabled: disabled
|
|
445
|
+
})
|
|
446
|
+
]
|
|
447
|
+
})
|
|
448
|
+
});
|
|
449
|
+
});
|
|
94
450
|
/**
|
|
95
451
|
* The sequence editor (AGL-2980): the name, the site and mailbox, the steps,
|
|
96
452
|
* who it sends to and when, beside a live preview of each email.
|
|
@@ -101,8 +457,21 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
101
457
|
* judge (whose mailbox, which countries the organization allows, what may
|
|
102
458
|
* no longer change once people are enrolled) comes back from the save and
|
|
103
459
|
* is shown in the same places.
|
|
460
|
+
*
|
|
461
|
+
* ## A keystroke redraws its own field (AGL-3423)
|
|
462
|
+
*
|
|
463
|
+
* The whole sequence is one draft, so every letter typed anywhere is a new
|
|
464
|
+
* draft, and drawing the editor from it inline redrew every field of every
|
|
465
|
+
* step, the settings and the preview with each one: 17 inputs and some 1,150
|
|
466
|
+
* components for one letter in a three-step sequence. That cost is what lets
|
|
467
|
+
* typed input queue, and queued keystrokes processed back to back are what
|
|
468
|
+
* drove the countries chips into React's #185. The sequence card, each step,
|
|
469
|
+
* the sending card and the preview are memoized and handed only what they
|
|
470
|
+
* show, through handlers that keep their identity; the handlers that wrote
|
|
471
|
+
* from the draft as drawn still do, through `drawn`. A letter now redraws the
|
|
472
|
+
* field it lands in, and the preview when it shows that step's email.
|
|
104
473
|
*/ export function OutreachSequenceEditor(props) {
|
|
105
|
-
var _ref, _props_mailboxes_mailboxes_find, _ref1,
|
|
474
|
+
var _ref, _props_mailboxes_mailboxes_find, _ref1, _props_org, _ref2;
|
|
106
475
|
var _settings_settings, _settings_settings1, _issues_find, _hosts_find;
|
|
107
476
|
const { orgId, orgMount, sequence, settings } = props;
|
|
108
477
|
const api = useOutreachApi(orgId);
|
|
@@ -126,9 +495,11 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
126
495
|
const [attempted, setAttempted] = useState(false);
|
|
127
496
|
const [serverIssues, setServerIssues] = useState([]);
|
|
128
497
|
const [saving, setSaving] = useState(false);
|
|
129
|
-
const [taskAnchor, setTaskAnchor] = useState(null);
|
|
130
498
|
// The step whose test-send dialog is open (AGL-3325), by index.
|
|
131
499
|
const [testing, setTesting] = useState(null);
|
|
500
|
+
// The draft as last drawn, which the handlers that build on it read.
|
|
501
|
+
const drawn = useRef(draft);
|
|
502
|
+
drawn.current = draft;
|
|
132
503
|
// A stored sequence the listener updated (a save, a status change) is the
|
|
133
504
|
// new starting point.
|
|
134
505
|
useEffect(()=>{
|
|
@@ -174,13 +545,19 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
174
545
|
]);
|
|
175
546
|
const templates = useOutreachEmailTemplates(orgId, draft.hostId || null, uid);
|
|
176
547
|
const mailbox = (_props_mailboxes_mailboxes_find = props.mailboxes.mailboxes.find((entry)=>entry.id === draft.mailboxId)) != null ? _props_mailboxes_mailboxes_find : null;
|
|
548
|
+
const sender = useMemo(()=>mailbox ? {
|
|
549
|
+
name: mailbox.displayName,
|
|
550
|
+
email: mailbox.sendAs || mailbox.email
|
|
551
|
+
} : null, [
|
|
552
|
+
mailbox
|
|
553
|
+
]);
|
|
177
554
|
const archived = (sequence == null ? void 0 : sequence.status) === 'archived';
|
|
178
555
|
/*
|
|
179
556
|
* The org's campaigns, for the picker (AGL-3254): read while the editor
|
|
180
557
|
* is open. A sequence is the organization's record and so is a campaign,
|
|
181
558
|
* so the choice is every campaign in the org, whichever site the
|
|
182
559
|
* sequence sends as.
|
|
183
|
-
*/ const campaigns =
|
|
560
|
+
*/ const campaigns = useOrgContainerOptions('campaign', orgId, {
|
|
184
561
|
enabled: true
|
|
185
562
|
});
|
|
186
563
|
const issues = useMemo(()=>{
|
|
@@ -198,23 +575,18 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
198
575
|
var _issues_find;
|
|
199
576
|
return (_issues_find = issues.find((issue)=>issue.path === path)) == null ? void 0 : _issues_find.message;
|
|
200
577
|
};
|
|
201
|
-
const stepIssues = (
|
|
202
|
-
const found = {};
|
|
203
|
-
for (const issue of issues){
|
|
204
|
-
const key = stepIssueKey(issue.path);
|
|
205
|
-
if ((key == null ? void 0 : key.index) === index && !found[key.field]) found[key.field] = issue.message;
|
|
206
|
-
}
|
|
207
|
-
return found;
|
|
208
|
-
};
|
|
578
|
+
const stepIssues = useStepIssues(issues, draft.steps.length);
|
|
209
579
|
const listIssue = issueAt('steps');
|
|
210
580
|
const countriesIssue = (_issues_find = issues.find((issue)=>issue.path.startsWith('settings.allowedCountries'))) == null ? void 0 : _issues_find.message;
|
|
211
581
|
// An edit clears what the last save refused. The empty list stays the same
|
|
212
582
|
// list, so an edit with nothing to clear does not change that state.
|
|
213
583
|
const clearServerIssues = useCallback(()=>setServerIssues((previous)=>previous.length ? [] : previous), []);
|
|
214
|
-
const update = (next)=>{
|
|
584
|
+
const update = useCallback((next)=>{
|
|
215
585
|
clearServerIssues();
|
|
216
586
|
setDraft((previous)=>_extends({}, previous, next));
|
|
217
|
-
}
|
|
587
|
+
}, [
|
|
588
|
+
clearServerIssues
|
|
589
|
+
]);
|
|
218
590
|
// Stable, for the memoized countries field: it reads the settings it
|
|
219
591
|
// replaces from the draft it is given rather than from this render's.
|
|
220
592
|
const setCountries = useCallback((next)=>{
|
|
@@ -227,21 +599,72 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
227
599
|
}, [
|
|
228
600
|
clearServerIssues
|
|
229
601
|
]);
|
|
230
|
-
const
|
|
231
|
-
|
|
232
|
-
})
|
|
233
|
-
|
|
602
|
+
const setName = useCallback((event)=>update({
|
|
603
|
+
name: event.target.value
|
|
604
|
+
}), [
|
|
605
|
+
update
|
|
606
|
+
]);
|
|
607
|
+
const setHost = useCallback((event)=>update({
|
|
608
|
+
hostId: event.target.value
|
|
609
|
+
}), [
|
|
610
|
+
update
|
|
611
|
+
]);
|
|
612
|
+
const setMailbox = useCallback((mailboxId)=>update({
|
|
613
|
+
mailboxId
|
|
614
|
+
}), [
|
|
615
|
+
update
|
|
616
|
+
]);
|
|
617
|
+
const setCampaigns = useCallback((campaignIds)=>update({
|
|
618
|
+
campaignIds
|
|
619
|
+
}), [
|
|
620
|
+
update
|
|
621
|
+
]);
|
|
622
|
+
const setSettings = useCallback((patch)=>update({
|
|
623
|
+
settings: _extends({}, drawn.current.settings, patch)
|
|
624
|
+
}), [
|
|
625
|
+
update
|
|
626
|
+
]);
|
|
627
|
+
const setStep = useCallback((index, step)=>update({
|
|
628
|
+
steps: drawn.current.steps.map((entry, position)=>position === index ? step : entry)
|
|
629
|
+
}), [
|
|
630
|
+
update
|
|
631
|
+
]);
|
|
632
|
+
const moveStep = useCallback((index, direction)=>{
|
|
234
633
|
const steps = [
|
|
235
|
-
...
|
|
634
|
+
...drawn.current.steps
|
|
236
635
|
];
|
|
237
636
|
const [moved] = steps.splice(index, 1);
|
|
238
637
|
steps.splice(index + direction, 0, moved);
|
|
239
638
|
update({
|
|
240
639
|
steps
|
|
241
640
|
});
|
|
242
|
-
}
|
|
641
|
+
}, [
|
|
642
|
+
update
|
|
643
|
+
]);
|
|
644
|
+
const removeStep = useCallback((index)=>update({
|
|
645
|
+
steps: drawn.current.steps.filter((_entry, position)=>position !== index)
|
|
646
|
+
}), [
|
|
647
|
+
update
|
|
648
|
+
]);
|
|
649
|
+
const addEmail = useCallback(()=>update({
|
|
650
|
+
steps: [
|
|
651
|
+
...drawn.current.steps,
|
|
652
|
+
newOutreachEmailStep(drawn.current.steps)
|
|
653
|
+
]
|
|
654
|
+
}), [
|
|
655
|
+
update
|
|
656
|
+
]);
|
|
657
|
+
const addTask = useCallback((kind)=>update({
|
|
658
|
+
steps: [
|
|
659
|
+
...drawn.current.steps,
|
|
660
|
+
newOutreachTaskStep(drawn.current.steps, kind)
|
|
661
|
+
]
|
|
662
|
+
}), [
|
|
663
|
+
update
|
|
664
|
+
]);
|
|
243
665
|
const emailCount = draft.steps.filter((step)=>step.kind === 'email').length;
|
|
244
666
|
const full = draft.steps.length >= OUTREACH_MAX_STEPS;
|
|
667
|
+
const firstEmail = draft.steps.findIndex((entry)=>entry.kind === 'email');
|
|
245
668
|
const threadSubject = (index)=>{
|
|
246
669
|
for(let position = index - 1; position >= 0; position -= 1){
|
|
247
670
|
const step = draft.steps[position];
|
|
@@ -274,6 +697,10 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
274
697
|
setSaving(false);
|
|
275
698
|
}
|
|
276
699
|
};
|
|
700
|
+
// The save as last drawn, behind a handler that keeps its identity.
|
|
701
|
+
const drawnSave = useRef(save);
|
|
702
|
+
drawnSave.current = save;
|
|
703
|
+
const onSave = useCallback(()=>void drawnSave.current(), []);
|
|
277
704
|
const siteName = (_ref1 = (_hosts_find = hosts.find((host)=>host.id === draft.hostId)) == null ? void 0 : _hosts_find.name) != null ? _ref1 : '';
|
|
278
705
|
const draftCountries = draft.settings.allowedCountries;
|
|
279
706
|
const countryOptions = useMemo(()=>[
|
|
@@ -308,289 +735,78 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
308
735
|
severity: "info",
|
|
309
736
|
children: "This sequence is archived, so it can’t be edited."
|
|
310
737
|
}) : null,
|
|
311
|
-
/*#__PURE__*/ _jsx(
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
children: saving ? 'Saving…' : sequence ? 'Save' : 'Create sequence'
|
|
337
|
-
})
|
|
338
|
-
]
|
|
339
|
-
})
|
|
340
|
-
},
|
|
341
|
-
contentGutterX: true,
|
|
342
|
-
contentGutterY: true,
|
|
343
|
-
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
344
|
-
spacing: 2,
|
|
345
|
-
children: [
|
|
346
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
347
|
-
label: "Name",
|
|
348
|
-
value: draft.name,
|
|
349
|
-
onChange: (event)=>update({
|
|
350
|
-
name: event.target.value
|
|
351
|
-
}),
|
|
352
|
-
disabled: archived,
|
|
353
|
-
error: Boolean(issueAt('name')),
|
|
354
|
-
helperText: issueAt('name'),
|
|
355
|
-
slotProps: {
|
|
356
|
-
htmlInput: {
|
|
357
|
-
maxLength: OUTREACH_SEQUENCE_NAME_MAX + 20
|
|
358
|
-
}
|
|
359
|
-
},
|
|
360
|
-
fullWidth: true
|
|
361
|
-
}),
|
|
362
|
-
/*#__PURE__*/ _jsx(TextField, {
|
|
363
|
-
select: true,
|
|
364
|
-
label: "Site",
|
|
365
|
-
value: hosts.some((host)=>host.id === draft.hostId) ? draft.hostId : '',
|
|
366
|
-
onChange: (event)=>update({
|
|
367
|
-
hostId: event.target.value
|
|
368
|
-
}),
|
|
369
|
-
disabled: archived || !hosts.length,
|
|
370
|
-
error: Boolean(issueAt('hostId')),
|
|
371
|
-
helperText: (_issueAt = issueAt('hostId')) != null ? _issueAt : 'The site whose contacts this sequence emails.',
|
|
372
|
-
fullWidth: true,
|
|
373
|
-
children: hosts.map((host)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
374
|
-
value: host.id,
|
|
375
|
-
children: host.name
|
|
376
|
-
}, host.id))
|
|
377
|
-
}),
|
|
378
|
-
/*#__PURE__*/ _jsx(OutreachSequenceMailboxPicker, {
|
|
379
|
-
orgId: orgId,
|
|
380
|
-
uid: uid,
|
|
381
|
-
mailboxes: props.mailboxes,
|
|
382
|
-
mailboxesPath: props.mailboxesPath,
|
|
383
|
-
value: draft.mailboxId,
|
|
384
|
-
onChange: (mailboxId)=>update({
|
|
385
|
-
mailboxId
|
|
386
|
-
}),
|
|
387
|
-
error: issueAt('mailboxId'),
|
|
388
|
-
disabled: archived
|
|
389
|
-
}),
|
|
390
|
-
/*#__PURE__*/ _jsx(CampaignPicker, {
|
|
391
|
-
options: campaigns.options,
|
|
392
|
-
value: draft.campaignIds,
|
|
393
|
-
onChange: (campaignIds)=>update({
|
|
394
|
-
campaignIds
|
|
395
|
-
}),
|
|
396
|
-
label: "Campaigns",
|
|
397
|
-
helperText: (_issueAt1 = issueAt('campaignIds')) != null ? _issueAt1 : 'The campaigns this sequence is part of. Everyone enrolled joins them, and its sends, replies, meetings and conversions count on their pages.',
|
|
398
|
-
disabled: archived,
|
|
399
|
-
empty: campaigns.ready && !campaigns.options.length,
|
|
400
|
-
emptyText: "There are no campaigns yet. Create one from Marketing to file this sequence under it."
|
|
401
|
-
})
|
|
402
|
-
]
|
|
403
|
-
})
|
|
738
|
+
/*#__PURE__*/ _jsx(SequenceCard, {
|
|
739
|
+
orgId: orgId,
|
|
740
|
+
uid: uid,
|
|
741
|
+
name: draft.name,
|
|
742
|
+
hostId: draft.hostId,
|
|
743
|
+
mailboxId: draft.mailboxId,
|
|
744
|
+
campaignIds: draft.campaignIds,
|
|
745
|
+
hosts: hosts,
|
|
746
|
+
mailboxes: props.mailboxes,
|
|
747
|
+
mailboxesPath: props.mailboxesPath,
|
|
748
|
+
campaignOptions: campaigns.options,
|
|
749
|
+
noCampaigns: campaigns.ready && !campaigns.options.length,
|
|
750
|
+
nameIssue: issueAt('name'),
|
|
751
|
+
hostIssue: issueAt('hostId'),
|
|
752
|
+
mailboxIssue: issueAt('mailboxId'),
|
|
753
|
+
campaignsIssue: issueAt('campaignIds'),
|
|
754
|
+
archived: archived,
|
|
755
|
+
saving: saving,
|
|
756
|
+
saveLabel: sequence ? 'Save' : 'Create sequence',
|
|
757
|
+
onCancel: props.onCancel,
|
|
758
|
+
onSave: onSave,
|
|
759
|
+
onName: setName,
|
|
760
|
+
onHost: setHost,
|
|
761
|
+
onMailbox: setMailbox,
|
|
762
|
+
onCampaigns: setCampaigns
|
|
404
763
|
}),
|
|
405
764
|
listIssue ? /*#__PURE__*/ _jsx(Alert, {
|
|
406
765
|
severity: "error",
|
|
407
766
|
children: listIssue
|
|
408
767
|
}) : null,
|
|
409
|
-
draft.steps.map((step, index)
|
|
768
|
+
draft.steps.map((step, index)=>{
|
|
769
|
+
const inThread = isInThreadEmailStep(draft.steps, index);
|
|
770
|
+
return /*#__PURE__*/ _jsx(SequenceStepItem, {
|
|
410
771
|
step: step,
|
|
411
772
|
index: index,
|
|
412
773
|
count: draft.steps.length,
|
|
413
|
-
firstEmail:
|
|
414
|
-
inThread:
|
|
415
|
-
|
|
774
|
+
firstEmail: firstEmail === index,
|
|
775
|
+
inThread: inThread,
|
|
776
|
+
// Read only by an email sent in the thread, so no other
|
|
777
|
+
// step is redrawn as the subject it replies to is typed.
|
|
778
|
+
threadSubject: inThread ? threadSubject(index) : '',
|
|
416
779
|
templates: templates.data,
|
|
417
|
-
issues: stepIssues
|
|
780
|
+
issues: stepIssues[index],
|
|
418
781
|
disabled: archived,
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
/*#__PURE__*/
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
rowGap: 1
|
|
433
|
-
},
|
|
434
|
-
children: [
|
|
435
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
436
|
-
variant: "outlined",
|
|
437
|
-
startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
438
|
-
path: mdiPlus.path,
|
|
439
|
-
fontSize: "small"
|
|
440
|
-
}),
|
|
441
|
-
disabled: archived || full || emailCount >= OUTREACH_MAX_EMAIL_STEPS,
|
|
442
|
-
onClick: ()=>update({
|
|
443
|
-
steps: [
|
|
444
|
-
...draft.steps,
|
|
445
|
-
newOutreachEmailStep(draft.steps)
|
|
446
|
-
]
|
|
447
|
-
}),
|
|
448
|
-
children: "Add email"
|
|
449
|
-
}),
|
|
450
|
-
/*#__PURE__*/ _jsx(Button, {
|
|
451
|
-
variant: "outlined",
|
|
452
|
-
startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
453
|
-
path: mdiPlus.path,
|
|
454
|
-
fontSize: "small"
|
|
455
|
-
}),
|
|
456
|
-
disabled: archived || full,
|
|
457
|
-
onClick: (event)=>setTaskAnchor(event.currentTarget),
|
|
458
|
-
"aria-haspopup": "menu",
|
|
459
|
-
children: "Add task"
|
|
460
|
-
}),
|
|
461
|
-
/*#__PURE__*/ _jsx(Menu, {
|
|
462
|
-
anchorEl: taskAnchor,
|
|
463
|
-
open: Boolean(taskAnchor),
|
|
464
|
-
onClose: ()=>setTaskAnchor(null),
|
|
465
|
-
children: OUTREACH_TASK_KINDS.map((kind)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
466
|
-
onClick: ()=>{
|
|
467
|
-
setTaskAnchor(null);
|
|
468
|
-
update({
|
|
469
|
-
steps: [
|
|
470
|
-
...draft.steps,
|
|
471
|
-
newOutreachTaskStep(draft.steps, kind)
|
|
472
|
-
]
|
|
473
|
-
});
|
|
474
|
-
},
|
|
475
|
-
children: OUTREACH_TASK_KIND_LABELS[kind]
|
|
476
|
-
}, kind))
|
|
477
|
-
})
|
|
478
|
-
]
|
|
782
|
+
canSendTest: step.kind === 'email' && !archived,
|
|
783
|
+
sendTestDisabledReason: sendTestDisabledReason,
|
|
784
|
+
onStepChange: setStep,
|
|
785
|
+
onStepMove: moveStep,
|
|
786
|
+
onStepRemove: removeStep,
|
|
787
|
+
onStepTest: setTesting
|
|
788
|
+
}, step.id || `step-${index}`);
|
|
789
|
+
}),
|
|
790
|
+
/*#__PURE__*/ _jsx(SequenceAddStep, {
|
|
791
|
+
emailDisabled: archived || full || emailCount >= OUTREACH_MAX_EMAIL_STEPS,
|
|
792
|
+
taskDisabled: archived || full,
|
|
793
|
+
onAddEmail: addEmail,
|
|
794
|
+
onAddTask: addTask
|
|
479
795
|
}),
|
|
480
796
|
/*#__PURE__*/ _jsx(Typography, {
|
|
481
797
|
variant: "caption",
|
|
482
798
|
color: "text.secondary",
|
|
483
799
|
children: `Up to ${OUTREACH_MAX_STEPS} steps, ${OUTREACH_MAX_EMAIL_STEPS} of them emails.`
|
|
484
800
|
}),
|
|
485
|
-
/*#__PURE__*/ _jsx(
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
spacing: 2,
|
|
495
|
-
children: [
|
|
496
|
-
/*#__PURE__*/ _jsx(OutreachCountriesField, {
|
|
497
|
-
options: countryOptions,
|
|
498
|
-
value: draftCountries,
|
|
499
|
-
onChange: setCountries,
|
|
500
|
-
issue: countriesIssue,
|
|
501
|
-
disabled: archived
|
|
502
|
-
}),
|
|
503
|
-
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
504
|
-
control: /*#__PURE__*/ _jsx(Switch, {
|
|
505
|
-
checked: draft.settings.allowCustomers,
|
|
506
|
-
onChange: (event)=>update({
|
|
507
|
-
settings: _extends({}, draft.settings, {
|
|
508
|
-
allowCustomers: event.target.checked
|
|
509
|
-
})
|
|
510
|
-
}),
|
|
511
|
-
disabled: archived
|
|
512
|
-
}),
|
|
513
|
-
label: "Include customers"
|
|
514
|
-
}),
|
|
515
|
-
/*#__PURE__*/ _jsxs(Stack, {
|
|
516
|
-
spacing: 0,
|
|
517
|
-
children: [
|
|
518
|
-
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
519
|
-
control: /*#__PURE__*/ _jsx(Switch, {
|
|
520
|
-
checked: draft.settings.trackClicks,
|
|
521
|
-
onChange: (event)=>update({
|
|
522
|
-
settings: _extends({}, draft.settings, {
|
|
523
|
-
trackClicks: event.target.checked
|
|
524
|
-
})
|
|
525
|
-
}),
|
|
526
|
-
disabled: archived
|
|
527
|
-
}),
|
|
528
|
-
label: "Count link clicks"
|
|
529
|
-
}),
|
|
530
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
531
|
-
variant: "caption",
|
|
532
|
-
color: "text.secondary",
|
|
533
|
-
children: "Links in these emails are replaced with links of ours that forward to the same page, so clicks can be counted. The recipient sees the replacement, not your address. The emails stay plain text."
|
|
534
|
-
})
|
|
535
|
-
]
|
|
536
|
-
}),
|
|
537
|
-
/*#__PURE__*/ _jsxs(Stack, {
|
|
538
|
-
spacing: 0,
|
|
539
|
-
children: [
|
|
540
|
-
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
541
|
-
control: /*#__PURE__*/ _jsx(Switch, {
|
|
542
|
-
checked: draft.settings.countOpens,
|
|
543
|
-
onChange: (event)=>update({
|
|
544
|
-
settings: _extends({}, draft.settings, {
|
|
545
|
-
countOpens: event.target.checked
|
|
546
|
-
})
|
|
547
|
-
}),
|
|
548
|
-
disabled: archived
|
|
549
|
-
}),
|
|
550
|
-
label: "Count opens"
|
|
551
|
-
}),
|
|
552
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
553
|
-
variant: "caption",
|
|
554
|
-
color: "text.secondary",
|
|
555
|
-
children: "Each email also goes out as HTML — the same words — with a tiny tracking image, so opens can be counted. The plain-text version is unchanged. It makes the email look less like one written by hand, and some spam filters score HTML with a remote image against you, so turn it on to measure a slice of your sends rather than for every sequence. Gmail opens count; Apple Mail loads every image on arrival, so its loads are counted separately."
|
|
556
|
-
})
|
|
557
|
-
]
|
|
558
|
-
}),
|
|
559
|
-
/*#__PURE__*/ _jsxs(Stack, {
|
|
560
|
-
spacing: 0,
|
|
561
|
-
children: [
|
|
562
|
-
/*#__PURE__*/ _jsx(FormControlLabel, {
|
|
563
|
-
control: /*#__PURE__*/ _jsx(Switch, {
|
|
564
|
-
checked: draft.settings.listUnsubscribe,
|
|
565
|
-
onChange: (event)=>update({
|
|
566
|
-
settings: _extends({}, draft.settings, {
|
|
567
|
-
listUnsubscribe: event.target.checked
|
|
568
|
-
})
|
|
569
|
-
}),
|
|
570
|
-
disabled: archived
|
|
571
|
-
}),
|
|
572
|
-
label: "Add a mail-client unsubscribe button"
|
|
573
|
-
}),
|
|
574
|
-
/*#__PURE__*/ _jsx(Typography, {
|
|
575
|
-
variant: "caption",
|
|
576
|
-
color: "text.secondary",
|
|
577
|
-
children: "Adds the List-Unsubscribe header, so the recipient’s mail app shows its own Unsubscribe button. It’s the easiest way out for them, and fewer people mark you as spam when they have it, but mail apps then label the email as coming from a mailing list. Off, the footer’s “reply ‘no’” line is the way out, and it’s always there."
|
|
578
|
-
})
|
|
579
|
-
]
|
|
580
|
-
}),
|
|
581
|
-
/*#__PURE__*/ _jsx(OutreachSendWindowFields, {
|
|
582
|
-
window: draft.settings.window,
|
|
583
|
-
onChange: (window)=>update({
|
|
584
|
-
settings: _extends({}, draft.settings, {
|
|
585
|
-
window
|
|
586
|
-
})
|
|
587
|
-
}),
|
|
588
|
-
daysIssue: issueAt('settings.window.days'),
|
|
589
|
-
hoursIssue: issueAt('settings.window'),
|
|
590
|
-
disabled: archived
|
|
591
|
-
})
|
|
592
|
-
]
|
|
593
|
-
})
|
|
801
|
+
/*#__PURE__*/ _jsx(SequenceSendingCard, {
|
|
802
|
+
settings: draft.settings,
|
|
803
|
+
countryOptions: countryOptions,
|
|
804
|
+
countriesIssue: countriesIssue,
|
|
805
|
+
daysIssue: issueAt('settings.window.days'),
|
|
806
|
+
hoursIssue: issueAt('settings.window'),
|
|
807
|
+
disabled: archived,
|
|
808
|
+
onCountries: setCountries,
|
|
809
|
+
onSettings: setSettings
|
|
594
810
|
})
|
|
595
811
|
]
|
|
596
812
|
})
|
|
@@ -612,23 +828,13 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
|
|
|
612
828
|
md: 5
|
|
613
829
|
},
|
|
614
830
|
children: /*#__PURE__*/ _jsx(Box, {
|
|
615
|
-
sx:
|
|
616
|
-
|
|
617
|
-
md: 'sticky'
|
|
618
|
-
},
|
|
619
|
-
top: {
|
|
620
|
-
md: 16
|
|
621
|
-
}
|
|
622
|
-
},
|
|
623
|
-
children: /*#__PURE__*/ _jsx(OutreachSequencePreview, {
|
|
831
|
+
sx: PREVIEW_COLUMN,
|
|
832
|
+
children: /*#__PURE__*/ _jsx(MemoSequencePreview, {
|
|
624
833
|
steps: draft.steps,
|
|
625
834
|
orgSettings: settings.settings,
|
|
626
835
|
orgSettingsStatus: settings.status,
|
|
627
836
|
templates: templates.data,
|
|
628
|
-
sender:
|
|
629
|
-
name: mailbox.displayName,
|
|
630
|
-
email: mailbox.sendAs || mailbox.email
|
|
631
|
-
} : null,
|
|
837
|
+
sender: sender,
|
|
632
838
|
siteName: siteName
|
|
633
839
|
})
|
|
634
840
|
})
|