@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/mailbox-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 {\n Alert,\n Autocomplete,\n Box,\n Button,\n Chip,\n FormHelperText,\n MenuItem,\n Stack,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n formatMinuteOfDay,\n isValidTimezone,\n OUTREACH_DEFAULT_WINDOW,\n OUTREACH_MAILBOX_STATUS_LABELS,\n OUTREACH_MAX_DAILY_CAP,\n OUTREACH_RAMP_STEPS,\n OUTREACH_WEEKDAY_LABELS,\n outreachEffectiveDailyCap,\n summarizeMailboxHealth,\n validateDailyCap,\n validateDisplayName,\n validateSendWindow,\n} from '../mailboxes/mailbox-settings'\nimport type {\n OutreachMailbox,\n OutreachMailboxStatus,\n OutreachSendWindow,\n} from '../model/outreach.types'\nimport type { OutreachMailboxApi } from './use-outreach-mailbox-api'\n\nexport interface MailboxCardProps {\n mailbox: OutreachMailbox\n /** The viewer is the member who connected it. */\n isMine: boolean\n /** The viewer may change, pause and disconnect it: its member, or an org owner or admin. */\n canManage: boolean\n api: OutreachMailboxApi\n /** Starts a new Google connect, for a mailbox that needs reconnecting. */\n onReconnect(): void\n /** True while that connect is on its way to Google. */\n reconnecting?: boolean\n /** The clock the health window and today's limit are read on. */\n nowMs?: number\n}\n\n/**\n * What the card says about a mailbox that paused ITSELF (AGL-2981): the\n * engine's own sentence, when it paused, and — after a complaint — the day\n * resuming stops being premature. Nothing sends from it until a member\n * resumes it.\n */\nexport function autoPauseSentence(mailbox: Pick<OutreachMailbox, 'autoPause' | 'timezone'>): string {\n const pause = mailbox.autoPause\n if (!pause) return ''\n const day = (atMs: number) => {\n try {\n return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeZone: mailbox.timezone }).format(atMs)\n } catch {\n return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeZone: 'UTC' }).format(atMs)\n }\n }\n const until = typeof pause.untilMs === 'number' ? ` Wait until ${day(pause.untilMs)} before resuming it.` : ''\n return `Sequences paused this mailbox on ${day(pause.atMs)}. ${pause.message}${until} Nothing sends from it until it is resumed.`\n}\n\n/** Accessible names of the card's actions, spelled once for the specs. */\nexport const MAILBOX_ACTION_LABELS = {\n save: 'Save settings',\n pause: 'Pause',\n resume: 'Resume',\n test: 'Send a test to myself',\n disconnect: 'Disconnect',\n reconnect: 'Reconnect',\n} as const\n\nconst WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']\n\nconst STATUS_COLOR: Record<OutreachMailboxStatus, 'success' | 'default' | 'warning' | 'error'> = {\n connected: 'success',\n paused: 'default',\n reconnect_required: 'warning',\n disconnected: 'error',\n}\n\n/** Every half hour of the day, and midnight at its end. */\nconst MINUTE_OPTIONS = Array.from({ length: 49 }, (_, index) => index * 30)\n\n/** The zones this runtime knows, the mailbox's own included whatever it is. */\nfunction timezoneOptions(current: string): string[] {\n const supported = (Intl as { supportedValuesOf?: (key: string) => string[] }).supportedValuesOf?.('timeZone') ?? []\n const zones = supported.length ? supported : ['UTC']\n return zones.includes(current) ? zones : [current, ...zones]\n}\n\n/** The ramp, as the cap's helper text reads it. */\nconst RAMP_SENTENCE = `Warm-up: ${OUTREACH_RAMP_STEPS.map((step, index) =>\n `week ${step.week}${index === OUTREACH_RAMP_STEPS.length - 1 ? '+' : ''}: ${step.limit}`,\n).join(' · ')} a day, never above the cap.`\n\ninterface Draft {\n sendAs: string\n displayName: string\n dailyCap: string\n window: OutreachSendWindow\n timezone: string\n}\n\nconst draftOf = (mailbox: OutreachMailbox): Draft => ({\n sendAs: mailbox.sendAs ?? mailbox.email,\n displayName: mailbox.displayName ?? '',\n dailyCap: String(mailbox.dailyCap ?? ''),\n window: mailbox.window ?? OUTREACH_DEFAULT_WINDOW,\n timezone: mailbox.timezone ?? 'UTC',\n})\n\n/**\n * One connected mailbox (AGL-2978): its account and status, its last seven\n * days, its settings, and pause, test and disconnect.\n *\n * Every control is offered only where the route will allow it: the settings\n * form and pause and disconnect to the member who connected the mailbox or an\n * org owner or admin, and the test and the reconnect to its member alone,\n * because both act in that member's own Google account.\n */\nexport function MailboxCard(props: MailboxCardProps) {\n const { mailbox, isMine, canManage, api, onReconnect, reconnecting = false, nowMs = Date.now() } = props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [draft, setDraft] = useState<Draft>(() => draftOf(mailbox))\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n const [busy, setBusy] = useState<'status' | 'test' | 'disconnect' | null>(null)\n\n // A save here, a change made from another tab, or a reconnect rewrites the\n // stored settings; the form follows them, unless the member has unsaved\n // edits of their own, which are never thrown away.\n const stored = useMemo(() => draftOf(mailbox), [mailbox])\n const storedKey = JSON.stringify(stored)\n const seededFrom = useRef(storedKey)\n const dirty = JSON.stringify(draft) !== storedKey\n useEffect(() => {\n if (seededFrom.current === storedKey) return\n const previous = seededFrom.current\n seededFrom.current = storedKey\n setDraft((current) => (JSON.stringify(current) === previous ? stored : current))\n }, [storedKey, stored])\n\n const health = summarizeMailboxHealth(mailbox.health, mailbox.timezone, nowMs)\n const needsReconnect = mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected'\n const capCheck = validateDailyCap(draft.dailyCap === '' ? Number.NaN : Number(draft.dailyCap))\n const windowCheck = validateSendWindow(draft.window)\n const nameCheck = validateDisplayName(draft.displayName)\n const timezoneValid = isValidTimezone(draft.timezone)\n const invalid =\n typeof capCheck !== 'number' || !('days' in windowCheck) || typeof nameCheck !== 'string' || !timezoneValid\n const todayLimit = outreachEffectiveDailyCap(\n {\n dailyCap: typeof capCheck === 'number' ? capCheck : mailbox.dailyCap,\n rampStartedAtMs: mailbox.rampStartedAtMs,\n timezone: timezoneValid ? draft.timezone : mailbox.timezone,\n },\n nowMs,\n )\n\n const save = async () => {\n setSaving(true)\n setSaveError(null)\n try {\n await api.saveSettings({\n mailboxId: mailbox.id,\n ...(draft.sendAs !== stored.sendAs ? { sendAs: draft.sendAs } : {}),\n ...(draft.displayName !== stored.displayName ? { displayName: draft.displayName } : {}),\n ...(draft.dailyCap !== stored.dailyCap ? { dailyCap: Number(draft.dailyCap) } : {}),\n ...(JSON.stringify(draft.window) !== JSON.stringify(stored.window) ? { window: draft.window } : {}),\n ...(draft.timezone !== stored.timezone ? { timezone: draft.timezone } : {}),\n })\n enqueueSnackbar('Mailbox settings saved', { variant: 'success' })\n } catch (error) {\n setSaveError((error as Error).message)\n } finally {\n setSaving(false)\n }\n }\n\n const togglePaused = async () => {\n const paused = mailbox.status !== 'paused'\n setBusy('status')\n try {\n await api.setPaused(mailbox.id, paused)\n enqueueSnackbar(paused ? 'Mailbox paused' : 'Mailbox resumed', { variant: 'success' })\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n }\n\n const sendTest = async () => {\n setBusy('test')\n try {\n const sent = await api.sendTest(mailbox.id)\n enqueueSnackbar(`Test sent to ${sent.sentTo}. Check your inbox.`, { variant: 'success' })\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n }\n\n const disconnect = async () => {\n const confirmed = await confirm({\n title: 'Disconnect this mailbox?',\n description:\n 'Disconnecting deletes the access stored for this mailbox and asks Google to revoke it. ' +\n 'Nothing is sent from it again unless it is connected again.',\n confirmationText: MAILBOX_ACTION_LABELS.disconnect,\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n setBusy('disconnect')\n try {\n const { revocation } = await api.disconnect(mailbox.id)\n if (revocation === 'failed') {\n enqueueSnackbar(\n 'Mailbox disconnected, but Google could not confirm the access was revoked. ' +\n 'Remove it from the Google Account’s third-party connections to be sure.',\n { variant: 'warning', persist: true },\n )\n } else if (revocation === 'kept-for-other-mailbox') {\n enqueueSnackbar(\n 'Mailbox disconnected. Google access stays in place for another connected mailbox on the same account.',\n { variant: 'success' },\n )\n } else {\n enqueueSnackbar('Mailbox disconnected', { variant: 'success' })\n }\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n setBusy(null)\n }\n }\n\n const sendAsLabel = (email: string) => {\n const option = mailbox.sendAsOptions?.find((entry) => entry.email === email)\n return option?.displayName ? `${option.displayName} <${option.email}>` : email\n }\n\n return (\n <CardDisplay\n header={mailbox.email}\n subheader={isMine ? 'Your mailbox' : 'A teammate’s mailbox'}\n help={pluginDocsHelp('sequences', { anchor: '#connect-a-mailbox' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Chip\n size=\"small\"\n label={OUTREACH_MAILBOX_STATUS_LABELS[mailbox.status] ?? mailbox.status}\n color={STATUS_COLOR[mailbox.status] ?? 'default'}\n />\n ),\n }}\n >\n <Stack spacing={2}>\n {needsReconnect ? (\n <Alert\n severity=\"warning\"\n action={\n isMine ? (\n <Button color=\"inherit\" size=\"small\" disabled={reconnecting} onClick={onReconnect}>\n {MAILBOX_ACTION_LABELS.reconnect}\n </Button>\n ) : undefined\n }\n >\n {`${\n mailbox.status === 'disconnected'\n ? 'This mailbox was disconnected.'\n : 'Google stopped accepting this mailbox’s connection.'\n } ${\n isMine\n ? 'Nothing sends from it until you connect it again.'\n : 'Only the member who connected it can reconnect it.'\n }`}\n </Alert>\n ) : null}\n {mailbox.status === 'paused' && mailbox.autoPause ? (\n <Alert severity=\"error\" role=\"alert\">\n {autoPauseSentence(mailbox)}\n </Alert>\n ) : null}\n\n <Box>\n <Typography variant=\"subtitle2\">Last 7 days</Typography>\n {health.hasSent ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n No sends yet\n </Typography>\n )}\n <Stack direction=\"row\" spacing={3} sx={{ mt: 0.5 }}>\n {[\n ['Sent', health.sent],\n ['Bounces', health.bounces],\n ['Replies', health.replies],\n ].map(([label, value]) => (\n <Box key={label as string} aria-label={`${label} in the last 7 days`}>\n <Typography variant=\"h6\" component=\"p\">\n {value}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {label}\n </Typography>\n </Box>\n ))}\n </Stack>\n </Box>\n\n {canManage ? (\n <Stack spacing={2} component=\"form\" onSubmit={(event) => event.preventDefault()}>\n <TextField\n select\n size=\"small\"\n label=\"Send as\"\n value={draft.sendAs}\n onChange={(event) => setDraft({ ...draft, sendAs: event.target.value })}\n helperText=\"Addresses Gmail has verified for this account.\"\n >\n {(mailbox.sendAsOptions?.length ? mailbox.sendAsOptions : [{ email: mailbox.sendAs }]).map((option) => (\n <MenuItem key={option.email} value={option.email}>\n {sendAsLabel(option.email)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Display name\"\n value={draft.displayName}\n onChange={(event) => setDraft({ ...draft, displayName: event.target.value })}\n error={typeof nameCheck !== 'string'}\n helperText={typeof nameCheck !== 'string' ? nameCheck.message : 'The name recipients see beside the address.'}\n />\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Daily cap\"\n value={draft.dailyCap}\n onChange={(event) => setDraft({ ...draft, dailyCap: event.target.value })}\n error={typeof capCheck !== 'number'}\n slotProps={{ htmlInput: { min: 1, max: OUTREACH_MAX_DAILY_CAP, step: 1 } }}\n helperText={\n typeof capCheck !== 'number'\n ? capCheck.message\n : `${RAMP_SENTENCE} Today’s limit: ${todayLimit}.`\n }\n />\n <Box>\n <Typography variant=\"subtitle2\" gutterBottom>\n Sending window\n </Typography>\n <ToggleButtonGroup\n size=\"small\"\n aria-label=\"Sending days\"\n value={draft.window.days}\n onChange={(_event, days: number[]) =>\n setDraft({ ...draft, window: { ...draft.window, days: [...days].sort((a, b) => a - b) } })\n }\n >\n {OUTREACH_WEEKDAY_LABELS.map((label, day) => (\n <ToggleButton key={label} value={day} aria-label={WEEKDAY_NAMES[day]}>\n {label}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ mt: 2 }}>\n <TextField\n select\n size=\"small\"\n label=\"From\"\n value={draft.window.startMinute}\n onChange={(event) =>\n setDraft({ ...draft, window: { ...draft.window, startMinute: Number(event.target.value) } })\n }\n >\n {MINUTE_OPTIONS.slice(0, -1).map((minute) => (\n <MenuItem key={minute} value={minute}>\n {formatMinuteOfDay(minute)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Until\"\n value={draft.window.endMinute}\n onChange={(event) =>\n setDraft({ ...draft, window: { ...draft.window, endMinute: Number(event.target.value) } })\n }\n >\n {MINUTE_OPTIONS.slice(1).map((minute) => (\n <MenuItem key={minute} value={minute}>\n {formatMinuteOfDay(minute)}\n </MenuItem>\n ))}\n </TextField>\n <Autocomplete\n size=\"small\"\n disableClearable\n options={timezoneOptions(mailbox.timezone)}\n value={draft.timezone}\n onChange={(_event, timezone) => setDraft({ ...draft, timezone: timezone ?? draft.timezone })}\n sx={{ minWidth: 240 }}\n renderInput={(params) => <TextField {...params} label=\"Timezone\" />}\n />\n </Stack>\n {'days' in windowCheck ? null : <FormHelperText error>{windowCheck.message}</FormHelperText>}\n </Box>\n {saveError ? <FormHelperText error>{saveError}</FormHelperText> : null}\n <Box>\n <Button variant=\"contained\" disabled={!dirty || invalid || saving} onClick={() => void save()}>\n {MAILBOX_ACTION_LABELS.save}\n </Button>\n </Box>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Sends as ${sendAsLabel(stored.sendAs)}, at most ${stored.dailyCap} a day, ` +\n `${stored.window.days.map((day) => OUTREACH_WEEKDAY_LABELS[day]).join(', ')} ` +\n `${formatMinuteOfDay(stored.window.startMinute)}–${formatMinuteOfDay(stored.window.endMinute)} ` +\n `(${stored.timezone}).`}\n </Typography>\n )}\n\n {canManage || isMine ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {canManage && !needsReconnect ? (\n <Button variant=\"outlined\" disabled={busy !== null} onClick={() => void togglePaused()}>\n {mailbox.status === 'paused' ? MAILBOX_ACTION_LABELS.resume : MAILBOX_ACTION_LABELS.pause}\n </Button>\n ) : null}\n {isMine && !needsReconnect ? (\n <Button variant=\"outlined\" disabled={busy !== null} onClick={() => void sendTest()}>\n {MAILBOX_ACTION_LABELS.test}\n </Button>\n ) : null}\n {canManage ? (\n <Button color=\"error\" disabled={busy !== null} onClick={() => void disconnect()}>\n {MAILBOX_ACTION_LABELS.disconnect}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nMailboxCard.displayName = 'MailboxCard'\n\nexport default MailboxCard\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","useSnackbar","Alert","Autocomplete","Box","Button","Chip","FormHelperText","MenuItem","Stack","TextField","ToggleButton","ToggleButtonGroup","Typography","useEffect","useMemo","useRef","useState","formatMinuteOfDay","isValidTimezone","OUTREACH_DEFAULT_WINDOW","OUTREACH_MAILBOX_STATUS_LABELS","OUTREACH_MAX_DAILY_CAP","OUTREACH_RAMP_STEPS","OUTREACH_WEEKDAY_LABELS","outreachEffectiveDailyCap","summarizeMailboxHealth","validateDailyCap","validateDisplayName","validateSendWindow","autoPauseSentence","mailbox","pause","autoPause","day","atMs","Intl","DateTimeFormat","dateStyle","timeZone","timezone","format","until","untilMs","message","MAILBOX_ACTION_LABELS","save","resume","test","disconnect","reconnect","WEEKDAY_NAMES","STATUS_COLOR","connected","paused","reconnect_required","disconnected","MINUTE_OPTIONS","Array","from","length","_","index","timezoneOptions","current","supported","supportedValuesOf","zones","includes","RAMP_SENTENCE","map","step","week","limit","join","draftOf","sendAs","email","displayName","dailyCap","String","window","MailboxCard","props","isMine","canManage","api","onReconnect","reconnecting","nowMs","Date","now","enqueueSnackbar","confirm","draft","setDraft","saving","setSaving","saveError","setSaveError","busy","setBusy","stored","storedKey","JSON","stringify","seededFrom","dirty","previous","health","needsReconnect","status","capCheck","Number","NaN","windowCheck","nameCheck","timezoneValid","invalid","todayLimit","rampStartedAtMs","saveSettings","mailboxId","id","variant","error","togglePaused","setPaused","allowDuplicate","sendTest","sent","sentTo","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","revocation","persist","sendAsLabel","option","sendAsOptions","find","entry","header","subheader","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","size","label","spacing","severity","disabled","onClick","undefined","role","hasSent","direction","sx","mt","bounces","replies","value","aria-label","component","onSubmit","event","preventDefault","select","onChange","target","helperText","type","slotProps","htmlInput","min","max","gutterBottom","days","_event","sort","a","b","xs","sm","startMinute","slice","minute","endMinute","disableClearable","options","minWidth","renderInput","params","useFlexGap","flexWrap"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/mailbox-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 {\n Alert,\n Autocomplete,\n Box,\n Button,\n Chip,\n FormControlLabel,\n FormHelperText,\n MenuItem,\n Stack,\n Switch,\n TextField,\n ToggleButton,\n ToggleButtonGroup,\n Typography,\n} from '@mui/material'\nimport { useEffect, useMemo, useRef, useState } from 'react'\nimport {\n formatMinuteOfDay,\n isValidTimezone,\n OUTREACH_DEFAULT_WINDOW,\n OUTREACH_MAILBOX_STATUS_LABELS,\n OUTREACH_MAX_DAILY_CAP,\n OUTREACH_RAMP_STEPS,\n OUTREACH_WEEKDAY_LABELS,\n outreachEffectiveDailyCap,\n summarizeMailboxHealth,\n validateDailyCap,\n validateDisplayName,\n validateSendWindow,\n} from '../mailboxes/mailbox-settings'\nimport type {\n OutreachMailbox,\n OutreachMailboxStatus,\n OutreachSendWindow,\n} from '../model/outreach.types'\nimport type { OutreachMailboxApi } from './use-outreach-mailbox-api'\n\nexport interface MailboxCardProps {\n mailbox: OutreachMailbox\n /** The viewer is the member who connected it. */\n isMine: boolean\n /** The viewer may change, pause and disconnect it: its member, or an org owner or admin. */\n canManage: boolean\n api: OutreachMailboxApi\n /** Starts a new Google connect, for a mailbox that needs reconnecting. */\n onReconnect(): void\n /** True while that connect is on its way to Google. */\n reconnecting?: boolean\n /** The clock the health window and today's limit are read on. */\n nowMs?: number\n}\n\n/**\n * What the card says about a mailbox that paused ITSELF (AGL-2981): the\n * engine's own sentence, when it paused, and — after a complaint — the day\n * resuming stops being premature. Nothing sends from it until a member\n * resumes it.\n */\nexport function autoPauseSentence(mailbox: Pick<OutreachMailbox, 'autoPause' | 'timezone'>): string {\n const pause = mailbox.autoPause\n if (!pause) return ''\n const day = (atMs: number) => {\n try {\n return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeZone: mailbox.timezone }).format(atMs)\n } catch {\n return new Intl.DateTimeFormat('en-US', { dateStyle: 'medium', timeZone: 'UTC' }).format(atMs)\n }\n }\n const until = typeof pause.untilMs === 'number' ? ` Wait until ${day(pause.untilMs)} before resuming it.` : ''\n return `Sequences paused this mailbox on ${day(pause.atMs)}. ${pause.message}${until} Nothing sends from it until it is resumed.`\n}\n\n/** Accessible names of the card's actions, spelled once for the specs. */\nexport const MAILBOX_ACTION_LABELS = {\n save: 'Save settings',\n pause: 'Pause',\n resume: 'Resume',\n test: 'Send a test to myself',\n testElsewhere: 'Send a test',\n testAddress: 'Test address',\n warmUp: 'Warm up gradually',\n disconnect: 'Disconnect',\n reconnect: 'Reconnect',\n} as const\n\nconst WEEKDAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']\n\nconst STATUS_COLOR: Record<OutreachMailboxStatus, 'success' | 'default' | 'warning' | 'error'> = {\n connected: 'success',\n paused: 'default',\n reconnect_required: 'warning',\n disconnected: 'error',\n}\n\n/** Every half hour of the day, and midnight at its end. */\nconst MINUTE_OPTIONS = Array.from({ length: 49 }, (_, index) => index * 30)\n\n/** The zones this runtime knows, the mailbox's own included whatever it is. */\nfunction timezoneOptions(current: string): string[] {\n const supported = (Intl as { supportedValuesOf?: (key: string) => string[] }).supportedValuesOf?.('timeZone') ?? []\n const zones = supported.length ? supported : ['UTC']\n return zones.includes(current) ? zones : [current, ...zones]\n}\n\n/** The ramp, as the cap's helper text reads it. */\nconst RAMP_SENTENCE = `Warm-up: ${OUTREACH_RAMP_STEPS.map((step, index) =>\n `week ${step.week}${index === OUTREACH_RAMP_STEPS.length - 1 ? '+' : ''}: ${step.limit}`,\n).join(' · ')} a day, never above the cap.`\n\ninterface Draft {\n sendAs: string\n displayName: string\n dailyCap: string\n window: OutreachSendWindow\n timezone: string\n /** Whether the warm-up ramp applies — `rampStartedAtMs` as a switch. */\n warmUp: boolean\n}\n\nconst draftOf = (mailbox: OutreachMailbox): Draft => ({\n sendAs: mailbox.sendAs ?? mailbox.email,\n displayName: mailbox.displayName ?? '',\n dailyCap: String(mailbox.dailyCap ?? ''),\n window: mailbox.window ?? OUTREACH_DEFAULT_WINDOW,\n timezone: mailbox.timezone ?? 'UTC',\n warmUp: typeof mailbox.rampStartedAtMs === 'number',\n})\n\n/**\n * One connected mailbox (AGL-2978): its account and status, its last seven\n * days, its settings, and pause, test and disconnect.\n *\n * Every control is offered only where the route will allow it: the settings\n * form and pause and disconnect to the member who connected the mailbox or an\n * org owner or admin, and the test and the reconnect to its member alone,\n * because both act in that member's own Google account.\n */\nexport function MailboxCard(props: MailboxCardProps) {\n const { mailbox, isMine, canManage, api, onReconnect, reconnecting = false, nowMs = Date.now() } = props\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const [draft, setDraft] = useState<Draft>(() => draftOf(mailbox))\n const [saving, setSaving] = useState(false)\n const [saveError, setSaveError] = useState<string | null>(null)\n const [busy, setBusy] = useState<'status' | 'test' | 'disconnect' | null>(null)\n // Where a test goes when not to the member themself (AGL-3228): a test to\n // one's own address never leaves Google and shows no authentication result.\n const [testAddress, setTestAddress] = useState('')\n\n // A save here, a change made from another tab, or a reconnect rewrites the\n // stored settings; the form follows them, unless the member has unsaved\n // edits of their own, which are never thrown away.\n const stored = useMemo(() => draftOf(mailbox), [mailbox])\n const storedKey = JSON.stringify(stored)\n const seededFrom = useRef(storedKey)\n const dirty = JSON.stringify(draft) !== storedKey\n useEffect(() => {\n if (seededFrom.current === storedKey) return\n const previous = seededFrom.current\n seededFrom.current = storedKey\n setDraft((current) => (JSON.stringify(current) === previous ? stored : current))\n }, [storedKey, stored])\n\n const health = summarizeMailboxHealth(mailbox.health, mailbox.timezone, nowMs)\n const needsReconnect = mailbox.status === 'reconnect_required' || mailbox.status === 'disconnected'\n const capCheck = validateDailyCap(draft.dailyCap === '' ? Number.NaN : Number(draft.dailyCap))\n const windowCheck = validateSendWindow(draft.window)\n const nameCheck = validateDisplayName(draft.displayName)\n const timezoneValid = isValidTimezone(draft.timezone)\n const invalid =\n typeof capCheck !== 'number' || !('days' in windowCheck) || typeof nameCheck !== 'string' || !timezoneValid\n const todayLimit = outreachEffectiveDailyCap(\n {\n dailyCap: typeof capCheck === 'number' ? capCheck : mailbox.dailyCap,\n // A ramp switched on in the draft reads as starting today.\n rampStartedAtMs: draft.warmUp ? (mailbox.rampStartedAtMs ?? nowMs) : null,\n timezone: timezoneValid ? draft.timezone : mailbox.timezone,\n },\n nowMs,\n )\n\n const save = async () => {\n setSaving(true)\n setSaveError(null)\n try {\n await api.saveSettings({\n mailboxId: mailbox.id,\n ...(draft.sendAs !== stored.sendAs ? { sendAs: draft.sendAs } : {}),\n ...(draft.displayName !== stored.displayName ? { displayName: draft.displayName } : {}),\n ...(draft.dailyCap !== stored.dailyCap ? { dailyCap: Number(draft.dailyCap) } : {}),\n ...(JSON.stringify(draft.window) !== JSON.stringify(stored.window) ? { window: draft.window } : {}),\n ...(draft.timezone !== stored.timezone ? { timezone: draft.timezone } : {}),\n ...(draft.warmUp !== stored.warmUp ? { warmUp: draft.warmUp } : {}),\n })\n enqueueSnackbar('Mailbox settings saved', { variant: 'success' })\n } catch (error) {\n setSaveError((error as Error).message)\n } finally {\n setSaving(false)\n }\n }\n\n const togglePaused = async () => {\n const paused = mailbox.status !== 'paused'\n setBusy('status')\n try {\n await api.setPaused(mailbox.id, paused)\n enqueueSnackbar(paused ? 'Mailbox paused' : 'Mailbox resumed', { variant: 'success' })\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n }\n\n const sendTest = async () => {\n setBusy('test')\n try {\n const to = testAddress.trim()\n const sent = await api.sendTest(mailbox.id, to || undefined)\n enqueueSnackbar(\n to\n ? `Test sent to ${sent.sentTo}. Its original source shows whether the sender authenticated.`\n : `Test sent to ${sent.sentTo}. Check your inbox.`,\n { variant: 'success' },\n )\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n } finally {\n setBusy(null)\n }\n }\n\n const disconnect = async () => {\n const confirmed = await confirm({\n title: 'Disconnect this mailbox?',\n description:\n 'Disconnecting deletes the access stored for this mailbox and asks Google to revoke it. ' +\n 'Nothing is sent from it again unless it is connected again.',\n confirmationText: MAILBOX_ACTION_LABELS.disconnect,\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n setBusy('disconnect')\n try {\n const { revocation } = await api.disconnect(mailbox.id)\n if (revocation === 'failed') {\n enqueueSnackbar(\n 'Mailbox disconnected, but Google could not confirm the access was revoked. ' +\n 'Remove it from the Google Account’s third-party connections to be sure.',\n { variant: 'warning', persist: true },\n )\n } else if (revocation === 'kept-for-other-mailbox') {\n enqueueSnackbar(\n 'Mailbox disconnected. Google access stays in place for another connected mailbox on the same account.',\n { variant: 'success' },\n )\n } else {\n enqueueSnackbar('Mailbox disconnected', { variant: 'success' })\n }\n } catch (error) {\n enqueueSnackbar((error as Error).message, { variant: 'error', allowDuplicate: true })\n setBusy(null)\n }\n }\n\n const sendAsLabel = (email: string) => {\n const option = mailbox.sendAsOptions?.find((entry) => entry.email === email)\n return option?.displayName ? `${option.displayName} <${option.email}>` : email\n }\n\n return (\n <CardDisplay\n header={mailbox.email}\n subheader={isMine ? 'Your mailbox' : 'A teammate’s mailbox'}\n help={pluginDocsHelp('sequences', { anchor: '#connect-a-mailbox' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Chip\n size=\"small\"\n label={OUTREACH_MAILBOX_STATUS_LABELS[mailbox.status] ?? mailbox.status}\n color={STATUS_COLOR[mailbox.status] ?? 'default'}\n />\n ),\n }}\n >\n <Stack spacing={2}>\n {needsReconnect ? (\n <Alert\n severity=\"warning\"\n action={\n isMine ? (\n <Button color=\"inherit\" size=\"small\" disabled={reconnecting} onClick={onReconnect}>\n {MAILBOX_ACTION_LABELS.reconnect}\n </Button>\n ) : undefined\n }\n >\n {`${\n mailbox.status === 'disconnected'\n ? 'This mailbox was disconnected.'\n : 'Google stopped accepting this mailbox’s connection.'\n } ${\n isMine\n ? 'Nothing sends from it until you connect it again.'\n : 'Only the member who connected it can reconnect it.'\n }`}\n </Alert>\n ) : null}\n {mailbox.status === 'paused' && mailbox.autoPause ? (\n <Alert severity=\"error\" role=\"alert\">\n {autoPauseSentence(mailbox)}\n </Alert>\n ) : null}\n\n <Box>\n <Typography variant=\"subtitle2\">Last 7 days</Typography>\n {health.hasSent ? null : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n No sends yet\n </Typography>\n )}\n <Stack direction=\"row\" spacing={3} sx={{ mt: 0.5 }}>\n {[\n ['Sent', health.sent],\n ['Bounces', health.bounces],\n ['Replies', health.replies],\n ].map(([label, value]) => (\n <Box key={label as string} aria-label={`${label} in the last 7 days`}>\n <Typography variant=\"h6\" component=\"p\">\n {value}\n </Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {label}\n </Typography>\n </Box>\n ))}\n </Stack>\n </Box>\n\n {canManage ? (\n <Stack spacing={2} component=\"form\" onSubmit={(event) => event.preventDefault()}>\n <TextField\n select\n size=\"small\"\n label=\"Send as\"\n value={draft.sendAs}\n onChange={(event) => setDraft({ ...draft, sendAs: event.target.value })}\n helperText=\"Addresses Gmail has verified for this account.\"\n >\n {(mailbox.sendAsOptions?.length ? mailbox.sendAsOptions : [{ email: mailbox.sendAs }]).map((option) => (\n <MenuItem key={option.email} value={option.email}>\n {sendAsLabel(option.email)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n size=\"small\"\n label=\"Display name\"\n value={draft.displayName}\n onChange={(event) => setDraft({ ...draft, displayName: event.target.value })}\n error={typeof nameCheck !== 'string'}\n helperText={typeof nameCheck !== 'string' ? nameCheck.message : 'The name recipients see beside the address.'}\n />\n <TextField\n size=\"small\"\n type=\"number\"\n label=\"Daily cap\"\n value={draft.dailyCap}\n onChange={(event) => setDraft({ ...draft, dailyCap: event.target.value })}\n error={typeof capCheck !== 'number'}\n slotProps={{ htmlInput: { min: 1, max: OUTREACH_MAX_DAILY_CAP, step: 1 } }}\n helperText={\n typeof capCheck !== 'number'\n ? capCheck.message\n : draft.warmUp\n ? `${RAMP_SENTENCE} Today’s limit: ${todayLimit}.`\n : `No warm-up: today’s limit is the cap, ${todayLimit}.`\n }\n />\n <FormControlLabel\n control={\n <Switch\n checked={draft.warmUp}\n onChange={(event) => setDraft({ ...draft, warmUp: event.target.checked })}\n />\n }\n label={MAILBOX_ACTION_LABELS.warmUp}\n />\n <FormHelperText sx={{ mt: -1 }}>\n {'For a mailbox that is new or has not sent much. One that has sent mail for years already has ' +\n 'its reputation and can start at its cap.'}\n </FormHelperText>\n <Box>\n <Typography variant=\"subtitle2\" gutterBottom>\n Sending window\n </Typography>\n <ToggleButtonGroup\n size=\"small\"\n aria-label=\"Sending days\"\n value={draft.window.days}\n onChange={(_event, days: number[]) =>\n setDraft({ ...draft, window: { ...draft.window, days: [...days].sort((a, b) => a - b) } })\n }\n >\n {OUTREACH_WEEKDAY_LABELS.map((label, day) => (\n <ToggleButton key={label} value={day} aria-label={WEEKDAY_NAMES[day]}>\n {label}\n </ToggleButton>\n ))}\n </ToggleButtonGroup>\n <Stack direction={{ xs: 'column', sm: 'row' }} spacing={2} sx={{ mt: 2 }}>\n <TextField\n select\n size=\"small\"\n label=\"From\"\n value={draft.window.startMinute}\n onChange={(event) =>\n setDraft({ ...draft, window: { ...draft.window, startMinute: Number(event.target.value) } })\n }\n >\n {MINUTE_OPTIONS.slice(0, -1).map((minute) => (\n <MenuItem key={minute} value={minute}>\n {formatMinuteOfDay(minute)}\n </MenuItem>\n ))}\n </TextField>\n <TextField\n select\n size=\"small\"\n label=\"Until\"\n value={draft.window.endMinute}\n onChange={(event) =>\n setDraft({ ...draft, window: { ...draft.window, endMinute: Number(event.target.value) } })\n }\n >\n {MINUTE_OPTIONS.slice(1).map((minute) => (\n <MenuItem key={minute} value={minute}>\n {formatMinuteOfDay(minute)}\n </MenuItem>\n ))}\n </TextField>\n <Autocomplete\n size=\"small\"\n disableClearable\n options={timezoneOptions(mailbox.timezone)}\n value={draft.timezone}\n onChange={(_event, timezone) => setDraft({ ...draft, timezone: timezone ?? draft.timezone })}\n sx={{ minWidth: 240 }}\n renderInput={(params) => <TextField {...params} label=\"Timezone\" />}\n />\n </Stack>\n {'days' in windowCheck ? null : <FormHelperText error>{windowCheck.message}</FormHelperText>}\n </Box>\n {saveError ? <FormHelperText error>{saveError}</FormHelperText> : null}\n <Box>\n <Button variant=\"contained\" disabled={!dirty || invalid || saving} onClick={() => void save()}>\n {MAILBOX_ACTION_LABELS.save}\n </Button>\n </Box>\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {`Sends as ${sendAsLabel(stored.sendAs)}, at most ${stored.dailyCap} a day, ` +\n `${stored.window.days.map((day) => OUTREACH_WEEKDAY_LABELS[day]).join(', ')} ` +\n `${formatMinuteOfDay(stored.window.startMinute)}–${formatMinuteOfDay(stored.window.endMinute)} ` +\n `(${stored.timezone}).`}\n </Typography>\n )}\n\n {isMine && !needsReconnect ? (\n <TextField\n label={MAILBOX_ACTION_LABELS.testAddress}\n value={testAddress}\n onChange={(event) => setTestAddress(event.target.value)}\n disabled={busy !== null}\n size=\"small\"\n type=\"email\"\n helperText={\n 'Leave empty to send the test to yourself. A test to an outside mailbox you can ' +\n 'read is the only one whose original source shows the sender authentication results.'\n }\n fullWidth\n />\n ) : null}\n\n {canManage || isMine ? (\n <Stack direction=\"row\" spacing={1} useFlexGap sx={{ flexWrap: 'wrap' }}>\n {canManage && !needsReconnect ? (\n <Button variant=\"outlined\" disabled={busy !== null} onClick={() => void togglePaused()}>\n {mailbox.status === 'paused' ? MAILBOX_ACTION_LABELS.resume : MAILBOX_ACTION_LABELS.pause}\n </Button>\n ) : null}\n {isMine && !needsReconnect ? (\n <Button variant=\"outlined\" disabled={busy !== null} onClick={() => void sendTest()}>\n {testAddress.trim() ? MAILBOX_ACTION_LABELS.testElsewhere : MAILBOX_ACTION_LABELS.test}\n </Button>\n ) : null}\n {canManage ? (\n <Button color=\"error\" disabled={busy !== null} onClick={() => void disconnect()}>\n {MAILBOX_ACTION_LABELS.disconnect}\n </Button>\n ) : null}\n </Stack>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nMailboxCard.displayName = 'MailboxCard'\n\nexport default MailboxCard\n"],"names":["pluginDocsHelp","CardDisplay","useConfirmationContext","useSnackbar","Alert","Autocomplete","Box","Button","Chip","FormControlLabel","FormHelperText","MenuItem","Stack","Switch","TextField","ToggleButton","ToggleButtonGroup","Typography","useEffect","useMemo","useRef","useState","formatMinuteOfDay","isValidTimezone","OUTREACH_DEFAULT_WINDOW","OUTREACH_MAILBOX_STATUS_LABELS","OUTREACH_MAX_DAILY_CAP","OUTREACH_RAMP_STEPS","OUTREACH_WEEKDAY_LABELS","outreachEffectiveDailyCap","summarizeMailboxHealth","validateDailyCap","validateDisplayName","validateSendWindow","autoPauseSentence","mailbox","pause","autoPause","day","atMs","Intl","DateTimeFormat","dateStyle","timeZone","timezone","format","until","untilMs","message","MAILBOX_ACTION_LABELS","save","resume","test","testElsewhere","testAddress","warmUp","disconnect","reconnect","WEEKDAY_NAMES","STATUS_COLOR","connected","paused","reconnect_required","disconnected","MINUTE_OPTIONS","Array","from","length","_","index","timezoneOptions","current","supported","supportedValuesOf","zones","includes","RAMP_SENTENCE","map","step","week","limit","join","draftOf","sendAs","email","displayName","dailyCap","String","window","rampStartedAtMs","MailboxCard","props","isMine","canManage","api","onReconnect","reconnecting","nowMs","Date","now","enqueueSnackbar","confirm","draft","setDraft","saving","setSaving","saveError","setSaveError","busy","setBusy","setTestAddress","stored","storedKey","JSON","stringify","seededFrom","dirty","previous","health","needsReconnect","status","capCheck","Number","NaN","windowCheck","nameCheck","timezoneValid","invalid","todayLimit","saveSettings","mailboxId","id","variant","error","togglePaused","setPaused","allowDuplicate","sendTest","to","trim","sent","undefined","sentTo","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","revocation","persist","sendAsLabel","option","sendAsOptions","find","entry","header","subheader","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","size","label","spacing","severity","disabled","onClick","role","hasSent","direction","sx","mt","bounces","replies","value","aria-label","component","onSubmit","event","preventDefault","select","onChange","target","helperText","type","slotProps","htmlInput","min","max","control","checked","gutterBottom","days","_event","sort","a","b","xs","sm","startMinute","slice","minute","endMinute","disableClearable","options","minWidth","renderInput","params","fullWidth","useFlexGap","flexWrap"],"mappings":"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"}
@@ -1,8 +1,10 @@
1
1
  import type { OutreachLoad } from './use-outreach-data';
2
- /** A saved Contacts view people can be enrolled from. */
2
+ /** A saved Contacts or Leads view people can be enrolled from (AGL-3234). */
3
3
  export interface OutreachViewOption {
4
4
  id: string;
5
5
  name: string;
6
+ /** Which list the view is of; a Leads view selects the sequence's site's leads. */
7
+ section: 'contacts' | 'leads';
6
8
  }
7
9
  /** The most saved views the picker lists. */
8
10
  export declare const OUTREACH_VIEWS_LIMIT = 50;
@@ -48,3 +50,20 @@ export declare function useOutreachContactSearch(input: {
48
50
  }): OutreachLoad<OutreachContactOption[]> & {
49
51
  idle: boolean;
50
52
  };
53
+ /** The most leads the picker reads — the Leads list's own window. */
54
+ export declare const OUTREACH_LEADS_WINDOW = 200;
55
+ /**
56
+ * The open leads at `hostId` a search finds (AGL-3234): the site's most
57
+ * recently seen leads, read once while the tab is open and narrowed in
58
+ * the browser by a word of the name or the address — a lead carries no
59
+ * search tokens, and the window is the Leads list's own. Converted and
60
+ * closed leads are left out: they are a contact's, or a verdict's, to
61
+ * enroll. `idle` until something is typed.
62
+ */
63
+ export declare function useOutreachLeadSearch(input: {
64
+ hostId: string | null;
65
+ text: string;
66
+ enabled: boolean;
67
+ }): OutreachLoad<OutreachContactOption[]> & {
68
+ idle: boolean;
69
+ };
@@ -16,7 +16,7 @@
16
16
  */ 'use client';
17
17
  import { _ as _extends } from "@swc/helpers/_/_extends";
18
18
  import { contactDisplayName, normalizeContactEmail } from "@aglyn/aglyn/app-utils/contacts";
19
- import { CRM_COLLECTIONS, crmViewIsListed, normalizeCrmViewFilters } from "@aglyn/aglyn/app-utils/crm";
19
+ import { CRM_COLLECTIONS, crmLeadDisplayName, crmViewIsListed, isCrmLeadOpen, normalizeCrmViewFilters } from "@aglyn/aglyn/app-utils/crm";
20
20
  import { CRM_EMAIL_TEMPLATES_LIMIT, crmEmailTemplateIsListed, normalizeCrmEmailTemplate } from "@aglyn/aglyn/app-utils/crm-email-templates";
21
21
  import { dynamicListDimensionsForCrmView } from "@aglyn/aglyn/app-utils/dynamic-list-rule";
22
22
  import { nameSearchToken } from "@aglyn/aglyn/app-utils/name-search";
@@ -55,7 +55,10 @@ import { useEffect, useState } from "react";
55
55
  data: []
56
56
  });
