@aglyn/plugins-outreach 1.0.0-beta.231 → 1.0.0-beta.233

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 (27) hide show
  1. package/package.json +14 -14
  2. package/src/lib/components/curated-step-editor.js +1 -0
  3. package/src/lib/components/curated-step-editor.js.map +1 -1
  4. package/src/lib/components/do-not-contact-domains.js +23 -8
  5. package/src/lib/components/do-not-contact-domains.js.map +1 -1
  6. package/src/lib/components/enroll-dialog.js +6 -0
  7. package/src/lib/components/enroll-dialog.js.map +1 -1
  8. package/src/lib/components/enrollment-detail.js +3 -0
  9. package/src/lib/components/enrollment-detail.js.map +1 -1
  10. package/src/lib/components/enrollments-table.js +94 -48
  11. package/src/lib/components/enrollments-table.js.map +1 -1
  12. package/src/lib/components/link-domains-card.js +2 -0
  13. package/src/lib/components/link-domains-card.js.map +1 -1
  14. package/src/lib/components/mailboxes-section.js +1 -0
  15. package/src/lib/components/mailboxes-section.js.map +1 -1
  16. package/src/lib/components/sequence-detail.js +1 -0
  17. package/src/lib/components/sequence-detail.js.map +1 -1
  18. package/src/lib/components/sequence-report-card.js +1 -0
  19. package/src/lib/components/sequence-report-card.js.map +1 -1
  20. package/src/lib/components/sequences-section.js +47 -15
  21. package/src/lib/components/sequences-section.js.map +1 -1
  22. package/src/lib/model/do-not-contact-domain-list-query.js +60 -2
  23. package/src/lib/model/do-not-contact-domain-list-query.js.map +1 -1
  24. package/src/lib/model/enrollment-list-query.js +55 -2
  25. package/src/lib/model/enrollment-list-query.js.map +1 -1
  26. package/src/lib/model/sequence-list-query.js +36 -2
  27. package/src/lib/model/sequence-list-query.js.map +1 -1
