@aglyn/plugins-outreach 1.0.0-beta.195 → 1.0.0-beta.197

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 (90) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/curate-step-dialog.d.ts +50 -0
  3. package/src/lib/components/curate-step-dialog.js +235 -0
  4. package/src/lib/components/curate-step-dialog.js.map +1 -0
  5. package/src/lib/components/curated-step-editor.d.ts +80 -0
  6. package/src/lib/components/curated-step-editor.js +255 -0
  7. package/src/lib/components/curated-step-editor.js.map +1 -0
  8. package/src/lib/components/enroll-dialog.d.ts +15 -0
  9. package/src/lib/components/enroll-dialog.js +214 -14
  10. package/src/lib/components/enroll-dialog.js.map +1 -1
  11. package/src/lib/components/enrollments-table.d.ts +2 -0
  12. package/src/lib/components/enrollments-table.js +62 -8
  13. package/src/lib/components/enrollments-table.js.map +1 -1
  14. package/src/lib/components/mailbox-card.js +11 -2
  15. package/src/lib/components/mailbox-card.js.map +1 -1
  16. package/src/lib/components/sequence-detail.js +1 -0
  17. package/src/lib/components/sequence-detail.js.map +1 -1
  18. package/src/lib/components/sequence-editor.d.ts +2 -0
  19. package/src/lib/components/sequence-editor.js +26 -2
  20. package/src/lib/components/sequence-editor.js.map +1 -1
  21. package/src/lib/components/sequence-step-card.d.ts +7 -0
  22. package/src/lib/components/sequence-step-card.js +37 -14
  23. package/src/lib/components/sequence-step-card.js.map +1 -1
  24. package/src/lib/components/sequences-section.js +12 -3
  25. package/src/lib/components/sequences-section.js.map +1 -1
  26. package/src/lib/components/step-test-dialog.d.ts +31 -0
  27. package/src/lib/components/step-test-dialog.js +247 -0
  28. package/src/lib/components/step-test-dialog.js.map +1 -0
  29. package/src/lib/components/use-outreach-api.d.ts +15 -1
  30. package/src/lib/components/use-outreach-api.js +16 -0
  31. package/src/lib/components/use-outreach-api.js.map +1 -1
  32. package/src/lib/components/use-outreach-data.d.ts +6 -1
  33. package/src/lib/components/use-outreach-data.js +6 -3
  34. package/src/lib/components/use-outreach-data.js.map +1 -1
  35. package/src/lib/constants/api-routes.d.ts +15 -0
  36. package/src/lib/constants/api-routes.js +12 -0
  37. package/src/lib/constants/api-routes.js.map +1 -1
  38. package/src/lib/engine/compose.d.ts +12 -2
  39. package/src/lib/engine/compose.js +32 -6
  40. package/src/lib/engine/compose.js.map +1 -1
  41. package/src/lib/engine/curation-prompt.d.ts +73 -0
  42. package/src/lib/engine/curation-prompt.js +151 -0
  43. package/src/lib/engine/curation-prompt.js.map +1 -0
  44. package/src/lib/engine/enrollment-activity.d.ts +24 -0
  45. package/src/lib/engine/enrollment-activity.js +25 -0
  46. package/src/lib/engine/enrollment-activity.js.map +1 -1
  47. package/src/lib/engine/sequence-validation.d.ts +10 -1
  48. package/src/lib/engine/sequence-validation.js +54 -0
  49. package/src/lib/engine/sequence-validation.js.map +1 -1
  50. package/src/lib/enrollment/step-overrides.d.ts +53 -0
  51. package/src/lib/enrollment/step-overrides.js +140 -0
  52. package/src/lib/enrollment/step-overrides.js.map +1 -0
  53. package/src/lib/model/outreach-api.d.ts +89 -2
  54. package/src/lib/model/outreach-api.js.map +1 -1
  55. package/src/lib/model/outreach.types.d.ts +42 -0
  56. package/src/lib/model/outreach.types.js +18 -0
  57. package/src/lib/model/outreach.types.js.map +1 -1
  58. package/src/lib/model/step-preview.d.ts +10 -2
  59. package/src/lib/model/step-preview.js +9 -5
  60. package/src/lib/model/step-preview.js.map +1 -1
  61. package/src/lib/model/stored-records.d.ts +9 -1
  62. package/src/lib/model/stored-records.js +47 -2
  63. package/src/lib/model/stored-records.js.map +1 -1
  64. package/src/lib/routes/curate-routes.d.ts +63 -0
  65. package/src/lib/routes/curate-routes.js +424 -0
  66. package/src/lib/routes/curate-routes.js.map +1 -0
  67. package/src/lib/routes/enroll-routes.d.ts +7 -0
  68. package/src/lib/routes/enroll-routes.js +58 -4
  69. package/src/lib/routes/enroll-routes.js.map +1 -1
  70. package/src/lib/routes/platform-deps.d.ts +2 -2
  71. package/src/lib/routes/platform-deps.js +2 -2
  72. package/src/lib/routes/platform-deps.js.map +1 -1
  73. package/src/lib/routes/preview-routes.d.ts +24 -0
  74. package/src/lib/routes/preview-routes.js +103 -85
  75. package/src/lib/routes/preview-routes.js.map +1 -1
  76. package/src/lib/routes/register-routes.d.ts +7 -0
  77. package/src/lib/routes/register-routes.js +41 -0
  78. package/src/lib/routes/register-routes.js.map +1 -1
  79. package/src/lib/routes/step-test-routes.d.ts +88 -0
  80. package/src/lib/routes/step-test-routes.js +275 -0
  81. package/src/lib/routes/step-test-routes.js.map +1 -0
  82. package/src/lib/runtime/click-events.js +10 -0
  83. package/src/lib/runtime/click-events.js.map +1 -1
  84. package/src/lib/runtime/click-link.d.ts +11 -0
  85. package/src/lib/runtime/click-link.js +9 -4
  86. package/src/lib/runtime/click-link.js.map +1 -1
  87. package/src/lib/runtime/click-route.js +3 -0
  88. package/src/lib/runtime/click-route.js.map +1 -1
  89. package/src/lib/runtime/send-job.js +2 -0
  90. package/src/lib/runtime/send-job.js.map +1 -1
@@ -5,6 +5,8 @@ import type { OutreachSettingsLoad } from './use-outreach-settings';
5
5
  export interface OutreachSequenceEditorProps {
6
6
  orgId: string;
7
7
  orgMount?: ConsolePluginOrgMount;
8
+ /** The organization document, for the consent group a test send's person picker searches (AGL-3325). */
9
+ org?: Record<string, unknown> | null;
8
10
  /** The stored sequence, or `null` for a new one. */
9
11
  sequence: OutreachSequence | null;
10
12
  /** The organization's compliance settings: its countries, and the preview's footer. */
@@ -18,6 +18,7 @@ import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
19
19
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
20
20
  import { pluginDocsHelp } from "@aglyn/aglyn";
21
+ import { consentGroupForHost } from "@aglyn/aglyn/app-utils/consent-groups";
21
22
  import { mdiPlus } from "@aglyn/shared-data-mdi";
22
23
  import { CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
23
24
  import CampaignPicker from "@aglyn/shared-ui-email-campaigns/components/campaign-picker.component";
@@ -33,6 +34,7 @@ import { OutreachSendWindowFields } from "./send-window-fields.js";
33
34
  import { OutreachSequenceMailboxPicker } from "./sequence-mailbox-picker.js";
34
35
  import { OutreachSequencePreview } from "./sequence-preview.js";
35
36
  import { OutreachStepCard } from "./sequence-step-card.js";
37
+ import { OutreachStepTestDialog } from "./step-test-dialog.js";
36
38
  import { OutreachRouteError, useOutreachApi } from "./use-outreach-api.js";
37
39
  import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
38
40
  /** The step a dotted issue path is about, and the field within it. */ function stepIssueKey(path) {
@@ -56,7 +58,7 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
56
58
  * no longer change once people are enrolled) comes back from the save and
57
59
  * is shown in the same places.
58
60
  */ export function OutreachSequenceEditor(props) {
59
- var _ref, _ref1, _props_mailboxes_mailboxes_find, _ref2, _issueAt, _issueAt1;
61
+ var _ref, _ref1, _props_mailboxes_mailboxes_find, _ref2, _issueAt, _issueAt1, _props_org, _ref3;
60
62
  var _settings_settings, _settings_settings1, _issues_find, _hosts_find;
61
63
  const { orgId, orgMount, sequence, settings } = props;
62
64
  const api = useOutreachApi(orgId);
@@ -78,6 +80,8 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
78
80
  const [serverIssues, setServerIssues] = useState([]);
79
81
  const [saving, setSaving] = useState(false);
80
82
  const [taskAnchor, setTaskAnchor] = useState(null);
83
+ // The step whose test-send dialog is open (AGL-3325), by index.
84
+ const [testing, setTesting] = useState(null);
81
85
  // A stored sequence the listener updated (a save, a status change) is the
82
86
  // new starting point.
83
87
  useEffect(()=>{
@@ -215,6 +219,13 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
215
219
  ...draft.settings.allowedCountries
216
220
  ])
217
221
  ];
222
+ /*
223
+ * A test sends the step as STORED (AGL-3325): the route reads the saved
224
+ * sequence, so a new one has nothing to send yet and an edited one sends
225
+ * its last save — said on the button and in the dialog rather than found
226
+ * out in the inbox.
227
+ */ const unsaved = sequence !== null && JSON.stringify(draft) !== JSON.stringify(outreachSequenceDraftOf(sequence));
228
+ const sendTestDisabledReason = !sequence ? 'Create the sequence to send a test of it.' : !draft.mailboxId ? 'Choose a mailbox to send the test from.' : null;
218
229
  return /*#__PURE__*/ _jsxs(Grid, {
219
230
  container: true,
220
231
  spacing: 2,
@@ -318,7 +329,9 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
318
329
  onMove: (direction)=>moveStep(index, direction),
319
330
  onRemove: ()=>update({
320
331
  steps: draft.steps.filter((_entry, position)=>position !== index)
321
- })
332
+ }),
333
+ onSendTest: step.kind === 'email' && !archived ? ()=>setTesting(index) : undefined,
334
+ sendTestDisabledReason: sendTestDisabledReason
322
335
  }, step.id || `step-${index}`)),
323
336
  /*#__PURE__*/ _jsxs(Stack, {
324
337
  direction: "row",
@@ -514,6 +527,17 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
514
527
  ]
515
528
  })
516
529
  }),