57
57
  if (!orgId) return undefined;
58
- return onSnapshot(query(collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.views), where('section', '==', 'contacts'), orderBy('name'), limit(OUTREACH_VIEWS_LIMIT)), (snapshot)=>setResult({
58
+ return onSnapshot(query(collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.views), where('section', 'in', [
59
+ 'contacts',
60
+ 'leads'
61
+ ]), orderBy('name'), limit(OUTREACH_VIEWS_LIMIT)), (snapshot)=>setResult({
59
62
  status: 'ready',
60
63
  data: snapshot.docs.map((entry)=>({
61
64
  id: entry.id,
@@ -65,10 +68,13 @@ import { useEffect, useState } from "react";
65
68
  return typeof data['name'] === 'string' && data['name'] !== '' && crmViewIsListed({
66
69
  shared: data['shared'] === true,
67
70
  ownerUid: String((_data_ownerUid = data['ownerUid']) != null ? _data_ownerUid : '')
68
- }, uid) && dynamicListDimensionsForCrmView(normalizeCrmViewFilters(data['filters'])).unsupported.length === 0;
71
+ }, uid) && // A Leads view narrows by status alone, which enrolling can
72
+ // always apply; a Contacts view must be readable as a list.
73
+ (data['section'] === 'leads' || dynamicListDimensionsForCrmView(normalizeCrmViewFilters(data['filters'])).unsupported.length === 0);
69
74
  }).map(({ id, data })=>({
70
75
  id,
71
- name: String(data['name'])
76
+ name: String(data['name']),
77
+ section: data['section'] === 'leads' ? 'leads' : 'contacts'
72
78
  }))
73
79
  }), (error)=>{
74
80
  if (!denied(error)) console.error('[outreach] saved views could not be read', error);
@@ -196,5 +202,73 @@ import { useEffect, useState } from "react";
196
202
  idle
197
203
  });