@@ -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 ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.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 { hiddenFilterVisibility, type ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport {\n listFilterGridColumns,\n type ListFilterClause,\n type ListFilterOption,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport {\n Card,\n CardContent,\n Chip,\n Link,\n Stack,\n Tooltip,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { type MouseEvent, type ReactNode, useMemo } from 'react'\nimport {\n outreachClickCountLabel,\n outreachClickSummary,\n outreachMachineOpenLabel,\n outreachOpenCountLabel,\n outreachOpenSummary,\n type OutreachClickSummary,\n type OutreachOpenSummary,\n} from '../model/enrollment-engagement'\nimport {\n OUTREACH_ENROLLMENT_CLICK_FIELDS,\n OUTREACH_ENROLLMENT_LIST_QUERY,\n} from '../model/enrollment-list-query'\nimport {\n OUTREACH_ENROLLMENT_STATUSES,\n OUTREACH_STOP_REASONS,\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport { outreachNextSendState, type OutreachNextSendState } from '../engine/next-send'\nimport { useOutreachEnrollmentActions } from './enrollment-actions'\nimport { OUTREACH_CLICKED_FILTER_VALUE, OUTREACH_UNCLICKED_FILTER_VALUE } from './enrollment-filter-params'\nimport {\n OutreachNextSend,\n outreachNextSendDetail,\n outreachNextSendLabel,\n useOutreachNowMs,\n} from './next-send'\nimport {\n formatOutreachTime,\n OUTREACH_ENROLLMENT_STATUS_LABELS,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport { useOutreachEnrollmentList } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n /** The organization, whose enrollments the table asks for. */\n orgId: string\n /** The sequence whose people these are: every page is narrowed to it. */\n sequenceId: string\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 /**\n * Whether this sequence counts opens, or once did (AGL-3488). The Opens\n * column is shown only then, for the reason the Clicks column is: a\n * column of dashes where nothing was measured reads as \"nobody opened\".\n */\n countOpens?: boolean\n /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */\n timeZone: string | null\n /**\n * The sequence's mailbox, whose pacing a due enrollment waits on\n * (AGL-3366): `undefined` while the mailboxes load, `null` when it has none.\n */\n mailbox?: OutreachMailbox | null\n /** The sequence, whose status and window a due enrollment waits on too. */\n sequence?: Pick<OutreachSequence, 'status' | 'steps' | 'settings'> | null\n api: OutreachApi\n /** Shown in the empty state when the sequence takes people. */\n enrollAction?: ReactNode\n /**\n * Opens one person's page (AGL-3332): a row's click, Enter on a focused\n * row, or the name on a phone's card. The row's menu never opens it.\n */\n onOpen?: (enrollment: OutreachEnrollment) => void\n /** The person's page, for the name on a phone's card, which is a real link. */\n hrefFor?: (enrollment: OutreachEnrollment) => string\n /**\n * The clauses the table is narrowed by, when the page holds them — so the\n * Results card's \"Clicked\" and \"Links followed\" can set one (AGL-3332).\n * Omitted, the table holds its own.\n */\n clauses?: readonly ListFilterClause[]\n onClausesChange?: (clauses: ListFilterClause[]) => void\n /** Destinations the sequence's links went to, for the \"Link followed\" filter's choices. */\n linkOptions?: readonly ListFilterOption[]\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/** Whether the step the enrollment is on goes out as this person's own copy (AGL-3324). */\nexport function outreachStepIsCurated(\n enrollment: Pick<OutreachEnrollment, 'stepIndex' | 'stepOverrides'>,\n): boolean {\n return Boolean(enrollment.stepOverrides?.[String(enrollment.stepIndex)])\n}\n\n/*\n * What the enrollments grid's Filters panel offers (AGL-3317), each field on\n * the table's Firestore query (AGL-3321, `OUTREACH_ENROLLMENT_LIST_QUERY`).\n * Status, whether the person is a lead and the stop reason are picked; the\n * address is typed whole. `target` and `email` are no column of their own,\n * so they are hidden columns the panel can still reach; the search box\n * reads the person's name and address.\n *\n * A sequence that counts clicks adds two more (AGL-3332), on the same query:\n * whether the person clicked, and a destination they followed — matched\n * whole against the destinations stored on the enrollment, never by a piece\n * of its address, so \"followed /pricing\" does not also list the people who\n * followed \"/pricing/enterprise\".\n */\nconst BASE_FILTER_FIELDS = OUTREACH_ENROLLMENT_LIST_QUERY.fields.filter(\n (field) => !(OUTREACH_ENROLLMENT_CLICK_FIELDS as readonly string[]).includes(field.column),\n)\nconst ENROLLMENT_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n target: 'Enrolled as',\n stopReason: 'Stop reason',\n email: 'Email',\n enrolled: 'Enrolled',\n clicked: 'Clicked',\n link: 'Link followed',\n}\nconst BASE_FILTER_OPTIONS: Record<string, readonly ListFilterOption[]> = {\n status: OUTREACH_ENROLLMENT_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_ENROLLMENT_STATUS_LABELS[status],\n })),\n target: [\n { value: 'contact', label: 'Contact' },\n { value: 'lead', label: 'Lead' },\n ],\n stopReason: OUTREACH_STOP_REASONS.map((reason) => ({\n value: reason,\n label: OUTREACH_STOP_REASON_LABELS[reason],\n })),\n}\nconst CLICKED_OPTIONS: readonly ListFilterOption[] = [\n { value: OUTREACH_CLICKED_FILTER_VALUE, label: 'Yes' },\n { value: OUTREACH_UNCLICKED_FILTER_VALUE, label: 'No' },\n]\n/** Columns a reader can show from the column chooser, off until they do (AGL-3332). */\nconst CLICK_DETAIL_COLUMNS = ['linksFollowed', 'lastClick', 'scannerClicks'] as const\n/** The opens column a reader can show from the column chooser, off until they do. */\nconst OPEN_DETAIL_COLUMNS = ['lastOpen'] as const\n\n/** The table's grammar, with the click fields when the sequence counts clicks. */\nfunction filterFieldsFor(trackClicks: boolean): readonly ListFilterField[] {\n return trackClicks ? OUTREACH_ENROLLMENT_LIST_QUERY.fields : BASE_FILTER_FIELDS\n}\n\n/** What the grid's cells and its export read off one enrollment. */\nfunction filterRow(\n enrollment: OutreachEnrollment,\n steps: readonly OutreachSequenceStep[],\n clicks: OutreachClickSummary,\n) {\n return {\n enrollment,\n status: enrollment.status,\n target: enrollment.target === 'lead' ? 'lead' : 'contact',\n contactName: enrollment.contactName,\n email: enrollment.email,\n step: outreachCurrentStepLabel(enrollment, steps),\n stopReason: outreachStopLabel(enrollment),\n clicked: clicks.clicks > 0 ? OUTREACH_CLICKED_FILTER_VALUE : OUTREACH_UNCLICKED_FILTER_VALUE,\n link: clicks.followed,\n }\n}\n\n/** The stop reason: its short label, with every word of it on hover and on the person's page. */\nfunction StopReasonCell(props: { enrollment: OutreachEnrollment }) {\n const { enrollment } = props\n if (!enrollment.stopReason) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n —\n </Typography>\n )\n }\n const label = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n const text = (\n <Typography\n variant=\"body2\"\n sx={{\n whiteSpace: 'normal',\n lineHeight: 1.3,\n ...(enrollment.stopDetail\n ? { textDecoration: 'underline dotted', textUnderlineOffset: 3, cursor: 'help' }\n : {}),\n }}\n >\n {label}\n </Typography>\n )\n return enrollment.stopDetail ? (\n <Tooltip title={outreachStopLabel(enrollment)} describeChild>\n {text}\n </Tooltip>\n ) : (\n text\n )\n}\n\n/**\n * The Opens cell (AGL-3488): the person's opens, and under them the fetches\n * a machine made — counted apart and never in the person's number, as\n * scanner clicks are never in their clicks.\n */\nfunction OpensCell(props: { opens: OutreachOpenSummary }) {\n const { opens } = props\n const machines = outreachMachineOpenLabel(opens)\n return (\n <Stack spacing={0} sx={{ justifyContent: 'center', height: '100%' }}>\n <Typography variant=\"body2\" color={opens.opens ? undefined : 'text.secondary'}>\n {opens.opens ? String(opens.opens) : '—'}\n </Typography>\n {machines ? (\n <Tooltip\n title=\"Fetches of the tracking image by a mail provider’s proxy or a security scanner. They aren’t counted as the person’s opens.\"\n describeChild\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n noWrap\n sx={{ textDecoration: 'underline dotted', textUnderlineOffset: 3, cursor: 'help' }}\n >\n {machines}\n </Typography>\n </Tooltip>\n ) : null}\n </Stack>\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 countOpens: boolean,\n timeZone: string | null,\n): NonNullable<ListTableProps['columns']> {\n return [\n {\n field: 'person',\n headerName: 'Person',\n flex: 1.2,\n minWidth: 190,\n sortable: false,\n // What the export writes for a column that is drawn rather than read.\n valueGetter: (_value: unknown, row: { enrollment: OutreachEnrollment }) =>\n row.enrollment.contactName\n ? `${row.enrollment.contactName} <${row.enrollment.email}>`\n : row.enrollment.email,\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: 110,\n renderCell: ({ row }) => (\n <OutreachEnrollmentStatusChip status={row.status} />\n ),\n },\n {\n field: 'step',\n headerName: 'Current step',\n flex: 1,\n minWidth: 170,\n renderCell: ({ row }) => (\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center', height: '100%', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.step}\n </Typography>\n {outreachStepIsCurated(row.enrollment) ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"success\" label=\"Curated\" />\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'nextSend',\n headerName: 'Next send',\n width: 190,\n sortable: false,\n renderCell: ({ row }: { row: { nextSendState: OutreachNextSendState } }) => (\n <Stack sx={{ justifyContent: 'center', height: '100%' }}>\n <OutreachNextSend state={row.nextSendState} timeZone={timeZone} />\n </Stack>\n ),\n },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 170,\n sortable: false,\n },\n ...(countOpens\n ? [\n {\n field: 'opens',\n headerName: 'Opens',\n description:\n 'A person’s opens of the emails. Fetches by mail proxies and security scanners are shown under them and not counted.',\n width: 130,\n sortable: false,\n renderCell: ({ row }: { row: { openSummary: OutreachOpenSummary } }) => (\n <OpensCell opens={row.openSummary} />\n ),\n },\n { field: 'lastOpen', headerName: 'Last open', width: 170, sortable: false },\n ]\n : []),\n ...(trackClicks\n ? [\n {\n field: 'clicks',\n headerName: 'Clicks',\n description: 'A person’s clicks, and how many different links they followed. Scanners are not counted.',\n width: 110,\n sortable: false,\n renderCell: ({ row }: { row: { clicks: string } }) => (\n <Typography variant=\"body2\" color={row.clicks === '—' ? 'text.secondary' : undefined}>\n {row.clicks}\n </Typography>\n ),\n },\n {\n field: 'linksFollowed',\n headerName: 'Links followed',\n flex: 1,\n minWidth: 220,\n sortable: false,\n },\n { field: 'lastClick', headerName: 'Last click', width: 170, sortable: false },\n {\n field: 'scannerClicks',\n headerName: 'Scanner clicks',\n description: 'Clicks a security scanner made, which are not counted as the person’s.',\n width: 130,\n sortable: false,\n },\n ]\n : []),\n {\n field: 'stopReason',\n headerName: 'Stop reason',\n flex: 1,\n minWidth: 170,\n renderCell: ({ row }) => <StopReasonCell enrollment={row.enrollment} />,\n },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/** A plain click on a link, which the page handles itself; any other is the browser's. */\nconst plainClick = (event: MouseEvent) =>\n !event.defaultPrevented &&\n event.button === 0 &&\n !event.metaKey &&\n !event.ctrlKey &&\n !event.shiftKey &&\n !event.altKey\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 *\n * Each row opens the person's own page (AGL-3332), which holds everything\n * else known about them: so a cell says one thing, whole, and the detail\n * behind it is a click away rather than cut off at the column's edge.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { steps, timeZone, api, onOpen, hrefFor } = props\n const trackClicks = props.trackClicks === true\n const countOpens = props.countOpens === true\n const nowMs = useOutreachNowMs()\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const actions = useOutreachEnrollmentActions({ api, steps })\n const fields = useMemo(() => filterFieldsFor(trackClicks), [trackClicks])\n const gridFilter = useListGridFilter({\n selectFields: ['status', 'target', 'stopReason', 'clicked', 'link'],\n ...(props.clauses !== undefined\n ? { clauses: props.clauses, onChange: props.onClausesChange }\n : {}),\n })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n /*\n * One page of the sequence's enrollments, newest enrolled first, with\n * every clause and the search word on the query (AGL-3321): a filter\n * reaches the enrollment on page forty as surely as the one on page one,\n * and nothing is matched over the rows that happen to be loaded. The\n * click filters (AGL-3332) are on it too, answered by the `clicked` flag\n * and the destinations the click route stores on each enrollment.\n */\n const enrollments = useOutreachEnrollmentList(props.orgId, props.sequenceId, {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n })\n const loadedData = enrollments.rows\n const summaries = useMemo(\n () =>\n new Map(\n loadedData.map((enrollment) => [enrollment.id, outreachClickSummary(enrollment.engagement)]),\n ),\n [loadedData],\n )\n const clicksOf = (enrollment: OutreachEnrollment) =>\n summaries.get(enrollment.id) ?? outreachClickSummary(enrollment.engagement)\n\n /*\n * The destinations the \"Link followed\" filter offers: the sequence's own\n * rollup, and any a loaded person followed that it does not list — the\n * rollup is capped, and a filter that could not name a destination a\n * person followed would hide them from it.\n */\n const linkOptions = useMemo(() => {\n const options = new Map<string, string>()\n for (const option of props.linkOptions ?? []) options.set(option.value, option.label)\n for (const summary of summaries.values()) {\n for (const url of summary.followed) if (!options.has(url)) options.set(url, url)\n }\n return [...options].map(([value, label]) => ({ value, label }))\n }, [props.linkOptions, summaries])\n const filterOptions = useMemo(\n () =>\n trackClicks\n ? { ...BASE_FILTER_OPTIONS, clicked: CLICKED_OPTIONS, link: linkOptions }\n : BASE_FILTER_OPTIONS,\n [trackClicks, linkOptions],\n )\n const hiddenColumns = useMemo(\n () => ({\n ...hiddenFilterVisibility(fields, ['status', 'stopReason']),\n ...(trackClicks\n ? Object.fromEntries(CLICK_DETAIL_COLUMNS.map((column) => [column, false]))\n : {}),\n ...(countOpens\n ? Object.fromEntries(OPEN_DETAIL_COLUMNS.map((column) => [column, false]))\n : {}),\n }),\n [fields, trackClicks, countOpens],\n )\n /*\n * A person who followed this destination before each click was recorded\n * on its own, and another link after it, is known by their LAST link only\n * — so a \"Link followed\" filter says it may miss them rather than\n * implying the list is whole. Which people those are is exactly what the\n * query cannot see, so the caveat stands whenever the filter does.\n */\n const linkFilterMayMiss =\n trackClicks && gridFilter.clauses.some((clause) => clause.field === 'link')\n\n const chips = (\n <ListFilterChips\n fields={fields}\n headers={ENROLLMENT_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n )\n if (enrollments.status === 'loading' && !filtering)\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <Stack spacing={1.5}>\n {chips}\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n </Stack>\n )\n }\n if (\n enrollments.status === 'ready' &&\n !enrollments.rows.length &&\n !filtering &&\n enrollments.page === 0\n ) {\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={actions.menuItems(enrollment)}\n />\n )\n /*\n * A next send whose time has come says it is queued, and why, rather than\n * showing a time already past (AGL-3366): the mailbox sends its share of\n * the day each run, follow-ups first, so a first email can wait its turn\n * for an hour or more without anything having failed.\n */\n const nextSendState = (enrollment: OutreachEnrollment) =>\n outreachNextSendState({\n enrollment,\n sequence: props.sequence ?? null,\n mailbox: props.mailbox,\n nowMs,\n })\n const personName = (enrollment: OutreachEnrollment) => {\n const name = enrollment.contactName || enrollment.email\n if (!hrefFor) return <Typography variant=\"body2\">{name}</Typography>\n return (\n <Link\n href={hrefFor(enrollment)}\n variant=\"body2\"\n underline=\"hover\"\n onClick={(event) => {\n if (!onOpen || !plainClick(event)) return\n event.preventDefault()\n onOpen(enrollment)\n }}\n >\n {name}\n </Link>\n )\n }\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n {personName(enrollment)}\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 pageRows = enrollments.rows\n\n return (\n <Stack spacing={1.5}>\n {chips}\n <ListQueryNotices\n refused={listQueryRefusals(enrollments.plan.refused, {\n fields,\n headers: ENROLLMENT_FILTER_HEADERS,\n options: filterOptions,\n })}\n notices={enrollments.plan.notices}\n />\n {linkFilterMayMiss ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n Clicks made before each one was recorded on its own kept only the person’s last link, so\n someone who followed this link and then another back then is not listed here.\n </Typography>\n ) : null}\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 const clicks = clicksOf(enrollment)\n const opens = outreachOpenSummary(enrollment.engagement)\n return (\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 <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\n {outreachStepIsCurated(enrollment) ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"success\" label=\"Curated\" />\n ) : null}\n </Stack>\n <Stack\n direction=\"row\"\n spacing={0.5}\n sx={{ alignItems: 'baseline', flexWrap: 'wrap', color: 'text.secondary' }}\n >\n <Typography variant=\"body2\">Next send:</Typography>\n <OutreachNextSend state={nextSendState(enrollment)} timeZone={timeZone} />\n <Typography variant=\"body2\">\n {`· Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`}\n </Typography>\n </Stack>\n {countOpens && (opens.opens || opens.machineOpens) ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Opens: ${outreachOpenCountLabel(opens)}`}\n </Typography>\n ) : null}\n {trackClicks && clicks.clicks ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Clicks: ${outreachClickCountLabel(clicks)}`}\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 })}\n </Stack>\n ) : (\n <ListTable\n aria-label=\"Enrollments\"\n columns={listFilterGridColumns(\n columns(rowActions, trackClicks, countOpens, timeZone),\n fields,\n filterOptions,\n ENROLLMENT_FILTER_HEADERS,\n )}\n initialState={{ columns: { columnVisibilityModel: hiddenColumns } }}\n loading={enrollments.status === 'loading'}\n rows={pageRows.map((enrollment) => {\n const clicks = clicksOf(enrollment)\n const opens = outreachOpenSummary(enrollment.engagement)\n const next = nextSendState(enrollment)\n return {\n $id: enrollment.id,\n ...filterRow(enrollment, steps, clicks),\n nextSendState: next,\n // What the export writes: the label, and for a queued send, why.\n nextSend: [outreachNextSendLabel(next, timeZone), outreachNextSendDetail(next, timeZone)]\n .filter(Boolean)\n .join(' — '),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n // What the export writes; the cells draw these their own way.\n openSummary: opens,\n opens: outreachOpenCountLabel(opens),\n lastOpen: opens.opens ? formatOutreachTime(opens.lastOpenAtMs, timeZone) : '—',\n clicks: outreachClickCountLabel(clicks),\n linksFollowed: clicks.followed.join(' ') || '—',\n lastClick: clicks.clicks\n ? formatOutreachTime(enrollment.engagement?.lastClickAtMs, timeZone)\n : '—',\n scannerClicks: clicks.machineClicks ? String(clicks.machineClicks) : '—',\n stopReason: outreachStopLabel(enrollment) || '—',\n }\n })}\n onOpen={\n onOpen\n ? (_id, row: { enrollment: OutreachEnrollment }) => onOpen(row.enrollment)\n : undefined\n }\n /*\n * The panel and the search are the grid's; every clause and the\n * search word are on the table's query (AGL-3321). The grid\n * neither filters nor sorts the page it is handed: the query's\n * one order, newest enrolled first, is the table's.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n disableColumnSorting\n noRowsLabel=\"No enrollments match these filters\"\n hideFooter\n />\n )}\n <ListPagination\n page={enrollments.page}\n pageSize={enrollments.pageSize}\n rowCount={pageRows.length}\n hasMore={enrollments.hasMore}\n onPageChange={enrollments.setPage}\n onPageSizeChange={enrollments.setPageSize}\n />\n {actions.dialogs}\n </Stack>\n )\n}\nOutreachEnrollmentsTable.displayName = 'OutreachEnrollmentsTable'\n\nexport default OutreachEnrollmentsTable\n"],"names":["EmptyStateComponent","ListFilterChips","ListQueryNotices","listQueryRefusals","ListPagination","ListRowActions","ListTable","listActionsColumn","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","Card","CardContent","Chip","Link","Stack","Tooltip","Typography","useMediaQuery","useTheme","useMemo","outreachClickCountLabel","outreachClickSummary","outreachMachineOpenLabel","outreachOpenCountLabel","outreachOpenSummary","OUTREACH_ENROLLMENT_CLICK_FIELDS","OUTREACH_ENROLLMENT_LIST_QUERY","OUTREACH_ENROLLMENT_STATUSES","OUTREACH_STOP_REASONS","OUTREACH_TASK_KIND_LABELS","outreachNextSendState","useOutreachEnrollmentActions","OUTREACH_CLICKED_FILTER_VALUE","OUTREACH_UNCLICKED_FILTER_VALUE","OutreachNextSend","outreachNextSendDetail","outreachNextSendLabel","useOutreachNowMs","formatOutreachTime","OUTREACH_ENROLLMENT_STATUS_LABELS","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","useOutreachEnrollmentList","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","outreachStepIsCurated","Boolean","stepOverrides","String","BASE_FILTER_FIELDS","fields","filter","field","includes","column","ENROLLMENT_FILTER_HEADERS","target","email","enrolled","clicked","link","BASE_FILTER_OPTIONS","map","value","label","CLICKED_OPTIONS","CLICK_DETAIL_COLUMNS","OPEN_DETAIL_COLUMNS","filterFieldsFor","trackClicks","filterRow","clicks","contactName","followed","StopReasonCell","props","variant","color","text","sx","whiteSpace","lineHeight","textDecoration","textUnderlineOffset","cursor","title","describeChild","OpensCell","opens","machines","spacing","justifyContent","height","undefined","noWrap","columns","rowActions","countOpens","timeZone","headerName","flex","minWidth","sortable","valueGetter","_value","row","renderCell","direction","alignItems","size","width","state","nextSendState","description","openSummary","plainClick","event","defaultPrevented","button","metaKey","ctrlKey","shiftKey","altKey","OutreachEnrollmentsTable","api","onOpen","hrefFor","nowMs","theme","narrow","breakpoints","down","actions","gridFilter","selectFields","clauses","onChange","onClausesChange","filtering","searchWords","some","word","trim","enrollments","orgId","sequenceId","search","loadedData","rows","summaries","Map","id","engagement","clicksOf","get","linkOptions","options","option","set","summary","values","url","has","filterOptions","hiddenColumns","Object","fromEntries","linkFilterMayMiss","clause","chips","headers","setClauses","what","page","action","enrollAction","items","menuItems","sequence","mailbox","personName","name","href","underline","onClick","preventDefault","person","pageRows","refused","plan","notices","component","listStyle","p","m","aria-label","flexGrow","flexWrap","machineOpens","initialState","columnVisibilityModel","loading","next","$id","nextSend","join","lastActivity","lastOpen","lastOpenAtMs","linksFollowed","lastClick","lastClickAtMs","scannerClicks","machineClicks","_id","filterMode","filterModel","onFilterModelChange","quickFilter","disableColumnSorting","noRowsLabel","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","dialogs","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 ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.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 { hiddenFilterVisibility, type ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport {\n listFilterGridColumns,\n type ListFilterClause,\n type ListFilterOption,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport { useListColumnSort } from '@aglyn/shared-ui-jsx/hooks/use-list-column-sort'\nimport type { ListQuerySort } from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport {\n Card,\n CardContent,\n Chip,\n Link,\n Stack,\n Tooltip,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { type MouseEvent, type ReactNode, useMemo, useState } from 'react'\nimport {\n outreachClickCountLabel,\n outreachClickSummary,\n outreachMachineOpenLabel,\n outreachOpenCountLabel,\n outreachOpenSummary,\n type OutreachClickSummary,\n type OutreachOpenSummary,\n} from '../model/enrollment-engagement'\nimport {\n OUTREACH_ENROLLMENT_CLICK_FIELDS,\n OUTREACH_ENROLLMENT_LIST_QUERY,\n} from '../model/enrollment-list-query'\nimport {\n OUTREACH_ENROLLMENT_STATUSES,\n OUTREACH_STOP_REASONS,\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachMailbox,\n type OutreachSequence,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport { outreachNextSendState, type OutreachNextSendState } from '../engine/next-send'\nimport { useOutreachEnrollmentActions } from './enrollment-actions'\nimport { OUTREACH_CLICKED_FILTER_VALUE, OUTREACH_UNCLICKED_FILTER_VALUE } from './enrollment-filter-params'\nimport {\n OutreachNextSend,\n outreachNextSendDetail,\n outreachNextSendLabel,\n useOutreachNowMs,\n} from './next-send'\nimport {\n formatOutreachTime,\n OUTREACH_ENROLLMENT_STATUS_LABELS,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport { useOutreachEnrollmentList } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n /** The organization, whose enrollments the table asks for. */\n orgId: string\n /** The sequence whose people these are: every page is narrowed to it. */\n sequenceId: string\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 /**\n * Whether this sequence counts opens, or once did (AGL-3488). The Opens\n * column is shown only then, for the reason the Clicks column is: a\n * column of dashes where nothing was measured reads as \"nobody opened\".\n */\n countOpens?: boolean\n /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */\n timeZone: string | null\n /**\n * The sequence's mailbox, whose pacing a due enrollment waits on\n * (AGL-3366): `undefined` while the mailboxes load, `null` when it has none.\n */\n mailbox?: OutreachMailbox | null\n /** The sequence, whose status and window a due enrollment waits on too. */\n sequence?: Pick<OutreachSequence, 'status' | 'steps' | 'settings'> | null\n api: OutreachApi\n /** Shown in the empty state when the sequence takes people. */\n enrollAction?: ReactNode\n /**\n * Opens one person's page (AGL-3332): a row's click, Enter on a focused\n * row, or the name on a phone's card. The row's menu never opens it.\n */\n onOpen?: (enrollment: OutreachEnrollment) => void\n /** The person's page, for the name on a phone's card, which is a real link. */\n hrefFor?: (enrollment: OutreachEnrollment) => string\n /**\n * The clauses the table is narrowed by, when the page holds them — so the\n * Results card's \"Clicked\" and \"Links followed\" can set one (AGL-3332).\n * Omitted, the table holds its own.\n */\n clauses?: readonly ListFilterClause[]\n onClausesChange?: (clauses: ListFilterClause[]) => void\n /** Destinations the sequence's links went to, for the \"Link followed\" filter's choices. */\n linkOptions?: readonly ListFilterOption[]\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/** Whether the step the enrollment is on goes out as this person's own copy (AGL-3324). */\nexport function outreachStepIsCurated(\n enrollment: Pick<OutreachEnrollment, 'stepIndex' | 'stepOverrides'>,\n): boolean {\n return Boolean(enrollment.stepOverrides?.[String(enrollment.stepIndex)])\n}\n\n/*\n * What the enrollments grid's Filters panel offers (AGL-3317), each field on\n * the table's Firestore query (AGL-3321, `OUTREACH_ENROLLMENT_LIST_QUERY`).\n * Status, whether the person is a lead and the stop reason are picked; the\n * address is typed whole. `target` and `email` are no column of their own,\n * so they are hidden columns the panel can still reach; the search box\n * reads the person's name and address.\n *\n * A sequence that counts clicks adds two more (AGL-3332), on the same query:\n * whether the person clicked, and a destination they followed — matched\n * whole against the destinations stored on the enrollment, never by a piece\n * of its address, so \"followed /pricing\" does not also list the people who\n * followed \"/pricing/enterprise\".\n */\nconst BASE_FILTER_FIELDS = OUTREACH_ENROLLMENT_LIST_QUERY.fields.filter(\n (field) => !(OUTREACH_ENROLLMENT_CLICK_FIELDS as readonly string[]).includes(field.column),\n)\nconst ENROLLMENT_FILTER_HEADERS: Readonly<Record<string, string>> = {\n status: 'Status',\n target: 'Enrolled as',\n stopReason: 'Stop reason',\n email: 'Email',\n enrolled: 'Enrolled',\n clicked: 'Clicked',\n link: 'Link followed',\n}\nconst BASE_FILTER_OPTIONS: Record<string, readonly ListFilterOption[]> = {\n status: OUTREACH_ENROLLMENT_STATUSES.map((status) => ({\n value: status,\n label: OUTREACH_ENROLLMENT_STATUS_LABELS[status],\n })),\n target: [\n { value: 'contact', label: 'Contact' },\n { value: 'lead', label: 'Lead' },\n ],\n stopReason: OUTREACH_STOP_REASONS.map((reason) => ({\n value: reason,\n label: OUTREACH_STOP_REASON_LABELS[reason],\n })),\n}\nconst CLICKED_OPTIONS: readonly ListFilterOption[] = [\n { value: OUTREACH_CLICKED_FILTER_VALUE, label: 'Yes' },\n { value: OUTREACH_UNCLICKED_FILTER_VALUE, label: 'No' },\n]\n/** Columns a reader can show from the column chooser, off until they do (AGL-3332). */\nconst CLICK_DETAIL_COLUMNS = ['linksFollowed', 'lastClick', 'scannerClicks'] as const\n/** The opens column a reader can show from the column chooser, off until they do. */\nconst OPEN_DETAIL_COLUMNS = ['lastOpen'] as const\n\n/** The table's grammar, with the click fields when the sequence counts clicks. */\nfunction filterFieldsFor(trackClicks: boolean): readonly ListFilterField[] {\n return trackClicks ? OUTREACH_ENROLLMENT_LIST_QUERY.fields : BASE_FILTER_FIELDS\n}\n\n/** What the grid's cells and its export read off one enrollment. */\nfunction filterRow(\n enrollment: OutreachEnrollment,\n steps: readonly OutreachSequenceStep[],\n clicks: OutreachClickSummary,\n) {\n return {\n enrollment,\n status: enrollment.status,\n target: enrollment.target === 'lead' ? 'lead' : 'contact',\n contactName: enrollment.contactName,\n email: enrollment.email,\n step: outreachCurrentStepLabel(enrollment, steps),\n stopReason: outreachStopLabel(enrollment),\n clicked: clicks.clicks > 0 ? OUTREACH_CLICKED_FILTER_VALUE : OUTREACH_UNCLICKED_FILTER_VALUE,\n link: clicks.followed,\n }\n}\n\n/** One table row: the enrollment and what its cells draw. */\ntype EnrollmentTableRow = ReturnType<typeof filterRow> & {\n $id: string\n openSummary: OutreachOpenSummary\n clickSummary: OutreachClickSummary\n}\n\n/*\n * The columns read from `engagement.*` — absent until a person's first\n * event — and the derived Last activity sort the page on screen (AGL-3680);\n * Person, Status, Current step, Next send and Stop reason order the query\n * (`OUTREACH_ENROLLMENT_LIST_QUERY.sorts`).\n */\nconst ENROLLMENT_PAGE_SORTS = {\n lastActivity: (row: EnrollmentTableRow) => lastActivityMs(row.enrollment) || null,\n opens: (row: EnrollmentTableRow) => row.openSummary.opens,\n lastOpen: (row: EnrollmentTableRow) => row.openSummary.lastOpenAtMs,\n clicks: (row: EnrollmentTableRow) => row.clickSummary.clicks,\n linksFollowed: (row: EnrollmentTableRow) => row.clickSummary.followed.length,\n lastClick: (row: EnrollmentTableRow) => row.enrollment.engagement?.lastClickAtMs ?? null,\n scannerClicks: (row: EnrollmentTableRow) => row.clickSummary.machineClicks,\n}\nconst ENROLLMENT_PAGE_SORT_HEADERS = {\n lastActivity: 'Last activity',\n opens: 'Opens',\n lastOpen: 'Last open',\n clicks: 'Clicks',\n linksFollowed: 'Links followed',\n lastClick: 'Last click',\n scannerClicks: 'Scanner clicks',\n}\n\n/** The stop reason: its short label, with every word of it on hover and on the person's page. */\nfunction StopReasonCell(props: { enrollment: OutreachEnrollment }) {\n const { enrollment } = props\n if (!enrollment.stopReason) {\n return (\n <Typography variant=\"body2\" color=\"text.secondary\">\n —\n </Typography>\n )\n }\n const label = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n const text = (\n <Typography\n variant=\"body2\"\n sx={{\n whiteSpace: 'normal',\n lineHeight: 1.3,\n ...(enrollment.stopDetail\n ? { textDecoration: 'underline dotted', textUnderlineOffset: 3, cursor: 'help' }\n : {}),\n }}\n >\n {label}\n </Typography>\n )\n return enrollment.stopDetail ? (\n <Tooltip title={outreachStopLabel(enrollment)} describeChild>\n {text}\n </Tooltip>\n ) : (\n text\n )\n}\n\n/**\n * The Opens cell (AGL-3488): the person's opens, and under them the fetches\n * a machine made — counted apart and never in the person's number, as\n * scanner clicks are never in their clicks.\n */\nfunction OpensCell(props: { opens: OutreachOpenSummary }) {\n const { opens } = props\n const machines = outreachMachineOpenLabel(opens)\n return (\n <Stack spacing={0} sx={{ justifyContent: 'center', height: '100%' }}>\n <Typography variant=\"body2\" color={opens.opens ? undefined : 'text.secondary'}>\n {opens.opens ? String(opens.opens) : '—'}\n </Typography>\n {machines ? (\n <Tooltip\n title=\"Fetches of the tracking image by a mail provider’s proxy or a security scanner. They aren’t counted as the person’s opens.\"\n describeChild\n >\n <Typography\n variant=\"caption\"\n color=\"text.secondary\"\n noWrap\n sx={{ textDecoration: 'underline dotted', textUnderlineOffset: 3, cursor: 'help' }}\n >\n {machines}\n </Typography>\n </Tooltip>\n ) : null}\n </Stack>\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 countOpens: boolean,\n timeZone: string | null,\n): NonNullable<ListTableProps['columns']> {\n return [\n {\n field: 'person',\n headerName: 'Person',\n flex: 1.2,\n minWidth: 190,\n sortable: false,\n // What the export writes for a column that is drawn rather than read.\n valueGetter: (_value: unknown, row: { enrollment: OutreachEnrollment }) =>\n row.enrollment.contactName\n ? `${row.enrollment.contactName} <${row.enrollment.email}>`\n : row.enrollment.email,\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: 110,\n renderCell: ({ row }) => (\n <OutreachEnrollmentStatusChip status={row.status} />\n ),\n },\n {\n field: 'step',\n headerName: 'Current step',\n flex: 1,\n minWidth: 170,\n renderCell: ({ row }) => (\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center', height: '100%', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.step}\n </Typography>\n {outreachStepIsCurated(row.enrollment) ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"success\" label=\"Curated\" />\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'nextSend',\n headerName: 'Next send',\n width: 190,\n sortable: false,\n renderCell: ({ row }: { row: { nextSendState: OutreachNextSendState } }) => (\n <Stack sx={{ justifyContent: 'center', height: '100%' }}>\n <OutreachNextSend state={row.nextSendState} timeZone={timeZone} />\n </Stack>\n ),\n },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 170,\n sortable: false,\n },\n ...(countOpens\n ? [\n {\n field: 'opens',\n headerName: 'Opens',\n description:\n 'A person’s opens of the emails. Fetches by mail proxies and security scanners are shown under them and not counted.',\n width: 130,\n sortable: false,\n renderCell: ({ row }: { row: { openSummary: OutreachOpenSummary } }) => (\n <OpensCell opens={row.openSummary} />\n ),\n },\n { field: 'lastOpen', headerName: 'Last open', width: 170, sortable: false },\n ]\n : []),\n ...(trackClicks\n ? [\n {\n field: 'clicks',\n headerName: 'Clicks',\n description: 'A person’s clicks, and how many different links they followed. Scanners are not counted.',\n width: 110,\n sortable: false,\n renderCell: ({ row }: { row: { clicks: string } }) => (\n <Typography variant=\"body2\" color={row.clicks === '—' ? 'text.secondary' : undefined}>\n {row.clicks}\n </Typography>\n ),\n },\n {\n field: 'linksFollowed',\n headerName: 'Links followed',\n flex: 1,\n minWidth: 220,\n sortable: false,\n },\n { field: 'lastClick', headerName: 'Last click', width: 170, sortable: false },\n {\n field: 'scannerClicks',\n headerName: 'Scanner clicks',\n description: 'Clicks a security scanner made, which are not counted as the person’s.',\n width: 130,\n sortable: false,\n },\n ]\n : []),\n {\n field: 'stopReason',\n headerName: 'Stop reason',\n flex: 1,\n minWidth: 170,\n renderCell: ({ row }) => <StopReasonCell enrollment={row.enrollment} />,\n },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/** A plain click on a link, which the page handles itself; any other is the browser's. */\nconst plainClick = (event: MouseEvent) =>\n !event.defaultPrevented &&\n event.button === 0 &&\n !event.metaKey &&\n !event.ctrlKey &&\n !event.shiftKey &&\n !event.altKey\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 *\n * Each row opens the person's own page (AGL-3332), which holds everything\n * else known about them: so a cell says one thing, whole, and the detail\n * behind it is a click away rather than cut off at the column's edge.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { steps, timeZone, api, onOpen, hrefFor } = props\n const trackClicks = props.trackClicks === true\n const countOpens = props.countOpens === true\n const nowMs = useOutreachNowMs()\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const actions = useOutreachEnrollmentActions({ api, steps })\n const fields = useMemo(() => filterFieldsFor(trackClicks), [trackClicks])\n const gridFilter = useListGridFilter({\n selectFields: ['status', 'target', 'stopReason', 'clicked', 'link'],\n ...(props.clauses !== undefined\n ? { clauses: props.clauses, onChange: props.onClausesChange }\n : {}),\n })\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n /*\n * One page of the sequence's enrollments, newest enrolled first, with\n * every clause and the search word on the query (AGL-3321): a filter\n * reaches the enrollment on page forty as surely as the one on page one,\n * and nothing is matched over the rows that happen to be loaded. The\n * click filters (AGL-3332) are on it too, answered by the `clicked` flag\n * and the destinations the click route stores on each enrollment.\n */\n const [askedSort, setAskedSort] = useState<ListQuerySort | null>(null)\n const enrollments = useOutreachEnrollmentList(props.orgId, props.sequenceId, {\n clauses: gridFilter.clauses,\n search: gridFilter.searchWords,\n sort: askedSort,\n })\n const loadedData = enrollments.rows\n const summaries = useMemo(\n () =>\n new Map(\n loadedData.map((enrollment) => [enrollment.id, outreachClickSummary(enrollment.engagement)]),\n ),\n [loadedData],\n )\n const clicksOf = (enrollment: OutreachEnrollment) =>\n summaries.get(enrollment.id) ?? outreachClickSummary(enrollment.engagement)\n\n /*\n * The destinations the \"Link followed\" filter offers: the sequence's own\n * rollup, and any a loaded person followed that it does not list — the\n * rollup is capped, and a filter that could not name a destination a\n * person followed would hide them from it.\n */\n const linkOptions = useMemo(() => {\n const options = new Map<string, string>()\n for (const option of props.linkOptions ?? []) options.set(option.value, option.label)\n for (const summary of summaries.values()) {\n for (const url of summary.followed) if (!options.has(url)) options.set(url, url)\n }\n return [...options].map(([value, label]) => ({ value, label }))\n }, [props.linkOptions, summaries])\n const filterOptions = useMemo(\n () =>\n trackClicks\n ? { ...BASE_FILTER_OPTIONS, clicked: CLICKED_OPTIONS, link: linkOptions }\n : BASE_FILTER_OPTIONS,\n [trackClicks, linkOptions],\n )\n const hiddenColumns = useMemo(\n () => ({\n ...hiddenFilterVisibility(fields, ['status', 'stopReason']),\n ...(trackClicks\n ? Object.fromEntries(CLICK_DETAIL_COLUMNS.map((column) => [column, false]))\n : {}),\n ...(countOpens\n ? Object.fromEntries(OPEN_DETAIL_COLUMNS.map((column) => [column, false]))\n : {}),\n }),\n [fields, trackClicks, countOpens],\n )\n /*\n * A person who followed this destination before each click was recorded\n * on its own, and another link after it, is known by their LAST link only\n * — so a \"Link followed\" filter says it may miss them rather than\n * implying the list is whole. Which people those are is exactly what the\n * query cannot see, so the caveat stands whenever the filter does.\n */\n const linkFilterMayMiss =\n trackClicks && gridFilter.clauses.some((clause) => clause.field === 'link')\n\n const chips = (\n <ListFilterChips\n fields={fields}\n headers={ENROLLMENT_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n )\n /*\n * A next send whose time has come says it is queued, and why, rather than\n * showing a time already past (AGL-3366): the mailbox sends its share of\n * the day each run, follow-ups first, so a first email can wait its turn\n * for an hour or more without anything having failed.\n */\n const nextSendState = (enrollment: OutreachEnrollment) =>\n outreachNextSendState({\n enrollment,\n sequence: props.sequence ?? null,\n mailbox: props.mailbox,\n nowMs,\n })\n const pageRows = enrollments.rows\n const tableRows: EnrollmentTableRow[] = pageRows.map((enrollment) => {\n const clicks = clicksOf(enrollment)\n const opens = outreachOpenSummary(enrollment.engagement)\n const next = nextSendState(enrollment)\n return {\n $id: enrollment.id,\n ...filterRow(enrollment, steps, clicks),\n nextSendState: next,\n // What the export writes: the label, and for a queued send, why.\n nextSend: [outreachNextSendLabel(next, timeZone), outreachNextSendDetail(next, timeZone)]\n .filter(Boolean)\n .join(' — '),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n // What the export writes; the cells draw these their own way.\n openSummary: opens,\n clickSummary: clicks,\n opens: outreachOpenCountLabel(opens),\n lastOpen: opens.opens ? formatOutreachTime(opens.lastOpenAtMs, timeZone) : '—',\n clicks: outreachClickCountLabel(clicks),\n linksFollowed: clicks.followed.join(' ') || '—',\n lastClick: clicks.clicks\n ? formatOutreachTime(enrollment.engagement?.lastClickAtMs, timeZone)\n : '—',\n scannerClicks: clicks.machineClicks ? String(clicks.machineClicks) : '—',\n stopReason: outreachStopLabel(enrollment) || '—',\n } as EnrollmentTableRow\n })\n const columnSort = useListColumnSort<EnrollmentTableRow>({\n sorts: OUTREACH_ENROLLMENT_LIST_QUERY.sorts,\n sort: askedSort,\n onSortChange: setAskedSort,\n orderBy: enrollments.plan.orderBy,\n rows: tableRows,\n pageSorts: ENROLLMENT_PAGE_SORTS,\n headers: ENROLLMENT_PAGE_SORT_HEADERS,\n })\n\n if (enrollments.status === 'loading' && !filtering)\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <Stack spacing={1.5}>\n {chips}\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n </Stack>\n )\n }\n if (\n enrollments.status === 'ready' &&\n !enrollments.rows.length &&\n !filtering &&\n enrollments.page === 0\n ) {\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={actions.menuItems(enrollment)}\n />\n )\n const personName = (enrollment: OutreachEnrollment) => {\n const name = enrollment.contactName || enrollment.email\n if (!hrefFor) return <Typography variant=\"body2\">{name}</Typography>\n return (\n <Link\n href={hrefFor(enrollment)}\n variant=\"body2\"\n underline=\"hover\"\n onClick={(event) => {\n if (!onOpen || !plainClick(event)) return\n event.preventDefault()\n onOpen(enrollment)\n }}\n >\n {name}\n </Link>\n )\n }\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n {personName(enrollment)}\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\n return (\n <Stack spacing={1.5}>\n {chips}\n <ListQueryNotices\n refused={listQueryRefusals(enrollments.plan.refused, {\n fields,\n headers: ENROLLMENT_FILTER_HEADERS,\n options: filterOptions,\n })}\n notices={[...enrollments.plan.notices, ...columnSort.notices]}\n />\n {linkFilterMayMiss ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n Clicks made before each one was recorded on its own kept only the person’s last link, so\n someone who followed this link and then another back then is not listed here.\n </Typography>\n ) : null}\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 const clicks = clicksOf(enrollment)\n const opens = outreachOpenSummary(enrollment.engagement)\n return (\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 <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\n {outreachStepIsCurated(enrollment) ? (\n <Chip size=\"small\" variant=\"outlined\" color=\"success\" label=\"Curated\" />\n ) : null}\n </Stack>\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={0.5}\n sx={{ alignItems: 'baseline', flexWrap: 'wrap', color: 'text.secondary' }}\n >\n <Typography variant=\"body2\">Next send:</Typography>\n <OutreachNextSend state={nextSendState(enrollment)} timeZone={timeZone} />\n <Typography variant=\"body2\">\n {`· Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`}\n </Typography>\n </Stack>\n {countOpens && (opens.opens || opens.machineOpens) ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Opens: ${outreachOpenCountLabel(opens)}`}\n </Typography>\n ) : null}\n {trackClicks && clicks.clicks ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Clicks: ${outreachClickCountLabel(clicks)}`}\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 })}\n </Stack>\n ) : (\n <ListTable\n aria-label=\"Enrollments\"\n columns={listFilterGridColumns(\n columns(rowActions, trackClicks, countOpens, timeZone),\n fields,\n filterOptions,\n ENROLLMENT_FILTER_HEADERS,\n )}\n initialState={{ columns: { columnVisibilityModel: hiddenColumns } }}\n loading={enrollments.status === 'loading'}\n rows={columnSort.rows}\n onOpen={\n onOpen\n ? (_id, row: { enrollment: OutreachEnrollment }) => onOpen(row.enrollment)\n : undefined\n }\n /*\n * The panel and the search are the grid's; every clause and the\n * search word are on the table's query (AGL-3321). The grid\n * neither filters nor sorts the page it is handed: a stored\n * column's header orders the query, an engagement one the page\n * (AGL-3680), newest enrolled first until one is clicked.\n */\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n columnSort={columnSort}\n noRowsLabel=\"No enrollments match these filters\"\n hideFooter\n />\n )}\n <ListPagination\n page={enrollments.page}\n pageSize={enrollments.pageSize}\n rowCount={pageRows.length}\n hasMore={enrollments.hasMore}\n onPageChange={enrollments.setPage}\n onPageSizeChange={enrollments.setPageSize}\n />\n {actions.dialogs}\n </Stack>\n )\n}\nOutreachEnrollmentsTable.displayName = 'OutreachEnrollmentsTable'\n\nexport default OutreachEnrollmentsTable\n"],"names":["EmptyStateComponent","ListFilterChips","ListQueryNotices","listQueryRefusals","ListPagination","ListRowActions","ListTable","listActionsColumn","hiddenFilterVisibility","listFilterGridColumns","useListGridFilter","useListColumnSort","Card","CardContent","Chip","Link","Stack","Tooltip","Typography","useMediaQuery","useTheme","useMemo","useState","outreachClickCountLabel","outreachClickSummary","outreachMachineOpenLabel","outreachOpenCountLabel","outreachOpenSummary","OUTREACH_ENROLLMENT_CLICK_FIELDS","OUTREACH_ENROLLMENT_LIST_QUERY","OUTREACH_ENROLLMENT_STATUSES","OUTREACH_STOP_REASONS","OUTREACH_TASK_KIND_LABELS","outreachNextSendState","useOutreachEnrollmentActions","OUTREACH_CLICKED_FILTER_VALUE","OUTREACH_UNCLICKED_FILTER_VALUE","OutreachNextSend","outreachNextSendDetail","outreachNextSendLabel","useOutreachNowMs","formatOutreachTime","OUTREACH_ENROLLMENT_STATUS_LABELS","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","useOutreachEnrollmentList","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","outreachStepIsCurated","Boolean","stepOverrides","String","BASE_FILTER_FIELDS","fields","filter","field","includes","column","ENROLLMENT_FILTER_HEADERS","target","email","enrolled","clicked","link","BASE_FILTER_OPTIONS","map","value","label","CLICKED_OPTIONS","CLICK_DETAIL_COLUMNS","OPEN_DETAIL_COLUMNS","filterFieldsFor","trackClicks","filterRow","clicks","contactName","followed","ENROLLMENT_PAGE_SORTS","lastActivity","row","opens","openSummary","lastOpen","lastOpenAtMs","clickSummary","linksFollowed","lastClick","engagement","lastClickAtMs","scannerClicks","machineClicks","ENROLLMENT_PAGE_SORT_HEADERS","StopReasonCell","props","variant","color","text","sx","whiteSpace","lineHeight","textDecoration","textUnderlineOffset","cursor","title","describeChild","OpensCell","machines","spacing","justifyContent","height","undefined","noWrap","columns","rowActions","countOpens","timeZone","headerName","flex","minWidth","sortable","valueGetter","_value","renderCell","direction","alignItems","size","width","state","nextSendState","description","plainClick","event","defaultPrevented","button","metaKey","ctrlKey","shiftKey","altKey","OutreachEnrollmentsTable","api","onOpen","hrefFor","nowMs","theme","narrow","breakpoints","down","actions","gridFilter","selectFields","clauses","onChange","onClausesChange","filtering","searchWords","some","word","trim","askedSort","setAskedSort","enrollments","orgId","sequenceId","search","sort","loadedData","rows","summaries","Map","id","clicksOf","get","linkOptions","options","option","set","summary","values","url","has","filterOptions","hiddenColumns","Object","fromEntries","linkFilterMayMiss","clause","chips","headers","setClauses","sequence","mailbox","pageRows","tableRows","next","$id","nextSend","join","columnSort","sorts","onSortChange","orderBy","plan","pageSorts","what","page","action","enrollAction","items","menuItems","personName","name","href","underline","onClick","preventDefault","person","refused","notices","component","listStyle","p","m","aria-label","flexGrow","useFlexGap","flexWrap","machineOpens","initialState","columnVisibilityModel","loading","_id","filterMode","filterModel","onFilterModelChange","quickFilter","noRowsLabel","hideFooter","pageSize","rowCount","hasMore","onPageChange","setPage","onPageSizeChange","setPageSize","dialogs","displayName"],"mappings":"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"}
@@ -166,6 +166,7 @@ function LinkDomainRow(props) {
166
166
  spacing: 1,
167
167
  children: [
168
168
  /*#__PURE__*/ _jsxs(Stack, {
169
+ useFlexGap: true,
169
170
  direction: "row",
170
171
  spacing: 1,
171
172
  sx: {
@@ -245,6 +246,7 @@ function LinkDomainRow(props) {
245
246
  children: `Point ${row.host} at this app — a CNAME to its address, or your proxy’s — then check it.`
246
247
  }) : null,
247
248
  /*#__PURE__*/ _jsxs(Stack, {
249
+ useFlexGap: true,
248
250
  direction: "row",
249
251
  spacing: 1,
250
252
  sx: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/link-domains-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Box, Button, Chip, Divider, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type {\n OutreachLinkDomain,\n OutreachLinkDomainRequest,\n OutreachLinkDomainStatus,\n OutreachLinkDomainsResponse,\n} from '../model/outreach-api'\nimport { OutreachLoading, OutreachLoadProblem } from './outreach-ui'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\n\nexport interface OutreachLinkDomainsCardProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n}\n\n/** Where a link domain stands, as the card's chip says it. */\nexport const OUTREACH_LINK_DOMAIN_STATUS_LABELS: Record<OutreachLinkDomainStatus, string> = {\n 'not-set-up': 'Not set up',\n requested: 'Not added yet',\n 'records-issued': 'Waiting for DNS',\n verified: 'Verified',\n failed: 'Not answering',\n}\n\nconst STATUS_COLORS: Record<OutreachLinkDomainStatus, 'default' | 'success' | 'warning' | 'error'> = {\n 'not-set-up': 'default',\n requested: 'warning',\n 'records-issued': 'warning',\n verified: 'success',\n failed: 'error',\n}\n\n/** Accessible names of the card's controls, spelled once for the specs. */\nexport const LINK_DOMAIN_LABELS = {\n setUp: (host: string) => `Set up ${host}`,\n check: (host: string) => `Check ${host}`,\n remove: (host: string) => `Remove ${host}`,\n} as const\n\ntype Load =\n | { status: 'loading' }\n | { status: 'ready'; data: OutreachLinkDomainsResponse }\n | { status: 'error' | 'refused' }\n\n/**\n * Sequences → Mailboxes → Link domains (AGL-3306): the host tracked links in\n * a mailbox's email point at.\n *\n * With **Count link clicks** on, every link in a sequence email is a short\n * link. It reads `https://links.<the mailbox's domain>/…` once that host is\n * set up here and verified — the same `links.` name, CNAME and check a\n * sending domain's campaign click tracking uses — and the app's own address\n * until then. A link on your own domain looks like the rest of your email.\n */\nexport function OutreachLinkDomainsCard(props: OutreachLinkDomainsCardProps) {\n const { orgId } = props\n const api = useOutreachApi(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [load, setLoad] = useState<Load>({ status: 'loading' })\n const [busy, setBusy] = useState<string | null>(null)\n\n useEffect(() => {\n if (!orgId) return undefined\n let current = true\n setLoad({ status: 'loading' })\n api\n .readLinkDomains()\n .then((data) => current && setLoad({ status: 'ready', data }))\n .catch(\n (error: Error) =>\n current &&\n setLoad({ status: error instanceof OutreachRouteError && error.refused ? 'refused' : 'error' }),\n )\n return () => {\n current = false\n }\n }, [api, orgId])\n\n const change = useCallback(\n async (action: OutreachLinkDomainRequest['action'], row: OutreachLinkDomain) => {\n if (action === 'remove') {\n const confirmed = await confirm({\n title: `Remove ${row.host}?`,\n description:\n `Links already sent on ${row.host} stop working, and new sequence emails use this ` +\n 'app’s address instead. Remove it only once no one will click those links again.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n }\n setBusy(row.domain)\n try {\n const answer = await api.changeLinkDomain(action, row.domain)\n setLoad({ status: 'ready', data: answer })\n const now = answer.domains.find((entry) => entry.domain === row.domain)\n enqueueSnackbar(\n action === 'remove'\n ? `${row.host} is no longer a link domain.`\n : now?.status === 'verified'\n ? `${row.host} is verified. Tracked links now use it.`\n : action === 'set-up'\n ? `${row.host} is set up. Publish the DNS record, then check it.`\n : (now?.detail ?? `${row.host} is not verified yet.`),\n { variant: now?.status === 'verified' || action === 'remove' ? 'success' : 'info' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n },\n [api, confirm, enqueueSnackbar],\n )\n\n return (\n <CardDisplay\n header=\"Link domains\"\n help={pluginDocsHelp('sequences', { anchor: '#link-domains' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'With Count link clicks on, the links in a sequence email are short links. Set up your ' +\n 'mailbox domain’s links host and they read links.<your domain> instead of this app’s ' +\n 'address — your own domain, like the rest of the email.'}\n </Typography>\n {load.status === 'loading' ? <OutreachLoading label=\"Loading link domains…\" /> : null}\n {load.status === 'error' || load.status === 'refused' ? (\n <OutreachLoadProblem status={load.status} what=\"the link domains\" />\n ) : null}\n {load.status === 'ready' && !load.data.domains.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Connect a mailbox on your own domain to set up its link domain.\n </Typography>\n ) : null}\n {load.status === 'ready'\n ? load.data.domains.map((row, index) => (\n <Stack key={row.domain} spacing={1.5}>\n {index > 0 ? <Divider /> : null}\n <LinkDomainRow\n row={row}\n canManage={load.data.canManage}\n busy={busy !== null}\n onAction={(action) => void change(action, row)}\n />\n </Stack>\n ))\n : null}\n </Stack>\n </CardDisplay>\n )\n}\nOutreachLinkDomainsCard.displayName = 'OutreachLinkDomainsCard'\n\nfunction LinkDomainRow(props: {\n row: OutreachLinkDomain\n canManage: boolean\n busy: boolean\n onAction: (action: OutreachLinkDomainRequest['action']) => void\n}) {\n const { row, canManage, busy, onAction } = props\n const setUp = row.status !== 'not-set-up'\n return (\n <Stack spacing={1}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 500, wordBreak: 'break-all' }}>\n {row.host}\n </Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={STATUS_COLORS[row.status]}\n label={OUTREACH_LINK_DOMAIN_STATUS_LABELS[row.status]}\n />\n </Stack>\n {row.linkPrefix ? (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ wordBreak: 'break-all' }}>\n {`Links in email from ${row.domain} read ${row.linkPrefix}…`}\n </Typography>\n ) : null}\n {row.detail && row.status !== 'verified' ? <Alert severity=\"info\">{row.detail}</Alert> : null}\n {setUp && row.status !== 'verified' && row.records.length ? (\n <Stack spacing={1} component=\"dl\" sx={{ m: 0 }}>\n {row.records.map((record) => (\n <Box\n key={`${record.type}:${record.name}:${record.value}`}\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }}\n >\n <Typography variant=\"caption\" component=\"dt\" color=\"text.secondary\">\n {`${record.type} · ${record.name}${record.required ? '' : ' · if needed'}`}\n </Typography>\n <Typography\n variant=\"body2\"\n component=\"dd\"\n sx={{ m: 0, fontFamily: 'monospace', wordBreak: 'break-all' }}\n >\n {record.value}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {record.note}\n </Typography>\n </Box>\n ))}\n </Stack>\n ) : null}\n {setUp && row.status !== 'verified' && !row.records.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Point ${row.host} at this app — a CNAME to its address, or your proxy’s — then check it.`}\n </Typography>\n ) : null}\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap' }}>\n {!setUp && canManage ? (\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={busy}\n aria-label={LINK_DOMAIN_LABELS.setUp(row.host)}\n onClick={() => onAction('set-up')}\n >\n Set up\n </Button>\n ) : null}\n {setUp ? (\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={busy}\n aria-label={LINK_DOMAIN_LABELS.check(row.host)}\n onClick={() => onAction('check')}\n >\n Check\n </Button>\n ) : null}\n {setUp && canManage ? (\n <Button\n color=\"error\"\n size=\"small\"\n disabled={busy}\n aria-label={LINK_DOMAIN_LABELS.remove(row.host)}\n onClick={() => onAction('remove')}\n >\n Remove\n </Button>\n ) : null}\n </Stack>\n </Stack>\n )\n}\n\nexport default OutreachLinkDomainsCard\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","useSnackbar","Alert","Box","Button","Chip","Divider","Stack","Typography","useCallback","useEffect","useState","OutreachLoading","OutreachLoadProblem","OutreachRouteError","useOutreachApi","OUTREACH_LINK_DOMAIN_STATUS_LABELS","requested","verified","failed","STATUS_COLORS","LINK_DOMAIN_LABELS","setUp","host","check","remove","OutreachLinkDomainsCard","props","orgId","api","enqueueSnackbar","confirm","load","setLoad","status","busy","setBusy","undefined","current","readLinkDomains","then","data","catch","error","refused","change","action","row","confirmed","title","description","confirmationText","confirmationButtonProps","color","domain","answer","changeLinkDomain","now","domains","find","entry","detail","variant","message","allowDuplicate","header","help","anchor","contentGutterX","contentGutterY","spacing","label","what","length","map","index","LinkDomainRow","canManage","onAction","displayName","direction","sx","alignItems","flexWrap","fontWeight","wordBreak","size","linkPrefix","severity","records","component","m","record","border","borderColor","borderRadius","p","type","name","required","fontFamily","value","note","disabled","aria-label","onClick"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/link-domains-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp } from '@aglyn/aglyn'\nimport { CardDisplay, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { Alert, Box, Button, Chip, Divider, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useState } from 'react'\nimport type {\n OutreachLinkDomain,\n OutreachLinkDomainRequest,\n OutreachLinkDomainStatus,\n OutreachLinkDomainsResponse,\n} from '../model/outreach-api'\nimport { OutreachLoading, OutreachLoadProblem } from './outreach-ui'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\n\nexport interface OutreachLinkDomainsCardProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n}\n\n/** Where a link domain stands, as the card's chip says it. */\nexport const OUTREACH_LINK_DOMAIN_STATUS_LABELS: Record<OutreachLinkDomainStatus, string> = {\n 'not-set-up': 'Not set up',\n requested: 'Not added yet',\n 'records-issued': 'Waiting for DNS',\n verified: 'Verified',\n failed: 'Not answering',\n}\n\nconst STATUS_COLORS: Record<OutreachLinkDomainStatus, 'default' | 'success' | 'warning' | 'error'> = {\n 'not-set-up': 'default',\n requested: 'warning',\n 'records-issued': 'warning',\n verified: 'success',\n failed: 'error',\n}\n\n/** Accessible names of the card's controls, spelled once for the specs. */\nexport const LINK_DOMAIN_LABELS = {\n setUp: (host: string) => `Set up ${host}`,\n check: (host: string) => `Check ${host}`,\n remove: (host: string) => `Remove ${host}`,\n} as const\n\ntype Load =\n | { status: 'loading' }\n | { status: 'ready'; data: OutreachLinkDomainsResponse }\n | { status: 'error' | 'refused' }\n\n/**\n * Sequences → Mailboxes → Link domains (AGL-3306): the host tracked links in\n * a mailbox's email point at.\n *\n * With **Count link clicks** on, every link in a sequence email is a short\n * link. It reads `https://links.<the mailbox's domain>/…` once that host is\n * set up here and verified — the same `links.` name, CNAME and check a\n * sending domain's campaign click tracking uses — and the app's own address\n * until then. A link on your own domain looks like the rest of your email.\n */\nexport function OutreachLinkDomainsCard(props: OutreachLinkDomainsCardProps) {\n const { orgId } = props\n const api = useOutreachApi(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [load, setLoad] = useState<Load>({ status: 'loading' })\n const [busy, setBusy] = useState<string | null>(null)\n\n useEffect(() => {\n if (!orgId) return undefined\n let current = true\n setLoad({ status: 'loading' })\n api\n .readLinkDomains()\n .then((data) => current && setLoad({ status: 'ready', data }))\n .catch(\n (error: Error) =>\n current &&\n setLoad({ status: error instanceof OutreachRouteError && error.refused ? 'refused' : 'error' }),\n )\n return () => {\n current = false\n }\n }, [api, orgId])\n\n const change = useCallback(\n async (action: OutreachLinkDomainRequest['action'], row: OutreachLinkDomain) => {\n if (action === 'remove') {\n const confirmed = await confirm({\n title: `Remove ${row.host}?`,\n description:\n `Links already sent on ${row.host} stop working, and new sequence emails use this ` +\n 'app’s address instead. Remove it only once no one will click those links again.',\n confirmationText: 'Remove',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n }\n setBusy(row.domain)\n try {\n const answer = await api.changeLinkDomain(action, row.domain)\n setLoad({ status: 'ready', data: answer })\n const now = answer.domains.find((entry) => entry.domain === row.domain)\n enqueueSnackbar(\n action === 'remove'\n ? `${row.host} is no longer a link domain.`\n : now?.status === 'verified'\n ? `${row.host} is verified. Tracked links now use it.`\n : action === 'set-up'\n ? `${row.host} is set up. Publish the DNS record, then check it.`\n : (now?.detail ?? `${row.host} is not verified yet.`),\n { variant: now?.status === 'verified' || action === 'remove' ? 'success' : 'info' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n },\n [api, confirm, enqueueSnackbar],\n )\n\n return (\n <CardDisplay\n header=\"Link domains\"\n help={pluginDocsHelp('sequences', { anchor: '#link-domains' })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'With Count link clicks on, the links in a sequence email are short links. Set up your ' +\n 'mailbox domain’s links host and they read links.<your domain> instead of this app’s ' +\n 'address — your own domain, like the rest of the email.'}\n </Typography>\n {load.status === 'loading' ? <OutreachLoading label=\"Loading link domains…\" /> : null}\n {load.status === 'error' || load.status === 'refused' ? (\n <OutreachLoadProblem status={load.status} what=\"the link domains\" />\n ) : null}\n {load.status === 'ready' && !load.data.domains.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Connect a mailbox on your own domain to set up its link domain.\n </Typography>\n ) : null}\n {load.status === 'ready'\n ? load.data.domains.map((row, index) => (\n <Stack key={row.domain} spacing={1.5}>\n {index > 0 ? <Divider /> : null}\n <LinkDomainRow\n row={row}\n canManage={load.data.canManage}\n busy={busy !== null}\n onAction={(action) => void change(action, row)}\n />\n </Stack>\n ))\n : null}\n </Stack>\n </CardDisplay>\n )\n}\nOutreachLinkDomainsCard.displayName = 'OutreachLinkDomainsCard'\n\nfunction LinkDomainRow(props: {\n row: OutreachLinkDomain\n canManage: boolean\n busy: boolean\n onAction: (action: OutreachLinkDomainRequest['action']) => void\n}) {\n const { row, canManage, busy, onAction } = props\n const setUp = row.status !== 'not-set-up'\n return (\n <Stack spacing={1}>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ alignItems: 'center', flexWrap: 'wrap' }}>\n <Typography variant=\"body2\" sx={{ fontWeight: 500, wordBreak: 'break-all' }}>\n {row.host}\n </Typography>\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color={STATUS_COLORS[row.status]}\n label={OUTREACH_LINK_DOMAIN_STATUS_LABELS[row.status]}\n />\n </Stack>\n {row.linkPrefix ? (\n <Typography variant=\"caption\" color=\"text.secondary\" sx={{ wordBreak: 'break-all' }}>\n {`Links in email from ${row.domain} read ${row.linkPrefix}…`}\n </Typography>\n ) : null}\n {row.detail && row.status !== 'verified' ? <Alert severity=\"info\">{row.detail}</Alert> : null}\n {setUp && row.status !== 'verified' && row.records.length ? (\n <Stack spacing={1} component=\"dl\" sx={{ m: 0 }}>\n {row.records.map((record) => (\n <Box\n key={`${record.type}:${record.name}:${record.value}`}\n sx={{ border: 1, borderColor: 'divider', borderRadius: 1, p: 1.5 }}\n >\n <Typography variant=\"caption\" component=\"dt\" color=\"text.secondary\">\n {`${record.type} · ${record.name}${record.required ? '' : ' · if needed'}`}\n </Typography>\n <Typography\n variant=\"body2\"\n component=\"dd\"\n sx={{ m: 0, fontFamily: 'monospace', wordBreak: 'break-all' }}\n >\n {record.value}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {record.note}\n </Typography>\n </Box>\n ))}\n </Stack>\n ) : null}\n {setUp && row.status !== 'verified' && !row.records.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Point ${row.host} at this app — a CNAME to its address, or your proxy’s — then check it.`}\n </Typography>\n ) : null}\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap' }}>\n {!setUp && canManage ? (\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={busy}\n aria-label={LINK_DOMAIN_LABELS.setUp(row.host)}\n onClick={() => onAction('set-up')}\n >\n Set up\n </Button>\n ) : null}\n {setUp ? (\n <Button\n variant=\"outlined\"\n size=\"small\"\n disabled={busy}\n aria-label={LINK_DOMAIN_LABELS.check(row.host)}\n onClick={() => onAction('check')}\n >\n Check\n </Button>\n ) : null}\n {setUp && canManage ? (\n <Button\n color=\"error\"\n size=\"small\"\n disabled={busy}\n aria-label={LINK_DOMAIN_LABELS.remove(row.host)}\n onClick={() => onAction('remove')}\n >\n Remove\n </Button>\n ) : null}\n </Stack>\n </Stack>\n )\n}\n\nexport default OutreachLinkDomainsCard\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","useSnackbar","Alert","Box","Button","Chip","Divider","Stack","Typography","useCallback","useEffect","useState","OutreachLoading","OutreachLoadProblem","OutreachRouteError","useOutreachApi","OUTREACH_LINK_DOMAIN_STATUS_LABELS","requested","verified","failed","STATUS_COLORS","LINK_DOMAIN_LABELS","setUp","host","check","remove","OutreachLinkDomainsCard","props","orgId","api","enqueueSnackbar","confirm","load","setLoad","status","busy","setBusy","undefined","current","readLinkDomains","then","data","catch","error","refused","change","action","row","confirmed","title","description","confirmationText","confirmationButtonProps","color","domain","answer","changeLinkDomain","now","domains","find","entry","detail","variant","message","allowDuplicate","header","help","anchor","contentGutterX","contentGutterY","spacing","label","what","length","map","index","LinkDomainRow","canManage","onAction","displayName","useFlexGap","direction","sx","alignItems","flexWrap","fontWeight","wordBreak","size","linkPrefix","severity","records","component","m","record","border","borderColor","borderRadius","p","type","name","required","fontFamily","value","note","disabled","aria-label","onClick"],"mappings":"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"}
@@ -164,6 +164,7 @@ export const CONNECT_WITH_MICROSOFT_LABEL = 'Connect with Microsoft';
164
164
  const configured = googleReady || microsoftReady;
165
165
  const canManageAll = availability.status === 'ready' && availability.canManageAll;
166
166
  const connectButtons = /*#__PURE__*/ _jsxs(Stack, {
167
+ useFlexGap: true,
167
168
  direction: "row",
168
169
  spacing: 1,
169
170
  sx: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/mailboxes-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 } from '@aglyn/aglyn'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Card, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n parseConnectReturnFragment,\n type OutreachConnectReturnError,\n type OutreachMailboxAvailability,\n} from '../mailboxes/mailbox-api'\nimport type { OutreachMailbox, OutreachMailboxProvider } from '../model/outreach.types'\nimport { OutreachLinkDomainsCard } from './link-domains-card'\nimport { MailboxCard } from './mailbox-card'\nimport { useOutreachMailboxApi } from './use-outreach-mailbox-api'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\n\nexport interface OutreachMailboxesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n /** Sends the browser to the provider's consent screen. A test seam; the page's own location by default. */\n navigate?: (url: string) => void\n}\n\nconst leavePage = (url: string) => window.location.assign(url)\n\n/** The connect buttons' accessible names, spelled once for the specs. */\nexport const CONNECT_WITH_GOOGLE_LABEL = 'Connect with Google'\nexport const CONNECT_WITH_MICROSOFT_LABEL = 'Connect with Microsoft'\n\n/** What the provider's redirect back said, in the words the panel shows. */\nfunction returnErrorMessage(reason: OutreachConnectReturnError, provider: OutreachMailboxProvider): string {\n const name = provider === 'microsoft' ? 'Microsoft' : 'Google'\n if (reason === 'access_denied') return `${name} access was not granted, so no mailbox was connected.`\n if (reason === 'expired') return 'The connection took too long. Connect the mailbox again.'\n return `${name} could not complete the connection. Connect the mailbox again.`\n}\n\n/** Whether a provider's mailboxes can be connected, from an availability answer. */\nfunction providerReady(availability: Availability, provider: OutreachMailboxProvider): boolean {\n if (availability.status !== 'ready') return false\n // An answer with no `providers` is from a deployment that connects Google only.\n return availability.providers ? availability.providers[provider] === true : provider === 'google' && availability.configured\n}\n\ntype Availability =\n | { status: 'loading' }\n | ({ status: 'ready' } & OutreachMailboxAvailability)\n | { status: 'error'; message: string }\n\ntype Returned =\n | { status: 'completing' }\n | { status: 'connected'; email: string; created: boolean; confirmedAliases: string[] }\n | { status: 'failed'; message: string }\n\n/** Takes a connect's return fragment out of the address bar without a history entry. */\nfunction stripFragment(): void {\n try {\n window.history.replaceState(null, '', window.location.pathname + window.location.search)\n } catch {\n // A restricted document: the connect still finishes.\n }\n}\n\n/** The browser's IANA timezone, for a new mailbox's sending window. */\nfunction browserTimezone(): string | undefined {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * The Mailboxes section (AGL-2978): the organization's connected Google and\n * Microsoft 365 mailboxes (AGL-3489), and the connects that add one.\n *\n * A connect leaves the console for the provider's consent screen and comes back\n * here through the callback route, which puts the authorization code in this\n * page's URL fragment. The section reads the fragment once, takes it out of\n * the address bar before anything else, and finishes the connect with the\n * member's own session — the step that refuses a connect started by anyone\n * else.\n */\nexport function OutreachMailboxesSection(props: OutreachMailboxesSectionProps) {\n const { orgId, navigate = leavePage } = props\n const { data: user } = useUser()\n // Effects key on the uid, never on the account object: a fresh object for\n // the same person must not re-run a connect's completion or a fetch.\n const uid = user?.uid ?? null\n const api = useOutreachMailboxApi(orgId)\n const listed = useOutreachMailboxes(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const [availability, setAvailability] = useState<Availability>({ status: 'loading' })\n const [returned, setReturned] = useState<Returned | null>(null)\n const [connecting, setConnecting] = useState(false)\n const returnHandled = useRef(false)\n\n // The connect's return: read once per mount, before availability, so the\n // code leaves the address bar as early as the page can take it.\n useEffect(() => {\n if (returnHandled.current || !orgId || !uid) return\n const fragment = parseConnectReturnFragment(window.location.hash)\n returnHandled.current = true\n if (!fragment) return\n stripFragment()\n if (fragment.kind === 'error') {\n setReturned({ status: 'failed', message: returnErrorMessage(fragment.reason, fragment.provider ?? 'google') })\n return\n }\n setReturned({ status: 'completing' })\n api\n .complete({ code: fragment.code, state: fragment.state, timezone: browserTimezone() })\n .then((done) =>\n setReturned({\n status: 'connected',\n email: done.mailbox.email,\n created: done.created,\n confirmedAliases: done.confirmedAliases,\n }),\n )\n .catch((error: Error) => setReturned({ status: 'failed', message: error.message }))\n }, [api, orgId, uid])\n\n useEffect(() => {\n if (!orgId || !uid) return undefined\n let current = true\n setAvailability({ status: 'loading' })\n api\n .availability()\n .then((answer) => current && setAvailability({ status: 'ready', ...answer }))\n .catch((error: Error) => current && setAvailability({ status: 'error', message: error.message }))\n return () => {\n current = false\n }\n }, [api, orgId, uid])\n\n const connect = useCallback(\n async (provider: OutreachMailboxProvider, loginHint?: string) => {\n setConnecting(true)\n try {\n navigate(await api.connect({ provider, ...(loginHint ? { loginHint } : {}) }))\n } catch (error) {\n setConnecting(false)\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n }\n },\n [api, enqueueSnackbar, navigate],\n )\n\n const googleReady = providerReady(availability, 'google')\n const microsoftReady = providerReady(availability, 'microsoft')\n const configured = googleReady || microsoftReady\n const canManageAll = availability.status === 'ready' && availability.canManageAll\n const connectButtons = (\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', justifyContent: 'center' }}>\n <Button variant=\"contained\" disabled={!googleReady || connecting} onClick={() => void connect('google')}>\n {CONNECT_WITH_GOOGLE_LABEL}\n </Button>\n <Button variant=\"outlined\" disabled={!microsoftReady || connecting} onClick={() => void connect('microsoft')}>\n {CONNECT_WITH_MICROSOFT_LABEL}\n </Button>\n </Stack>\n )\n const reconnect = (mailbox: OutreachMailbox) =>\n mailbox.provider === 'microsoft' ? connect('microsoft', mailbox.email) : connect('google')\n const unconfigured = (['google', 'microsoft'] as const).filter((provider) => !providerReady(availability, provider))\n const mine = listed.mailboxes.filter((mailbox) => mailbox.connectedByUid === uid)\n const others = listed.mailboxes.filter((mailbox) => mailbox.connectedByUid !== uid)\n\n return (\n <Stack spacing={2}>\n <CardDisplay\n header=\"Mailboxes\"\n help={pluginDocsHelp('sequences', { anchor: '#connect-a-mailbox' })}\n contentGutterX\n contentGutterY\n HeaderProps={{ action: listed.mailboxes.length ? connectButtons : undefined }}\n >\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'A sequence sends from your own Google or Microsoft 365 mailbox, so a message sits in your ' +\n 'Sent mail and a reply comes back to your inbox. Each member connects their own.'}\n </Typography>\n {availability.status === 'ready' && unconfigured.length ? (\n <Alert severity=\"info\">\n {unconfigured.length === 2\n ? 'Connecting a mailbox is not configured on this deployment.'\n : unconfigured[0] === 'microsoft'\n ? 'Connecting a Microsoft 365 mailbox is not configured on this deployment.'\n : 'Connecting a Google mailbox is not configured on this deployment.'}\n </Alert>\n ) : null}\n {availability.status === 'error' ? <Alert severity=\"error\">{availability.message}</Alert> : null}\n {returned?.status === 'completing' ? (\n <Alert severity=\"info\" icon={<CircularProgress size={18} />}>\n Finishing the connection…\n </Alert>\n ) : null}\n {returned?.status === 'connected' ? (\n <Alert severity=\"success\" onClose={() => setReturned(null)}>\n {`${returned.created ? 'Connected' : 'Reconnected'} ${returned.email}.` +\n (returned.confirmedAliases.length\n ? ` Verified ${returned.confirmedAliases.join(', ')} as your own.`\n : '')}\n </Alert>\n ) : null}\n {returned?.status === 'failed' ? (\n <Alert severity=\"error\" onClose={() => setReturned(null)}>\n {returned.message}\n </Alert>\n ) : null}\n </Stack>\n </CardDisplay>\n\n {listed.status === 'loading' ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading mailboxes…\n </Typography>\n </Stack>\n ) : null}\n {listed.status === 'error' ? (\n <Alert severity=\"error\">The mailboxes could not be loaded. Reload the page to try again.</Alert>\n ) : null}\n {listed.status === 'ready' && !listed.mailboxes.length ? (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No mailboxes connected\"\n description={\n configured\n ? 'Connect your Google or Microsoft 365 mailbox to send sequences from your own address.'\n : undefined\n }\n action={configured ? connectButtons : undefined}\n />\n </Card>\n ) : null}\n {[...mine, ...others].map((mailbox) => {\n const isMine = mailbox.connectedByUid === uid\n return (\n <MailboxCard\n key={mailbox.id}\n mailbox={mailbox}\n isMine={isMine}\n canManage={isMine || canManageAll}\n api={api}\n onReconnect={() => void reconnect(mailbox)}\n reconnecting={connecting || !providerReady(availability, mailbox.provider === 'microsoft' ? 'microsoft' : 'google')}\n />\n )\n })}\n {listed.status === 'ready' && listed.mailboxes.length ? <OutreachLinkDomainsCard orgId={orgId} /> : null}\n </Stack>\n )\n}\nOutreachMailboxesSection.displayName = 'OutreachMailboxesSection'\n\nexport default OutreachMailboxesSection\n"],"names":["pluginDocsHelp","EmptyStateComponent","CardDisplay","useSnackbar","useUser","Alert","Button","Card","CircularProgress","Stack","Typography","useCallback","useEffect","useRef","useState","parseConnectReturnFragment","OutreachLinkDomainsCard","MailboxCard","useOutreachMailboxApi","useOutreachMailboxes","leavePage","url","window","location","assign","CONNECT_WITH_GOOGLE_LABEL","CONNECT_WITH_MICROSOFT_LABEL","returnErrorMessage","reason","provider","name","providerReady","availability","status","providers","configured","stripFragment","history","replaceState","pathname","search","browserTimezone","Intl","DateTimeFormat","resolvedOptions","timeZone","undefined","OutreachMailboxesSection","props","orgId","navigate","data","user","uid","api","listed","enqueueSnackbar","setAvailability","returned","setReturned","connecting","setConnecting","returnHandled","current","fragment","hash","kind","message","complete","code","state","timezone","then","done","email","mailbox","created","confirmedAliases","catch","error","answer","connect","loginHint","variant","allowDuplicate","googleReady","microsoftReady","canManageAll","connectButtons","direction","spacing","sx","flexWrap","justifyContent","disabled","onClick","reconnect","unconfigured","filter","mine","mailboxes","connectedByUid","others","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","length","color","severity","icon","size","onClose","join","alignItems","role","label","description","map","isMine","canManage","onReconnect","reconnecting","id","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/mailboxes-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 } from '@aglyn/aglyn'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { Alert, Button, Card, CircularProgress, Stack, Typography } from '@mui/material'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n parseConnectReturnFragment,\n type OutreachConnectReturnError,\n type OutreachMailboxAvailability,\n} from '../mailboxes/mailbox-api'\nimport type { OutreachMailbox, OutreachMailboxProvider } from '../model/outreach.types'\nimport { OutreachLinkDomainsCard } from './link-domains-card'\nimport { MailboxCard } from './mailbox-card'\nimport { useOutreachMailboxApi } from './use-outreach-mailbox-api'\nimport { useOutreachMailboxes } from './use-outreach-mailboxes'\n\nexport interface OutreachMailboxesSectionProps {\n /** The organization the hub is mounted under; `null` until the shell knows it. */\n orgId: string | null\n /** Sends the browser to the provider's consent screen. A test seam; the page's own location by default. */\n navigate?: (url: string) => void\n}\n\nconst leavePage = (url: string) => window.location.assign(url)\n\n/** The connect buttons' accessible names, spelled once for the specs. */\nexport const CONNECT_WITH_GOOGLE_LABEL = 'Connect with Google'\nexport const CONNECT_WITH_MICROSOFT_LABEL = 'Connect with Microsoft'\n\n/** What the provider's redirect back said, in the words the panel shows. */\nfunction returnErrorMessage(reason: OutreachConnectReturnError, provider: OutreachMailboxProvider): string {\n const name = provider === 'microsoft' ? 'Microsoft' : 'Google'\n if (reason === 'access_denied') return `${name} access was not granted, so no mailbox was connected.`\n if (reason === 'expired') return 'The connection took too long. Connect the mailbox again.'\n return `${name} could not complete the connection. Connect the mailbox again.`\n}\n\n/** Whether a provider's mailboxes can be connected, from an availability answer. */\nfunction providerReady(availability: Availability, provider: OutreachMailboxProvider): boolean {\n if (availability.status !== 'ready') return false\n // An answer with no `providers` is from a deployment that connects Google only.\n return availability.providers ? availability.providers[provider] === true : provider === 'google' && availability.configured\n}\n\ntype Availability =\n | { status: 'loading' }\n | ({ status: 'ready' } & OutreachMailboxAvailability)\n | { status: 'error'; message: string }\n\ntype Returned =\n | { status: 'completing' }\n | { status: 'connected'; email: string; created: boolean; confirmedAliases: string[] }\n | { status: 'failed'; message: string }\n\n/** Takes a connect's return fragment out of the address bar without a history entry. */\nfunction stripFragment(): void {\n try {\n window.history.replaceState(null, '', window.location.pathname + window.location.search)\n } catch {\n // A restricted document: the connect still finishes.\n }\n}\n\n/** The browser's IANA timezone, for a new mailbox's sending window. */\nfunction browserTimezone(): string | undefined {\n try {\n return Intl.DateTimeFormat().resolvedOptions().timeZone || undefined\n } catch {\n return undefined\n }\n}\n\n/**\n * The Mailboxes section (AGL-2978): the organization's connected Google and\n * Microsoft 365 mailboxes (AGL-3489), and the connects that add one.\n *\n * A connect leaves the console for the provider's consent screen and comes back\n * here through the callback route, which puts the authorization code in this\n * page's URL fragment. The section reads the fragment once, takes it out of\n * the address bar before anything else, and finishes the connect with the\n * member's own session — the step that refuses a connect started by anyone\n * else.\n */\nexport function OutreachMailboxesSection(props: OutreachMailboxesSectionProps) {\n const { orgId, navigate = leavePage } = props\n const { data: user } = useUser()\n // Effects key on the uid, never on the account object: a fresh object for\n // the same person must not re-run a connect's completion or a fetch.\n const uid = user?.uid ?? null\n const api = useOutreachMailboxApi(orgId)\n const listed = useOutreachMailboxes(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const [availability, setAvailability] = useState<Availability>({ status: 'loading' })\n const [returned, setReturned] = useState<Returned | null>(null)\n const [connecting, setConnecting] = useState(false)\n const returnHandled = useRef(false)\n\n // The connect's return: read once per mount, before availability, so the\n // code leaves the address bar as early as the page can take it.\n useEffect(() => {\n if (returnHandled.current || !orgId || !uid) return\n const fragment = parseConnectReturnFragment(window.location.hash)\n returnHandled.current = true\n if (!fragment) return\n stripFragment()\n if (fragment.kind === 'error') {\n setReturned({ status: 'failed', message: returnErrorMessage(fragment.reason, fragment.provider ?? 'google') })\n return\n }\n setReturned({ status: 'completing' })\n api\n .complete({ code: fragment.code, state: fragment.state, timezone: browserTimezone() })\n .then((done) =>\n setReturned({\n status: 'connected',\n email: done.mailbox.email,\n created: done.created,\n confirmedAliases: done.confirmedAliases,\n }),\n )\n .catch((error: Error) => setReturned({ status: 'failed', message: error.message }))\n }, [api, orgId, uid])\n\n useEffect(() => {\n if (!orgId || !uid) return undefined\n let current = true\n setAvailability({ status: 'loading' })\n api\n .availability()\n .then((answer) => current && setAvailability({ status: 'ready', ...answer }))\n .catch((error: Error) => current && setAvailability({ status: 'error', message: error.message }))\n return () => {\n current = false\n }\n }, [api, orgId, uid])\n\n const connect = useCallback(\n async (provider: OutreachMailboxProvider, loginHint?: string) => {\n setConnecting(true)\n try {\n navigate(await api.connect({ provider, ...(loginHint ? { loginHint } : {}) }))\n } catch (error) {\n setConnecting(false)\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n }\n },\n [api, enqueueSnackbar, navigate],\n )\n\n const googleReady = providerReady(availability, 'google')\n const microsoftReady = providerReady(availability, 'microsoft')\n const configured = googleReady || microsoftReady\n const canManageAll = availability.status === 'ready' && availability.canManageAll\n const connectButtons = (\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', justifyContent: 'center' }}>\n <Button variant=\"contained\" disabled={!googleReady || connecting} onClick={() => void connect('google')}>\n {CONNECT_WITH_GOOGLE_LABEL}\n </Button>\n <Button variant=\"outlined\" disabled={!microsoftReady || connecting} onClick={() => void connect('microsoft')}>\n {CONNECT_WITH_MICROSOFT_LABEL}\n </Button>\n </Stack>\n )\n const reconnect = (mailbox: OutreachMailbox) =>\n mailbox.provider === 'microsoft' ? connect('microsoft', mailbox.email) : connect('google')\n const unconfigured = (['google', 'microsoft'] as const).filter((provider) => !providerReady(availability, provider))\n const mine = listed.mailboxes.filter((mailbox) => mailbox.connectedByUid === uid)\n const others = listed.mailboxes.filter((mailbox) => mailbox.connectedByUid !== uid)\n\n return (\n <Stack spacing={2}>\n <CardDisplay\n header=\"Mailboxes\"\n help={pluginDocsHelp('sequences', { anchor: '#connect-a-mailbox' })}\n contentGutterX\n contentGutterY\n HeaderProps={{ action: listed.mailboxes.length ? connectButtons : undefined }}\n >\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'A sequence sends from your own Google or Microsoft 365 mailbox, so a message sits in your ' +\n 'Sent mail and a reply comes back to your inbox. Each member connects their own.'}\n </Typography>\n {availability.status === 'ready' && unconfigured.length ? (\n <Alert severity=\"info\">\n {unconfigured.length === 2\n ? 'Connecting a mailbox is not configured on this deployment.'\n : unconfigured[0] === 'microsoft'\n ? 'Connecting a Microsoft 365 mailbox is not configured on this deployment.'\n : 'Connecting a Google mailbox is not configured on this deployment.'}\n </Alert>\n ) : null}\n {availability.status === 'error' ? <Alert severity=\"error\">{availability.message}</Alert> : null}\n {returned?.status === 'completing' ? (\n <Alert severity=\"info\" icon={<CircularProgress size={18} />}>\n Finishing the connection…\n </Alert>\n ) : null}\n {returned?.status === 'connected' ? (\n <Alert severity=\"success\" onClose={() => setReturned(null)}>\n {`${returned.created ? 'Connected' : 'Reconnected'} ${returned.email}.` +\n (returned.confirmedAliases.length\n ? ` Verified ${returned.confirmedAliases.join(', ')} as your own.`\n : '')}\n </Alert>\n ) : null}\n {returned?.status === 'failed' ? (\n <Alert severity=\"error\" onClose={() => setReturned(null)}>\n {returned.message}\n </Alert>\n ) : null}\n </Stack>\n </CardDisplay>\n\n {listed.status === 'loading' ? (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }} role=\"status\">\n <CircularProgress size={18} />\n <Typography variant=\"body2\" color=\"text.secondary\">\n Loading mailboxes…\n </Typography>\n </Stack>\n ) : null}\n {listed.status === 'error' ? (\n <Alert severity=\"error\">The mailboxes could not be loaded. Reload the page to try again.</Alert>\n ) : null}\n {listed.status === 'ready' && !listed.mailboxes.length ? (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"No mailboxes connected\"\n description={\n configured\n ? 'Connect your Google or Microsoft 365 mailbox to send sequences from your own address.'\n : undefined\n }\n action={configured ? connectButtons : undefined}\n />\n </Card>\n ) : null}\n {[...mine, ...others].map((mailbox) => {\n const isMine = mailbox.connectedByUid === uid\n return (\n <MailboxCard\n key={mailbox.id}\n mailbox={mailbox}\n isMine={isMine}\n canManage={isMine || canManageAll}\n api={api}\n onReconnect={() => void reconnect(mailbox)}\n reconnecting={connecting || !providerReady(availability, mailbox.provider === 'microsoft' ? 'microsoft' : 'google')}\n />\n )\n })}\n {listed.status === 'ready' && listed.mailboxes.length ? <OutreachLinkDomainsCard orgId={orgId} /> : null}\n </Stack>\n )\n}\nOutreachMailboxesSection.displayName = 'OutreachMailboxesSection'\n\nexport default OutreachMailboxesSection\n"],"names":["pluginDocsHelp","EmptyStateComponent","CardDisplay","useSnackbar","useUser","Alert","Button","Card","CircularProgress","Stack","Typography","useCallback","useEffect","useRef","useState","parseConnectReturnFragment","OutreachLinkDomainsCard","MailboxCard","useOutreachMailboxApi","useOutreachMailboxes","leavePage","url","window","location","assign","CONNECT_WITH_GOOGLE_LABEL","CONNECT_WITH_MICROSOFT_LABEL","returnErrorMessage","reason","provider","name","providerReady","availability","status","providers","configured","stripFragment","history","replaceState","pathname","search","browserTimezone","Intl","DateTimeFormat","resolvedOptions","timeZone","undefined","OutreachMailboxesSection","props","orgId","navigate","data","user","uid","api","listed","enqueueSnackbar","setAvailability","returned","setReturned","connecting","setConnecting","returnHandled","current","fragment","hash","kind","message","complete","code","state","timezone","then","done","email","mailbox","created","confirmedAliases","catch","error","answer","connect","loginHint","variant","allowDuplicate","googleReady","microsoftReady","canManageAll","connectButtons","useFlexGap","direction","spacing","sx","flexWrap","justifyContent","disabled","onClick","reconnect","unconfigured","filter","mine","mailboxes","connectedByUid","others","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","length","color","severity","icon","size","onClose","join","alignItems","role","label","description","map","isMine","canManage","onReconnect","reconnecting","id","displayName"],"mappings":"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"}
@@ -297,6 +297,7 @@ import { useOutreachSequence, useOutreachSequenceLinks } from "./use-outreach-da
297
297
  ]
298
298
  }),
299
299
  /*#__PURE__*/ _jsxs(Stack, {
300
+ useFlexGap: true,
300
301
  direction: "row",
301
302
  spacing: 1,
302
303
  sx: {