@aglyn/plugins-outreach 1.0.0-beta.156 → 1.0.0-beta.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/package.json +13 -12
  2. package/src/lib/components/enrollments-table.d.ts +7 -0
  3. package/src/lib/components/enrollments-table.js +46 -2
  4. package/src/lib/components/enrollments-table.js.map +1 -1
  5. package/src/lib/components/sequence-detail.js +14 -4
  6. package/src/lib/components/sequence-detail.js.map +1 -1
  7. package/src/lib/components/sequence-editor.js +22 -0
  8. package/src/lib/components/sequence-editor.js.map +1 -1
  9. package/src/lib/components/sequence-report-card.d.ts +23 -0
  10. package/src/lib/components/sequence-report-card.js +191 -0
  11. package/src/lib/components/sequence-report-card.js.map +1 -0
  12. package/src/lib/components/use-outreach-data.d.ts +11 -0
  13. package/src/lib/components/use-outreach-data.js +24 -1
  14. package/src/lib/components/use-outreach-data.js.map +1 -1
  15. package/src/lib/constants/api-routes.d.ts +9 -0
  16. package/src/lib/constants/api-routes.js +9 -1
  17. package/src/lib/constants/api-routes.js.map +1 -1
  18. package/src/lib/engine/click-tracking.d.ts +98 -0
  19. package/src/lib/engine/click-tracking.js +252 -0
  20. package/src/lib/engine/click-tracking.js.map +1 -0
  21. package/src/lib/engine/compose.d.ts +24 -0
  22. package/src/lib/engine/compose.js +15 -2
  23. package/src/lib/engine/compose.js.map +1 -1
  24. package/src/lib/engine/sequence-validation.js +4 -1
  25. package/src/lib/engine/sequence-validation.js.map +1 -1
  26. package/src/lib/model/enrollment-engagement.d.ts +31 -0
  27. package/src/lib/model/enrollment-engagement.js +44 -0
  28. package/src/lib/model/enrollment-engagement.js.map +1 -0
  29. package/src/lib/model/outreach.types.d.ts +89 -0
  30. package/src/lib/model/outreach.types.js +25 -0
  31. package/src/lib/model/outreach.types.js.map +1 -1
  32. package/src/lib/model/sequence-draft.js +5 -1
  33. package/src/lib/model/sequence-draft.js.map +1 -1
  34. package/src/lib/model/sequence-report.d.ts +68 -0
  35. package/src/lib/model/sequence-report.js +118 -0
  36. package/src/lib/model/sequence-report.js.map +1 -0
  37. package/src/lib/model/stored-records.d.ts +11 -1
  38. package/src/lib/model/stored-records.js +34 -1
  39. package/src/lib/model/stored-records.js.map +1 -1
  40. package/src/lib/routes/sequence-routes.js +4 -1
  41. package/src/lib/routes/sequence-routes.js.map +1 -1
  42. package/src/lib/runtime/click-events.d.ts +67 -0
  43. package/src/lib/runtime/click-events.js +232 -0
  44. package/src/lib/runtime/click-events.js.map +1 -0
  45. package/src/lib/runtime/click-link.d.ts +94 -0
  46. package/src/lib/runtime/click-link.js +158 -0
  47. package/src/lib/runtime/click-link.js.map +1 -0
  48. package/src/lib/runtime/click-route.d.ts +19 -0
  49. package/src/lib/runtime/click-route.js +108 -0
  50. package/src/lib/runtime/click-route.js.map +1 -0
  51. package/src/lib/runtime/platform-runtime-deps.d.ts +2 -0
  52. package/src/lib/runtime/platform-runtime-deps.js +9 -0
  53. package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
  54. package/src/lib/runtime/runtime-deps.d.ts +11 -0
  55. package/src/lib/runtime/runtime-deps.js.map +1 -1
  56. package/src/lib/runtime/send-job.js +65 -5
  57. package/src/lib/runtime/send-job.js.map +1 -1
  58. package/src/lib/server.js +8 -0
  59. package/src/lib/server.js.map +1 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aglyn/plugins-outreach",
3
- "version": "1.0.0-beta.156",
3
+ "version": "1.0.0-beta.158",
4
4
  "license": "Apache-2.0",
5
5
  "homepage": "https://aglyn.com",
6
6
  "repository": {
@@ -25,17 +25,18 @@
25
25
  "./package.json": "./package.json"
26
26
  },
