@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.
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
@@ -19,18 +19,75 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  import { mdiArrowDown, mdiArrowUp, mdiCodeBraces, mdiEmailFastOutline, mdiTrashCanOutline } from "@aglyn/shared-data-mdi";
20
20
  import { MdiIcon } from "@aglyn/shared-ui-jsx";
21
21
  import { Autocomplete, Button, Card, CardContent, FormControlLabel, IconButton, ListSubheader, Menu, MenuItem, Stack, Switch, TextField, Tooltip, Typography } from "@mui/material";
22
- import { useRef, useState } from "react";
22
+ import { memo, useCallback, useRef, useState } from "react";
23
23
  import { OUTREACH_MERGE_FIELDS } from "../engine/sequence-validation.js";
24
24
  import { OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS, OUTREACH_TASK_KIND_LABELS, OUTREACH_TASK_KINDS } from "../model/outreach.types.js";
25
25
  /** The merge fields, grouped the way the picker heads them. */ const FIELD_GROUPS = [
26
26
  ...new Set(OUTREACH_MERGE_FIELDS.map((field)=>field.group))
27
27
  ];
28
+ /** A text field that redraws only when what it shows changes (AGL-3423). */ const MemoTextField = /*#__PURE__*/ memo(TextField);
29
+ const HEADER_ROW = {
30
+ alignItems: 'center'
31
+ };
32
+ const TITLE = {
33
+ flexGrow: 1
34
+ };
35
+ const DELAY_FIELD = {
36
+ maxWidth: {
37
+ sm: 360
38
+ }
39
+ };
40
+ const DELAY_SLOT_PROPS = {
41
+ htmlInput: {
42
+ min: 0,
43
+ max: OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS,
44
+ step: 1
45
+ }
46
+ };
47
+ const ACTIONS_ROW = {
48
+ flexWrap: 'wrap',
49
+ rowGap: 1
50
+ };
51
+ const TASK_ROW = {
52
+ xs: 'column',
53
+ sm: 'row'
54
+ };
55
+ const TASK_KIND_FIELD = {
56
+ minWidth: {
57
+ sm: 180
58
+ }
59
+ };
60
+ const templateName = (option)=>option.name;
61
+ const sameTemplate = (option, value)=>option.id === value.id;
62
+ /** Every task kind, as the Task select lists them, built once. */ const TASK_KIND_MENU = OUTREACH_TASK_KINDS.map((kind)=>/*#__PURE__*/ _jsx(MenuItem, {
63
+ value: kind,
64
+ children: OUTREACH_TASK_KIND_LABELS[kind]
65
+ }, kind));
28
66
  /**
29
67
  * One step of the sequence editor (AGL-2980): an email — subject, body or a
30
68
  * CRM template, merge fields, the wait before it — or a task for the rep.
69
+ *
70
+ * ## Each field redraws on its own (AGL-3423)
71
+ *
72
+ * The editor keeps the whole sequence in one draft, so a letter typed into a
73
+ * step hands its card a new step. The card's fields are memoized, with
74
+ * handlers that keep their identity and write the step as last drawn, so the
75
+ * letter redraws the field it lands in rather than the wait, the subject, the
76
+ * template picker and the body together. A keystroke that costs one field
77
+ * leaves typed input nothing to queue behind, which is what kept the
78
+ * countries chips from being redrawn by fifty keystrokes in a row (#185).
31
79
  */ export function OutreachStepCard(props) {
32
80
  var _OUTREACH_TASK_KIND_LABELS_step_taskKind, _props_issues_step;
33
81
  const { step, index, count, disabled } = props;
82
+ // The card as last drawn, which every handler below writes from.
83
+ const latest = useRef(props);
84
+ latest.current = props;
85
+ const moveUp = useCallback(()=>latest.current.onMove(-1), []);
86
+ const moveDown = useCallback(()=>latest.current.onMove(1), []);
87
+ const remove = useCallback(()=>latest.current.onRemove(), []);
88
+ const setDelay = useCallback((event)=>latest.current.onChange(_extends({}, latest.current.step, {
89
+ delayBusinessDays: event.target.value === '' ? Number.NaN : Number(event.target.value)
90
+ })), []);
34
91
  const title = step.kind === 'email' ? `Step ${index + 1} · Email` : `Step ${index + 1} · ${(_OUTREACH_TASK_KIND_LABELS_step_taskKind = OUTREACH_TASK_KIND_LABELS[step.taskKind]) != null ? _OUTREACH_TASK_KIND_LABELS_step_taskKind : 'Task'}`;
35
92
  return /*#__PURE__*/ _jsx(Card, {
36
93
  variant: "outlined",
@@ -40,68 +97,22 @@ import { OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS, OUTREACH_TASK_KIND_LABELS, OUTRE
40
97
  children: /*#__PURE__*/ _jsxs(Stack, {
41
98
  spacing: 2,
42
99
  children: [
43
- /*#__PURE__*/ _jsxs(Stack, {
44
- direction: "row",
45
- spacing: 1,
46
- sx: {
47
- alignItems: 'center'
48
- },
49
- children: [
50
- /*#__PURE__*/ _jsx(Typography, {
51
- variant: "subtitle1",
52
- sx: {
53
- flexGrow: 1
54
- },
55
- children: title
56
- }),
57
- /*#__PURE__*/ _jsx(Tooltip, {
58
- title: "Move up",
59
- children: /*#__PURE__*/ _jsx("span", {
60
- children: /*#__PURE__*/ _jsx(IconButton, {
61
- size: "small",
62
- "aria-label": `Move step ${index + 1} up`,
63
- disabled: disabled || index === 0,
64
- onClick: ()=>props.onMove(-1),
65
- children: /*#__PURE__*/ _jsx(MdiIcon, {
66
- path: mdiArrowUp.path,
67
- fontSize: "small"
68
- })
69
- })
70
- })
71
- }),
72
- /*#__PURE__*/ _jsx(Tooltip, {
73
- title: "Move down",
74
- children: /*#__PURE__*/ _jsx("span", {
75
- children: /*#__PURE__*/ _jsx(IconButton, {
76
- size: "small",
77
- "aria-label": `Move step ${index + 1} down`,
78
- disabled: disabled || index === count - 1,
79
- onClick: ()=>props.onMove(1),
80
- children: /*#__PURE__*/ _jsx(MdiIcon, {
81
- path: mdiArrowDown.path,
82
- fontSize: "small"
83
- })
84
- })
85
- })
86
- }),
87
- /*#__PURE__*/ _jsx(Tooltip, {
88
- title: "Remove",
89
- children: /*#__PURE__*/ _jsx("span", {
90
- children: /*#__PURE__*/ _jsx(IconButton, {
91
- size: "small",
92
- "aria-label": `Remove step ${index + 1}`,
93
- disabled: disabled || count === 1,
94
- onClick: props.onRemove,
95
- children: /*#__PURE__*/ _jsx(MdiIcon, {
96
- path: mdiTrashCanOutline.path,
97
- fontSize: "small"
98
- })
99
- })
100
- })
101
- })
102
- ]
100
+ /*#__PURE__*/ _jsx(StepHeader, {
101
+ title: title,
102
+ index: index,
103
+ count: count,
104
+ disabled: disabled,
105
+ onMoveUp: moveUp,
106
+ onMoveDown: moveDown,
107
+ onRemove: remove
108
+ }),
109
+ /*#__PURE__*/ _jsx(DelayField, {
110
+ first: index === 0,
111
+ value: step.delayBusinessDays,
112
+ issue: props.issues['delayBusinessDays'],
113
+ disabled: disabled,
114
+ onChange: setDelay
103
115
  }),
104
- /*#__PURE__*/ _jsx(DelayField, _extends({}, props)),
105
116
  step.kind === 'email' ? /*#__PURE__*/ _jsx(EmailStepFields, _extends({}, props, {
106
117
  step: step
107
118
  })) : step.kind === 'task' ? /*#__PURE__*/ _jsx(TaskStepFields, _extends({}, props, {
@@ -117,82 +128,159 @@ import { OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS, OUTREACH_TASK_KIND_LABELS, OUTRE
117
128
  });
118
129
  }
119
130
  OutreachStepCard.displayName = 'OutreachStepCard';
120
- function DelayField(props) {
121
- const { step, index, disabled } = props;
122
- const first = index === 0;
123
- const issue = props.issues['delayBusinessDays'];
131
+ /** The step's title and the controls that move or remove it. */ const StepHeader = /*#__PURE__*/ memo(function StepHeader(props) {
132
+ const { index, count, disabled } = props;
133
+ return /*#__PURE__*/ _jsxs(Stack, {
134
+ direction: "row",
135
+ spacing: 1,
136
+ sx: HEADER_ROW,
137
+ children: [
138
+ /*#__PURE__*/ _jsx(Typography, {
139
+ variant: "subtitle1",
140
+ sx: TITLE,
141
+ children: props.title
142
+ }),
143
+ /*#__PURE__*/ _jsx(Tooltip, {
144
+ title: "Move up",
145
+ children: /*#__PURE__*/ _jsx("span", {
146
+ children: /*#__PURE__*/ _jsx(IconButton, {
147
+ size: "small",
148
+ "aria-label": `Move step ${index + 1} up`,
149
+ disabled: disabled || index === 0,
150
+ onClick: props.onMoveUp,
151
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
152
+ path: mdiArrowUp.path,
153
+ fontSize: "small"
154
+ })
155
+ })
156
+ })
157
+ }),
158
+ /*#__PURE__*/ _jsx(Tooltip, {
159
+ title: "Move down",
160
+ children: /*#__PURE__*/ _jsx("span", {
161
+ children: /*#__PURE__*/ _jsx(IconButton, {
162
+ size: "small",
163
+ "aria-label": `Move step ${index + 1} down`,
164
+ disabled: disabled || index === count - 1,
165
+ onClick: props.onMoveDown,
166
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
167
+ path: mdiArrowDown.path,
168
+ fontSize: "small"
169
+ })
170
+ })
171
+ })
172
+ }),
173
+ /*#__PURE__*/ _jsx(Tooltip, {
174
+ title: "Remove",
175
+ children: /*#__PURE__*/ _jsx("span", {
176
+ children: /*#__PURE__*/ _jsx(IconButton, {
177
+ size: "small",
178
+ "aria-label": `Remove step ${index + 1}`,
179
+ disabled: disabled || count === 1,
180
+ onClick: props.onRemove,
181
+ children: /*#__PURE__*/ _jsx(MdiIcon, {
182
+ path: mdiTrashCanOutline.path,
183
+ fontSize: "small"
184
+ })
185
+ })
186
+ })
187
+ })
188
+ ]
189
+ });
190
+ });
191
+ const DelayField = /*#__PURE__*/ memo(function DelayField(props) {
192
+ const { first, value, issue } = props;
124
193
  return /*#__PURE__*/ _jsx(TextField, {
125
194
  type: "number",
126
195
  label: first ? 'Wait after enrolling (business days)' : 'Wait after the step before (business days)',
127
- value: Number.isFinite(step.delayBusinessDays) ? step.delayBusinessDays : '',
128
- onChange: (event)=>props.onChange(_extends({}, step, {
129
- delayBusinessDays: event.target.value === '' ? Number.NaN : Number(event.target.value)
130
- })),
131
- disabled: disabled,
196
+ value: Number.isFinite(value) ? value : '',
197
+ onChange: props.onChange,
198
+ disabled: props.disabled,
132
199
  error: Boolean(issue),
133
200
  helperText: issue != null ? issue : first ? '0 sends at the next opening of the sending hours.' : 'Counted in the mailbox’s timezone, skipping weekends.',
134
- slotProps: {
135
- htmlInput: {
136
- min: 0,
137
- max: OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS,
138
- step: 1
139
- }
140
- },
141
- sx: {
142
- maxWidth: {
143
- sm: 360
144
- }
145
- }
201
+ slotProps: DELAY_SLOT_PROPS,
202
+ sx: DELAY_FIELD
146
203
  });
147
- }
204
+ });
148
205
  function EmailStepFields(props) {
149
- var _props_templates_find, _props_sendTestDisabledReason;
206
+ var _props_templates_find;
150
207
  const { step, disabled, issues } = props;
208
+ // The step as last drawn, which every handler below writes from.
209
+ const latest = useRef(props);
210
+ latest.current = props;
151
211
  const subjectRef = useRef(null);
152
212
  const bodyRef = useRef(null);
153
213
  const lastFocused = useRef('body');
154
214
  const [fieldsAnchor, setFieldsAnchor] = useState(null);
155
215
  const template = (_props_templates_find = props.templates.find((option)=>option.id === step.templateId)) != null ? _props_templates_find : null;
156
216
  const showSubject = !props.inThread;
157
- /** Puts `{{key}}` where the caret was in the field last focused. */ const insertField = (key)=>{
217
+ const write = useCallback((patch)=>latest.current.onChange(_extends({}, latest.current.step, patch)), []);
218
+ const setReply = useCallback((event)=>write({
219
+ replyInThread: event.target.checked
220
+ }), [
221
+ write
222
+ ]);
223
+ const setSubject = useCallback((event)=>write({
224
+ subject: event.target.value
225
+ }), [
226
+ write
227
+ ]);
228
+ const setBody = useCallback((event)=>write({
229
+ body: event.target.value
230
+ }), [
231
+ write
232
+ ]);
233
+ const pickTemplate = useCallback((next)=>{
234
+ var _ref;
235
+ return write({
236
+ templateId: (_ref = next == null ? void 0 : next.id) != null ? _ref : null,
237
+ body: next ? '' : latest.current.step.body
238
+ });
239
+ }, [
240
+ write
241
+ ]);
242
+ const focusSubject = useCallback(()=>{
243
+ lastFocused.current = 'subject';
244
+ }, []);
245
+ const focusBody = useCallback(()=>{
246
+ lastFocused.current = 'body';
247
+ }, []);
248
+ const openFields = useCallback((event)=>setFieldsAnchor(event.currentTarget), []);
249
+ const closeFields = useCallback(()=>setFieldsAnchor(null), []);
250
+ const sendTest = useCallback(()=>latest.current.onSendTest == null ? void 0 : latest.current.onSendTest.call(latest.current), []);
251
+ /** Puts `{{key}}` where the caret was in the field last focused. */ const insertField = useCallback((key)=>{
158
252
  var _ref, _ref1;
159
253
  setFieldsAnchor(null);
254
+ const current = latest.current;
160
255
  const token = `{{${key}}}`;
161
- const target = lastFocused.current === 'subject' && showSubject ? 'subject' : template ? null : 'body';
256
+ const target = lastFocused.current === 'subject' && !current.inThread ? 'subject' : current.templates.some((option)=>option.id === current.step.templateId) ? null : 'body';
162
257
  if (!target) return;
163
258
  const element = target === 'subject' ? subjectRef.current : bodyRef.current;
164
- const value = step[target];
259
+ const value = current.step[target];
165
260
  const start = (_ref = element == null ? void 0 : element.selectionStart) != null ? _ref : value.length;
166
261
  const end = (_ref1 = element == null ? void 0 : element.selectionEnd) != null ? _ref1 : value.length;
167
- props.onChange(_extends({}, step, {
262
+ current.onChange(_extends({}, current.step, {
168
263
  [target]: `${value.slice(0, start)}${token}${value.slice(end)}`
169
264
  }));
170
265
  requestAnimationFrame(()=>{
171
266
  element == null ? void 0 : element.focus();
172
267
  element == null ? void 0 : element.setSelectionRange(start + token.length, start + token.length);
173
268
  });
174
- };
269
+ }, []);
175
270
  return /*#__PURE__*/ _jsxs(Stack, {
176
271
  spacing: 2,
177
272
  children: [
178
- props.firstEmail ? null : /*#__PURE__*/ _jsx(FormControlLabel, {
179
- control: /*#__PURE__*/ _jsx(Switch, {
180
- checked: step.replyInThread !== false,
181
- onChange: (event)=>props.onChange(_extends({}, step, {
182
- replyInThread: event.target.checked
183
- })),
184
- disabled: disabled
185
- }),
186
- label: "Reply in the same thread"
273
+ props.firstEmail ? null : /*#__PURE__*/ _jsx(ReplyInThreadField, {
274
+ checked: step.replyInThread !== false,
275
+ disabled: disabled,
276
+ onChange: setReply
187
277
  }),
188
- showSubject ? /*#__PURE__*/ _jsx(TextField, {
278
+ showSubject ? /*#__PURE__*/ _jsx(MemoTextField, {
189
279
  label: "Subject",
190
280
  value: step.subject,
191
281
  inputRef: subjectRef,
192
- onFocus: ()=>lastFocused.current = 'subject',
193
- onChange: (event)=>props.onChange(_extends({}, step, {
194
- subject: event.target.value
195
- })),
282
+ onFocus: focusSubject,
283
+ onChange: setSubject,
196
284
  disabled: disabled,
197
285
  error: Boolean(issues['subject']),
198
286
  helperText: issues['subject'],
@@ -202,41 +290,25 @@ function EmailStepFields(props) {
202
290
  color: "text.secondary",
203
291
  children: props.threadSubject ? `Sent as a reply: “Re: ${props.threadSubject}”.` : 'Sent as a reply in the thread the earlier email started.'
204
292
  }),
205
- /*#__PURE__*/ _jsx(Autocomplete, {
206
- options: [
207
- ...props.templates
208
- ],
209
- value: template,
210
- getOptionLabel: (option)=>option.name,
211
- isOptionEqualToValue: (option, value)=>option.id === value.id,
212
- onChange: (_event, next)=>{
213
- var _ref;
214
- return props.onChange(_extends({}, step, {
215
- templateId: (_ref = next == null ? void 0 : next.id) != null ? _ref : null,
216
- body: next ? '' : step.body
217
- }));
218
- },
293
+ /*#__PURE__*/ _jsx(TemplateField, {
294
+ templates: props.templates,
295
+ template: template,
219
296
  disabled: disabled,
220
- renderInput: (params)=>/*#__PURE__*/ _jsx(TextField, _extends({}, params, {
221
- label: "CRM email template",
222
- helperText: template ? 'The template’s body is sent; this step’s own subject is the one used.' : 'Optional. Pick one to send its body instead of writing it here.'
223
- }))
297
+ onChange: pickTemplate
224
298
  }),
225
- template ? /*#__PURE__*/ _jsx(TextField, {
299
+ template ? /*#__PURE__*/ _jsx(MemoTextField, {
226
300
  label: "Body (from the template)",
227
301
  value: template.body,
228
302
  multiline: true,
229
303
  minRows: 4,
230
304
  fullWidth: true,
231
305
  disabled: true
232
- }) : /*#__PURE__*/ _jsx(TextField, {
306
+ }) : /*#__PURE__*/ _jsx(MemoTextField, {
233
307
  label: "Body",
234
308
  value: step.body,
235
309
  inputRef: bodyRef,
236
- onFocus: ()=>lastFocused.current = 'body',
237
- onChange: (event)=>props.onChange(_extends({}, step, {
238
- body: event.target.value
239
- })),
310
+ onFocus: focusBody,
311
+ onChange: setBody,
240
312
  disabled: disabled,
241
313
  error: Boolean(issues['body']),
242
314
  helperText: issues['body'],
@@ -244,98 +316,131 @@ function EmailStepFields(props) {
244
316
  minRows: 6,
245
317
  fullWidth: true
246
318
  }),
247
- /*#__PURE__*/ _jsxs(Stack, {
248
- direction: "row",
249
- spacing: 1,
250
- sx: {
251
- flexWrap: 'wrap',
252
- rowGap: 1
253
- },
254
- children: [
255
- /*#__PURE__*/ _jsx(Button, {
319
+ /*#__PURE__*/ _jsx(EmailStepActions, {
320
+ stepNumber: props.index + 1,
321
+ insertDisabled: Boolean(disabled || template && !showSubject),
322
+ canSendTest: Boolean(props.onSendTest),
323
+ sendTestDisabledReason: props.sendTestDisabledReason,
324
+ onInsertField: openFields,
325
+ onSendTest: sendTest
326
+ }),
327
+ /*#__PURE__*/ _jsx(MergeFieldMenu, {
328
+ anchorEl: fieldsAnchor,
329
+ onClose: closeFields,
330
+ onPick: insertField
331
+ })
332
+ ]
333
+ });
334
+ }
335
+ const ReplyInThreadField = /*#__PURE__*/ memo(function ReplyInThreadField(props) {
336
+ return /*#__PURE__*/ _jsx(FormControlLabel, {
337
+ control: /*#__PURE__*/ _jsx(Switch, {
338
+ checked: props.checked,
339
+ onChange: props.onChange,
340
+ disabled: props.disabled
341
+ }),
342
+ label: "Reply in the same thread"
343
+ });
344
+ });
345
+ const TemplateField = /*#__PURE__*/ memo(function TemplateField(props) {
346
+ const { template, onChange } = props;
347
+ return /*#__PURE__*/ _jsx(Autocomplete, {
348
+ options: [
349
+ ...props.templates
350
+ ],
351
+ value: template,
352
+ getOptionLabel: templateName,
353
+ isOptionEqualToValue: sameTemplate,
354
+ onChange: (_event, next)=>onChange(next),
355
+ disabled: props.disabled,
356
+ renderInput: (params)=>/*#__PURE__*/ _jsx(TextField, _extends({}, params, {
357
+ label: "CRM email template",
358
+ helperText: template ? 'The template’s body is sent; this step’s own subject is the one used.' : 'Optional. Pick one to send its body instead of writing it here.'
359
+ }))
360
+ });
361
+ });
362
+ const EmailStepActions = /*#__PURE__*/ memo(function EmailStepActions(props) {
363
+ var _props_sendTestDisabledReason;
364
+ return /*#__PURE__*/ _jsxs(Stack, {
365
+ direction: "row",
366
+ spacing: 1,
367
+ sx: ACTIONS_ROW,
368
+ children: [
369
+ /*#__PURE__*/ _jsx(Button, {
370
+ size: "small",
371
+ variant: "outlined",
372
+ startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
373
+ path: mdiCodeBraces.path,
374
+ fontSize: "small"
375
+ }),
376
+ onClick: props.onInsertField,
377
+ disabled: props.insertDisabled,
378
+ "aria-haspopup": "menu",
379
+ children: "Insert field"
380
+ }),
381
+ props.canSendTest ? /*#__PURE__*/ _jsx(Tooltip, {
382
+ title: (_props_sendTestDisabledReason = props.sendTestDisabledReason) != null ? _props_sendTestDisabledReason : '',
383
+ children: /*#__PURE__*/ _jsx("span", {
384
+ children: /*#__PURE__*/ _jsx(Button, {
256
385
  size: "small",
257
386
  variant: "outlined",
258
387
  startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
259
- path: mdiCodeBraces.path,
388
+ path: mdiEmailFastOutline.path,
260
389
  fontSize: "small"
261
390
  }),
262
- onClick: (event)=>setFieldsAnchor(event.currentTarget),
263
- disabled: disabled || Boolean(template) && !showSubject,
264
- "aria-haspopup": "menu",
265
- children: "Insert field"
266
- }),
267
- props.onSendTest ? /*#__PURE__*/ _jsx(Tooltip, {
268
- title: (_props_sendTestDisabledReason = props.sendTestDisabledReason) != null ? _props_sendTestDisabledReason : '',
269
- children: /*#__PURE__*/ _jsx("span", {
270
- children: /*#__PURE__*/ _jsx(Button, {
271
- size: "small",
272
- variant: "outlined",
273
- startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
274
- path: mdiEmailFastOutline.path,
275
- fontSize: "small"
276
- }),
277
- onClick: props.onSendTest,
278
- disabled: Boolean(props.sendTestDisabledReason),
279
- "aria-label": `Send a test of step ${props.index + 1}`,
280
- children: "Send a test"
281
- })
282
- })
283
- }) : null
284
- ]
285
- }),
286
- /*#__PURE__*/ _jsx(Menu, {
287
- anchorEl: fieldsAnchor,
288
- open: Boolean(fieldsAnchor),
289
- onClose: ()=>setFieldsAnchor(null),
290
- children: FIELD_GROUPS.flatMap((group)=>[
291
- /*#__PURE__*/ _jsx(ListSubheader, {
292
- children: group
293
- }, `group-${group}`),
294
- ...OUTREACH_MERGE_FIELDS.filter((field)=>field.group === group).map((field)=>/*#__PURE__*/ _jsx(MenuItem, {
295
- onClick: ()=>insertField(field.key),
296
- children: field.label
297
- }, field.key))
298
- ])
299
- })
391
+ onClick: props.onSendTest,
392
+ disabled: Boolean(props.sendTestDisabledReason),
393
+ "aria-label": `Send a test of step ${props.stepNumber}`,
394
+ children: "Send a test"
395
+ })
396
+ })
397
+ }) : null
300
398
  ]
301
399
  });
302
- }
400
+ });
401
+ const MergeFieldMenu = /*#__PURE__*/ memo(function MergeFieldMenu(props) {
402
+ const { onPick } = props;
403
+ return /*#__PURE__*/ _jsx(Menu, {
404
+ anchorEl: props.anchorEl,
405
+ open: Boolean(props.anchorEl),
406
+ onClose: props.onClose,
407
+ children: FIELD_GROUPS.flatMap((group)=>[
408
+ /*#__PURE__*/ _jsx(ListSubheader, {
409
+ children: group
410
+ }, `group-${group}`),
411
+ ...OUTREACH_MERGE_FIELDS.filter((field)=>field.group === group).map((field)=>/*#__PURE__*/ _jsx(MenuItem, {
412
+ onClick: ()=>onPick(field.key),
413
+ children: field.label
414
+ }, field.key))
415
+ ])
416
+ });
417
+ });
303
418
  function TaskStepFields(props) {
304
419
  var _issues_title;
305
420
  const { step, disabled, issues } = props;
421
+ // The step as last drawn, which both handlers write from.
422
+ const latest = useRef(props);
423
+ latest.current = props;
424
+ const setKind = useCallback((event)=>latest.current.onChange(_extends({}, latest.current.step, {
425
+ taskKind: event.target.value
426
+ })), []);
427
+ const setTitle = useCallback((event)=>latest.current.onChange(_extends({}, latest.current.step, {
428
+ title: event.target.value
429
+ })), []);
306
430
  return /*#__PURE__*/ _jsxs(Stack, {
307
- direction: {
308
- xs: 'column',
309
- sm: 'row'
310
- },
431
+ direction: TASK_ROW,
311
432
  spacing: 2,
312
433
  children: [
313
- /*#__PURE__*/ _jsx(TextField, {
314
- select: true,
315
- label: "Task",
434
+ /*#__PURE__*/ _jsx(TaskKindField, {
316
435
  value: OUTREACH_TASK_KINDS.includes(step.taskKind) ? step.taskKind : '',
317
- onChange: (event)=>props.onChange(_extends({}, step, {
318
- taskKind: event.target.value
319
- })),
436
+ issue: issues['taskKind'],
320
437
  disabled: disabled,
321
- error: Boolean(issues['taskKind']),
322
- helperText: issues['taskKind'],
323
- sx: {
324
- minWidth: {
325
- sm: 180
326
- }
327
- },
328
- children: OUTREACH_TASK_KINDS.map((kind)=>/*#__PURE__*/ _jsx(MenuItem, {
329
- value: kind,
330
- children: OUTREACH_TASK_KIND_LABELS[kind]
331
- }, kind))
438
+ onChange: setKind
332
439
  }),
333
- /*#__PURE__*/ _jsx(TextField, {
440
+ /*#__PURE__*/ _jsx(MemoTextField, {
334
441
  label: "Title",
335
442
  value: step.title,
336
- onChange: (event)=>props.onChange(_extends({}, step, {
337
- title: event.target.value
338
- })),
443
+ onChange: setTitle,
339
444
  disabled: disabled,
340
445
  error: Boolean(issues['title']),
341
446
  helperText: (_issues_title = issues['title']) != null ? _issues_title : 'What the task says, for example “Connect on LinkedIn”.',
@@ -344,6 +449,19 @@ function TaskStepFields(props) {
344
449
  ]
345
450
  });
346
451
  }
452
+ const TaskKindField = /*#__PURE__*/ memo(function TaskKindField(props) {
453
+ return /*#__PURE__*/ _jsx(TextField, {
454
+ select: true,
455
+ label: "Task",
456
+ value: props.value,
457
+ onChange: props.onChange,
458
+ disabled: props.disabled,
459
+ error: Boolean(props.issue),
460
+ helperText: props.issue,
461
+ sx: TASK_KIND_FIELD,
462
+ children: TASK_KIND_MENU
463
+ });
464
+ });
347
465
  export default OutreachStepCard;
348
466
 
349
467
  //# sourceMappingURL=sequence-step-card.js.map