@aglyn/plugins-outreach 1.0.0-beta.151 → 1.0.0-beta.153

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +5 -1
  2. package/package.json +12 -12
  3. package/src/lib/components/compliance-section.js +1 -1
  4. package/src/lib/components/compliance-section.js.map +1 -1
  5. package/src/lib/components/enroll-dialog.js +142 -32
  6. package/src/lib/components/enroll-dialog.js.map +1 -1
  7. package/src/lib/components/enrollments-table.js +37 -8
  8. package/src/lib/components/enrollments-table.js.map +1 -1
  9. package/src/lib/components/mailbox-card.d.ts +3 -0
  10. package/src/lib/components/mailbox-card.js +44 -8
  11. package/src/lib/components/mailbox-card.js.map +1 -1
  12. package/src/lib/components/use-outreach-crm.d.ts +20 -1
  13. package/src/lib/components/use-outreach-crm.js +78 -4
  14. package/src/lib/components/use-outreach-crm.js.map +1 -1
  15. package/src/lib/components/use-outreach-mailbox-api.d.ts +2 -1
  16. package/src/lib/components/use-outreach-mailbox-api.js +5 -3
  17. package/src/lib/components/use-outreach-mailbox-api.js.map +1 -1
  18. package/src/lib/declarations.console-server.js +14 -0
  19. package/src/lib/declarations.console-server.js.map +1 -1
  20. package/src/lib/engine/compose.d.ts +7 -2
  21. package/src/lib/engine/compose.js +11 -5
  22. package/src/lib/engine/compose.js.map +1 -1
  23. package/src/lib/engine/enrollment-state.d.ts +6 -1
  24. package/src/lib/engine/enrollment-state.js +3 -1
  25. package/src/lib/engine/enrollment-state.js.map +1 -1
  26. package/src/lib/engine/opt-out-intent.js +1 -1
  27. package/src/lib/engine/opt-out-intent.js.map +1 -1
  28. package/src/lib/enrollment/enroll-people.d.ts +52 -7
  29. package/src/lib/enrollment/enroll-people.js +109 -12
  30. package/src/lib/enrollment/enroll-people.js.map +1 -1
  31. package/src/lib/enrollment/gate-lookups.d.ts +12 -3
  32. package/src/lib/enrollment/gate-lookups.js +17 -14
  33. package/src/lib/enrollment/gate-lookups.js.map +1 -1
  34. package/src/lib/mailboxes/mailbox-api.d.ts +27 -2
  35. package/src/lib/mailboxes/mailbox-api.js.map +1 -1
  36. package/src/lib/mailboxes/mailbox-routes.js +52 -8
  37. package/src/lib/mailboxes/mailbox-routes.js.map +1 -1
  38. package/src/lib/mailboxes/oauth-redirect.d.ts +15 -4
  39. package/src/lib/mailboxes/oauth-redirect.js +15 -6
  40. package/src/lib/mailboxes/oauth-redirect.js.map +1 -1
  41. package/src/lib/model/outreach-api.d.ts +39 -8
  42. package/src/lib/model/outreach-api.js.map +1 -1
  43. package/src/lib/model/outreach.types.d.ts +29 -4
  44. package/src/lib/model/outreach.types.js +9 -0
  45. package/src/lib/model/outreach.types.js.map +1 -1
  46. package/src/lib/model/stored-records.js +7 -0
  47. package/src/lib/model/stored-records.js.map +1 -1
  48. package/src/lib/routes/enroll-routes.js +112 -39
  49. package/src/lib/routes/enroll-routes.js.map +1 -1
  50. package/src/lib/routes/preview-routes.js +18 -5
  51. package/src/lib/routes/preview-routes.js.map +1 -1
  52. package/src/lib/runtime/lead-records.d.ts +26 -0
  53. package/src/lib/runtime/lead-records.js +84 -0
  54. package/src/lib/runtime/lead-records.js.map +1 -0
  55. package/src/lib/runtime/send-job.js +30 -12
  56. package/src/lib/runtime/send-job.js.map +1 -1
  57. package/src/lib/runtime/sync-job.js +11 -3
  58. package/src/lib/runtime/sync-job.js.map +1 -1
  59. package/src/lib/runtime/timeline.d.ts +6 -4
  60. package/src/lib/runtime/timeline.js +3 -7
  61. package/src/lib/runtime/timeline.js.map +1 -1
  62. package/src/lib/storage/outreach-records.d.ts +21 -2
  63. package/src/lib/storage/outreach-records.js +31 -2
  64. package/src/lib/storage/outreach-records.js.map +1 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enroll-dialog.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 { HelpTip } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Button,\n Checkbox,\n Chip,\n type ChipProps,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n FormGroup,\n List,\n ListItem,\n ListItemButton,\n ListItemIcon,\n ListItemText,\n MenuItem,\n Paper,\n Stack,\n Tab,\n Tabs,\n TextField,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport {\n OUTREACH_ENROLL_BATCH_MAX,\n type OutreachEnrollOutcome,\n type OutreachEnrollPreviewPerson,\n type OutreachEnrollPreviewResponse,\n type OutreachEnrollPreviewStatus,\n type OutreachEnrollSource,\n type OutreachPreviewResponse,\n} from '../model/outreach-api'\nimport {\n OUTREACH_ATTESTATION_LABELS,\n OUTREACH_PERSONAL_LINE_MAX,\n type OutreachAttestationKind,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { OutreachLoading, OutreachLoadProblem } from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport {\n type OutreachContactOption,\n useOutreachContactSearch,\n useOutreachSavedViews,\n} from './use-outreach-crm'\n\nexport interface OutreachEnrollDialogProps {\n open: boolean\n onClose(): void\n orgId: string\n sequence: OutreachSequence\n /** The consent group of the sequence's site: whose names a search shows. */\n contactGroupId: string\n uid: string | null\n api: OutreachApi\n}\n\nconst STATUS_LABELS: Record<OutreachEnrollPreviewStatus, string> = {\n eligible: 'Eligible',\n needs_confirmation: 'Needs you',\n blocked: 'Blocked',\n}\n\nconst STATUS_COLORS: Record<OutreachEnrollPreviewStatus, ChipProps['color']> = {\n eligible: 'success',\n needs_confirmation: 'warning',\n blocked: 'default',\n}\n\n/** What the rep has supplied for one person. */\ninterface Supplied {\n include: boolean\n personalLine: string\n attestations: OutreachAttestationKind[]\n}\n\ntype Stage =\n | { kind: 'source' }\n | { kind: 'previewing' }\n | { kind: 'preview'; answer: OutreachEnrollPreviewResponse }\n | { kind: 'enrolling'; answer: OutreachEnrollPreviewResponse }\n | { kind: 'done'; results: OutreachEnrollOutcome[]; enrolled: number }\n\n/** Whether a person may be sent to Confirm as the rep has left them. */\nexport function outreachPersonReady(\n person: OutreachEnrollPreviewPerson,\n supplied: Supplied | undefined,\n): boolean {\n if (person.status === 'blocked' || !supplied?.include) return false\n const line = supplied.personalLine.trim()\n if (line.length > OUTREACH_PERSONAL_LINE_MAX) return false\n if (person.status === 'eligible') return true\n if (person.requires.personalLine && !line) return false\n return person.requires.attestations.every((kind) =>\n supplied.attestations.includes(kind),\n )\n}\n\n/**\n * Enroll people in a sequence (AGL-2980).\n *\n * The rep picks a saved Contacts view or searches for people, and sees each\n * marked as the gates see them: eligible, blocked with the reason, or\n * waiting on them — for a cold contact, a personal line and the three\n * attestations only they can make, stored on the enrollment with who ticked\n * them and when. Enroll sends only the people who are ready, and the server\n * checks every gate again as it enrolls them.\n */\nexport function OutreachEnrollDialog(props: OutreachEnrollDialogProps) {\n const { orgId, sequence, api } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('sm'))\n const [tab, setTab] = useState<'view' | 'search'>('view')\n const [viewId, setViewId] = useState('')\n const [text, setText] = useState('')\n const [picked, setPicked] = useState<Map<string, OutreachContactOption>>(\n new Map(),\n )\n const [stage, setStage] = useState<Stage>({ kind: 'source' })\n const [problem, setProblem] = useState<string | null>(null)\n const [supplied, setSupplied] = useState<Record<string, Supplied>>({})\n const [emailPreview, setEmailPreview] = useState<\n Record<\n string,\n | { status: 'loading' }\n | { status: 'ready'; answer: OutreachPreviewResponse }\n | { status: 'failed'; message: string }\n >\n >({})\n\n const views = useOutreachSavedViews(props.open ? orgId : null, props.uid)\n const search = useOutreachContactSearch({\n orgId: props.open && tab === 'search' ? orgId : null,\n hostId: sequence.hostId,\n contactGroupId: props.contactGroupId,\n text,\n })\n\n const reset = () => {\n setStage({ kind: 'source' })\n setProblem(null)\n setSupplied({})\n setEmailPreview({})\n }\n const close = () => {\n reset()\n setPicked(new Map())\n setText('')\n setViewId('')\n props.onClose()\n }\n\n const preview = async (source: OutreachEnrollSource) => {\n setProblem(null)\n setStage({ kind: 'previewing' })\n try {\n const answer = await api.previewEnrollment(sequence.id, source)\n setSupplied(\n Object.fromEntries(\n answer.people.map((person) => [\n person.contactId,\n {\n include: person.status !== 'blocked',\n personalLine: '',\n attestations: [],\n },\n ]),\n ),\n )\n setStage({ kind: 'preview', answer })\n } catch (error) {\n setProblem((error as Error).message)\n setStage({ kind: 'source' })\n }\n }\n\n const answer =\n stage.kind === 'preview' || stage.kind === 'enrolling' ? stage.answer : null\n const ready = useMemo(\n () =>\n answer\n ? answer.people.filter((person) =>\n outreachPersonReady(person, supplied[person.contactId]),\n )\n : [],\n [answer, supplied],\n )\n const waiting = answer\n ? answer.people.filter(\n (person) =>\n person.status === 'needs_confirmation' &&\n supplied[person.contactId]?.include &&\n !outreachPersonReady(person, supplied[person.contactId]),\n ).length\n : 0\n\n const enroll = async () => {\n if (!answer || !ready.length) return\n setProblem(null)\n setStage({ kind: 'enrolling', answer })\n try {\n const result = await api.enroll(\n sequence.id,\n ready.map((person) => ({\n contactId: person.contactId,\n personalLine: supplied[person.contactId]?.personalLine.trim() ?? '',\n attestations: supplied[person.contactId]?.attestations ?? [],\n })),\n )\n setStage({\n kind: 'done',\n results: result.results,\n enrolled: result.enrolled,\n })\n } catch (error) {\n setProblem((error as Error).message)\n setStage({ kind: 'preview', answer })\n }\n }\n\n const supply = (contactId: string, next: Partial<Supplied>) =>\n setSupplied((previous) => ({\n ...previous,\n [contactId]: { ...previous[contactId], ...next } as Supplied,\n }))\n\n const previewEmail = async (person: OutreachEnrollPreviewPerson) => {\n setEmailPreview((previous) => ({\n ...previous,\n [person.contactId]: { status: 'loading' },\n }))\n try {\n const result = await api.previewEmail({\n sequenceId: sequence.id,\n contactId: person.contactId,\n personalLine: supplied[person.contactId]?.personalLine.trim() ?? '',\n })\n setEmailPreview((previous) => ({\n ...previous,\n [person.contactId]: { status: 'ready', answer: result },\n }))\n } catch (error) {\n setEmailPreview((previous) => ({\n ...previous,\n [person.contactId]: {\n status: 'failed',\n message: (error as Error).message,\n },\n }))\n }\n }\n\n const title = `Enroll people in ${sequence.name}`\n\n return (\n <Dialog\n open={props.open}\n onClose={close}\n fullWidth\n maxWidth=\"md\"\n fullScreen={narrow}\n aria-label={title}\n >\n <DialogTitle>\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center' }}>\n <span>{title}</span>\n <HelpTip {...pluginDocsHelp('sequences', { anchor: '#enroll' })} />\n </Stack>\n </DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2}>\n {problem ? <Alert severity=\"error\">{problem}</Alert> : null}\n\n {stage.kind === 'source' ? (\n <>\n <Tabs\n value={tab}\n onChange={(_event, next) => setTab(next)}\n aria-label=\"Where the people come from\"\n >\n <Tab value=\"view\" label=\"Saved view\" />\n <Tab value=\"search\" label=\"Search\" />\n </Tabs>\n {tab === 'view' ? (\n views.status === 'loading' ? (\n <OutreachLoading label=\"Loading saved views…\" />\n ) : views.status === 'error' || views.status === 'refused' ? (\n <OutreachLoadProblem\n status={views.status}\n what=\"saved views\"\n message={\n views.status === 'refused'\n ? 'Enrolling reads your CRM’s contacts, and your role does not include Manage data.'\n : null\n }\n />\n ) : views.data.length ? (\n <TextField\n select\n label=\"Saved Contacts view\"\n value={viewId}\n onChange={(event) => setViewId(event.target.value)}\n helperText={`Enrolls up to ${OUTREACH_ENROLL_BATCH_MAX} of the people the view shows at this sequence’s site.`}\n fullWidth\n >\n {views.data.map((view) => (\n <MenuItem key={view.id} value={view.id}>\n {view.name}\n </MenuItem>\n ))}\n </TextField>\n ) : (\n <Alert severity=\"info\">\n You have no saved Contacts views to enroll from. Save one in\n the CRM, or search instead.\n </Alert>\n )\n ) : (\n <Stack spacing={1.5}>\n <TextField\n label=\"Search contacts\"\n placeholder=\"A name, or an email address\"\n value={text}\n onChange={(event) => setText(event.target.value)}\n fullWidth\n autoFocus\n />\n {search.status === 'loading' ? (\n <OutreachLoading label=\"Searching…\" />\n ) : null}\n {search.status === 'error' || search.status === 'refused' ? (\n <OutreachLoadProblem\n status={search.status}\n what=\"contacts\"\n message={\n search.status === 'refused'\n ? 'Enrolling reads your CRM’s contacts, and your role does not include Manage data.'\n : 'The search failed. Try again.'\n }\n />\n ) : null}\n {search.status === 'ready' &&\n !search.idle &&\n !search.data.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n No contacts at this sequence’s site match.\n </Typography>\n ) : null}\n {search.data.length ? (\n <List dense aria-label=\"Search results\">\n {search.data.map((contact) => {\n const checked = picked.has(contact.id)\n return (\n <ListItem key={contact.id} disablePadding>\n <ListItemButton\n onClick={() =>\n setPicked((previous) => {\n const next = new Map(previous)\n if (checked) next.delete(contact.id)\n else if (\n next.size < OUTREACH_ENROLL_BATCH_MAX\n )\n next.set(contact.id, contact)\n return next\n })\n }\n >\n <ListItemIcon>\n <Checkbox\n edge=\"start\"\n checked={checked}\n tabIndex={-1}\n disableRipple\n />\n </ListItemIcon>\n <ListItemText\n primary={\n contact.name || contact.email || contact.id\n }\n secondary={contact.email}\n />\n </ListItemButton>\n </ListItem>\n )\n })}\n </List>\n ) : null}\n {picked.size ? (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', rowGap: 1 }}\n aria-label=\"Picked\"\n >\n {[...picked.values()].map((contact) => (\n <Chip\n key={contact.id}\n size=\"small\"\n label={contact.name || contact.email || contact.id}\n onDelete={() =>\n setPicked((previous) => {\n const next = new Map(previous)\n next.delete(contact.id)\n return next\n })\n }\n />\n ))}\n </Stack>\n ) : null}\n </Stack>\n )}\n </>\n ) : null}\n\n {stage.kind === 'previewing' ? (\n <OutreachLoading label=\"Checking each person…\" />\n ) : null}\n\n {answer ? (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {summary(answer)}\n </Typography>\n {answer.truncated ? (\n <Alert severity=\"info\">\n {`This shows the first ${answer.people.length} of ${answer.total}. Enroll these, then preview again for the rest.`}\n </Alert>\n ) : null}\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"People\"\n >\n {answer.people.map((person) => (\n <PersonRow\n key={person.contactId}\n person={person}\n supplied={supplied[person.contactId]}\n onSupply={(next) => supply(person.contactId, next)}\n emailPreview={emailPreview[person.contactId]}\n onPreviewEmail={() => void previewEmail(person)}\n disabled={stage.kind === 'enrolling'}\n />\n ))}\n </Stack>\n {waiting ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${waiting} ${waiting === 1 ? 'person still needs' : 'people still need'} a personal line or a confirmation, and won't be enrolled until they have one.`}\n </Typography>\n ) : null}\n </Stack>\n ) : null}\n\n {stage.kind === 'done' ? (\n <DoneSummary results={stage.results} enrolled={stage.enrolled} />\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n {stage.kind === 'done' ? (\n <Button variant=\"contained\" onClick={close}>\n Done\n </Button>\n ) : (\n <>\n <Button onClick={close}>Cancel</Button>\n {stage.kind === 'preview' || stage.kind === 'enrolling' ? (\n <>\n <Button onClick={reset} disabled={stage.kind === 'enrolling'}>\n Back\n </Button>\n <Button\n variant=\"contained\"\n disabled={!ready.length || stage.kind === 'enrolling'}\n onClick={() => void enroll()}\n >\n {stage.kind === 'enrolling'\n ? 'Enrolling…'\n : `Enroll ${ready.length} ${ready.length === 1 ? 'person' : 'people'}`}\n </Button>\n </>\n ) : (\n <Button\n variant=\"contained\"\n disabled={\n stage.kind === 'previewing' ||\n (tab === 'view' ? !viewId : !picked.size)\n }\n onClick={() =>\n void preview(\n tab === 'view'\n ? { kind: 'view', viewId }\n : { kind: 'contacts', contactIds: [...picked.keys()] },\n )\n }\n >\n Check people\n </Button>\n )}\n </>\n )}\n </DialogActions>\n </Dialog>\n )\n}\nOutreachEnrollDialog.displayName = 'OutreachEnrollDialog'\n\nfunction summary(answer: OutreachEnrollPreviewResponse): string {\n const count = (status: OutreachEnrollPreviewStatus) =>\n answer.people.filter((person) => person.status === status).length\n return `${count('eligible')} eligible · ${count('needs_confirmation')} need you · ${count('blocked')} blocked`\n}\n\nfunction PersonRow(props: {\n person: OutreachEnrollPreviewPerson\n supplied: Supplied | undefined\n onSupply(next: Partial<Supplied>): void\n emailPreview:\n | { status: 'loading' }\n | { status: 'ready'; answer: OutreachPreviewResponse }\n | { status: 'failed'; message: string }\n | undefined\n onPreviewEmail(): void\n disabled: boolean\n}) {\n const { person, supplied, disabled } = props\n const name = person.name || person.email || person.contactId\n const line = supplied?.personalLine ?? ''\n const tooLong = line.trim().length > OUTREACH_PERSONAL_LINE_MAX\n return (\n <Paper component=\"li\" variant=\"outlined\" sx={{ p: 1.5 }} aria-label={name}>\n <Stack spacing={1}>\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ alignItems: { sm: 'center' } }}\n >\n <Stack sx={{ flexGrow: 1, minWidth: 0 }}>\n <Typography variant=\"body2\">{name}</Typography>\n {person.email && person.email !== name ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {person.email}\n </Typography>\n ) : null}\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n {person.cold ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Cold\" />\n ) : null}\n <Chip\n size=\"small\"\n label={STATUS_LABELS[person.status]}\n color={STATUS_COLORS[person.status]}\n />\n </Stack>\n </Stack>\n {person.status === 'blocked' ? (\n <Stack component=\"ul\" spacing={0.5} sx={{ m: 0, pl: 2.5 }}>\n {person.blocks.map((block) => (\n <Typography\n key={block.code}\n component=\"li\"\n variant=\"body2\"\n color=\"text.secondary\"\n >\n {block.reason}\n </Typography>\n ))}\n </Stack>\n ) : (\n <Stack spacing={1}>\n <FormControlLabel\n control={\n <Checkbox\n checked={supplied?.include ?? true}\n onChange={(event) =>\n props.onSupply({ include: event.target.checked })\n }\n disabled={disabled}\n />\n }\n label=\"Enroll this person\"\n />\n {supplied?.include === false ? null : (\n <>\n <TextField\n label={\n person.requires.personalLine\n ? 'Personal line'\n : 'Personal line (optional)'\n }\n placeholder=\"One sentence on why you're writing to this person now.\"\n value={line}\n onChange={(event) =>\n props.onSupply({ personalLine: event.target.value })\n }\n disabled={disabled}\n required={person.requires.personalLine}\n error={tooLong}\n helperText={\n tooLong\n ? `Keep it to one sentence, under ${OUTREACH_PERSONAL_LINE_MAX} characters.`\n : 'Goes where the emails say {{enrollment.personalLine}}.'\n }\n fullWidth\n size=\"small\"\n />\n {person.requires.attestations.length ? (\n <FormGroup aria-label={`What you confirm about ${name}`}>\n {person.requires.attestations.map((kind) => (\n <FormControlLabel\n key={kind}\n control={\n <Checkbox\n checked={\n supplied?.attestations.includes(kind) ?? false\n }\n onChange={(event) =>\n props.onSupply({\n attestations: event.target.checked\n ? [...(supplied?.attestations ?? []), kind]\n : (supplied?.attestations ?? []).filter(\n (entry) => entry !== kind,\n ),\n })\n }\n disabled={disabled}\n />\n }\n label={OUTREACH_ATTESTATION_LABELS[kind]}\n />\n ))}\n </FormGroup>\n ) : null}\n <Stack direction=\"row\">\n <Button\n size=\"small\"\n onClick={props.onPreviewEmail}\n disabled={disabled}\n >\n Preview the first email\n </Button>\n </Stack>\n {props.emailPreview?.status === 'loading' ? (\n <OutreachLoading label=\"Writing the email…\" />\n ) : null}\n {props.emailPreview?.status === 'failed' ? (\n <Alert severity=\"error\">{props.emailPreview.message}</Alert>\n ) : null}\n {props.emailPreview?.status === 'ready' ? (\n props.emailPreview.answer.error ? (\n <Alert severity=\"info\">\n {props.emailPreview.answer.error.message}\n </Alert>\n ) : (\n <Paper\n variant=\"outlined\"\n sx={{ p: 1.5 }}\n aria-label={`First email to ${name}`}\n >\n <Typography variant=\"body2\">{`Subject: ${props.emailPreview.answer.subject}`}</Typography>\n <Typography\n variant=\"body2\"\n sx={{\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n mt: 1,\n }}\n >\n {props.emailPreview.answer.text}\n </Typography>\n </Paper>\n )\n ) : null}\n </>\n )}\n </Stack>\n )}\n </Stack>\n </Paper>\n )\n}\n\nfunction DoneSummary(props: {\n results: OutreachEnrollOutcome[]\n enrolled: number\n}) {\n const refused = props.results.filter(\n (\n result,\n ): result is Extract<OutreachEnrollOutcome, { outcome: 'blocked' }> =>\n result.outcome === 'blocked',\n )\n return (\n <Stack spacing={1.5}>\n <Alert severity={props.enrolled ? 'success' : 'info'}>\n {props.enrolled\n ? `Enrolled ${props.enrolled} ${props.enrolled === 1 ? 'person' : 'people'}. Their first step goes out in the mailbox's sending hours.`\n : 'Nobody was enrolled.'}\n </Alert>\n {refused.length ? (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">\n Not enrolled, checked again just now:\n </Typography>\n {refused.map((result) => (\n <Stack key={result.contactId} spacing={0.25}>\n <Typography variant=\"body2\">\n {result.email ?? result.contactId}\n </Typography>\n {result.blocks.map((block) => (\n <Typography\n key={block.code}\n variant=\"body2\"\n color=\"text.secondary\"\n >\n {block.reason}\n </Typography>\n ))}\n </Stack>\n ))}\n </Stack>\n ) : null}\n </Stack>\n )\n}\n\nexport default OutreachEnrollDialog\n"],"names":["pluginDocsHelp","HelpTip","Alert","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","FormGroup","List","ListItem","ListItemButton","ListItemIcon","ListItemText","MenuItem","Paper","Stack","Tab","Tabs","TextField","Typography","useMediaQuery","useTheme","useMemo","useState","OUTREACH_ENROLL_BATCH_MAX","OUTREACH_ATTESTATION_LABELS","OUTREACH_PERSONAL_LINE_MAX","OutreachLoading","OutreachLoadProblem","useOutreachContactSearch","useOutreachSavedViews","STATUS_LABELS","eligible","needs_confirmation","blocked","STATUS_COLORS","outreachPersonReady","person","supplied","status","include","line","personalLine","trim","length","requires","attestations","every","kind","includes","OutreachEnrollDialog","props","orgId","sequence","api","theme","narrow","breakpoints","down","tab","setTab","viewId","setViewId","text","setText","picked","setPicked","Map","stage","setStage","problem","setProblem","setSupplied","emailPreview","setEmailPreview","views","open","uid","search","hostId","contactGroupId","reset","close","onClose","preview","source","answer","previewEnrollment","id","Object","fromEntries","people","map","contactId","error","message","ready","filter","waiting","enroll","result","results","enrolled","supply","next","previous","previewEmail","sequenceId","title","name","fullWidth","maxWidth","fullScreen","aria-label","direction","spacing","sx","alignItems","span","anchor","dividers","severity","value","onChange","_event","label","what","data","select","event","target","helperText","view","placeholder","autoFocus","idle","variant","color","dense","contact","checked","has","disablePadding","onClick","delete","size","set","edge","tabIndex","disableRipple","primary","email","secondary","flexWrap","rowGap","values","onDelete","summary","truncated","total","component","listStyle","p","m","PersonRow","onSupply","onPreviewEmail","disabled","DoneSummary","contactIds","keys","displayName","count","tooLong","xs","sm","flexGrow","minWidth","cold","pl","blocks","block","reason","code","control","required","entry","subject","whiteSpace","wordBreak","mt","refused","outcome"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enroll-dialog.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 { HelpTip } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Button,\n Checkbox,\n Chip,\n type ChipProps,\n Dialog,\n DialogActions,\n DialogContent,\n DialogTitle,\n FormControlLabel,\n FormGroup,\n List,\n ListItem,\n ListItemButton,\n ListItemIcon,\n ListItemText,\n MenuItem,\n Paper,\n Stack,\n Tab,\n Tabs,\n TextField,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport {\n OUTREACH_ENROLL_BATCH_MAX,\n type OutreachEnrollOutcome,\n type OutreachEnrollPreviewPerson,\n type OutreachEnrollPreviewResponse,\n type OutreachEnrollPreviewStatus,\n type OutreachEnrollSource,\n type OutreachPreviewResponse,\n} from '../model/outreach-api'\nimport {\n OUTREACH_ATTESTATION_LABELS,\n OUTREACH_PERSONAL_LINE_MAX,\n type OutreachAttestationKind,\n type OutreachSequence,\n} from '../model/outreach.types'\nimport { OutreachLoading, OutreachLoadProblem } from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport {\n type OutreachContactOption,\n useOutreachContactSearch,\n useOutreachLeadSearch,\n useOutreachSavedViews,\n} from './use-outreach-crm'\n\nexport interface OutreachEnrollDialogProps {\n open: boolean\n onClose(): void\n orgId: string\n sequence: OutreachSequence\n /** The consent group of the sequence's site: whose names a search shows. */\n contactGroupId: string\n uid: string | null\n api: OutreachApi\n}\n\nconst STATUS_LABELS: Record<OutreachEnrollPreviewStatus, string> = {\n eligible: 'Eligible',\n needs_confirmation: 'Needs you',\n blocked: 'Blocked',\n}\n\nconst STATUS_COLORS: Record<OutreachEnrollPreviewStatus, ChipProps['color']> = {\n eligible: 'success',\n needs_confirmation: 'warning',\n blocked: 'default',\n}\n\n/** What the rep has supplied for one person. */\ninterface Supplied {\n include: boolean\n personalLine: string\n attestations: OutreachAttestationKind[]\n}\n\ntype Stage =\n | { kind: 'source' }\n | { kind: 'previewing' }\n | { kind: 'preview'; answer: OutreachEnrollPreviewResponse }\n | { kind: 'enrolling'; answer: OutreachEnrollPreviewResponse }\n | { kind: 'done'; results: OutreachEnrollOutcome[]; enrolled: number }\n\n/** Whether a person may be sent to Confirm as the rep has left them. */\nexport function outreachPersonReady(\n person: OutreachEnrollPreviewPerson,\n supplied: Supplied | undefined,\n): boolean {\n if (person.status === 'blocked' || !supplied?.include) return false\n const line = supplied.personalLine.trim()\n if (line.length > OUTREACH_PERSONAL_LINE_MAX) return false\n if (person.status === 'eligible') return true\n if (person.requires.personalLine && !line) return false\n return person.requires.attestations.every((kind) =>\n supplied.attestations.includes(kind),\n )\n}\n\n/**\n * Enroll people in a sequence (AGL-2980).\n *\n * The rep picks a saved Contacts view or searches for people, and sees each\n * marked as the gates see them: eligible, blocked with the reason, or\n * waiting on them — for a cold contact, a personal line and the three\n * attestations only they can make, stored on the enrollment with who ticked\n * them and when. Enroll sends only the people who are ready, and the server\n * checks every gate again as it enrolls them.\n */\nexport function OutreachEnrollDialog(props: OutreachEnrollDialogProps) {\n const { orgId, sequence, api } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('sm'))\n const [tab, setTab] = useState<'view' | 'search' | 'leads'>('view')\n const [viewId, setViewId] = useState('')\n const [text, setText] = useState('')\n const [picked, setPicked] = useState<Map<string, OutreachContactOption>>(\n new Map(),\n )\n // The leads picked from the sequence's site (AGL-3234), kept apart from\n // the contacts: the two are different records and the source names which.\n const [leadText, setLeadText] = useState('')\n const [pickedLeads, setPickedLeads] = useState<Map<string, OutreachContactOption>>(\n new Map(),\n )\n const [stage, setStage] = useState<Stage>({ kind: 'source' })\n const [problem, setProblem] = useState<string | null>(null)\n const [supplied, setSupplied] = useState<Record<string, Supplied>>({})\n const [emailPreview, setEmailPreview] = useState<\n Record<\n string,\n | { status: 'loading' }\n | { status: 'ready'; answer: OutreachPreviewResponse }\n | { status: 'failed'; message: string }\n >\n >({})\n\n const views = useOutreachSavedViews(props.open ? orgId : null, props.uid)\n const search = useOutreachContactSearch({\n orgId: props.open && tab === 'search' ? orgId : null,\n hostId: sequence.hostId,\n contactGroupId: props.contactGroupId,\n text,\n })\n const leadSearch = useOutreachLeadSearch({\n hostId: sequence.hostId,\n text: leadText,\n enabled: props.open && tab === 'leads',\n })\n\n const reset = () => {\n setStage({ kind: 'source' })\n setProblem(null)\n setSupplied({})\n setEmailPreview({})\n }\n const close = () => {\n reset()\n setPicked(new Map())\n setPickedLeads(new Map())\n setText('')\n setLeadText('')\n setViewId('')\n props.onClose()\n }\n\n const preview = async (source: OutreachEnrollSource) => {\n setProblem(null)\n setStage({ kind: 'previewing' })\n try {\n const answer = await api.previewEnrollment(sequence.id, source)\n setSupplied(\n Object.fromEntries(\n answer.people.map((person) => [\n person.personId,\n {\n include: person.status !== 'blocked',\n personalLine: '',\n attestations: [],\n },\n ]),\n ),\n )\n setStage({ kind: 'preview', answer })\n } catch (error) {\n setProblem((error as Error).message)\n setStage({ kind: 'source' })\n }\n }\n\n const answer =\n stage.kind === 'preview' || stage.kind === 'enrolling' ? stage.answer : null\n const ready = useMemo(\n () =>\n answer\n ? answer.people.filter((person) =>\n outreachPersonReady(person, supplied[person.personId]),\n )\n : [],\n [answer, supplied],\n )\n const waiting = answer\n ? answer.people.filter(\n (person) =>\n person.status === 'needs_confirmation' &&\n supplied[person.personId]?.include &&\n !outreachPersonReady(person, supplied[person.personId]),\n ).length\n : 0\n\n const enroll = async () => {\n if (!answer || !ready.length) return\n setProblem(null)\n setStage({ kind: 'enrolling', answer })\n try {\n const result = await api.enroll(\n sequence.id,\n ready.map((person) => ({\n // A contact by id, or a lead by its key (AGL-3234).\n ...(person.target === 'lead' && person.leadId\n ? { leadId: person.leadId }\n : { contactId: person.contactId }),\n personalLine: supplied[person.personId]?.personalLine.trim() ?? '',\n attestations: supplied[person.personId]?.attestations ?? [],\n })),\n )\n setStage({\n kind: 'done',\n results: result.results,\n enrolled: result.enrolled,\n })\n } catch (error) {\n setProblem((error as Error).message)\n setStage({ kind: 'preview', answer })\n }\n }\n\n const supply = (personId: string, next: Partial<Supplied>) =>\n setSupplied((previous) => ({\n ...previous,\n [personId]: { ...previous[personId], ...next } as Supplied,\n }))\n\n const previewEmail = async (person: OutreachEnrollPreviewPerson) => {\n setEmailPreview((previous) => ({\n ...previous,\n [person.personId]: { status: 'loading' },\n }))\n try {\n const result = await api.previewEmail({\n sequenceId: sequence.id,\n ...(person.target === 'lead' && person.leadId\n ? { leadId: person.leadId }\n : { contactId: person.contactId }),\n personalLine: supplied[person.personId]?.personalLine.trim() ?? '',\n })\n setEmailPreview((previous) => ({\n ...previous,\n [person.personId]: { status: 'ready', answer: result },\n }))\n } catch (error) {\n setEmailPreview((previous) => ({\n ...previous,\n [person.personId]: {\n status: 'failed',\n message: (error as Error).message,\n },\n }))\n }\n }\n\n const title = `Enroll people in ${sequence.name}`\n\n return (\n <Dialog\n open={props.open}\n onClose={close}\n fullWidth\n maxWidth=\"md\"\n fullScreen={narrow}\n aria-label={title}\n >\n <DialogTitle>\n <Stack direction=\"row\" spacing={0.5} sx={{ alignItems: 'center' }}>\n <span>{title}</span>\n <HelpTip {...pluginDocsHelp('sequences', { anchor: '#enroll' })} />\n </Stack>\n </DialogTitle>\n <DialogContent dividers>\n <Stack spacing={2}>\n {problem ? <Alert severity=\"error\">{problem}</Alert> : null}\n\n {stage.kind === 'source' ? (\n <>\n <Tabs\n value={tab}\n onChange={(_event, next) => setTab(next)}\n aria-label=\"Where the people come from\"\n >\n <Tab value=\"view\" label=\"Saved view\" />\n <Tab value=\"search\" label=\"Search\" />\n <Tab value=\"leads\" label=\"Leads\" />\n </Tabs>\n {tab === 'view' ? (\n views.status === 'loading' ? (\n <OutreachLoading label=\"Loading saved views…\" />\n ) : views.status === 'error' || views.status === 'refused' ? (\n <OutreachLoadProblem\n status={views.status}\n what=\"saved views\"\n message={\n views.status === 'refused'\n ? 'Enrolling reads your CRM’s contacts, and your role does not include Manage data.'\n : null\n }\n />\n ) : views.data.length ? (\n <TextField\n select\n label=\"Saved Contacts view\"\n value={viewId}\n onChange={(event) => setViewId(event.target.value)}\n helperText={`Enrolls up to ${OUTREACH_ENROLL_BATCH_MAX} of the people the view shows at this sequence’s site — a Contacts view, or a Leads view of the site’s leads.`}\n fullWidth\n >\n {views.data.map((view) => (\n <MenuItem key={view.id} value={view.id}>\n {view.section === 'leads' ? `${view.name} (Leads)` : view.name}\n </MenuItem>\n ))}\n </TextField>\n ) : (\n <Alert severity=\"info\">\n You have no saved Contacts views to enroll from. Save one in\n the CRM, or search instead.\n </Alert>\n )\n ) : tab === 'leads' ? (\n <Stack spacing={1.5}>\n <TextField\n label=\"Search leads\"\n placeholder=\"A name, or an email address\"\n value={leadText}\n onChange={(event) => setLeadText(event.target.value)}\n helperText=\"The open leads at this sequence’s site. A lead is enrolled as it is, and follows itself to the contact it becomes.\"\n fullWidth\n autoFocus\n />\n {leadSearch.status === 'loading' ? (\n <OutreachLoading label=\"Reading leads…\" />\n ) : null}\n {leadSearch.status === 'error' || leadSearch.status === 'refused' ? (\n <OutreachLoadProblem\n status={leadSearch.status}\n what=\"leads\"\n message={\n leadSearch.status === 'refused'\n ? 'Enrolling reads your CRM’s leads, and your role does not include Manage data.'\n : 'The leads could not be read. Try again.'\n }\n />\n ) : null}\n {leadSearch.status === 'ready' && !leadSearch.idle && !leadSearch.data.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n No open leads at this sequence’s site match.\n </Typography>\n ) : null}\n {leadSearch.data.length ? (\n <List dense aria-label=\"Lead results\">\n {leadSearch.data.map((lead) => {\n const checked = pickedLeads.has(lead.id)\n return (\n <ListItem key={lead.id} disablePadding>\n <ListItemButton\n onClick={() =>\n setPickedLeads((previous) => {\n const next = new Map(previous)\n if (checked) next.delete(lead.id)\n else if (next.size < OUTREACH_ENROLL_BATCH_MAX) next.set(lead.id, lead)\n return next\n })\n }\n >\n <ListItemIcon>\n <Checkbox edge=\"start\" checked={checked} tabIndex={-1} disableRipple />\n </ListItemIcon>\n <ListItemText\n primary={lead.name || lead.email || lead.id}\n secondary={lead.email}\n />\n </ListItemButton>\n </ListItem>\n )\n })}\n </List>\n ) : null}\n {pickedLeads.size ? (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', rowGap: 1 }}\n aria-label=\"Picked leads\"\n >\n {[...pickedLeads.values()].map((lead) => (\n <Chip\n key={lead.id}\n size=\"small\"\n label={lead.name || lead.email || lead.id}\n onDelete={() =>\n setPickedLeads((previous) => {\n const next = new Map(previous)\n next.delete(lead.id)\n return next\n })\n }\n />\n ))}\n </Stack>\n ) : null}\n </Stack>\n ) : (\n <Stack spacing={1.5}>\n <TextField\n label=\"Search contacts\"\n placeholder=\"A name, or an email address\"\n value={text}\n onChange={(event) => setText(event.target.value)}\n fullWidth\n autoFocus\n />\n {search.status === 'loading' ? (\n <OutreachLoading label=\"Searching…\" />\n ) : null}\n {search.status === 'error' || search.status === 'refused' ? (\n <OutreachLoadProblem\n status={search.status}\n what=\"contacts\"\n message={\n search.status === 'refused'\n ? 'Enrolling reads your CRM’s contacts, and your role does not include Manage data.'\n : 'The search failed. Try again.'\n }\n />\n ) : null}\n {search.status === 'ready' &&\n !search.idle &&\n !search.data.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n No contacts at this sequence’s site match.\n </Typography>\n ) : null}\n {search.data.length ? (\n <List dense aria-label=\"Search results\">\n {search.data.map((contact) => {\n const checked = picked.has(contact.id)\n return (\n <ListItem key={contact.id} disablePadding>\n <ListItemButton\n onClick={() =>\n setPicked((previous) => {\n const next = new Map(previous)\n if (checked) next.delete(contact.id)\n else if (\n next.size < OUTREACH_ENROLL_BATCH_MAX\n )\n next.set(contact.id, contact)\n return next\n })\n }\n >\n <ListItemIcon>\n <Checkbox\n edge=\"start\"\n checked={checked}\n tabIndex={-1}\n disableRipple\n />\n </ListItemIcon>\n <ListItemText\n primary={\n contact.name || contact.email || contact.id\n }\n secondary={contact.email}\n />\n </ListItemButton>\n </ListItem>\n )\n })}\n </List>\n ) : null}\n {picked.size ? (\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', rowGap: 1 }}\n aria-label=\"Picked\"\n >\n {[...picked.values()].map((contact) => (\n <Chip\n key={contact.id}\n size=\"small\"\n label={contact.name || contact.email || contact.id}\n onDelete={() =>\n setPicked((previous) => {\n const next = new Map(previous)\n next.delete(contact.id)\n return next\n })\n }\n />\n ))}\n </Stack>\n ) : null}\n </Stack>\n )}\n </>\n ) : null}\n\n {stage.kind === 'previewing' ? (\n <OutreachLoading label=\"Checking each person…\" />\n ) : null}\n\n {answer ? (\n <Stack spacing={1.5}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {summary(answer)}\n </Typography>\n {answer.truncated ? (\n <Alert severity=\"info\">\n {`This shows the first ${answer.people.length} of ${answer.total}. Enroll these, then preview again for the rest.`}\n </Alert>\n ) : null}\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"People\"\n >\n {answer.people.map((person) => (\n <PersonRow\n key={person.personId}\n person={person}\n supplied={supplied[person.personId]}\n onSupply={(next) => supply(person.personId, next)}\n emailPreview={emailPreview[person.personId]}\n onPreviewEmail={() => void previewEmail(person)}\n disabled={stage.kind === 'enrolling'}\n />\n ))}\n </Stack>\n {waiting ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`${waiting} ${waiting === 1 ? 'person still needs' : 'people still need'} a personal line or a confirmation, and won't be enrolled until they have one.`}\n </Typography>\n ) : null}\n </Stack>\n ) : null}\n\n {stage.kind === 'done' ? (\n <DoneSummary results={stage.results} enrolled={stage.enrolled} />\n ) : null}\n </Stack>\n </DialogContent>\n <DialogActions>\n {stage.kind === 'done' ? (\n <Button variant=\"contained\" onClick={close}>\n Done\n </Button>\n ) : (\n <>\n <Button onClick={close}>Cancel</Button>\n {stage.kind === 'preview' || stage.kind === 'enrolling' ? (\n <>\n <Button onClick={reset} disabled={stage.kind === 'enrolling'}>\n Back\n </Button>\n <Button\n variant=\"contained\"\n disabled={!ready.length || stage.kind === 'enrolling'}\n onClick={() => void enroll()}\n >\n {stage.kind === 'enrolling'\n ? 'Enrolling…'\n : `Enroll ${ready.length} ${ready.length === 1 ? 'person' : 'people'}`}\n </Button>\n </>\n ) : (\n <Button\n variant=\"contained\"\n disabled={\n stage.kind === 'previewing' ||\n (tab === 'view' ? !viewId : tab === 'leads' ? !pickedLeads.size : !picked.size)\n }\n onClick={() =>\n void preview(\n tab === 'view'\n ? { kind: 'view', viewId }\n : tab === 'leads'\n ? { kind: 'leads', leadIds: [...pickedLeads.keys()] }\n : { kind: 'contacts', contactIds: [...picked.keys()] },\n )\n }\n >\n Check people\n </Button>\n )}\n </>\n )}\n </DialogActions>\n </Dialog>\n )\n}\nOutreachEnrollDialog.displayName = 'OutreachEnrollDialog'\n\nfunction summary(answer: OutreachEnrollPreviewResponse): string {\n const count = (status: OutreachEnrollPreviewStatus) =>\n answer.people.filter((person) => person.status === status).length\n return `${count('eligible')} eligible · ${count('needs_confirmation')} need you · ${count('blocked')} blocked`\n}\n\nfunction PersonRow(props: {\n person: OutreachEnrollPreviewPerson\n supplied: Supplied | undefined\n onSupply(next: Partial<Supplied>): void\n emailPreview:\n | { status: 'loading' }\n | { status: 'ready'; answer: OutreachPreviewResponse }\n | { status: 'failed'; message: string }\n | undefined\n onPreviewEmail(): void\n disabled: boolean\n}) {\n const { person, supplied, disabled } = props\n const name = person.name || person.email || person.personId\n const line = supplied?.personalLine ?? ''\n const tooLong = line.trim().length > OUTREACH_PERSONAL_LINE_MAX\n return (\n <Paper component=\"li\" variant=\"outlined\" sx={{ p: 1.5 }} aria-label={name}>\n <Stack spacing={1}>\n <Stack\n direction={{ xs: 'column', sm: 'row' }}\n spacing={1}\n sx={{ alignItems: { sm: 'center' } }}\n >\n <Stack sx={{ flexGrow: 1, minWidth: 0 }}>\n <Typography variant=\"body2\">{name}</Typography>\n {person.email && person.email !== name ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {person.email}\n </Typography>\n ) : null}\n </Stack>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n {person.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n {person.cold ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Cold\" />\n ) : null}\n <Chip\n size=\"small\"\n label={STATUS_LABELS[person.status]}\n color={STATUS_COLORS[person.status]}\n />\n </Stack>\n </Stack>\n {person.status === 'blocked' ? (\n <Stack component=\"ul\" spacing={0.5} sx={{ m: 0, pl: 2.5 }}>\n {person.blocks.map((block) => (\n <Typography\n key={block.code}\n component=\"li\"\n variant=\"body2\"\n color=\"text.secondary\"\n >\n {block.reason}\n </Typography>\n ))}\n </Stack>\n ) : (\n <Stack spacing={1}>\n <FormControlLabel\n control={\n <Checkbox\n checked={supplied?.include ?? true}\n onChange={(event) =>\n props.onSupply({ include: event.target.checked })\n }\n disabled={disabled}\n />\n }\n label=\"Enroll this person\"\n />\n {supplied?.include === false ? null : (\n <>\n <TextField\n label={\n person.requires.personalLine\n ? 'Personal line'\n : 'Personal line (optional)'\n }\n placeholder=\"One sentence on why you're writing to this person now.\"\n value={line}\n onChange={(event) =>\n props.onSupply({ personalLine: event.target.value })\n }\n disabled={disabled}\n required={person.requires.personalLine}\n error={tooLong}\n helperText={\n tooLong\n ? `Keep it to one sentence, under ${OUTREACH_PERSONAL_LINE_MAX} characters.`\n : 'Goes where the emails say {{enrollment.personalLine}}.'\n }\n fullWidth\n size=\"small\"\n />\n {person.requires.attestations.length ? (\n <FormGroup aria-label={`What you confirm about ${name}`}>\n {person.requires.attestations.map((kind) => (\n <FormControlLabel\n key={kind}\n control={\n <Checkbox\n checked={\n supplied?.attestations.includes(kind) ?? false\n }\n onChange={(event) =>\n props.onSupply({\n attestations: event.target.checked\n ? [...(supplied?.attestations ?? []), kind]\n : (supplied?.attestations ?? []).filter(\n (entry) => entry !== kind,\n ),\n })\n }\n disabled={disabled}\n />\n }\n label={OUTREACH_ATTESTATION_LABELS[kind]}\n />\n ))}\n </FormGroup>\n ) : null}\n <Stack direction=\"row\">\n <Button\n size=\"small\"\n onClick={props.onPreviewEmail}\n disabled={disabled}\n >\n Preview the first email\n </Button>\n </Stack>\n {props.emailPreview?.status === 'loading' ? (\n <OutreachLoading label=\"Writing the email…\" />\n ) : null}\n {props.emailPreview?.status === 'failed' ? (\n <Alert severity=\"error\">{props.emailPreview.message}</Alert>\n ) : null}\n {props.emailPreview?.status === 'ready' ? (\n props.emailPreview.answer.error ? (\n <Alert severity=\"info\">\n {props.emailPreview.answer.error.message}\n </Alert>\n ) : (\n <Paper\n variant=\"outlined\"\n sx={{ p: 1.5 }}\n aria-label={`First email to ${name}`}\n >\n <Typography variant=\"body2\">{`Subject: ${props.emailPreview.answer.subject}`}</Typography>\n <Typography\n variant=\"body2\"\n sx={{\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n mt: 1,\n }}\n >\n {props.emailPreview.answer.text}\n </Typography>\n </Paper>\n )\n ) : null}\n </>\n )}\n </Stack>\n )}\n </Stack>\n </Paper>\n )\n}\n\nfunction DoneSummary(props: {\n results: OutreachEnrollOutcome[]\n enrolled: number\n}) {\n const refused = props.results.filter(\n (\n result,\n ): result is Extract<OutreachEnrollOutcome, { outcome: 'blocked' }> =>\n result.outcome === 'blocked',\n )\n return (\n <Stack spacing={1.5}>\n <Alert severity={props.enrolled ? 'success' : 'info'}>\n {props.enrolled\n ? `Enrolled ${props.enrolled} ${props.enrolled === 1 ? 'person' : 'people'}. Their first step goes out in the mailbox's sending hours.`\n : 'Nobody was enrolled.'}\n </Alert>\n {refused.length ? (\n <Stack spacing={1}>\n <Typography variant=\"subtitle2\">\n Not enrolled, checked again just now:\n </Typography>\n {refused.map((result) => (\n <Stack key={result.personId} spacing={0.25}>\n <Typography variant=\"body2\">\n {result.email ?? result.personId}\n </Typography>\n {result.blocks.map((block) => (\n <Typography\n key={block.code}\n variant=\"body2\"\n color=\"text.secondary\"\n >\n {block.reason}\n </Typography>\n ))}\n </Stack>\n ))}\n </Stack>\n ) : null}\n </Stack>\n )\n}\n\nexport default OutreachEnrollDialog\n"],"names":["pluginDocsHelp","HelpTip","Alert","Button","Checkbox","Chip","Dialog","DialogActions","DialogContent","DialogTitle","FormControlLabel","FormGroup","List","ListItem","ListItemButton","ListItemIcon","ListItemText","MenuItem","Paper","Stack","Tab","Tabs","TextField","Typography","useMediaQuery","useTheme","useMemo","useState","OUTREACH_ENROLL_BATCH_MAX","OUTREACH_ATTESTATION_LABELS","OUTREACH_PERSONAL_LINE_MAX","OutreachLoading","OutreachLoadProblem","useOutreachContactSearch","useOutreachLeadSearch","useOutreachSavedViews","STATUS_LABELS","eligible","needs_confirmation","blocked","STATUS_COLORS","outreachPersonReady","person","supplied","status","include","line","personalLine","trim","length","requires","attestations","every","kind","includes","OutreachEnrollDialog","props","orgId","sequence","api","theme","narrow","breakpoints","down","tab","setTab","viewId","setViewId","text","setText","picked","setPicked","Map","leadText","setLeadText","pickedLeads","setPickedLeads","stage","setStage","problem","setProblem","setSupplied","emailPreview","setEmailPreview","views","open","uid","search","hostId","contactGroupId","leadSearch","enabled","reset","close","onClose","preview","source","answer","previewEnrollment","id","Object","fromEntries","people","map","personId","error","message","ready","filter","waiting","enroll","result","target","leadId","contactId","results","enrolled","supply","next","previous","previewEmail","sequenceId","title","name","fullWidth","maxWidth","fullScreen","aria-label","direction","spacing","sx","alignItems","span","anchor","dividers","severity","value","onChange","_event","label","what","data","select","event","helperText","view","section","placeholder","autoFocus","idle","variant","color","dense","lead","checked","has","disablePadding","onClick","delete","size","set","edge","tabIndex","disableRipple","primary","email","secondary","flexWrap","rowGap","values","onDelete","contact","summary","truncated","total","component","listStyle","p","m","PersonRow","onSupply","onPreviewEmail","disabled","DoneSummary","leadIds","keys","contactIds","displayName","count","tooLong","xs","sm","flexGrow","minWidth","cold","pl","blocks","block","reason","code","control","required","entry","subject","whiteSpace","wordBreak","mt","refused","outcome"],"mappings":"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"}
@@ -20,7 +20,7 @@ import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.
20
20
  import { ListRowActions, ListTable, listActionsColumn } from "@aglyn/shared-ui-jsx/components/list-table.component";
21
21
  import { TABLE_PAGE_SIZE_DEFAULT } from "@aglyn/shared-ui-jsx/const/table-pagination";
22
22
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
23
- import { Button, Card, CardContent, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack, TextField, Typography, useMediaQuery, useTheme } from "@mui/material";
23
+ import { Button, Card, CardContent, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, Stack, TextField, Typography, useMediaQuery, useTheme, Chip } from "@mui/material";
24
24
  import { useState } from "react";
25
25
  import { OUTREACH_TASK_KIND_LABELS } from "../model/outreach.types.js";
26
26
  import { formatOutreachTime, OUTREACH_STOP_REASON_LABELS, OutreachEnrollmentStatusChip, OutreachLoading, OutreachLoadProblem } from "./outreach-ui.js";
@@ -86,10 +86,25 @@ const ACTION_LABELS = {
86
86
  minWidth: 0
87
87
  },
88
88
  children: [
89
- /*#__PURE__*/ _jsx(Typography, {
90
- variant: "body2",
91
- noWrap: true,
92
- children: row.enrollment.contactName || row.enrollment.email
89
+ /*#__PURE__*/ _jsxs(Stack, {
90
+ direction: "row",
91
+ spacing: 0.75,
92
+ sx: {
93
+ alignItems: 'center',
94
+ minWidth: 0
95
+ },
96
+ children: [
97
+ /*#__PURE__*/ _jsx(Typography, {
98
+ variant: "body2",
99
+ noWrap: true,
100
+ children: row.enrollment.contactName || row.enrollment.email
101
+ }),
102
+ row.enrollment.target === 'lead' ? /*#__PURE__*/ _jsx(Chip, {
103
+ size: "small",
104
+ variant: "outlined",
105
+ label: "Lead"
106
+ }) : null
107
+ ]
93
108
  }),
94
109
  row.enrollment.contactName ? /*#__PURE__*/ _jsx(Typography, {
95
110
  variant: "caption",
@@ -214,9 +229,23 @@ const ACTION_LABELS = {
214
229
  const person = (enrollment)=>/*#__PURE__*/ _jsxs(Stack, {
215
230
  spacing: 0,
216
231
  children: [
217
- /*#__PURE__*/ _jsx(Typography, {
218
- variant: "body2",
219
- children: enrollment.contactName || enrollment.email
232
+ /*#__PURE__*/ _jsxs(Stack, {
233
+ direction: "row",
234
+ spacing: 0.75,
235
+ sx: {
236
+ alignItems: 'center'
237
+ },
238
+ children: [
239
+ /*#__PURE__*/ _jsx(Typography, {
240
+ variant: "body2",
241
+ children: enrollment.contactName || enrollment.email
242
+ }),
243
+ enrollment.target === 'lead' ? /*#__PURE__*/ _jsx(Chip, {
244
+ size: "small",
245
+ variant: "outlined",
246
+ label: "Lead"
247
+ }) : null
248
+ ]
220
249
  }),
221
250
  enrollment.contactName ? /*#__PURE__*/ _jsx(Typography, {
222
251
  variant: "caption",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enrollments-table.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Card,\n CardContent,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n useMediaQuery,\n useTheme,\n} from '@mui/material'\nimport { type ReactNode, useState } from 'react'\nimport type { OutreachEnrollmentAction } from '../model/outreach-api'\nimport {\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n formatOutreachTime,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport type { OutreachEnrollmentsLoad } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n enrollments: OutreachEnrollmentsLoad\n steps: readonly OutreachSequenceStep[]\n /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */\n timeZone: string | null\n api: OutreachApi\n /** Shown in the empty state when the sequence takes people. */\n enrollAction?: ReactNode\n}\n\n/** The step an enrollment is on, as a person reads it. */\nexport function outreachCurrentStepLabel(\n enrollment: Pick<OutreachEnrollment, 'status' | 'stepIndex'>,\n steps: readonly OutreachSequenceStep[],\n): string {\n if (enrollment.status === 'finished' || enrollment.stepIndex >= steps.length)\n return 'All steps done'\n const step = steps[enrollment.stepIndex]\n const kind =\n step?.kind === 'email'\n ? 'Email'\n : step?.kind === 'task'\n ? OUTREACH_TASK_KIND_LABELS[step.taskKind]\n : 'Step'\n return `Step ${enrollment.stepIndex + 1} of ${steps.length} · ${kind}`\n}\n\n/** Why an enrollment stopped, as the table says it; `''` while it runs. */\nexport function outreachStopLabel(\n enrollment: Pick<OutreachEnrollment, 'stopReason' | 'stopDetail'>,\n): string {\n if (!enrollment.stopReason) return ''\n const reason = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n return enrollment.stopDetail ? `${reason} — ${enrollment.stopDetail}` : reason\n}\n\n/** The last thing that happened: a send, a stop, or the enrollment itself. */\nfunction lastActivityMs(enrollment: OutreachEnrollment): number {\n return Math.max(\n enrollment.lastSentAtMs ?? 0,\n enrollment.stoppedAtMs ?? 0,\n enrollment.createdAtMs ?? 0,\n )\n}\n\n/** What a member may do to an enrollment in the status it is in. */\nfunction actionsFor(\n enrollment: OutreachEnrollment,\n): OutreachEnrollmentAction[] {\n const actions: OutreachEnrollmentAction[] = []\n if (enrollment.status === 'active') actions.push('pause', 'stop')\n if (enrollment.status === 'paused') actions.push('resume', 'stop')\n if (enrollment.status !== 'opted_out') actions.push('do_not_contact')\n return actions\n}\n\nconst ACTION_LABELS: Record<OutreachEnrollmentAction, string> = {\n pause: 'Pause',\n resume: 'Resume',\n stop: 'Stop',\n do_not_contact: 'Mark do-not-contact',\n}\n\n/** What the snackbar says once an action lands. */\nconst DONE_LABELS: Record<\n Exclude<OutreachEnrollmentAction, 'do_not_contact'>,\n string\n> = {\n pause: 'Paused.',\n resume: 'Resumed.',\n stop: 'Stopped.',\n}\n\n/** The two actions that end something for good ask first. */\nconst CONFIRM: Partial<\n Record<\n OutreachEnrollmentAction,\n { title: string; body: string; label: string }\n >\n> = {\n stop: {\n title: 'Stop this enrollment?',\n body: 'They won’t get any more steps from this sequence, and they can’t be enrolled in it again.',\n label: 'Stop',\n },\n do_not_contact: {\n title: 'Mark do-not-contact?',\n body:\n 'The address goes on your organization’s do-not-contact list, which every sequence checks before ' +\n 'every send, and they are stopped in every sequence they are in.',\n label: 'Mark do-not-contact',\n },\n}\n\n/** The table's columns; the person and the actions are drawn from the row's enrollment. */\nfunction columns(\n rowActions: (enrollment: OutreachEnrollment) => ReactNode,\n): NonNullable<ListTableProps['columns']> {\n return [\n {\n field: 'person',\n headerName: 'Person',\n flex: 1,\n minWidth: 200,\n sortable: false,\n renderCell: ({ row }) => (\n <Stack\n spacing={0}\n sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}\n >\n <Typography variant=\"body2\" noWrap>\n {row.enrollment.contactName || row.enrollment.email}\n </Typography>\n {row.enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 120,\n renderCell: ({ row }) => (\n <OutreachEnrollmentStatusChip status={row.status} />\n ),\n },\n { field: 'step', headerName: 'Current step', flex: 1, minWidth: 170 },\n { field: 'nextSend', headerName: 'Next send', width: 190, sortable: false },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 190,\n sortable: false,\n },\n { field: 'stopReason', headerName: 'Stop reason', flex: 1, minWidth: 170 },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/**\n * A sequence's enrollments (AGL-2980): who is in it, where each person\n * stands, when their next step goes out in the mailbox's timezone, and the\n * four things a member can do about it — pause, resume, stop, and mark\n * do-not-contact. A table on wider screens, a list of cards on a phone.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { enrollments, steps, timeZone, api } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const { enqueueSnackbar } = useSnackbar()\n const [confirming, setConfirming] = useState<{\n enrollment: OutreachEnrollment\n action: OutreachEnrollmentAction\n } | null>(null)\n const [detail, setDetail] = useState('')\n const [busy, setBusy] = useState<string | null>(null)\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n\n const act = async (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n why?: string,\n ) => {\n setBusy(enrollment.id)\n try {\n const answer = await api.actOnEnrollment(enrollment.id, action, why)\n const others = answer.stoppedOthers\n enqueueSnackbar(\n action === 'do_not_contact'\n ? `Marked do-not-contact${others ? `, and stopped in ${others} other ${others === 1 ? 'sequence' : 'sequences'}` : ''}.`\n : answer.changed\n ? DONE_LABELS[action]\n : 'Nothing to change.',\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(null)\n }\n }\n\n const choose = (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n ) => {\n if (CONFIRM[action]) {\n setDetail('')\n setConfirming({ enrollment, action })\n } else {\n void act(enrollment, action)\n }\n }\n\n if (enrollments.status === 'loading')\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n )\n }\n if (!enrollments.data.length) {\n return (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"Nobody is enrolled yet\"\n description=\"People you enroll show here, with where each of them is in the sequence.\"\n action={props.enrollAction}\n />\n </Card>\n )\n }\n\n const rowActions = (enrollment: OutreachEnrollment) => (\n <ListRowActions\n label={enrollment.contactName || enrollment.email}\n items={actionsFor(enrollment).map((action) => ({\n key: action,\n label: ACTION_LABELS[action],\n onClick: () => choose(enrollment, action),\n destructive: action === 'stop' || action === 'do_not_contact',\n disabled: busy === enrollment.id,\n }))}\n />\n )\n const nextSend = (enrollment: OutreachEnrollment) =>\n enrollment.status === 'active' || enrollment.status === 'paused'\n ? formatOutreachTime(enrollment.nextDueAtMs, timeZone)\n : '—'\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Typography variant=\"body2\">\n {enrollment.contactName || enrollment.email}\n </Typography>\n {enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n )\n\n const loaded = enrollments.data\n const pageRows = loaded.slice(page * pageSize, (page + 1) * pageSize)\n const lastLoadedPage = Math.max(0, Math.ceil(loaded.length / pageSize) - 1)\n /** Past the rows read so far, the next page reads another window first. */\n const turnTo = (next: number) => {\n if ((next + 1) * pageSize > loaded.length && enrollments.hasMore)\n enrollments.showMore()\n setPage(next)\n }\n\n return (\n <Stack spacing={1.5}>\n {narrow ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Enrollments\"\n >\n {pageRows.map((enrollment) => (\n <Card key={enrollment.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n >\n <Stack sx={{ flexGrow: 1, minWidth: 0 }}>\n {person(enrollment)}\n </Stack>\n <OutreachEnrollmentStatusChip status={enrollment.status} />\n {rowActions(enrollment)}\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Next send: ${nextSend(enrollment)} · Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`}\n </Typography>\n {outreachStopLabel(enrollment) ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachStopLabel(enrollment)}\n </Typography>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <ListTable\n aria-label=\"Enrollments\"\n columns={columns(rowActions)}\n rows={pageRows.map((enrollment) => ({\n $id: enrollment.id,\n enrollment,\n status: enrollment.status,\n step: outreachCurrentStepLabel(enrollment, steps),\n nextSend: nextSend(enrollment),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n stopReason: outreachStopLabel(enrollment) || '—',\n }))}\n quickFilter={false}\n hideFooter\n />\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={enrollments.hasMore ? undefined : loaded.length}\n hasMore={enrollments.hasMore || page < lastLoadedPage}\n onPageChange={turnTo}\n onPageSizeChange={setPageSize}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n {confirming ? (\n <>\n <DialogTitle>{CONFIRM[confirming.action]?.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>\n {CONFIRM[confirming.action]?.body}\n </DialogContentText>\n <TextField\n label=\"Why (optional)\"\n value={detail}\n onChange={(event) => setDetail(event.target.value)}\n fullWidth\n slotProps={{ htmlInput: { maxLength: 200 } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const { enrollment, action } = confirming\n setConfirming(null)\n void act(enrollment, action, detail.trim() || undefined)\n }}\n >\n {CONFIRM[confirming.action]?.label}\n </Button>\n </DialogActions>\n </>\n ) : null}\n </Dialog>\n </Stack>\n )\n}\nOutreachEnrollmentsTable.displayName = 'OutreachEnrollmentsTable'\n\nexport default OutreachEnrollmentsTable\n"],"names":["EmptyStateComponent","ListPagination","ListRowActions","ListTable","listActionsColumn","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Button","Card","CardContent","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useMediaQuery","useTheme","useState","OUTREACH_TASK_KIND_LABELS","formatOutreachTime","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","actionsFor","actions","push","ACTION_LABELS","pause","resume","stop","do_not_contact","DONE_LABELS","CONFIRM","title","body","label","columns","rowActions","field","headerName","flex","minWidth","sortable","renderCell","row","spacing","sx","justifyContent","height","variant","noWrap","contactName","email","color","width","OutreachEnrollmentsTable","props","enrollments","timeZone","api","theme","narrow","breakpoints","down","enqueueSnackbar","confirming","setConfirming","detail","setDetail","busy","setBusy","page","setPage","pageSize","setPageSize","act","action","why","id","answer","actOnEnrollment","others","stoppedOthers","changed","error","message","allowDuplicate","choose","what","data","description","enrollAction","items","map","key","onClick","destructive","disabled","nextSend","nextDueAtMs","person","loaded","pageRows","slice","lastLoadedPage","ceil","turnTo","next","hasMore","showMore","component","listStyle","p","m","aria-label","direction","alignItems","flexGrow","rows","$id","lastActivity","quickFilter","hideFooter","rowCount","count","undefined","onPageChange","onPageSizeChange","open","Boolean","onClose","fullWidth","maxWidth","value","onChange","event","target","slotProps","htmlInput","maxLength","trim","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/enrollments-table.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport EmptyStateComponent from '@aglyn/shared-ui-jsx/components/empty-state.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n type ListTableProps,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport { TABLE_PAGE_SIZE_DEFAULT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Card,\n CardContent,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n Stack,\n TextField,\n Typography,\n useMediaQuery,\n useTheme,\n Chip,\n} from '@mui/material'\nimport { type ReactNode, useState } from 'react'\nimport type { OutreachEnrollmentAction } from '../model/outreach-api'\nimport {\n OUTREACH_TASK_KIND_LABELS,\n type OutreachEnrollment,\n type OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n formatOutreachTime,\n OUTREACH_STOP_REASON_LABELS,\n OutreachEnrollmentStatusChip,\n OutreachLoading,\n OutreachLoadProblem,\n} from './outreach-ui'\nimport type { OutreachApi } from './use-outreach-api'\nimport type { OutreachEnrollmentsLoad } from './use-outreach-data'\n\nexport interface OutreachEnrollmentsTableProps {\n enrollments: OutreachEnrollmentsLoad\n steps: readonly OutreachSequenceStep[]\n /** The mailbox's IANA zone, which next sends are read in; `null` for the reader's own. */\n timeZone: string | null\n api: OutreachApi\n /** Shown in the empty state when the sequence takes people. */\n enrollAction?: ReactNode\n}\n\n/** The step an enrollment is on, as a person reads it. */\nexport function outreachCurrentStepLabel(\n enrollment: Pick<OutreachEnrollment, 'status' | 'stepIndex'>,\n steps: readonly OutreachSequenceStep[],\n): string {\n if (enrollment.status === 'finished' || enrollment.stepIndex >= steps.length)\n return 'All steps done'\n const step = steps[enrollment.stepIndex]\n const kind =\n step?.kind === 'email'\n ? 'Email'\n : step?.kind === 'task'\n ? OUTREACH_TASK_KIND_LABELS[step.taskKind]\n : 'Step'\n return `Step ${enrollment.stepIndex + 1} of ${steps.length} · ${kind}`\n}\n\n/** Why an enrollment stopped, as the table says it; `''` while it runs. */\nexport function outreachStopLabel(\n enrollment: Pick<OutreachEnrollment, 'stopReason' | 'stopDetail'>,\n): string {\n if (!enrollment.stopReason) return ''\n const reason = OUTREACH_STOP_REASON_LABELS[enrollment.stopReason] ?? ''\n return enrollment.stopDetail ? `${reason} — ${enrollment.stopDetail}` : reason\n}\n\n/** The last thing that happened: a send, a stop, or the enrollment itself. */\nfunction lastActivityMs(enrollment: OutreachEnrollment): number {\n return Math.max(\n enrollment.lastSentAtMs ?? 0,\n enrollment.stoppedAtMs ?? 0,\n enrollment.createdAtMs ?? 0,\n )\n}\n\n/** What a member may do to an enrollment in the status it is in. */\nfunction actionsFor(\n enrollment: OutreachEnrollment,\n): OutreachEnrollmentAction[] {\n const actions: OutreachEnrollmentAction[] = []\n if (enrollment.status === 'active') actions.push('pause', 'stop')\n if (enrollment.status === 'paused') actions.push('resume', 'stop')\n if (enrollment.status !== 'opted_out') actions.push('do_not_contact')\n return actions\n}\n\nconst ACTION_LABELS: Record<OutreachEnrollmentAction, string> = {\n pause: 'Pause',\n resume: 'Resume',\n stop: 'Stop',\n do_not_contact: 'Mark do-not-contact',\n}\n\n/** What the snackbar says once an action lands. */\nconst DONE_LABELS: Record<\n Exclude<OutreachEnrollmentAction, 'do_not_contact'>,\n string\n> = {\n pause: 'Paused.',\n resume: 'Resumed.',\n stop: 'Stopped.',\n}\n\n/** The two actions that end something for good ask first. */\nconst CONFIRM: Partial<\n Record<\n OutreachEnrollmentAction,\n { title: string; body: string; label: string }\n >\n> = {\n stop: {\n title: 'Stop this enrollment?',\n body: 'They won’t get any more steps from this sequence, and they can’t be enrolled in it again.',\n label: 'Stop',\n },\n do_not_contact: {\n title: 'Mark do-not-contact?',\n body:\n 'The address goes on your organization’s do-not-contact list, which every sequence checks before ' +\n 'every send, and they are stopped in every sequence they are in.',\n label: 'Mark do-not-contact',\n },\n}\n\n/** The table's columns; the person and the actions are drawn from the row's enrollment. */\nfunction columns(\n rowActions: (enrollment: OutreachEnrollment) => ReactNode,\n): NonNullable<ListTableProps['columns']> {\n return [\n {\n field: 'person',\n headerName: 'Person',\n flex: 1,\n minWidth: 200,\n sortable: false,\n renderCell: ({ row }) => (\n <Stack\n spacing={0}\n sx={{ justifyContent: 'center', height: '100%', minWidth: 0 }}\n >\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center', minWidth: 0 }}>\n <Typography variant=\"body2\" noWrap>\n {row.enrollment.contactName || row.enrollment.email}\n </Typography>\n {row.enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {row.enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\" noWrap>\n {row.enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n ),\n },\n {\n field: 'status',\n headerName: 'Status',\n width: 120,\n renderCell: ({ row }) => (\n <OutreachEnrollmentStatusChip status={row.status} />\n ),\n },\n { field: 'step', headerName: 'Current step', flex: 1, minWidth: 170 },\n { field: 'nextSend', headerName: 'Next send', width: 190, sortable: false },\n {\n field: 'lastActivity',\n headerName: 'Last activity',\n width: 190,\n sortable: false,\n },\n { field: 'stopReason', headerName: 'Stop reason', flex: 1, minWidth: 170 },\n listActionsColumn((row) => rowActions(row.enrollment), { width: 72 }),\n ]\n}\n\n/**\n * A sequence's enrollments (AGL-2980): who is in it, where each person\n * stands, when their next step goes out in the mailbox's timezone, and the\n * four things a member can do about it — pause, resume, stop, and mark\n * do-not-contact. A table on wider screens, a list of cards on a phone.\n */\nexport function OutreachEnrollmentsTable(props: OutreachEnrollmentsTableProps) {\n const { enrollments, steps, timeZone, api } = props\n const theme = useTheme()\n const narrow = useMediaQuery(theme.breakpoints.down('md'))\n const { enqueueSnackbar } = useSnackbar()\n const [confirming, setConfirming] = useState<{\n enrollment: OutreachEnrollment\n action: OutreachEnrollmentAction\n } | null>(null)\n const [detail, setDetail] = useState('')\n const [busy, setBusy] = useState<string | null>(null)\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n\n const act = async (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n why?: string,\n ) => {\n setBusy(enrollment.id)\n try {\n const answer = await api.actOnEnrollment(enrollment.id, action, why)\n const others = answer.stoppedOthers\n enqueueSnackbar(\n action === 'do_not_contact'\n ? `Marked do-not-contact${others ? `, and stopped in ${others} other ${others === 1 ? 'sequence' : 'sequences'}` : ''}.`\n : answer.changed\n ? DONE_LABELS[action]\n : 'Nothing to change.',\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setBusy(null)\n }\n }\n\n const choose = (\n enrollment: OutreachEnrollment,\n action: OutreachEnrollmentAction,\n ) => {\n if (CONFIRM[action]) {\n setDetail('')\n setConfirming({ enrollment, action })\n } else {\n void act(enrollment, action)\n }\n }\n\n if (enrollments.status === 'loading')\n return <OutreachLoading label=\"Loading enrollments…\" />\n if (enrollments.status === 'error' || enrollments.status === 'refused') {\n return (\n <OutreachLoadProblem status={enrollments.status} what=\"enrollments\" />\n )\n }\n if (!enrollments.data.length) {\n return (\n <Card variant=\"outlined\">\n <EmptyStateComponent\n label=\"Nobody is enrolled yet\"\n description=\"People you enroll show here, with where each of them is in the sequence.\"\n action={props.enrollAction}\n />\n </Card>\n )\n }\n\n const rowActions = (enrollment: OutreachEnrollment) => (\n <ListRowActions\n label={enrollment.contactName || enrollment.email}\n items={actionsFor(enrollment).map((action) => ({\n key: action,\n label: ACTION_LABELS[action],\n onClick: () => choose(enrollment, action),\n destructive: action === 'stop' || action === 'do_not_contact',\n disabled: busy === enrollment.id,\n }))}\n />\n )\n const nextSend = (enrollment: OutreachEnrollment) =>\n enrollment.status === 'active' || enrollment.status === 'paused'\n ? formatOutreachTime(enrollment.nextDueAtMs, timeZone)\n : '—'\n const person = (enrollment: OutreachEnrollment) => (\n <Stack spacing={0}>\n <Stack direction=\"row\" spacing={0.75} sx={{ alignItems: 'center' }}>\n <Typography variant=\"body2\">\n {enrollment.contactName || enrollment.email}\n </Typography>\n {enrollment.target === 'lead' ? (\n <Chip size=\"small\" variant=\"outlined\" label=\"Lead\" />\n ) : null}\n </Stack>\n {enrollment.contactName ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {enrollment.email}\n </Typography>\n ) : null}\n </Stack>\n )\n\n const loaded = enrollments.data\n const pageRows = loaded.slice(page * pageSize, (page + 1) * pageSize)\n const lastLoadedPage = Math.max(0, Math.ceil(loaded.length / pageSize) - 1)\n /** Past the rows read so far, the next page reads another window first. */\n const turnTo = (next: number) => {\n if ((next + 1) * pageSize > loaded.length && enrollments.hasMore)\n enrollments.showMore()\n setPage(next)\n }\n\n return (\n <Stack spacing={1.5}>\n {narrow ? (\n <Stack\n spacing={1}\n component=\"ul\"\n sx={{ listStyle: 'none', p: 0, m: 0 }}\n aria-label=\"Enrollments\"\n >\n {pageRows.map((enrollment) => (\n <Card key={enrollment.id} variant=\"outlined\" component=\"li\">\n <CardContent>\n <Stack spacing={1}>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'flex-start' }}\n >\n <Stack sx={{ flexGrow: 1, minWidth: 0 }}>\n {person(enrollment)}\n </Stack>\n <OutreachEnrollmentStatusChip status={enrollment.status} />\n {rowActions(enrollment)}\n </Stack>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachCurrentStepLabel(enrollment, steps)}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Next send: ${nextSend(enrollment)} · Last activity: ${formatOutreachTime(lastActivityMs(enrollment), timeZone)}`}\n </Typography>\n {outreachStopLabel(enrollment) ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {outreachStopLabel(enrollment)}\n </Typography>\n ) : null}\n </Stack>\n </CardContent>\n </Card>\n ))}\n </Stack>\n ) : (\n <ListTable\n aria-label=\"Enrollments\"\n columns={columns(rowActions)}\n rows={pageRows.map((enrollment) => ({\n $id: enrollment.id,\n enrollment,\n status: enrollment.status,\n step: outreachCurrentStepLabel(enrollment, steps),\n nextSend: nextSend(enrollment),\n lastActivity: formatOutreachTime(\n lastActivityMs(enrollment),\n timeZone,\n ),\n stopReason: outreachStopLabel(enrollment) || '—',\n }))}\n quickFilter={false}\n hideFooter\n />\n )}\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={pageRows.length}\n count={enrollments.hasMore ? undefined : loaded.length}\n hasMore={enrollments.hasMore || page < lastLoadedPage}\n onPageChange={turnTo}\n onPageSizeChange={setPageSize}\n />\n\n <Dialog\n open={Boolean(confirming)}\n onClose={() => setConfirming(null)}\n fullWidth\n maxWidth=\"xs\"\n >\n {confirming ? (\n <>\n <DialogTitle>{CONFIRM[confirming.action]?.title}</DialogTitle>\n <DialogContent>\n <Stack spacing={2}>\n <DialogContentText>\n {CONFIRM[confirming.action]?.body}\n </DialogContentText>\n <TextField\n label=\"Why (optional)\"\n value={detail}\n onChange={(event) => setDetail(event.target.value)}\n fullWidth\n slotProps={{ htmlInput: { maxLength: 200 } }}\n />\n </Stack>\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setConfirming(null)}>Cancel</Button>\n <Button\n color=\"error\"\n variant=\"contained\"\n onClick={() => {\n const { enrollment, action } = confirming\n setConfirming(null)\n void act(enrollment, action, detail.trim() || undefined)\n }}\n >\n {CONFIRM[confirming.action]?.label}\n </Button>\n </DialogActions>\n </>\n ) : null}\n </Dialog>\n </Stack>\n )\n}\nOutreachEnrollmentsTable.displayName = 'OutreachEnrollmentsTable'\n\nexport default OutreachEnrollmentsTable\n"],"names":["EmptyStateComponent","ListPagination","ListRowActions","ListTable","listActionsColumn","TABLE_PAGE_SIZE_DEFAULT","useSnackbar","Button","Card","CardContent","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","Stack","TextField","Typography","useMediaQuery","useTheme","Chip","useState","OUTREACH_TASK_KIND_LABELS","formatOutreachTime","OUTREACH_STOP_REASON_LABELS","OutreachEnrollmentStatusChip","OutreachLoading","OutreachLoadProblem","outreachCurrentStepLabel","enrollment","steps","status","stepIndex","length","step","kind","taskKind","outreachStopLabel","stopReason","reason","stopDetail","lastActivityMs","Math","max","lastSentAtMs","stoppedAtMs","createdAtMs","actionsFor","actions","push","ACTION_LABELS","pause","resume","stop","do_not_contact","DONE_LABELS","CONFIRM","title","body","label","columns","rowActions","field","headerName","flex","minWidth","sortable","renderCell","row","spacing","sx","justifyContent","height","direction","alignItems","variant","noWrap","contactName","email","target","size","color","width","OutreachEnrollmentsTable","props","enrollments","timeZone","api","theme","narrow","breakpoints","down","enqueueSnackbar","confirming","setConfirming","detail","setDetail","busy","setBusy","page","setPage","pageSize","setPageSize","act","action","why","id","answer","actOnEnrollment","others","stoppedOthers","changed","error","message","allowDuplicate","choose","what","data","description","enrollAction","items","map","key","onClick","destructive","disabled","nextSend","nextDueAtMs","person","loaded","pageRows","slice","lastLoadedPage","ceil","turnTo","next","hasMore","showMore","component","listStyle","p","m","aria-label","flexGrow","rows","$id","lastActivity","quickFilter","hideFooter","rowCount","count","undefined","onPageChange","onPageSizeChange","open","Boolean","onClose","fullWidth","maxWidth","value","onChange","event","slotProps","htmlInput","maxLength","trim","displayName"],"mappings":"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"}
@@ -27,6 +27,9 @@ export declare const MAILBOX_ACTION_LABELS: {
27
27
  readonly pause: "Pause";
28
28
  readonly resume: "Resume";
29
29
  readonly test: "Send a test to myself";
30
+ readonly testElsewhere: "Send a test";
31
+ readonly testAddress: "Test address";
32
+ readonly warmUp: "Warm up gradually";
30
33
  readonly disconnect: "Disconnect";
31
34
  readonly reconnect: "Reconnect";
32
35
  };
@@ -19,7 +19,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
19
19
  import { pluginDocsHelp } from "@aglyn/aglyn";
20
20
  import { CardDisplay, useConfirmationContext } from "@aglyn/shared-ui-jsx";
21
21
  import { useSnackbar } from "@aglyn/shared-ui-snackstack";
22
- import { Alert, Autocomplete, Box, Button, Chip, FormHelperText, MenuItem, Stack, TextField, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material";
22
+ import { Alert, Autocomplete, Box, Button, Chip, FormControlLabel, FormHelperText, MenuItem, Stack, Switch, TextField, ToggleButton, ToggleButtonGroup, Typography } from "@mui/material";
23
23
  import { useEffect, useMemo, useRef, useState } from "react";
24
24
  import { formatMinuteOfDay, isValidTimezone, OUTREACH_DEFAULT_WINDOW, OUTREACH_MAILBOX_STATUS_LABELS, OUTREACH_MAX_DAILY_CAP, OUTREACH_RAMP_STEPS, OUTREACH_WEEKDAY_LABELS, outreachEffectiveDailyCap, summarizeMailboxHealth, validateDailyCap, validateDisplayName, validateSendWindow } from "../mailboxes/mailbox-settings.js";
25
25
  /**
@@ -51,6 +51,9 @@ import { formatMinuteOfDay, isValidTimezone, OUTREACH_DEFAULT_WINDOW, OUTREACH_M
51
51
  pause: 'Pause',
52
52
  resume: 'Resume',
53
53
  test: 'Send a test to myself',
54
+ testElsewhere: 'Send a test',
55
+ testAddress: 'Test address',
56
+ warmUp: 'Warm up gradually',
54
57
  disconnect: 'Disconnect',
55
58
  reconnect: 'Reconnect'
56
59
  };
@@ -91,7 +94,8 @@ const draftOf = (mailbox)=>{
91
94
  displayName: (_mailbox_displayName = mailbox.displayName) != null ? _mailbox_displayName : '',
92
95
  dailyCap: String((_mailbox_dailyCap = mailbox.dailyCap) != null ? _mailbox_dailyCap : ''),
93
96
  window: (_mailbox_window = mailbox.window) != null ? _mailbox_window : OUTREACH_DEFAULT_WINDOW,
94
- timezone: (_mailbox_timezone = mailbox.timezone) != null ? _mailbox_timezone : 'UTC'
97
+ timezone: (_mailbox_timezone = mailbox.timezone) != null ? _mailbox_timezone : 'UTC',
98
+ warmUp: typeof mailbox.rampStartedAtMs === 'number'
95
99
  };
96
100
  };
97
101
  /**
@@ -103,7 +107,7 @@ const draftOf = (mailbox)=>{
103
107
  * org owner or admin, and the test and the reconnect to its member alone,
104
108
  * because both act in that member's own Google account.
105
109
  */ export function MailboxCard(props) {
106
- var _OUTREACH_MAILBOX_STATUS_LABELS_mailbox_status, _STATUS_COLOR_mailbox_status;
110
+ var _mailbox_rampStartedAtMs, _OUTREACH_MAILBOX_STATUS_LABELS_mailbox_status, _STATUS_COLOR_mailbox_status;
107
111
  var _mailbox_sendAsOptions;
108
112
  const { mailbox, isMine, canManage, api, onReconnect, reconnecting = false, nowMs = Date.now() } = props;
109
113
  const { enqueueSnackbar } = useSnackbar();
@@ -112,6 +116,9 @@ const draftOf = (mailbox)=>{
112
116
  const [saving, setSaving] = useState(false);
113
117
  const [saveError, setSaveError] = useState(null);
114
118
  const [busy, setBusy] = useState(null);
119
+ // Where a test goes when not to the member themself (AGL-3228): a test to
120
+ // one's own address never leaves Google and shows no authentication result.
121
+ const [testAddress, setTestAddress] = useState('');
115
122
  // A save here, a change made from another tab, or a reconnect rewrites the
116
123
  // stored settings; the form follows them, unless the member has unsaved
117
124
  // edits of their own, which are never thrown away.
@@ -139,7 +146,8 @@ const draftOf = (mailbox)=>{
139
146
  const invalid = typeof capCheck !== 'number' || !('days' in windowCheck) || typeof nameCheck !== 'string' || !timezoneValid;
140
147
  const todayLimit = outreachEffectiveDailyCap({
141
148
  dailyCap: typeof capCheck === 'number' ? capCheck : mailbox.dailyCap,
142
- rampStartedAtMs: mailbox.rampStartedAtMs,
149
+ // A ramp switched on in the draft reads as starting today.
150
+ rampStartedAtMs: draft.warmUp ? (_mailbox_rampStartedAtMs = mailbox.rampStartedAtMs) != null ? _mailbox_rampStartedAtMs : nowMs : null,
143
151
  timezone: timezoneValid ? draft.timezone : mailbox.timezone
144
152
  }, nowMs);
145
153
  const save = async ()=>{
@@ -158,6 +166,8 @@ const draftOf = (mailbox)=>{
158
166
  window: draft.window
159
167
  } : {}, draft.timezone !== stored.timezone ? {
160
168
  timezone: draft.timezone
169
+ } : {}, draft.warmUp !== stored.warmUp ? {
170
+ warmUp: draft.warmUp
161
171
  } : {}));
162
172
  enqueueSnackbar('Mailbox settings saved', {
163
173
  variant: 'success'
@@ -188,8 +198,9 @@ const draftOf = (mailbox)=>{
188
198
  const sendTest = async ()=>{
189
199
  setBusy('test');
190
200
  try {
191
- const sent = await api.sendTest(mailbox.id);
192
- enqueueSnackbar(`Test sent to ${sent.sentTo}. Check your inbox.`, {
201
+ const to = testAddress.trim();
202
+ const sent = await api.sendTest(mailbox.id, to || undefined);
203
+ enqueueSnackbar(to ? `Test sent to ${sent.sentTo}. Its original source shows whether the sender authenticated.` : `Test sent to ${sent.sentTo}. Check your inbox.`, {
193
204
  variant: 'success'
194
205
  });
195
206
  } catch (error) {
@@ -372,7 +383,22 @@ const draftOf = (mailbox)=>{
372
383
  step: 1
373
384
  }
374
385
  },
375
- helperText: typeof capCheck !== 'number' ? capCheck.message : `${RAMP_SENTENCE} Today’s limit: ${todayLimit}.`
386
+ helperText: typeof capCheck !== 'number' ? capCheck.message : draft.warmUp ? `${RAMP_SENTENCE} Today’s limit: ${todayLimit}.` : `No warm-up: today’s limit is the cap, ${todayLimit}.`
387
+ }),
388
+ /*#__PURE__*/ _jsx(FormControlLabel, {
389
+ control: /*#__PURE__*/ _jsx(Switch, {
390
+ checked: draft.warmUp,
391
+ onChange: (event)=>setDraft(_extends({}, draft, {
392
+ warmUp: event.target.checked
393
+ }))
394
+ }),
395
+ label: MAILBOX_ACTION_LABELS.warmUp
396
+ }),
397
+ /*#__PURE__*/ _jsx(FormHelperText, {
398
+ sx: {
399
+ mt: -1
400
+ },
401
+ children: 'For a mailbox that is new or has not sent much. One that has sent mail for years already has ' + 'its reputation and can start at its cap.'
376
402
  }),
377
403
  /*#__PURE__*/ _jsxs(Box, {
378
404
  children: [
@@ -479,6 +505,16 @@ const draftOf = (mailbox)=>{
479
505
  color: "text.secondary",
480
506
  children: `Sends as ${sendAsLabel(stored.sendAs)}, at most ${stored.dailyCap} a day, ` + `${stored.window.days.map((day)=>OUTREACH_WEEKDAY_LABELS[day]).join(', ')} ` + `${formatMinuteOfDay(stored.window.startMinute)}–${formatMinuteOfDay(stored.window.endMinute)} ` + `(${stored.timezone}).`
481
507
  }),
508
+ isMine && !needsReconnect ? /*#__PURE__*/ _jsx(TextField, {
509
+ label: MAILBOX_ACTION_LABELS.testAddress,
510
+ value: testAddress,
511
+ onChange: (event)=>setTestAddress(event.target.value),
512
+ disabled: busy !== null,
513
+ size: "small",
514
+ type: "email",
515
+ helperText: 'Leave empty to send the test to yourself. A test to an outside mailbox you can ' + 'read is the only one whose original source shows the sender authentication results.',
516
+ fullWidth: true
517
+ }) : null,
482
518
  canManage || isMine ? /*#__PURE__*/ _jsxs(Stack, {
483
519
  direction: "row",
484
520
  spacing: 1,
@@ -497,7 +533,7 @@ const draftOf = (mailbox)=>{
497
533
  variant: "outlined",
498
534
  disabled: busy !== null,
499
535
  onClick: ()=>void sendTest(),
500
- children: MAILBOX_ACTION_LABELS.test
536
+ children: testAddress.trim() ? MAILBOX_ACTION_LABELS.testElsewhere : MAILBOX_ACTION_LABELS.test
501
537
  }) : null,
502
538
  canManage ? /*#__PURE__*/ _jsx(Button, {
503
539
  color: "error",