27
27
  "dependencies": {
28
- "@aglyn/aglyn": "1.0.0-beta.156",
29
- "@aglyn/shared-data-mdi": "1.0.0-beta.156",
30
- "@aglyn/shared-ui-jsx": "1.0.0-beta.156",
31
- "@aglyn/shared-ui-next": "1.0.0-beta.156",
32
- "@aglyn/shared-ui-snackstack": "1.0.0-beta.156",
33
- "@aglyn/shared-util-http": "1.0.0-beta.156",
34
- "@aglyn/shared-util-timestamp": "1.0.0-beta.156",
35
- "@aglyn/shared-util-tools": "1.0.0-beta.156",
36
- "@aglyn/tenant-data-admin": "1.0.0-beta.156",
37
- "@aglyn/tenant-feature-instance": "1.0.0-beta.156",
38
- "@aglyn/tenant-runtime": "1.0.0-beta.156",
28
+ "@aglyn/aglyn": "1.0.0-beta.158",
29
+ "@aglyn/shared-data-mdi": "1.0.0-beta.158",
30
+ "@aglyn/shared-ui-email-campaigns": "1.0.0-beta.158",
31
+ "@aglyn/shared-ui-jsx": "1.0.0-beta.158",
32
+ "@aglyn/shared-ui-next": "1.0.0-beta.158",
33
+ "@aglyn/shared-ui-snackstack": "1.0.0-beta.158",
34
+ "@aglyn/shared-util-http": "1.0.0-beta.158",
35
+ "@aglyn/shared-util-timestamp": "1.0.0-beta.158",
36
+ "@aglyn/shared-util-tools": "1.0.0-beta.158",
37
+ "@aglyn/tenant-data-admin": "1.0.0-beta.158",
38
+ "@aglyn/tenant-feature-instance": "1.0.0-beta.158",
39
+ "@aglyn/tenant-runtime": "1.0.0-beta.158",
39
40
  "@swc/helpers": "0.5.23"
40
41
  },
