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

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.
@@ -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: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-detail.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport {\n mdiAccountMultiplePlusOutline,\n mdiChevronLeft,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Card,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { useMemo, useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport type { OutreachEnrollment } from '../model/outreach.types'\nimport { outreachSequenceLinkReport } from '../model/sequence-report'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport {\n outreachClauseLivesInUrl,\n outreachEnrollmentFilterSearch,\n outreachEnrollmentsFilterHref,\n outreachEnrollmentUrlClauses,\n} from './enrollment-filter-params'\nimport { OutreachEnrollmentsTable } from './enrollments-table'\nimport {\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { OutreachSequenceReportCard } from './sequence-report-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachSequence,\n useOutreachSequenceLinks,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'The provider stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** One person's page (AGL-3332): `…/sequences/{id}/enrollments/{enrollmentId}`. */\nexport function outreachEnrollmentPath(\n sectionPath: string,\n sequenceId: string,\n enrollmentId: string,\n): string {\n return `${sectionPath}/${sequenceId}/enrollments/${encodeURIComponent(enrollmentId)}`\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n // One document however large the sequence, and read on both tabs, because\n // the report card sits above them (AGL-3239).\n const links = useOutreachSequenceLinks(orgId, sequenceId)\n const [busy, setBusy] = useState(false)\n const [refusal, setRefusal] = useState<{\n message: string\n issues: OutreachSequenceIssue[]\n } | null>(null)\n const [confirming, setConfirming] = useState<'archive' | 'delete' | null>(\n null,\n )\n const [enrolling, setEnrolling] = useState(false)\n\n /*\n * The enrollments table's filter (AGL-3332). \"Who clicked\" and \"who\n * followed this destination\" live in the URL, where the Results card\n * sets them and where back from a person finds them again; every other\n * clause the panel sets is the page's own.\n */\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const search = searchParams?.toString() ?? ''\n const urlClauses = useMemo(\n () => outreachEnrollmentUrlClauses(new URLSearchParams(search)),\n [search],\n )\n const [ownClauses, setOwnClauses] = useState<ListFilterClause[]>([])\n const clauses = useMemo(() => [...ownClauses, ...urlClauses], [ownClauses, urlClauses])\n const setClauses = (next: ListFilterClause[]) => {\n setOwnClauses(next.filter((clause) => !outreachClauseLivesInUrl(clause)))\n const nextSearch = outreachEnrollmentFilterSearch(next, search)\n if (nextSearch !== search) {\n router.replace(nextSearch ? `${pathname}?${nextSearch}` : pathname, { scroll: false })\n }\n }\n const linkOptions = useMemo(\n () =>\n outreachSequenceLinkReport(\n links.status === 'ready' ? (links.data ?? undefined) : undefined,\n ).rows.map((row) => ({ value: row.url, label: row.url })),\n [links],\n )\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const enrollmentsPath = `${detailPath}/enrollments`\n const enrollmentHref = (enrollment: OutreachEnrollment) =>\n outreachEnrollmentPath(sectionPath, sequenceId, enrollment.id)\n const back = (\n <OutreachLink href={sectionPath}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n component=\"span\"\n sx={{ alignItems: 'center', display: 'inline-flex' }}\n >\n <MdiIcon path={mdiChevronLeft.path} fontSize=\"small\" />\n <span>Sequences</span>\n </Stack>\n </OutreachLink>\n )\n\n if (loaded.status === 'loading')\n return <OutreachLoading label=\"Loading the sequence…\" />\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return <OutreachLoadProblem status={loaded.status} what=\"sequence\" />\n }\n if (!sequence) {\n return (\n <Stack spacing={2}>\n {back}\n <Card variant=\"outlined\">\n <EmptyStateComponent label=\"This sequence no longer exists\" />\n </Card>\n </Stack>\n )\n }\n\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === sequence.mailboxId) ??\n null\n const mailboxState =\n props.mailboxes.status === 'ready'\n ? outreachSequenceMailboxState(sequence.mailboxId, mailbox)\n : 'sending'\n const activatable = sequence.status === 'draft' || sequence.status === 'paused'\n const blockers = activatable\n ? outreachActivationBlockers({\n sequence,\n mailboxes: props.mailboxes,\n settings: props.settings,\n paths: {\n steps: detailPath,\n mailboxes: props.mailboxesPath,\n compliance: props.compliancePath,\n },\n })\n : []\n // Enrolling onto a mailbox that is gone is refused by the route; a paused\n // one only makes the new people wait with everyone else.\n const canEnroll =\n sequence.status === 'active' &&\n mailboxState !== 'gone' &&\n mailboxState !== 'none'\n const act = async (action: OutreachSequenceAction) => {\n setBusy(true)\n setRefusal(null)\n try {\n const answer = await api.setSequenceStatus(sequence.id, action)\n enqueueSnackbar(\n action === 'archive' && answer.stoppedEnrollments\n ? `${ACTIONS.archive.done} ${answer.stoppedEnrollments} ${answer.stoppedEnrollments === 1 ? 'person was' : 'people were'} stopped.`\n : ACTIONS[action].done,\n { variant: 'success' },\n )\n } catch (error) {\n setRefusal({\n message: (error as Error).message,\n issues:\n error instanceof OutreachRouteError\n ? error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n )\n : [],\n })\n } finally {\n setBusy(false)\n }\n }\n const remove = async () => {\n setBusy(true)\n setRefusal(null)\n try {\n await api.deleteSequence(sequence.id)\n enqueueSnackbar('Sequence deleted.', { variant: 'success' })\n navigate(sectionPath)\n } catch (error) {\n setRefusal({ message: (error as Error).message, issues: [] })\n setBusy(false)\n }\n }\n\n const enrollButton = (\n <Button\n variant=\"contained\"\n startIcon={\n <MdiIcon path={mdiAccountMultiplePlusOutline.path} fontSize=\"small\" />\n }\n disabled={!canEnroll}\n onClick={() => setEnrolling(true)}\n >\n Enroll people\n </Button>\n )\n\n return (\n <Stack spacing={2}>\n {back}\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n spacing={1.5}\n sx={{ alignItems: { md: 'center' }, justifyContent: 'space-between' }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Typography\n variant=\"h5\"\n component=\"h2\"\n sx={{ wordBreak: 'break-word' }}\n >\n {sequence.name}\n </Typography>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n {activatable ? (\n <Button\n variant=\"outlined\"\n disabled={busy || blockers.length > 0}\n onClick={() => void act('activate')}\n >\n {ACTIONS.activate.label}\n </Button>\n ) : null}\n {sequence.status === 'active' ? (\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void act('pause')}\n >\n {ACTIONS.pause.label}\n </Button>\n ) : null}\n {sequence.status !== 'archived' ? (\n <Button\n variant=\"outlined\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('archive')}\n >\n {ACTIONS.archive.label}\n </Button>\n ) : null}\n {sequence.status === 'draft' ? (\n <Button\n variant=\"text\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('delete')}\n >\n Delete\n </Button>\n ) : null}\n {sequence.status === 'active' ? enrollButton : null}\n </Stack>\n </Stack>\n\n {refusal ? (\n <Alert severity=\"error\" onClose={() => setRefusal(null)}>\n <Stack spacing={0.5}>\n <span>{refusal.message}</span>\n {refusal.issues.length > 1 ? (\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {refusal.issues.slice(1).map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n ) : null}\n {blockers.length ? (\n <Alert severity=\"warning\" data-testid=\"outreach-activation-blockers\">\n <Stack spacing={0.5}>\n <span>\n {sequence.status === 'draft'\n ? 'A draft sends nothing, and this one can’t be activated yet:'\n : 'This sequence can’t be activated again yet:'}\n </span>\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {blockers.map((blocker) => (\n <li key={blocker.message}>\n {blocker.message}{' '}\n <OutreachLink href={blocker.href}>{blocker.linkLabel}</OutreachLink>\n </li>\n ))}\n </Stack>\n </Stack>\n </Alert>\n ) : sequence.status === 'draft' ? (\n <Alert severity=\"info\">\n A draft sends nothing. Activate it to enroll people.\n </Alert>\n ) : null}\n {sequence.status === 'active' && mailboxState !== 'sending' ? (\n <Alert severity=\"warning\">\n {NOT_SENDING[mailboxState]}{' '}\n <OutreachLink href={props.mailboxesPath}>Open Mailboxes</OutreachLink>\n </Alert>\n ) : null}\n\n {/*\n * Above the tabs, not inside one (AGL-3239): what a sequence is doing\n * is the question a rep opens it with, and putting it behind a tab\n * makes the steps the first answer to \"how is this going\".\n *\n * A draft has sent nothing and measured nothing, so it gets no card\n * rather than a row of zeroes.\n */}\n {sequence.status === 'draft' ? null : (\n <OutreachSequenceReportCard\n sequence={sequence}\n links={links}\n timeZone={mailbox?.timezone ?? null}\n // Each leads to the people behind it, on the Enrollments tab's own\n // filter (AGL-3332).\n onShowClickers={() =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { clicked: true }))\n }\n onShowLink={(url) =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { link: url }))\n }\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : enrollmentsPath)\n }\n aria-label=\"Sequence\"\n >\n <Tab value=\"steps\" label=\"Steps\" />\n <Tab value=\"enrollments\" label=\"Enrollments\" />\n </Tabs>\n\n {props.tab === 'steps' ? (\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n orgId={orgId}\n sequenceId={sequence.id}\n steps={sequence.steps}\n // A sequence that counted clicks once has them to show, whether or\n // not it counts them now.\n trackClicks={sequence.settings.trackClicks || sequence.stats?.clickTracked === true}\n // The same for opens (AGL-3488).\n countOpens={sequence.settings.countOpens || sequence.stats?.openTracked === true}\n timeZone={mailbox?.timezone ?? null}\n mailbox={props.mailboxes.status === 'ready' ? mailbox : undefined}\n sequence={sequence}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n onOpen={(enrollment) => navigate(enrollmentHref(enrollment))}\n hrefFor={enrollmentHref}\n clauses={clauses}\n onClausesChange={setClauses}\n linkOptions={linkOptions}\n />\n )}\n\n <OutreachEnrollDialog\n open={enrolling}\n onClose={() => setEnrolling(false)}\n orgId={orgId}\n sequence={sequence}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n uid={user?.uid ?? null}\n api={api}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>\n {confirming === 'delete'\n ? 'Delete this draft?'\n : 'Archive this sequence?'}\n </DialogTitle>\n <DialogContent>\n <DialogContentText>\n {confirming === 'delete'\n ? 'It is removed for good. Nobody was ever enrolled in it.'\n : 'Archiving is final. Everyone still in it is stopped, and it takes no one new.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const chosen = confirming\n setConfirming(null)\n if (chosen === 'delete') void remove()\n else void act('archive')\n }}\n >\n {confirming === 'delete' ? 'Delete' : 'Archive'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nOutreachSequenceDetail.displayName = 'OutreachSequenceDetail'\n\nexport default OutreachSequenceDetail\n"],"names":["consentGroupForHost","mdiAccountMultiplePlusOutline","mdiChevronLeft","MdiIcon","useSnackbar","useUser","Alert","Button","Card","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","Tab","Tabs","Typography","EmptyStateComponent","usePathname","useRouter","useSearchParams","useMemo","useState","validateOutreachOrgSettings","outreachSequenceLinkReport","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","outreachClauseLivesInUrl","outreachEnrollmentFilterSearch","outreachEnrollmentsFilterHref","outreachEnrollmentUrlClauses","OutreachEnrollmentsTable","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceEditor","OutreachSequenceReportCard","OutreachRouteError","useOutreachApi","useOutreachSequence","useOutreachSequenceLinks","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","outreachEnrollmentPath","sectionPath","sequenceId","enrollmentId","encodeURIComponent","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","api","navigate","enqueueSnackbar","data","user","loaded","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","router","pathname","searchParams","search","toString","urlClauses","URLSearchParams","ownClauses","setOwnClauses","clauses","setClauses","next","filter","clause","nextSearch","replace","scroll","linkOptions","undefined","rows","map","row","value","url","detailPath","enrollmentsPath","enrollmentHref","enrollment","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","li","data-testid","blocker","timeZone","timezone","onShowClickers","clicked","onShowLink","link","tab","onChange","_event","aria-label","orgMount","org","onSaved","trackClicks","stats","clickTracked","countOpens","openTracked","enrollAction","onOpen","hrefFor","onClausesChange","open","contactGroupId","hostId","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-detail.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport type { ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport {\n mdiAccountMultiplePlusOutline,\n mdiChevronLeft,\n} from '@aglyn/shared-data-mdi'\nimport { MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Card,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n Tab,\n Tabs,\n Typography,\n} from '@mui/material'\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport type { ListFilterClause } from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport { usePathname, useRouter, useSearchParams } from 'next/navigation'\nimport { useMemo, useState } from 'react'\nimport { validateOutreachOrgSettings } from '../engine/sequence-validation'\nimport type { OutreachSequenceAction } from '../model/outreach-api'\nimport type { OutreachEnrollment } from '../model/outreach.types'\nimport { outreachSequenceLinkReport } from '../model/sequence-report'\nimport {\n outreachMailboxActivationIssue,\n outreachSequenceMailboxState,\n type OutreachSequenceIssue,\n type OutreachSequenceMailboxState,\n} from '../model/sequence-draft'\nimport { OutreachEnrollDialog } from './enroll-dialog'\nimport {\n outreachClauseLivesInUrl,\n outreachEnrollmentFilterSearch,\n outreachEnrollmentsFilterHref,\n outreachEnrollmentUrlClauses,\n} from './enrollment-filter-params'\nimport { OutreachEnrollmentsTable } from './enrollments-table'\nimport {\n OutreachLink,\n OutreachLoading,\n OutreachLoadProblem,\n OutreachSequenceStatusChip,\n useOutreachNavigate,\n} from './outreach-ui'\nimport { OutreachSequenceEditor } from './sequence-editor'\nimport { OutreachSequenceReportCard } from './sequence-report-card'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport {\n useOutreachSequence,\n useOutreachSequenceLinks,\n} from './use-outreach-data'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport type OutreachSequenceTab = 'steps' | 'enrollments'\n\nexport interface OutreachSequenceDetailProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document the shell loaded: whose consent groups name a site's contacts. */\n org?: Record<string, unknown> | null\n /** Where the Sequences section lives: `/[orgSlug]/outreach/sequences`. */\n sectionPath: string\n sequenceId: string\n tab: OutreachSequenceTab\n settings: OutreachSettingsLoad\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section: where a mailbox is connected, resumed or reconnected. */\n mailboxesPath: string\n /** The Compliance section: where the footer's legal name and address are set. */\n compliancePath: string\n}\n\n/** Something activation would refuse, and where it is put right. */\nexport interface OutreachActivationBlocker {\n message: string\n href: string\n linkLabel: string\n}\n\n/**\n * What stops a sequence being activated, read before anyone clicks: its\n * mailbox not sending, and a footer the organization cannot write yet —\n * each in the words the status route refuses with. Only a finished read\n * says something is missing, so nothing is claimed while one is loading.\n */\nexport function outreachActivationBlockers(input: {\n sequence: { mailboxId: string }\n mailboxes: OutreachMailboxesResult\n settings: OutreachSettingsLoad\n paths: { steps: string; mailboxes: string; compliance: string }\n}): OutreachActivationBlocker[] {\n const blockers: OutreachActivationBlocker[] = []\n if (input.mailboxes.status === 'ready') {\n const mailbox =\n input.mailboxes.mailboxes.find((entry) => entry.id === input.sequence.mailboxId) ?? null\n const issue = outreachMailboxActivationIssue(input.sequence.mailboxId, mailbox)\n if (issue) {\n blockers.push(\n issue.code === 'mailbox_not_sending'\n ? { message: issue.message, href: input.paths.mailboxes, linkLabel: 'Open Mailboxes' }\n : { message: issue.message, href: input.paths.steps, linkLabel: 'Choose a mailbox' },\n )\n }\n }\n if (input.settings.status === 'ready') {\n for (const issue of validateOutreachOrgSettings(input.settings.settings)) {\n blockers.push({ message: issue.message, href: input.paths.compliance, linkLabel: 'Open Compliance' })\n }\n }\n return blockers\n}\n\n/** What an active sequence's page says while its mailbox is not sending. */\nconst NOT_SENDING: Record<Exclude<OutreachSequenceMailboxState, 'sending'>, string> = {\n none: 'This sequence has no mailbox, so nothing is sent from it.',\n gone: 'This sequence’s mailbox is no longer connected, so nothing is sent from it.',\n paused: 'This sequence’s mailbox is paused, so nothing is sent until it is resumed.',\n reconnect_required:\n 'The provider stopped accepting this sequence’s mailbox, so nothing is sent until it is reconnected.',\n}\n\n/** One person's page (AGL-3332): `…/sequences/{id}/enrollments/{enrollmentId}`. */\nexport function outreachEnrollmentPath(\n sectionPath: string,\n sequenceId: string,\n enrollmentId: string,\n): string {\n return `${sectionPath}/${sequenceId}/enrollments/${encodeURIComponent(enrollmentId)}`\n}\n\n/** The words an action's button and its snackbar use. */\nconst ACTIONS: Record<OutreachSequenceAction, { label: string; done: string }> =\n {\n activate: { label: 'Activate', done: 'Sequence activated.' },\n pause: { label: 'Pause', done: 'Sequence paused.' },\n archive: { label: 'Archive', done: 'Sequence archived.' },\n }\n\n/**\n * One sequence (AGL-2980): its steps in the editor, the people enrolled in\n * it, and what can be done with it — activate, pause, archive, delete a\n * draft, and enroll people in an active one.\n */\nexport function OutreachSequenceDetail(props: OutreachSequenceDetailProps) {\n const { orgId, sequenceId, sectionPath } = props\n const api = useOutreachApi(orgId)\n const navigate = useOutreachNavigate()\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const loaded = useOutreachSequence(orgId, sequenceId)\n // One document however large the sequence, and read on both tabs, because\n // the report card sits above them (AGL-3239).\n const links = useOutreachSequenceLinks(orgId, sequenceId)\n const [busy, setBusy] = useState(false)\n const [refusal, setRefusal] = useState<{\n message: string\n issues: OutreachSequenceIssue[]\n } | null>(null)\n const [confirming, setConfirming] = useState<'archive' | 'delete' | null>(\n null,\n )\n const [enrolling, setEnrolling] = useState(false)\n\n /*\n * The enrollments table's filter (AGL-3332). \"Who clicked\" and \"who\n * followed this destination\" live in the URL, where the Results card\n * sets them and where back from a person finds them again; every other\n * clause the panel sets is the page's own.\n */\n const router = useRouter()\n const pathname = usePathname()\n const searchParams = useSearchParams()\n const search = searchParams?.toString() ?? ''\n const urlClauses = useMemo(\n () => outreachEnrollmentUrlClauses(new URLSearchParams(search)),\n [search],\n )\n const [ownClauses, setOwnClauses] = useState<ListFilterClause[]>([])\n const clauses = useMemo(() => [...ownClauses, ...urlClauses], [ownClauses, urlClauses])\n const setClauses = (next: ListFilterClause[]) => {\n setOwnClauses(next.filter((clause) => !outreachClauseLivesInUrl(clause)))\n const nextSearch = outreachEnrollmentFilterSearch(next, search)\n if (nextSearch !== search) {\n router.replace(nextSearch ? `${pathname}?${nextSearch}` : pathname, { scroll: false })\n }\n }\n const linkOptions = useMemo(\n () =>\n outreachSequenceLinkReport(\n links.status === 'ready' ? (links.data ?? undefined) : undefined,\n ).rows.map((row) => ({ value: row.url, label: row.url })),\n [links],\n )\n\n const sequence = loaded.data\n const detailPath = `${sectionPath}/${sequenceId}`\n const enrollmentsPath = `${detailPath}/enrollments`\n const enrollmentHref = (enrollment: OutreachEnrollment) =>\n outreachEnrollmentPath(sectionPath, sequenceId, enrollment.id)\n const back = (\n <OutreachLink href={sectionPath}>\n <Stack\n direction=\"row\"\n spacing={0.5}\n component=\"span\"\n sx={{ alignItems: 'center', display: 'inline-flex' }}\n >\n <MdiIcon path={mdiChevronLeft.path} fontSize=\"small\" />\n <span>Sequences</span>\n </Stack>\n </OutreachLink>\n )\n\n if (loaded.status === 'loading')\n return <OutreachLoading label=\"Loading the sequence…\" />\n if (loaded.status === 'error' || loaded.status === 'refused') {\n return <OutreachLoadProblem status={loaded.status} what=\"sequence\" />\n }\n if (!sequence) {\n return (\n <Stack spacing={2}>\n {back}\n <Card variant=\"outlined\">\n <EmptyStateComponent label=\"This sequence no longer exists\" />\n </Card>\n </Stack>\n )\n }\n\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === sequence.mailboxId) ??\n null\n const mailboxState =\n props.mailboxes.status === 'ready'\n ? outreachSequenceMailboxState(sequence.mailboxId, mailbox)\n : 'sending'\n const activatable = sequence.status === 'draft' || sequence.status === 'paused'\n const blockers = activatable\n ? outreachActivationBlockers({\n sequence,\n mailboxes: props.mailboxes,\n settings: props.settings,\n paths: {\n steps: detailPath,\n mailboxes: props.mailboxesPath,\n compliance: props.compliancePath,\n },\n })\n : []\n // Enrolling onto a mailbox that is gone is refused by the route; a paused\n // one only makes the new people wait with everyone else.\n const canEnroll =\n sequence.status === 'active' &&\n mailboxState !== 'gone' &&\n mailboxState !== 'none'\n const act = async (action: OutreachSequenceAction) => {\n setBusy(true)\n setRefusal(null)\n try {\n const answer = await api.setSequenceStatus(sequence.id, action)\n enqueueSnackbar(\n action === 'archive' && answer.stoppedEnrollments\n ? `${ACTIONS.archive.done} ${answer.stoppedEnrollments} ${answer.stoppedEnrollments === 1 ? 'person was' : 'people were'} stopped.`\n : ACTIONS[action].done,\n { variant: 'success' },\n )\n } catch (error) {\n setRefusal({\n message: (error as Error).message,\n issues:\n error instanceof OutreachRouteError\n ? error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n )\n : [],\n })\n } finally {\n setBusy(false)\n }\n }\n const remove = async () => {\n setBusy(true)\n setRefusal(null)\n try {\n await api.deleteSequence(sequence.id)\n enqueueSnackbar('Sequence deleted.', { variant: 'success' })\n navigate(sectionPath)\n } catch (error) {\n setRefusal({ message: (error as Error).message, issues: [] })\n setBusy(false)\n }\n }\n\n const enrollButton = (\n <Button\n variant=\"contained\"\n startIcon={\n <MdiIcon path={mdiAccountMultiplePlusOutline.path} fontSize=\"small\" />\n }\n disabled={!canEnroll}\n onClick={() => setEnrolling(true)}\n >\n Enroll people\n </Button>\n )\n\n return (\n <Stack spacing={2}>\n {back}\n <Stack\n direction={{ xs: 'column', md: 'row' }}\n spacing={1.5}\n sx={{ alignItems: { md: 'center' }, justifyContent: 'space-between' }}\n >\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center', minWidth: 0 }}\n >\n <Typography\n variant=\"h5\"\n component=\"h2\"\n sx={{ wordBreak: 'break-word' }}\n >\n {sequence.name}\n </Typography>\n <OutreachSequenceStatusChip status={sequence.status} />\n </Stack>\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', rowGap: 1 }}>\n {activatable ? (\n <Button\n variant=\"outlined\"\n disabled={busy || blockers.length > 0}\n onClick={() => void act('activate')}\n >\n {ACTIONS.activate.label}\n </Button>\n ) : null}\n {sequence.status === 'active' ? (\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void act('pause')}\n >\n {ACTIONS.pause.label}\n </Button>\n ) : null}\n {sequence.status !== 'archived' ? (\n <Button\n variant=\"outlined\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('archive')}\n >\n {ACTIONS.archive.label}\n </Button>\n ) : null}\n {sequence.status === 'draft' ? (\n <Button\n variant=\"text\"\n color=\"error\"\n disabled={busy}\n onClick={() => setConfirming('delete')}\n >\n Delete\n </Button>\n ) : null}\n {sequence.status === 'active' ? enrollButton : null}\n </Stack>\n </Stack>\n\n {refusal ? (\n <Alert severity=\"error\" onClose={() => setRefusal(null)}>\n <Stack spacing={0.5}>\n <span>{refusal.message}</span>\n {refusal.issues.length > 1 ? (\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {refusal.issues.slice(1).map((issue) => (\n <li key={`${issue.path}:${issue.code}`}>{issue.message}</li>\n ))}\n </Stack>\n ) : null}\n </Stack>\n </Alert>\n ) : null}\n {blockers.length ? (\n <Alert severity=\"warning\" data-testid=\"outreach-activation-blockers\">\n <Stack spacing={0.5}>\n <span>\n {sequence.status === 'draft'\n ? 'A draft sends nothing, and this one can’t be activated yet:'\n : 'This sequence can’t be activated again yet:'}\n </span>\n <Stack component=\"ul\" spacing={0.25} sx={{ m: 0, pl: 2.5 }}>\n {blockers.map((blocker) => (\n <li key={blocker.message}>\n {blocker.message}{' '}\n <OutreachLink href={blocker.href}>{blocker.linkLabel}</OutreachLink>\n </li>\n ))}\n </Stack>\n </Stack>\n </Alert>\n ) : sequence.status === 'draft' ? (\n <Alert severity=\"info\">\n A draft sends nothing. Activate it to enroll people.\n </Alert>\n ) : null}\n {sequence.status === 'active' && mailboxState !== 'sending' ? (\n <Alert severity=\"warning\">\n {NOT_SENDING[mailboxState]}{' '}\n <OutreachLink href={props.mailboxesPath}>Open Mailboxes</OutreachLink>\n </Alert>\n ) : null}\n\n {/*\n * Above the tabs, not inside one (AGL-3239): what a sequence is doing\n * is the question a rep opens it with, and putting it behind a tab\n * makes the steps the first answer to \"how is this going\".\n *\n * A draft has sent nothing and measured nothing, so it gets no card\n * rather than a row of zeroes.\n */}\n {sequence.status === 'draft' ? null : (\n <OutreachSequenceReportCard\n sequence={sequence}\n links={links}\n timeZone={mailbox?.timezone ?? null}\n // Each leads to the people behind it, on the Enrollments tab's own\n // filter (AGL-3332).\n onShowClickers={() =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { clicked: true }))\n }\n onShowLink={(url) =>\n navigate(outreachEnrollmentsFilterHref(enrollmentsPath, { link: url }))\n }\n />\n )}\n\n <Tabs\n value={props.tab}\n onChange={(_event, next: OutreachSequenceTab) =>\n navigate(next === 'steps' ? detailPath : enrollmentsPath)\n }\n aria-label=\"Sequence\"\n >\n <Tab value=\"steps\" label=\"Steps\" />\n <Tab value=\"enrollments\" label=\"Enrollments\" />\n </Tabs>\n\n {props.tab === 'steps' ? (\n <OutreachSequenceEditor\n orgId={orgId}\n orgMount={props.orgMount}\n org={props.org}\n sequence={sequence}\n settings={props.settings}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n onSaved={() => undefined}\n />\n ) : (\n <OutreachEnrollmentsTable\n orgId={orgId}\n sequenceId={sequence.id}\n steps={sequence.steps}\n // A sequence that counted clicks once has them to show, whether or\n // not it counts them now.\n trackClicks={sequence.settings.trackClicks || sequence.stats?.clickTracked === true}\n // The same for opens (AGL-3488).\n countOpens={sequence.settings.countOpens || sequence.stats?.openTracked === true}\n timeZone={mailbox?.timezone ?? null}\n mailbox={props.mailboxes.status === 'ready' ? mailbox : undefined}\n sequence={sequence}\n api={api}\n enrollAction={sequence.status === 'active' ? enrollButton : undefined}\n onOpen={(enrollment) => navigate(enrollmentHref(enrollment))}\n hrefFor={enrollmentHref}\n clauses={clauses}\n onClausesChange={setClauses}\n linkOptions={linkOptions}\n />\n )}\n\n <OutreachEnrollDialog\n open={enrolling}\n onClose={() => setEnrolling(false)}\n orgId={orgId}\n sequence={sequence}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n uid={user?.uid ?? null}\n api={api}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>\n {confirming === 'delete'\n ? 'Delete this draft?'\n : 'Archive this sequence?'}\n </DialogTitle>\n <DialogContent>\n <DialogContentText>\n {confirming === 'delete'\n ? 'It is removed for good. Nobody was ever enrolled in it.'\n : 'Archiving is final. Everyone still in it is stopped, and it takes no one new.'}\n </DialogContentText>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const chosen = confirming\n setConfirming(null)\n if (chosen === 'delete') void remove()\n else void act('archive')\n }}\n >\n {confirming === 'delete' ? 'Delete' : 'Archive'}\n </Button>\n </DialogActions>\n </Dialog>\n </Stack>\n )\n}\nOutreachSequenceDetail.displayName = 'OutreachSequenceDetail'\n\nexport default OutreachSequenceDetail\n"],"names":["consentGroupForHost","mdiAccountMultiplePlusOutline","mdiChevronLeft","MdiIcon","useSnackbar","useUser","Alert","Button","Card","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","Tab","Tabs","Typography","EmptyStateComponent","usePathname","useRouter","useSearchParams","useMemo","useState","validateOutreachOrgSettings","outreachSequenceLinkReport","outreachMailboxActivationIssue","outreachSequenceMailboxState","OutreachEnrollDialog","outreachClauseLivesInUrl","outreachEnrollmentFilterSearch","outreachEnrollmentsFilterHref","outreachEnrollmentUrlClauses","OutreachEnrollmentsTable","OutreachLink","OutreachLoading","OutreachLoadProblem","OutreachSequenceStatusChip","useOutreachNavigate","OutreachSequenceEditor","OutreachSequenceReportCard","OutreachRouteError","useOutreachApi","useOutreachSequence","useOutreachSequenceLinks","outreachActivationBlockers","input","blockers","mailboxes","status","mailbox","find","entry","id","sequence","mailboxId","issue","push","code","message","href","paths","linkLabel","steps","settings","compliance","NOT_SENDING","none","gone","paused","reconnect_required","outreachEnrollmentPath","sectionPath","sequenceId","enrollmentId","encodeURIComponent","ACTIONS","activate","label","done","pause","archive","OutreachSequenceDetail","props","orgId","api","navigate","enqueueSnackbar","data","user","loaded","links","busy","setBusy","refusal","setRefusal","confirming","setConfirming","enrolling","setEnrolling","router","pathname","searchParams","search","toString","urlClauses","URLSearchParams","ownClauses","setOwnClauses","clauses","setClauses","next","filter","clause","nextSearch","replace","scroll","linkOptions","undefined","rows","map","row","value","url","detailPath","enrollmentsPath","enrollmentHref","enrollment","back","direction","spacing","component","sx","alignItems","display","path","fontSize","span","what","variant","mailboxState","activatable","mailboxesPath","compliancePath","canEnroll","act","action","answer","setSequenceStatus","stoppedEnrollments","error","issues","remove","deleteSequence","enrollButton","startIcon","disabled","onClick","xs","md","justifyContent","minWidth","wordBreak","name","useFlexGap","flexWrap","rowGap","length","color","severity","onClose","m","pl","slice","li","data-testid","blocker","timeZone","timezone","onShowClickers","clicked","onShowLink","link","tab","onChange","_event","aria-label","orgMount","org","onSaved","trackClicks","stats","clickTracked","countOpens","openTracked","enrollAction","onOpen","hrefFor","onClausesChange","open","contactGroupId","hostId","groupId","uid","Boolean","fullWidth","maxWidth","chosen","displayName"],"mappings":"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"}
@@ -111,6 +111,7 @@ export function OutreachSequenceReportCard(props) {
111
111
  children: "Results"
112
112
  }),
