@aglyn/plugins-outreach 1.0.0-beta.199 → 1.0.0-beta.201

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 (108) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/do-not-contact-domains.js +60 -42
  3. package/src/lib/components/do-not-contact-domains.js.map +1 -1
  4. package/src/lib/components/enrollment-actions.d.ts +33 -0
  5. package/src/lib/components/enrollment-actions.js +174 -0
  6. package/src/lib/components/enrollment-actions.js.map +1 -0
  7. package/src/lib/components/enrollment-detail.d.ts +21 -0
  8. package/src/lib/components/enrollment-detail.js +870 -0
  9. package/src/lib/components/enrollment-detail.js.map +1 -0
  10. package/src/lib/components/enrollment-filter-params.d.ts +58 -0
  11. package/src/lib/components/enrollment-filter-params.js +98 -0
  12. package/src/lib/components/enrollment-filter-params.js.map +1 -0
  13. package/src/lib/components/enrollments-table.d.ts +25 -2
  14. package/src/lib/components/enrollments-table.js +303 -283
  15. package/src/lib/components/enrollments-table.js.map +1 -1
  16. package/src/lib/components/outreach-console-page.js +10 -0
  17. package/src/lib/components/outreach-console-page.js.map +1 -1
  18. package/src/lib/components/sequence-detail.d.ts +2 -0
  19. package/src/lib/components/sequence-detail.js +73 -11
  20. package/src/lib/components/sequence-detail.js.map +1 -1
  21. package/src/lib/components/sequence-report-card.d.ts +17 -0
  22. package/src/lib/components/sequence-report-card.js +63 -15
  23. package/src/lib/components/sequence-report-card.js.map +1 -1
  24. package/src/lib/components/sequences-section.d.ts +13 -1
  25. package/src/lib/components/sequences-section.js +108 -83
  26. package/src/lib/components/sequences-section.js.map +1 -1
  27. package/src/lib/components/use-outreach-api.d.ts +3 -1
  28. package/src/lib/components/use-outreach-api.js +6 -0
  29. package/src/lib/components/use-outreach-api.js.map +1 -1
  30. package/src/lib/components/use-outreach-data.d.ts +30 -25
  31. package/src/lib/components/use-outreach-data.js +93 -104
  32. package/src/lib/components/use-outreach-data.js.map +1 -1
  33. package/src/lib/components/use-outreach-enrollment.d.ts +18 -0
  34. package/src/lib/components/use-outreach-enrollment.js +133 -0
  35. package/src/lib/components/use-outreach-enrollment.js.map +1 -0
  36. package/src/lib/constants/api-routes.d.ts +2 -0
  37. package/src/lib/constants/api-routes.js +1 -0
  38. package/src/lib/constants/api-routes.js.map +1 -1
  39. package/src/lib/engine/gates.d.ts +4 -3
  40. package/src/lib/engine/gates.js.map +1 -1
  41. package/src/lib/engine/gateway-block.d.ts +4 -8
  42. package/src/lib/engine/gateway-block.js +6 -26
  43. package/src/lib/engine/gateway-block.js.map +1 -1
  44. package/src/lib/engine/mail-gateway.d.ts +18 -73
  45. package/src/lib/engine/mail-gateway.js +32 -206
  46. package/src/lib/engine/mail-gateway.js.map +1 -1
  47. package/src/lib/enrollment/enrollment-search.d.ts +32 -0
  48. package/src/lib/enrollment/enrollment-search.js +40 -0
  49. package/src/lib/enrollment/enrollment-search.js.map +1 -0
  50. package/src/lib/model/do-not-contact-domain-list-query.d.ts +32 -0
  51. package/src/lib/model/do-not-contact-domain-list-query.js +107 -0
  52. package/src/lib/model/do-not-contact-domain-list-query.js.map +1 -0
  53. package/src/lib/model/enrollment-engagement.d.ts +26 -2
  54. package/src/lib/model/enrollment-engagement.js +40 -3
  55. package/src/lib/model/enrollment-engagement.js.map +1 -1
  56. package/src/lib/model/enrollment-history.d.ts +40 -0
  57. package/src/lib/model/enrollment-history.js +95 -0
  58. package/src/lib/model/enrollment-history.js.map +1 -0
  59. package/src/lib/model/enrollment-list-query.d.ts +28 -0
  60. package/src/lib/model/enrollment-list-query.js +151 -0
  61. package/src/lib/model/enrollment-list-query.js.map +1 -0
  62. package/src/lib/model/enrollment-timeline.d.ts +78 -0
  63. package/src/lib/model/enrollment-timeline.js +430 -0
  64. package/src/lib/model/enrollment-timeline.js.map +1 -0
  65. package/src/lib/model/outreach-api.d.ts +24 -0
  66. package/src/lib/model/outreach-api.js.map +1 -1
  67. package/src/lib/model/outreach.types.d.ts +90 -29
  68. package/src/lib/model/outreach.types.js +33 -8
  69. package/src/lib/model/outreach.types.js.map +1 -1
  70. package/src/lib/model/sequence-list-query.d.ts +22 -0
  71. package/src/lib/model/sequence-list-query.js +99 -0
  72. package/src/lib/model/sequence-list-query.js.map +1 -0
  73. package/src/lib/routes/enroll-routes.js +34 -3
  74. package/src/lib/routes/enroll-routes.js.map +1 -1
  75. package/src/lib/routes/enrollment-routes.d.ts +10 -0
  76. package/src/lib/routes/enrollment-routes.js +82 -1
  77. package/src/lib/routes/enrollment-routes.js.map +1 -1
  78. package/src/lib/routes/register-routes.js +14 -4
  79. package/src/lib/routes/register-routes.js.map +1 -1
  80. package/src/lib/routes/route-deps.d.ts +3 -1
  81. package/src/lib/routes/route-deps.js.map +1 -1
  82. package/src/lib/routes/sequence-routes.js +6 -1
  83. package/src/lib/routes/sequence-routes.js.map +1 -1
  84. package/src/lib/runtime/click-events.d.ts +10 -5
  85. package/src/lib/runtime/click-events.js +45 -6
  86. package/src/lib/runtime/click-events.js.map +1 -1
  87. package/src/lib/runtime/gateway-ledger.d.ts +6 -0
  88. package/src/lib/runtime/gateway-ledger.js +9 -3
  89. package/src/lib/runtime/gateway-ledger.js.map +1 -1
  90. package/src/lib/runtime/host-suspension.d.ts +30 -0
  91. package/src/lib/runtime/host-suspension.js +35 -0
  92. package/src/lib/runtime/host-suspension.js.map +1 -0
  93. package/src/lib/runtime/person-erasure.d.ts +4 -1
  94. package/src/lib/runtime/person-erasure.js +14 -3
  95. package/src/lib/runtime/person-erasure.js.map +1 -1
  96. package/src/lib/runtime/platform-runtime-deps.js +34 -3
  97. package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
  98. package/src/lib/runtime/runtime-deps.d.ts +27 -1
  99. package/src/lib/runtime/runtime-deps.js.map +1 -1
  100. package/src/lib/runtime/send-job.js +46 -6
  101. package/src/lib/runtime/send-job.js.map +1 -1
  102. package/src/lib/runtime/sync-job.js +4 -0
  103. package/src/lib/runtime/sync-job.js.map +1 -1
  104. package/src/lib/storage/do-not-contact-store.js +6 -1
  105. package/src/lib/storage/do-not-contact-store.js.map +1 -1
  106. package/src/lib/storage/domain-intel-store.d.ts +61 -43
  107. package/src/lib/storage/domain-intel-store.js +138 -226
  108. package/src/lib/storage/domain-intel-store.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequences-section.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 EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListTable,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport {\n filterListRows,\n inMemoryListField,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport {\n Alert,\n Button,\n Card,\n CardContent,\n Chip,\n Stack,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useEffect, useMemo, useState } from 'react'\nimport {\n OUTREACH_SEQUENCE_STATUSES,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport {\n OUTREACH_SEQUENCE_STATUS_LABELS,\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport {\n OutreachSequenceDetail,\n type OutreachSequenceTab,\n} from './sequence-detail'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { useOutreachApi } from './use-outreach-api'\nimport {\n type OutreachSequenceCounts,\n useOutreachSequenceCounts,\n useOutreachSequences,\n} from './use-outreach-data'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachSequencesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n orgMount?: ConsolePluginOrgMount\n org?: Record<string, unknown> | null\n /** The section's own path: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n /** The path below the section: `[]`, `['new']`, `[id]` or `[id, 'enrollments']`. */\n subpath: readonly string[]\n /** The Mailboxes section: `/[orgSlug]/outreach/mailboxes`. */\n mailboxesPath: string\n /** The Compliance section: `/[orgSlug]/outreach/compliance`. */\n compliancePath: string\n}\n\n/** The new-sequence page's segment below the section. */\nexport const OUTREACH_NEW_SEQUENCE_SEGMENT = 'new'\n\n/**\n * The Sequences section (AGL-2980), and the pages below it: the list, a new\n * sequence, and one sequence's steps and enrollments. Each is a real URL, so\n * a sequence is a link a rep can keep and the back button walks them.\n */\nexport function OutreachSequencesSection(props: OutreachSequencesSectionProps) {\n const { orgId, sectionPath, subpath } = props\n const api = useOutreachApi(orgId)\n const settings = useOutreachComplianceSettings(api, orgId)\n const mailboxes = useOutreachMailboxes(orgId)\n const navigate = useOutreachNavigate()\n\n if (!orgId) return <OutreachLoading label=\"Loading sequences…\" />\n const [first, second] = subpath\n if (first === OUTREACH_NEW_SEQUENCE_SEGMENT) {\n return (\n <Stack spacing={2}>\n <OutreachLink href={sectionPath}>Sequences</OutreachLink>\n <Typography variant=\"h5\" component=\"h2\">\n New sequence\n </Typography>\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n sequence={null}\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={(sequence) => navigate(`${sectionPath}/${sequence.id}`)}\n onCancel={() => navigate(sectionPath)}\n />\n </Stack>\n )\n }\n if (first) {\n return (\n <OutreachSequenceDetail\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sectionPath={sectionPath}\n sequenceId={first}\n tab={\n (second === 'enrollments'\n ? 'enrollments'\n : 'steps') as OutreachSequenceTab\n }\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n compliancePath={props.compliancePath}\n />\n )\n }\n return (\n <OutreachSequenceList\n orgId={orgId}\n sectionPath={sectionPath}\n mailboxes={mailboxes.mailboxes}\n />\n )\n}\nOutreachSequencesSection.displayName = 'OutreachSequencesSection'\n\nconst COUNT_COLUMNS: ReadonlyArray<[keyof OutreachSequenceCounts, string]> = [\n ['enrolled', 'Enrolled'],\n ['active', 'Active'],\n ['replied', 'Replied'],\n ['bounced', 'Bounced'],\n ['optedOut', 'Opted out'],\n]\n\n/*\n * What the sequences grid's Filters panel offers (AGL-3317). The list holds\n * every sequence it read, so each clause and the quick search are answered\n * over that whole set before it is paged, never over the page on screen.\n */\nconst SEQUENCE_FILTER_FIELDS = [\n inMemoryListField('name', 'text'),\n inMemoryListField('mailbox', 'text'),\n inMemoryListField('status', 'select'),\n]\nconst SEQUENCE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Name',\n mailbox: 'Mailbox',\n status: 'Status',\n}\nconst SEQUENCE_FILTER_OPTIONS = {\n status: OUTREACH_SEQUENCE_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_SEQUENCE_STATUS_LABELS[status],\n })),\n}\n/** What the quick search reads on a sequence row. */\nconst SEQUENCE_SEARCH_FIELDS = ['name', 'mailbox'] as const\n\n/**\n * Every sequence: its mailbox, its status, and how many people it enrolled\n * and where they stand — a table on wider screens, cards on a phone.\n */\nexport function OutreachSequenceList(props: {\n orgId: string\n sectionPath: string\n mailboxes: readonly OutreachMailbox[]\n}) {\n const { orgId, sectionPath } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const navigate = useOutreachNavigate()\n const sequences = useOutreachSequences(orgId)\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const gridFilter = useListGridFilter({ selectFields: ['status'] })\n const mailboxLabel = (sequence: OutreachSequence) => {\n const mailbox = props.mailboxes.find(\n (entry) => entry.id === sequence.mailboxId,\n )\n return mailbox\n ? mailbox.sendAs || mailbox.email\n : sequence.mailboxId\n ? 'Mailbox removed'\n : 'No mailbox yet'\n }\n /*\n * Filtered BEFORE the page is cut: the list read every sequence (under\n * `OUTREACH_SEQUENCES_LIMIT`), so a clause or a search word answers over\n * all of them, and the footer counts the matches.\n */\n const searchKey = gridFilter.searchWords.join(' ')\n const matching = useMemo(\n () =>\n filterListRows(\n sequences.data.map((sequence) => ({\n sequence,\n name: sequence.name || 'Untitled',\n mailbox: mailboxLabel(sequence),\n status: sequence.status,\n })),\n SEQUENCE_FILTER_FIELDS,\n gridFilter.clauses,\n { paths: SEQUENCE_SEARCH_FIELDS, words: gridFilter.searchWords },\n ).map((row) => row.sequence),\n // `searchKey` stands for the words, which are a new array each render.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [sequences.data, props.mailboxes, gridFilter.clauses, searchKey],\n )\n // A narrowed list starts again at its first page.\n useEffect(() => setPage(0), [gridFilter.clauses, searchKey])\n const pageRows = matching.slice(page * pageSize, (page + 1) * pageSize)\n // Counted for the page on screen only: five aggregations a sequence.\n const counts = useOutreachSequenceCounts(\n orgId,\n pageRows.map((sequence) => sequence.id),\n )\n const footer = (\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={matching.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n )\n const newHref = `${sectionPath}/${OUTREACH_NEW_SEQUENCE_SEGMENT}`\n const newButton = (\n <Button\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n href={newHref}\n onClick={(event) => {\n event.preventDefault()\n navigate(newHref)\n }}\n >\n New sequence\n </Button>\n )\n const count = (\n sequence: OutreachSequence,\n key: keyof OutreachSequenceCounts,\n ) => (counts[sequence.id] ? String(counts[sequence.id][key]) : '—')\n\n /*\n * The read stops at `OUTREACH_SEQUENCES_LIMIT`, newest first, and probes one\n * past it, so this shows only when older sequences really exist.\n */\n const truncatedNotice = sequences.truncated ? (\n <Alert severity=\"info\">\n {`Showing the ${sequences.data.length.toLocaleString()} newest sequences. ` +\n 'The filters and the search narrow these only; older sequences are ' +\n 'not listed here.'}\n </Alert>\n ) : null\n\n const chips = (\n <ListFilterChips\n fields={SEQUENCE_FILTER_FIELDS}\n headers={SEQUENCE_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={SEQUENCE_FILTER_OPTIONS}\n />\n )\n let body\n if (sequences.status === 'loading')\n body = <OutreachLoading label=\"Loading sequences…\" />\n else if (sequences.status === 'error' || sequences.status === 'refused') {\n body = <OutreachLoadProblem status={sequences.status} what=\"sequences\" />\n } else if (!sequences.data.length) {\n body = (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No sequences yet\"\n description=\"A sequence is the emails, and the tasks between them, one person gets from one rep.\"\n action={newButton}\n />\n </Card>\n )\n } else if (narrow) {\n body = (\n <Stack spacing={1}>\n {chips}\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Sequences\"\n >\n {pageRows.map((sequence) => (\n <Card key={sequence.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <OutreachLink href={`${sectionPath}/${sequence.id}`}>\n {sequence.name || 'Untitled'}\n </OutreachLink>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {mailboxLabel(sequence)}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ flexWrap: 'wrap', rowGap: 0.5 }}\n >\n {COUNT_COLUMNS.map(([key, label]) => (\n <Chip\n key={key}\n size=\"small\"\n variant=\"outlined\"\n label={`${label} ${count(sequence, key)}`}\n />\n ))}\n </Stack>\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n {footer}\n {truncatedNotice}\n </Stack>\n )\n } else {\n const columns: NonNullable<ListTableProps['columns']> = listFilterGridColumns([\n { field: 'name', headerName: 'Name', flex: 1, minWidth: 180 },\n { field: 'mailbox', headerName: 'Mailbox', flex: 1, minWidth: 180 },\n {\n field: 'status',\n headerName: 'Status',\n width: 110,\n renderCell: ({ row }) => (\n <OutreachSequenceStatusChip status={row.status} />\n ),\n },\n ...COUNT_COLUMNS.map(([key, label]) => ({\n field: key,\n headerName: label,\n width: 96,\n align: 'right' as const,\n headerAlign: 'right' as const,\n })),\n ], SEQUENCE_FILTER_FIELDS, SEQUENCE_FILTER_OPTIONS, SEQUENCE_FILTER_HEADERS)\n body = (\n <Stack spacing={1}>\n {chips}\n <ListTable\n aria-label=\"Sequences\"\n columns={columns}\n rows={pageRows.map((sequence) => ({\n $id: sequence.id,\n name: sequence.name || 'Untitled',\n mailbox: mailboxLabel(sequence),\n status: sequence.status,\n ...Object.fromEntries(\n COUNT_COLUMNS.map(([key]) => [key, count(sequence, key)]),\n ),\n }))}\n onOpen={(id) => navigate(`${sectionPath}/${id}`)}\n /*\n * The panel and the search are the grid's; the list answers them\n * over every sequence it read (AGL-3317).\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n noRowsLabel=\"No sequences match these filters\"\n hideFooter\n />\n {footer}\n {truncatedNotice}\n </Stack>\n )\n }\n\n return (\n <CardDisplay\n header=\"Sequences\"\n help={pluginDocsHelp('sequences', { anchor: '#sequences' })}\n HeaderProps={{\n action:\n sequences.status === 'ready' && sequences.data.length\n ? newButton\n : undefined,\n }}\n contentGutterX\n contentGutterY\n >\n {body}\n </CardDisplay>\n )\n}\nOutreachSequenceList.displayName = 'OutreachSequenceList'\n\nexport default OutreachSequencesSection\n"],"names":["pluginDocsHelp","mdiPlus","CardDisplay","MdiIcon","EmptyStateComponent","ListFilterChips","ListPagination","ListTable","filterListRows","inMemoryListField","listFilterGridColumns","TABLE_PAGE_SIZE_DEFAULT","useListGridFilter","Alert","Button","Card","CardContent","Chip","Stack","Typography","useMediaQuery","useTheme","useEffect","useMemo","useState","OUTREACH_SEQUENCE_STATUSES","OUTREACH_SEQUENCE_STATUS_LABELS","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceDetail","OutreachSequenceEditor","useOutreachApi","useOutreachSequenceCounts","useOutreachSequences","useOutreachMailboxes","useOutreachComplianceSettings","OUTREACH_NEW_SEQUENCE_SEGMENT","OutreachSequencesSection","props","orgId","sectionPath","subpath","api","settings","mailboxes","navigate","label","first","second","spacing","href","variant","component","orgMount","sequence","mailboxesPath","onSaved","id","onCancel","org","sequenceId","tab","compliancePath","OutreachSequenceList","displayName","COUNT_COLUMNS","SEQUENCE_FILTER_FIELDS","SEQUENCE_FILTER_HEADERS","name","mailbox","status","SEQUENCE_FILTER_OPTIONS","map","value","SEQUENCE_SEARCH_FIELDS","theme","narrow","breakpoints","down","sequences","page","setPage","pageSize","setPageSize","gridFilter","selectFields","mailboxLabel","find","entry","mailboxId","sendAs","email","searchKey","searchWords","join","matching","data","clauses","paths","words","row","pageRows","slice","counts","footer","rowCount","length","count","onPageChange","onPageSizeChange","newHref","newButton","startIcon","path","fontSize","onClick","event","preventDefault","key","String","truncatedNotice","truncated","severity","toLocaleString","chips","fields","headers","onChange","setClauses","options","body","what","description","action","sx","listStyle","p","m","aria-label","direction","alignItems","justifyContent","color","flexWrap","rowGap","size","columns","field","headerName","flex","minWidth","width","renderCell","align","headerAlign","rows","$id","Object","fromEntries","onOpen","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","hideFooter","header","help","anchor","HeaderProps","undefined","contentGutterX","contentGutterY"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequences-section.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 EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListTable,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport { hiddenFilterVisibility } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport { listFilterGridColumns } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport {\n Button,\n Card,\n CardContent,\n Chip,\n Stack,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useMemo } from 'react'\nimport {\n OUTREACH_SEQUENCE_STATUSES,\n type OutreachMailbox,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { OUTREACH_SEQUENCE_LIST_QUERY } from '../model/sequence-list-query'\nimport {\n OUTREACH_SEQUENCE_STATUS_LABELS,\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachEnrollmentDetail } from './enrollment-detail'\nimport {\n OutreachSequenceDetail,\n type OutreachSequenceTab,\n} from './sequence-detail'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { useOutreachApi } from './use-outreach-api'\nimport {\n type OutreachSequenceCounts,\n useOutreachSequenceCounts,\n useOutreachSequenceList,\n} from './use-outreach-data'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\nimport { useOutreachComplianceSettings } from './use-outreach-settings'\n\nexport interface OutreachSequencesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n orgMount?: ConsolePluginOrgMount\n org?: Record<string, unknown> | null\n /** The section's own path: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n /**\n * The path below the section: `[]`, `['new']`, `[id]`, `[id, 'enrollments']`\n * or `[id, 'enrollments', enrollmentId]` — one person's page (AGL-3332).\n */\n subpath: readonly string[]\n /** The Mailboxes section: `/[orgSlug]/outreach/mailboxes`. */\n mailboxesPath: string\n /** The Compliance section: `/[orgSlug]/outreach/compliance`. */\n compliancePath: string\n}\n\n/** The new-sequence page's segment below the section. */\nexport const OUTREACH_NEW_SEQUENCE_SEGMENT = 'new'\n\n/**\n * The Sequences section (AGL-2980), and the pages below it: the list, a new\n * sequence, and one sequence's steps and enrollments. Each is a real URL, so\n * a sequence is a link a rep can keep and the back button walks them.\n */\nexport function OutreachSequencesSection(props: OutreachSequencesSectionProps) {\n const { orgId, sectionPath, subpath } = props\n const api = useOutreachApi(orgId)\n const settings = useOutreachComplianceSettings(api, orgId)\n const mailboxes = useOutreachMailboxes(orgId)\n const navigate = useOutreachNavigate()\n\n if (!orgId) return <OutreachLoading label=\"Loading sequences…\" />\n const [first, second, third] = subpath\n if (first === OUTREACH_NEW_SEQUENCE_SEGMENT) {\n return (\n <Stack spacing={2}>\n <OutreachLink href={sectionPath}>Sequences</OutreachLink>\n <Typography variant=\"h5\" component=\"h2\">\n New sequence\n </Typography>\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n sequence={null}\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={(sequence) => navigate(`${sectionPath}/${sequence.id}`)}\n onCancel={() => navigate(sectionPath)}\n />\n </Stack>\n )\n }\n if (first && second === 'enrollments' && third) {\n return (\n <OutreachEnrollmentDetail\n orgId={orgId}\n orgMount={props.orgMount}\n sectionPath={sectionPath}\n sequenceId={first}\n enrollmentId={third}\n mailboxes={mailboxes}\n />\n )\n }\n if (first) {\n return (\n <OutreachSequenceDetail\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sectionPath={sectionPath}\n sequenceId={first}\n tab={\n (second === 'enrollments'\n ? 'enrollments'\n : 'steps') as OutreachSequenceTab\n }\n settings={settings}\n mailboxes={mailboxes}\n mailboxesPath={props.mailboxesPath}\n compliancePath={props.compliancePath}\n />\n )\n }\n return (\n <OutreachSequenceList\n orgId={orgId}\n sectionPath={sectionPath}\n mailboxes={mailboxes.mailboxes}\n />\n )\n}\nOutreachSequencesSection.displayName = 'OutreachSequencesSection'\n\nconst COUNT_COLUMNS: ReadonlyArray<[keyof OutreachSequenceCounts, string]> = [\n ['enrolled', 'Enrolled'],\n ['active', 'Active'],\n ['replied', 'Replied'],\n ['bounced', 'Bounced'],\n ['optedOut', 'Opted out'],\n]\n\n/*\n * What the sequences grid's Filters panel offers (AGL-3317), each field on\n * the list's Firestore query (AGL-3321, `OUTREACH_SEQUENCE_LIST_QUERY`).\n */\nconst SEQUENCE_FILTER_FIELDS = OUTREACH_SEQUENCE_LIST_QUERY.fields\nconst SEQUENCE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n name: 'Name',\n mailbox: 'Mailbox',\n status: 'Status',\n created: 'Created',\n}\n/** `created` is no column of its own: a filter-only column, never shown. */\nconst SEQUENCE_HIDDEN_COLUMNS = hiddenFilterVisibility(SEQUENCE_FILTER_FIELDS, [\n 'name',\n 'mailbox',\n 'status',\n])\nconst SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_SEQUENCE_STATUS_LABELS[status],\n}))\n\n/** How a mailbox reads in the list: the address it sends as. */\nconst mailboxName = (mailbox: OutreachMailbox) => mailbox.sendAs || mailbox.email\n\n/**\n * Every sequence: its mailbox, its status, and how many people it enrolled\n * and where they stand — a table on wider screens, cards on a phone.\n *\n * ## Asked of Firestore, a page at a time\n *\n * The Filters panel and the search box are the grid's; the clauses and the\n * words go onto ONE query over the organization's sequences, newest first,\n * and each page is a page of that query's matches (AGL-3321). A clause the\n * query cannot hold beside the others is not applied, and the notice above\n * the list says which and why — never answered over the sequences that\n * happen to be loaded.\n */\nexport function OutreachSequenceList(props: {\n orgId: string\n sectionPath: string\n mailboxes: readonly OutreachMailbox[]\n}) {\n const { orgId, sectionPath } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const navigate = useOutreachNavigate()\n const gridFilter = useListGridFilter({ selectFields: ['status', 'mailbox'] })\n const sequences = useOutreachSequenceList(orgId, {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n const filterOptions = useMemo(\n () => ({\n status: SEQUENCE_STATUS_OPTIONS,\n mailbox: props.mailboxes.map((mailbox) => ({\n value: mailbox.id,\n label: mailboxName(mailbox),\n })),\n }),\n [props.mailboxes],\n )\n const mailboxLabel = (sequence: OutreachSequence) => {\n const mailbox = props.mailboxes.find(\n (entry) => entry.id === sequence.mailboxId,\n )\n return mailbox\n ? mailboxName(mailbox)\n : sequence.mailboxId\n ? 'Mailbox removed'\n : 'No mailbox yet'\n }\n const pageRows = sequences.rows\n // Counted for the page on screen only: five aggregations a sequence.\n const counts = useOutreachSequenceCounts(\n orgId,\n pageRows.map((sequence) => sequence.id),\n )\n const footer = (\n <ListPagination\n page={sequences.page}\n pageSize={sequences.pageSize}\n rowCount={pageRows.length}\n hasMore={sequences.hasMore}\n onPageChange={sequences.setPage}\n onPageSizeChange={sequences.setPageSize}\n />\n )\n const newHref = `${sectionPath}/${OUTREACH_NEW_SEQUENCE_SEGMENT}`\n const newButton = (\n <Button\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n href={newHref}\n onClick={(event) => {\n event.preventDefault()\n navigate(newHref)\n }}\n >\n New sequence\n </Button>\n )\n const count = (\n sequence: OutreachSequence,\n key: keyof OutreachSequenceCounts,\n ) => (counts[sequence.id] ? String(counts[sequence.id][key]) : '—')\n\n const chips = (\n <ListFilterChips\n fields={SEQUENCE_FILTER_FIELDS}\n headers={SEQUENCE_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n )\n const notices = (\n <ListQueryNotices\n refused={listQueryRefusals(sequences.plan.refused, {\n fields: SEQUENCE_FILTER_FIELDS,\n headers: SEQUENCE_FILTER_HEADERS,\n options: filterOptions,\n })}\n notices={sequences.plan.notices}\n />\n )\n /** Nothing stored at all — not a filter that matched nothing. */\n const none =\n sequences.status === 'ready' &&\n !pageRows.length &&\n !filtering &&\n sequences.page === 0\n let body\n if (sequences.status === 'loading' && !filtering)\n body = <OutreachLoading label=\"Loading sequences…\" />\n else if (sequences.status === 'error' || sequences.status === 'refused') {\n body = (\n <Stack spacing={1}>\n {chips}\n <OutreachLoadProblem status={sequences.status} what=\"sequences\" />\n </Stack>\n )\n } else if (none) {\n body = (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No sequences yet\"\n description=\"A sequence is the emails, and the tasks between them, one person gets from one rep.\"\n action={newButton}\n />\n </Card>\n )\n } else if (narrow) {\n body = (\n <Stack spacing={1}>\n {chips}\n {notices}\n {pageRows.length ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Sequences\"\n >\n {pageRows.map((sequence) => (\n <Card key={sequence.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{\n alignItems: 'center',\n justifyContent: 'space-between',\n }}\n >\n <OutreachLink href={`${sectionPath}/${sequence.id}`}>\n {sequence.name || 'Untitled'}\n </OutreachLink>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {mailboxLabel(sequence)}\n </Typography>\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ flexWrap: 'wrap', rowGap: 0.5 }}\n >\n {COUNT_COLUMNS.map(([key, label]) => (\n <Chip\n key={key}\n size=\"small\"\n variant=\"outlined\"\n label={`${label} ${count(sequence, key)}`}\n />\n ))}\n </Stack>\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {sequences.status === 'loading'\n ? 'Loading sequences…'\n : 'No sequences match these filters'}\n </Typography>\n )}\n {footer}\n </Stack>\n )\n } else {\n const columns: NonNullable<ListTableProps['columns']> = listFilterGridColumns([\n { field: 'name', headerName: 'Name', flex: 1, minWidth: 180 },\n { field: 'mailbox', headerName: 'Mailbox', flex: 1, minWidth: 180 },\n {\n field: 'status',\n headerName: 'Status',\n width: 110,\n renderCell: ({ row }) => (\n <OutreachSequenceStatusChip status={row.status} />\n ),\n },\n ...COUNT_COLUMNS.map(([key, label]) => ({\n field: key,\n headerName: label,\n width: 96,\n align: 'right' as const,\n headerAlign: 'right' as const,\n })),\n ], SEQUENCE_FILTER_FIELDS, filterOptions, SEQUENCE_FILTER_HEADERS)\n body = (\n <Stack spacing={1}>\n {chips}\n {notices}\n <ListTable\n aria-label=\"Sequences\"\n columns={columns}\n initialState={{ columns: { columnVisibilityModel: SEQUENCE_HIDDEN_COLUMNS } }}\n rows={pageRows.map((sequence) => ({\n $id: sequence.id,\n name: sequence.name || 'Untitled',\n mailbox: mailboxLabel(sequence),\n status: sequence.status,\n ...Object.fromEntries(\n COUNT_COLUMNS.map(([key]) => [key, count(sequence, key)]),\n ),\n }))}\n onOpen={(id) => navigate(`${sectionPath}/${id}`)}\n loading={sequences.status === 'loading'}\n /*\n * The panel and the search are the grid's; every clause and the\n * search word are on the list's query (AGL-3321), and the grid\n * neither filters nor sorts the page it is handed: the query's\n * one order, newest first, is the list's.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n disableColumnSorting\n noRowsLabel=\"No sequences match these filters\"\n hideFooter\n />\n {footer}\n </Stack>\n )\n }\n\n return (\n <CardDisplay\n header=\"Sequences\"\n help={pluginDocsHelp('sequences', { anchor: '#sequences' })}\n HeaderProps={{\n action: none || sequences.status === 'loading' ? undefined : newButton,\n }}\n contentGutterX\n contentGutterY\n >\n {body}\n </CardDisplay>\n )\n}\nOutreachSequenceList.displayName = 'OutreachSequenceList'\n\nexport default OutreachSequencesSection\n"],"names":["pluginDocsHelp","mdiPlus","CardDisplay","MdiIcon","EmptyStateComponent","ListFilterChips","ListPagination","ListTable","ListQueryNotices","listQueryRefusals","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","Button","Card","CardContent","Chip","Stack","Typography","useMediaQuery","useTheme","useMemo","OUTREACH_SEQUENCE_STATUSES","OUTREACH_SEQUENCE_LIST_QUERY","OUTREACH_SEQUENCE_STATUS_LABELS","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachEnrollmentDetail","OutreachSequenceDetail","OutreachSequenceEditor","useOutreachApi","useOutreachSequenceCounts","useOutreachSequenceList","useOutreachMailboxes","useOutreachComplianceSettings","OUTREACH_NEW_SEQUENCE_SEGMENT","OutreachSequencesSection","props","orgId","sectionPath","subpath","api","settings","mailboxes","navigate","label","first","second","third","spacing","href","variant","component","orgMount","sequence","mailboxesPath","onSaved","id","onCancel","sequenceId","enrollmentId","org","tab","compliancePath","OutreachSequenceList","displayName","COUNT_COLUMNS","SEQUENCE_FILTER_FIELDS","fields","SEQUENCE_FILTER_HEADERS","name","mailbox","status","created","SEQUENCE_HIDDEN_COLUMNS","SEQUENCE_STATUS_OPTIONS","map","value","mailboxName","sendAs","email","theme","narrow","breakpoints","down","gridFilter","selectFields","sequences","clauses","search","searchWords","filtering","length","some","word","trim","filterOptions","mailboxLabel","find","entry","mailboxId","pageRows","rows","counts","footer","page","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","newHref","newButton","startIcon","path","fontSize","onClick","event","preventDefault","count","key","String","chips","headers","onChange","setClauses","options","notices","refused","plan","none","body","what","description","action","sx","listStyle","p","m","aria-label","direction","alignItems","justifyContent","color","flexWrap","rowGap","size","columns","field","headerName","flex","minWidth","width","renderCell","row","align","headerAlign","initialState","columnVisibilityModel","$id","Object","fromEntries","onOpen","loading","filterMode","filterModel","onFilterModelChange","quickFilter","disableColumnSorting","noRowsLabel","hideFooter","header","help","anchor","HeaderProps","undefined","contentGutterX","contentGutterY"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import type { OutreachComplianceIssue } from '../model/compliance-settings';
2
- import type { OutreachCurateDraftRequest, OutreachCurateDraftResponse, OutreachCurateSaveRequest, OutreachCurateSaveResponse, OutreachDoNotContactDomainRequest, OutreachDoNotContactDomainResponse, OutreachDoNotContactDomainsResponse, OutreachEnrollmentAction, OutreachEnrollmentActionResponse, OutreachEnrollPersonRequest, OutreachEnrollPreviewResponse, OutreachEnrollResponse, OutreachEnrollSource, OutreachLinkDomainRequest, OutreachLinkDomainResponse, OutreachLinkDomainsResponse, OutreachPreviewRequest, OutreachPreviewResponse, OutreachRouteRefusalReason, OutreachSequenceAction, OutreachSequenceSaveResponse, OutreachSequenceStatusResponse, OutreachSettingsResponse, OutreachSettingsSaveRequest, OutreachSettingsSaveResponse, OutreachStepTestRequest, OutreachStepTestResponse } from '../model/outreach-api';
2
+ import type { OutreachCurateDraftRequest, OutreachCurateDraftResponse, OutreachCurateSaveRequest, OutreachCurateSaveResponse, OutreachDoNotContactDomainRequest, OutreachDoNotContactDomainResponse, OutreachDoNotContactDomainsResponse, OutreachEnrollmentAction, OutreachEnrollmentActionResponse, OutreachEnrollmentGatewayResponse, OutreachEnrollPersonRequest, OutreachEnrollPreviewResponse, OutreachEnrollResponse, OutreachEnrollSource, OutreachLinkDomainRequest, OutreachLinkDomainResponse, OutreachLinkDomainsResponse, OutreachPreviewRequest, OutreachPreviewResponse, OutreachRouteRefusalReason, OutreachSequenceAction, OutreachSequenceSaveResponse, OutreachSequenceStatusResponse, OutreachSettingsResponse, OutreachSettingsSaveRequest, OutreachSettingsSaveResponse, OutreachStepTestRequest, OutreachStepTestResponse } from '../model/outreach-api';
3
3
  import type { OutreachSequenceDraft, OutreachSequenceIssue } from '../model/sequence-draft';
4
4
  /**
5
5
  * A route's refusal, carrying its sentence, its stable reason and any
@@ -26,6 +26,8 @@ export interface OutreachApi {
26
26
  previewEnrollment(sequenceId: string, source: OutreachEnrollSource): Promise<OutreachEnrollPreviewResponse>;
27
27
  enroll(sequenceId: string, people: OutreachEnrollPersonRequest[]): Promise<OutreachEnrollResponse>;
28
28
  actOnEnrollment(enrollmentId: string, action: OutreachEnrollmentAction, detail?: string): Promise<OutreachEnrollmentActionResponse>;
29
+ /** The mail gateway in front of one enrolled person's domain (AGL-3332). */
30
+ readEnrollmentGateway(enrollmentId: string): Promise<OutreachEnrollmentGatewayResponse>;
29
31
  previewEmail(input: Omit<OutreachPreviewRequest, 'orgId'>): Promise<OutreachPreviewResponse>;
30
32
  /**
31
33
  * Sends one step of a saved sequence to the member as a test (AGL-3325):
@@ -127,6 +127,12 @@ import { OUTREACH_API_ROUTES } from "../constants/api-routes.js";
127
127
  detail
128
128
  } : {})
129
129
  }),
130
+ readEnrollmentGateway: (enrollmentId)=>call(OUTREACH_API_ROUTES.enrollmentsGateway, {
131
+ method: 'POST',
132
+ body: {
133
+ enrollmentId
134
+ }
135
+ }),
130
136
  previewEmail: (input)=>call(OUTREACH_API_ROUTES.preview, {
131
137
  method: 'POST',
132
138
  body: _extends({}, input)
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-api.ts"],"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 { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useMemo } from 'react'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport type { OutreachComplianceIssue } from '../model/compliance-settings'\nimport type {\n OutreachCurateDraftRequest,\n OutreachCurateDraftResponse,\n OutreachCurateSaveRequest,\n OutreachCurateSaveResponse,\n OutreachDoNotContactDomainRequest,\n OutreachDoNotContactDomainResponse,\n OutreachDoNotContactDomainsResponse,\n OutreachEnrollmentAction,\n OutreachEnrollmentActionResponse,\n OutreachEnrollPersonRequest,\n OutreachEnrollPreviewResponse,\n OutreachEnrollResponse,\n OutreachEnrollSource,\n OutreachLinkDomainRequest,\n OutreachLinkDomainResponse,\n OutreachLinkDomainsResponse,\n OutreachPreviewRequest,\n OutreachPreviewResponse,\n OutreachRouteRefusal,\n OutreachRouteRefusalReason,\n OutreachSequenceAction,\n OutreachSequenceSaveResponse,\n OutreachSequenceStatusResponse,\n OutreachSettingsResponse,\n OutreachSettingsSaveRequest,\n OutreachSettingsSaveResponse,\n OutreachStepTestRequest,\n OutreachStepTestResponse,\n} from '../model/outreach-api'\nimport type {\n OutreachSequenceDraft,\n OutreachSequenceIssue,\n} from '../model/sequence-draft'\n\n/**\n * A route's refusal, carrying its sentence, its stable reason and any\n * field-level issues. `refused` is the one a page shows as \"you can't use\n * this here\" rather than as something that went wrong: no permission, no\n * entitlement, or the release gate answering as if the route did not exist.\n */\nexport class OutreachRouteError extends Error {\n readonly reason: OutreachRouteRefusalReason | 'unreachable' | 'not-found'\n readonly status: number\n readonly issues: ReadonlyArray<\n OutreachComplianceIssue | OutreachSequenceIssue\n >\n\n constructor(\n message: string,\n reason: OutreachRouteError['reason'],\n status: number,\n issues: OutreachRouteError['issues'] = [],\n ) {\n super(message)\n this.name = 'OutreachRouteError'\n this.reason = reason\n this.status = status\n this.issues = issues\n }\n\n get refused(): boolean {\n return (\n this.reason === 'permission' ||\n this.reason === 'entitlement' ||\n this.reason === 'not-org-wide' ||\n this.reason === 'not-a-member' ||\n // The dispatcher answers a route whose plugin is not released to this\n // workspace as though it did not exist.\n this.reason === 'not-found'\n )\n }\n}\n\n/** The routes, as the Outreach pages call them. */\nexport interface OutreachApi {\n readSettings(): Promise<OutreachSettingsResponse>\n saveSettings(\n input: Omit<OutreachSettingsSaveRequest, 'orgId'>,\n ): Promise<OutreachSettingsSaveResponse>\n saveSequence(\n sequenceId: string | null,\n sequence: OutreachSequenceDraft,\n ): Promise<OutreachSequenceSaveResponse>\n setSequenceStatus(\n sequenceId: string,\n action: OutreachSequenceAction,\n ): Promise<OutreachSequenceStatusResponse>\n deleteSequence(sequenceId: string): Promise<{ ok: true }>\n previewEnrollment(\n sequenceId: string,\n source: OutreachEnrollSource,\n ): Promise<OutreachEnrollPreviewResponse>\n enroll(\n sequenceId: string,\n people: OutreachEnrollPersonRequest[],\n ): Promise<OutreachEnrollResponse>\n actOnEnrollment(\n enrollmentId: string,\n action: OutreachEnrollmentAction,\n detail?: string,\n ): Promise<OutreachEnrollmentActionResponse>\n previewEmail(\n input: Omit<OutreachPreviewRequest, 'orgId'>,\n ): Promise<OutreachPreviewResponse>\n /**\n * Sends one step of a saved sequence to the member as a test (AGL-3325):\n * the preview's email, through the sequence's mailbox, `[Test]` in front.\n */\n sendStepTest(\n input: Omit<OutreachStepTestRequest, 'orgId'>,\n ): Promise<OutreachStepTestResponse & { unresolvedFields?: string[] }>\n /**\n * One person's own copies of the sequence's email steps, drafted by the\n * workspace's AI (AGL-3324); nothing is stored until the member confirms.\n */\n curateDrafts(\n input: Omit<OutreachCurateDraftRequest, 'orgId'>,\n ): Promise<OutreachCurateDraftResponse>\n /** Stores a confirmed copy of one step on an enrollment, or clears it with `null`. */\n saveCuratedStep(\n enrollmentId: string,\n stepIndex: number,\n override: OutreachCurateSaveRequest['override'],\n ): Promise<OutreachCurateSaveResponse>\n /** The domains on the do-not-contact list (AGL-3244). */\n readDoNotContactDomains(): Promise<OutreachDoNotContactDomainsResponse>\n /** Puts a domain on the list, or takes one off. */\n changeDoNotContactDomain(\n action: OutreachDoNotContactDomainRequest['action'],\n domain: string,\n detail?: string,\n ): Promise<OutreachDoNotContactDomainResponse>\n /** Each mailbox domain's click-tracking host (AGL-3306). */\n readLinkDomains(): Promise<OutreachLinkDomainsResponse>\n /** Sets a link domain up, checks it, or removes it. */\n changeLinkDomain(\n action: OutreachLinkDomainRequest['action'],\n domain: string,\n ): Promise<OutreachLinkDomainResponse>\n}\n\n/**\n * The Outreach pages' one door to the settings, sequence and enrollment\n * routes (AGL-2980), with the member's session attached and the\n * organization named on every call. A refusal is thrown as an\n * {@link OutreachRouteError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachApi(orgId: string | null): OutreachApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(\n route: string,\n init: { method: 'GET' | 'POST'; body?: Record<string, unknown> },\n ): Promise<T> => {\n if (!orgId)\n throw new OutreachRouteError(\n 'Open an organization first.',\n 'org-required',\n 400,\n )\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n let response: Response\n try {\n response = await authorizedFetch(user, path, {\n method: init.method,\n ...(init.method === 'POST'\n ? {\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...init.body }),\n }\n : {}),\n })\n } catch {\n throw new OutreachRouteError(\n 'Sequences could not be reached. Try again.',\n 'unreachable',\n 0,\n )\n }\n const payload = (await response.json().catch(() => null)) as\n (T & { error?: undefined }) | Partial<OutreachRouteRefusal> | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as Partial<OutreachRouteRefusal>\n throw new OutreachRouteError(\n refusal.error && refusal.reason\n ? refusal.error\n : response.status === 404\n ? \"Sequences isn't available to this workspace yet.\"\n : 'Sequences could not be reached. Try again.',\n refusal.reason ??\n (response.status === 404 ? 'not-found' : 'unreachable'),\n response.status,\n refusal.issues ?? [],\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachApi>(\n () => ({\n readSettings: () => call(OUTREACH_API_ROUTES.settings, { method: 'GET' }),\n saveSettings: (input) =>\n call(OUTREACH_API_ROUTES.settings, {\n method: 'POST',\n body: { ...input },\n }),\n saveSequence: (sequenceId, sequence) =>\n call(OUTREACH_API_ROUTES.sequencesSave, {\n method: 'POST',\n body: { ...(sequenceId ? { sequenceId } : {}), sequence },\n }),\n setSequenceStatus: (sequenceId, action) =>\n call(OUTREACH_API_ROUTES.sequencesStatus, {\n method: 'POST',\n body: { sequenceId, action },\n }),\n deleteSequence: (sequenceId) =>\n call(OUTREACH_API_ROUTES.sequencesDelete, {\n method: 'POST',\n body: { sequenceId },\n }),\n previewEnrollment: (sequenceId, source) =>\n call(OUTREACH_API_ROUTES.enrollPreview, {\n method: 'POST',\n body: { sequenceId, source },\n }),\n enroll: (sequenceId, people) =>\n call(OUTREACH_API_ROUTES.enroll, {\n method: 'POST',\n body: { sequenceId, people },\n }),\n actOnEnrollment: (enrollmentId, action, detail) =>\n call(OUTREACH_API_ROUTES.enrollmentsAction, {\n method: 'POST',\n body: { enrollmentId, action, ...(detail ? { detail } : {}) },\n }),\n previewEmail: (input) =>\n call(OUTREACH_API_ROUTES.preview, {\n method: 'POST',\n body: { ...input },\n }),\n sendStepTest: (input) =>\n call(OUTREACH_API_ROUTES.stepTest, {\n method: 'POST',\n body: { ...input },\n }),\n curateDrafts: (input) =>\n call(OUTREACH_API_ROUTES.curateDraft, {\n method: 'POST',\n body: { ...input },\n }),\n saveCuratedStep: (enrollmentId, stepIndex, override) =>\n call(OUTREACH_API_ROUTES.curateSave, {\n method: 'POST',\n body: { enrollmentId, stepIndex, override },\n }),\n readDoNotContactDomains: () =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, { method: 'GET' }),\n changeDoNotContactDomain: (action, domain, detail) =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, {\n method: 'POST',\n body: { action, domain, ...(detail ? { detail } : {}) },\n }),\n readLinkDomains: () => call(OUTREACH_API_ROUTES.linkDomains, { method: 'GET' }),\n changeLinkDomain: (action, domain) =>\n call(OUTREACH_API_ROUTES.linkDomains, {\n method: 'POST',\n body: { action, domain },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachRouteError","Error","refused","reason","message","status","issues","name","useOutreachApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","readSettings","settings","saveSettings","input","saveSequence","sequenceId","sequence","sequencesSave","setSequenceStatus","action","sequencesStatus","deleteSequence","sequencesDelete","previewEnrollment","source","enrollPreview","enroll","people","actOnEnrollment","enrollmentId","detail","enrollmentsAction","previewEmail","preview","sendStepTest","stepTest","curateDrafts","curateDraft","saveCuratedStep","stepIndex","override","curateSave","readDoNotContactDomains","doNotContactDomains","changeDoNotContactDomain","domain","readLinkDomains","linkDomains","changeLinkDomain"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-api.ts"],"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 { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useMemo } from 'react'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport type { OutreachComplianceIssue } from '../model/compliance-settings'\nimport type {\n OutreachCurateDraftRequest,\n OutreachCurateDraftResponse,\n OutreachCurateSaveRequest,\n OutreachCurateSaveResponse,\n OutreachDoNotContactDomainRequest,\n OutreachDoNotContactDomainResponse,\n OutreachDoNotContactDomainsResponse,\n OutreachEnrollmentAction,\n OutreachEnrollmentActionResponse,\n OutreachEnrollmentGatewayResponse,\n OutreachEnrollPersonRequest,\n OutreachEnrollPreviewResponse,\n OutreachEnrollResponse,\n OutreachEnrollSource,\n OutreachLinkDomainRequest,\n OutreachLinkDomainResponse,\n OutreachLinkDomainsResponse,\n OutreachPreviewRequest,\n OutreachPreviewResponse,\n OutreachRouteRefusal,\n OutreachRouteRefusalReason,\n OutreachSequenceAction,\n OutreachSequenceSaveResponse,\n OutreachSequenceStatusResponse,\n OutreachSettingsResponse,\n OutreachSettingsSaveRequest,\n OutreachSettingsSaveResponse,\n OutreachStepTestRequest,\n OutreachStepTestResponse,\n} from '../model/outreach-api'\nimport type {\n OutreachSequenceDraft,\n OutreachSequenceIssue,\n} from '../model/sequence-draft'\n\n/**\n * A route's refusal, carrying its sentence, its stable reason and any\n * field-level issues. `refused` is the one a page shows as \"you can't use\n * this here\" rather than as something that went wrong: no permission, no\n * entitlement, or the release gate answering as if the route did not exist.\n */\nexport class OutreachRouteError extends Error {\n readonly reason: OutreachRouteRefusalReason | 'unreachable' | 'not-found'\n readonly status: number\n readonly issues: ReadonlyArray<\n OutreachComplianceIssue | OutreachSequenceIssue\n >\n\n constructor(\n message: string,\n reason: OutreachRouteError['reason'],\n status: number,\n issues: OutreachRouteError['issues'] = [],\n ) {\n super(message)\n this.name = 'OutreachRouteError'\n this.reason = reason\n this.status = status\n this.issues = issues\n }\n\n get refused(): boolean {\n return (\n this.reason === 'permission' ||\n this.reason === 'entitlement' ||\n this.reason === 'not-org-wide' ||\n this.reason === 'not-a-member' ||\n // The dispatcher answers a route whose plugin is not released to this\n // workspace as though it did not exist.\n this.reason === 'not-found'\n )\n }\n}\n\n/** The routes, as the Outreach pages call them. */\nexport interface OutreachApi {\n readSettings(): Promise<OutreachSettingsResponse>\n saveSettings(\n input: Omit<OutreachSettingsSaveRequest, 'orgId'>,\n ): Promise<OutreachSettingsSaveResponse>\n saveSequence(\n sequenceId: string | null,\n sequence: OutreachSequenceDraft,\n ): Promise<OutreachSequenceSaveResponse>\n setSequenceStatus(\n sequenceId: string,\n action: OutreachSequenceAction,\n ): Promise<OutreachSequenceStatusResponse>\n deleteSequence(sequenceId: string): Promise<{ ok: true }>\n previewEnrollment(\n sequenceId: string,\n source: OutreachEnrollSource,\n ): Promise<OutreachEnrollPreviewResponse>\n enroll(\n sequenceId: string,\n people: OutreachEnrollPersonRequest[],\n ): Promise<OutreachEnrollResponse>\n actOnEnrollment(\n enrollmentId: string,\n action: OutreachEnrollmentAction,\n detail?: string,\n ): Promise<OutreachEnrollmentActionResponse>\n /** The mail gateway in front of one enrolled person's domain (AGL-3332). */\n readEnrollmentGateway(enrollmentId: string): Promise<OutreachEnrollmentGatewayResponse>\n previewEmail(\n input: Omit<OutreachPreviewRequest, 'orgId'>,\n ): Promise<OutreachPreviewResponse>\n /**\n * Sends one step of a saved sequence to the member as a test (AGL-3325):\n * the preview's email, through the sequence's mailbox, `[Test]` in front.\n */\n sendStepTest(\n input: Omit<OutreachStepTestRequest, 'orgId'>,\n ): Promise<OutreachStepTestResponse & { unresolvedFields?: string[] }>\n /**\n * One person's own copies of the sequence's email steps, drafted by the\n * workspace's AI (AGL-3324); nothing is stored until the member confirms.\n */\n curateDrafts(\n input: Omit<OutreachCurateDraftRequest, 'orgId'>,\n ): Promise<OutreachCurateDraftResponse>\n /** Stores a confirmed copy of one step on an enrollment, or clears it with `null`. */\n saveCuratedStep(\n enrollmentId: string,\n stepIndex: number,\n override: OutreachCurateSaveRequest['override'],\n ): Promise<OutreachCurateSaveResponse>\n /** The domains on the do-not-contact list (AGL-3244). */\n readDoNotContactDomains(): Promise<OutreachDoNotContactDomainsResponse>\n /** Puts a domain on the list, or takes one off. */\n changeDoNotContactDomain(\n action: OutreachDoNotContactDomainRequest['action'],\n domain: string,\n detail?: string,\n ): Promise<OutreachDoNotContactDomainResponse>\n /** Each mailbox domain's click-tracking host (AGL-3306). */\n readLinkDomains(): Promise<OutreachLinkDomainsResponse>\n /** Sets a link domain up, checks it, or removes it. */\n changeLinkDomain(\n action: OutreachLinkDomainRequest['action'],\n domain: string,\n ): Promise<OutreachLinkDomainResponse>\n}\n\n/**\n * The Outreach pages' one door to the settings, sequence and enrollment\n * routes (AGL-2980), with the member's session attached and the\n * organization named on every call. A refusal is thrown as an\n * {@link OutreachRouteError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachApi(orgId: string | null): OutreachApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(\n route: string,\n init: { method: 'GET' | 'POST'; body?: Record<string, unknown> },\n ): Promise<T> => {\n if (!orgId)\n throw new OutreachRouteError(\n 'Open an organization first.',\n 'org-required',\n 400,\n )\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n let response: Response\n try {\n response = await authorizedFetch(user, path, {\n method: init.method,\n ...(init.method === 'POST'\n ? {\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...init.body }),\n }\n : {}),\n })\n } catch {\n throw new OutreachRouteError(\n 'Sequences could not be reached. Try again.',\n 'unreachable',\n 0,\n )\n }\n const payload = (await response.json().catch(() => null)) as\n (T & { error?: undefined }) | Partial<OutreachRouteRefusal> | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as Partial<OutreachRouteRefusal>\n throw new OutreachRouteError(\n refusal.error && refusal.reason\n ? refusal.error\n : response.status === 404\n ? \"Sequences isn't available to this workspace yet.\"\n : 'Sequences could not be reached. Try again.',\n refusal.reason ??\n (response.status === 404 ? 'not-found' : 'unreachable'),\n response.status,\n refusal.issues ?? [],\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachApi>(\n () => ({\n readSettings: () => call(OUTREACH_API_ROUTES.settings, { method: 'GET' }),\n saveSettings: (input) =>\n call(OUTREACH_API_ROUTES.settings, {\n method: 'POST',\n body: { ...input },\n }),\n saveSequence: (sequenceId, sequence) =>\n call(OUTREACH_API_ROUTES.sequencesSave, {\n method: 'POST',\n body: { ...(sequenceId ? { sequenceId } : {}), sequence },\n }),\n setSequenceStatus: (sequenceId, action) =>\n call(OUTREACH_API_ROUTES.sequencesStatus, {\n method: 'POST',\n body: { sequenceId, action },\n }),\n deleteSequence: (sequenceId) =>\n call(OUTREACH_API_ROUTES.sequencesDelete, {\n method: 'POST',\n body: { sequenceId },\n }),\n previewEnrollment: (sequenceId, source) =>\n call(OUTREACH_API_ROUTES.enrollPreview, {\n method: 'POST',\n body: { sequenceId, source },\n }),\n enroll: (sequenceId, people) =>\n call(OUTREACH_API_ROUTES.enroll, {\n method: 'POST',\n body: { sequenceId, people },\n }),\n actOnEnrollment: (enrollmentId, action, detail) =>\n call(OUTREACH_API_ROUTES.enrollmentsAction, {\n method: 'POST',\n body: { enrollmentId, action, ...(detail ? { detail } : {}) },\n }),\n readEnrollmentGateway: (enrollmentId) =>\n call(OUTREACH_API_ROUTES.enrollmentsGateway, {\n method: 'POST',\n body: { enrollmentId },\n }),\n previewEmail: (input) =>\n call(OUTREACH_API_ROUTES.preview, {\n method: 'POST',\n body: { ...input },\n }),\n sendStepTest: (input) =>\n call(OUTREACH_API_ROUTES.stepTest, {\n method: 'POST',\n body: { ...input },\n }),\n curateDrafts: (input) =>\n call(OUTREACH_API_ROUTES.curateDraft, {\n method: 'POST',\n body: { ...input },\n }),\n saveCuratedStep: (enrollmentId, stepIndex, override) =>\n call(OUTREACH_API_ROUTES.curateSave, {\n method: 'POST',\n body: { enrollmentId, stepIndex, override },\n }),\n readDoNotContactDomains: () =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, { method: 'GET' }),\n changeDoNotContactDomain: (action, domain, detail) =>\n call(OUTREACH_API_ROUTES.doNotContactDomains, {\n method: 'POST',\n body: { action, domain, ...(detail ? { detail } : {}) },\n }),\n readLinkDomains: () => call(OUTREACH_API_ROUTES.linkDomains, { method: 'GET' }),\n changeLinkDomain: (action, domain) =>\n call(OUTREACH_API_ROUTES.linkDomains, {\n method: 'POST',\n body: { action, domain },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachRouteError","Error","refused","reason","message","status","issues","name","useOutreachApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","readSettings","settings","saveSettings","input","saveSequence","sequenceId","sequence","sequencesSave","setSequenceStatus","action","sequencesStatus","deleteSequence","sequencesDelete","previewEnrollment","source","enrollPreview","enroll","people","actOnEnrollment","enrollmentId","detail","enrollmentsAction","readEnrollmentGateway","enrollmentsGateway","previewEmail","preview","sendStepTest","stepTest","curateDrafts","curateDraft","saveCuratedStep","stepIndex","override","curateSave","readDoNotContactDomains","doNotContactDomains","changeDoNotContactDomain","domain","readLinkDomains","linkDomains","changeLinkDomain"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  import type { CampaignLinkRollup } from '@aglyn/shared-ui-email-campaigns/model';
2
+ import type { ListQueryPlan, ListQueryRequest } from '@aglyn/shared-ui-jsx/const/list-query-plan';
2
3
  import { type OutreachDoNotContactDomainEntry, type OutreachEnrollment, type OutreachSequence } from '../model/outreach.types';
3
4
  /**
4
5
  * The Outreach documents the console reads LIVE (AGL-2980): sequences,
@@ -18,15 +19,27 @@ export interface OutreachLoad<T> {
18
19
  status: OutreachLoadStatus;
19
20
  data: T;
20
21
  }
21
- /** The most sequences the list reads. */
22
- export declare const OUTREACH_SEQUENCES_LIMIT = 200;
23
- /** The sequences read, and whether the organization holds older ones. */
24
- export interface OutreachSequencesLoad extends OutreachLoad<OutreachSequence[]> {
25
- /** True when there are sequences older than the `OUTREACH_SEQUENCES_LIMIT` read. */
26
- truncated?: boolean;
22
+ /**
23
+ * One page of a list whose every filter and search word is on its Firestore
24
+ * query (AGL-3321): the rows of the page on screen, whether another page
25
+ * exists, the pager, and the plan — what the query holds and what it could
26
+ * not take, which the list says above itself.
27
+ */
28
+ export interface OutreachListPage<T> {
29
+ status: OutreachLoadStatus;
30
+ rows: T[];
31
+ hasMore: boolean;
32
+ page: number;
33
+ setPage(page: number): void;
34
+ pageSize: number;
35
+ setPageSize(pageSize: number): void;
36
+ plan: ListQueryPlan;
27
37
  }
28
- /** The organization's sequences, newest first. */
29
- export declare function useOutreachSequences(orgId: string | null): OutreachSequencesLoad;
38
+ /**
39
+ * The organization's sequences, newest first, one page at a time — each
40
+ * clause and the search on the query (`OUTREACH_SEQUENCE_LIST_QUERY`).
41
+ */
42
+ export declare function useOutreachSequenceList(orgId: string | null, request: ListQueryRequest): OutreachListPage<OutreachSequence>;
30
43
  /** One sequence, or `null` once it is known not to exist. */
31
44
  export declare function useOutreachSequence(orgId: string | null, sequenceId: string | null): OutreachLoad<OutreachSequence | null>;
32
45
  /**
@@ -41,26 +54,18 @@ export type OutreachSequenceLinksLoad = OutreachLoad<CampaignLinkRollup | null>;
41
54
  export declare function useOutreachSequenceLinks(orgId: string | null, sequenceId: string | null): OutreachSequenceLinksLoad;
42
55
  /**
43
56
  * The domains on the organization's do-not-contact list (AGL-3244),
44
- * alphabetically, live: an add or a remove through the route shows up here
45
- * without a reload, and so does a domain the sending runtime files after a
46
- * gateway block.
57
+ * alphabetically — the document id is the domain — one page at a time, live:
58
+ * an add or a remove through the route shows up without a reload, and so
59
+ * does a domain the sending runtime files after a gateway block. Each clause
60
+ * and the search are on the query (`OUTREACH_DO_NOT_CONTACT_DOMAIN_LIST_QUERY`).
47
61
  */
48
- export declare function useOutreachDoNotContactDomains(orgId: string | null): OutreachLoad<OutreachDoNotContactDomainEntry[]>;
49
- /** How many enrollments one page of the table reads; "Show more" reads another. */
50
- export declare const OUTREACH_ENROLLMENTS_PAGE = 200;
51
- export interface OutreachEnrollmentsLoad extends OutreachLoad<OutreachEnrollment[]> {
52
- /** Whether a further page may exist. */
53
- hasMore: boolean;
54
- showMore(): void;
55
- }
62
+ export declare function useOutreachDoNotContactDomainList(orgId: string | null, request: ListQueryRequest): OutreachListPage<OutreachDoNotContactDomainEntry>;
56
63
  /**
57
- * A sequence's enrollments, the most recently enrolled first.
58
- *
59
- * Paged by document id — an equality on `sequenceId` in the id's own order
60
- * needs no composite index — and sorted by enrollment time once read, so a
61
- * table of up to a page reads in the order people were enrolled.
64
+ * A sequence's enrollments, the most recently enrolled first, one page at a
65
+ * time — the sequence is the query's base (`sequenceId ==`), and each clause
66
+ * and the search are on it beside that (`OUTREACH_ENROLLMENT_LIST_QUERY`).
62
67
  */
63
- export declare function useOutreachEnrollments(orgId: string | null, sequenceId: string | null): OutreachEnrollmentsLoad;
68
+ export declare function useOutreachEnrollmentList(orgId: string | null, sequenceId: string | null, request: Omit<ListQueryRequest, 'base'>): OutreachListPage<OutreachEnrollment>;
64
69
  /** The counts the sequence list shows beside each sequence. */
65
70
  export interface OutreachSequenceCounts {
66
71
  enrolled: number;
@@ -16,10 +16,14 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { useFirestore } from "@aglyn/tenant-feature-instance";
19
- import { collection, doc, documentId, getCountFromServer, limit, onSnapshot, orderBy, query, where } from "firebase/firestore";
19
+ import { useListQuery } from "@aglyn/tenant-feature-instance/hooks/use-list-query";
20
+ import { collection, doc, getCountFromServer, onSnapshot, query, where } from "firebase/firestore";
20
21
  import { useEffect, useMemo, useState } from "react";
21
22
  import { OUTREACH_COLLECTIONS, OUTREACH_LINK_ROLLUP_PATH } from "../model/outreach.types.js";
22
23
  import { readStoredOutreachEnrollment, readStoredOutreachSequence } from "../model/stored-records.js";
24
+ import { OUTREACH_DO_NOT_CONTACT_DOMAIN_LIST_QUERY } from "../model/do-not-contact-domain-list-query.js";
25
+ import { OUTREACH_ENROLLMENT_LIST_QUERY, outreachEnrollmentListBase } from "../model/enrollment-list-query.js";
26
+ import { OUTREACH_SEQUENCE_LIST_QUERY } from "../model/sequence-list-query.js";
23
27
  const denied = (error)=>(error == null ? void 0 : error.code) === 'permission-denied';
24
28
  function failed(data, error, what) {
25
29
  if (denied(error)) return {
@@ -32,36 +36,62 @@ function failed(data, error, what) {
32
36
  data
33
37
  };
34
38
  }
35
- /** The most sequences the list reads. */ export const OUTREACH_SEQUENCES_LIMIT = 200;
36
- /** The organization's sequences, newest first. */ export function useOutreachSequences(orgId) {
37
- const firestore = useFirestore();
38
- const [result, setResult] = useState({
39
- status: 'loading',
40
- data: []
39
+ /**
40
+ * A list read through `useListQuery`, in this module's four states and with
41
+ * each document read through its stored-record reader. A document the reader
42
+ * cannot make sense of is left out, as every Outreach read leaves it out.
43
+ */ function useOutreachListQuery(options) {
44
+ const listed = useListQuery({
45
+ collection: options.collection,
46
+ declaration: options.declaration,
47
+ request: options.request,
48
+ deps: options.deps,
49
+ idField: '$id'
41
50
  });
51
+ const { read, what } = options;
52
+ const rows = useMemo(()=>listed.rows.map((row)=>read(String(row.$id), row)).filter((row)=>row !== null), [
53
+ listed.rows,
54
+ read
55
+ ]);
56
+ const refused = listed.serverDenied || denied(listed.error);
42
57
  useEffect(()=>{
43
- setResult({
44
- status: 'loading',
45
- data: []
46
- });
47
- if (!orgId) return undefined;
48
- return onSnapshot(query(collection(firestore, 'orgs', orgId, OUTREACH_COLLECTIONS.sequences), // Ordered, so the cap keeps the newest rather than an arbitrary set;
49
- // every sequence carries `createdAtMs`, stamped by the save route.
50
- orderBy('createdAtMs', 'desc'), // One past the cap: the extra document says there are older
51
- // sequences, and is never listed.
52
- limit(OUTREACH_SEQUENCES_LIMIT + 1)), (snapshot)=>{
53
- const sequences = snapshot.docs.slice(0, OUTREACH_SEQUENCES_LIMIT).map((entry)=>readStoredOutreachSequence(entry.id, entry.data())).filter((sequence)=>sequence !== null).sort((a, b)=>b.createdAtMs - a.createdAtMs);
54
- setResult({
55
- status: 'ready',
56
- data: sequences,
57
- truncated: snapshot.docs.length > OUTREACH_SEQUENCES_LIMIT
58
- });
59
- }, (error)=>setResult(failed([], error, 'sequences')));
58
+ if (listed.status === 'error' && !refused) {
59
+ console.error(`[outreach] ${what} could not be read`, listed.error);
60
+ }
60
61
  }, [
61
- firestore,
62
- orgId
62
+ listed.status,
63
+ listed.error,
64
+ refused,
65
+ what
63
66
  ]);
64
- return result;
67
+ return {
68
+ status: listed.status === 'loading' ? 'loading' : listed.status === 'error' ? refused ? 'refused' : 'error' : 'ready',
69
+ rows,
70
+ hasMore: listed.hasMore,
71
+ page: listed.page,
72
+ setPage: listed.setPage,
73
+ pageSize: listed.pageSize,
74
+ setPageSize: listed.setPageSize,
75
+ plan: listed.plan
76
+ };
77
+ }
78
+ const readSequence = (id, data)=>readStoredOutreachSequence(id, data);
79
+ /**
80
+ * The organization's sequences, newest first, one page at a time — each
81
+ * clause and the search on the query (`OUTREACH_SEQUENCE_LIST_QUERY`).
82
+ */ export function useOutreachSequenceList(orgId, request) {
83
+ const firestore = useFirestore();
84
+ return useOutreachListQuery({
85
+ collection: orgId ? collection(firestore, 'orgs', orgId, OUTREACH_COLLECTIONS.sequences) : null,
86
+ declaration: OUTREACH_SEQUENCE_LIST_QUERY,
87
+ request,
88
+ deps: [
89
+ firestore,
90
+ orgId
91
+ ],
92
+ read: readSequence,
93
+ what: 'sequences'
94
+ });
65
95
  }
66
96
  /** One sequence, or `null` once it is known not to exist. */ export function useOutreachSequence(orgId, sequenceId) {
67
97
  const firestore = useFirestore();
@@ -109,91 +139,50 @@ export function useOutreachSequenceLinks(orgId, sequenceId) {
109
139
  ]);
110
140
  return result;
111
141
  }
142
+ const readDomain = (id, data)=>_extends({}, data, {
143
+ domain: id
144
+ });
112
145
  /**
113
146
  * The domains on the organization's do-not-contact list (AGL-3244),
114
- * alphabetically, live: an add or a remove through the route shows up here
115
- * without a reload, and so does a domain the sending runtime files after a
116
- * gateway block.
117
- */ export function useOutreachDoNotContactDomains(orgId) {
147
+ * alphabetically — the document id is the domain — one page at a time, live:
148
+ * an add or a remove through the route shows up without a reload, and so
149
+ * does a domain the sending runtime files after a gateway block. Each clause
150
+ * and the search are on the query (`OUTREACH_DO_NOT_CONTACT_DOMAIN_LIST_QUERY`).
151
+ */ export function useOutreachDoNotContactDomainList(orgId, request) {
118
152
  const firestore = useFirestore();
119
- const [result, setResult] = useState({
120
- status: 'loading',
121
- data: []
153
+ return useOutreachListQuery({
154
+ collection: orgId ? collection(firestore, 'orgs', orgId, OUTREACH_COLLECTIONS.doNotContactDomains) : null,
155
+ declaration: OUTREACH_DO_NOT_CONTACT_DOMAIN_LIST_QUERY,
156
+ request,
157
+ deps: [
158
+ firestore,
159
+ orgId
160
+ ],
161
+ read: readDomain,
162
+ what: 'the do-not-contact domains'
122
163
  });
123
- useEffect(()=>{
124
- setResult({
125
- status: 'loading',
126
- data: []
127
- });
128
- if (!orgId) return undefined;
129
- return onSnapshot(collection(firestore, 'orgs', orgId, OUTREACH_COLLECTIONS.doNotContactDomains), (snapshot)=>{
130
- const domains = snapshot.docs.map((entry)=>_extends({}, entry.data(), {
131
- domain: entry.id
132
- })).sort((a, b)=>a.domain.localeCompare(b.domain));
133
- setResult({
134
- status: 'ready',
135
- data: domains
136
- });
137
- }, (error)=>setResult(failed([], error, 'the do-not-contact domains')));
138
- }, [
139
- firestore,
140
- orgId
141
- ]);
142
- return result;
143
164
  }
144
- /** How many enrollments one page of the table reads; "Show more" reads another. */ export const OUTREACH_ENROLLMENTS_PAGE = 200;
165
+ const readEnrollment = (id, data)=>readStoredOutreachEnrollment(id, data);
145
166
  /**
146
- * A sequence's enrollments, the most recently enrolled first.
147
- *
148
- * Paged by document id — an equality on `sequenceId` in the id's own order
149
- * needs no composite index — and sorted by enrollment time once read, so a
150
- * table of up to a page reads in the order people were enrolled.
151
- */ export function useOutreachEnrollments(orgId, sequenceId) {
167
+ * A sequence's enrollments, the most recently enrolled first, one page at a
168
+ * time — the sequence is the query's base (`sequenceId ==`), and each clause
169
+ * and the search are on it beside that (`OUTREACH_ENROLLMENT_LIST_QUERY`).
170
+ */ export function useOutreachEnrollmentList(orgId, sequenceId, request) {
152
171
  const firestore = useFirestore();
153
- const [pages, setPages] = useState(1);
154
- const [result, setResult] = useState({
155
- status: 'loading',
156
- data: [],
157
- size: 0
172
+ return useOutreachListQuery({
173
+ collection: orgId && sequenceId ? collection(firestore, 'orgs', orgId, OUTREACH_COLLECTIONS.enrollments) : null,
174
+ declaration: OUTREACH_ENROLLMENT_LIST_QUERY,
175
+ request: _extends({}, request, {
176
+ base: sequenceId ? outreachEnrollmentListBase(sequenceId) : []
177
+ }),
178
+ deps: [
179
+ firestore,
180
+ orgId,
181
+ sequenceId
182
+ ],
183
+ read: readEnrollment,
184
+ what: 'the enrollments'
158
185
  });
159
- useEffect(()=>setPages(1), [
160
- orgId,
161
- sequenceId
162
- ]);
163
- useEffect(()=>{
164
- if (!orgId || !sequenceId) {
165
- setResult({
166
- status: 'loading',
167
- data: [],
168
- size: 0
169
- });
170
- return undefined;
171
- }
172
- return onSnapshot(query(collection(firestore, 'orgs', orgId, OUTREACH_COLLECTIONS.enrollments), where('sequenceId', '==', sequenceId), orderBy(documentId()), limit(pages * OUTREACH_ENROLLMENTS_PAGE)), (snapshot)=>{
173
- const enrollments = snapshot.docs.map((entry)=>readStoredOutreachEnrollment(entry.id, entry.data())).filter((enrollment)=>enrollment !== null).sort((a, b)=>b.createdAtMs - a.createdAtMs);
174
- setResult({
175
- status: 'ready',
176
- data: enrollments,
177
- size: snapshot.size
178
- });
179
- }, (error)=>setResult(_extends({}, failed([], error, 'the enrollments'), {
180
- size: 0
181
- })));
182
- }, [
183
- firestore,
184
- orgId,
185
- sequenceId,
186
- pages
187
- ]);
188
- return useMemo(()=>({
189
- status: result.status,
190
- data: result.data,
191
- hasMore: result.size >= pages * OUTREACH_ENROLLMENTS_PAGE,
192
- showMore: ()=>setPages((count)=>count + 1)
193
- }), [
194
- result,
195
- pages
196
- ]);
197
186
  }
198
187
  const COUNTED = [
199
188
  [