198
204
  }
205
+ /** The most leads the picker reads — the Leads list's own window. */ export const OUTREACH_LEADS_WINDOW = 200;
206
+ /**
207
+ * The open leads at `hostId` a search finds (AGL-3234): the site's most
208
+ * recently seen leads, read once while the tab is open and narrowed in
209
+ * the browser by a word of the name or the address — a lead carries no
210
+ * search tokens, and the window is the Leads list's own. Converted and
211
+ * closed leads are left out: they are a contact's, or a verdict's, to
212
+ * enroll. `idle` until something is typed.
213
+ */ export function useOutreachLeadSearch(input) {
214
+ const { hostId, text, enabled } = input;
215
+ const firestore = useFirestore();
216
+ const [window, setWindow] = useState({
217
+ status: 'ready',
218
+ data: []
219
+ });
220
+ useEffect(()=>{
221
+ if (!enabled || !hostId) {
222
+ setWindow({
223
+ status: 'ready',
224
+ data: []
225
+ });
226
+ return undefined;
227
+ }
228
+ let current = true;
229
+ setWindow({
230
+ status: 'loading',
231
+ data: []
232
+ });
233
+ getDocs(query(collection(firestore, 'hosts', hostId, 'leads'), orderBy('lastSeenAtMs', 'desc'), limit(OUTREACH_LEADS_WINDOW))).then((snapshot)=>{
234
+ if (!current) return;
235
+ setWindow({
236
+ status: 'ready',
237
+ data: snapshot.docs.map((entry)=>({
238
+ id: entry.id,
239
+ data: entry.data()
240
+ })).filter(({ data })=>isCrmLeadOpen(data) && !data['convertedContactId']).map(({ id, data })=>({
241
+ id,
242
+ name: crmLeadDisplayName(data),
243
+ email: normalizeContactEmail(data['email'])
244
+ }))
245
+ });
246
+ }).catch((error)=>{
247
+ if (!current) return;
248
+ if (!denied(error)) console.error('[outreach] the leads could not be read', error);
249
+ setWindow({
250
+ status: denied(error) ? 'refused' : 'error',
251
+ data: []
252
+ });
253
+ });
254
+ return ()=>{
255
+ current = false;
256
+ };
257
+ }, [
258
+ firestore,
259
+ hostId,
260
+ enabled
261
+ ]);
262
+ const needle = text.trim().toLowerCase();
263
+ const idle = !hostId || !needle;
264
+ const data = idle ? [] : window.data.filter((lead)=>{
265
+ var _lead_email;
266
+ return lead.name.toLowerCase().includes(needle) || ((_lead_email = lead.email) != null ? _lead_email : '').includes(needle);
267
+ }).slice(0, OUTREACH_SEARCH_LIMIT);
268
+ return _extends({}, window, {
269
+ data,
270
+ idle
271
+ });
272
+ }
199
273
 