113
113
  /*#__PURE__*/ _jsxs(Stack, {
114
+ useFlexGap: true,
114
115
  direction: "row",
115
116
  spacing: 3,
116
117
  sx: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-report-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 type { SendRate } from '@aglyn/shared-ui-email-campaigns/model/send-report'\nimport {\n Alert,\n ButtonBase,\n Card,\n CardContent,\n Divider,\n Link,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport type { ReactNode } from 'react'\n// Not a raw MUI `Table`: a destination is a long string, and a raw table is\n// cut off by the card around it with nothing able to scroll to what is past\n// the edge (AGL-3045).\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport type { OutreachSequence } from '../model/outreach.types'\nimport {\n outreachSequenceLinkReport,\n outreachSequenceReport,\n} from '../model/sequence-report'\nimport { formatOutreachTime, OutreachLoadProblem } from './outreach-ui'\nimport type { OutreachSequenceLinksLoad } from './use-outreach-data'\n\n/**\n * WHAT A SEQUENCE MEASURED (AGL-3239).\n *\n * Four figures and one rate — and, for a sequence that counts opens\n * (AGL-3395), the opens beside them — and the sentences saying what each of\n * them is not. The decisions are all in `model/sequence-report.ts`; this renders\n * them and adds no arithmetic of its own — in particular it never divides,\n * so there is no second place a percentage can be computed differently from\n * the way a campaign's report computes it.\n *\n * The caveats are rendered as prominently as the numbers rather than as\n * small print. \"Opens aren't measured\" is the thing a reader coming from\n * the campaign report most needs told, and a blank where an open rate would\n * be says the opposite of what is true.\n */\n\nexport interface OutreachSequenceReportCardProps {\n sequence: Pick<OutreachSequence, 'stats' | 'settings'>\n links: OutreachSequenceLinksLoad\n /** The mailbox's zone, which times are read in; `null` for the reader's own. */\n timeZone: string | null\n /** Lists the people who clicked, on the Enrollments tab (AGL-3332). */\n onShowClickers?: () => void\n /** Lists the people who followed one destination, on the Enrollments tab (AGL-3332). */\n onShowLink?: (url: string) => void\n}\n\n/**\n * One figure, with what it counts under it — and where it leads, when it\n * leads somewhere. The sequence's Results and one person's page (AGL-3332)\n * draw their numbers alike.\n */\nexport function OutreachFigure(props: {\n label: string\n value: string\n hint?: string\n /** Opens the list behind the figure; named for a screen reader by `action`. */\n onClick?: () => void\n action?: string\n}) {\n const body: ReactNode = (\n <>\n <Typography variant=\"h5\" component=\"p\" color={props.onClick ? 'primary' : undefined}>\n {props.value}\n </Typography>\n <Typography variant=\"body2\">{props.label}</Typography>\n {props.hint ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.hint}\n </Typography>\n ) : null}\n </>\n )\n if (!props.onClick) {\n return (\n <Stack spacing={0} sx={{ minWidth: 110 }}>\n {body}\n </Stack>\n )\n }\n return (\n <Tooltip title={props.action}>\n <ButtonBase\n onClick={props.onClick}\n aria-label={`${props.value} ${props.label}. ${props.action}`}\n sx={{\n minWidth: 110,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n textAlign: 'left',\n borderRadius: 1,\n px: 1,\n mx: -1,\n '&:hover, &.Mui-focusVisible': { bgcolor: 'action.hover' },\n }}\n >\n {body}\n </ButtonBase>\n </Tooltip>\n )\n}\n\n/** A whole number as the card prints one. */\nconst count = (value: number): string => value.toLocaleString()\n\n/**\n * A rate and the denominator it was taken over, or the em dash that stands\n * for a rate the model refused to take. The denominator is printed beside\n * the percentage, never dropped: \"12% of people emailed\" and \"12% of people\n * who opened\" are different claims and a bare \"12%\" is neither.\n */\nfunction rateText(rate: SendRate | null): { value: string; hint?: string } {\n if (!rate) return { value: '—' }\n return {\n value: `${(rate.value * 100).toFixed(1)}%`,\n hint: `${count(rate.numerator)} of ${count(rate.denominator)} ${rate.denominatorLabel}`,\n }\n}\n\nexport function OutreachSequenceReportCard(props: OutreachSequenceReportCardProps) {\n const report = outreachSequenceReport(\n props.sequence.stats,\n props.sequence.settings.trackClicks,\n props.sequence.settings.countOpens,\n )\n const clickRate = rateText(report.rates.click)\n const openRate = rateText(report.rates.open)\n const links = outreachSequenceLinkReport(\n props.links.status === 'ready' ? (props.links.data ?? undefined) : undefined,\n )\n\n return (\n <Card variant=\"outlined\">\n <CardContent>\n <Stack spacing={2}>\n <Typography variant=\"h6\" component=\"h2\">\n Results\n </Typography>\n <Stack direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap', rowGap: 2 }}>\n <OutreachFigure label=\"Emails sent\" value={count(report.sent)} />\n <OutreachFigure\n label=\"People emailed\"\n value={report.people === null ? '—' : count(report.people)}\n {...(report.people === null ? { hint: 'Not counted for this sequence' } : {})}\n />\n {/*\n * Opens (AGL-3395), only for a sequence whose emails have carried\n * the tracking image: for every other one, the caveat below\n * says opens are not measured, and a figure here would say\n * otherwise.\n */}\n {report.openTracked ? (\n <>\n <OutreachFigure\n label=\"Opened\"\n value={count(report.uniqueOpens)}\n hint={\n report.opens === report.uniqueOpens ? undefined : `${count(report.opens)} opens in all`\n }\n />\n <OutreachFigure\n label=\"Open rate\"\n {...(report.opensUnmeasured\n ? { value: 'Unmeasured', hint: 'Only machines fetched the image' }\n : { value: openRate.value, hint: openRate.hint })}\n />\n {report.machineOpens > 0 ? (\n <OutreachFigure\n label=\"Machine opens\"\n value={count(report.machineOpens)}\n hint={\n report.proxyOpens > 0\n ? `Not in the rate · ${count(report.proxyOpens)} by mail proxies`\n : 'Not in the rate'\n }\n />\n ) : null}\n </>\n ) : null}\n <OutreachFigure\n label=\"Clicked\"\n value={count(report.uniqueClicks)}\n hint={\n report.clicks === report.uniqueClicks\n ? undefined\n : `${count(report.clicks)} clicks in all`\n }\n {...(props.onShowClickers && report.uniqueClicks > 0\n ? { onClick: props.onShowClickers, action: 'Show who clicked' }\n : {})}\n />\n <OutreachFigure label=\"Click rate\" value={clickRate.value} hint={clickRate.hint} />\n {report.machineClicks > 0 ? (\n <OutreachFigure\n label=\"Scanner clicks\"\n value={count(report.machineClicks)}\n hint=\"Not in the rate\"\n />\n ) : null}\n </Stack>\n {report.lastOpenAtMs === null ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Last open {formatOutreachTime(report.lastOpenAtMs, props.timeZone)}\n </Typography>\n )}\n {report.lastClickAtMs === null ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Last click {formatOutreachTime(report.lastClickAtMs, props.timeZone)}\n </Typography>\n )}\n\n {report.caveats.map((entry) => (\n <Alert key={entry.id} severity=\"info\" variant=\"outlined\">\n {entry.message}\n </Alert>\n ))}\n\n {props.links.status === 'error' || props.links.status === 'refused' ? (\n <OutreachLoadProblem status={props.links.status} what=\"the link report\" />\n ) : links.rows.length ? (\n <Stack spacing={1}>\n <Divider />\n <Typography variant=\"subtitle2\" component=\"h3\">\n Links followed\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Destination</TableCell>\n <TableCell align=\"right\">Clicks</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {links.rows.map((row) => (\n <TableRow key={row.url} hover={Boolean(props.onShowLink)}>\n <TableCell sx={{ wordBreak: 'break-all' }}>\n {props.onShowLink ? (\n <Link\n component=\"button\"\n type=\"button\"\n variant=\"body2\"\n underline=\"hover\"\n onClick={() => props.onShowLink?.(row.url)}\n aria-label={`Show who followed ${row.url}`}\n sx={{ textAlign: 'left', wordBreak: 'break-all' }}\n >\n {row.url}\n </Link>\n ) : (\n row.url\n )}\n </TableCell>\n <TableCell align=\"right\">{count(row.clicks)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.onShowLink ? 'Select a destination to see who followed it. ' : ''}\n Two links to the same page are counted as one destination, whatever\n tracking parameters follow the address.\n {links.truncated\n ? ` ${count(links.overflowClicks)} more clicks went to destinations past the ones listed.`\n : ''}\n </Typography>\n </Stack>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n )\n}\n"],"names":["Alert","ButtonBase","Card","CardContent","Divider","Link","Stack","TableBody","TableCell","TableHead","TableRow","Tooltip","Typography","ScrollTable","outreachSequenceLinkReport","outreachSequenceReport","formatOutreachTime","OutreachLoadProblem","OutreachFigure","props","body","variant","component","color","onClick","undefined","value","label","hint","spacing","sx","minWidth","title","action","aria-label","display","flexDirection","alignItems","textAlign","borderRadius","px","mx","bgcolor","count","toLocaleString","rateText","rate","toFixed","numerator","denominator","denominatorLabel","OutreachSequenceReportCard","report","sequence","stats","settings","trackClicks","countOpens","clickRate","rates","click","openRate","open","links","status","data","direction","flexWrap","rowGap","sent","people","openTracked","uniqueOpens","opens","opensUnmeasured","machineOpens","proxyOpens","uniqueClicks","clicks","onShowClickers","machineClicks","lastOpenAtMs","timeZone","lastClickAtMs","caveats","map","entry","severity","message","id","what","rows","length","size","align","row","hover","Boolean","onShowLink","wordBreak","type","underline","url","truncated","overflowClicks"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-report-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 type { SendRate } from '@aglyn/shared-ui-email-campaigns/model/send-report'\nimport {\n Alert,\n ButtonBase,\n Card,\n CardContent,\n Divider,\n Link,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Tooltip,\n Typography,\n} from '@mui/material'\nimport type { ReactNode } from 'react'\n// Not a raw MUI `Table`: a destination is a long string, and a raw table is\n// cut off by the card around it with nothing able to scroll to what is past\n// the edge (AGL-3045).\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport type { OutreachSequence } from '../model/outreach.types'\nimport {\n outreachSequenceLinkReport,\n outreachSequenceReport,\n} from '../model/sequence-report'\nimport { formatOutreachTime, OutreachLoadProblem } from './outreach-ui'\nimport type { OutreachSequenceLinksLoad } from './use-outreach-data'\n\n/**\n * WHAT A SEQUENCE MEASURED (AGL-3239).\n *\n * Four figures and one rate — and, for a sequence that counts opens\n * (AGL-3395), the opens beside them — and the sentences saying what each of\n * them is not. The decisions are all in `model/sequence-report.ts`; this renders\n * them and adds no arithmetic of its own — in particular it never divides,\n * so there is no second place a percentage can be computed differently from\n * the way a campaign's report computes it.\n *\n * The caveats are rendered as prominently as the numbers rather than as\n * small print. \"Opens aren't measured\" is the thing a reader coming from\n * the campaign report most needs told, and a blank where an open rate would\n * be says the opposite of what is true.\n */\n\nexport interface OutreachSequenceReportCardProps {\n sequence: Pick<OutreachSequence, 'stats' | 'settings'>\n links: OutreachSequenceLinksLoad\n /** The mailbox's zone, which times are read in; `null` for the reader's own. */\n timeZone: string | null\n /** Lists the people who clicked, on the Enrollments tab (AGL-3332). */\n onShowClickers?: () => void\n /** Lists the people who followed one destination, on the Enrollments tab (AGL-3332). */\n onShowLink?: (url: string) => void\n}\n\n/**\n * One figure, with what it counts under it — and where it leads, when it\n * leads somewhere. The sequence's Results and one person's page (AGL-3332)\n * draw their numbers alike.\n */\nexport function OutreachFigure(props: {\n label: string\n value: string\n hint?: string\n /** Opens the list behind the figure; named for a screen reader by `action`. */\n onClick?: () => void\n action?: string\n}) {\n const body: ReactNode = (\n <>\n <Typography variant=\"h5\" component=\"p\" color={props.onClick ? 'primary' : undefined}>\n {props.value}\n </Typography>\n <Typography variant=\"body2\">{props.label}</Typography>\n {props.hint ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.hint}\n </Typography>\n ) : null}\n </>\n )\n if (!props.onClick) {\n return (\n <Stack spacing={0} sx={{ minWidth: 110 }}>\n {body}\n </Stack>\n )\n }\n return (\n <Tooltip title={props.action}>\n <ButtonBase\n onClick={props.onClick}\n aria-label={`${props.value} ${props.label}. ${props.action}`}\n sx={{\n minWidth: 110,\n display: 'flex',\n flexDirection: 'column',\n alignItems: 'flex-start',\n textAlign: 'left',\n borderRadius: 1,\n px: 1,\n mx: -1,\n '&:hover, &.Mui-focusVisible': { bgcolor: 'action.hover' },\n }}\n >\n {body}\n </ButtonBase>\n </Tooltip>\n )\n}\n\n/** A whole number as the card prints one. */\nconst count = (value: number): string => value.toLocaleString()\n\n/**\n * A rate and the denominator it was taken over, or the em dash that stands\n * for a rate the model refused to take. The denominator is printed beside\n * the percentage, never dropped: \"12% of people emailed\" and \"12% of people\n * who opened\" are different claims and a bare \"12%\" is neither.\n */\nfunction rateText(rate: SendRate | null): { value: string; hint?: string } {\n if (!rate) return { value: '—' }\n return {\n value: `${(rate.value * 100).toFixed(1)}%`,\n hint: `${count(rate.numerator)} of ${count(rate.denominator)} ${rate.denominatorLabel}`,\n }\n}\n\nexport function OutreachSequenceReportCard(props: OutreachSequenceReportCardProps) {\n const report = outreachSequenceReport(\n props.sequence.stats,\n props.sequence.settings.trackClicks,\n props.sequence.settings.countOpens,\n )\n const clickRate = rateText(report.rates.click)\n const openRate = rateText(report.rates.open)\n const links = outreachSequenceLinkReport(\n props.links.status === 'ready' ? (props.links.data ?? undefined) : undefined,\n )\n\n return (\n <Card variant=\"outlined\">\n <CardContent>\n <Stack spacing={2}>\n <Typography variant=\"h6\" component=\"h2\">\n Results\n </Typography>\n <Stack useFlexGap direction=\"row\" spacing={3} sx={{ flexWrap: 'wrap', rowGap: 2 }}>\n <OutreachFigure label=\"Emails sent\" value={count(report.sent)} />\n <OutreachFigure\n label=\"People emailed\"\n value={report.people === null ? '—' : count(report.people)}\n {...(report.people === null ? { hint: 'Not counted for this sequence' } : {})}\n />\n {/*\n * Opens (AGL-3395), only for a sequence whose emails have carried\n * the tracking image: for every other one, the caveat below\n * says opens are not measured, and a figure here would say\n * otherwise.\n */}\n {report.openTracked ? (\n <>\n <OutreachFigure\n label=\"Opened\"\n value={count(report.uniqueOpens)}\n hint={\n report.opens === report.uniqueOpens ? undefined : `${count(report.opens)} opens in all`\n }\n />\n <OutreachFigure\n label=\"Open rate\"\n {...(report.opensUnmeasured\n ? { value: 'Unmeasured', hint: 'Only machines fetched the image' }\n : { value: openRate.value, hint: openRate.hint })}\n />\n {report.machineOpens > 0 ? (\n <OutreachFigure\n label=\"Machine opens\"\n value={count(report.machineOpens)}\n hint={\n report.proxyOpens > 0\n ? `Not in the rate · ${count(report.proxyOpens)} by mail proxies`\n : 'Not in the rate'\n }\n />\n ) : null}\n </>\n ) : null}\n <OutreachFigure\n label=\"Clicked\"\n value={count(report.uniqueClicks)}\n hint={\n report.clicks === report.uniqueClicks\n ? undefined\n : `${count(report.clicks)} clicks in all`\n }\n {...(props.onShowClickers && report.uniqueClicks > 0\n ? { onClick: props.onShowClickers, action: 'Show who clicked' }\n : {})}\n />\n <OutreachFigure label=\"Click rate\" value={clickRate.value} hint={clickRate.hint} />\n {report.machineClicks > 0 ? (\n <OutreachFigure\n label=\"Scanner clicks\"\n value={count(report.machineClicks)}\n hint=\"Not in the rate\"\n />\n ) : null}\n </Stack>\n {report.lastOpenAtMs === null ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Last open {formatOutreachTime(report.lastOpenAtMs, props.timeZone)}\n </Typography>\n )}\n {report.lastClickAtMs === null ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Last click {formatOutreachTime(report.lastClickAtMs, props.timeZone)}\n </Typography>\n )}\n\n {report.caveats.map((entry) => (\n <Alert key={entry.id} severity=\"info\" variant=\"outlined\">\n {entry.message}\n </Alert>\n ))}\n\n {props.links.status === 'error' || props.links.status === 'refused' ? (\n <OutreachLoadProblem status={props.links.status} what=\"the link report\" />\n ) : links.rows.length ? (\n <Stack spacing={1}>\n <Divider />\n <Typography variant=\"subtitle2\" component=\"h3\">\n Links followed\n </Typography>\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>Destination</TableCell>\n <TableCell align=\"right\">Clicks</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {links.rows.map((row) => (\n <TableRow key={row.url} hover={Boolean(props.onShowLink)}>\n <TableCell sx={{ wordBreak: 'break-all' }}>\n {props.onShowLink ? (\n <Link\n component=\"button\"\n type=\"button\"\n variant=\"body2\"\n underline=\"hover\"\n onClick={() => props.onShowLink?.(row.url)}\n aria-label={`Show who followed ${row.url}`}\n sx={{ textAlign: 'left', wordBreak: 'break-all' }}\n >\n {row.url}\n </Link>\n ) : (\n row.url\n )}\n </TableCell>\n <TableCell align=\"right\">{count(row.clicks)}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </ScrollTable>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {props.onShowLink ? 'Select a destination to see who followed it. ' : ''}\n Two links to the same page are counted as one destination, whatever\n tracking parameters follow the address.\n {links.truncated\n ? ` ${count(links.overflowClicks)} more clicks went to destinations past the ones listed.`\n : ''}\n </Typography>\n </Stack>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n )\n}\n"],"names":["Alert","ButtonBase","Card","CardContent","Divider","Link","Stack","TableBody","TableCell","TableHead","TableRow","Tooltip","Typography","ScrollTable","outreachSequenceLinkReport","outreachSequenceReport","formatOutreachTime","OutreachLoadProblem","OutreachFigure","props","body","variant","component","color","onClick","undefined","value","label","hint","spacing","sx","minWidth","title","action","aria-label","display","flexDirection","alignItems","textAlign","borderRadius","px","mx","bgcolor","count","toLocaleString","rateText","rate","toFixed","numerator","denominator","denominatorLabel","OutreachSequenceReportCard","report","sequence","stats","settings","trackClicks","countOpens","clickRate","rates","click","openRate","open","links","status","data","useFlexGap","direction","flexWrap","rowGap","sent","people","openTracked","uniqueOpens","opens","opensUnmeasured","machineOpens","proxyOpens","uniqueClicks","clicks","onShowClickers","machineClicks","lastOpenAtMs","timeZone","lastClickAtMs","caveats","map","entry","severity","message","id","what","rows","length","size","align","row","hover","Boolean","onShowLink","wordBreak","type","underline","url","truncated","overflowClicks"],"mappings":"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"}
@@ -305,6 +305,7 @@ const SEQUENCE_STATUS_OPTIONS = OUTREACH_SEQUENCE_STATUSES.map((status)=>({
305
305
  children: mailboxLabel(sequence)
306
306
  }),
307
307
  /*#__PURE__*/ _jsx(Stack, {
308
+ useFlexGap: true,
308
309
  direction: "row",
309
310
  spacing: 0.5,
310
311
  sx: {