41
42
  "peerDependencies": {
@@ -5,6 +5,13 @@ import type { OutreachEnrollmentsLoad } from './use-outreach-data';
5
5
  export interface OutreachEnrollmentsTableProps {
6
6
  enrollments: OutreachEnrollmentsLoad;
7
7
  steps: readonly OutreachSequenceStep[];
8
+ /**
9
+ * Whether this sequence counts link clicks (AGL-3239). The Clicks column
10
+ * is shown only when it does: a column of dashes for a sequence that
11
+ * measures nothing reads as "nobody clicked", which is a different claim
12
+ * and a false one.
13
+ */
14
+ trackClicks?: boolean;
8
15
  /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */
9
16
  timeZone: string | null;
10
17
  api: OutreachApi;
@@ -22,6 +22,7 @@ import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagina
22
22
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
23
23
  import { Button, Card, CardContent, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack, TextField, Typography, useMediaQuery, useTheme, Chip } from "@mui/material";
24
24
  import { useState } from "react";
25
+ import { readOutreachEngagement } from "../model/enrollment-engagement.js";
25
26
  import { OUTREACH_TASK_KIND_LABELS } from "../model/outreach.types.js";
26
27
  import { formatOutreachTime, OUTREACH_STOP_REASON_LABELS, OutreachEnrollmentStatusChip, OutreachLoading, OutreachLoadProblem } from "./outreach-ui.js";
27
28
  /** The step an enrollment is on, as a person reads it. */ export function outreachCurrentStepLabel(enrollment, steps) {
@@ -70,7 +71,7 @@ const ACTION_LABELS = {
70
71
  label: 'Mark do-not-contact'
71
72
  }
72
73
  };
73
- /** The table's columns; the person and the actions are drawn from the row's enrollment. */ function columns(rowActions) {
74
+ /** The table's columns; the person and the actions are drawn from the row's enrollment. */ function columns(rowActions, trackClicks) {
74
75
  return [
75
76
  {
76
77
  field: 'person',
@@ -141,6 +142,43 @@ const ACTION_LABELS = {
141
142
  width: 190,
142
143
  sortable: false
143
144
  },
145
+ ...trackClicks ? [
146
+ {
147
+ field: 'clicks',
148
+ headerName: 'Clicks',
149
+ width: 130,
150
+ sortable: false,
151
+ renderCell: ({ row })=>{
152
+ const engagement = readOutreachEngagement(row.enrollment.engagement);
153
+ if (!engagement.clicks) {
154
+ return /*#__PURE__*/ _jsx(Typography, {
155
+ variant: "body2",
156
+ color: "text.secondary",
157
+ children: "—"
158
+ });
159
+ }
160
+ return /*#__PURE__*/ _jsxs(Stack, {
161
+ spacing: 0,
162
+ sx: {
163
+ justifyContent: 'center',
164
+ height: '100%'
165
+ },
166
+ children: [
167
+ /*#__PURE__*/ _jsx(Typography, {
168
+ variant: "body2",
169
+ children: engagement.clicks === 1 ? '1 click' : `${engagement.clicks} clicks`
170
+ }),
171
+ engagement.lastClickUrl ? /*#__PURE__*/ _jsx(Typography, {
172
+ variant: "caption",
173
+ color: "text.secondary",
174
+ noWrap: true,
175
+ children: engagement.lastClickUrl
176
+ }) : null
177
+ ]
178
+ });
179
+ }
180
+ }
181
+ ] : [],
144
182
  {
145
183
  field: 'stopReason',
146
184
  headerName: 'Stop reason',
@@ -160,6 +198,7 @@ const ACTION_LABELS = {
160
198
  */ export function OutreachEnrollmentsTable(props) {
161
199
  var _CONFIRM_confirming_action, _CONFIRM_confirming_action1, _CONFIRM_confirming_action2;
162
200
  const { enrollments, steps, timeZone, api } = props;
201
+ const trackClicks = props.trackClicks === true;
163
202
  const theme = useTheme();
164
203
  const narrow = useMediaQuery(theme.breakpoints.down('md'));
165
204
  const { enqueueSnackbar } = useSnackbar();
@@ -310,6 +349,11 @@ const ACTION_LABELS = {
310
349
  color: "text.secondary",
311
350
  children: `Next send: ${nextSend(enrollment)} · Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`
312
351
  }),
352
+ trackClicks && readOutreachEngagement(enrollment.engagement).clicks ? /*#__PURE__*/ _jsx(Typography, {
353
+ variant: "body2",
354
+ color: "text.secondary",
355
+ children: `Clicked ${readOutreachEngagement(enrollment.engagement).clicks} ${readOutreachEngagement(enrollment.engagement).clicks === 1 ? 'time' : 'times'}`
356
+ }) : null,
313
357
  outreachStopLabel(enrollment) ? /*#__PURE__*/ _jsx(Typography, {
314
358
  variant: "body2",
315
359
  color: "text.secondary",
@@ -321,7 +365,7 @@ const ACTION_LABELS = {
321
365
  }, enrollment.id))
322
366
  }) : /*#__PURE__*/ _jsx(ListTable, {
323
367
  "aria-label": "Enrollments",
324
- columns: columns(rowActions),
368
+ columns: columns(rowActions, trackClicks),
325
369
  rows: pageRows.map((enrollment)=>({
326
370
  $id: enrollment.id,
327
371
  enrollment,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enrollments-table.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 EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Card,\n CardContent,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n useMediaQuery,\n useTheme,\n Chip,\n} from '@mui/material'\nimport { type ReactNode, useState } from 'react'\nimport type { OutreachEnrollmentAction } from '../model/outreach-api'\nimport {\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n formatOutreachTime,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport type { OutreachEnrollmentsLoad } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n enrollments: OutreachEnrollmentsLoad\n steps: readonly OutreachSequenceStep[]\n /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */\n timeZone: string | null\n api: OutreachApi\n /** Shown in the empty state when the sequence takes people. */\n enrollAction?: ReactNode\n}\n\n/** The step an enrollment is on, as a person reads it. */\nexport function outreachCurrentStepLabel(\n enrollment: Pick<OutreachEnrollment, 'status' | 'stepIndex'>,\n steps: readonly OutreachSequenceStep[],\n): string {\n if (enrollment.status === 'finished' || enrollment.stepIndex >= steps.length)\n return 'All steps done'\n const step = steps[enrollment.stepIndex]\n const kind =\n step?.kind === 'email'\n ? 'Email'\n : step?.kind === 'task'\n ? OUTREACH_TASK_KIND_LABELS[step.taskKind]\n : 'Step'\n return `Step ${enrollment.stepIndex + 1} of ${steps.length} · ${kind}`\n}\n\n/** Why an enrollment stopped, as the table says it; `''` while it runs. */\nexport function outreachStopLabel(\n enrollment: Pick<OutreachEnrollment, 'stopReason' | 'stopDetail'>,\n): string {\n if (!enrollment.stopReason) return ''\n const reason = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n return enrollment.stopDetail ? `${reason} — ${enrollment.stopDetail}` : reason\n}\n\n/** The last thing that happened: a send, a stop, or the enrollment itself. */\nfunction lastActivityMs(enrollment: OutreachEnrollment): number {\n return Math.max(\n enrollment.lastSentAtMs ?? 0,\n enrollment.stoppedAtMs ?? 0,\n enrollment.createdAtMs ?? 0,\n )\n}\n\n/** What a member may do to an enrollment in the status it is in. */\nfunction actionsFor(\n enrollment: OutreachEnrollment,\n): OutreachEnrollmentAction[] {\n const actions: OutreachEnrollmentAction[] = []\n if (enrollment.status === 'active') actions.push('pause', 'stop')\n if (enrollment.status === 'paused') actions.push('resume', 'stop')\n if (enrollment.status !== 'opted_out') actions.push('do_not_contact')\n return actions\n}\n\nconst ACTION_LABELS: Record<OutreachEnrollmentAction, string> = {\n pause: 'Pause',\n resume: 'Resume',\n stop: 'Stop',\n do_not_contact: 'Mark do-not-contact',\n}\n\n/** What the snackbar says once an action lands. */\nconst DONE_LABELS: Record<\n Exclude<OutreachEnrollmentAction, 'do_not_contact'>,\n string\n> = {\n pause: 'Paused.',\n resume: 'Resumed.',\n stop: 'Stopped.',\n}\n\n/** The two actions that end something for good ask first. */\nconst CONFIRM: Partial<\n Record<\n OutreachEnrollmentAction,\n { title: string; body: string; label: string }\n >\n> = {\n stop: {\n title: 'Stop this enrollment?',\n body: 'They won’t get any more steps from this sequence, and they can’t be enrolled in it again.',\n label: 'Stop',\n },\n do_not_contact: {\n title: 'Mark do-not-contact?',\n body:\n 'The address goes on your organization’s do-not-contact list, which every sequence checks before ' +\n 'every send, and they are stopped in every sequence they are in.',\n label: 'Mark do-not-contact',\n },\n}\n\n/** The table's columns; the person and the actions are drawn from the row's enrollment. */\nfunction columns(\n rowActions: (enrollment: OutreachEnrollment) => ReactNode,\n): NonNullable<ListTableProps['columns']> {\n return [\n {\n field: 'person',\n headerName: 'Person',\n flex: 1,\n minWidth: 200,\n sortable: false,\n renderCell: ({ row }) => (\n <Stack\n spacing={0}\n sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}\n >\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.enrollment.contactName || row.enrollment.email}\n </Typography>\n {row.enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {row.enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 120,\n renderCell: ({ row }) => (\n <OutreachEnrollmentStatusChip status={row.status} />\n ),\n },\n { field: 'step', headerName: 'Current step', flex: 1, minWidth: 170 },\n { field: 'nextSend', headerName: 'Next send', width: 190, sortable: false },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 190,\n sortable: false,\n },\n { field: 'stopReason', headerName: 'Stop reason', flex: 1, minWidth: 170 },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/**\n * A sequence's enrollments (AGL-2980): who is in it, where each person\n * stands, when their next step goes out in the mailbox's timezone, and the\n * four things a member can do about it — pause, resume, stop, and mark\n * do-not-contact. A table on wider screens, a list of cards on a phone.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { enrollments, steps, timeZone, api } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const { enqueueSnackbar } = useSnackbar()\n const [confirming, setConfirming] = useState<{\n enrollment: OutreachEnrollment\n action: OutreachEnrollmentAction\n } | null>(null)\n const [detail, setDetail] = useState('')\n const [busy, setBusy] = useState<string | null>(null)\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n\n const act = async (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n why?: string,\n ) => {\n setBusy(enrollment.id)\n try {\n const answer = await api.actOnEnrollment(enrollment.id, action, why)\n const others = answer.stoppedOthers\n enqueueSnackbar(\n action === 'do_not_contact'\n ? `Marked do-not-contact${others ? `, and stopped in ${others} other ${others === 1 ? 'sequence' : 'sequences'}` : ''}.`\n : answer.changed\n ? DONE_LABELS[action]\n : 'Nothing to change.',\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(null)\n }\n }\n\n const choose = (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n ) => {\n if (CONFIRM[action]) {\n setDetail('')\n setConfirming({ enrollment, action })\n } else {\n void act(enrollment, action)\n }\n }\n\n if (enrollments.status === 'loading')\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n )\n }\n if (!enrollments.data.length) {\n return (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"Nobody is enrolled yet\"\n description=\"People you enroll show here, with where each of them is in the sequence.\"\n action={props.enrollAction}\n />\n </Card>\n )\n }\n\n const rowActions = (enrollment: OutreachEnrollment) => (\n <ListRowActions\n label={enrollment.contactName || enrollment.email}\n items={actionsFor(enrollment).map((action) => ({\n key: action,\n label: ACTION_LABELS[action],\n onClick: () => choose(enrollment, action),\n destructive: action === 'stop' || action === 'do_not_contact',\n disabled: busy === enrollment.id,\n }))}\n />\n )\n const nextSend = (enrollment: OutreachEnrollment) =>\n enrollment.status === 'active' || enrollment.status === 'paused'\n ? formatOutreachTime(enrollment.nextDueAtMs, timeZone)\n : '—'\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">\n {enrollment.contactName || enrollment.email}\n </Typography>\n {enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n )\n\n const loaded = enrollments.data\n const pageRows = loaded.slice(page * pageSize, (page + 1) * pageSize)\n const lastLoadedPage = Math.max(0, Math.ceil(loaded.length / pageSize) - 1)\n /** Past the rows read so far, the next page reads another window first. */\n const turnTo = (next: number) => {\n if ((next + 1) * pageSize > loaded.length && enrollments.hasMore)\n enrollments.showMore()\n setPage(next)\n }\n\n return (\n <Stack spacing={1.5}>\n {narrow ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Enrollments\"\n >\n {pageRows.map((enrollment) => (\n <Card key={enrollment.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n >\n <Stack sx={{ flexGrow: 1, minWidth: 0 }}>\n {person(enrollment)}\n </Stack>\n <OutreachEnrollmentStatusChip status={enrollment.status} />\n {rowActions(enrollment)}\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Next send: ${nextSend(enrollment)} · Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`}\n </Typography>\n {outreachStopLabel(enrollment) ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachStopLabel(enrollment)}\n </Typography>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <ListTable\n aria-label=\"Enrollments\"\n columns={columns(rowActions)}\n rows={pageRows.map((enrollment) => ({\n $id: enrollment.id,\n enrollment,\n status: enrollment.status,\n step: outreachCurrentStepLabel(enrollment, steps),\n nextSend: nextSend(enrollment),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n stopReason: outreachStopLabel(enrollment) || '—',\n }))}\n quickFilter={false}\n hideFooter\n />\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={enrollments.hasMore ? undefined : loaded.length}\n hasMore={enrollments.hasMore || page < lastLoadedPage}\n onPageChange={turnTo}\n onPageSizeChange={setPageSize}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n {confirming ? (\n <>\n <DialogTitle>{CONFIRM[confirming.action]?.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>\n {CONFIRM[confirming.action]?.body}\n </DialogContentText>\n <TextField\n label=\"Why (optional)\"\n value={detail}\n onChange={(event) => setDetail(event.target.value)}\n fullWidth\n slotProps={{ htmlInput: { maxLength: 200 } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const { enrollment, action } = confirming\n setConfirming(null)\n void act(enrollment, action, detail.trim() || undefined)\n }}\n >\n {CONFIRM[confirming.action]?.label}\n </Button>\n </DialogActions>\n </>\n ) : null}\n </Dialog>\n </Stack>\n )\n}\nOutreachEnrollmentsTable.displayName = 'OutreachEnrollmentsTable'\n\nexport default OutreachEnrollmentsTable\n"],"names":["EmptyStateComponent","ListPagination","ListRowActions","ListTable","listActionsColumn","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Button","Card","CardContent","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useMediaQuery","useTheme","Chip","useState","OUTREACH_TASK_KIND_LABELS","formatOutreachTime","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","actionsFor","actions","push","ACTION_LABELS","pause","resume","stop","do_not_contact","DONE_LABELS","CONFIRM","title","body","label","columns","rowActions","field","headerName","flex","minWidth","sortable","renderCell","row","spacing","sx","justifyContent","height","direction","alignItems","variant","noWrap","contactName","email","target","size","color","width","OutreachEnrollmentsTable","props","enrollments","timeZone","api","theme","narrow","breakpoints","down","enqueueSnackbar","confirming","setConfirming","detail","setDetail","busy","setBusy","page","setPage","pageSize","setPageSize","act","action","why","id","answer","actOnEnrollment","others","stoppedOthers","changed","error","message","allowDuplicate","choose","what","data","description","enrollAction","items","map","key","onClick","destructive","disabled","nextSend","nextDueAtMs","person","loaded","pageRows","slice","lastLoadedPage","ceil","turnTo","next","hasMore","showMore","component","listStyle","p","m","aria-label","flexGrow","rows","$id","lastActivity","quickFilter","hideFooter","rowCount","count","undefined","onPageChange","onPageSizeChange","open","Boolean","onClose","fullWidth","maxWidth","value","onChange","event","slotProps","htmlInput","maxLength","trim","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enrollments-table.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 EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Card,\n CardContent,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n useMediaQuery,\n useTheme,\n Chip,\n} from '@mui/material'\nimport { type ReactNode, useState } from 'react'\nimport { readOutreachEngagement } from '../model/enrollment-engagement'\nimport type { OutreachEnrollmentAction } from '../model/outreach-api'\nimport {\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n formatOutreachTime,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport type { OutreachEnrollmentsLoad } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n enrollments: OutreachEnrollmentsLoad\n steps: readonly OutreachSequenceStep[]\n /**\n * Whether this sequence counts link clicks (AGL-3239). The Clicks column\n * is shown only when it does: a column of dashes for a sequence that\n * measures nothing reads as \"nobody clicked\", which is a different claim\n * and a false one.\n */\n trackClicks?: boolean\n /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */\n timeZone: string | null\n api: OutreachApi\n /** Shown in the empty state when the sequence takes people. */\n enrollAction?: ReactNode\n}\n\n/** The step an enrollment is on, as a person reads it. */\nexport function outreachCurrentStepLabel(\n enrollment: Pick<OutreachEnrollment, 'status' | 'stepIndex'>,\n steps: readonly OutreachSequenceStep[],\n): string {\n if (enrollment.status === 'finished' || enrollment.stepIndex >= steps.length)\n return 'All steps done'\n const step = steps[enrollment.stepIndex]\n const kind =\n step?.kind === 'email'\n ? 'Email'\n : step?.kind === 'task'\n ? OUTREACH_TASK_KIND_LABELS[step.taskKind]\n : 'Step'\n return `Step ${enrollment.stepIndex + 1} of ${steps.length} · ${kind}`\n}\n\n/** Why an enrollment stopped, as the table says it; `''` while it runs. */\nexport function outreachStopLabel(\n enrollment: Pick<OutreachEnrollment, 'stopReason' | 'stopDetail'>,\n): string {\n if (!enrollment.stopReason) return ''\n const reason = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n return enrollment.stopDetail ? `${reason} — ${enrollment.stopDetail}` : reason\n}\n\n/** The last thing that happened: a send, a stop, or the enrollment itself. */\nfunction lastActivityMs(enrollment: OutreachEnrollment): number {\n return Math.max(\n enrollment.lastSentAtMs ?? 0,\n enrollment.stoppedAtMs ?? 0,\n enrollment.createdAtMs ?? 0,\n )\n}\n\n/** What a member may do to an enrollment in the status it is in. */\nfunction actionsFor(\n enrollment: OutreachEnrollment,\n): OutreachEnrollmentAction[] {\n const actions: OutreachEnrollmentAction[] = []\n if (enrollment.status === 'active') actions.push('pause', 'stop')\n if (enrollment.status === 'paused') actions.push('resume', 'stop')\n if (enrollment.status !== 'opted_out') actions.push('do_not_contact')\n return actions\n}\n\nconst ACTION_LABELS: Record<OutreachEnrollmentAction, string> = {\n pause: 'Pause',\n resume: 'Resume',\n stop: 'Stop',\n do_not_contact: 'Mark do-not-contact',\n}\n\n/** What the snackbar says once an action lands. */\nconst DONE_LABELS: Record<\n Exclude<OutreachEnrollmentAction, 'do_not_contact'>,\n string\n> = {\n pause: 'Paused.',\n resume: 'Resumed.',\n stop: 'Stopped.',\n}\n\n/** The two actions that end something for good ask first. */\nconst CONFIRM: Partial<\n Record<\n OutreachEnrollmentAction,\n { title: string; body: string; label: string }\n >\n> = {\n stop: {\n title: 'Stop this enrollment?',\n body: 'They won’t get any more steps from this sequence, and they can’t be enrolled in it again.',\n label: 'Stop',\n },\n do_not_contact: {\n title: 'Mark do-not-contact?',\n body:\n 'The address goes on your organization’s do-not-contact list, which every sequence checks before ' +\n 'every send, and they are stopped in every sequence they are in.',\n label: 'Mark do-not-contact',\n },\n}\n\n/** The table's columns; the person and the actions are drawn from the row's enrollment. */\nfunction columns(\n rowActions: (enrollment: OutreachEnrollment) => ReactNode,\n trackClicks: boolean,\n): NonNullable<ListTableProps['columns']> {\n return [\n {\n field: 'person',\n headerName: 'Person',\n flex: 1,\n minWidth: 200,\n sortable: false,\n renderCell: ({ row }) => (\n <Stack\n spacing={0}\n sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}\n >\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.enrollment.contactName || row.enrollment.email}\n </Typography>\n {row.enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {row.enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 120,\n renderCell: ({ row }) => (\n <OutreachEnrollmentStatusChip status={row.status} />\n ),\n },\n { field: 'step', headerName: 'Current step', flex: 1, minWidth: 170 },\n { field: 'nextSend', headerName: 'Next send', width: 190, sortable: false },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 190,\n sortable: false,\n },\n ...(trackClicks\n ? [\n {\n field: 'clicks',\n headerName: 'Clicks',\n width: 130,\n sortable: false,\n renderCell: ({ row }: { row: { enrollment: OutreachEnrollment } }) => {\n const engagement = readOutreachEngagement(row.enrollment.engagement)\n if (!engagement.clicks) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n —\n </Typography>\n )\n }\n return (\n <Stack spacing={0} sx={{ justifyContent: 'center', height: '100%' }}>\n <Typography variant=\"body2\">\n {engagement.clicks === 1 ? '1 click' : `${engagement.clicks} clicks`}\n </Typography>\n {engagement.lastClickUrl ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {engagement.lastClickUrl}\n </Typography>\n ) : null}\n </Stack>\n )\n },\n },\n ]\n : []),\n { field: 'stopReason', headerName: 'Stop reason', flex: 1, minWidth: 170 },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/**\n * A sequence's enrollments (AGL-2980): who is in it, where each person\n * stands, when their next step goes out in the mailbox's timezone, and the\n * four things a member can do about it — pause, resume, stop, and mark\n * do-not-contact. A table on wider screens, a list of cards on a phone.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { enrollments, steps, timeZone, api } = props\n const trackClicks = props.trackClicks === true\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const { enqueueSnackbar } = useSnackbar()\n const [confirming, setConfirming] = useState<{\n enrollment: OutreachEnrollment\n action: OutreachEnrollmentAction\n } | null>(null)\n const [detail, setDetail] = useState('')\n const [busy, setBusy] = useState<string | null>(null)\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n\n const act = async (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n why?: string,\n ) => {\n setBusy(enrollment.id)\n try {\n const answer = await api.actOnEnrollment(enrollment.id, action, why)\n const others = answer.stoppedOthers\n enqueueSnackbar(\n action === 'do_not_contact'\n ? `Marked do-not-contact${others ? `, and stopped in ${others} other ${others === 1 ? 'sequence' : 'sequences'}` : ''}.`\n : answer.changed\n ? DONE_LABELS[action]\n : 'Nothing to change.',\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(null)\n }\n }\n\n const choose = (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n ) => {\n if (CONFIRM[action]) {\n setDetail('')\n setConfirming({ enrollment, action })\n } else {\n void act(enrollment, action)\n }\n }\n\n if (enrollments.status === 'loading')\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n )\n }\n if (!enrollments.data.length) {\n return (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"Nobody is enrolled yet\"\n description=\"People you enroll show here, with where each of them is in the sequence.\"\n action={props.enrollAction}\n />\n </Card>\n )\n }\n\n const rowActions = (enrollment: OutreachEnrollment) => (\n <ListRowActions\n label={enrollment.contactName || enrollment.email}\n items={actionsFor(enrollment).map((action) => ({\n key: action,\n label: ACTION_LABELS[action],\n onClick: () => choose(enrollment, action),\n destructive: action === 'stop' || action === 'do_not_contact',\n disabled: busy === enrollment.id,\n }))}\n />\n )\n const nextSend = (enrollment: OutreachEnrollment) =>\n enrollment.status === 'active' || enrollment.status === 'paused'\n ? formatOutreachTime(enrollment.nextDueAtMs, timeZone)\n : '—'\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">\n {enrollment.contactName || enrollment.email}\n </Typography>\n {enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n )\n\n const loaded = enrollments.data\n const pageRows = loaded.slice(page * pageSize, (page + 1) * pageSize)\n const lastLoadedPage = Math.max(0, Math.ceil(loaded.length / pageSize) - 1)\n /** Past the rows read so far, the next page reads another window first. */\n const turnTo = (next: number) => {\n if ((next + 1) * pageSize > loaded.length && enrollments.hasMore)\n enrollments.showMore()\n setPage(next)\n }\n\n return (\n <Stack spacing={1.5}>\n {narrow ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Enrollments\"\n >\n {pageRows.map((enrollment) => (\n <Card key={enrollment.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n >\n <Stack sx={{ flexGrow: 1, minWidth: 0 }}>\n {person(enrollment)}\n </Stack>\n <OutreachEnrollmentStatusChip status={enrollment.status} />\n {rowActions(enrollment)}\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Next send: ${nextSend(enrollment)} · Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`}\n </Typography>\n {trackClicks && readOutreachEngagement(enrollment.engagement).clicks ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Clicked ${readOutreachEngagement(enrollment.engagement).clicks} ${\n readOutreachEngagement(enrollment.engagement).clicks === 1 ? 'time' : 'times'\n }`}\n </Typography>\n ) : null}\n {outreachStopLabel(enrollment) ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachStopLabel(enrollment)}\n </Typography>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <ListTable\n aria-label=\"Enrollments\"\n columns={columns(rowActions, trackClicks)}\n rows={pageRows.map((enrollment) => ({\n $id: enrollment.id,\n enrollment,\n status: enrollment.status,\n step: outreachCurrentStepLabel(enrollment, steps),\n nextSend: nextSend(enrollment),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n stopReason: outreachStopLabel(enrollment) || '—',\n }))}\n quickFilter={false}\n hideFooter\n />\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={enrollments.hasMore ? undefined : loaded.length}\n hasMore={enrollments.hasMore || page < lastLoadedPage}\n onPageChange={turnTo}\n onPageSizeChange={setPageSize}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n {confirming ? (\n <>\n <DialogTitle>{CONFIRM[confirming.action]?.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>\n {CONFIRM[confirming.action]?.body}\n </DialogContentText>\n <TextField\n label=\"Why (optional)\"\n value={detail}\n onChange={(event) => setDetail(event.target.value)}\n fullWidth\n slotProps={{ htmlInput: { maxLength: 200 } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const { enrollment, action } = confirming\n setConfirming(null)\n void act(enrollment, action, detail.trim() || undefined)\n }}\n >\n {CONFIRM[confirming.action]?.label}\n </Button>\n </DialogActions>\n </>\n ) : null}\n </Dialog>\n </Stack>\n )\n}\nOutreachEnrollmentsTable.displayName = 'OutreachEnrollmentsTable'\n\nexport default OutreachEnrollmentsTable\n"],"names":["EmptyStateComponent","ListPagination","ListRowActions","ListTable","listActionsColumn","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Button","Card","CardContent","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useMediaQuery","useTheme","Chip","useState","readOutreachEngagement","OUTREACH_TASK_KIND_LABELS","formatOutreachTime","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","actionsFor","actions","push","ACTION_LABELS","pause","resume","stop","do_not_contact","DONE_LABELS","CONFIRM","title","body","label","columns","rowActions","trackClicks","field","headerName","flex","minWidth","sortable","renderCell","row","spacing","sx","justifyContent","height","direction","alignItems","variant","noWrap","contactName","email","target","size","color","width","engagement","clicks","lastClickUrl","OutreachEnrollmentsTable","props","enrollments","timeZone","api","theme","narrow","breakpoints","down","enqueueSnackbar","confirming","setConfirming","detail","setDetail","busy","setBusy","page","setPage","pageSize","setPageSize","act","action","why","id","answer","actOnEnrollment","others","stoppedOthers","changed","error","message","allowDuplicate","choose","what","data","description","enrollAction","items","map","key","onClick","destructive","disabled","nextSend","nextDueAtMs","person","loaded","pageRows","slice","lastLoadedPage","ceil","turnTo","next","hasMore","showMore","component","listStyle","p","m","aria-label","flexGrow","rows","$id","lastActivity","quickFilter","hideFooter","rowCount","count","undefined","onPageChange","onPageSizeChange","open","Boolean","onClose","fullWidth","maxWidth","value","onChange","event","slotProps","htmlInput","maxLength","trim","displayName"],"mappings":"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"}
@@ -29,8 +29,9 @@ import { OutreachEnrollDialog } from "./enroll-dialog.js";
29
29
  import { OutreachEnrollmentsTable } from "./enrollments-table.js";
30
30
  import { OutreachLink, OutreachLoading, OutreachLoadProblem, OutreachSequenceStatusChip, useOutreachNavigate } from "./outreach-ui.js";
31
31
  import { OutreachSequenceEditor } from "./sequence-editor.js";
32
+ import { OutreachSequenceReportCard } from "./sequence-report-card.js";
32
33
  import { OutreachRouteError, useOutreachApi } from "./use-outreach-api.js";
33
- import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data.js";
34
+ import { useOutreachEnrollments, useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-data.js";
34
35
  /**
35
36
  * What stops a sequence being activated, read before anyone clicks: its
36
37
  * mailbox not sending, and a footer the organization cannot write yet —
@@ -90,7 +91,7 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
90
91
  * it, and what can be done with it — activate, pause, archive, delete a
91
92
  * draft, and enroll people in an active one.
92
93
  */ export function OutreachSequenceDetail(props) {
93
- var _props_mailboxes_mailboxes_find, _ref, _props_org, _ref1;
94
+ var _props_mailboxes_mailboxes_find, _ref, _ref1, _props_org, _ref2;
94
95
  const { orgId, sequenceId, sectionPath } = props;
95
96
  const api = useOutreachApi(orgId);
96
97
  const navigate = useOutreachNavigate();
@@ -98,6 +99,9 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
98
99
  const { data: user } = useUser();
99
100
  const loaded = useOutreachSequence(orgId, sequenceId);
100
101
  const enrollments = useOutreachEnrollments(props.tab === 'enrollments' ? orgId : null, sequenceId);
102
+ // One document however large the sequence, and read on both tabs, because
103
+ // the report card sits above them (AGL-3239).
104
+ const links = useOutreachSequenceLinks(orgId, sequenceId);
101
105
  const [busy, setBusy] = useState(false);
102
106
  const [refusal, setRefusal] = useState(null);
103
107
  const [confirming, setConfirming] = useState(null);
@@ -352,6 +356,11 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
352
356
  })
353
357
  ]
354
358
  }) : null,
359
+ sequence.status === 'draft' ? null : /*#__PURE__*/ _jsx(OutreachSequenceReportCard, {
360
+ sequence: sequence,
361
+ links: links,
362
+ timeZone: (_ref = mailbox == null ? void 0 : mailbox.timezone) != null ? _ref : null
363
+ }),
355
364
  /*#__PURE__*/ _jsxs(Tabs, {
356
365
  value: props.tab,
357
366
  onChange: (_event, next)=>navigate(next === 'steps' ? detailPath : `${detailPath}/enrollments`),
@@ -378,7 +387,8 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
378
387
  }) : /*#__PURE__*/ _jsx(OutreachEnrollmentsTable, {
379
388
  enrollments: enrollments,
380
389
  steps: sequence.steps,
381
- timeZone: (_ref = mailbox == null ? void 0 : mailbox.timezone) != null ? _ref : null,
390
+ trackClicks: sequence.settings.trackClicks,
391
+ timeZone: (_ref1 = mailbox == null ? void 0 : mailbox.timezone) != null ? _ref1 : null,
382
392
  api: api,
383
393
  enrollAction: sequence.status === 'active' ? enrollButton : undefined
384
394
  }),
@@ -388,7 +398,7 @@ import { useOutreachEnrollments, useOutreachSequence } from "./use-outreach-data
388
398
  orgId: orgId,
389
399
  sequence: sequence,
390
400
  contactGroupId: sequence.hostId ? consentGroupForHost((_props_org = props.org) != null ? _props_org : null, sequence.hostId).groupId : '',
391
- uid: (_ref1 = user == null ? void 0 : user.uid) != null ? _ref1 : null,
401
+ uid: (_ref2 = user == null ? void 0 : user.uid) != null ? _ref2 : null,
392
402
  api: api
393
403
  }),
394
404
  /*#__PURE__*/ _jsxs(Dialog, {
@@ -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 { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachEnrollments,\n useOutreachSequence,\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 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 <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 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","OutreachRouteError","useOutreachApi","useOutreachEnrollments","useOutreachSequence","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","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","value","onChange","_event","next","aria-label","orgMount","onSaved","undefined","timeZone","timezone","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 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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"}
@@ -408,6 +408,28 @@ import { useOutreachEmailTemplates } from "./use-outreach-crm.js";
408
408
  }),
409
409
  label: "Include customers"
410
410
  }),
411
+ /*#__PURE__*/ _jsxs(Stack, {
412
+ spacing: 0,
413
+ children: [
414
+ /*#__PURE__*/ _jsx(FormControlLabel, {
415
+ control: /*#__PURE__*/ _jsx(Switch, {
416
+ checked: draft.settings.trackClicks,
417
+ onChange: (event)=>update({
418
+ settings: _extends({}, draft.settings, {
419
+ trackClicks: event.target.checked
420
+ })
421
+ }),
422
+ disabled: archived
423
+ }),
424
+ label: "Count link clicks"
425
+ }),
426
+ /*#__PURE__*/ _jsx(Typography, {
427
+ variant: "caption",
428
+ color: "text.secondary",
429
+ children: "Links in these emails are replaced with links of ours that forward to the same page, so clicks can be counted. The recipient sees the replacement, not your address. Opens aren’t counted either way: these emails are plain text, and counting an open needs a tracking image."
430
+ })
431
+ ]
432
+ }),
411
433
  /*#__PURE__*/ _jsx(OutreachSendWindowFields, {
412
434
  window: draft.settings.window,
413
435
  onChange: (window)=>update({