200
274
  //# sourceMappingURL=use-outreach-crm.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-crm.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n contactDisplayName,\n normalizeContactEmail,\n} from '@aglyn/aglyn/app-utils/contacts'\nimport {\n CRM_COLLECTIONS,\n crmViewIsListed,\n normalizeCrmViewFilters,\n} from '@aglyn/aglyn/app-utils/crm'\nimport {\n CRM_EMAIL_TEMPLATES_LIMIT,\n crmEmailTemplateIsListed,\n normalizeCrmEmailTemplate,\n} from '@aglyn/aglyn/app-utils/crm-email-templates'\nimport { dynamicListDimensionsForCrmView } from '@aglyn/aglyn/app-utils/dynamic-list-rule'\nimport { nameSearchToken } from '@aglyn/aglyn/app-utils/name-search'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n collection,\n getDocs,\n limit,\n onSnapshot,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useEffect, useState } from 'react'\nimport type { OutreachLoad } from './use-outreach-data'\n\n/**\n * What Outreach reads from the organization's CRM (AGL-2980): the saved\n * Contacts views people can be enrolled from, the email templates a step\n * can send, and the contacts a search finds.\n *\n * Through the core's own CRM model — the collection names, the listing\n * rules, the view translation the dynamic-list sweep uses — and never\n * through the CRM plugin, which a plugin may not import. The Firestore\n * rules gate every one of these reads on `data.manage`, the same permission\n * the enroll routes ask for, so a member who cannot read the people cannot\n * enroll them either.\n */\n\nconst denied = (error: unknown) =>\n (error as { code?: unknown } | null)?.code === 'permission-denied'\n\n/** A saved Contacts view people can be enrolled from. */\nexport interface OutreachViewOption {\n id: string\n name: string\n}\n\n/** The most saved views the picker lists. */\nexport const OUTREACH_VIEWS_LIMIT = 50\n\n/**\n * The saved Contacts views this reader may enroll from: their own and the\n * shared ones — a colleague's private view is theirs — that translate whole\n * into an audience. A view filtering on something enrolling cannot apply is\n * not offered, because dropping that filter would enroll more people than\n * the view shows.\n */\nexport function useOutreachSavedViews(\n orgId: string | null,\n uid: string | null,\n): OutreachLoad<OutreachViewOption[]> {\n const firestore = useFirestore()\n const [result, setResult] = useState<OutreachLoad<OutreachViewOption[]>>({\n status: 'loading',\n data: [],\n })\n useEffect(() => {\n setResult({ status: 'loading', data: [] })\n if (!orgId) return undefined\n return onSnapshot(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.views),\n where('section', '==', 'contacts'),\n orderBy('name'),\n limit(OUTREACH_VIEWS_LIMIT),\n ),\n (snapshot) =>\n setResult({\n status: 'ready',\n data: snapshot.docs\n .map((entry) => ({\n id: entry.id,\n data: entry.data() as Record<string, unknown>,\n }))\n .filter(\n ({ data }) =>\n typeof data['name'] === 'string' &&\n data['name'] !== '' &&\n crmViewIsListed(\n {\n shared: data['shared'] === true,\n ownerUid: String(data['ownerUid'] ?? ''),\n },\n uid,\n ) &&\n dynamicListDimensionsForCrmView(\n normalizeCrmViewFilters(data['filters']),\n ).unsupported.length === 0,\n )\n .map(({ id, data }) => ({ id, name: String(data['name']) })),\n }),\n (error) => {\n if (!denied(error))\n console.error('[outreach] saved views could not be read', error)\n setResult({ status: denied(error) ? 'refused' : 'error', data: [] })\n },\n )\n }, [firestore, orgId, uid])\n return result\n}\n\n/** A CRM email template a step can send the body of. */\nexport interface OutreachTemplateOption {\n id: string\n name: string\n subject: string\n body: string\n}\n\n/**\n * The CRM email templates a step at this site can use: whole letters, not\n * snippets; the team's and this reader's own; written for the whole\n * organization or for this site.\n */\nexport function useOutreachEmailTemplates(\n orgId: string | null,\n hostId: string | null,\n uid: string | null,\n): OutreachLoad<OutreachTemplateOption[]> {\n const firestore = useFirestore()\n const [result, setResult] = useState<OutreachLoad<OutreachTemplateOption[]>>({\n status: 'loading',\n data: [],\n })\n useEffect(() => {\n setResult({ status: 'loading', data: [] })\n if (!orgId) return undefined\n return onSnapshot(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.emailTemplates),\n orderBy('name'),\n limit(CRM_EMAIL_TEMPLATES_LIMIT),\n ),\n (snapshot) =>\n setResult({\n status: 'ready',\n data: snapshot.docs\n .map((entry) => ({\n id: entry.id,\n template: normalizeCrmEmailTemplate(entry.data()),\n }))\n .filter(\n ({ template }) =>\n template.kind === 'template' &&\n crmEmailTemplateIsListed(template, uid) &&\n (!hostId || visibleToHost(template.visibleTo, hostId)),\n )\n .map(({ id, template }) => ({\n id,\n name: template.name,\n subject: template.subject,\n body: template.body,\n })),\n }),\n (error) => {\n if (!denied(error))\n console.error('[outreach] email templates could not be read', error)\n setResult({ status: denied(error) ? 'refused' : 'error', data: [] })\n },\n )\n }, [firestore, orgId, hostId, uid])\n return result\n}\n\n/** A contact a search found. */\nexport interface OutreachContactOption {\n id: string\n name: string\n email: string | null\n}\n\n/** The most contacts one search answers with. */\nexport const OUTREACH_SEARCH_LIMIT = 25\n\n/**\n * The contacts at `hostId` a search finds: by address when the text is one,\n * otherwise by a word of the name — the `nameTokens` the contacts carry for\n * the CRM's own search. `idle` until something is typed.\n */\nexport function useOutreachContactSearch(input: {\n orgId: string | null\n hostId: string | null\n contactGroupId: string | null\n text: string\n}): OutreachLoad<OutreachContactOption[]> & { idle: boolean } {\n const { orgId, hostId, contactGroupId, text } = input\n const firestore = useFirestore()\n const [result, setResult] = useState<OutreachLoad<OutreachContactOption[]>>({\n status: 'ready',\n data: [],\n })\n const email = normalizeContactEmail(text)\n const token = email ? '' : nameSearchToken(text)\n const idle = !orgId || !hostId || (!email && !token)\n useEffect(() => {\n if (idle || !orgId || !hostId) {\n setResult({ status: 'ready', data: [] })\n return undefined\n }\n let current = true\n setResult((previous) => ({ status: 'loading', data: previous.data }))\n const contacts = collection(firestore, 'orgs', orgId, 'contacts')\n // Settles the typing before it reads: one query per pause, not per key.\n const timer = setTimeout(() => {\n getDocs(\n email\n ? query(\n contacts,\n where('email', '==', email),\n limit(OUTREACH_SEARCH_LIMIT),\n )\n : query(\n contacts,\n where('nameTokens', 'array-contains', token),\n orderBy('nameLower'),\n limit(OUTREACH_SEARCH_LIMIT),\n ),\n )\n .then((snapshot) => {\n if (!current) return\n setResult({\n status: 'ready',\n data: snapshot.docs\n .map((entry) => ({\n id: entry.id,\n data: entry.data() as Record<string, unknown>,\n }))\n .filter(({ data }) =>\n visibleToHost(\n data['visibleTo'] as string[] | undefined,\n hostId,\n ),\n )\n .map(({ id, data }) => ({\n id,\n name: contactDisplayName(data, contactGroupId ?? hostId).trim(),\n email: normalizeContactEmail(data['email']),\n })),\n })\n })\n .catch((error: unknown) => {\n if (!current) return\n if (!denied(error))\n console.error('[outreach] the contact search failed', error)\n setResult({ status: denied(error) ? 'refused' : 'error', data: [] })\n })\n }, 300)\n return () => {\n current = false\n clearTimeout(timer)\n }\n }, [firestore, orgId, hostId, contactGroupId, email, token, idle])\n return { ...result, idle }\n}\n"],"names":["contactDisplayName","normalizeContactEmail","CRM_COLLECTIONS","crmViewIsListed","normalizeCrmViewFilters","CRM_EMAIL_TEMPLATES_LIMIT","crmEmailTemplateIsListed","normalizeCrmEmailTemplate","dynamicListDimensionsForCrmView","nameSearchToken","visibleToHost","useFirestore","collection","getDocs","limit","onSnapshot","orderBy","query","where","useEffect","useState","denied","error","code","OUTREACH_VIEWS_LIMIT","useOutreachSavedViews","orgId","uid","firestore","result","setResult","status","data","undefined","views","snapshot","docs","map","entry","id","filter","shared","ownerUid","String","unsupported","length","name","console","useOutreachEmailTemplates","hostId","emailTemplates","template","kind","visibleTo","subject","body","OUTREACH_SEARCH_LIMIT","useOutreachContactSearch","input","contactGroupId","text","email","token","idle","current","previous","contacts","timer","setTimeout","then","trim","catch","clearTimeout"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-crm.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n contactDisplayName,\n normalizeContactEmail,\n} from '@aglyn/aglyn/app-utils/contacts'\nimport {\n CRM_COLLECTIONS,\n crmLeadDisplayName,\n crmViewIsListed,\n isCrmLeadOpen,\n normalizeCrmViewFilters,\n} from '@aglyn/aglyn/app-utils/crm'\nimport {\n CRM_EMAIL_TEMPLATES_LIMIT,\n crmEmailTemplateIsListed,\n normalizeCrmEmailTemplate,\n} from '@aglyn/aglyn/app-utils/crm-email-templates'\nimport { dynamicListDimensionsForCrmView } from '@aglyn/aglyn/app-utils/dynamic-list-rule'\nimport { nameSearchToken } from '@aglyn/aglyn/app-utils/name-search'\nimport { visibleToHost } from '@aglyn/aglyn/app-utils/scope-tokens'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport {\n collection,\n getDocs,\n limit,\n onSnapshot,\n orderBy,\n query,\n where,\n} from 'firebase/firestore'\nimport { useEffect, useState } from 'react'\nimport type { OutreachLoad } from './use-outreach-data'\n\n/**\n * What Outreach reads from the organization's CRM (AGL-2980): the saved\n * Contacts views people can be enrolled from, the email templates a step\n * can send, and the contacts a search finds.\n *\n * Through the core's own CRM model — the collection names, the listing\n * rules, the view translation the dynamic-list sweep uses — and never\n * through the CRM plugin, which a plugin may not import. The Firestore\n * rules gate every one of these reads on `data.manage`, the same permission\n * the enroll routes ask for, so a member who cannot read the people cannot\n * enroll them either.\n */\n\nconst denied = (error: unknown) =>\n (error as { code?: unknown } | null)?.code === 'permission-denied'\n\n/** A saved Contacts or Leads view people can be enrolled from (AGL-3234). */\nexport interface OutreachViewOption {\n id: string\n name: string\n /** Which list the view is of; a Leads view selects the sequence's site's leads. */\n section: 'contacts' | 'leads'\n}\n\n/** The most saved views the picker lists. */\nexport const OUTREACH_VIEWS_LIMIT = 50\n\n/**\n * The saved Contacts views this reader may enroll from: their own and the\n * shared ones — a colleague's private view is theirs — that translate whole\n * into an audience. A view filtering on something enrolling cannot apply is\n * not offered, because dropping that filter would enroll more people than\n * the view shows.\n */\nexport function useOutreachSavedViews(\n orgId: string | null,\n uid: string | null,\n): OutreachLoad<OutreachViewOption[]> {\n const firestore = useFirestore()\n const [result, setResult] = useState<OutreachLoad<OutreachViewOption[]>>({\n status: 'loading',\n data: [],\n })\n useEffect(() => {\n setResult({ status: 'loading', data: [] })\n if (!orgId) return undefined\n return onSnapshot(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.views),\n where('section', 'in', ['contacts', 'leads']),\n orderBy('name'),\n limit(OUTREACH_VIEWS_LIMIT),\n ),\n (snapshot) =>\n setResult({\n status: 'ready',\n data: snapshot.docs\n .map((entry) => ({\n id: entry.id,\n data: entry.data() as Record<string, unknown>,\n }))\n .filter(\n ({ data }) =>\n typeof data['name'] === 'string' &&\n data['name'] !== '' &&\n crmViewIsListed(\n {\n shared: data['shared'] === true,\n ownerUid: String(data['ownerUid'] ?? ''),\n },\n uid,\n ) &&\n // A Leads view narrows by status alone, which enrolling can\n // always apply; a Contacts view must be readable as a list.\n (data['section'] === 'leads' ||\n dynamicListDimensionsForCrmView(\n normalizeCrmViewFilters(data['filters']),\n ).unsupported.length === 0),\n )\n .map(({ id, data }) => ({\n id,\n name: String(data['name']),\n section: data['section'] === 'leads' ? ('leads' as const) : ('contacts' as const),\n })),\n }),\n (error) => {\n if (!denied(error))\n console.error('[outreach] saved views could not be read', error)\n setResult({ status: denied(error) ? 'refused' : 'error', data: [] })\n },\n )\n }, [firestore, orgId, uid])\n return result\n}\n\n/** A CRM email template a step can send the body of. */\nexport interface OutreachTemplateOption {\n id: string\n name: string\n subject: string\n body: string\n}\n\n/**\n * The CRM email templates a step at this site can use: whole letters, not\n * snippets; the team's and this reader's own; written for the whole\n * organization or for this site.\n */\nexport function useOutreachEmailTemplates(\n orgId: string | null,\n hostId: string | null,\n uid: string | null,\n): OutreachLoad<OutreachTemplateOption[]> {\n const firestore = useFirestore()\n const [result, setResult] = useState<OutreachLoad<OutreachTemplateOption[]>>({\n status: 'loading',\n data: [],\n })\n useEffect(() => {\n setResult({ status: 'loading', data: [] })\n if (!orgId) return undefined\n return onSnapshot(\n query(\n collection(firestore, 'orgs', orgId, CRM_COLLECTIONS.emailTemplates),\n orderBy('name'),\n limit(CRM_EMAIL_TEMPLATES_LIMIT),\n ),\n (snapshot) =>\n setResult({\n status: 'ready',\n data: snapshot.docs\n .map((entry) => ({\n id: entry.id,\n template: normalizeCrmEmailTemplate(entry.data()),\n }))\n .filter(\n ({ template }) =>\n template.kind === 'template' &&\n crmEmailTemplateIsListed(template, uid) &&\n (!hostId || visibleToHost(template.visibleTo, hostId)),\n )\n .map(({ id, template }) => ({\n id,\n name: template.name,\n subject: template.subject,\n body: template.body,\n })),\n }),\n (error) => {\n if (!denied(error))\n console.error('[outreach] email templates could not be read', error)\n setResult({ status: denied(error) ? 'refused' : 'error', data: [] })\n },\n )\n }, [firestore, orgId, hostId, uid])\n return result\n}\n\n/** A contact a search found. */\nexport interface OutreachContactOption {\n id: string\n name: string\n email: string | null\n}\n\n/** The most contacts one search answers with. */\nexport const OUTREACH_SEARCH_LIMIT = 25\n\n/**\n * The contacts at `hostId` a search finds: by address when the text is one,\n * otherwise by a word of the name — the `nameTokens` the contacts carry for\n * the CRM's own search. `idle` until something is typed.\n */\nexport function useOutreachContactSearch(input: {\n orgId: string | null\n hostId: string | null\n contactGroupId: string | null\n text: string\n}): OutreachLoad<OutreachContactOption[]> & { idle: boolean } {\n const { orgId, hostId, contactGroupId, text } = input\n const firestore = useFirestore()\n const [result, setResult] = useState<OutreachLoad<OutreachContactOption[]>>({\n status: 'ready',\n data: [],\n })\n const email = normalizeContactEmail(text)\n const token = email ? '' : nameSearchToken(text)\n const idle = !orgId || !hostId || (!email && !token)\n useEffect(() => {\n if (idle || !orgId || !hostId) {\n setResult({ status: 'ready', data: [] })\n return undefined\n }\n let current = true\n setResult((previous) => ({ status: 'loading', data: previous.data }))\n const contacts = collection(firestore, 'orgs', orgId, 'contacts')\n // Settles the typing before it reads: one query per pause, not per key.\n const timer = setTimeout(() => {\n getDocs(\n email\n ? query(\n contacts,\n where('email', '==', email),\n limit(OUTREACH_SEARCH_LIMIT),\n )\n : query(\n contacts,\n where('nameTokens', 'array-contains', token),\n orderBy('nameLower'),\n limit(OUTREACH_SEARCH_LIMIT),\n ),\n )\n .then((snapshot) => {\n if (!current) return\n setResult({\n status: 'ready',\n data: snapshot.docs\n .map((entry) => ({\n id: entry.id,\n data: entry.data() as Record<string, unknown>,\n }))\n .filter(({ data }) =>\n visibleToHost(\n data['visibleTo'] as string[] | undefined,\n hostId,\n ),\n )\n .map(({ id, data }) => ({\n id,\n name: contactDisplayName(data, contactGroupId ?? hostId).trim(),\n email: normalizeContactEmail(data['email']),\n })),\n })\n })\n .catch((error: unknown) => {\n if (!current) return\n if (!denied(error))\n console.error('[outreach] the contact search failed', error)\n setResult({ status: denied(error) ? 'refused' : 'error', data: [] })\n })\n }, 300)\n return () => {\n current = false\n clearTimeout(timer)\n }\n }, [firestore, orgId, hostId, contactGroupId, email, token, idle])\n return { ...result, idle }\n}\n\n/** The most leads the picker reads — the Leads list's own window. */\nexport const OUTREACH_LEADS_WINDOW = 200\n\n/**\n * The open leads at `hostId` a search finds (AGL-3234): the site's most\n * recently seen leads, read once while the tab is open and narrowed in\n * the browser by a word of the name or the address — a lead carries no\n * search tokens, and the window is the Leads list's own. Converted and\n * closed leads are left out: they are a contact's, or a verdict's, to\n * enroll. `idle` until something is typed.\n */\nexport function useOutreachLeadSearch(input: {\n hostId: string | null\n text: string\n enabled: boolean\n}): OutreachLoad<OutreachContactOption[]> & { idle: boolean } {\n const { hostId, text, enabled } = input\n const firestore = useFirestore()\n const [window, setWindow] = useState<OutreachLoad<OutreachContactOption[]>>({\n status: 'ready',\n data: [],\n })\n useEffect(() => {\n if (!enabled || !hostId) {\n setWindow({ status: 'ready', data: [] })\n return undefined\n }\n let current = true\n setWindow({ status: 'loading', data: [] })\n getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'leads'),\n orderBy('lastSeenAtMs', 'desc'),\n limit(OUTREACH_LEADS_WINDOW),\n ),\n )\n .then((snapshot) => {\n if (!current) return\n setWindow({\n status: 'ready',\n data: snapshot.docs\n .map((entry) => ({ id: entry.id, data: entry.data() as Record<string, unknown> }))\n .filter(({ data }) => isCrmLeadOpen(data as never) && !data['convertedContactId'])\n .map(({ id, data }) => ({\n id,\n name: crmLeadDisplayName(data),\n email: normalizeContactEmail(data['email']),\n })),\n })\n })\n .catch((error: unknown) => {\n if (!current) return\n if (!denied(error)) console.error('[outreach] the leads could not be read', error)\n setWindow({ status: denied(error) ? 'refused' : 'error', data: [] })\n })\n return () => {\n current = false\n }\n }, [firestore, hostId, enabled])\n const needle = text.trim().toLowerCase()\n const idle = !hostId || !needle\n const data = idle\n ? []\n : window.data\n .filter(\n (lead) =>\n lead.name.toLowerCase().includes(needle) ||\n (lead.email ?? '').includes(needle),\n )\n .slice(0, OUTREACH_SEARCH_LIMIT)\n return { ...window, data, idle }\n}\n"],"names":["contactDisplayName","normalizeContactEmail","CRM_COLLECTIONS","crmLeadDisplayName","crmViewIsListed","isCrmLeadOpen","normalizeCrmViewFilters","CRM_EMAIL_TEMPLATES_LIMIT","crmEmailTemplateIsListed","normalizeCrmEmailTemplate","dynamicListDimensionsForCrmView","nameSearchToken","visibleToHost","useFirestore","collection","getDocs","limit","onSnapshot","orderBy","query","where","useEffect","useState","denied","error","code","OUTREACH_VIEWS_LIMIT","useOutreachSavedViews","orgId","uid","firestore","result","setResult","status","data","undefined","views","snapshot","docs","map","entry","id","filter","shared","ownerUid","String","unsupported","length","name","section","console","useOutreachEmailTemplates","hostId","emailTemplates","template","kind","visibleTo","subject","body","OUTREACH_SEARCH_LIMIT","useOutreachContactSearch","input","contactGroupId","text","email","token","idle","current","previous","contacts","timer","setTimeout","then","trim","catch","clearTimeout","OUTREACH_LEADS_WINDOW","useOutreachLeadSearch","enabled","window","setWindow","needle","toLowerCase","lead","includes","slice"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SACEA,kBAAkB,EAClBC,qBAAqB,QAChB,kCAAiC;AACxC,SACEC,eAAe,EACfC,kBAAkB,EAClBC,eAAe,EACfC,aAAa,EACbC,uBAAuB,QAClB,6BAA4B;AACnC,SACEC,yBAAyB,EACzBC,wBAAwB,EACxBC,yBAAyB,QACpB,6CAA4C;AACnD,SAASC,+BAA+B,QAAQ,2CAA0C;AAC1F,SAASC,eAAe,QAAQ,qCAAoC;AACpE,SAASC,aAAa,QAAQ,sCAAqC;AACnE,SAASC,YAAY,QAAQ,iCAAgC;AAC7D,SACEC,UAAU,EACVC,OAAO,EACPC,KAAK,EACLC,UAAU,EACVC,OAAO,EACPC,KAAK,EACLC,KAAK,QACA,qBAAoB;AAC3B,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAG3C;;;;;;;;;;;CAWC,GAED,MAAMC,SAAS,CAACC,QACd,CAACA,yBAAD,AAACA,MAAqCC,IAAI,MAAK;AAUjD,2CAA2C,GAC3C,OAAO,MAAMC,uBAAuB,GAAE;AAEtC;;;;;;CAMC,GACD,OAAO,SAASC,sBACdC,KAAoB,EACpBC,GAAkB;IAElB,MAAMC,YAAYjB;IAClB,MAAM,CAACkB,QAAQC,UAAU,GAAGV,SAA6C;QACvEW,QAAQ;QACRC,MAAM,EAAE;IACV;IACAb,UAAU;QACRW,UAAU;YAAEC,QAAQ;YAAWC,MAAM,EAAE;QAAC;QACxC,IAAI,CAACN,OAAO,OAAOO;QACnB,OAAOlB,WACLE,MACEL,WAAWgB,WAAW,QAAQF,OAAO1B,gBAAgBkC,KAAK,GAC1DhB,MAAM,WAAW,MAAM;YAAC;YAAY;SAAQ,GAC5CF,QAAQ,SACRF,MAAMU,wBAER,CAACW,WACCL,UAAU;gBACRC,QAAQ;gBACRC,MAAMG,SAASC,IAAI,CAChBC,GAAG,CAAC,CAACC,QAAW,CAAA;wBACfC,IAAID,MAAMC,EAAE;wBACZP,MAAMM,MAAMN,IAAI;oBAClB,CAAA,GACCQ,MAAM,CACL,CAAC,EAAER,IAAI,EAAE;wBAMcA;2BALrB,OAAOA,IAAI,CAAC,OAAO,KAAK,YACxBA,IAAI,CAAC,OAAO,KAAK,MACjB9B,gBACE;wBACEuC,QAAQT,IAAI,CAAC,SAAS,KAAK;wBAC3BU,UAAUC,QAAOX,iBAAAA,IAAI,CAAC,WAAW,YAAhBA,iBAAoB;oBACvC,GACAL,QAEF,4DAA4D;oBAC5D,4DAA4D;oBAC3DK,CAAAA,IAAI,CAAC,UAAU,KAAK,WACnBxB,gCACEJ,wBAAwB4B,IAAI,CAAC,UAAU,GACvCY,WAAW,CAACC,MAAM,KAAK,CAAA;mBAE9BR,GAAG,CAAC,CAAC,EAAEE,EAAE,EAAEP,IAAI,EAAE,GAAM,CAAA;wBACtBO;wBACAO,MAAMH,OAAOX,IAAI,CAAC,OAAO;wBACzBe,SAASf,IAAI,CAAC,UAAU,KAAK,UAAW,UAAqB;oBAC/D,CAAA;YACJ,IACF,CAACV;YACC,IAAI,CAACD,OAAOC,QACV0B,QAAQ1B,KAAK,CAAC,4CAA4CA;YAC5DQ,UAAU;gBAAEC,QAAQV,OAAOC,SAAS,YAAY;gBAASU,MAAM,EAAE;YAAC;QACpE;IAEJ,GAAG;QAACJ;QAAWF;QAAOC;KAAI;IAC1B,OAAOE;AACT;AAUA;;;;CAIC,GACD,OAAO,SAASoB,0BACdvB,KAAoB,EACpBwB,MAAqB,EACrBvB,GAAkB;IAElB,MAAMC,YAAYjB;IAClB,MAAM,CAACkB,QAAQC,UAAU,GAAGV,SAAiD;QAC3EW,QAAQ;QACRC,MAAM,EAAE;IACV;IACAb,UAAU;QACRW,UAAU;YAAEC,QAAQ;YAAWC,MAAM,EAAE;QAAC;QACxC,IAAI,CAACN,OAAO,OAAOO;QACnB,OAAOlB,WACLE,MACEL,WAAWgB,WAAW,QAAQF,OAAO1B,gBAAgBmD,cAAc,GACnEnC,QAAQ,SACRF,MAAMT,6BAER,CAAC8B,WACCL,UAAU;gBACRC,QAAQ;gBACRC,MAAMG,SAASC,IAAI,CAChBC,GAAG,CAAC,CAACC,QAAW,CAAA;wBACfC,IAAID,MAAMC,EAAE;wBACZa,UAAU7C,0BAA0B+B,MAAMN,IAAI;oBAChD,CAAA,GACCQ,MAAM,CACL,CAAC,EAAEY,QAAQ,EAAE,GACXA,SAASC,IAAI,KAAK,cAClB/C,yBAAyB8C,UAAUzB,QAClC,CAAA,CAACuB,UAAUxC,cAAc0C,SAASE,SAAS,EAAEJ,OAAM,GAEvDb,GAAG,CAAC,CAAC,EAAEE,EAAE,EAAEa,QAAQ,EAAE,GAAM,CAAA;wBAC1Bb;wBACAO,MAAMM,SAASN,IAAI;wBACnBS,SAASH,SAASG,OAAO;wBACzBC,MAAMJ,SAASI,IAAI;oBACrB,CAAA;YACJ,IACF,CAAClC;YACC,IAAI,CAACD,OAAOC,QACV0B,QAAQ1B,KAAK,CAAC,gDAAgDA;YAChEQ,UAAU;gBAAEC,QAAQV,OAAOC,SAAS,YAAY;gBAASU,MAAM,EAAE;YAAC;QACpE;IAEJ,GAAG;QAACJ;QAAWF;QAAOwB;QAAQvB;KAAI;IAClC,OAAOE;AACT;AASA,+CAA+C,GAC/C,OAAO,MAAM4B,wBAAwB,GAAE;AAEvC;;;;CAIC,GACD,OAAO,SAASC,yBAAyBC,KAKxC;IACC,MAAM,EAAEjC,KAAK,EAAEwB,MAAM,EAAEU,cAAc,EAAEC,IAAI,EAAE,GAAGF;IAChD,MAAM/B,YAAYjB;IAClB,MAAM,CAACkB,QAAQC,UAAU,GAAGV,SAAgD;QAC1EW,QAAQ;QACRC,MAAM,EAAE;IACV;IACA,MAAM8B,QAAQ/D,sBAAsB8D;IACpC,MAAME,QAAQD,QAAQ,KAAKrD,gBAAgBoD;IAC3C,MAAMG,OAAO,CAACtC,SAAS,CAACwB,UAAW,CAACY,SAAS,CAACC;IAC9C5C,UAAU;QACR,IAAI6C,QAAQ,CAACtC,SAAS,CAACwB,QAAQ;YAC7BpB,UAAU;gBAAEC,QAAQ;gBAASC,MAAM,EAAE;YAAC;YACtC,OAAOC;QACT;QACA,IAAIgC,UAAU;QACdnC,UAAU,CAACoC,WAAc,CAAA;gBAAEnC,QAAQ;gBAAWC,MAAMkC,SAASlC,IAAI;YAAC,CAAA;QAClE,MAAMmC,WAAWvD,WAAWgB,WAAW,QAAQF,OAAO;QACtD,wEAAwE;QACxE,MAAM0C,QAAQC,WAAW;YACvBxD,QACEiD,QACI7C,MACEkD,UACAjD,MAAM,SAAS,MAAM4C,QACrBhD,MAAM2C,0BAERxC,MACEkD,UACAjD,MAAM,cAAc,kBAAkB6C,QACtC/C,QAAQ,cACRF,MAAM2C,yBAGXa,IAAI,CAAC,CAACnC;gBACL,IAAI,CAAC8B,SAAS;gBACdnC,UAAU;oBACRC,QAAQ;oBACRC,MAAMG,SAASC,IAAI,CAChBC,GAAG,CAAC,CAACC,QAAW,CAAA;4BACfC,IAAID,MAAMC,EAAE;4BACZP,MAAMM,MAAMN,IAAI;wBAClB,CAAA,GACCQ,MAAM,CAAC,CAAC,EAAER,IAAI,EAAE,GACftB,cACEsB,IAAI,CAAC,YAAY,EACjBkB,SAGHb,GAAG,CAAC,CAAC,EAAEE,EAAE,EAAEP,IAAI,EAAE,GAAM,CAAA;4BACtBO;4BACAO,MAAMhD,mBAAmBkC,MAAM4B,yBAAAA,iBAAkBV,QAAQqB,IAAI;4BAC7DT,OAAO/D,sBAAsBiC,IAAI,CAAC,QAAQ;wBAC5C,CAAA;gBACJ;YACF,GACCwC,KAAK,CAAC,CAAClD;gBACN,IAAI,CAAC2C,SAAS;gBACd,IAAI,CAAC5C,OAAOC,QACV0B,QAAQ1B,KAAK,CAAC,wCAAwCA;gBACxDQ,UAAU;oBAAEC,QAAQV,OAAOC,SAAS,YAAY;oBAASU,MAAM,EAAE;gBAAC;YACpE;QACJ,GAAG;QACH,OAAO;YACLiC,UAAU;YACVQ,aAAaL;QACf;IACF,GAAG;QAACxC;QAAWF;QAAOwB;QAAQU;QAAgBE;QAAOC;QAAOC;KAAK;IACjE,OAAO,aAAKnC;QAAQmC;;AACtB;AAEA,mEAAmE,GACnE,OAAO,MAAMU,wBAAwB,IAAG;AAExC;;;;;;;CAOC,GACD,OAAO,SAASC,sBAAsBhB,KAIrC;IACC,MAAM,EAAET,MAAM,EAAEW,IAAI,EAAEe,OAAO,EAAE,GAAGjB;IAClC,MAAM/B,YAAYjB;IAClB,MAAM,CAACkE,QAAQC,UAAU,GAAG1D,SAAgD;QAC1EW,QAAQ;QACRC,MAAM,EAAE;IACV;IACAb,UAAU;QACR,IAAI,CAACyD,WAAW,CAAC1B,QAAQ;YACvB4B,UAAU;gBAAE/C,QAAQ;gBAASC,MAAM,EAAE;YAAC;YACtC,OAAOC;QACT;QACA,IAAIgC,UAAU;QACda,UAAU;YAAE/C,QAAQ;YAAWC,MAAM,EAAE;QAAC;QACxCnB,QACEI,MACEL,WAAWgB,WAAW,SAASsB,QAAQ,UACvClC,QAAQ,gBAAgB,SACxBF,MAAM4D,yBAGPJ,IAAI,CAAC,CAACnC;YACL,IAAI,CAAC8B,SAAS;YACda,UAAU;gBACR/C,QAAQ;gBACRC,MAAMG,SAASC,IAAI,CAChBC,GAAG,CAAC,CAACC,QAAW,CAAA;wBAAEC,IAAID,MAAMC,EAAE;wBAAEP,MAAMM,MAAMN,IAAI;oBAA8B,CAAA,GAC9EQ,MAAM,CAAC,CAAC,EAAER,IAAI,EAAE,GAAK7B,cAAc6B,SAAkB,CAACA,IAAI,CAAC,qBAAqB,EAChFK,GAAG,CAAC,CAAC,EAAEE,EAAE,EAAEP,IAAI,EAAE,GAAM,CAAA;wBACtBO;wBACAO,MAAM7C,mBAAmB+B;wBACzB8B,OAAO/D,sBAAsBiC,IAAI,CAAC,QAAQ;oBAC5C,CAAA;YACJ;QACF,GACCwC,KAAK,CAAC,CAAClD;YACN,IAAI,CAAC2C,SAAS;YACd,IAAI,CAAC5C,OAAOC,QAAQ0B,QAAQ1B,KAAK,CAAC,0CAA0CA;YAC5EwD,UAAU;gBAAE/C,QAAQV,OAAOC,SAAS,YAAY;gBAASU,MAAM,EAAE;YAAC;QACpE;QACF,OAAO;YACLiC,UAAU;QACZ;IACF,GAAG;QAACrC;QAAWsB;QAAQ0B;KAAQ;IAC/B,MAAMG,SAASlB,KAAKU,IAAI,GAAGS,WAAW;IACtC,MAAMhB,OAAO,CAACd,UAAU,CAAC6B;IACzB,MAAM/C,OAAOgC,OACT,EAAE,GACFa,OAAO7C,IAAI,CACRQ,MAAM,CACL,CAACyC;YAEEA;eADDA,KAAKnC,IAAI,CAACkC,WAAW,GAAGE,QAAQ,CAACH,WACjC,EAACE,cAAAA,KAAKnB,KAAK,YAAVmB,cAAc,IAAIC,QAAQ,CAACH;OAE/BI,KAAK,CAAC,GAAG1B;IAChB,OAAO,aAAKoB;QAAQ7C;QAAMgC;;AAC5B"}
