@aglyn/plugins-outreach 1.0.0-beta.196 → 1.0.0-beta.198

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 (105) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/compliance-section.d.ts +3 -1
  3. package/src/lib/components/compliance-section.js +110 -45
  4. package/src/lib/components/compliance-section.js.map +1 -1
  5. package/src/lib/components/enroll-dialog.d.ts +7 -1
  6. package/src/lib/components/enroll-dialog.js +106 -3
  7. package/src/lib/components/enroll-dialog.js.map +1 -1
  8. package/src/lib/components/mailbox-card.js +11 -2
  9. package/src/lib/components/mailbox-card.js.map +1 -1
  10. package/src/lib/components/outreach-ui.js +1 -0
  11. package/src/lib/components/outreach-ui.js.map +1 -1
  12. package/src/lib/components/sequence-detail.js +1 -0
  13. package/src/lib/components/sequence-detail.js.map +1 -1
  14. package/src/lib/components/sequence-editor.d.ts +2 -0
  15. package/src/lib/components/sequence-editor.js +51 -26
  16. package/src/lib/components/sequence-editor.js.map +1 -1
  17. package/src/lib/components/sequence-step-card.d.ts +7 -0
  18. package/src/lib/components/sequence-step-card.js +37 -14
  19. package/src/lib/components/sequence-step-card.js.map +1 -1
  20. package/src/lib/components/sequences-section.js +12 -3
  21. package/src/lib/components/sequences-section.js.map +1 -1
  22. package/src/lib/components/step-test-dialog.d.ts +31 -0
  23. package/src/lib/components/step-test-dialog.js +247 -0
  24. package/src/lib/components/step-test-dialog.js.map +1 -0
  25. package/src/lib/components/use-outreach-api.d.ts +8 -1
  26. package/src/lib/components/use-outreach-api.js +4 -0
  27. package/src/lib/components/use-outreach-api.js.map +1 -1
  28. package/src/lib/components/use-outreach-data.d.ts +6 -1
  29. package/src/lib/components/use-outreach-data.js +6 -3
  30. package/src/lib/components/use-outreach-data.js.map +1 -1
  31. package/src/lib/constants/api-routes.d.ts +7 -0
  32. package/src/lib/constants/api-routes.js +6 -0
  33. package/src/lib/constants/api-routes.js.map +1 -1
  34. package/src/lib/engine/delivery-status.d.ts +8 -0
  35. package/src/lib/engine/delivery-status.js +10 -5
  36. package/src/lib/engine/delivery-status.js.map +1 -1
  37. package/src/lib/engine/enrollment-state.d.ts +2 -0
  38. package/src/lib/engine/enrollment-state.js +2 -2
  39. package/src/lib/engine/enrollment-state.js.map +1 -1
  40. package/src/lib/engine/gates.d.ts +28 -1
  41. package/src/lib/engine/gates.js +28 -1
  42. package/src/lib/engine/gates.js.map +1 -1
  43. package/src/lib/engine/mail-gateway.d.ts +97 -0
  44. package/src/lib/engine/mail-gateway.js +226 -0
  45. package/src/lib/engine/mail-gateway.js.map +1 -0
  46. package/src/lib/engine/mailbox-health.d.ts +3 -3
  47. package/src/lib/engine/mailbox-health.js +17 -11
  48. package/src/lib/engine/mailbox-health.js.map +1 -1
  49. package/src/lib/engine/thread-classification.d.ts +2 -0
  50. package/src/lib/engine/thread-classification.js +2 -1
  51. package/src/lib/engine/thread-classification.js.map +1 -1
  52. package/src/lib/enrollment/enroll-people.d.ts +6 -1
  53. package/src/lib/enrollment/enroll-people.js +18 -4
  54. package/src/lib/enrollment/enroll-people.js.map +1 -1
  55. package/src/lib/enrollment/gate-lookups.d.ts +7 -0
  56. package/src/lib/enrollment/gate-lookups.js +11 -2
  57. package/src/lib/enrollment/gate-lookups.js.map +1 -1
  58. package/src/lib/model/outreach-api.d.ts +35 -1
  59. package/src/lib/model/outreach-api.js.map +1 -1
  60. package/src/lib/model/outreach.types.d.ts +85 -3
  61. package/src/lib/model/outreach.types.js +21 -2
  62. package/src/lib/model/outreach.types.js.map +1 -1
  63. package/src/lib/model/step-preview.d.ts +7 -1
  64. package/src/lib/model/step-preview.js +3 -2
  65. package/src/lib/model/step-preview.js.map +1 -1
  66. package/src/lib/routes/enroll-routes.js +23 -4
  67. package/src/lib/routes/enroll-routes.js.map +1 -1
  68. package/src/lib/routes/enrollment-routes.js +15 -1
  69. package/src/lib/routes/enrollment-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 +99 -95
  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 +27 -0
  78. package/src/lib/routes/register-routes.js.map +1 -1
  79. package/src/lib/routes/route-deps.d.ts +6 -0
  80. package/src/lib/routes/route-deps.js.map +1 -1
  81. package/src/lib/routes/step-test-routes.d.ts +88 -0
  82. package/src/lib/routes/step-test-routes.js +275 -0
  83. package/src/lib/routes/step-test-routes.js.map +1 -0
  84. package/src/lib/runtime/click-events.js +10 -0
  85. package/src/lib/runtime/click-events.js.map +1 -1
  86. package/src/lib/runtime/click-link.d.ts +11 -0
  87. package/src/lib/runtime/click-link.js +9 -4
  88. package/src/lib/runtime/click-link.js.map +1 -1
  89. package/src/lib/runtime/click-route.js +3 -0
  90. package/src/lib/runtime/click-route.js.map +1 -1
  91. package/src/lib/runtime/gateway-ledger.d.ts +46 -0
  92. package/src/lib/runtime/gateway-ledger.js +113 -0
  93. package/src/lib/runtime/gateway-ledger.js.map +1 -0
  94. package/src/lib/runtime/platform-runtime-deps.js +3 -0
  95. package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
  96. package/src/lib/runtime/runtime-deps.d.ts +6 -0
  97. package/src/lib/runtime/runtime-deps.js.map +1 -1
  98. package/src/lib/runtime/send-job.js +48 -0
  99. package/src/lib/runtime/send-job.js.map +1 -1
  100. package/src/lib/runtime/sync-job.d.ts +2 -0
  101. package/src/lib/runtime/sync-job.js +40 -3
  102. package/src/lib/runtime/sync-job.js.map +1 -1
  103. package/src/lib/storage/domain-intel-store.d.ts +107 -0
  104. package/src/lib/storage/domain-intel-store.js +320 -0
  105. package/src/lib/storage/domain-intel-store.js.map +1 -0
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/outreach-ui.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 Alert,\n Button,\n Chip,\n type ChipProps,\n CircularProgress,\n Link,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useRouter } from 'next/navigation'\nimport type { MouseEvent, ReactNode } from 'react'\nimport type {\n OutreachEnrollmentStatus,\n OutreachSequenceStatus,\n OutreachStopReason,\n} from '../model/outreach.types'\nimport type { OutreachLoadStatus } from './use-outreach-data'\n\n/**\n * The pieces every Outreach page shares (AGL-2980): the words for each\n * status, the states a read can be in, and a link that navigates inside the\n * console without reloading it.\n */\n\nexport const OUTREACH_SEQUENCE_STATUS_LABELS: Record<\n OutreachSequenceStatus,\n string\n> = {\n draft: 'Draft',\n active: 'Active',\n paused: 'Paused',\n archived: 'Archived',\n}\n\nconst SEQUENCE_STATUS_COLORS: Record<\n OutreachSequenceStatus,\n ChipProps['color']\n> = {\n draft: 'default',\n active: 'success',\n paused: 'warning',\n archived: 'default',\n}\n\nexport const OUTREACH_ENROLLMENT_STATUS_LABELS: Record<\n OutreachEnrollmentStatus,\n string\n> = {\n active: 'Active',\n paused: 'Paused',\n finished: 'Finished',\n replied: 'Replied',\n bounced: 'Bounced',\n opted_out: 'Opted out',\n stopped: 'Stopped',\n failed: 'Failed',\n}\n\nconst ENROLLMENT_STATUS_COLORS: Record<\n OutreachEnrollmentStatus,\n ChipProps['color']\n> = {\n active: 'success',\n paused: 'warning',\n finished: 'default',\n replied: 'info',\n bounced: 'error',\n opted_out: 'default',\n stopped: 'default',\n failed: 'error',\n}\n\n/** Why an enrollment is not active, as the table says it. */\nexport const OUTREACH_STOP_REASON_LABELS: Record<OutreachStopReason, string> = {\n reply: 'They replied',\n hard_bounce: 'The address bounced',\n opt_out_reply: 'They asked not to be emailed',\n unsubscribe: 'They unsubscribed',\n do_not_contact: 'On the do-not-contact list',\n gate: 'No longer eligible',\n manual: 'By a member',\n sequence_archived: 'The sequence was archived',\n send_failed: \"An email couldn't be sent\",\n}\n\nexport function OutreachSequenceStatusChip(props: {\n status: OutreachSequenceStatus\n}) {\n return (\n <Chip\n size=\"small\"\n label={OUTREACH_SEQUENCE_STATUS_LABELS[props.status]}\n color={SEQUENCE_STATUS_COLORS[props.status]}\n variant={props.status === 'archived' ? 'outlined' : 'filled'}\n />\n )\n}\n\nexport function OutreachEnrollmentStatusChip(props: {\n status: OutreachEnrollmentStatus\n}) {\n return (\n <Chip\n size=\"small\"\n label={OUTREACH_ENROLLMENT_STATUS_LABELS[props.status]}\n color={ENROLLMENT_STATUS_COLORS[props.status]}\n />\n )\n}\n\n/** Progress for a read still on its way. */\nexport function OutreachLoading(props: { label: string }) {\n return (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', py: 2 }}\n role=\"status\"\n >\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.label}\n </Typography>\n </Stack>\n )\n}\n\n/** The words a refused read shows when the route gave none of its own. */\nexport const OUTREACH_REFUSED_MESSAGE =\n \"You don't have access to this in Sequences. Ask an organization owner or admin for the Use Sequences permission.\"\n\n/**\n * What a failed or refused read shows. A refusal is a statement about the\n * reader, not a fault, so it reads as information and offers no retry.\n */\nexport function OutreachLoadProblem(props: {\n status: Extract<OutreachLoadStatus, 'error' | 'refused'>\n /** What could not be read, for the error sentence. */\n what: string\n message?: string | null\n onRetry?: () => void\n}) {\n if (props.status === 'refused') {\n return (\n <Alert severity=\"info\">{props.message || OUTREACH_REFUSED_MESSAGE}</Alert>\n )\n }\n return (\n <Alert\n severity=\"error\"\n action={\n props.onRetry ? (\n <Button color=\"inherit\" size=\"small\" onClick={props.onRetry}>\n Try again\n </Button>\n ) : undefined\n }\n >\n {props.message ||\n `The ${props.what} could not be loaded. Reload the page to try again.`}\n </Alert>\n )\n}\n\n/** Navigation inside the console, without reloading it. */\nexport function useOutreachNavigate(): (href: string) => void {\n const router = useRouter()\n return (href: string) => router.push(href)\n}\n\n/**\n * A link inside the console: a real anchor, so it opens in a new tab when\n * asked to, and a client navigation otherwise.\n */\nexport function OutreachLink(props: {\n href: string\n children: ReactNode\n underline?: 'hover' | 'always' | 'none'\n}) {\n const navigate = useOutreachNavigate()\n const onClick = (event: MouseEvent<HTMLAnchorElement>) => {\n if (\n event.defaultPrevented ||\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return\n }\n event.preventDefault()\n navigate(props.href)\n }\n return (\n <Link\n href={props.href}\n onClick={onClick}\n underline={props.underline ?? 'hover'}\n >\n {props.children}\n </Link>\n )\n}\n\n/**\n * A moment as a reader in `timeZone` reads it — \"Sep 22, 9:14 AM CDT\" — or\n * in the reader's own zone when none is known.\n */\nexport function formatOutreachTime(\n ms: number | null | undefined,\n timeZone?: string | null,\n): string {\n if (typeof ms !== 'number' || !Number.isFinite(ms)) return '—'\n const options: Intl.DateTimeFormatOptions = {\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n timeZoneName: 'short',\n }\n try {\n return new Intl.DateTimeFormat('en-US', {\n ...options,\n ...(timeZone ? { timeZone } : {}),\n }).format(ms)\n } catch {\n return new Intl.DateTimeFormat('en-US', options).format(ms)\n }\n}\n"],"names":["Alert","Button","Chip","CircularProgress","Link","Stack","Typography","useRouter","OUTREACH_SEQUENCE_STATUS_LABELS","draft","active","paused","archived","SEQUENCE_STATUS_COLORS","OUTREACH_ENROLLMENT_STATUS_LABELS","finished","replied","bounced","opted_out","stopped","failed","ENROLLMENT_STATUS_COLORS","OUTREACH_STOP_REASON_LABELS","reply","hard_bounce","opt_out_reply","unsubscribe","do_not_contact","gate","manual","sequence_archived","send_failed","OutreachSequenceStatusChip","props","size","label","status","color","variant","OutreachEnrollmentStatusChip","OutreachLoading","direction","spacing","sx","alignItems","py","role","OUTREACH_REFUSED_MESSAGE","OutreachLoadProblem","severity","message","action","onRetry","onClick","undefined","what","useOutreachNavigate","router","href","push","OutreachLink","navigate","event","defaultPrevented","button","metaKey","ctrlKey","shiftKey","altKey","preventDefault","underline","children","formatOutreachTime","ms","timeZone","Number","isFinite","options","month","day","hour","minute","timeZoneName","Intl","DateTimeFormat","format"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/outreach-ui.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 Alert,\n Button,\n Chip,\n type ChipProps,\n CircularProgress,\n Link,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useRouter } from 'next/navigation'\nimport type { MouseEvent, ReactNode } from 'react'\nimport type {\n OutreachEnrollmentStatus,\n OutreachSequenceStatus,\n OutreachStopReason,\n} from '../model/outreach.types'\nimport type { OutreachLoadStatus } from './use-outreach-data'\n\n/**\n * The pieces every Outreach page shares (AGL-2980): the words for each\n * status, the states a read can be in, and a link that navigates inside the\n * console without reloading it.\n */\n\nexport const OUTREACH_SEQUENCE_STATUS_LABELS: Record<\n OutreachSequenceStatus,\n string\n> = {\n draft: 'Draft',\n active: 'Active',\n paused: 'Paused',\n archived: 'Archived',\n}\n\nconst SEQUENCE_STATUS_COLORS: Record<\n OutreachSequenceStatus,\n ChipProps['color']\n> = {\n draft: 'default',\n active: 'success',\n paused: 'warning',\n archived: 'default',\n}\n\nexport const OUTREACH_ENROLLMENT_STATUS_LABELS: Record<\n OutreachEnrollmentStatus,\n string\n> = {\n active: 'Active',\n paused: 'Paused',\n finished: 'Finished',\n replied: 'Replied',\n bounced: 'Bounced',\n opted_out: 'Opted out',\n stopped: 'Stopped',\n failed: 'Failed',\n}\n\nconst ENROLLMENT_STATUS_COLORS: Record<\n OutreachEnrollmentStatus,\n ChipProps['color']\n> = {\n active: 'success',\n paused: 'warning',\n finished: 'default',\n replied: 'info',\n bounced: 'error',\n opted_out: 'default',\n stopped: 'default',\n failed: 'error',\n}\n\n/** Why an enrollment is not active, as the table says it. */\nexport const OUTREACH_STOP_REASON_LABELS: Record<OutreachStopReason, string> = {\n reply: 'They replied',\n hard_bounce: 'The address bounced',\n opt_out_reply: 'They asked not to be emailed',\n unsubscribe: 'They unsubscribed',\n do_not_contact: 'On the do-not-contact list',\n gate: 'No longer eligible',\n manual: 'By a member',\n gateway_blocked_here: 'Held — gateway refused this sender twice',\n sequence_archived: 'The sequence was archived',\n send_failed: \"An email couldn't be sent\",\n}\n\nexport function OutreachSequenceStatusChip(props: {\n status: OutreachSequenceStatus\n}) {\n return (\n <Chip\n size=\"small\"\n label={OUTREACH_SEQUENCE_STATUS_LABELS[props.status]}\n color={SEQUENCE_STATUS_COLORS[props.status]}\n variant={props.status === 'archived' ? 'outlined' : 'filled'}\n />\n )\n}\n\nexport function OutreachEnrollmentStatusChip(props: {\n status: OutreachEnrollmentStatus\n}) {\n return (\n <Chip\n size=\"small\"\n label={OUTREACH_ENROLLMENT_STATUS_LABELS[props.status]}\n color={ENROLLMENT_STATUS_COLORS[props.status]}\n />\n )\n}\n\n/** Progress for a read still on its way. */\nexport function OutreachLoading(props: { label: string }) {\n return (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', py: 2 }}\n role=\"status\"\n >\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n {props.label}\n </Typography>\n </Stack>\n )\n}\n\n/** The words a refused read shows when the route gave none of its own. */\nexport const OUTREACH_REFUSED_MESSAGE =\n \"You don't have access to this in Sequences. Ask an organization owner or admin for the Use Sequences permission.\"\n\n/**\n * What a failed or refused read shows. A refusal is a statement about the\n * reader, not a fault, so it reads as information and offers no retry.\n */\nexport function OutreachLoadProblem(props: {\n status: Extract<OutreachLoadStatus, 'error' | 'refused'>\n /** What could not be read, for the error sentence. */\n what: string\n message?: string | null\n onRetry?: () => void\n}) {\n if (props.status === 'refused') {\n return (\n <Alert severity=\"info\">{props.message || OUTREACH_REFUSED_MESSAGE}</Alert>\n )\n }\n return (\n <Alert\n severity=\"error\"\n action={\n props.onRetry ? (\n <Button color=\"inherit\" size=\"small\" onClick={props.onRetry}>\n Try again\n </Button>\n ) : undefined\n }\n >\n {props.message ||\n `The ${props.what} could not be loaded. Reload the page to try again.`}\n </Alert>\n )\n}\n\n/** Navigation inside the console, without reloading it. */\nexport function useOutreachNavigate(): (href: string) => void {\n const router = useRouter()\n return (href: string) => router.push(href)\n}\n\n/**\n * A link inside the console: a real anchor, so it opens in a new tab when\n * asked to, and a client navigation otherwise.\n */\nexport function OutreachLink(props: {\n href: string\n children: ReactNode\n underline?: 'hover' | 'always' | 'none'\n}) {\n const navigate = useOutreachNavigate()\n const onClick = (event: MouseEvent<HTMLAnchorElement>) => {\n if (\n event.defaultPrevented ||\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return\n }\n event.preventDefault()\n navigate(props.href)\n }\n return (\n <Link\n href={props.href}\n onClick={onClick}\n underline={props.underline ?? 'hover'}\n >\n {props.children}\n </Link>\n )\n}\n\n/**\n * A moment as a reader in `timeZone` reads it — \"Sep 22, 9:14 AM CDT\" — or\n * in the reader's own zone when none is known.\n */\nexport function formatOutreachTime(\n ms: number | null | undefined,\n timeZone?: string | null,\n): string {\n if (typeof ms !== 'number' || !Number.isFinite(ms)) return '—'\n const options: Intl.DateTimeFormatOptions = {\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n timeZoneName: 'short',\n }\n try {\n return new Intl.DateTimeFormat('en-US', {\n ...options,\n ...(timeZone ? { timeZone } : {}),\n }).format(ms)\n } catch {\n return new Intl.DateTimeFormat('en-US', options).format(ms)\n }\n}\n"],"names":["Alert","Button","Chip","CircularProgress","Link","Stack","Typography","useRouter","OUTREACH_SEQUENCE_STATUS_LABELS","draft","active","paused","archived","SEQUENCE_STATUS_COLORS","OUTREACH_ENROLLMENT_STATUS_LABELS","finished","replied","bounced","opted_out","stopped","failed","ENROLLMENT_STATUS_COLORS","OUTREACH_STOP_REASON_LABELS","reply","hard_bounce","opt_out_reply","unsubscribe","do_not_contact","gate","manual","gateway_blocked_here","sequence_archived","send_failed","OutreachSequenceStatusChip","props","size","label","status","color","variant","OutreachEnrollmentStatusChip","OutreachLoading","direction","spacing","sx","alignItems","py","role","OUTREACH_REFUSED_MESSAGE","OutreachLoadProblem","severity","message","action","onRetry","onClick","undefined","what","useOutreachNavigate","router","href","push","OutreachLink","navigate","event","defaultPrevented","button","metaKey","ctrlKey","shiftKey","altKey","preventDefault","underline","children","formatOutreachTime","ms","timeZone","Number","isFinite","options","month","day","hour","minute","timeZoneName","Intl","DateTimeFormat","format"],"mappings":"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"}
@@ -379,6 +379,7 @@ import { useOutreachEnrollments, useOutreachSequence, useOutreachSequenceLinks }
379
379
  props.tab === 'steps' ? /*#__PURE__*/ _jsx(OutreachSequenceEditor, {
380
380
  orgId: orgId,
381
381
  orgMount: props.orgMount,
382
+ org: props.org,
382
383
  sequence: sequence,
383
384
  settings: props.settings,
384
385
  mailboxes: props.mailboxes,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-detail.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 { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport {\n mdiAccountMultiplePlusOutline,\n mdiChevronLeft,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Card,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport { OutreachEnrollmentsTable } from './enrollments-table'\nimport {\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { OutreachSequenceReportCard } from './sequence-report-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachEnrollments,\n useOutreachSequence,\n useOutreachSequenceLinks,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'Google stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n const enrollments = useOutreachEnrollments(\n props.tab === 'enrollments' ? orgId : null,\n sequenceId,\n )\n // One document however large the sequence, and read on both tabs, because\n // the report card sits above them (AGL-3239).\n const links = useOutreachSequenceLinks(orgId, sequenceId)\n const [busy, setBusy] = useState(false)\n const [refusal, setRefusal] = useState<{\n message: string\n issues: OutreachSequenceIssue[]\n } | null>(null)\n const [confirming, setConfirming] = useState<'archive' | 'delete' | null>(\n null,\n )\n const [enrolling, setEnrolling] = useState(false)\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const back = (\n <OutreachLink href={sectionPath}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n component=\"span\"\n sx={{ alignItems: 'center', display: 'inline-flex' }}\n >\n <MdiIcon path={mdiChevronLeft.path} fontSize=\"small\" />\n <span>Sequences</span>\n </Stack>\n </OutreachLink>\n )\n\n if (loaded.status === 'loading')\n return <OutreachLoading label=\"Loading the sequence…\" />\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return <OutreachLoadProblem status={loaded.status} what=\"sequence\" />\n }\n if (!sequence) {\n return (\n <Stack spacing={2}>\n {back}\n <Card variant=\"outlined\">\n <EmptyStateComponent label=\"This sequence no longer exists\" />\n </Card>\n </Stack>\n )\n }\n\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === sequence.mailboxId) ??\n null\n const mailboxState =\n props.mailboxes.status === 'ready'\n ? outreachSequenceMailboxState(sequence.mailboxId, mailbox)\n : 'sending'\n const activatable = sequence.status === 'draft' || sequence.status === 'paused'\n const blockers = activatable\n ? outreachActivationBlockers({\n sequence,\n mailboxes: props.mailboxes,\n settings: props.settings,\n paths: {\n steps: detailPath,\n mailboxes: props.mailboxesPath,\n compliance: props.compliancePath,\n },\n })\n : []\n // Enrolling onto a mailbox that is gone is refused by the route; a paused\n // one only makes the new people wait with everyone else.\n const canEnroll =\n sequence.status === 'active' &&\n mailboxState !== 'gone' &&\n mailboxState !== 'none'\n const act = async (action: OutreachSequenceAction) => {\n setBusy(true)\n setRefusal(null)\n try {\n const answer = await api.setSequenceStatus(sequence.id, action)\n enqueueSnackbar(\n action === 'archive' && answer.stoppedEnrollments\n ? `${ACTIONS.archive.done} ${answer.stoppedEnrollments} ${answer.stoppedEnrollments === 1 ? 'person was' : 'people were'} stopped.`\n : ACTIONS[action].done,\n { variant: 'success' },\n )\n } catch (error) {\n setRefusal({\n message: (error as Error).message,\n issues:\n error instanceof OutreachRouteError\n ? error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n )\n : [],\n })\n } finally {\n setBusy(false)\n }\n }\n const remove = async () => {\n setBusy(true)\n setRefusal(null)\n try {\n await api.deleteSequence(sequence.id)\n enqueueSnackbar('Sequence deleted.', { variant: 'success' })\n navigate(sectionPath)\n } catch (error) {\n setRefusal({ message: (error as Error).message, issues: [] })\n setBusy(false)\n }\n }\n\n const enrollButton = (\n <Button\n variant=\"contained\"\n startIcon={\n <MdiIcon path={mdiAccountMultiplePlusOutline.path} fontSize=\"small\" />\n }\n disabled={!canEnroll}\n onClick={() => setEnrolling(true)}\n >\n Enroll people\n </Button>\n )\n\n return (\n <Stack spacing={2}>\n {back}\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n spacing={1.5}\n sx={{ alignItems: { md: 'center' }, justifyContent: 'space-between' }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Typography\n variant=\"h5\"\n component=\"h2\"\n sx={{ wordBreak: 'break-word' }}\n >\n {sequence.name}\n </Typography>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n {activatable ? (\n <Button\n variant=\"outlined\"\n disabled={busy || blockers.length > 0}\n onClick={() => void act('activate')}\n >\n {ACTIONS.activate.label}\n </Button>\n ) : null}\n {sequence.status === 'active' ? (\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void act('pause')}\n >\n {ACTIONS.pause.label}\n </Button>\n ) : null}\n {sequence.status !== 'archived' ? (\n <Button\n variant=\"outlined\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('archive')}\n >\n {ACTIONS.archive.label}\n </Button>\n ) : null}\n {sequence.status === 'draft' ? (\n <Button\n variant=\"text\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('delete')}\n >\n Delete\n </Button>\n ) : null}\n {sequence.status === 'active' ? enrollButton : null}\n </Stack>\n </Stack>\n\n {refusal ? (\n <Alert severity=\"error\" onClose={() => setRefusal(null)}>\n <Stack spacing={0.5}>\n <span>{refusal.message}</span>\n {refusal.issues.length > 1 ? (\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {refusal.issues.slice(1).map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n ) : null}\n {blockers.length ? (\n <Alert severity=\"warning\" data-testid=\"outreach-activation-blockers\">\n <Stack spacing={0.5}>\n <span>\n {sequence.status === 'draft'\n ? 'A draft sends nothing, and this one can’t be activated yet:'\n : 'This sequence can’t be activated again yet:'}\n </span>\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {blockers.map((blocker) => (\n <li key={blocker.message}>\n {blocker.message}{' '}\n <OutreachLink href={blocker.href}>{blocker.linkLabel}</OutreachLink>\n </li>\n ))}\n </Stack>\n </Stack>\n </Alert>\n ) : sequence.status === 'draft' ? (\n <Alert severity=\"info\">\n A draft sends nothing. Activate it to enroll people.\n </Alert>\n ) : null}\n {sequence.status === 'active' && mailboxState !== 'sending' ? (\n <Alert severity=\"warning\">\n {NOT_SENDING[mailboxState]}{' '}\n <OutreachLink href={props.mailboxesPath}>Open Mailboxes</OutreachLink>\n </Alert>\n ) : null}\n\n {/*\n * Above the tabs, not inside one (AGL-3239): what a sequence is doing\n * is the question a rep opens it with, and putting it behind a tab\n * makes the steps the first answer to \"how is this going\".\n *\n * A draft has sent nothing and measured nothing, so it gets no card\n * rather than a row of zeroes.\n */}\n {sequence.status === 'draft' ? null : (\n <OutreachSequenceReportCard\n sequence={sequence}\n links={links}\n timeZone={mailbox?.timezone ?? null}\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : `${detailPath}/enrollments`)\n }\n aria-label=\"Sequence\"\n >\n <Tab value=\"steps\" label=\"Steps\" />\n <Tab value=\"enrollments\" label=\"Enrollments\" />\n </Tabs>\n\n {props.tab === 'steps' ? (\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n enrollments={enrollments}\n steps={sequence.steps}\n trackClicks={sequence.settings.trackClicks}\n timeZone={mailbox?.timezone ?? null}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n />\n )}\n\n <OutreachEnrollDialog\n open={enrolling}\n onClose={() => setEnrolling(false)}\n orgId={orgId}\n sequence={sequence}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n uid={user?.uid ?? null}\n api={api}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>\n {confirming === 'delete'\n ? 'Delete this draft?'\n : 'Archive this sequence?'}\n </DialogTitle>\n <DialogContent>\n <DialogContentText>\n {confirming === 'delete'\n ? 'It is removed for good. Nobody was ever enrolled in it.'\n : 'Archiving is final. Everyone still in it is stopped, and it takes no one new.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const chosen = confirming\n setConfirming(null)\n if (chosen === 'delete') void remove()\n else void act('archive')\n }}\n >\n {confirming === 'delete' ? 'Delete' : 'Archive'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nOutreachSequenceDetail.displayName = 'OutreachSequenceDetail'\n\nexport default OutreachSequenceDetail\n"],"names":["consentGroupForHost","mdiAccountMultiplePlusOutline","mdiChevronLeft","MdiIcon","useSnackbar","useUser","Alert","Button","Card","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","Tab","Tabs","Typography","EmptyStateComponent","useState","validateOutreachOrgSettings","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","OutreachEnrollmentsTable","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceEditor","OutreachSequenceReportCard","OutreachRouteError","useOutreachApi","useOutreachEnrollments","useOutreachSequence","useOutreachSequenceLinks","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","sequenceId","sectionPath","api","navigate","enqueueSnackbar","data","user","loaded","enrollments","tab","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","detailPath","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","filter","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","map","li","data-testid","blocker","timeZone","timezone","value","onChange","_event","next","aria-label","orgMount","onSaved","undefined","trackClicks","enrollAction","open","contactGroupId","hostId","org","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-detail.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 { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport {\n mdiAccountMultiplePlusOutline,\n mdiChevronLeft,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Card,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport { OutreachEnrollmentsTable } from './enrollments-table'\nimport {\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { OutreachSequenceReportCard } from './sequence-report-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachEnrollments,\n useOutreachSequence,\n useOutreachSequenceLinks,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'Google stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n const enrollments = useOutreachEnrollments(\n props.tab === 'enrollments' ? orgId : null,\n sequenceId,\n )\n // One document however large the sequence, and read on both tabs, because\n // the report card sits above them (AGL-3239).\n const links = useOutreachSequenceLinks(orgId, sequenceId)\n const [busy, setBusy] = useState(false)\n const [refusal, setRefusal] = useState<{\n message: string\n issues: OutreachSequenceIssue[]\n } | null>(null)\n const [confirming, setConfirming] = useState<'archive' | 'delete' | null>(\n null,\n )\n const [enrolling, setEnrolling] = useState(false)\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const back = (\n <OutreachLink href={sectionPath}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n component=\"span\"\n sx={{ alignItems: 'center', display: 'inline-flex' }}\n >\n <MdiIcon path={mdiChevronLeft.path} fontSize=\"small\" />\n <span>Sequences</span>\n </Stack>\n </OutreachLink>\n )\n\n if (loaded.status === 'loading')\n return <OutreachLoading label=\"Loading the sequence…\" />\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return <OutreachLoadProblem status={loaded.status} what=\"sequence\" />\n }\n if (!sequence) {\n return (\n <Stack spacing={2}>\n {back}\n <Card variant=\"outlined\">\n <EmptyStateComponent label=\"This sequence no longer exists\" />\n </Card>\n </Stack>\n )\n }\n\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === sequence.mailboxId) ??\n null\n const mailboxState =\n props.mailboxes.status === 'ready'\n ? outreachSequenceMailboxState(sequence.mailboxId, mailbox)\n : 'sending'\n const activatable = sequence.status === 'draft' || sequence.status === 'paused'\n const blockers = activatable\n ? outreachActivationBlockers({\n sequence,\n mailboxes: props.mailboxes,\n settings: props.settings,\n paths: {\n steps: detailPath,\n mailboxes: props.mailboxesPath,\n compliance: props.compliancePath,\n },\n })\n : []\n // Enrolling onto a mailbox that is gone is refused by the route; a paused\n // one only makes the new people wait with everyone else.\n const canEnroll =\n sequence.status === 'active' &&\n mailboxState !== 'gone' &&\n mailboxState !== 'none'\n const act = async (action: OutreachSequenceAction) => {\n setBusy(true)\n setRefusal(null)\n try {\n const answer = await api.setSequenceStatus(sequence.id, action)\n enqueueSnackbar(\n action === 'archive' && answer.stoppedEnrollments\n ? `${ACTIONS.archive.done} ${answer.stoppedEnrollments} ${answer.stoppedEnrollments === 1 ? 'person was' : 'people were'} stopped.`\n : ACTIONS[action].done,\n { variant: 'success' },\n )\n } catch (error) {\n setRefusal({\n message: (error as Error).message,\n issues:\n error instanceof OutreachRouteError\n ? error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n )\n : [],\n })\n } finally {\n setBusy(false)\n }\n }\n const remove = async () => {\n setBusy(true)\n setRefusal(null)\n try {\n await api.deleteSequence(sequence.id)\n enqueueSnackbar('Sequence deleted.', { variant: 'success' })\n navigate(sectionPath)\n } catch (error) {\n setRefusal({ message: (error as Error).message, issues: [] })\n setBusy(false)\n }\n }\n\n const enrollButton = (\n <Button\n variant=\"contained\"\n startIcon={\n <MdiIcon path={mdiAccountMultiplePlusOutline.path} fontSize=\"small\" />\n }\n disabled={!canEnroll}\n onClick={() => setEnrolling(true)}\n >\n Enroll people\n </Button>\n )\n\n return (\n <Stack spacing={2}>\n {back}\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n spacing={1.5}\n sx={{ alignItems: { md: 'center' }, justifyContent: 'space-between' }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Typography\n variant=\"h5\"\n component=\"h2\"\n sx={{ wordBreak: 'break-word' }}\n >\n {sequence.name}\n </Typography>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n {activatable ? (\n <Button\n variant=\"outlined\"\n disabled={busy || blockers.length > 0}\n onClick={() => void act('activate')}\n >\n {ACTIONS.activate.label}\n </Button>\n ) : null}\n {sequence.status === 'active' ? (\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void act('pause')}\n >\n {ACTIONS.pause.label}\n </Button>\n ) : null}\n {sequence.status !== 'archived' ? (\n <Button\n variant=\"outlined\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('archive')}\n >\n {ACTIONS.archive.label}\n </Button>\n ) : null}\n {sequence.status === 'draft' ? (\n <Button\n variant=\"text\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('delete')}\n >\n Delete\n </Button>\n ) : null}\n {sequence.status === 'active' ? enrollButton : null}\n </Stack>\n </Stack>\n\n {refusal ? (\n <Alert severity=\"error\" onClose={() => setRefusal(null)}>\n <Stack spacing={0.5}>\n <span>{refusal.message}</span>\n {refusal.issues.length > 1 ? (\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {refusal.issues.slice(1).map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n ) : null}\n {blockers.length ? (\n <Alert severity=\"warning\" data-testid=\"outreach-activation-blockers\">\n <Stack spacing={0.5}>\n <span>\n {sequence.status === 'draft'\n ? 'A draft sends nothing, and this one can’t be activated yet:'\n : 'This sequence can’t be activated again yet:'}\n </span>\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {blockers.map((blocker) => (\n <li key={blocker.message}>\n {blocker.message}{' '}\n <OutreachLink href={blocker.href}>{blocker.linkLabel}</OutreachLink>\n </li>\n ))}\n </Stack>\n </Stack>\n </Alert>\n ) : sequence.status === 'draft' ? (\n <Alert severity=\"info\">\n A draft sends nothing. Activate it to enroll people.\n </Alert>\n ) : null}\n {sequence.status === 'active' && mailboxState !== 'sending' ? (\n <Alert severity=\"warning\">\n {NOT_SENDING[mailboxState]}{' '}\n <OutreachLink href={props.mailboxesPath}>Open Mailboxes</OutreachLink>\n </Alert>\n ) : null}\n\n {/*\n * Above the tabs, not inside one (AGL-3239): what a sequence is doing\n * is the question a rep opens it with, and putting it behind a tab\n * makes the steps the first answer to \"how is this going\".\n *\n * A draft has sent nothing and measured nothing, so it gets no card\n * rather than a row of zeroes.\n */}\n {sequence.status === 'draft' ? null : (\n <OutreachSequenceReportCard\n sequence={sequence}\n links={links}\n timeZone={mailbox?.timezone ?? null}\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : `${detailPath}/enrollments`)\n }\n aria-label=\"Sequence\"\n >\n <Tab value=\"steps\" label=\"Steps\" />\n <Tab value=\"enrollments\" label=\"Enrollments\" />\n </Tabs>\n\n {props.tab === 'steps' ? (\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n enrollments={enrollments}\n steps={sequence.steps}\n trackClicks={sequence.settings.trackClicks}\n timeZone={mailbox?.timezone ?? null}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n />\n )}\n\n <OutreachEnrollDialog\n open={enrolling}\n onClose={() => setEnrolling(false)}\n orgId={orgId}\n sequence={sequence}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n uid={user?.uid ?? null}\n api={api}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>\n {confirming === 'delete'\n ? 'Delete this draft?'\n : 'Archive this sequence?'}\n </DialogTitle>\n <DialogContent>\n <DialogContentText>\n {confirming === 'delete'\n ? 'It is removed for good. Nobody was ever enrolled in it.'\n : 'Archiving is final. Everyone still in it is stopped, and it takes no one new.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const chosen = confirming\n setConfirming(null)\n if (chosen === 'delete') void remove()\n else void act('archive')\n }}\n >\n {confirming === 'delete' ? 'Delete' : 'Archive'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nOutreachSequenceDetail.displayName = 'OutreachSequenceDetail'\n\nexport default OutreachSequenceDetail\n"],"names":["consentGroupForHost","mdiAccountMultiplePlusOutline","mdiChevronLeft","MdiIcon","useSnackbar","useUser","Alert","Button","Card","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","Tab","Tabs","Typography","EmptyStateComponent","useState","validateOutreachOrgSettings","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","OutreachEnrollmentsTable","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceEditor","OutreachSequenceReportCard","OutreachRouteError","useOutreachApi","useOutreachEnrollments","useOutreachSequence","useOutreachSequenceLinks","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","sequenceId","sectionPath","api","navigate","enqueueSnackbar","data","user","loaded","enrollments","tab","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","detailPath","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","filter","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","map","li","data-testid","blocker","timeZone","timezone","value","onChange","_event","next","aria-label","orgMount","org","onSaved","undefined","trackClicks","enrollAction","open","contactGroupId","hostId","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
@@ -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,
@@ -236,6 +247,31 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
236
247
  help: pluginDocsHelp('sequences', {
237
248
  anchor: '#build-a-sequence'
238
249
  }),
250
+ // The whole sequence (steps and sending rules included) saves
251
+ // from its first card's header, where the console puts a card's
252
+ // actions (AGL-3333).
253
+ HeaderProps: {
254
+ action: archived ? undefined : /*#__PURE__*/ _jsxs(Stack, {
255
+ direction: "row",
256
+ spacing: 1,
257
+ children: [
258
+ props.onCancel ? /*#__PURE__*/ _jsx(Button, {
259
+ size: "small",
260
+ variant: "text",
261
+ onClick: props.onCancel,
262
+ disabled: saving,
263
+ children: "Cancel"
264
+ }) : null,
265
+ /*#__PURE__*/ _jsx(Button, {
266
+ size: "small",
267
+ variant: "contained",
268
+ onClick: ()=>void save(),
269
+ disabled: saving,
270
+ children: saving ? 'Saving…' : sequence ? 'Save' : 'Create sequence'
271
+ })
272
+ ]
273
+ })
274
+ },
239
275
  contentGutterX: true,
240
276
  contentGutterY: true,
241
277
  children: /*#__PURE__*/ _jsxs(Stack, {
@@ -318,7 +354,9 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
318
354
  onMove: (direction)=>moveStep(index, direction),
319
355
  onRemove: ()=>update({
320
356
  steps: draft.steps.filter((_entry, position)=>position !== index)
321
- })
357
+ }),
358
+ onSendTest: step.kind === 'email' && !archived ? ()=>setTesting(index) : undefined,
359
+ sendTestDisabledReason: sendTestDisabledReason
322
360
  }, step.id || `step-${index}`)),
323
361
  /*#__PURE__*/ _jsxs(Stack, {
324
362
  direction: "row",
@@ -486,34 +524,21 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
486
524
  })
487
525
  ]
488
526
  })