530
+ sequence && testing !== null ? /*#__PURE__*/ _jsx(OutreachStepTestDialog, {
531
+ open: true,
532
+ onClose: ()=>setTesting(null),
533
+ orgId: orgId,
534
+ sequence: sequence,
535
+ stepIndex: testing,
536
+ contactGroupId: sequence.hostId ? consentGroupForHost((_props_org = props.org) != null ? _props_org : null, sequence.hostId).groupId : '',
537
+ selfEmail: (_ref3 = user == null ? void 0 : user.email) != null ? _ref3 : null,
538
+ unsaved: unsaved,
539
+ api: api
540
+ }) : null,
517
541
  /*#__PURE__*/ _jsx(Grid, {
518
542
  size: {
519
543
  xs: 12,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-editor.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp, type ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport CampaignPicker from '@aglyn/shared-ui-email-campaigns/components/campaign-picker.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useOrgCampaigns, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Autocomplete,\n Box,\n Button,\n Chip,\n FormControlLabel,\n Grid,\n Menu,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n isInThreadEmailStep,\n OUTREACH_SEQUENCE_NAME_MAX,\n validateOutreachSequence,\n} from '../engine/sequence-validation'\nimport { outreachCountryLabel } from '../model/compliance-settings'\nimport {\n OUTREACH_MAX_EMAIL_STEPS,\n OUTREACH_MAX_STEPS,\n OUTREACH_TASK_KIND_LABELS,\n OUTREACH_TASK_KINDS,\n type OutreachSequence,\n type OutreachSequenceStep,\n type OutreachTaskKind,\n} from '../model/outreach.types'\nimport {\n emptyOutreachSequenceDraft,\n newOutreachEmailStep,\n newOutreachTaskStep,\n outreachSequenceDraftOf,\n type OutreachSequenceDraft,\n type OutreachSequenceIssue,\n} from '../model/sequence-draft'\nimport { OutreachSendWindowFields } from './send-window-fields'\nimport { OutreachSequenceMailboxPicker } from './sequence-mailbox-picker'\nimport { OutreachSequencePreview } from './sequence-preview'\nimport { OutreachStepCard } from './sequence-step-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport { useOutreachEmailTemplates } from './use-outreach-crm'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport interface OutreachSequenceEditorProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The stored sequence, or `null` for a new one. */\n sequence: OutreachSequence | null\n /** The organization's compliance settings: its countries, and the preview's footer. */\n settings: OutreachSettingsLoad\n /**\n * The organization's mailboxes, from the Mailboxes section's listener:\n * what the picker offers, and who the preview's emails are from.\n */\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section, where the picker sends a member to connect one. */\n mailboxesPath: string\n onSaved(sequence: OutreachSequence): void\n onCancel?(): void\n}\n\n/** The step a dotted issue path is about, and the field within it. */\nfunction stepIssueKey(path: string): { index: number; field: string } | null {\n const match = /^steps\\.(\\d+)(?:\\.(.+))?$/.exec(path)\n if (!match) return null\n const field = match[2] ?? 'step'\n return { index: Number(match[1]), field: field === 'id' ? 'step' : field }\n}\n\n/**\n * The sequence editor (AGL-2980): the name, the site and mailbox, the steps,\n * who it sends to and when, beside a live preview of each email.\n *\n * Every issue the engine's validator finds is shown beside its field — the\n * same validator the save route refuses with — errors once a save was\n * attempted, warnings as soon as they appear. What only the server can\n * judge (whose mailbox, which countries the organization allows, what may\n * no longer change once people are enrolled) comes back from the save and\n * is shown in the same places.\n */\nexport function OutreachSequenceEditor(props: OutreachSequenceEditorProps) {\n const { orgId, orgMount, sequence, settings } = props\n const api = useOutreachApi(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const uid = user?.uid ?? null\n const mountedHosts = orgMount?.hosts\n const hosts = useMemo(() => mountedHosts ?? [], [mountedHosts])\n const orgCountries = settings.settings?.allowedCountries ?? ['US']\n\n const [draft, setDraft] = useState<OutreachSequenceDraft>(() =>\n sequence\n ? outreachSequenceDraftOf(sequence)\n : emptyOutreachSequenceDraft({\n hostId: hosts.length === 1 ? hosts[0].id : '',\n allowedCountries: orgCountries,\n }),\n )\n const [attempted, setAttempted] = useState(false)\n const [serverIssues, setServerIssues] = useState<OutreachSequenceIssue[]>([])\n const [saving, setSaving] = useState(false)\n const [taskAnchor, setTaskAnchor] = useState<HTMLElement | null>(null)\n\n // A stored sequence the listener updated (a save, a status change) is the\n // new starting point.\n useEffect(() => {\n if (sequence) setDraft(outreachSequenceDraftOf(sequence))\n }, [sequence])\n // A new sequence starts on the organization's countries once they are read.\n const settingsCountries = settings.settings?.allowedCountries.join(',')\n useEffect(() => {\n if (!sequence && settingsCountries) {\n setDraft((previous) => ({\n ...previous,\n settings: {\n ...previous.settings,\n allowedCountries: settingsCountries.split(','),\n },\n }))\n }\n }, [sequence, settingsCountries])\n useEffect(() => {\n if (!sequence && !draft.hostId && hosts.length === 1)\n setDraft((previous) => ({ ...previous, hostId: hosts[0].id }))\n }, [sequence, draft.hostId, hosts])\n\n // A new sequence starts on the member's own mailbox when they have exactly\n // one that is sending.\n const ownSending = props.mailboxes.mailboxes.filter(\n (entry) => entry.connectedByUid === uid && entry.status === 'connected',\n )\n const onlyOwnMailbox = ownSending.length === 1 ? ownSending[0].id : ''\n useEffect(() => {\n if (!sequence && !draft.mailboxId && onlyOwnMailbox)\n setDraft((previous) => ({ ...previous, mailboxId: onlyOwnMailbox }))\n }, [sequence, draft.mailboxId, onlyOwnMailbox])\n\n const templates = useOutreachEmailTemplates(orgId, draft.hostId || null, uid)\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === draft.mailboxId) ??\n null\n const archived = sequence?.status === 'archived'\n /*\n * The org's campaigns, for the picker (AGL-3254): read while the editor\n * is open. A sequence is the organization's record and so is a campaign,\n * so the choice is every campaign in the org, whichever site the\n * sequence sends as.\n */\n const campaigns = useOrgCampaigns(orgId, { enabled: true })\n\n const issues = useMemo(() => {\n const judged = [\n ...validateOutreachSequence(draft),\n ...serverIssues,\n ] as OutreachSequenceIssue[]\n return judged.filter(\n (issue) =>\n issue.severity === 'warning' ||\n attempted ||\n serverIssues.includes(issue),\n )\n }, [draft, serverIssues, attempted])\n const issueAt = (path: string) =>\n issues.find((issue) => issue.path === path)?.message\n const stepIssues = (index: number) => {\n const found: Record<string, string> = {}\n for (const issue of issues) {\n const key = stepIssueKey(issue.path)\n if (key?.index === index && !found[key.field])\n found[key.field] = issue.message\n }\n return found\n }\n const listIssue = issueAt('steps')\n const countriesIssue = issues.find((issue) =>\n issue.path.startsWith('settings.allowedCountries'),\n )?.message\n\n const update = (next: Partial<OutreachSequenceDraft>) => {\n setServerIssues([])\n setDraft((previous) => ({ ...previous, ...next }))\n }\n const setStep = (index: number, step: OutreachSequenceStep) =>\n update({\n steps: draft.steps.map((entry, position) =>\n position === index ? step : entry,\n ),\n })\n const moveStep = (index: number, direction: -1 | 1) => {\n const steps = [...draft.steps]\n const [moved] = steps.splice(index, 1)\n steps.splice(index + direction, 0, moved)\n update({ steps })\n }\n const emailCount = draft.steps.filter((step) => step.kind === 'email').length\n const full = draft.steps.length >= OUTREACH_MAX_STEPS\n const threadSubject = (index: number) => {\n for (let position = index - 1; position >= 0; position -= 1) {\n const step = draft.steps[position]\n if (step.kind === 'email' && !isInThreadEmailStep(draft.steps, position))\n return step.subject\n }\n return ''\n }\n\n const save = async () => {\n setAttempted(true)\n if (\n validateOutreachSequence(draft).some(\n (issue) => issue.severity === 'error',\n )\n )\n return\n setSaving(true)\n try {\n const answer = await api.saveSequence(sequence?.id ?? null, draft)\n setServerIssues([])\n setAttempted(false)\n enqueueSnackbar(\n answer.created ? 'Sequence created.' : 'Sequence saved.',\n { variant: 'success' },\n )\n props.onSaved(answer.sequence)\n } catch (error) {\n if (error instanceof OutreachRouteError && error.issues.length) {\n setServerIssues(\n error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n ),\n )\n }\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(false)\n }\n }\n\n const siteName = hosts.find((host) => host.id === draft.hostId)?.name ?? ''\n const countryOptions = [\n ...new Set([...orgCountries, ...draft.settings.allowedCountries]),\n ]\n\n return (\n <Grid container spacing={2}>\n <Grid size={{ xs: 12, md: 7 }}>\n <Stack spacing={2}>\n {archived ? (\n <Alert severity=\"info\">\n This sequence is archived, so it can’t be edited.\n </Alert>\n ) : null}\n <CardDisplay\n header=\"Sequence\"\n help={pluginDocsHelp('sequences', { anchor: '#build-a-sequence' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <TextField\n label=\"Name\"\n value={draft.name}\n onChange={(event) => update({ name: event.target.value })}\n disabled={archived}\n error={Boolean(issueAt('name'))}\n helperText={issueAt('name')}\n slotProps={{\n htmlInput: { maxLength: OUTREACH_SEQUENCE_NAME_MAX + 20 },\n }}\n fullWidth\n />\n <TextField\n select\n label=\"Site\"\n value={\n hosts.some((host) => host.id === draft.hostId)\n ? draft.hostId\n : ''\n }\n onChange={(event) => update({ hostId: event.target.value })}\n disabled={archived || !hosts.length}\n error={Boolean(issueAt('hostId'))}\n helperText={\n issueAt('hostId') ??\n 'The site whose contacts this sequence emails.'\n }\n fullWidth\n >\n {hosts.map((host) => (\n <MenuItem key={host.id} value={host.id}>\n {host.name}\n </MenuItem>\n ))}\n </TextField>\n <OutreachSequenceMailboxPicker\n orgId={orgId}\n uid={uid}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n value={draft.mailboxId}\n onChange={(mailboxId) => update({ mailboxId })}\n error={issueAt('mailboxId')}\n disabled={archived}\n />\n {/*\n The campaigns this sequence is part of (AGL-3254), picked the\n way a form's page picks them. Everyone enrolled joins them,\n and what the sequence produces is reported under them; it\n does not change who is enrolled or what is sent.\n */}\n <CampaignPicker\n options={campaigns.options}\n value={draft.campaignIds}\n onChange={(campaignIds) => update({ campaignIds })}\n label=\"Campaigns\"\n helperText={\n issueAt('campaignIds') ??\n 'The campaigns this sequence is part of. Everyone enrolled joins them, and its sends, replies, meetings and conversions count on their pages.'\n }\n disabled={archived}\n empty={campaigns.ready && !campaigns.options.length}\n emptyText=\"There are no campaigns yet. Create one from Marketing to file this sequence under it.\"\n />\n </Stack>\n </CardDisplay>\n\n {listIssue ? <Alert severity=\"error\">{listIssue}</Alert> : null}\n {draft.steps.map((step, index) => (\n <OutreachStepCard\n key={step.id || `step-${index}`}\n step={step}\n index={index}\n count={draft.steps.length}\n firstEmail={\n draft.steps.findIndex((entry) => entry.kind === 'email') ===\n index\n }\n inThread={isInThreadEmailStep(draft.steps, index)}\n threadSubject={threadSubject(index)}\n templates={templates.data}\n issues={stepIssues(index)}\n disabled={archived}\n onChange={(next) => setStep(index, next)}\n onMove={(direction) => moveStep(index, direction)}\n onRemove={() =>\n update({\n steps: draft.steps.filter(\n (_entry, position) => position !== index,\n ),\n })\n }\n />\n ))}\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', rowGap: 1 }}\n >\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n disabled={\n archived || full || emailCount >= OUTREACH_MAX_EMAIL_STEPS\n }\n onClick={() =>\n update({\n steps: [...draft.steps, newOutreachEmailStep(draft.steps)],\n })\n }\n >\n Add email\n </Button>\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n disabled={archived || full}\n onClick={(event) => setTaskAnchor(event.currentTarget)}\n aria-haspopup=\"menu\"\n >\n Add task\n </Button>\n <Menu\n anchorEl={taskAnchor}\n open={Boolean(taskAnchor)}\n onClose={() => setTaskAnchor(null)}\n >\n {OUTREACH_TASK_KINDS.map((kind: OutreachTaskKind) => (\n <MenuItem\n key={kind}\n onClick={() => {\n setTaskAnchor(null)\n update({\n steps: [\n ...draft.steps,\n newOutreachTaskStep(draft.steps, kind),\n ],\n })\n }}\n >\n {OUTREACH_TASK_KIND_LABELS[kind]}\n </MenuItem>\n ))}\n </Menu>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Up to ${OUTREACH_MAX_STEPS} steps, ${OUTREACH_MAX_EMAIL_STEPS} of them emails.`}\n </Typography>\n\n <CardDisplay\n header=\"Who it sends to, and when\"\n help={pluginDocsHelp('sequences', {\n anchor: '#build-a-sequence',\n excerpt:\n '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.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Autocomplete<string, true>\n multiple\n options={countryOptions}\n value={draft.settings.allowedCountries}\n getOptionLabel={outreachCountryLabel}\n onChange={(_event, next) =>\n update({\n settings: { ...draft.settings, allowedCountries: next },\n })\n }\n disabled={archived}\n renderValue={(value, getItemProps) =>\n value.map((code, index) => {\n const { key, ...item } = getItemProps({ index })\n return (\n <Chip\n key={key}\n size=\"small\"\n label={outreachCountryLabel(code)}\n {...item}\n />\n )\n })\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Countries\"\n error={Boolean(countriesIssue)}\n helperText={\n countriesIssue ??\n 'Among the countries your organization allows. Cold contacts are emailed only in the United States.'\n }\n />\n )}\n />\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.allowCustomers}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n allowCustomers: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Include customers\"\n />\n {/*\n * Click tracking (AGL-3239), with its cost stated where it is\n * chosen rather than in a document nobody opens. Off by\n * default: it changes the link a cold recipient sees, and the\n * emails stay plain text either way — there is no open\n * tracking to turn on, because a tracking image needs an HTML\n * email and these are not.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.trackClicks}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n trackClicks: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Count link clicks\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Links in these emails are replaced with links of ours that\n forward to the same page, so clicks can be counted. The\n recipient sees the replacement, not your address. Opens\n aren’t counted either way: these emails are plain text, and\n counting an open needs a tracking image.\n </Typography>\n </Stack>\n {/*\n * The mail-client unsubscribe button (AGL-3296), off by\n * default: the header is what makes Apple Mail, Gmail and\n * Outlook present a one-to-one email as a mailing list. The\n * footer's \"reply 'no'\" line is the way out either way.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.listUnsubscribe}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n listUnsubscribe: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Add a mail-client unsubscribe button\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Adds the List-Unsubscribe header, so the recipient’s mail\n app shows its own Unsubscribe button. It’s the easiest way\n out for them, and fewer people mark you as spam when they\n have it, but mail apps then label the email as coming from\n a mailing list. Off, the footer’s “reply ‘no’” line is the\n way out, and it’s always there.\n </Typography>\n </Stack>\n <OutreachSendWindowFields\n window={draft.settings.window}\n onChange={(window) =>\n update({ settings: { ...draft.settings, window } })\n }\n daysIssue={issueAt('settings.window.days')}\n hoursIssue={issueAt('settings.window')}\n disabled={archived}\n />\n </Stack>\n </CardDisplay>\n\n {archived ? null : (\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ justifyContent: 'flex-end' }}\n >\n {props.onCancel ? (\n <Button\n variant=\"text\"\n onClick={props.onCancel}\n disabled={saving}\n >\n Cancel\n </Button>\n ) : null}\n <Button\n variant=\"contained\"\n onClick={() => void save()}\n disabled={saving}\n >\n {saving ? 'Saving…' : sequence ? 'Save' : 'Create sequence'}\n </Button>\n </Stack>\n )}\n </Stack>\n </Grid>\n <Grid size={{ xs: 12, md: 5 }}>\n <Box sx={{ position: { md: 'sticky' }, top: { md: 16 } }}>\n <OutreachSequencePreview\n steps={draft.steps}\n orgSettings={settings.settings}\n orgSettingsStatus={settings.status}\n templates={templates.data}\n sender={\n mailbox\n ? {\n name: mailbox.displayName,\n email: mailbox.sendAs || mailbox.email,\n }\n : null\n }\n siteName={siteName}\n />\n </Box>\n </Grid>\n </Grid>\n )\n}\nOutreachSequenceEditor.displayName = 'OutreachSequenceEditor'\n\nexport default OutreachSequenceEditor\n"],"names":["pluginDocsHelp","mdiPlus","CardDisplay","MdiIcon","CampaignPicker","useSnackbar","useOrgCampaigns","useUser","Alert","Autocomplete","Box","Button","Chip","FormControlLabel","Grid","Menu","MenuItem","Stack","Switch","TextField","Typography","useEffect","useMemo","useState","isInThreadEmailStep","OUTREACH_SEQUENCE_NAME_MAX","validateOutreachSequence","outreachCountryLabel","OUTREACH_MAX_EMAIL_STEPS","OUTREACH_MAX_STEPS","OUTREACH_TASK_KIND_LABELS","OUTREACH_TASK_KINDS","emptyOutreachSequenceDraft","newOutreachEmailStep","newOutreachTaskStep","outreachSequenceDraftOf","OutreachSendWindowFields","OutreachSequenceMailboxPicker","OutreachSequencePreview","OutreachStepCard","OutreachRouteError","useOutreachApi","useOutreachEmailTemplates","stepIssueKey","path","match","exec","field","index","Number","OutreachSequenceEditor","props","issueAt","settings","issues","hosts","orgId","orgMount","sequence","api","enqueueSnackbar","data","user","uid","mountedHosts","orgCountries","allowedCountries","draft","setDraft","hostId","length","id","attempted","setAttempted","serverIssues","setServerIssues","saving","setSaving","taskAnchor","setTaskAnchor","settingsCountries","join","previous","split","ownSending","mailboxes","filter","entry","connectedByUid","status","onlyOwnMailbox","mailboxId","templates","mailbox","find","archived","campaigns","enabled","judged","issue","severity","includes","message","stepIssues","found","key","listIssue","countriesIssue","startsWith","update","next","setStep","step","steps","map","position","moveStep","direction","moved","splice","emailCount","kind","full","threadSubject","subject","save","some","answer","saveSequence","created","variant","onSaved","error","allowDuplicate","siteName","host","name","countryOptions","Set","container","spacing","size","xs","md","header","help","anchor","contentGutterX","contentGutterY","label","value","onChange","event","target","disabled","Boolean","helperText","slotProps","htmlInput","maxLength","fullWidth","select","mailboxesPath","options","campaignIds","empty","ready","emptyText","count","firstEmail","findIndex","inThread","onMove","onRemove","_entry","sx","flexWrap","rowGap","startIcon","fontSize","onClick","currentTarget","aria-haspopup","anchorEl","open","onClose","color","excerpt","multiple","getOptionLabel","_event","renderValue","getItemProps","code","item","renderInput","params","control","checked","allowCustomers","trackClicks","listUnsubscribe","window","daysIssue","hoursIssue","sm","justifyContent","onCancel","top","orgSettings","orgSettingsStatus","sender","displayName","email","sendAs"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-editor.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp, type ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport CampaignPicker from '@aglyn/shared-ui-email-campaigns/components/campaign-picker.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useOrgCampaigns, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Autocomplete,\n Box,\n Button,\n Chip,\n FormControlLabel,\n Grid,\n Menu,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n isInThreadEmailStep,\n OUTREACH_SEQUENCE_NAME_MAX,\n validateOutreachSequence,\n} from '../engine/sequence-validation'\nimport { outreachCountryLabel } from '../model/compliance-settings'\nimport {\n OUTREACH_MAX_EMAIL_STEPS,\n OUTREACH_MAX_STEPS,\n OUTREACH_TASK_KIND_LABELS,\n OUTREACH_TASK_KINDS,\n type OutreachSequence,\n type OutreachSequenceStep,\n type OutreachTaskKind,\n} from '../model/outreach.types'\nimport {\n emptyOutreachSequenceDraft,\n newOutreachEmailStep,\n newOutreachTaskStep,\n outreachSequenceDraftOf,\n type OutreachSequenceDraft,\n type OutreachSequenceIssue,\n} from '../model/sequence-draft'\nimport { OutreachSendWindowFields } from './send-window-fields'\nimport { OutreachSequenceMailboxPicker } from './sequence-mailbox-picker'\nimport { OutreachSequencePreview } from './sequence-preview'\nimport { OutreachStepCard } from './sequence-step-card'\nimport { OutreachStepTestDialog } from './step-test-dialog'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport { useOutreachEmailTemplates } from './use-outreach-crm'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport interface OutreachSequenceEditorProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document, for the consent group a test send's person picker searches (AGL-3325). */\n org?: Record<string, unknown> | null\n /** The stored sequence, or `null` for a new one. */\n sequence: OutreachSequence | null\n /** The organization's compliance settings: its countries, and the preview's footer. */\n settings: OutreachSettingsLoad\n /**\n * The organization's mailboxes, from the Mailboxes section's listener:\n * what the picker offers, and who the preview's emails are from.\n */\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section, where the picker sends a member to connect one. */\n mailboxesPath: string\n onSaved(sequence: OutreachSequence): void\n onCancel?(): void\n}\n\n/** The step a dotted issue path is about, and the field within it. */\nfunction stepIssueKey(path: string): { index: number; field: string } | null {\n const match = /^steps\\.(\\d+)(?:\\.(.+))?$/.exec(path)\n if (!match) return null\n const field = match[2] ?? 'step'\n return { index: Number(match[1]), field: field === 'id' ? 'step' : field }\n}\n\n/**\n * The sequence editor (AGL-2980): the name, the site and mailbox, the steps,\n * who it sends to and when, beside a live preview of each email.\n *\n * Every issue the engine's validator finds is shown beside its field — the\n * same validator the save route refuses with — errors once a save was\n * attempted, warnings as soon as they appear. What only the server can\n * judge (whose mailbox, which countries the organization allows, what may\n * no longer change once people are enrolled) comes back from the save and\n * is shown in the same places.\n */\nexport function OutreachSequenceEditor(props: OutreachSequenceEditorProps) {\n const { orgId, orgMount, sequence, settings } = props\n const api = useOutreachApi(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const uid = user?.uid ?? null\n const mountedHosts = orgMount?.hosts\n const hosts = useMemo(() => mountedHosts ?? [], [mountedHosts])\n const orgCountries = settings.settings?.allowedCountries ?? ['US']\n\n const [draft, setDraft] = useState<OutreachSequenceDraft>(() =>\n sequence\n ? outreachSequenceDraftOf(sequence)\n : emptyOutreachSequenceDraft({\n hostId: hosts.length === 1 ? hosts[0].id : '',\n allowedCountries: orgCountries,\n }),\n )\n const [attempted, setAttempted] = useState(false)\n const [serverIssues, setServerIssues] = useState<OutreachSequenceIssue[]>([])\n const [saving, setSaving] = useState(false)\n const [taskAnchor, setTaskAnchor] = useState<HTMLElement | null>(null)\n // The step whose test-send dialog is open (AGL-3325), by index.\n const [testing, setTesting] = useState<number | null>(null)\n\n // A stored sequence the listener updated (a save, a status change) is the\n // new starting point.\n useEffect(() => {\n if (sequence) setDraft(outreachSequenceDraftOf(sequence))\n }, [sequence])\n // A new sequence starts on the organization's countries once they are read.\n const settingsCountries = settings.settings?.allowedCountries.join(',')\n useEffect(() => {\n if (!sequence && settingsCountries) {\n setDraft((previous) => ({\n ...previous,\n settings: {\n ...previous.settings,\n allowedCountries: settingsCountries.split(','),\n },\n }))\n }\n }, [sequence, settingsCountries])\n useEffect(() => {\n if (!sequence && !draft.hostId && hosts.length === 1)\n setDraft((previous) => ({ ...previous, hostId: hosts[0].id }))\n }, [sequence, draft.hostId, hosts])\n\n // A new sequence starts on the member's own mailbox when they have exactly\n // one that is sending.\n const ownSending = props.mailboxes.mailboxes.filter(\n (entry) => entry.connectedByUid === uid && entry.status === 'connected',\n )\n const onlyOwnMailbox = ownSending.length === 1 ? ownSending[0].id : ''\n useEffect(() => {\n if (!sequence && !draft.mailboxId && onlyOwnMailbox)\n setDraft((previous) => ({ ...previous, mailboxId: onlyOwnMailbox }))\n }, [sequence, draft.mailboxId, onlyOwnMailbox])\n\n const templates = useOutreachEmailTemplates(orgId, draft.hostId || null, uid)\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === draft.mailboxId) ??\n null\n const archived = sequence?.status === 'archived'\n /*\n * The org's campaigns, for the picker (AGL-3254): read while the editor\n * is open. A sequence is the organization's record and so is a campaign,\n * so the choice is every campaign in the org, whichever site the\n * sequence sends as.\n */\n const campaigns = useOrgCampaigns(orgId, { enabled: true })\n\n const issues = useMemo(() => {\n const judged = [\n ...validateOutreachSequence(draft),\n ...serverIssues,\n ] as OutreachSequenceIssue[]\n return judged.filter(\n (issue) =>\n issue.severity === 'warning' ||\n attempted ||\n serverIssues.includes(issue),\n )\n }, [draft, serverIssues, attempted])\n const issueAt = (path: string) =>\n issues.find((issue) => issue.path === path)?.message\n const stepIssues = (index: number) => {\n const found: Record<string, string> = {}\n for (const issue of issues) {\n const key = stepIssueKey(issue.path)\n if (key?.index === index && !found[key.field])\n found[key.field] = issue.message\n }\n return found\n }\n const listIssue = issueAt('steps')\n const countriesIssue = issues.find((issue) =>\n issue.path.startsWith('settings.allowedCountries'),\n )?.message\n\n const update = (next: Partial<OutreachSequenceDraft>) => {\n setServerIssues([])\n setDraft((previous) => ({ ...previous, ...next }))\n }\n const setStep = (index: number, step: OutreachSequenceStep) =>\n update({\n steps: draft.steps.map((entry, position) =>\n position === index ? step : entry,\n ),\n })\n const moveStep = (index: number, direction: -1 | 1) => {\n const steps = [...draft.steps]\n const [moved] = steps.splice(index, 1)\n steps.splice(index + direction, 0, moved)\n update({ steps })\n }\n const emailCount = draft.steps.filter((step) => step.kind === 'email').length\n const full = draft.steps.length >= OUTREACH_MAX_STEPS\n const threadSubject = (index: number) => {\n for (let position = index - 1; position >= 0; position -= 1) {\n const step = draft.steps[position]\n if (step.kind === 'email' && !isInThreadEmailStep(draft.steps, position))\n return step.subject\n }\n return ''\n }\n\n const save = async () => {\n setAttempted(true)\n if (\n validateOutreachSequence(draft).some(\n (issue) => issue.severity === 'error',\n )\n )\n return\n setSaving(true)\n try {\n const answer = await api.saveSequence(sequence?.id ?? null, draft)\n setServerIssues([])\n setAttempted(false)\n enqueueSnackbar(\n answer.created ? 'Sequence created.' : 'Sequence saved.',\n { variant: 'success' },\n )\n props.onSaved(answer.sequence)\n } catch (error) {\n if (error instanceof OutreachRouteError && error.issues.length) {\n setServerIssues(\n error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n ),\n )\n }\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(false)\n }\n }\n\n const siteName = hosts.find((host) => host.id === draft.hostId)?.name ?? ''\n const countryOptions = [\n ...new Set([...orgCountries, ...draft.settings.allowedCountries]),\n ]\n /*\n * A test sends the step as STORED (AGL-3325): the route reads the saved\n * sequence, so a new one has nothing to send yet and an edited one sends\n * its last save — said on the button and in the dialog rather than found\n * out in the inbox.\n */\n const unsaved =\n sequence !== null &&\n JSON.stringify(draft) !== JSON.stringify(outreachSequenceDraftOf(sequence))\n const sendTestDisabledReason = !sequence\n ? 'Create the sequence to send a test of it.'\n : !draft.mailboxId\n ? 'Choose a mailbox to send the test from.'\n : null\n\n return (\n <Grid container spacing={2}>\n <Grid size={{ xs: 12, md: 7 }}>\n <Stack spacing={2}>\n {archived ? (\n <Alert severity=\"info\">\n This sequence is archived, so it can’t be edited.\n </Alert>\n ) : null}\n <CardDisplay\n header=\"Sequence\"\n help={pluginDocsHelp('sequences', { anchor: '#build-a-sequence' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <TextField\n label=\"Name\"\n value={draft.name}\n onChange={(event) => update({ name: event.target.value })}\n disabled={archived}\n error={Boolean(issueAt('name'))}\n helperText={issueAt('name')}\n slotProps={{\n htmlInput: { maxLength: OUTREACH_SEQUENCE_NAME_MAX + 20 },\n }}\n fullWidth\n />\n <TextField\n select\n label=\"Site\"\n value={\n hosts.some((host) => host.id === draft.hostId)\n ? draft.hostId\n : ''\n }\n onChange={(event) => update({ hostId: event.target.value })}\n disabled={archived || !hosts.length}\n error={Boolean(issueAt('hostId'))}\n helperText={\n issueAt('hostId') ??\n 'The site whose contacts this sequence emails.'\n }\n fullWidth\n >\n {hosts.map((host) => (\n <MenuItem key={host.id} value={host.id}>\n {host.name}\n </MenuItem>\n ))}\n </TextField>\n <OutreachSequenceMailboxPicker\n orgId={orgId}\n uid={uid}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n value={draft.mailboxId}\n onChange={(mailboxId) => update({ mailboxId })}\n error={issueAt('mailboxId')}\n disabled={archived}\n />\n {/*\n The campaigns this sequence is part of (AGL-3254), picked the\n way a form's page picks them. Everyone enrolled joins them,\n and what the sequence produces is reported under them; it\n does not change who is enrolled or what is sent.\n */}\n <CampaignPicker\n options={campaigns.options}\n value={draft.campaignIds}\n onChange={(campaignIds) => update({ campaignIds })}\n label=\"Campaigns\"\n helperText={\n issueAt('campaignIds') ??\n 'The campaigns this sequence is part of. Everyone enrolled joins them, and its sends, replies, meetings and conversions count on their pages.'\n }\n disabled={archived}\n empty={campaigns.ready && !campaigns.options.length}\n emptyText=\"There are no campaigns yet. Create one from Marketing to file this sequence under it.\"\n />\n </Stack>\n </CardDisplay>\n\n {listIssue ? <Alert severity=\"error\">{listIssue}</Alert> : null}\n {draft.steps.map((step, index) => (\n <OutreachStepCard\n key={step.id || `step-${index}`}\n step={step}\n index={index}\n count={draft.steps.length}\n firstEmail={\n draft.steps.findIndex((entry) => entry.kind === 'email') ===\n index\n }\n inThread={isInThreadEmailStep(draft.steps, index)}\n threadSubject={threadSubject(index)}\n templates={templates.data}\n issues={stepIssues(index)}\n disabled={archived}\n onChange={(next) => setStep(index, next)}\n onMove={(direction) => moveStep(index, direction)}\n onRemove={() =>\n update({\n steps: draft.steps.filter(\n (_entry, position) => position !== index,\n ),\n })\n }\n onSendTest={\n step.kind === 'email' && !archived\n ? () => setTesting(index)\n : undefined\n }\n sendTestDisabledReason={sendTestDisabledReason}\n />\n ))}\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', rowGap: 1 }}\n >\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n disabled={\n archived || full || emailCount >= OUTREACH_MAX_EMAIL_STEPS\n }\n onClick={() =>\n update({\n steps: [...draft.steps, newOutreachEmailStep(draft.steps)],\n })\n }\n >\n Add email\n </Button>\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n disabled={archived || full}\n onClick={(event) => setTaskAnchor(event.currentTarget)}\n aria-haspopup=\"menu\"\n >\n Add task\n </Button>\n <Menu\n anchorEl={taskAnchor}\n open={Boolean(taskAnchor)}\n onClose={() => setTaskAnchor(null)}\n >\n {OUTREACH_TASK_KINDS.map((kind: OutreachTaskKind) => (\n <MenuItem\n key={kind}\n onClick={() => {\n setTaskAnchor(null)\n update({\n steps: [\n ...draft.steps,\n newOutreachTaskStep(draft.steps, kind),\n ],\n })\n }}\n >\n {OUTREACH_TASK_KIND_LABELS[kind]}\n </MenuItem>\n ))}\n </Menu>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Up to ${OUTREACH_MAX_STEPS} steps, ${OUTREACH_MAX_EMAIL_STEPS} of them emails.`}\n </Typography>\n\n <CardDisplay\n header=\"Who it sends to, and when\"\n help={pluginDocsHelp('sequences', {\n anchor: '#build-a-sequence',\n excerpt:\n '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.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Autocomplete<string, true>\n multiple\n options={countryOptions}\n value={draft.settings.allowedCountries}\n getOptionLabel={outreachCountryLabel}\n onChange={(_event, next) =>\n update({\n settings: { ...draft.settings, allowedCountries: next },\n })\n }\n disabled={archived}\n renderValue={(value, getItemProps) =>\n value.map((code, index) => {\n const { key, ...item } = getItemProps({ index })\n return (\n <Chip\n key={key}\n size=\"small\"\n label={outreachCountryLabel(code)}\n {...item}\n />\n )\n })\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Countries\"\n error={Boolean(countriesIssue)}\n helperText={\n countriesIssue ??\n 'Among the countries your organization allows. Cold contacts are emailed only in the United States.'\n }\n />\n )}\n />\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.allowCustomers}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n allowCustomers: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Include customers\"\n />\n {/*\n * Click tracking (AGL-3239), with its cost stated where it is\n * chosen rather than in a document nobody opens. Off by\n * default: it changes the link a cold recipient sees, and the\n * emails stay plain text either way — there is no open\n * tracking to turn on, because a tracking image needs an HTML\n * email and these are not.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.trackClicks}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n trackClicks: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Count link clicks\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Links in these emails are replaced with links of ours that\n forward to the same page, so clicks can be counted. The\n recipient sees the replacement, not your address. Opens\n aren’t counted either way: these emails are plain text, and\n counting an open needs a tracking image.\n </Typography>\n </Stack>\n {/*\n * The mail-client unsubscribe button (AGL-3296), off by\n * default: the header is what makes Apple Mail, Gmail and\n * Outlook present a one-to-one email as a mailing list. The\n * footer's \"reply 'no'\" line is the way out either way.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.listUnsubscribe}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n listUnsubscribe: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Add a mail-client unsubscribe button\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Adds the List-Unsubscribe header, so the recipient’s mail\n app shows its own Unsubscribe button. It’s the easiest way\n out for them, and fewer people mark you as spam when they\n have it, but mail apps then label the email as coming from\n a mailing list. Off, the footer’s “reply ‘no’” line is the\n way out, and it’s always there.\n </Typography>\n </Stack>\n <OutreachSendWindowFields\n window={draft.settings.window}\n onChange={(window) =>\n update({ settings: { ...draft.settings, window } })\n }\n daysIssue={issueAt('settings.window.days')}\n hoursIssue={issueAt('settings.window')}\n disabled={archived}\n />\n </Stack>\n </CardDisplay>\n\n {archived ? null : (\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ justifyContent: 'flex-end' }}\n >\n {props.onCancel ? (\n <Button\n variant=\"text\"\n onClick={props.onCancel}\n disabled={saving}\n >\n Cancel\n </Button>\n ) : null}\n <Button\n variant=\"contained\"\n onClick={() => void save()}\n disabled={saving}\n >\n {saving ? 'Saving…' : sequence ? 'Save' : 'Create sequence'}\n </Button>\n </Stack>\n )}\n </Stack>\n </Grid>\n {sequence && testing !== null ? (\n <OutreachStepTestDialog\n open\n onClose={() => setTesting(null)}\n orgId={orgId}\n sequence={sequence}\n stepIndex={testing}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n selfEmail={user?.email ?? null}\n unsaved={unsaved}\n api={api}\n />\n ) : null}\n <Grid size={{ xs: 12, md: 5 }}>\n <Box sx={{ position: { md: 'sticky' }, top: { md: 16 } }}>\n <OutreachSequencePreview\n steps={draft.steps}\n orgSettings={settings.settings}\n orgSettingsStatus={settings.status}\n templates={templates.data}\n sender={\n mailbox\n ? {\n name: mailbox.displayName,\n email: mailbox.sendAs || mailbox.email,\n }\n : null\n }\n siteName={siteName}\n />\n </Box>\n </Grid>\n </Grid>\n )\n}\nOutreachSequenceEditor.displayName = 'OutreachSequenceEditor'\n\nexport default OutreachSequenceEditor\n"],"names":["pluginDocsHelp","consentGroupForHost","mdiPlus","CardDisplay","MdiIcon","CampaignPicker","useSnackbar","useOrgCampaigns","useUser","Alert","Autocomplete","Box","Button","Chip","FormControlLabel","Grid","Menu","MenuItem","Stack","Switch","TextField","Typography","useEffect","useMemo","useState","isInThreadEmailStep","OUTREACH_SEQUENCE_NAME_MAX","validateOutreachSequence","outreachCountryLabel","OUTREACH_MAX_EMAIL_STEPS","OUTREACH_MAX_STEPS","OUTREACH_TASK_KIND_LABELS","OUTREACH_TASK_KINDS","emptyOutreachSequenceDraft","newOutreachEmailStep","newOutreachTaskStep","outreachSequenceDraftOf","OutreachSendWindowFields","OutreachSequenceMailboxPicker","OutreachSequencePreview","OutreachStepCard","OutreachStepTestDialog","OutreachRouteError","useOutreachApi","useOutreachEmailTemplates","stepIssueKey","path","match","exec","field","index","Number","OutreachSequenceEditor","props","issueAt","settings","issues","hosts","orgId","orgMount","sequence","api","enqueueSnackbar","data","user","uid","mountedHosts","orgCountries","allowedCountries","draft","setDraft","hostId","length","id","attempted","setAttempted","serverIssues","setServerIssues","saving","setSaving","taskAnchor","setTaskAnchor","testing","setTesting","settingsCountries","join","previous","split","ownSending","mailboxes","filter","entry","connectedByUid","status","onlyOwnMailbox","mailboxId","templates","mailbox","find","archived","campaigns","enabled","judged","issue","severity","includes","message","stepIssues","found","key","listIssue","countriesIssue","startsWith","update","next","setStep","step","steps","map","position","moveStep","direction","moved","splice","emailCount","kind","full","threadSubject","subject","save","some","answer","saveSequence","created","variant","onSaved","error","allowDuplicate","siteName","host","name","countryOptions","Set","unsaved","JSON","stringify","sendTestDisabledReason","container","spacing","size","xs","md","header","help","anchor","contentGutterX","contentGutterY","label","value","onChange","event","target","disabled","Boolean","helperText","slotProps","htmlInput","maxLength","fullWidth","select","mailboxesPath","options","campaignIds","empty","ready","emptyText","count","firstEmail","findIndex","inThread","onMove","onRemove","_entry","onSendTest","undefined","sx","flexWrap","rowGap","startIcon","fontSize","onClick","currentTarget","aria-haspopup","anchorEl","open","onClose","color","excerpt","multiple","getOptionLabel","_event","renderValue","getItemProps","code","item","renderInput","params","control","checked","allowCustomers","trackClicks","listUnsubscribe","window","daysIssue","hoursIssue","sm","justifyContent","onCancel","stepIndex","contactGroupId","org","groupId","selfEmail","email","top","orgSettings","orgSettingsStatus","sender","displayName","sendAs"],"mappings":"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"}
@@ -19,6 +19,13 @@ export interface OutreachStepCardProps {
19
19
  onChange(step: OutreachSequenceStep): void;
20
20
  onMove(direction: -1 | 1): void;
21
21
  onRemove(): void;
22
+ /**
23
+ * Opens the test-send dialog for this email step (AGL-3325). Absent for a
24
+ * step that cannot be tested — a task, or an archived sequence.
25
+ */
26
+ onSendTest?(): void;
27
+ /** Why the test cannot be sent yet, shown on the disabled button; `null` when it can. */
28
+ sendTestDisabledReason?: string | null;
22
29
  }
23
30
  /**
24
31
  * One step of the sequence editor (AGL-2980): an email — subject, body or a
@@ -16,7 +16,7 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
- import { mdiArrowDown, mdiArrowUp, mdiCodeBraces, mdiTrashCanOutline } from "@aglyn/shared-data-mdi";
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
22
  import { useRef, useState } from "react";
@@ -146,7 +146,7 @@ function DelayField(props) {
146
146
  });
147
147
  }
148
148
  function EmailStepFields(props) {
149
- var _props_templates_find;
149
+ var _props_templates_find, _props_sendTestDisabledReason;
150
150
  const { step, disabled, issues } = props;
151
151
  const subjectRef = useRef(null);
152
152
  const bodyRef = useRef(null);
@@ -244,21 +244,44 @@ function EmailStepFields(props) {
244
244
  minRows: 6,
245
245
  fullWidth: true
246
246
  }),
247
- /*#__PURE__*/ _jsx(Stack, {
247
+ /*#__PURE__*/ _jsxs(Stack, {
248
248
  direction: "row",
249
249
  spacing: 1,
250
- children: /*#__PURE__*/ _jsx(Button, {
251
- size: "small",
252
- variant: "outlined",
253
- startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
254
- path: mdiCodeBraces.path,
255
- fontSize: "small"
250
+ sx: {
251
+ flexWrap: 'wrap',
252
+ rowGap: 1
253
+ },
254
+ children: [
255
+ /*#__PURE__*/ _jsx(Button, {
256
+ size: "small",
257
+ variant: "outlined",
258
+ startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
259
+ path: mdiCodeBraces.path,
260
+ fontSize: "small"
261
+ }),
262
+ onClick: (event)=>setFieldsAnchor(event.currentTarget),
263
+ disabled: disabled || Boolean(template) && !showSubject,
264
+ "aria-haspopup": "menu",
265
+ children: "Insert field"
256
266
  }),
257
- onClick: (event)=>setFieldsAnchor(event.currentTarget),
258
- disabled: disabled || Boolean(template) && !showSubject,
259
- "aria-haspopup": "menu",
260
- children: "Insert field"
261
- })
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
+ ]
262
285
  }),
263
286
  /*#__PURE__*/ _jsx(Menu, {
264
287
  anchorEl: fieldsAnchor,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-step-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n mdiArrowDown,\n mdiArrowUp,\n mdiCodeBraces,\n mdiTrashCanOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Autocomplete,\n Button,\n Card,\n CardContent,\n FormControlLabel,\n IconButton,\n ListSubheader,\n Menu,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { useRef, useState } from 'react'\nimport { OUTREACH_MERGE_FIELDS } from '../engine/sequence-validation'\nimport {\n OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS,\n OUTREACH_TASK_KIND_LABELS,\n OUTREACH_TASK_KINDS,\n type OutreachEmailStep,\n type OutreachSequenceStep,\n type OutreachTaskKind,\n type OutreachTaskStep,\n} from '../model/outreach.types'\nimport type { OutreachTemplateOption } from './use-outreach-crm'\n\nexport interface OutreachStepCardProps {\n step: OutreachSequenceStep\n /** Its position, from 0. */\n index: number\n /** How many steps the sequence has, for the move controls. */\n count: number\n /** Whether it is the sequence's first email, which always starts the thread. */\n firstEmail: boolean\n /** Whether it is sent as a reply in the thread an earlier email started. */\n inThread: boolean\n /** The subject the thread was started with, for an in-thread email's note. */\n threadSubject: string\n templates: readonly OutreachTemplateOption[]\n /** The issue to show beside each field, by the field's own name. */\n issues: Partial<Record<string, string>>\n disabled?: boolean\n onChange(step: OutreachSequenceStep): void\n onMove(direction: -1 | 1): void\n onRemove(): void\n}\n\n/** The merge fields, grouped the way the picker heads them. */\nconst FIELD_GROUPS = [\n ...new Set(OUTREACH_MERGE_FIELDS.map((field) => field.group)),\n]\n\n/**\n * One step of the sequence editor (AGL-2980): an email — subject, body or a\n * CRM template, merge fields, the wait before it — or a task for the rep.\n */\nexport function OutreachStepCard(props: OutreachStepCardProps) {\n const { step, index, count, disabled } = props\n const title =\n step.kind === 'email'\n ? `Step ${index + 1} · Email`\n : `Step ${index + 1} · ${OUTREACH_TASK_KIND_LABELS[(step as OutreachTaskStep).taskKind] ?? 'Task'}`\n return (\n <Card variant=\"outlined\" component=\"section\" aria-label={title}>\n <CardContent>\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle1\" sx={{ flexGrow: 1 }}>\n {title}\n </Typography>\n <Tooltip title=\"Move up\">\n <span>\n <IconButton\n size=\"small\"\n aria-label={`Move step ${index + 1} up`}\n disabled={disabled || index === 0}\n onClick={() => props.onMove(-1)}\n >\n <MdiIcon path={mdiArrowUp.path} fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n <Tooltip title=\"Move down\">\n <span>\n <IconButton\n size=\"small\"\n aria-label={`Move step ${index + 1} down`}\n disabled={disabled || index === count - 1}\n onClick={() => props.onMove(1)}\n >\n <MdiIcon path={mdiArrowDown.path} fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n <Tooltip title=\"Remove\">\n <span>\n <IconButton\n size=\"small\"\n aria-label={`Remove step ${index + 1}`}\n disabled={disabled || count === 1}\n onClick={props.onRemove}\n >\n <MdiIcon path={mdiTrashCanOutline.path} fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n </Stack>\n <DelayField {...props} />\n {step.kind === 'email' ? (\n <EmailStepFields {...props} step={step} />\n ) : step.kind === 'task' ? (\n <TaskStepFields {...props} step={step} />\n ) : (\n <Typography variant=\"body2\" color=\"error\">\n {props.issues['step'] ?? 'A step is an email or a task.'}\n </Typography>\n )}\n </Stack>\n </CardContent>\n </Card>\n )\n}\nOutreachStepCard.displayName = 'OutreachStepCard'\n\nfunction DelayField(props: OutreachStepCardProps) {\n const { step, index, disabled } = props\n const first = index === 0\n const issue = props.issues['delayBusinessDays']\n return (\n <TextField\n type=\"number\"\n label={\n first\n ? 'Wait after enrolling (business days)'\n : 'Wait after the step before (business days)'\n }\n value={\n Number.isFinite(step.delayBusinessDays) ? step.delayBusinessDays : ''\n }\n onChange={(event) =>\n props.onChange({\n ...step,\n delayBusinessDays:\n event.target.value === '' ? Number.NaN : Number(event.target.value),\n })\n }\n disabled={disabled}\n error={Boolean(issue)}\n helperText={\n issue ??\n (first\n ? '0 sends at the next opening of the sending hours.'\n : 'Counted in the mailbox’s timezone, skipping weekends.')\n }\n slotProps={{\n htmlInput: {\n min: 0,\n max: OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS,\n step: 1,\n },\n }}\n sx={{ maxWidth: { sm: 360 } }}\n />\n )\n}\n\nfunction EmailStepFields(\n props: OutreachStepCardProps & { step: OutreachEmailStep },\n) {\n const { step, disabled, issues } = props\n const subjectRef = useRef<HTMLInputElement | null>(null)\n const bodyRef = useRef<HTMLTextAreaElement | null>(null)\n const lastFocused = useRef<'subject' | 'body'>('body')\n const [fieldsAnchor, setFieldsAnchor] = useState<HTMLElement | null>(null)\n const template =\n props.templates.find((option) => option.id === step.templateId) ?? null\n const showSubject = !props.inThread\n\n /** Puts `{{key}}` where the caret was in the field last focused. */\n const insertField = (key: string) => {\n setFieldsAnchor(null)\n const token = `{{${key}}}`\n const target =\n lastFocused.current === 'subject' && showSubject\n ? 'subject'\n : template\n ? null\n : 'body'\n if (!target) return\n const element = target === 'subject' ? subjectRef.current : bodyRef.current\n const value = step[target]\n const start = element?.selectionStart ?? value.length\n const end = element?.selectionEnd ?? value.length\n props.onChange({\n ...step,\n [target]: `${value.slice(0, start)}${token}${value.slice(end)}`,\n })\n requestAnimationFrame(() => {\n element?.focus()\n element?.setSelectionRange(start + token.length, start + token.length)\n })\n }\n\n return (\n <Stack spacing={2}>\n {props.firstEmail ? null : (\n <FormControlLabel\n control={\n <Switch\n checked={step.replyInThread !== false}\n onChange={(event) =>\n props.onChange({ ...step, replyInThread: event.target.checked })\n }\n disabled={disabled}\n />\n }\n label=\"Reply in the same thread\"\n />\n )}\n {showSubject ? (\n <TextField\n label=\"Subject\"\n value={step.subject}\n inputRef={subjectRef}\n onFocus={() => (lastFocused.current = 'subject')}\n onChange={(event) =>\n props.onChange({ ...step, subject: event.target.value })\n }\n disabled={disabled}\n error={Boolean(issues['subject'])}\n helperText={issues['subject']}\n fullWidth\n />\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.threadSubject\n ? `Sent as a reply: “Re: ${props.threadSubject}”.`\n : 'Sent as a reply in the thread the earlier email started.'}\n </Typography>\n )}\n <Autocomplete<OutreachTemplateOption>\n options={[...props.templates]}\n value={template}\n getOptionLabel={(option) => option.name}\n isOptionEqualToValue={(option, value) => option.id === value.id}\n onChange={(_event, next) =>\n props.onChange({\n ...step,\n templateId: next?.id ?? null,\n body: next ? '' : step.body,\n })\n }\n disabled={disabled}\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"CRM email template\"\n helperText={\n template\n ? 'The template’s body is sent; this step’s own subject is the one used.'\n : 'Optional. Pick one to send its body instead of writing it here.'\n }\n />\n )}\n />\n {template ? (\n <TextField\n label=\"Body (from the template)\"\n value={template.body}\n multiline\n minRows={4}\n fullWidth\n disabled\n />\n ) : (\n <TextField\n label=\"Body\"\n value={step.body}\n inputRef={bodyRef}\n onFocus={() => (lastFocused.current = 'body')}\n onChange={(event) =>\n props.onChange({ ...step, body: event.target.value })\n }\n disabled={disabled}\n error={Boolean(issues['body'])}\n helperText={issues['body']}\n multiline\n minRows={6}\n fullWidth\n />\n )}\n <Stack direction=\"row\" spacing={1}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCodeBraces.path} fontSize=\"small\" />}\n onClick={(event) => setFieldsAnchor(event.currentTarget)}\n disabled={disabled || (Boolean(template) && !showSubject)}\n aria-haspopup=\"menu\"\n >\n Insert field\n </Button>\n </Stack>\n <Menu\n anchorEl={fieldsAnchor}\n open={Boolean(fieldsAnchor)}\n onClose={() => setFieldsAnchor(null)}\n >\n {FIELD_GROUPS.flatMap((group) => [\n <ListSubheader key={`group-${group}`}>{group}</ListSubheader>,\n ...OUTREACH_MERGE_FIELDS.filter((field) => field.group === group).map(\n (field) => (\n <MenuItem key={field.key} onClick={() => insertField(field.key)}>\n {field.label}\n </MenuItem>\n ),\n ),\n ])}\n </Menu>\n </Stack>\n )\n}\n\nfunction TaskStepFields(\n props: OutreachStepCardProps & { step: OutreachTaskStep },\n) {\n const { step, disabled, issues } = props\n return (\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <TextField\n select\n label=\"Task\"\n value={OUTREACH_TASK_KINDS.includes(step.taskKind) ? step.taskKind : ''}\n onChange={(event) =>\n props.onChange({\n ...step,\n taskKind: event.target.value as OutreachTaskKind,\n })\n }\n disabled={disabled}\n error={Boolean(issues['taskKind'])}\n helperText={issues['taskKind']}\n sx={{ minWidth: { sm: 180 } }}\n >\n {OUTREACH_TASK_KINDS.map((kind) => (\n <MenuItem key={kind} value={kind}>\n {OUTREACH_TASK_KIND_LABELS[kind]}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Title\"\n value={step.title}\n onChange={(event) =>\n props.onChange({ ...step, title: event.target.value })\n }\n disabled={disabled}\n error={Boolean(issues['title'])}\n helperText={\n issues['title'] ??\n 'What the task says, for example “Connect on LinkedIn”.'\n }\n fullWidth\n />\n </Stack>\n )\n}\n\nexport default OutreachStepCard\n"],"names":["mdiArrowDown","mdiArrowUp","mdiCodeBraces","mdiTrashCanOutline","MdiIcon","Autocomplete","Button","Card","CardContent","FormControlLabel","IconButton","ListSubheader","Menu","MenuItem","Stack","Switch","TextField","Tooltip","Typography","useRef","useState","OUTREACH_MERGE_FIELDS","OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS","OUTREACH_TASK_KIND_LABELS","OUTREACH_TASK_KINDS","FIELD_GROUPS","Set","map","field","group","OutreachStepCard","props","step","index","count","disabled","title","kind","taskKind","variant","component","aria-label","spacing","direction","sx","alignItems","flexGrow","span","size","onClick","onMove","path","fontSize","onRemove","DelayField","EmailStepFields","TaskStepFields","color","issues","displayName","first","issue","type","label","value","Number","isFinite","delayBusinessDays","onChange","event","target","NaN","error","Boolean","helperText","slotProps","htmlInput","min","max","maxWidth","sm","subjectRef","bodyRef","lastFocused","fieldsAnchor","setFieldsAnchor","template","templates","find","option","id","templateId","showSubject","inThread","insertField","key","token","current","element","start","selectionStart","length","end","selectionEnd","slice","requestAnimationFrame","focus","setSelectionRange","firstEmail","control","checked","replyInThread","subject","inputRef","onFocus","fullWidth","threadSubject","options","getOptionLabel","name","isOptionEqualToValue","_event","next","body","renderInput","params","multiline","minRows","startIcon","currentTarget","aria-haspopup","anchorEl","open","onClose","flatMap","filter","xs","select","includes","minWidth"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-step-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n mdiArrowDown,\n mdiArrowUp,\n mdiCodeBraces,\n mdiEmailFastOutline,\n mdiTrashCanOutline,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport {\n Autocomplete,\n Button,\n Card,\n CardContent,\n FormControlLabel,\n IconButton,\n ListSubheader,\n Menu,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport { useRef, useState } from 'react'\nimport { OUTREACH_MERGE_FIELDS } from '../engine/sequence-validation'\nimport {\n OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS,\n OUTREACH_TASK_KIND_LABELS,\n OUTREACH_TASK_KINDS,\n type OutreachEmailStep,\n type OutreachSequenceStep,\n type OutreachTaskKind,\n type OutreachTaskStep,\n} from '../model/outreach.types'\nimport type { OutreachTemplateOption } from './use-outreach-crm'\n\nexport interface OutreachStepCardProps {\n step: OutreachSequenceStep\n /** Its position, from 0. */\n index: number\n /** How many steps the sequence has, for the move controls. */\n count: number\n /** Whether it is the sequence's first email, which always starts the thread. */\n firstEmail: boolean\n /** Whether it is sent as a reply in the thread an earlier email started. */\n inThread: boolean\n /** The subject the thread was started with, for an in-thread email's note. */\n threadSubject: string\n templates: readonly OutreachTemplateOption[]\n /** The issue to show beside each field, by the field's own name. */\n issues: Partial<Record<string, string>>\n disabled?: boolean\n onChange(step: OutreachSequenceStep): void\n onMove(direction: -1 | 1): void\n onRemove(): void\n /**\n * Opens the test-send dialog for this email step (AGL-3325). Absent for a\n * step that cannot be tested — a task, or an archived sequence.\n */\n onSendTest?(): void\n /** Why the test cannot be sent yet, shown on the disabled button; `null` when it can. */\n sendTestDisabledReason?: string | null\n}\n\n/** The merge fields, grouped the way the picker heads them. */\nconst FIELD_GROUPS = [\n ...new Set(OUTREACH_MERGE_FIELDS.map((field) => field.group)),\n]\n\n/**\n * One step of the sequence editor (AGL-2980): an email — subject, body or a\n * CRM template, merge fields, the wait before it — or a task for the rep.\n */\nexport function OutreachStepCard(props: OutreachStepCardProps) {\n const { step, index, count, disabled } = props\n const title =\n step.kind === 'email'\n ? `Step ${index + 1} · Email`\n : `Step ${index + 1} · ${OUTREACH_TASK_KIND_LABELS[(step as OutreachTaskStep).taskKind] ?? 'Task'}`\n return (\n <Card variant=\"outlined\" component=\"section\" aria-label={title}>\n <CardContent>\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Typography variant=\"subtitle1\" sx={{ flexGrow: 1 }}>\n {title}\n </Typography>\n <Tooltip title=\"Move up\">\n <span>\n <IconButton\n size=\"small\"\n aria-label={`Move step ${index + 1} up`}\n disabled={disabled || index === 0}\n onClick={() => props.onMove(-1)}\n >\n <MdiIcon path={mdiArrowUp.path} fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n <Tooltip title=\"Move down\">\n <span>\n <IconButton\n size=\"small\"\n aria-label={`Move step ${index + 1} down`}\n disabled={disabled || index === count - 1}\n onClick={() => props.onMove(1)}\n >\n <MdiIcon path={mdiArrowDown.path} fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n <Tooltip title=\"Remove\">\n <span>\n <IconButton\n size=\"small\"\n aria-label={`Remove step ${index + 1}`}\n disabled={disabled || count === 1}\n onClick={props.onRemove}\n >\n <MdiIcon path={mdiTrashCanOutline.path} fontSize=\"small\" />\n </IconButton>\n </span>\n </Tooltip>\n </Stack>\n <DelayField {...props} />\n {step.kind === 'email' ? (\n <EmailStepFields {...props} step={step} />\n ) : step.kind === 'task' ? (\n <TaskStepFields {...props} step={step} />\n ) : (\n <Typography variant=\"body2\" color=\"error\">\n {props.issues['step'] ?? 'A step is an email or a task.'}\n </Typography>\n )}\n </Stack>\n </CardContent>\n </Card>\n )\n}\nOutreachStepCard.displayName = 'OutreachStepCard'\n\nfunction DelayField(props: OutreachStepCardProps) {\n const { step, index, disabled } = props\n const first = index === 0\n const issue = props.issues['delayBusinessDays']\n return (\n <TextField\n type=\"number\"\n label={\n first\n ? 'Wait after enrolling (business days)'\n : 'Wait after the step before (business days)'\n }\n value={\n Number.isFinite(step.delayBusinessDays) ? step.delayBusinessDays : ''\n }\n onChange={(event) =>\n props.onChange({\n ...step,\n delayBusinessDays:\n event.target.value === '' ? Number.NaN : Number(event.target.value),\n })\n }\n disabled={disabled}\n error={Boolean(issue)}\n helperText={\n issue ??\n (first\n ? '0 sends at the next opening of the sending hours.'\n : 'Counted in the mailbox’s timezone, skipping weekends.')\n }\n slotProps={{\n htmlInput: {\n min: 0,\n max: OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS,\n step: 1,\n },\n }}\n sx={{ maxWidth: { sm: 360 } }}\n />\n )\n}\n\nfunction EmailStepFields(\n props: OutreachStepCardProps & { step: OutreachEmailStep },\n) {\n const { step, disabled, issues } = props\n const subjectRef = useRef<HTMLInputElement | null>(null)\n const bodyRef = useRef<HTMLTextAreaElement | null>(null)\n const lastFocused = useRef<'subject' | 'body'>('body')\n const [fieldsAnchor, setFieldsAnchor] = useState<HTMLElement | null>(null)\n const template =\n props.templates.find((option) => option.id === step.templateId) ?? null\n const showSubject = !props.inThread\n\n /** Puts `{{key}}` where the caret was in the field last focused. */\n const insertField = (key: string) => {\n setFieldsAnchor(null)\n const token = `{{${key}}}`\n const target =\n lastFocused.current === 'subject' && showSubject\n ? 'subject'\n : template\n ? null\n : 'body'\n if (!target) return\n const element = target === 'subject' ? subjectRef.current : bodyRef.current\n const value = step[target]\n const start = element?.selectionStart ?? value.length\n const end = element?.selectionEnd ?? value.length\n props.onChange({\n ...step,\n [target]: `${value.slice(0, start)}${token}${value.slice(end)}`,\n })\n requestAnimationFrame(() => {\n element?.focus()\n element?.setSelectionRange(start + token.length, start + token.length)\n })\n }\n\n return (\n <Stack spacing={2}>\n {props.firstEmail ? null : (\n <FormControlLabel\n control={\n <Switch\n checked={step.replyInThread !== false}\n onChange={(event) =>\n props.onChange({ ...step, replyInThread: event.target.checked })\n }\n disabled={disabled}\n />\n }\n label=\"Reply in the same thread\"\n />\n )}\n {showSubject ? (\n <TextField\n label=\"Subject\"\n value={step.subject}\n inputRef={subjectRef}\n onFocus={() => (lastFocused.current = 'subject')}\n onChange={(event) =>\n props.onChange({ ...step, subject: event.target.value })\n }\n disabled={disabled}\n error={Boolean(issues['subject'])}\n helperText={issues['subject']}\n fullWidth\n />\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.threadSubject\n ? `Sent as a reply: “Re: ${props.threadSubject}”.`\n : 'Sent as a reply in the thread the earlier email started.'}\n </Typography>\n )}\n <Autocomplete<OutreachTemplateOption>\n options={[...props.templates]}\n value={template}\n getOptionLabel={(option) => option.name}\n isOptionEqualToValue={(option, value) => option.id === value.id}\n onChange={(_event, next) =>\n props.onChange({\n ...step,\n templateId: next?.id ?? null,\n body: next ? '' : step.body,\n })\n }\n disabled={disabled}\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"CRM email template\"\n helperText={\n template\n ? 'The template’s body is sent; this step’s own subject is the one used.'\n : 'Optional. Pick one to send its body instead of writing it here.'\n }\n />\n )}\n />\n {template ? (\n <TextField\n label=\"Body (from the template)\"\n value={template.body}\n multiline\n minRows={4}\n fullWidth\n disabled\n />\n ) : (\n <TextField\n label=\"Body\"\n value={step.body}\n inputRef={bodyRef}\n onFocus={() => (lastFocused.current = 'body')}\n onChange={(event) =>\n props.onChange({ ...step, body: event.target.value })\n }\n disabled={disabled}\n error={Boolean(issues['body'])}\n helperText={issues['body']}\n multiline\n minRows={6}\n fullWidth\n />\n )}\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiCodeBraces.path} fontSize=\"small\" />}\n onClick={(event) => setFieldsAnchor(event.currentTarget)}\n disabled={disabled || (Boolean(template) && !showSubject)}\n aria-haspopup=\"menu\"\n >\n Insert field\n </Button>\n {props.onSendTest ? (\n <Tooltip title={props.sendTestDisabledReason ?? ''}>\n <span>\n <Button\n size=\"small\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiEmailFastOutline.path} fontSize=\"small\" />}\n onClick={props.onSendTest}\n disabled={Boolean(props.sendTestDisabledReason)}\n aria-label={`Send a test of step ${props.index + 1}`}\n >\n Send a test\n </Button>\n </span>\n </Tooltip>\n ) : null}\n </Stack>\n <Menu\n anchorEl={fieldsAnchor}\n open={Boolean(fieldsAnchor)}\n onClose={() => setFieldsAnchor(null)}\n >\n {FIELD_GROUPS.flatMap((group) => [\n <ListSubheader key={`group-${group}`}>{group}</ListSubheader>,\n ...OUTREACH_MERGE_FIELDS.filter((field) => field.group === group).map(\n (field) => (\n <MenuItem key={field.key} onClick={() => insertField(field.key)}>\n {field.label}\n </MenuItem>\n ),\n ),\n ])}\n </Menu>\n </Stack>\n )\n}\n\nfunction TaskStepFields(\n props: OutreachStepCardProps & { step: OutreachTaskStep },\n) {\n const { step, disabled, issues } = props\n return (\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2}>\n <TextField\n select\n label=\"Task\"\n value={OUTREACH_TASK_KINDS.includes(step.taskKind) ? step.taskKind : ''}\n onChange={(event) =>\n props.onChange({\n ...step,\n taskKind: event.target.value as OutreachTaskKind,\n })\n }\n disabled={disabled}\n error={Boolean(issues['taskKind'])}\n helperText={issues['taskKind']}\n sx={{ minWidth: { sm: 180 } }}\n >\n {OUTREACH_TASK_KINDS.map((kind) => (\n <MenuItem key={kind} value={kind}>\n {OUTREACH_TASK_KIND_LABELS[kind]}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n label=\"Title\"\n value={step.title}\n onChange={(event) =>\n props.onChange({ ...step, title: event.target.value })\n }\n disabled={disabled}\n error={Boolean(issues['title'])}\n helperText={\n issues['title'] ??\n 'What the task says, for example “Connect on LinkedIn”.'\n }\n fullWidth\n />\n </Stack>\n )\n}\n\nexport default OutreachStepCard\n"],"names":["mdiArrowDown","mdiArrowUp","mdiCodeBraces","mdiEmailFastOutline","mdiTrashCanOutline","MdiIcon","Autocomplete","Button","Card","CardContent","FormControlLabel","IconButton","ListSubheader","Menu","MenuItem","Stack","Switch","TextField","Tooltip","Typography","useRef","useState","OUTREACH_MERGE_FIELDS","OUTREACH_MAX_STEP_DELAY_BUSINESS_DAYS","OUTREACH_TASK_KIND_LABELS","OUTREACH_TASK_KINDS","FIELD_GROUPS","Set","map","field","group","OutreachStepCard","props","step","index","count","disabled","title","kind","taskKind","variant","component","aria-label","spacing","direction","sx","alignItems","flexGrow","span","size","onClick","onMove","path","fontSize","onRemove","DelayField","EmailStepFields","TaskStepFields","color","issues","displayName","first","issue","type","label","value","Number","isFinite","delayBusinessDays","onChange","event","target","NaN","error","Boolean","helperText","slotProps","htmlInput","min","max","maxWidth","sm","subjectRef","bodyRef","lastFocused","fieldsAnchor","setFieldsAnchor","template","templates","find","option","id","templateId","showSubject","inThread","insertField","key","token","current","element","start","selectionStart","length","end","selectionEnd","slice","requestAnimationFrame","focus","setSelectionRange","firstEmail","control","checked","replyInThread","subject","inputRef","onFocus","fullWidth","threadSubject","options","getOptionLabel","name","isOptionEqualToValue","_event","next","body","renderInput","params","multiline","minRows","flexWrap","rowGap","startIcon","currentTarget","aria-haspopup","onSendTest","sendTestDisabledReason","anchorEl","open","onClose","flatMap","filter","xs","select","includes","minWidth"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SACEA,YAAY,EACZC,UAAU,EACVC,aAAa,EACbC,mBAAmB,EACnBC,kBAAkB,QACb,yBAAwB;AAC/B,SAASC,OAAO,QAAQ,uBAAsB;AAC9C,SACEC,YAAY,EACZC,MAAM,EACNC,IAAI,EACJC,WAAW,EACXC,gBAAgB,EAChBC,UAAU,EACVC,aAAa,EACbC,IAAI,EACJC,QAAQ,EACRC,KAAK,EACLC,MAAM,EACNC,SAAS,EACTC,OAAO,EACPC,UAAU,QACL,gBAAe;AACtB,SAASC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACxC,SAASC,qBAAqB,QAAQ,mCAA+B;AACrE,SACEC,qCAAqC,EACrCC,yBAAyB,EACzBC,mBAAmB,QAKd,6BAAyB;AA+BhC,6DAA6D,GAC7D,MAAMC,eAAe;OAChB,IAAIC,IAAIL,sBAAsBM,GAAG,CAAC,CAACC,QAAUA,MAAMC,KAAK;CAC5D;AAED;;;CAGC,GACD,OAAO,SAASC,iBAAiBC,KAA4B;QAK9BR,0CAqDhBQ;IAzDb,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAEC,KAAK,EAAEC,QAAQ,EAAE,GAAGJ;IACzC,MAAMK,QACJJ,KAAKK,IAAI,KAAK,UACV,CAAC,KAAK,EAAEJ,QAAQ,EAAE,QAAQ,CAAC,GAC3B,CAAC,KAAK,EAAEA,QAAQ,EAAE,GAAG,GAAEV,2CAAAA,yBAAyB,CAAC,AAACS,KAA0BM,QAAQ,CAAC,YAA9Df,2CAAkE,QAAQ;IACvG,qBACE,KAAChB;QAAKgC,SAAQ;QAAWC,WAAU;QAAUC,cAAYL;kBACvD,cAAA,KAAC5B;sBACC,cAAA,MAACM;gBAAM4B,SAAS;;kCACd,MAAC5B;wBAAM6B,WAAU;wBAAMD,SAAS;wBAAGE,IAAI;4BAAEC,YAAY;wBAAS;;0CAC5D,KAAC3B;gCAAWqB,SAAQ;gCAAYK,IAAI;oCAAEE,UAAU;gCAAE;0CAC/CV;;0CAEH,KAACnB;gCAAQmB,OAAM;0CACb,cAAA,KAACW;8CACC,cAAA,KAACrC;wCACCsC,MAAK;wCACLP,cAAY,CAAC,UAAU,EAAER,QAAQ,EAAE,GAAG,CAAC;wCACvCE,UAAUA,YAAYF,UAAU;wCAChCgB,SAAS,IAAMlB,MAAMmB,MAAM,CAAC,CAAC;kDAE7B,cAAA,KAAC9C;4CAAQ+C,MAAMnD,WAAWmD,IAAI;4CAAEC,UAAS;;;;;0CAI/C,KAACnC;gCAAQmB,OAAM;0CACb,cAAA,KAACW;8CACC,cAAA,KAACrC;wCACCsC,MAAK;wCACLP,cAAY,CAAC,UAAU,EAAER,QAAQ,EAAE,KAAK,CAAC;wCACzCE,UAAUA,YAAYF,UAAUC,QAAQ;wCACxCe,SAAS,IAAMlB,MAAMmB,MAAM,CAAC;kDAE5B,cAAA,KAAC9C;4CAAQ+C,MAAMpD,aAAaoD,IAAI;4CAAEC,UAAS;;;;;0CAIjD,KAACnC;gCAAQmB,OAAM;0CACb,cAAA,KAACW;8CACC,cAAA,KAACrC;wCACCsC,MAAK;wCACLP,cAAY,CAAC,YAAY,EAAER,QAAQ,GAAG;wCACtCE,UAAUA,YAAYD,UAAU;wCAChCe,SAASlB,MAAMsB,QAAQ;kDAEvB,cAAA,KAACjD;4CAAQ+C,MAAMhD,mBAAmBgD,IAAI;4CAAEC,UAAS;;;;;;;kCAKzD,KAACE,yBAAevB;oBACfC,KAAKK,IAAI,KAAK,wBACb,KAACkB,8BAAoBxB;wBAAOC,MAAMA;0BAChCA,KAAKK,IAAI,KAAK,uBAChB,KAACmB,6BAAmBzB;wBAAOC,MAAMA;wCAEjC,KAACd;wBAAWqB,SAAQ;wBAAQkB,OAAM;mCAC/B1B,qBAAAA,MAAM2B,MAAM,CAAC,OAAO,YAApB3B,qBAAwB;;;;;;AAOvC;AACAD,iBAAiB6B,WAAW,GAAG;AAE/B,SAASL,WAAWvB,KAA4B;IAC9C,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAEE,QAAQ,EAAE,GAAGJ;IAClC,MAAM6B,QAAQ3B,UAAU;IACxB,MAAM4B,QAAQ9B,MAAM2B,MAAM,CAAC,oBAAoB;IAC/C,qBACE,KAAC1C;QACC8C,MAAK;QACLC,OACEH,QACI,yCACA;QAENI,OACEC,OAAOC,QAAQ,CAAClC,KAAKmC,iBAAiB,IAAInC,KAAKmC,iBAAiB,GAAG;QAErEC,UAAU,CAACC,QACTtC,MAAMqC,QAAQ,CAAC,aACVpC;gBACHmC,mBACEE,MAAMC,MAAM,CAACN,KAAK,KAAK,KAAKC,OAAOM,GAAG,GAAGN,OAAOI,MAAMC,MAAM,CAACN,KAAK;;QAGxE7B,UAAUA;QACVqC,OAAOC,QAAQZ;QACfa,UAAU,EACRb,gBAAAA,QACCD,QACG,sDACA;QAENe,WAAW;YACTC,WAAW;gBACTC,KAAK;gBACLC,KAAKxD;gBACLU,MAAM;YACR;QACF;QACAY,IAAI;YAAEmC,UAAU;gBAAEC,IAAI;YAAI;QAAE;;AAGlC;AAEA,SAASzB,gBACPxB,KAA0D;QAQxDA,uBAgIsBA;IAtIxB,MAAM,EAAEC,IAAI,EAAEG,QAAQ,EAAEuB,MAAM,EAAE,GAAG3B;IACnC,MAAMkD,aAAa9D,OAAgC;IACnD,MAAM+D,UAAU/D,OAAmC;IACnD,MAAMgE,cAAchE,OAA2B;IAC/C,MAAM,CAACiE,cAAcC,gBAAgB,GAAGjE,SAA6B;IACrE,MAAMkE,YACJvD,wBAAAA,MAAMwD,SAAS,CAACC,IAAI,CAAC,CAACC,SAAWA,OAAOC,EAAE,KAAK1D,KAAK2D,UAAU,aAA9D5D,wBAAmE;IACrE,MAAM6D,cAAc,CAAC7D,MAAM8D,QAAQ;IAEnC,kEAAkE,GAClE,MAAMC,cAAc,CAACC;;QACnBV,gBAAgB;QAChB,MAAMW,QAAQ,CAAC,EAAE,EAAED,IAAI,EAAE,CAAC;QAC1B,MAAMzB,SACJa,YAAYc,OAAO,KAAK,aAAaL,cACjC,YACAN,WACE,OACA;QACR,IAAI,CAAChB,QAAQ;QACb,MAAM4B,UAAU5B,WAAW,YAAYW,WAAWgB,OAAO,GAAGf,QAAQe,OAAO;QAC3E,MAAMjC,QAAQhC,IAAI,CAACsC,OAAO;QAC1B,MAAM6B,gBAAQD,2BAAAA,QAASE,cAAc,mBAAIpC,MAAMqC,MAAM;QACrD,MAAMC,eAAMJ,2BAAAA,QAASK,YAAY,oBAAIvC,MAAMqC,MAAM;QACjDtE,MAAMqC,QAAQ,CAAC,aACVpC;YACH,CAACsC,OAAO,EAAE,GAAGN,MAAMwC,KAAK,CAAC,GAAGL,SAASH,QAAQhC,MAAMwC,KAAK,CAACF,MAAM;;QAEjEG,sBAAsB;YACpBP,2BAAAA,QAASQ,KAAK;YACdR,2BAAAA,QAASS,iBAAiB,CAACR,QAAQH,MAAMK,MAAM,EAAEF,QAAQH,MAAMK,MAAM;QACvE;IACF;IAEA,qBACE,MAACvF;QAAM4B,SAAS;;YACbX,MAAM6E,UAAU,GAAG,qBAClB,KAACnG;gBACCoG,uBACE,KAAC9F;oBACC+F,SAAS9E,KAAK+E,aAAa,KAAK;oBAChC3C,UAAU,CAACC,QACTtC,MAAMqC,QAAQ,CAAC,aAAKpC;4BAAM+E,eAAe1C,MAAMC,MAAM,CAACwC,OAAO;;oBAE/D3E,UAAUA;;gBAGd4B,OAAM;;YAGT6B,4BACC,KAAC5E;gBACC+C,OAAM;gBACNC,OAAOhC,KAAKgF,OAAO;gBACnBC,UAAUhC;gBACViC,SAAS,IAAO/B,YAAYc,OAAO,GAAG;gBACtC7B,UAAU,CAACC,QACTtC,MAAMqC,QAAQ,CAAC,aAAKpC;wBAAMgF,SAAS3C,MAAMC,MAAM,CAACN,KAAK;;gBAEvD7B,UAAUA;gBACVqC,OAAOC,QAAQf,MAAM,CAAC,UAAU;gBAChCgB,YAAYhB,MAAM,CAAC,UAAU;gBAC7ByD,SAAS;+BAGX,KAACjG;gBAAWqB,SAAQ;gBAAQkB,OAAM;0BAC/B1B,MAAMqF,aAAa,GAChB,CAAC,sBAAsB,EAAErF,MAAMqF,aAAa,CAAC,EAAE,CAAC,GAChD;;0BAGR,KAAC/G;gBACCgH,SAAS;uBAAItF,MAAMwD,SAAS;iBAAC;gBAC7BvB,OAAOsB;gBACPgC,gBAAgB,CAAC7B,SAAWA,OAAO8B,IAAI;gBACvCC,sBAAsB,CAAC/B,QAAQzB,QAAUyB,OAAOC,EAAE,KAAK1B,MAAM0B,EAAE;gBAC/DtB,UAAU,CAACqD,QAAQC;;2BACjB3F,MAAMqC,QAAQ,CAAC,aACVpC;wBACH2D,UAAU,UAAE+B,wBAAAA,KAAMhC,EAAE,mBAAI;wBACxBiC,MAAMD,OAAO,KAAK1F,KAAK2F,IAAI;;;gBAG/BxF,UAAUA;gBACVyF,aAAa,CAACC,uBACZ,KAAC7G,wBACK6G;wBACJ9D,OAAM;wBACNW,YACEY,WACI,0EACA;;;YAKXA,yBACC,KAACtE;gBACC+C,OAAM;gBACNC,OAAOsB,SAASqC,IAAI;gBACpBG,SAAS;gBACTC,SAAS;gBACTZ,SAAS;gBACThF,QAAQ;+BAGV,KAACnB;gBACC+C,OAAM;gBACNC,OAAOhC,KAAK2F,IAAI;gBAChBV,UAAU/B;gBACVgC,SAAS,IAAO/B,YAAYc,OAAO,GAAG;gBACtC7B,UAAU,CAACC,QACTtC,MAAMqC,QAAQ,CAAC,aAAKpC;wBAAM2F,MAAMtD,MAAMC,MAAM,CAACN,KAAK;;gBAEpD7B,UAAUA;gBACVqC,OAAOC,QAAQf,MAAM,CAAC,OAAO;gBAC7BgB,YAAYhB,MAAM,CAAC,OAAO;gBAC1BoE,SAAS;gBACTC,SAAS;gBACTZ,SAAS;;0BAGb,MAACrG;gBAAM6B,WAAU;gBAAMD,SAAS;gBAAGE,IAAI;oBAAEoF,UAAU;oBAAQC,QAAQ;gBAAE;;kCACnE,KAAC3H;wBACC0C,MAAK;wBACLT,SAAQ;wBACR2F,yBAAW,KAAC9H;4BAAQ+C,MAAMlD,cAAckD,IAAI;4BAAEC,UAAS;;wBACvDH,SAAS,CAACoB,QAAUgB,gBAAgBhB,MAAM8D,aAAa;wBACvDhG,UAAUA,YAAasC,QAAQa,aAAa,CAACM;wBAC7CwC,iBAAc;kCACf;;oBAGArG,MAAMsG,UAAU,iBACf,KAACpH;wBAAQmB,KAAK,GAAEL,gCAAAA,MAAMuG,sBAAsB,YAA5BvG,gCAAgC;kCAC9C,cAAA,KAACgB;sCACC,cAAA,KAACzC;gCACC0C,MAAK;gCACLT,SAAQ;gCACR2F,yBAAW,KAAC9H;oCAAQ+C,MAAMjD,oBAAoBiD,IAAI;oCAAEC,UAAS;;gCAC7DH,SAASlB,MAAMsG,UAAU;gCACzBlG,UAAUsC,QAAQ1C,MAAMuG,sBAAsB;gCAC9C7F,cAAY,CAAC,oBAAoB,EAAEV,MAAME,KAAK,GAAG,GAAG;0CACrD;;;yBAKH;;;0BAEN,KAACrB;gBACC2H,UAAUnD;gBACVoD,MAAM/D,QAAQW;gBACdqD,SAAS,IAAMpD,gBAAgB;0BAE9B5D,aAAaiH,OAAO,CAAC,CAAC7G,QAAU;sCAC/B,KAAClB;sCAAsCkB;2BAAnB,CAAC,MAAM,EAAEA,OAAO;2BACjCR,sBAAsBsH,MAAM,CAAC,CAAC/G,QAAUA,MAAMC,KAAK,KAAKA,OAAOF,GAAG,CACnE,CAACC,sBACC,KAACf;gCAAyBoC,SAAS,IAAM6C,YAAYlE,MAAMmE,GAAG;0CAC3DnE,MAAMmC,KAAK;+BADCnC,MAAMmE,GAAG;qBAK7B;;;;AAIT;AAEA,SAASvC,eACPzB,KAAyD;QAmCjD2B;IAjCR,MAAM,EAAE1B,IAAI,EAAEG,QAAQ,EAAEuB,MAAM,EAAE,GAAG3B;IACnC,qBACE,MAACjB;QAAM6B,WAAW;YAAEiG,IAAI;YAAU5D,IAAI;QAAM;QAAGtC,SAAS;;0BACtD,KAAC1B;gBACC6H,MAAM;gBACN9E,OAAM;gBACNC,OAAOxC,oBAAoBsH,QAAQ,CAAC9G,KAAKM,QAAQ,IAAIN,KAAKM,QAAQ,GAAG;gBACrE8B,UAAU,CAACC,QACTtC,MAAMqC,QAAQ,CAAC,aACVpC;wBACHM,UAAU+B,MAAMC,MAAM,CAACN,KAAK;;gBAGhC7B,UAAUA;gBACVqC,OAAOC,QAAQf,MAAM,CAAC,WAAW;gBACjCgB,YAAYhB,MAAM,CAAC,WAAW;gBAC9Bd,IAAI;oBAAEmG,UAAU;wBAAE/D,IAAI;oBAAI;gBAAE;0BAE3BxD,oBAAoBG,GAAG,CAAC,CAACU,qBACxB,KAACxB;wBAAoBmD,OAAO3B;kCACzBd,yBAAyB,CAACc,KAAK;uBADnBA;;0BAKnB,KAACrB;gBACC+C,OAAM;gBACNC,OAAOhC,KAAKI,KAAK;gBACjBgC,UAAU,CAACC,QACTtC,MAAMqC,QAAQ,CAAC,aAAKpC;wBAAMI,OAAOiC,MAAMC,MAAM,CAACN,KAAK;;gBAErD7B,UAAUA;gBACVqC,OAAOC,QAAQf,MAAM,CAAC,QAAQ;gBAC9BgB,UAAU,GACRhB,gBAAAA,MAAM,CAAC,QAAQ,YAAfA,gBACA;gBAEFyD,SAAS;;;;AAIjB;AAEA,eAAerF,iBAAgB"}
@@ -26,7 +26,7 @@ import { ListTable } from "@aglyn/shared-ui-jsx/components/list-table.component"
26
26
  import { filterListRows, inMemoryListField, listFilterGridColumns } from "@aglyn/shared-ui-jsx/const/list-grid-filter";
27
27
  import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
28
28
  import { useListGridFilter } from "@aglyn/shared-ui-jsx/hooks/use-list-grid-filter";
29
- import { Button, Card, CardContent, Chip, Stack, Typography, useMediaQuery, useTheme } from "@mui/material";
29
+ import { Alert, Button, Card, CardContent, Chip, Stack, Typography, useMediaQuery, useTheme } from "@mui/material";
30
30
  import { useEffect, useMemo, useState } from "react";
31
31
  import { OUTREACH_SEQUENCE_STATUSES } from "../model/outreach.types.js";
32
32
  import { OUTREACH_SEQUENCE_STATUS_LABELS, OutreachLink, OutreachLoading, OutreachLoadProblem, OutreachSequenceStatusChip, useOutreachNavigate } from "./outreach-ui.js";
@@ -216,6 +216,13 @@ const SEQUENCE_FILTER_OPTIONS = {
216
216
  children: "New sequence"
217
217
  });
218
218
  const count = (sequence, key)=>counts[sequence.id] ? String(counts[sequence.id][key]) : '—';
219
+ /*
220
+ * The read stops at `OUTREACH_SEQUENCES_LIMIT`, newest first, and probes one
221
+ * past it, so this shows only when older sequences really exist.
222
+ */ const truncatedNotice = sequences.truncated ? /*#__PURE__*/ _jsx(Alert, {
223
+ severity: "info",
224
+ children: `Showing the ${sequences.data.length.toLocaleString()} newest sequences. ` + 'The filters and the search narrow these only; older sequences are ' + 'not listed here.'
225
+ }) : null;
219
226
  const chips = /*#__PURE__*/ _jsx(ListFilterChips, {
220
227
  fields: SEQUENCE_FILTER_FIELDS,
221
228
  headers: SEQUENCE_FILTER_HEADERS,
@@ -302,7 +309,8 @@ const SEQUENCE_FILTER_OPTIONS = {
302
309
  })
303
310
  }, sequence.id))
304
311
  }),
305
- footer
312
+ footer,
313
+ truncatedNotice
306
314
  ]
307
315
  });
308
316
  } else {
@@ -362,7 +370,8 @@ const SEQUENCE_FILTER_OPTIONS = {
362
370
  noRowsLabel: "No sequences match these filters",
363
371
  hideFooter: true
364
372
  }),
365
- footer
373
+ footer,
374
+ truncatedNotice
366
375
  ]
367
376
  });
368
377
  }