@@ -17,7 +17,8 @@ export interface OutreachMailboxApi {
17
17
  }): Promise<OutreachConnectCompleteResponse>;
18
18
  saveSettings(input: Omit<OutreachMailboxSettingsRequest, 'orgId'>): Promise<OutreachMailboxResponse>;
19
19
  setPaused(mailboxId: string, paused: boolean): Promise<OutreachMailboxResponse>;
20
- sendTest(mailboxId: string): Promise<OutreachMailboxTestResponse>;
20
+ /** A test from the mailbox — to `to`, or to the account's own address. */
21
+ sendTest(mailboxId: string, to?: string): Promise<OutreachMailboxTestResponse>;
21
22
  disconnect(mailboxId: string): Promise<OutreachMailboxDisconnectResponse>;
22
23
  }
23
24
  /**
@@ -81,11 +81,13 @@ import { OUTREACH_API_ROUTES } from "../constants/api-routes.js";
81
81
  paused
82
82
  }
83
83
  }),
84
- sendTest: (mailboxId)=>call(OUTREACH_API_ROUTES.mailboxesTest, {
84
+ sendTest: (mailboxId, to)=>call(OUTREACH_API_ROUTES.mailboxesTest, {
85
85
  method: 'POST',
86
- body: {
86
+ body: _extends({
87
87
  mailboxId
88
- }
88
+ }, to ? {
89
+ to
90
+ } : {})
89
91
  }),
90
92
  disconnect: (mailboxId)=>call(OUTREACH_API_ROUTES.mailboxesDisconnect, {
91
93
  method: 'POST',
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-mailbox-api.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useMemo } from 'react'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport type {\n OutreachApiRefusalReason,\n OutreachConnectCompleteResponse,\n OutreachConnectResponse,\n OutreachMailboxAvailability,\n OutreachMailboxDisconnectResponse,\n OutreachMailboxResponse,\n OutreachMailboxSettingsRequest,\n OutreachMailboxTestResponse,\n} from '../mailboxes/mailbox-api'\n\n/** A mailbox route's refusal, carrying the sentence and the stable reason. */\nexport class OutreachApiError extends Error {\n readonly reason: OutreachApiRefusalReason | 'unreachable'\n readonly status: number\n\n constructor(message: string, reason: OutreachApiRefusalReason | 'unreachable', status: number) {\n super(message)\n this.name = 'OutreachApiError'\n this.reason = reason\n this.status = status\n }\n}\n\n/** The mailbox routes, as the Mailboxes panel calls them. */\nexport interface OutreachMailboxApi {\n availability(): Promise<OutreachMailboxAvailability>\n /** Google's consent address for a new connect, or a reconnect. */\n connect(): Promise<string>\n complete(input: { code: string; state: string; timezone?: string }): Promise<OutreachConnectCompleteResponse>\n saveSettings(\n input: Omit<OutreachMailboxSettingsRequest, 'orgId'>,\n ): Promise<OutreachMailboxResponse>\n setPaused(mailboxId: string, paused: boolean): Promise<OutreachMailboxResponse>\n sendTest(mailboxId: string): Promise<OutreachMailboxTestResponse>\n disconnect(mailboxId: string): Promise<OutreachMailboxDisconnectResponse>\n}\n\n/**\n * The Mailboxes panel's one door to the mailbox routes (AGL-2978), with the\n * member's session attached and the organization named on every call — the\n * dispatcher's release gate reads it. A refusal is thrown as an\n * {@link OutreachApiError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachMailboxApi(orgId: string | null): OutreachMailboxApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(route: string, init: { method: 'GET' | 'POST'; body?: Record<string, unknown> }): Promise<T> => {\n if (!orgId) throw new OutreachApiError('Open an organization first.', 'org-required', 400)\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n const response = await authorizedFetch(user, path, {\n method: init.method,\n ...(init.method === 'POST'\n ? {\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...init.body }),\n }\n : {}),\n })\n const payload = (await response.json().catch(() => null)) as\n | (T & { error?: undefined })\n | { error?: string; reason?: OutreachApiRefusalReason }\n | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as { error?: string; reason?: OutreachApiRefusalReason }\n throw new OutreachApiError(\n refusal.error || 'Sequences could not be reached. Try again.',\n refusal.reason ?? 'unreachable',\n response.status,\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachMailboxApi>(\n () => ({\n availability: () =>\n call<OutreachMailboxAvailability>(OUTREACH_API_ROUTES.mailboxesAvailability, { method: 'GET' }),\n connect: async () =>\n (await call<OutreachConnectResponse>(OUTREACH_API_ROUTES.mailboxesConnect, { method: 'POST' })).url,\n complete: (input) =>\n call<OutreachConnectCompleteResponse>(OUTREACH_API_ROUTES.mailboxesConnectComplete, {\n method: 'POST',\n body: input,\n }),\n saveSettings: (input) =>\n call<OutreachMailboxResponse>(OUTREACH_API_ROUTES.mailboxesSettings, {\n method: 'POST',\n body: input as unknown as Record<string, unknown>,\n }),\n setPaused: (mailboxId, paused) =>\n call<OutreachMailboxResponse>(OUTREACH_API_ROUTES.mailboxesStatus, {\n method: 'POST',\n body: { mailboxId, paused },\n }),\n sendTest: (mailboxId) =>\n call<OutreachMailboxTestResponse>(OUTREACH_API_ROUTES.mailboxesTest, {\n method: 'POST',\n body: { mailboxId },\n }),\n disconnect: (mailboxId) =>\n call<OutreachMailboxDisconnectResponse>(OUTREACH_API_ROUTES.mailboxesDisconnect, {\n method: 'POST',\n body: { mailboxId },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachApiError","Error","message","reason","status","name","useOutreachMailboxApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","availability","mailboxesAvailability","connect","mailboxesConnect","url","complete","input","mailboxesConnectComplete","saveSettings","mailboxesSettings","setPaused","mailboxId","paused","mailboxesStatus","sendTest","mailboxesTest","disconnect","mailboxesDisconnect"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,WAAW,EAAEC,OAAO,QAAQ,QAAO;AAC5C,SAASC,mBAAmB,QAAQ,6BAAyB;AAY7D,4EAA4E,GAC5E,OAAO,MAAMC,yBAAyBC;IAIpC,YAAYC,OAAe,EAAEC,MAAgD,EAAEC,MAAc,CAAE;QAC7F,KAAK,CAACF;QACN,IAAI,CAACG,IAAI,GAAG;QACZ,IAAI,CAACF,MAAM,GAAGA;QACd,IAAI,CAACC,MAAM,GAAGA;IAChB;AACF;AAgBA;;;;;;CAMC,GACD,OAAO,SAASE,sBAAsBC,KAAoB;IACxD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGb;IAEvB,MAAMc,OAAOb,YACX,OAAUc,OAAeC;QACvB,IAAI,CAACL,OAAO,MAAM,IAAIP,iBAAiB,+BAA+B,gBAAgB;QACtF,MAAMa,OACJD,KAAKE,MAAM,KAAK,QACZ,CAAC,KAAK,EAAEH,MAAM,OAAO,EAAEI,mBAAmBR,QAAQ,GAClD,CAAC,KAAK,EAAEI,OAAO;QACrB,MAAMK,WAAW,MAAMrB,gBAAgBc,MAAMI,MAAM;YACjDC,QAAQF,KAAKE,MAAM;WACfF,KAAKE,MAAM,KAAK,SAChB;YACEG,SAAS;gBAAE,gBAAgB;YAAmB;YAC9CC,MAAMC,KAAKC,SAAS,CAAC;gBAAEb;eAAUK,KAAKM,IAAI;QAC5C,IACA,CAAC;QAEP,MAAMG,UAAW,MAAML,SAASM,IAAI,GAAGC,KAAK,CAAC,IAAM;QAInD,IAAI,CAACP,SAASQ,EAAE,IAAI,CAACH,SAAS;gBAI1BI;YAHF,MAAMA,UAAWJ,kBAAAA,UAAW,CAAC;YAC7B,MAAM,IAAIrB,iBACRyB,QAAQC,KAAK,IAAI,+CACjBD,kBAAAA,QAAQtB,MAAM,YAAdsB,kBAAkB,eAClBT,SAASZ,MAAM;QAEnB;QACA,OAAOiB;IACT,GACA;QAACZ;QAAMF;KAAM;IAGf,OAAOT,QACL,IAAO,CAAA;YACL6B,cAAc,IACZjB,KAAkCX,oBAAoB6B,qBAAqB,EAAE;oBAAEd,QAAQ;gBAAM;YAC/Fe,SAAS,UACP,AAAC,CAAA,MAAMnB,KAA8BX,oBAAoB+B,gBAAgB,EAAE;oBAAEhB,QAAQ;gBAAO,EAAC,EAAGiB,GAAG;YACrGC,UAAU,CAACC,QACTvB,KAAsCX,oBAAoBmC,wBAAwB,EAAE;oBAClFpB,QAAQ;oBACRI,MAAMe;gBACR;YACFE,cAAc,CAACF,QACbvB,KAA8BX,oBAAoBqC,iBAAiB,EAAE;oBACnEtB,QAAQ;oBACRI,MAAMe;gBACR;YACFI,WAAW,CAACC,WAAWC,SACrB7B,KAA8BX,oBAAoByC,eAAe,EAAE;oBACjE1B,QAAQ;oBACRI,MAAM;wBAAEoB;wBAAWC;oBAAO;gBAC5B;YACFE,UAAU,CAACH,YACT5B,KAAkCX,oBAAoB2C,aAAa,EAAE;oBACnE5B,QAAQ;oBACRI,MAAM;wBAAEoB;oBAAU;gBACpB;YACFK,YAAY,CAACL,YACX5B,KAAwCX,oBAAoB6C,mBAAmB,EAAE;oBAC/E9B,QAAQ;oBACRI,MAAM;wBAAEoB;oBAAU;gBACpB;QACJ,CAAA,GACA;QAAC5B;KAAK;AAEV"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/use-outreach-mailbox-api.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { authorizedFetch } from '@aglyn/shared-util-http/authorized-token'\nimport { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useMemo } from 'react'\nimport { OUTREACH_API_ROUTES } from '../constants/api-routes'\nimport type {\n OutreachApiRefusalReason,\n OutreachConnectCompleteResponse,\n OutreachConnectResponse,\n OutreachMailboxAvailability,\n OutreachMailboxDisconnectResponse,\n OutreachMailboxResponse,\n OutreachMailboxSettingsRequest,\n OutreachMailboxTestResponse,\n} from '../mailboxes/mailbox-api'\n\n/** A mailbox route's refusal, carrying the sentence and the stable reason. */\nexport class OutreachApiError extends Error {\n readonly reason: OutreachApiRefusalReason | 'unreachable'\n readonly status: number\n\n constructor(message: string, reason: OutreachApiRefusalReason | 'unreachable', status: number) {\n super(message)\n this.name = 'OutreachApiError'\n this.reason = reason\n this.status = status\n }\n}\n\n/** The mailbox routes, as the Mailboxes panel calls them. */\nexport interface OutreachMailboxApi {\n availability(): Promise<OutreachMailboxAvailability>\n /** Google's consent address for a new connect, or a reconnect. */\n connect(): Promise<string>\n complete(input: { code: string; state: string; timezone?: string }): Promise<OutreachConnectCompleteResponse>\n saveSettings(\n input: Omit<OutreachMailboxSettingsRequest, 'orgId'>,\n ): Promise<OutreachMailboxResponse>\n setPaused(mailboxId: string, paused: boolean): Promise<OutreachMailboxResponse>\n /** A test from the mailbox — to `to`, or to the account's own address. */\n sendTest(mailboxId: string, to?: string): Promise<OutreachMailboxTestResponse>\n disconnect(mailboxId: string): Promise<OutreachMailboxDisconnectResponse>\n}\n\n/**\n * The Mailboxes panel's one door to the mailbox routes (AGL-2978), with the\n * member's session attached and the organization named on every call — the\n * dispatcher's release gate reads it. A refusal is thrown as an\n * {@link OutreachApiError} carrying the route's own sentence, which is\n * written for the person reading it.\n */\nexport function useOutreachMailboxApi(orgId: string | null): OutreachMailboxApi {\n const { data: user } = useUser()\n\n const call = useCallback(\n async <T>(route: string, init: { method: 'GET' | 'POST'; body?: Record<string, unknown> }): Promise<T> => {\n if (!orgId) throw new OutreachApiError('Open an organization first.', 'org-required', 400)\n const path =\n init.method === 'GET'\n ? `/api/${route}?orgId=${encodeURIComponent(orgId)}`\n : `/api/${route}`\n const response = await authorizedFetch(user, path, {\n method: init.method,\n ...(init.method === 'POST'\n ? {\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ orgId, ...init.body }),\n }\n : {}),\n })\n const payload = (await response.json().catch(() => null)) as\n | (T & { error?: undefined })\n | { error?: string; reason?: OutreachApiRefusalReason }\n | null\n if (!response.ok || !payload) {\n const refusal = (payload ?? {}) as { error?: string; reason?: OutreachApiRefusalReason }\n throw new OutreachApiError(\n refusal.error || 'Sequences could not be reached. Try again.',\n refusal.reason ?? 'unreachable',\n response.status,\n )\n }\n return payload as T\n },\n [user, orgId],\n )\n\n return useMemo<OutreachMailboxApi>(\n () => ({\n availability: () =>\n call<OutreachMailboxAvailability>(OUTREACH_API_ROUTES.mailboxesAvailability, { method: 'GET' }),\n connect: async () =>\n (await call<OutreachConnectResponse>(OUTREACH_API_ROUTES.mailboxesConnect, { method: 'POST' })).url,\n complete: (input) =>\n call<OutreachConnectCompleteResponse>(OUTREACH_API_ROUTES.mailboxesConnectComplete, {\n method: 'POST',\n body: input,\n }),\n saveSettings: (input) =>\n call<OutreachMailboxResponse>(OUTREACH_API_ROUTES.mailboxesSettings, {\n method: 'POST',\n body: input as unknown as Record<string, unknown>,\n }),\n setPaused: (mailboxId, paused) =>\n call<OutreachMailboxResponse>(OUTREACH_API_ROUTES.mailboxesStatus, {\n method: 'POST',\n body: { mailboxId, paused },\n }),\n sendTest: (mailboxId, to) =>\n call<OutreachMailboxTestResponse>(OUTREACH_API_ROUTES.mailboxesTest, {\n method: 'POST',\n body: { mailboxId, ...(to ? { to } : {}) },\n }),\n disconnect: (mailboxId) =>\n call<OutreachMailboxDisconnectResponse>(OUTREACH_API_ROUTES.mailboxesDisconnect, {\n method: 'POST',\n body: { mailboxId },\n }),\n }),\n [call],\n )\n}\n"],"names":["authorizedFetch","useUser","useCallback","useMemo","OUTREACH_API_ROUTES","OutreachApiError","Error","message","reason","status","name","useOutreachMailboxApi","orgId","data","user","call","route","init","path","method","encodeURIComponent","response","headers","body","JSON","stringify","payload","json","catch","ok","refusal","error","availability","mailboxesAvailability","connect","mailboxesConnect","url","complete","input","mailboxesConnectComplete","saveSettings","mailboxesSettings","setPaused","mailboxId","paused","mailboxesStatus","sendTest","to","mailboxesTest","disconnect","mailboxesDisconnect"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,eAAe,QAAQ,2CAA0C;AAC1E,SAASC,OAAO,QAAQ,iCAAgC;AACxD,SAASC,WAAW,EAAEC,OAAO,QAAQ,QAAO;AAC5C,SAASC,mBAAmB,QAAQ,6BAAyB;AAY7D,4EAA4E,GAC5E,OAAO,MAAMC,yBAAyBC;IAIpC,YAAYC,OAAe,EAAEC,MAAgD,EAAEC,MAAc,CAAE;QAC7F,KAAK,CAACF;QACN,IAAI,CAACG,IAAI,GAAG;QACZ,IAAI,CAACF,MAAM,GAAGA;QACd,IAAI,CAACC,MAAM,GAAGA;IAChB;AACF;AAiBA;;;;;;CAMC,GACD,OAAO,SAASE,sBAAsBC,KAAoB;IACxD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGb;IAEvB,MAAMc,OAAOb,YACX,OAAUc,OAAeC;QACvB,IAAI,CAACL,OAAO,MAAM,IAAIP,iBAAiB,+BAA+B,gBAAgB;QACtF,MAAMa,OACJD,KAAKE,MAAM,KAAK,QACZ,CAAC,KAAK,EAAEH,MAAM,OAAO,EAAEI,mBAAmBR,QAAQ,GAClD,CAAC,KAAK,EAAEI,OAAO;QACrB,MAAMK,WAAW,MAAMrB,gBAAgBc,MAAMI,MAAM;YACjDC,QAAQF,KAAKE,MAAM;WACfF,KAAKE,MAAM,KAAK,SAChB;YACEG,SAAS;gBAAE,gBAAgB;YAAmB;YAC9CC,MAAMC,KAAKC,SAAS,CAAC;gBAAEb;eAAUK,KAAKM,IAAI;QAC5C,IACA,CAAC;QAEP,MAAMG,UAAW,MAAML,SAASM,IAAI,GAAGC,KAAK,CAAC,IAAM;QAInD,IAAI,CAACP,SAASQ,EAAE,IAAI,CAACH,SAAS;gBAI1BI;YAHF,MAAMA,UAAWJ,kBAAAA,UAAW,CAAC;YAC7B,MAAM,IAAIrB,iBACRyB,QAAQC,KAAK,IAAI,+CACjBD,kBAAAA,QAAQtB,MAAM,YAAdsB,kBAAkB,eAClBT,SAASZ,MAAM;QAEnB;QACA,OAAOiB;IACT,GACA;QAACZ;QAAMF;KAAM;IAGf,OAAOT,QACL,IAAO,CAAA;YACL6B,cAAc,IACZjB,KAAkCX,oBAAoB6B,qBAAqB,EAAE;oBAAEd,QAAQ;gBAAM;YAC/Fe,SAAS,UACP,AAAC,CAAA,MAAMnB,KAA8BX,oBAAoB+B,gBAAgB,EAAE;oBAAEhB,QAAQ;gBAAO,EAAC,EAAGiB,GAAG;YACrGC,UAAU,CAACC,QACTvB,KAAsCX,oBAAoBmC,wBAAwB,EAAE;oBAClFpB,QAAQ;oBACRI,MAAMe;gBACR;YACFE,cAAc,CAACF,QACbvB,KAA8BX,oBAAoBqC,iBAAiB,EAAE;oBACnEtB,QAAQ;oBACRI,MAAMe;gBACR;YACFI,WAAW,CAACC,WAAWC,SACrB7B,KAA8BX,oBAAoByC,eAAe,EAAE;oBACjE1B,QAAQ;oBACRI,MAAM;wBAAEoB;wBAAWC;oBAAO;gBAC5B;YACFE,UAAU,CAACH,WAAWI,KACpBhC,KAAkCX,oBAAoB4C,aAAa,EAAE;oBACnE7B,QAAQ;oBACRI,MAAM;wBAAEoB;uBAAeI,KAAK;wBAAEA;oBAAG,IAAI,CAAC;gBACxC;YACFE,YAAY,CAACN,YACX5B,KAAwCX,oBAAoB8C,mBAAmB,EAAE;oBAC/E/B,QAAQ;oBACRI,MAAM;wBAAEoB;oBAAU;gBACpB;QACJ,CAAA,GACA;QAAC5B;KAAK;AAEV"}
@@ -17,6 +17,7 @@
17
17
  // boot needs one registry, not the whole server surface.