489
- }),
490
- archived ? null : /*#__PURE__*/ _jsxs(Stack, {
491
- direction: {
492
- xs: 'column',
493
- sm: 'row'
494
- },
495
- spacing: 1,
496
- sx: {
497
- justifyContent: 'flex-end'
498
- },
499
- children: [
500
- props.onCancel ? /*#__PURE__*/ _jsx(Button, {
501
- variant: "text",
502
- onClick: props.onCancel,
503
- disabled: saving,
504
- children: "Cancel"
505
- }) : null,
506
- /*#__PURE__*/ _jsx(Button, {
507
- variant: "contained",
508
- onClick: ()=>void save(),
509
- disabled: saving,
510
- children: saving ? 'Saving…' : sequence ? 'Save' : 'Create sequence'
511
- })
512
- ]
513
527
  })
514
528
  ]
515
529
  })
516
530
  }),
531
+ sequence && testing !== null ? /*#__PURE__*/ _jsx(OutreachStepTestDialog, {
532
+ open: true,
533
+ onClose: ()=>setTesting(null),
534
+ orgId: orgId,
535
+ sequence: sequence,
536
+ stepIndex: testing,
537
+ contactGroupId: sequence.hostId ? consentGroupForHost((_props_org = props.org) != null ? _props_org : null, sequence.hostId).groupId : '',
538
+ selfEmail: (_ref3 = user == null ? void 0 : user.email) != null ? _ref3 : null,
539
+ unsaved: unsaved,
540
+ api: api
541
+ }) : null,
517
542
  /*#__PURE__*/ _jsx(Grid, {
518
543
  size: {
519
544
  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 // The whole sequence (steps and sending rules included) saves\n // from its first card's header, where the console puts a card's\n // actions (AGL-3333).\n HeaderProps={{\n action: archived ? undefined : (\n <Stack direction=\"row\" spacing={1}>\n {props.onCancel ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={props.onCancel}\n disabled={saving}\n >\n Cancel\n </Button>\n ) : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => void save()}\n disabled={saving}\n >\n {saving ? 'Saving…' : sequence ? 'Save' : 'Create sequence'}\n </Button>\n </Stack>\n ),\n }}\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 </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","HeaderProps","action","undefined","onCancel","onClick","disabled","contentGutterX","contentGutterY","label","value","onChange","event","target","Boolean","helperText","slotProps","htmlInput","maxLength","fullWidth","select","mailboxesPath","options","campaignIds","empty","ready","emptyText","count","firstEmail","findIndex","inThread","onMove","onRemove","_entry","onSendTest","sx","flexWrap","rowGap","startIcon","fontSize","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","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