18
18
  import { registerPluginConsoleCron } from "@aglyn/aglyn/plugin-manager/plugin-console-crons";
19
19
  import { listPluginOrgErasers, registerPluginOrgEraser } from "@aglyn/aglyn/plugin-manager/plugin-org-erasure";
20
+ import { listPluginLeadConversionListeners, registerPluginLeadConversionListener } from "@aglyn/aglyn/plugin-manager/plugin-lead-conversion";
20
21
  import { registerPluginPersonEraser } from "@aglyn/aglyn/plugin-manager/plugin-person-erasure";
21
22
  import { listPluginUserErasers, registerPluginUserEraser } from "@aglyn/aglyn/plugin-manager/plugin-user-erasure";
22
23
  import { OUTREACH_PLUGIN_ID } from "./constants/bundle-common.js";
@@ -64,6 +65,19 @@ import { OUTREACH_SEND_JOB_ID, OUTREACH_SYNC_JOB_ID } from "./constants/runtime-
64
65
  registerPluginPersonEraser(async (request)=>(await runtime()).platformOutreachPersonEraser()(request), {
65
66
  pluginId: OUTREACH_PLUGIN_ID
66
67
  });
68
+ // A lead that converts takes its enrollments to the contact it became
69
+ // (AGL-3234): the same document, now naming the contact.
70
+ if (!listPluginLeadConversionListeners().includes(OUTREACH_PLUGIN_ID)) {
71
+ registerPluginLeadConversionListener(async (request)=>{
72
+ const [{ followOutreachLeadToContact }, platform] = await Promise.all([
73
+ import("./runtime/lead-records.js"),
74
+ runtime()
75
+ ]);
76
+ return followOutreachLeadToContact(platform.platformOutreachRuntimeDeps().firestore(), request);
77
+ }, {
78
+ pluginId: OUTREACH_PLUGIN_ID
79
+ });
80
+ }
67
81
  // The sending runtime, on the console's fifteen-minute tick.
68
82
  registerPluginConsoleCron({
69
83
  id: OUTREACH_SEND_JOB_ID,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/outreach/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Each registry from its own module, not the `@aglyn/aglyn/server` barrel:\n// boot needs one registry, not the whole server surface.\nimport { registerPluginConsoleCron } from '@aglyn/aglyn/plugin-manager/plugin-console-crons'\nimport {\n listPluginOrgErasers,\n registerPluginOrgEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-org-erasure'\nimport { registerPluginPersonEraser } from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport {\n listPluginUserErasers,\n registerPluginUserEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport { OUTREACH_PLUGIN_ID } from './constants/bundle-common'\nimport { OUTREACH_SEND_JOB_ID, OUTREACH_SYNC_JOB_ID } from './constants/runtime-jobs'\n\n/**\n * Outreach's CONSOLE-ONLY server declarations (AGL-2978), named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot.\n *\n * The erasers are registered here rather than from `serverDeclarations`,\n * which the tenant runtime also loads: the workspace eraser revokes each\n * rep's Google grant, and the account eraser (AGL-3106) revokes and deletes\n * the person's own, and both open a sealed token with `OUTREACH_TOKEN_KEY`,\n * which only the console holds. The tenant may not so much as bundle the code\n * that does (`outreach-credential-isolation.spec.ts`). Every erasure runs in\n * the console, so this is where the erasers are needed.\n *\n * The sending runtime (AGL-2981) is declared here too, for the same reason:\n * the send and sync jobs open each rep's sealed grant, so they run on the\n * console's `plugin-console-crons` tick and nowhere else, and the person\n * eraser removes the enrollments a workspace kept about someone it erases.\n *\n * Light at boot: every module that does the work is imported when a job, an\n * erasure or a tick first asks for it, so the boot cost is the registration.\n */\nexport function registerOutreachConsoleServerDeclarations(): void {\n // Idempotent against the REGISTRY, so a reset (a spec) registers again.\n if (!listPluginOrgErasers().includes(OUTREACH_PLUGIN_ID)) {\n registerPluginOrgEraser(\n async (request) => {\n const { createOutreachOrgEraser, defaultOutreachErasureDeps } = await import(\n './mailboxes/mailbox-erasure'\n )\n return createOutreachOrgEraser(defaultOutreachErasureDeps())(request)\n },\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n }\n if (!listPluginUserErasers().includes(OUTREACH_PLUGIN_ID)) {\n registerPluginUserEraser(\n async (request) => {\n const { createOutreachUserEraser, defaultOutreachErasureDeps } = await import(\n './mailboxes/mailbox-erasure'\n )\n return createOutreachUserEraser(defaultOutreachErasureDeps())(request)\n },\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n }\n // A person erased from a workspace takes their enrollments with them; the\n // do-not-contact entry stays, stripped of anything that names them.\n registerPluginPersonEraser(\n async (request) => (await runtime()).platformOutreachPersonEraser()(request),\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n // The sending runtime, on the console's fifteen-minute tick.\n registerPluginConsoleCron(\n {\n id: OUTREACH_SEND_JOB_ID,\n label: 'Sequence sends',\n drives:\n 'Sends every sequence step that has come due, from the rep’s own connected mailbox, and files each on the contact’s timeline. If it stops, no sequence sends anything and every task step waits.',\n run: async (context) => {\n const [{ runOutreachSendJob }, platform] = await Promise.all([import('./runtime/send-job'), runtime()])\n return runOutreachSendJob(platform.platformOutreachRuntimeDeps(), context)\n },\n },\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n registerPluginConsoleCron(\n {\n id: OUTREACH_SYNC_JOB_ID,\n label: 'Sequence replies and bounces',\n drives:\n 'Reads each connected mailbox for replies, out-of-office answers, opt-outs and bounces, and stops or postpones the sequence each one is about. If it stops, a person who replied or asked to be left alone keeps getting follow-ups, and a mailbox that bounces never pauses itself.',\n run: async (context) => {\n const [{ runOutreachSyncJob }, platform] = await Promise.all([import('./runtime/sync-job'), runtime()])\n return runOutreachSyncJob(platform.platformOutreachRuntimeDeps(), context)\n },\n },\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n}\n\n/** The runtime's platform reach, loaded when a job or an erasure first runs. */\nconst runtime = () => import('./runtime/platform-runtime-deps')\n"],"names":["registerPluginConsoleCron","listPluginOrgErasers","registerPluginOrgEraser","registerPluginPersonEraser","listPluginUserErasers","registerPluginUserEraser","OUTREACH_PLUGIN_ID","OUTREACH_SEND_JOB_ID","OUTREACH_SYNC_JOB_ID","registerOutreachConsoleServerDeclarations","includes","request","createOutreachOrgEraser","defaultOutreachErasureDeps","pluginId","createOutreachUserEraser","runtime","platformOutreachPersonEraser","id","label","drives","run","context","runOutreachSendJob","platform","Promise","all","platformOutreachRuntimeDeps","runOutreachSyncJob"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,2EAA2E;AAC3E,yDAAyD;AACzD,SAASA,yBAAyB,QAAQ,mDAAkD;AAC5F,SACEC,oBAAoB,EACpBC,uBAAuB,QAClB,iDAAgD;AACvD,SAASC,0BAA0B,QAAQ,oDAAmD;AAC9F,SACEC,qBAAqB,EACrBC,wBAAwB,QACnB,kDAAiD;AACxD,SAASC,kBAAkB,QAAQ,+BAA2B;AAC9D,SAASC,oBAAoB,EAAEC,oBAAoB,QAAQ,8BAA0B;AAErF;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC;IACd,wEAAwE;IACxE,IAAI,CAACR,uBAAuBS,QAAQ,CAACJ,qBAAqB;QACxDJ,wBACE,OAAOS;YACL,MAAM,EAAEC,uBAAuB,EAAEC,0BAA0B,EAAE,GAAG,MAAM,MAAM,CAC1E;YAEF,OAAOD,wBAAwBC,8BAA8BF;QAC/D,GACA;YAAEG,UAAUR;QAAmB;IAEnC;IACA,IAAI,CAACF,wBAAwBM,QAAQ,CAACJ,qBAAqB;QACzDD,yBACE,OAAOM;YACL,MAAM,EAAEI,wBAAwB,EAAEF,0BAA0B,EAAE,GAAG,MAAM,MAAM,CAC3E;YAEF,OAAOE,yBAAyBF,8BAA8BF;QAChE,GACA;YAAEG,UAAUR;QAAmB;IAEnC;IACA,0EAA0E;IAC1E,oEAAoE;IACpEH,2BACE,OAAOQ,UAAY,AAAC,CAAA,MAAMK,SAAQ,EAAGC,4BAA4B,GAAGN,UACpE;QAAEG,UAAUR;IAAmB;IAEjC,6DAA6D;IAC7DN,0BACE;QACEkB,IAAIX;QACJY,OAAO;QACPC,QACE;QACFC,KAAK,OAAOC;YACV,MAAM,CAAC,EAAEC,kBAAkB,EAAE,EAAEC,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;gBAAC,MAAM,CAAC;gBAAuBV;aAAU;YACtG,OAAOO,mBAAmBC,SAASG,2BAA2B,IAAIL;QACpE;IACF,GACA;QAAER,UAAUR;IAAmB;IAEjCN,0BACE;QACEkB,IAAIV;QACJW,OAAO;QACPC,QACE;QACFC,KAAK,OAAOC;YACV,MAAM,CAAC,EAAEM,kBAAkB,EAAE,EAAEJ,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;gBAAC,MAAM,CAAC;gBAAuBV;aAAU;YACtG,OAAOY,mBAAmBJ,SAASG,2BAA2B,IAAIL;QACpE;IACF,GACA;QAAER,UAAUR;IAAmB;AAEnC;AAEA,8EAA8E,GAC9E,MAAMU,UAAU,IAAM,MAAM,CAAC"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/outreach/src/lib/declarations.console-server.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n// Each registry from its own module, not the `@aglyn/aglyn/server` barrel:\n// boot needs one registry, not the whole server surface.\nimport { registerPluginConsoleCron } from '@aglyn/aglyn/plugin-manager/plugin-console-crons'\nimport {\n listPluginOrgErasers,\n registerPluginOrgEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-org-erasure'\nimport {\n listPluginLeadConversionListeners,\n registerPluginLeadConversionListener,\n} from '@aglyn/aglyn/plugin-manager/plugin-lead-conversion'\nimport { registerPluginPersonEraser } from '@aglyn/aglyn/plugin-manager/plugin-person-erasure'\nimport {\n listPluginUserErasers,\n registerPluginUserEraser,\n} from '@aglyn/aglyn/plugin-manager/plugin-user-erasure'\nimport { OUTREACH_PLUGIN_ID } from './constants/bundle-common'\nimport { OUTREACH_SEND_JOB_ID, OUTREACH_SYNC_JOB_ID } from './constants/runtime-jobs'\n\n/**\n * Outreach's CONSOLE-ONLY server declarations (AGL-2978), named under\n * `consoleServerDeclarations` in `plugins.config.json` and run at the\n * console's boot.\n *\n * The erasers are registered here rather than from `serverDeclarations`,\n * which the tenant runtime also loads: the workspace eraser revokes each\n * rep's Google grant, and the account eraser (AGL-3106) revokes and deletes\n * the person's own, and both open a sealed token with `OUTREACH_TOKEN_KEY`,\n * which only the console holds. The tenant may not so much as bundle the code\n * that does (`outreach-credential-isolation.spec.ts`). Every erasure runs in\n * the console, so this is where the erasers are needed.\n *\n * The sending runtime (AGL-2981) is declared here too, for the same reason:\n * the send and sync jobs open each rep's sealed grant, so they run on the\n * console's `plugin-console-crons` tick and nowhere else, and the person\n * eraser removes the enrollments a workspace kept about someone it erases.\n *\n * Light at boot: every module that does the work is imported when a job, an\n * erasure or a tick first asks for it, so the boot cost is the registration.\n */\nexport function registerOutreachConsoleServerDeclarations(): void {\n // Idempotent against the REGISTRY, so a reset (a spec) registers again.\n if (!listPluginOrgErasers().includes(OUTREACH_PLUGIN_ID)) {\n registerPluginOrgEraser(\n async (request) => {\n const { createOutreachOrgEraser, defaultOutreachErasureDeps } = await import(\n './mailboxes/mailbox-erasure'\n )\n return createOutreachOrgEraser(defaultOutreachErasureDeps())(request)\n },\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n }\n if (!listPluginUserErasers().includes(OUTREACH_PLUGIN_ID)) {\n registerPluginUserEraser(\n async (request) => {\n const { createOutreachUserEraser, defaultOutreachErasureDeps } = await import(\n './mailboxes/mailbox-erasure'\n )\n return createOutreachUserEraser(defaultOutreachErasureDeps())(request)\n },\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n }\n // A person erased from a workspace takes their enrollments with them; the\n // do-not-contact entry stays, stripped of anything that names them.\n registerPluginPersonEraser(\n async (request) => (await runtime()).platformOutreachPersonEraser()(request),\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n // A lead that converts takes its enrollments to the contact it became\n // (AGL-3234): the same document, now naming the contact.\n if (!listPluginLeadConversionListeners().includes(OUTREACH_PLUGIN_ID)) {\n registerPluginLeadConversionListener(\n async (request) => {\n const [{ followOutreachLeadToContact }, platform] = await Promise.all([\n import('./runtime/lead-records'),\n runtime(),\n ])\n return followOutreachLeadToContact(platform.platformOutreachRuntimeDeps().firestore(), request)\n },\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n }\n // The sending runtime, on the console's fifteen-minute tick.\n registerPluginConsoleCron(\n {\n id: OUTREACH_SEND_JOB_ID,\n label: 'Sequence sends',\n drives:\n 'Sends every sequence step that has come due, from the rep’s own connected mailbox, and files each on the contact’s timeline. If it stops, no sequence sends anything and every task step waits.',\n run: async (context) => {\n const [{ runOutreachSendJob }, platform] = await Promise.all([import('./runtime/send-job'), runtime()])\n return runOutreachSendJob(platform.platformOutreachRuntimeDeps(), context)\n },\n },\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n registerPluginConsoleCron(\n {\n id: OUTREACH_SYNC_JOB_ID,\n label: 'Sequence replies and bounces',\n drives:\n 'Reads each connected mailbox for replies, out-of-office answers, opt-outs and bounces, and stops or postpones the sequence each one is about. If it stops, a person who replied or asked to be left alone keeps getting follow-ups, and a mailbox that bounces never pauses itself.',\n run: async (context) => {\n const [{ runOutreachSyncJob }, platform] = await Promise.all([import('./runtime/sync-job'), runtime()])\n return runOutreachSyncJob(platform.platformOutreachRuntimeDeps(), context)\n },\n },\n { pluginId: OUTREACH_PLUGIN_ID },\n )\n}\n\n/** The runtime's platform reach, loaded when a job or an erasure first runs. */\nconst runtime = () => import('./runtime/platform-runtime-deps')\n"],"names":["registerPluginConsoleCron","listPluginOrgErasers","registerPluginOrgEraser","listPluginLeadConversionListeners","registerPluginLeadConversionListener","registerPluginPersonEraser","listPluginUserErasers","registerPluginUserEraser","OUTREACH_PLUGIN_ID","OUTREACH_SEND_JOB_ID","OUTREACH_SYNC_JOB_ID","registerOutreachConsoleServerDeclarations","includes","request","createOutreachOrgEraser","defaultOutreachErasureDeps","pluginId","createOutreachUserEraser","runtime","platformOutreachPersonEraser","followOutreachLeadToContact","platform","Promise","all","platformOutreachRuntimeDeps","firestore","id","label","drives","run","context","runOutreachSendJob","runOutreachSyncJob"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED,2EAA2E;AAC3E,yDAAyD;AACzD,SAASA,yBAAyB,QAAQ,mDAAkD;AAC5F,SACEC,oBAAoB,EACpBC,uBAAuB,QAClB,iDAAgD;AACvD,SACEC,iCAAiC,EACjCC,oCAAoC,QAC/B,qDAAoD;AAC3D,SAASC,0BAA0B,QAAQ,oDAAmD;AAC9F,SACEC,qBAAqB,EACrBC,wBAAwB,QACnB,kDAAiD;AACxD,SAASC,kBAAkB,QAAQ,+BAA2B;AAC9D,SAASC,oBAAoB,EAAEC,oBAAoB,QAAQ,8BAA0B;AAErF;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC;IACd,wEAAwE;IACxE,IAAI,CAACV,uBAAuBW,QAAQ,CAACJ,qBAAqB;QACxDN,wBACE,OAAOW;YACL,MAAM,EAAEC,uBAAuB,EAAEC,0BAA0B,EAAE,GAAG,MAAM,MAAM,CAC1E;YAEF,OAAOD,wBAAwBC,8BAA8BF;QAC/D,GACA;YAAEG,UAAUR;QAAmB;IAEnC;IACA,IAAI,CAACF,wBAAwBM,QAAQ,CAACJ,qBAAqB;QACzDD,yBACE,OAAOM;YACL,MAAM,EAAEI,wBAAwB,EAAEF,0BAA0B,EAAE,GAAG,MAAM,MAAM,CAC3E;YAEF,OAAOE,yBAAyBF,8BAA8BF;QAChE,GACA;YAAEG,UAAUR;QAAmB;IAEnC;IACA,0EAA0E;IAC1E,oEAAoE;IACpEH,2BACE,OAAOQ,UAAY,AAAC,CAAA,MAAMK,SAAQ,EAAGC,4BAA4B,GAAGN,UACpE;QAAEG,UAAUR;IAAmB;IAEjC,sEAAsE;IACtE,yDAAyD;IACzD,IAAI,CAACL,oCAAoCS,QAAQ,CAACJ,qBAAqB;QACrEJ,qCACE,OAAOS;YACL,MAAM,CAAC,EAAEO,2BAA2B,EAAE,EAAEC,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;gBACpE,MAAM,CAAC;gBACPL;aACD;YACD,OAAOE,4BAA4BC,SAASG,2BAA2B,GAAGC,SAAS,IAAIZ;QACzF,GACA;YAAEG,UAAUR;QAAmB;IAEnC;IACA,6DAA6D;IAC7DR,0BACE;QACE0B,IAAIjB;QACJkB,OAAO;QACPC,QACE;QACFC,KAAK,OAAOC;YACV,MAAM,CAAC,EAAEC,kBAAkB,EAAE,EAAEV,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;gBAAC,MAAM,CAAC;gBAAuBL;aAAU;YACtG,OAAOa,mBAAmBV,SAASG,2BAA2B,IAAIM;QACpE;IACF,GACA;QAAEd,UAAUR;IAAmB;IAEjCR,0BACE;QACE0B,IAAIhB;QACJiB,OAAO;QACPC,QACE;QACFC,KAAK,OAAOC;YACV,MAAM,CAAC,EAAEE,kBAAkB,EAAE,EAAEX,SAAS,GAAG,MAAMC,QAAQC,GAAG,CAAC;gBAAC,MAAM,CAAC;gBAAuBL;aAAU;YACtG,OAAOc,mBAAmBX,SAASG,2BAA2B,IAAIM;QACpE;IACF,GACA;QAAEd,UAAUR;IAAmB;AAEnC;AAEA,8EAA8E,GAC9E,MAAMU,UAAU,IAAM,MAAM,CAAC"}
@@ -17,8 +17,13 @@
17
17
  import { type CrmMergeContext } from '@aglyn/aglyn/app-utils/crm-email-templates';
18
18
  import type { OutreachEnrollment, OutreachOrgSettings, OutreachSequence } from '../model/outreach.types';
19
19
  /** The way out every email offers, word for word. */
20
- export declare const OUTREACH_OPT_OUT_LINE = "Not relevant? Reply \"no\" and I won't email again.";
21
- /** The sentence that says what the email is. */
20
+ export declare const OUTREACH_OPT_OUT_LINE = "Not interested? Reply \"no\" and I won't email again.";
21
+ /**
22
+ * The sentence that says what the email is. "Sales email", not "business
23
+ * solicitation": an inbox-placement test on 2026-09-19 named the latter
24
+ * phrase as a spam trigger, and the law asks that the email be identifiable
25
+ * as a solicitation, not for any particular words (AGL-3228).
26
+ */
22
27
  export declare function outreachSolicitationStatement(senderName: string): string;
23
28
  /**
24
29
  * The message the transport sends. Header values are single lines already;