@aglyn/plugins-outreach 1.0.0-beta.219 → 1.0.0-beta.221
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.
- package/package.json +15 -15
- package/src/lib/components/compliance-section.d.ts +5 -0
- package/src/lib/components/compliance-section.js +233 -149
- package/src/lib/components/compliance-section.js.map +1 -1
- package/src/lib/components/enrollment-detail.js +2 -2
- package/src/lib/components/enrollment-detail.js.map +1 -1
- package/src/lib/components/sequence-editor.d.ts +13 -0
- package/src/lib/components/sequence-editor.js +508 -302
- package/src/lib/components/sequence-editor.js.map +1 -1
- package/src/lib/components/sequence-preview.js +25 -10
- package/src/lib/components/sequence-preview.js.map +1 -1
- package/src/lib/components/sequence-step-card.d.ts +10 -0
- package/src/lib/components/sequence-step-card.js +320 -202
- package/src/lib/components/sequence-step-card.js.map +1 -1
- package/src/lib/engine/compose.js +2 -2
- package/src/lib/engine/compose.js.map +1 -1
- package/src/lib/engine/delivery-status.js +1 -1
- package/src/lib/engine/delivery-status.js.map +1 -1
- package/src/lib/engine/enrollment-state.js +2 -2
- package/src/lib/engine/enrollment-state.js.map +1 -1
- package/src/lib/engine/gates.d.ts +2 -2
- package/src/lib/engine/gates.js.map +1 -1
- package/src/lib/engine/opt-out-intent.js +1 -1
- package/src/lib/engine/opt-out-intent.js.map +1 -1
- package/src/lib/engine/sequence-validation.js +2 -2
- package/src/lib/engine/sequence-validation.js.map +1 -1
- package/src/lib/engine/thread-classification.js +3 -3
- package/src/lib/engine/thread-classification.js.map +1 -1
- package/src/lib/enrollment/gate-lookups.js +5 -4
- package/src/lib/enrollment/gate-lookups.js.map +1 -1
- package/src/lib/model/outreach.types.d.ts +1 -1
- package/src/lib/model/outreach.types.js.map +1 -1
- package/src/lib/model/sales-topic.d.ts +27 -0
- package/src/lib/model/sales-topic.js +27 -0
- package/src/lib/model/sales-topic.js.map +1 -0
- package/src/lib/model/sequence-draft.d.ts +1 -1
- package/src/lib/model/sequence-draft.js +2 -2
- package/src/lib/model/sequence-draft.js.map +1 -1
- package/src/lib/model/stored-records.js +2 -2
- package/src/lib/model/stored-records.js.map +1 -1
- package/src/lib/routes/curate-routes.js +7 -7
- package/src/lib/routes/curate-routes.js.map +1 -1
- package/src/lib/routes/enroll-routes.js +10 -13
- package/src/lib/routes/enroll-routes.js.map +1 -1
- package/src/lib/routes/sequence-routes.js +9 -9
- package/src/lib/routes/sequence-routes.js.map +1 -1
- package/src/lib/runtime/campaign-credit.js +2 -2
- package/src/lib/runtime/campaign-credit.js.map +1 -1
- package/src/lib/runtime/campaign-detach.js +3 -2
- package/src/lib/runtime/campaign-detach.js.map +1 -1
- package/src/lib/runtime/lead-records.d.ts +6 -1
- package/src/lib/runtime/lead-records.js +25 -6
- package/src/lib/runtime/lead-records.js.map +1 -1
- package/src/lib/runtime/platform-runtime-deps.js +3 -3
- package/src/lib/runtime/platform-runtime-deps.js.map +1 -1
- package/src/lib/runtime/send-job.js +20 -4
- package/src/lib/runtime/send-job.js.map +1 -1
- package/src/lib/runtime/sync-job.js +3 -3
- package/src/lib/runtime/sync-job.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-editor.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp, type ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport CampaignPicker from '@aglyn/shared-ui-email-campaigns/components/campaign-picker.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useOrgCampaigns, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Autocomplete,\n Box,\n Button,\n Chip,\n FormControlLabel,\n Grid,\n Menu,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { memo, useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n isInThreadEmailStep,\n OUTREACH_SEQUENCE_NAME_MAX,\n validateOutreachSequence,\n} from '../engine/sequence-validation'\nimport { outreachCountryLabel } from '../model/compliance-settings'\nimport {\n OUTREACH_MAX_EMAIL_STEPS,\n OUTREACH_MAX_STEPS,\n OUTREACH_TASK_KIND_LABELS,\n OUTREACH_TASK_KINDS,\n type OutreachSequence,\n type OutreachSequenceStep,\n type OutreachTaskKind,\n} from '../model/outreach.types'\nimport {\n emptyOutreachSequenceDraft,\n newOutreachEmailStep,\n newOutreachTaskStep,\n outreachSequenceDraftOf,\n type OutreachSequenceDraft,\n type OutreachSequenceIssue,\n} from '../model/sequence-draft'\nimport { OutreachSendWindowFields } from './send-window-fields'\nimport { OutreachSequenceMailboxPicker } from './sequence-mailbox-picker'\nimport { OutreachSequencePreview } from './sequence-preview'\nimport { OutreachStepCard } from './sequence-step-card'\nimport { OutreachStepTestDialog } from './step-test-dialog'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport { useOutreachEmailTemplates } from './use-outreach-crm'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport interface OutreachSequenceEditorProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document, for the consent group a test send's person picker searches (AGL-3325). */\n org?: Record<string, unknown> | null\n /** The stored sequence, or `null` for a new one. */\n sequence: OutreachSequence | null\n /** The organization's compliance settings: its countries, and the preview's footer. */\n settings: OutreachSettingsLoad\n /**\n * The organization's mailboxes, from the Mailboxes section's listener:\n * what the picker offers, and who the preview's emails are from.\n */\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section, where the picker sends a member to connect one. */\n mailboxesPath: string\n onSaved(sequence: OutreachSequence): void\n onCancel?(): void\n}\n\n/** The step a dotted issue path is about, and the field within it. */\nfunction stepIssueKey(path: string): { index: number; field: string } | null {\n const match = /^steps\\.(\\d+)(?:\\.(.+))?$/.exec(path)\n if (!match) return null\n const field = match[2] ?? 'step'\n return { index: Number(match[1]), field: field === 'id' ? 'step' : field }\n}\n\ninterface OutreachCountriesFieldProps {\n options: readonly string[]\n value: readonly string[]\n onChange(next: string[]): void\n issue?: string\n disabled: boolean\n}\n\n/**\n * The countries a sequence sends to, as chips in an Autocomplete.\n *\n * MEMOIZED, AND EVERY PROP IT TAKES IS STABLE WHILE THE COUNTRIES ARE\n * (AGL-3423). A multiple Autocomplete hands its input a new chip array as\n * `startAdornment` on every render, and MUI's `InputBase` copies that into\n * its `FormControl` from a passive effect — a state update after every\n * commit this field takes part in. React 19 counts a commit that leaves such\n * an update pending as a nested update, and keystrokes the browser delivers\n * back to back (a held key, fast typing on a busy page) commit one after\n * another with nothing in between to clear the count: fifty-one of them and\n * the next keystroke's own `setState` throws \"Maximum update depth exceeded\"\n * (minified error #185), which is what typing a step's body reported. Kept\n * out of the editor's other renders, the field commits only when the\n * countries, their options or their issue change.\n */\nconst OutreachCountriesField = memo(function OutreachCountriesField(\n props: OutreachCountriesFieldProps,\n) {\n return (\n <Autocomplete<string, true>\n multiple\n options={props.options}\n value={props.value as string[]}\n getOptionLabel={outreachCountryLabel}\n onChange={(_event, next) => props.onChange(next)}\n disabled={props.disabled}\n renderValue={(value, getItemProps) =>\n value.map((code, index) => {\n const { key, ...item } = getItemProps({ index })\n return (\n <Chip\n key={key}\n size=\"small\"\n label={outreachCountryLabel(code)}\n {...item}\n />\n )\n })\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Countries\"\n error={Boolean(props.issue)}\n helperText={\n props.issue ??\n 'Among the countries your organization allows. Cold contacts are emailed only in the United States.'\n }\n />\n )}\n />\n )\n})\n\n/**\n * The sequence editor (AGL-2980): the name, the site and mailbox, the steps,\n * who it sends to and when, beside a live preview of each email.\n *\n * Every issue the engine's validator finds is shown beside its field — the\n * same validator the save route refuses with — errors once a save was\n * attempted, warnings as soon as they appear. What only the server can\n * judge (whose mailbox, which countries the organization allows, what may\n * no longer change once people are enrolled) comes back from the save and\n * is shown in the same places.\n */\nexport function OutreachSequenceEditor(props: OutreachSequenceEditorProps) {\n const { orgId, orgMount, sequence, settings } = props\n const api = useOutreachApi(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const uid = user?.uid ?? null\n const mountedHosts = orgMount?.hosts\n const hosts = useMemo(() => mountedHosts ?? [], [mountedHosts])\n const allowedCountries = settings.settings?.allowedCountries\n const orgCountries = useMemo(\n () => allowedCountries ?? ['US'],\n [allowedCountries],\n )\n\n const [draft, setDraft] = useState<OutreachSequenceDraft>(() =>\n sequence\n ? outreachSequenceDraftOf(sequence)\n : emptyOutreachSequenceDraft({\n hostId: hosts.length === 1 ? hosts[0].id : '',\n allowedCountries: orgCountries,\n }),\n )\n const [attempted, setAttempted] = useState(false)\n const [serverIssues, setServerIssues] = useState<OutreachSequenceIssue[]>([])\n const [saving, setSaving] = useState(false)\n const [taskAnchor, setTaskAnchor] = useState<HTMLElement | null>(null)\n // The step whose test-send dialog is open (AGL-3325), by index.\n const [testing, setTesting] = useState<number | null>(null)\n\n // A stored sequence the listener updated (a save, a status change) is the\n // new starting point.\n useEffect(() => {\n if (sequence) setDraft(outreachSequenceDraftOf(sequence))\n }, [sequence])\n // A new sequence starts on the organization's countries once they are read.\n const settingsCountries = settings.settings?.allowedCountries.join(',')\n useEffect(() => {\n if (!sequence && settingsCountries) {\n setDraft((previous) => ({\n ...previous,\n settings: {\n ...previous.settings,\n allowedCountries: settingsCountries.split(','),\n },\n }))\n }\n }, [sequence, settingsCountries])\n useEffect(() => {\n if (!sequence && !draft.hostId && hosts.length === 1)\n setDraft((previous) => ({ ...previous, hostId: hosts[0].id }))\n }, [sequence, draft.hostId, hosts])\n\n // A new sequence starts on the member's own mailbox when they have exactly\n // one that is sending.\n const ownSending = props.mailboxes.mailboxes.filter(\n (entry) => entry.connectedByUid === uid && entry.status === 'connected',\n )\n const onlyOwnMailbox = ownSending.length === 1 ? ownSending[0].id : ''\n useEffect(() => {\n if (!sequence && !draft.mailboxId && onlyOwnMailbox)\n setDraft((previous) => ({ ...previous, mailboxId: onlyOwnMailbox }))\n }, [sequence, draft.mailboxId, onlyOwnMailbox])\n\n const templates = useOutreachEmailTemplates(orgId, draft.hostId || null, uid)\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === draft.mailboxId) ??\n null\n const archived = sequence?.status === 'archived'\n /*\n * The org's campaigns, for the picker (AGL-3254): read while the editor\n * is open. A sequence is the organization's record and so is a campaign,\n * so the choice is every campaign in the org, whichever site the\n * sequence sends as.\n */\n const campaigns = useOrgCampaigns(orgId, { enabled: true })\n\n const issues = useMemo(() => {\n const judged = [\n ...validateOutreachSequence(draft),\n ...serverIssues,\n ] as OutreachSequenceIssue[]\n return judged.filter(\n (issue) =>\n issue.severity === 'warning' ||\n attempted ||\n serverIssues.includes(issue),\n )\n }, [draft, serverIssues, attempted])\n const issueAt = (path: string) =>\n issues.find((issue) => issue.path === path)?.message\n const stepIssues = (index: number) => {\n const found: Record<string, string> = {}\n for (const issue of issues) {\n const key = stepIssueKey(issue.path)\n if (key?.index === index && !found[key.field])\n found[key.field] = issue.message\n }\n return found\n }\n const listIssue = issueAt('steps')\n const countriesIssue = issues.find((issue) =>\n issue.path.startsWith('settings.allowedCountries'),\n )?.message\n\n // An edit clears what the last save refused. The empty list stays the same\n // list, so an edit with nothing to clear does not change that state.\n const clearServerIssues = useCallback(\n () => setServerIssues((previous) => (previous.length ? [] : previous)),\n [],\n )\n const update = (next: Partial<OutreachSequenceDraft>) => {\n clearServerIssues()\n setDraft((previous) => ({ ...previous, ...next }))\n }\n // Stable, for the memoized countries field: it reads the settings it\n // replaces from the draft it is given rather than from this render's.\n const setCountries = useCallback(\n (next: string[]) => {\n clearServerIssues()\n setDraft((previous) => ({\n ...previous,\n settings: { ...previous.settings, allowedCountries: next },\n }))\n },\n [clearServerIssues],\n )\n const setStep = (index: number, step: OutreachSequenceStep) =>\n update({\n steps: draft.steps.map((entry, position) =>\n position === index ? step : entry,\n ),\n })\n const moveStep = (index: number, direction: -1 | 1) => {\n const steps = [...draft.steps]\n const [moved] = steps.splice(index, 1)\n steps.splice(index + direction, 0, moved)\n update({ steps })\n }\n const emailCount = draft.steps.filter((step) => step.kind === 'email').length\n const full = draft.steps.length >= OUTREACH_MAX_STEPS\n const threadSubject = (index: number) => {\n for (let position = index - 1; position >= 0; position -= 1) {\n const step = draft.steps[position]\n if (step.kind === 'email' && !isInThreadEmailStep(draft.steps, position))\n return step.subject\n }\n return ''\n }\n\n const save = async () => {\n setAttempted(true)\n if (\n validateOutreachSequence(draft).some(\n (issue) => issue.severity === 'error',\n )\n )\n return\n setSaving(true)\n try {\n const answer = await api.saveSequence(sequence?.id ?? null, draft)\n setServerIssues([])\n setAttempted(false)\n enqueueSnackbar(\n answer.created ? 'Sequence created.' : 'Sequence saved.',\n { variant: 'success' },\n )\n props.onSaved(answer.sequence)\n } catch (error) {\n if (error instanceof OutreachRouteError && error.issues.length) {\n setServerIssues(\n error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n ),\n )\n }\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(false)\n }\n }\n\n const siteName = hosts.find((host) => host.id === draft.hostId)?.name ?? ''\n const draftCountries = draft.settings.allowedCountries\n const countryOptions = useMemo(\n () => [...new Set([...orgCountries, ...draftCountries])],\n [orgCountries, draftCountries],\n )\n /*\n * A test sends the step as STORED (AGL-3325): the route reads the saved\n * sequence, so a new one has nothing to send yet and an edited one sends\n * its last save — said on the button and in the dialog rather than found\n * out in the inbox.\n */\n const unsaved =\n sequence !== null &&\n JSON.stringify(draft) !== JSON.stringify(outreachSequenceDraftOf(sequence))\n const sendTestDisabledReason = !sequence\n ? 'Create the sequence to send a test of it.'\n : !draft.mailboxId\n ? 'Choose a mailbox to send the test from.'\n : null\n\n return (\n <Grid container spacing={2}>\n <Grid size={{ xs: 12, md: 7 }}>\n <Stack spacing={2}>\n {archived ? (\n <Alert severity=\"info\">\n This sequence is archived, so it can’t be edited.\n </Alert>\n ) : null}\n <CardDisplay\n header=\"Sequence\"\n help={pluginDocsHelp('sequences', { anchor: '#build-a-sequence' })}\n // The whole sequence (steps and sending rules included) saves\n // from its first card's header, where the console puts a card's\n // actions (AGL-3333).\n HeaderProps={{\n action: archived ? undefined : (\n <Stack direction=\"row\" spacing={1}>\n {props.onCancel ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={props.onCancel}\n disabled={saving}\n >\n Cancel\n </Button>\n ) : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => void save()}\n disabled={saving}\n >\n {saving ? 'Saving…' : sequence ? 'Save' : 'Create sequence'}\n </Button>\n </Stack>\n ),\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <TextField\n label=\"Name\"\n value={draft.name}\n onChange={(event) => update({ name: event.target.value })}\n disabled={archived}\n error={Boolean(issueAt('name'))}\n helperText={issueAt('name')}\n slotProps={{\n htmlInput: { maxLength: OUTREACH_SEQUENCE_NAME_MAX + 20 },\n }}\n fullWidth\n />\n <TextField\n select\n label=\"Site\"\n value={\n hosts.some((host) => host.id === draft.hostId)\n ? draft.hostId\n : ''\n }\n onChange={(event) => update({ hostId: event.target.value })}\n disabled={archived || !hosts.length}\n error={Boolean(issueAt('hostId'))}\n helperText={\n issueAt('hostId') ??\n 'The site whose contacts this sequence emails.'\n }\n fullWidth\n >\n {hosts.map((host) => (\n <MenuItem key={host.id} value={host.id}>\n {host.name}\n </MenuItem>\n ))}\n </TextField>\n <OutreachSequenceMailboxPicker\n orgId={orgId}\n uid={uid}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n value={draft.mailboxId}\n onChange={(mailboxId) => update({ mailboxId })}\n error={issueAt('mailboxId')}\n disabled={archived}\n />\n {/*\n The campaigns this sequence is part of (AGL-3254), picked the\n way a form's page picks them. Everyone enrolled joins them,\n and what the sequence produces is reported under them; it\n does not change who is enrolled or what is sent.\n */}\n <CampaignPicker\n options={campaigns.options}\n value={draft.campaignIds}\n onChange={(campaignIds) => update({ campaignIds })}\n label=\"Campaigns\"\n helperText={\n issueAt('campaignIds') ??\n 'The campaigns this sequence is part of. Everyone enrolled joins them, and its sends, replies, meetings and conversions count on their pages.'\n }\n disabled={archived}\n empty={campaigns.ready && !campaigns.options.length}\n emptyText=\"There are no campaigns yet. Create one from Marketing to file this sequence under it.\"\n />\n </Stack>\n </CardDisplay>\n\n {listIssue ? <Alert severity=\"error\">{listIssue}</Alert> : null}\n {draft.steps.map((step, index) => (\n <OutreachStepCard\n key={step.id || `step-${index}`}\n step={step}\n index={index}\n count={draft.steps.length}\n firstEmail={\n draft.steps.findIndex((entry) => entry.kind === 'email') ===\n index\n }\n inThread={isInThreadEmailStep(draft.steps, index)}\n threadSubject={threadSubject(index)}\n templates={templates.data}\n issues={stepIssues(index)}\n disabled={archived}\n onChange={(next) => setStep(index, next)}\n onMove={(direction) => moveStep(index, direction)}\n onRemove={() =>\n update({\n steps: draft.steps.filter(\n (_entry, position) => position !== index,\n ),\n })\n }\n onSendTest={\n step.kind === 'email' && !archived\n ? () => setTesting(index)\n : undefined\n }\n sendTestDisabledReason={sendTestDisabledReason}\n />\n ))}\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', rowGap: 1 }}\n >\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n disabled={\n archived || full || emailCount >= OUTREACH_MAX_EMAIL_STEPS\n }\n onClick={() =>\n update({\n steps: [...draft.steps, newOutreachEmailStep(draft.steps)],\n })\n }\n >\n Add email\n </Button>\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n disabled={archived || full}\n onClick={(event) => setTaskAnchor(event.currentTarget)}\n aria-haspopup=\"menu\"\n >\n Add task\n </Button>\n <Menu\n anchorEl={taskAnchor}\n open={Boolean(taskAnchor)}\n onClose={() => setTaskAnchor(null)}\n >\n {OUTREACH_TASK_KINDS.map((kind: OutreachTaskKind) => (\n <MenuItem\n key={kind}\n onClick={() => {\n setTaskAnchor(null)\n update({\n steps: [\n ...draft.steps,\n newOutreachTaskStep(draft.steps, kind),\n ],\n })\n }}\n >\n {OUTREACH_TASK_KIND_LABELS[kind]}\n </MenuItem>\n ))}\n </Menu>\n </Stack>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Up to ${OUTREACH_MAX_STEPS} steps, ${OUTREACH_MAX_EMAIL_STEPS} of them emails.`}\n </Typography>\n\n <CardDisplay\n header=\"Who it sends to, and when\"\n help={pluginDocsHelp('sequences', {\n anchor: '#build-a-sequence',\n excerpt:\n 'A sequence sends only to the countries both it and your organization allow, and in its mailbox’s sending hours unless it has its own.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <OutreachCountriesField\n options={countryOptions}\n value={draftCountries}\n onChange={setCountries}\n issue={countriesIssue}\n disabled={archived}\n />\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.allowCustomers}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n allowCustomers: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Include customers\"\n />\n {/*\n * Click tracking (AGL-3239), with its cost stated where it is\n * chosen rather than in a document nobody opens. Off by\n * default: it changes the link a cold recipient sees. The\n * emails stay plain text either way; opens are the separate\n * switch below.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.trackClicks}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n trackClicks: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Count link clicks\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Links in these emails are replaced with links of ours that\n forward to the same page, so clicks can be counted. The\n recipient sees the replacement, not your address. The\n emails stay plain text.\n </Typography>\n </Stack>\n {/*\n * Open counting (AGL-3395), off by default, its cost stated\n * where it is chosen: an HTML copy with a tracking image is the\n * least one-to-one thing a sequence email can carry, so it is\n * for measuring a slice, not for every sequence.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.countOpens}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n countOpens: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Count opens\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Each email also goes out as HTML — the same words — with a\n tiny tracking image, so opens can be counted. The plain-text\n version is unchanged. It makes the email look less like one\n written by hand, and some spam filters score HTML with a\n remote image against you, so turn it on to measure a slice\n of your sends rather than for every sequence. Gmail opens\n count; Apple Mail loads every image on arrival, so its loads\n are counted separately.\n </Typography>\n </Stack>\n {/*\n * The mail-client unsubscribe button (AGL-3296), off by\n * default: the header is what makes Apple Mail, Gmail and\n * Outlook present a one-to-one email as a mailing list. The\n * footer's \"reply 'no'\" line is the way out either way.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={draft.settings.listUnsubscribe}\n onChange={(event) =>\n update({\n settings: {\n ...draft.settings,\n listUnsubscribe: event.target.checked,\n },\n })\n }\n disabled={archived}\n />\n }\n label=\"Add a mail-client unsubscribe button\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Adds the List-Unsubscribe header, so the recipient’s mail\n app shows its own Unsubscribe button. It’s the easiest way\n out for them, and fewer people mark you as spam when they\n have it, but mail apps then label the email as coming from\n a mailing list. Off, the footer’s “reply ‘no’” line is the\n way out, and it’s always there.\n </Typography>\n </Stack>\n <OutreachSendWindowFields\n window={draft.settings.window}\n onChange={(window) =>\n update({ settings: { ...draft.settings, window } })\n }\n daysIssue={issueAt('settings.window.days')}\n hoursIssue={issueAt('settings.window')}\n disabled={archived}\n />\n </Stack>\n </CardDisplay>\n\n </Stack>\n </Grid>\n {sequence && testing !== null ? (\n <OutreachStepTestDialog\n open\n onClose={() => setTesting(null)}\n orgId={orgId}\n sequence={sequence}\n stepIndex={testing}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n selfEmail={user?.email ?? null}\n unsaved={unsaved}\n api={api}\n />\n ) : null}\n <Grid size={{ xs: 12, md: 5 }}>\n <Box sx={{ position: { md: 'sticky' }, top: { md: 16 } }}>\n <OutreachSequencePreview\n steps={draft.steps}\n orgSettings={settings.settings}\n orgSettingsStatus={settings.status}\n templates={templates.data}\n sender={\n mailbox\n ? {\n name: mailbox.displayName,\n email: mailbox.sendAs || mailbox.email,\n }\n : null\n }\n siteName={siteName}\n />\n </Box>\n </Grid>\n </Grid>\n )\n}\nOutreachSequenceEditor.displayName = 'OutreachSequenceEditor'\n\nexport default OutreachSequenceEditor\n"],"names":["pluginDocsHelp","consentGroupForHost","mdiPlus","CardDisplay","MdiIcon","CampaignPicker","useSnackbar","useOrgCampaigns","useUser","Alert","Autocomplete","Box","Button","Chip","FormControlLabel","Grid","Menu","MenuItem","Stack","Switch","TextField","Typography","memo","useCallback","useEffect","useMemo","useState","isInThreadEmailStep","OUTREACH_SEQUENCE_NAME_MAX","validateOutreachSequence","outreachCountryLabel","OUTREACH_MAX_EMAIL_STEPS","OUTREACH_MAX_STEPS","OUTREACH_TASK_KIND_LABELS","OUTREACH_TASK_KINDS","emptyOutreachSequenceDraft","newOutreachEmailStep","newOutreachTaskStep","outreachSequenceDraftOf","OutreachSendWindowFields","OutreachSequenceMailboxPicker","OutreachSequencePreview","OutreachStepCard","OutreachStepTestDialog","OutreachRouteError","useOutreachApi","useOutreachEmailTemplates","stepIssueKey","path","match","exec","field","index","Number","OutreachCountriesField","props","multiple","options","value","getOptionLabel","onChange","_event","next","disabled","renderValue","getItemProps","map","code","key","item","size","label","renderInput","params","error","Boolean","issue","helperText","OutreachSequenceEditor","issueAt","settings","issues","hosts","orgId","orgMount","sequence","api","enqueueSnackbar","data","user","uid","mountedHosts","allowedCountries","orgCountries","draft","setDraft","hostId","length","id","attempted","setAttempted","serverIssues","setServerIssues","saving","setSaving","taskAnchor","setTaskAnchor","testing","setTesting","settingsCountries","join","previous","split","ownSending","mailboxes","filter","entry","connectedByUid","status","onlyOwnMailbox","mailboxId","templates","mailbox","find","archived","campaigns","enabled","judged","severity","includes","message","stepIssues","found","listIssue","countriesIssue","startsWith","clearServerIssues","update","setCountries","setStep","step","steps","position","moveStep","direction","moved","splice","emailCount","kind","full","threadSubject","subject","save","some","answer","saveSequence","created","variant","onSaved","allowDuplicate","siteName","host","name","draftCountries","countryOptions","Set","unsaved","JSON","stringify","sendTestDisabledReason","container","spacing","xs","md","header","help","anchor","HeaderProps","action","undefined","onCancel","onClick","contentGutterX","contentGutterY","event","target","slotProps","htmlInput","maxLength","fullWidth","select","mailboxesPath","campaignIds","empty","ready","emptyText","count","firstEmail","findIndex","inThread","onMove","onRemove","_entry","onSendTest","sx","flexWrap","rowGap","startIcon","fontSize","currentTarget","aria-haspopup","anchorEl","open","onClose","color","excerpt","control","checked","allowCustomers","trackClicks","countOpens","listUnsubscribe","window","daysIssue","hoursIssue","stepIndex","contactGroupId","org","groupId","selfEmail","email","top","orgSettings","orgSettingsStatus","sender","displayName","sendAs"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-editor.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { pluginDocsHelp, type ConsolePluginOrgMount } from '@aglyn/aglyn'\nimport { consentGroupForHost } from '@aglyn/aglyn/app-utils/consent-groups'\nimport { mdiPlus } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport ContainerPicker, {\n type ContainerPickerProps,\n} from '@aglyn/tenant-feature-instance/components/container-picker'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { useOrgContainerOptions, useUser } from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Autocomplete,\n Box,\n Button,\n Chip,\n FormControlLabel,\n Grid,\n Menu,\n MenuItem,\n Stack,\n Switch,\n TextField,\n Typography,\n} from '@mui/material'\nimport { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport {\n isInThreadEmailStep,\n OUTREACH_SEQUENCE_NAME_MAX,\n validateOutreachSequence,\n} from '../engine/sequence-validation'\nimport { outreachCountryLabel } from '../model/compliance-settings'\nimport {\n OUTREACH_MAX_EMAIL_STEPS,\n OUTREACH_MAX_STEPS,\n OUTREACH_TASK_KIND_LABELS,\n OUTREACH_TASK_KINDS,\n type OutreachSequence,\n type OutreachSequenceStep,\n type OutreachTaskKind,\n} from '../model/outreach.types'\nimport {\n emptyOutreachSequenceDraft,\n newOutreachEmailStep,\n newOutreachTaskStep,\n outreachSequenceDraftOf,\n type OutreachSequenceDraft,\n type OutreachSequenceIssue,\n} from '../model/sequence-draft'\nimport { OutreachSendWindowFields } from './send-window-fields'\nimport { OutreachSequenceMailboxPicker } from './sequence-mailbox-picker'\nimport { OutreachSequencePreview } from './sequence-preview'\nimport {\n OutreachStepCard,\n type OutreachStepCardProps,\n} from './sequence-step-card'\nimport { OutreachStepTestDialog } from './step-test-dialog'\nimport { OutreachRouteError, useOutreachApi } from './use-outreach-api'\nimport { useOutreachEmailTemplates } from './use-outreach-crm'\nimport type { OutreachMailboxesResult } from './use-outreach-mailboxes'\nimport type { OutreachSettingsLoad } from './use-outreach-settings'\n\nexport interface OutreachSequenceEditorProps {\n orgId: string\n orgMount?: ConsolePluginOrgMount\n /** The organization document, for the consent group a test send's person picker searches (AGL-3325). */\n org?: Record<string, unknown> | null\n /** The stored sequence, or `null` for a new one. */\n sequence: OutreachSequence | null\n /** The organization's compliance settings: its countries, and the preview's footer. */\n settings: OutreachSettingsLoad\n /**\n * The organization's mailboxes, from the Mailboxes section's listener:\n * what the picker offers, and who the preview's emails are from.\n */\n mailboxes: OutreachMailboxesResult\n /** The Mailboxes section, where the picker sends a member to connect one. */\n mailboxesPath: string\n onSaved(sequence: OutreachSequence): void\n onCancel?(): void\n}\n\n/** The step a dotted issue path is about, and the field within it. */\nfunction stepIssueKey(path: string): { index: number; field: string } | null {\n const match = /^steps\\.(\\d+)(?:\\.(.+))?$/.exec(path)\n if (!match) return null\n const field = match[2] ?? 'step'\n return { index: Number(match[1]), field: field === 'id' ? 'step' : field }\n}\n\ninterface OutreachCountriesFieldProps {\n options: readonly string[]\n value: readonly string[]\n onChange(next: string[]): void\n issue?: string\n disabled: boolean\n}\n\n/**\n * The countries a sequence sends to, as chips in an Autocomplete.\n *\n * MEMOIZED, AND EVERY PROP IT TAKES IS STABLE WHILE THE COUNTRIES ARE\n * (AGL-3423). A multiple Autocomplete hands its input a new chip array as\n * `startAdornment` on every render, and MUI's `InputBase` copies that into\n * its `FormControl` from a passive effect — a state update after every\n * commit this field takes part in. React 19 counts a commit that leaves such\n * an update pending as a nested update, and keystrokes the browser delivers\n * back to back (a held key, fast typing on a busy page) commit one after\n * another with nothing in between to clear the count: fifty-one of them and\n * the next keystroke's own `setState` throws \"Maximum update depth exceeded\"\n * (minified error #185), which is what typing a step's body reported. Kept\n * out of the editor's other renders, the field commits only when the\n * countries, their options or their issue change.\n */\nconst OutreachCountriesField = memo(function OutreachCountriesField(\n props: OutreachCountriesFieldProps,\n) {\n return (\n <Autocomplete<string, true>\n multiple\n options={props.options}\n value={props.value as string[]}\n getOptionLabel={outreachCountryLabel}\n onChange={(_event, next) => props.onChange(next)}\n disabled={props.disabled}\n renderValue={(value, getItemProps) =>\n value.map((code, index) => {\n const { key, ...item } = getItemProps({ index })\n return (\n <Chip\n key={key}\n size=\"small\"\n label={outreachCountryLabel(code)}\n {...item}\n />\n )\n })\n }\n renderInput={(params) => (\n <TextField\n {...params}\n label=\"Countries\"\n error={Boolean(props.issue)}\n helperText={\n props.issue ??\n 'Among the countries your organization allows. Cold contacts are emailed only in the United States.'\n }\n />\n )}\n />\n )\n})\n\n/** Each step's issues by the field they are about, as its card reads them. */\ntype StepIssues = Partial<Record<string, string>>\n\n/**\n * Each step's issues, the SAME record for a step while what it holds reads\n * the same (AGL-3423), so a step a keystroke did not touch keeps its props.\n */\nfunction useStepIssues(\n issues: readonly OutreachSequenceIssue[],\n count: number,\n): StepIssues[] {\n const held = useRef<Array<{ key: string; value: StepIssues }>>([])\n return useMemo(() => {\n const found: Record<string, string>[] = Array.from(\n { length: count },\n () => ({}),\n )\n for (const issue of issues) {\n const key = stepIssueKey(issue.path)\n const record = key ? found[key.index] : undefined\n if (key && record && !record[key.field])\n record[key.field] = issue.message\n }\n held.current = found.map((value, index) => {\n const key = JSON.stringify(value)\n const before = held.current[index]\n return before?.key === key ? before : { key, value }\n })\n return held.current.map((entry) => entry.value)\n }, [issues, count])\n}\n\n/** A text field that redraws only when what it shows changes (AGL-3423). */\nconst MemoTextField = memo(TextField) as typeof TextField\n\n/*\n * The sections below are drawn from the one draft, and each redraws only\n * when what it shows changes (AGL-3423).\n */\nconst MemoMailboxPicker = memo(OutreachSequenceMailboxPicker)\nconst MemoContainerPicker = memo(ContainerPicker)\nconst MemoSendWindowFields = memo(OutreachSendWindowFields)\nconst MemoSequencePreview = memo(OutreachSequencePreview)\n\ntype FieldChange = { target: { value: string } }\ntype SwitchChange = { target: { checked: boolean } }\ntype SequenceSettings = OutreachSequenceDraft['settings']\n\nconst NAME_SLOT_PROPS = {\n htmlInput: { maxLength: OUTREACH_SEQUENCE_NAME_MAX + 20 },\n}\nconst ADD_STEP_ROW = { flexWrap: 'wrap', rowGap: 1 } as const\nconst PREVIEW_COLUMN = {\n position: { md: 'sticky' },\n top: { md: 16 },\n} as const\n\nconst SequenceSiteField = memo(function SequenceSiteField(props: {\n hosts: ConsolePluginOrgMount['hosts']\n value: string\n issue?: string\n disabled: boolean\n onChange(event: FieldChange): void\n}) {\n const { hosts, value, issue } = props\n return (\n <TextField\n select\n label=\"Site\"\n value={hosts.some((host) => host.id === value) ? value : ''}\n onChange={props.onChange}\n disabled={props.disabled || !hosts.length}\n error={Boolean(issue)}\n helperText={issue ?? 'The site whose contacts this sequence emails.'}\n fullWidth\n >\n {hosts.map((host) => (\n <MenuItem key={host.id} value={host.id}>\n {host.name}\n </MenuItem>\n ))}\n </TextField>\n )\n})\n\n/** The sequence card's header actions: Cancel, and the save of the whole sequence. */\nconst SequenceSaveActions = memo(function SequenceSaveActions(props: {\n saving: boolean\n /** \"Save\" for a stored sequence, \"Create sequence\" for a new one. */\n saveLabel: string\n onCancel?(): void\n onSave(): void\n}) {\n return (\n <Stack direction=\"row\" spacing={1}>\n {props.onCancel ? (\n <Button\n size=\"small\"\n variant=\"text\"\n onClick={props.onCancel}\n disabled={props.saving}\n >\n Cancel\n </Button>\n ) : null}\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={props.onSave}\n disabled={props.saving}\n >\n {props.saving ? 'Saving…' : props.saveLabel}\n </Button>\n </Stack>\n )\n})\n\ninterface SequenceCardProps {\n orgId: string\n uid: string | null\n name: string\n hostId: string\n mailboxId: string\n campaignIds: readonly string[]\n hosts: ConsolePluginOrgMount['hosts']\n mailboxes: OutreachMailboxesResult\n mailboxesPath: string\n campaignOptions: ContainerPickerProps['options']\n /** The organization has no campaigns at all. */\n noCampaigns: boolean\n nameIssue?: string\n hostIssue?: string\n mailboxIssue?: string\n campaignsIssue?: string\n archived: boolean\n saving: boolean\n saveLabel: string\n onCancel?(): void\n onSave(): void\n onName(event: FieldChange): void\n onHost(event: FieldChange): void\n onMailbox(mailboxId: string): void\n onCampaigns(campaignIds: string[]): void\n}\n\n/** The sequence's own card: its name, site, mailbox and campaigns. */\nconst SequenceCard = memo(function SequenceCard(props: SequenceCardProps) {\n const { archived } = props\n return (\n <CardDisplay\n header=\"Sequence\"\n help={pluginDocsHelp('sequences', { anchor: '#build-a-sequence' })}\n // The whole sequence (steps and sending rules included) saves\n // from its first card's header, where the console puts a card's\n // actions (AGL-3333).\n HeaderProps={{\n action: archived ? undefined : (\n <SequenceSaveActions\n saving={props.saving}\n saveLabel={props.saveLabel}\n onCancel={props.onCancel}\n onSave={props.onSave}\n />\n ),\n }}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <MemoTextField\n label=\"Name\"\n value={props.name}\n onChange={props.onName}\n disabled={archived}\n error={Boolean(props.nameIssue)}\n helperText={props.nameIssue}\n slotProps={NAME_SLOT_PROPS}\n fullWidth\n />\n <SequenceSiteField\n hosts={props.hosts}\n value={props.hostId}\n issue={props.hostIssue}\n disabled={archived}\n onChange={props.onHost}\n />\n <MemoMailboxPicker\n orgId={props.orgId}\n uid={props.uid}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n value={props.mailboxId}\n onChange={props.onMailbox}\n error={props.mailboxIssue}\n disabled={archived}\n />\n {/*\n The campaigns this sequence is part of (AGL-3254), picked the\n way a form's page picks them. Everyone enrolled joins them,\n and what the sequence produces is reported under them; it\n does not change who is enrolled or what is sent.\n */}\n <MemoContainerPicker\n kind=\"campaign\"\n options={props.campaignOptions}\n value={props.campaignIds}\n onChange={props.onCampaigns}\n label=\"Campaigns\"\n helperText={\n props.campaignsIssue ??\n 'The campaigns this sequence is part of. Everyone enrolled joins them, and its sends, replies, meetings and conversions count on their pages.'\n }\n disabled={archived}\n empty={props.noCampaigns}\n emptyText=\"There are no campaigns yet. Create one from Marketing to file this sequence under it.\"\n />\n </Stack>\n </CardDisplay>\n )\n})\n\ninterface SequenceStepItemProps\n extends Omit<\n OutreachStepCardProps,\n 'onChange' | 'onMove' | 'onRemove' | 'onSendTest'\n > {\n /** Whether the step offers a test send: an email in a sequence that is not archived. */\n canSendTest: boolean\n onStepChange(index: number, step: OutreachSequenceStep): void\n onStepMove(index: number, direction: -1 | 1): void\n onStepRemove(index: number): void\n onStepTest(index: number): void\n}\n\n/**\n * One step's card, bound to its position. Memoized, with handlers that keep\n * their identity, so a keystroke in one step leaves the others undrawn.\n */\nconst SequenceStepItem = memo(function SequenceStepItem(\n props: SequenceStepItemProps,\n) {\n const {\n index,\n canSendTest,\n onStepChange,\n onStepMove,\n onStepRemove,\n onStepTest,\n ...card\n } = props\n const onChange = useCallback(\n (step: OutreachSequenceStep) => onStepChange(index, step),\n [index, onStepChange],\n )\n const onMove = useCallback(\n (direction: -1 | 1) => onStepMove(index, direction),\n [index, onStepMove],\n )\n const onRemove = useCallback(\n () => onStepRemove(index),\n [index, onStepRemove],\n )\n const onSendTest = useCallback(() => onStepTest(index), [index, onStepTest])\n return (\n <OutreachStepCard\n {...card}\n index={index}\n onChange={onChange}\n onMove={onMove}\n onRemove={onRemove}\n onSendTest={canSendTest ? onSendTest : undefined}\n />\n )\n})\n\n/** Adds an email, or a task of a kind picked from its menu. */\nconst SequenceAddStep = memo(function SequenceAddStep(props: {\n emailDisabled: boolean\n taskDisabled: boolean\n onAddEmail(): void\n onAddTask(kind: OutreachTaskKind): void\n}) {\n const { onAddTask } = props\n const [taskAnchor, setTaskAnchor] = useState<HTMLElement | null>(null)\n return (\n <Stack direction=\"row\" spacing={1} sx={ADD_STEP_ROW}>\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n disabled={props.emailDisabled}\n onClick={props.onAddEmail}\n >\n Add email\n </Button>\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPlus.path} fontSize=\"small\" />}\n disabled={props.taskDisabled}\n onClick={(event) => setTaskAnchor(event.currentTarget)}\n aria-haspopup=\"menu\"\n >\n Add task\n </Button>\n <Menu\n anchorEl={taskAnchor}\n open={Boolean(taskAnchor)}\n onClose={() => setTaskAnchor(null)}\n >\n {OUTREACH_TASK_KINDS.map((kind: OutreachTaskKind) => (\n <MenuItem\n key={kind}\n onClick={() => {\n setTaskAnchor(null)\n onAddTask(kind)\n }}\n >\n {OUTREACH_TASK_KIND_LABELS[kind]}\n </MenuItem>\n ))}\n </Menu>\n </Stack>\n )\n})\n\ninterface SequenceSendingCardProps {\n settings: SequenceSettings\n countryOptions: readonly string[]\n countriesIssue?: string\n daysIssue?: string\n hoursIssue?: string\n disabled: boolean\n onCountries(next: string[]): void\n onSettings(patch: Partial<SequenceSettings>): void\n}\n\n/** Who the sequence sends to, and when: its countries, switches and hours. */\nconst SequenceSendingCard = memo(function SequenceSendingCard(\n props: SequenceSendingCardProps,\n) {\n const { settings, disabled, onSettings } = props\n const setAllowCustomers = useCallback(\n (event: SwitchChange) =>\n onSettings({ allowCustomers: event.target.checked }),\n [onSettings],\n )\n const setTrackClicks = useCallback(\n (event: SwitchChange) => onSettings({ trackClicks: event.target.checked }),\n [onSettings],\n )\n const setCountOpens = useCallback(\n (event: SwitchChange) => onSettings({ countOpens: event.target.checked }),\n [onSettings],\n )\n const setListUnsubscribe = useCallback(\n (event: SwitchChange) =>\n onSettings({ listUnsubscribe: event.target.checked }),\n [onSettings],\n )\n const setWindow = useCallback(\n (window: SequenceSettings['window']) => onSettings({ window }),\n [onSettings],\n )\n return (\n <CardDisplay\n header=\"Who it sends to, and when\"\n help={pluginDocsHelp('sequences', {\n anchor: '#build-a-sequence',\n excerpt:\n 'A sequence sends only to the countries both it and your organization allow, and in its mailbox’s sending hours unless it has its own.',\n })}\n contentGutterX\n contentGutterY\n >\n <Stack spacing={2}>\n <OutreachCountriesField\n options={props.countryOptions}\n value={settings.allowedCountries}\n onChange={props.onCountries}\n issue={props.countriesIssue}\n disabled={disabled}\n />\n <FormControlLabel\n control={\n <Switch\n checked={settings.allowCustomers}\n onChange={setAllowCustomers}\n disabled={disabled}\n />\n }\n label=\"Include customers\"\n />\n {/*\n * Click tracking (AGL-3239), with its cost stated where it is\n * chosen rather than in a document nobody opens. Off by\n * default: it changes the link a cold recipient sees. The\n * emails stay plain text either way; opens are the separate\n * switch below.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={settings.trackClicks}\n onChange={setTrackClicks}\n disabled={disabled}\n />\n }\n label=\"Count link clicks\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Links in these emails are replaced with links of ours that\n forward to the same page, so clicks can be counted. The\n recipient sees the replacement, not your address. The\n emails stay plain text.\n </Typography>\n </Stack>\n {/*\n * Open counting (AGL-3395), off by default, its cost stated\n * where it is chosen: an HTML copy with a tracking image is the\n * least one-to-one thing a sequence email can carry, so it is\n * for measuring a slice, not for every sequence.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={settings.countOpens}\n onChange={setCountOpens}\n disabled={disabled}\n />\n }\n label=\"Count opens\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Each email also goes out as HTML — the same words — with a\n tiny tracking image, so opens can be counted. The plain-text\n version is unchanged. It makes the email look less like one\n written by hand, and some spam filters score HTML with a\n remote image against you, so turn it on to measure a slice\n of your sends rather than for every sequence. Gmail opens\n count; Apple Mail loads every image on arrival, so its loads\n are counted separately.\n </Typography>\n </Stack>\n {/*\n * The mail-client unsubscribe button (AGL-3296), off by\n * default: the header is what makes Apple Mail, Gmail and\n * Outlook present a one-to-one email as a mailing list. The\n * footer's \"reply 'no'\" line is the way out either way.\n */}\n <Stack spacing={0}>\n <FormControlLabel\n control={\n <Switch\n checked={settings.listUnsubscribe}\n onChange={setListUnsubscribe}\n disabled={disabled}\n />\n }\n label=\"Add a mail-client unsubscribe button\"\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n Adds the List-Unsubscribe header, so the recipient’s mail\n app shows its own Unsubscribe button. It’s the easiest way\n out for them, and fewer people mark you as spam when they\n have it, but mail apps then label the email as coming from\n a mailing list. Off, the footer’s “reply ‘no’” line is the\n way out, and it’s always there.\n </Typography>\n </Stack>\n <MemoSendWindowFields\n window={settings.window}\n onChange={setWindow}\n daysIssue={props.daysIssue}\n hoursIssue={props.hoursIssue}\n disabled={disabled}\n />\n </Stack>\n </CardDisplay>\n )\n})\n\n/**\n * The sequence editor (AGL-2980): the name, the site and mailbox, the steps,\n * who it sends to and when, beside a live preview of each email.\n *\n * Every issue the engine's validator finds is shown beside its field — the\n * same validator the save route refuses with — errors once a save was\n * attempted, warnings as soon as they appear. What only the server can\n * judge (whose mailbox, which countries the organization allows, what may\n * no longer change once people are enrolled) comes back from the save and\n * is shown in the same places.\n *\n * ## A keystroke redraws its own field (AGL-3423)\n *\n * The whole sequence is one draft, so every letter typed anywhere is a new\n * draft, and drawing the editor from it inline redrew every field of every\n * step, the settings and the preview with each one: 17 inputs and some 1,150\n * components for one letter in a three-step sequence. That cost is what lets\n * typed input queue, and queued keystrokes processed back to back are what\n * drove the countries chips into React's #185. The sequence card, each step,\n * the sending card and the preview are memoized and handed only what they\n * show, through handlers that keep their identity; the handlers that wrote\n * from the draft as drawn still do, through `drawn`. A letter now redraws the\n * field it lands in, and the preview when it shows that step's email.\n */\nexport function OutreachSequenceEditor(props: OutreachSequenceEditorProps) {\n const { orgId, orgMount, sequence, settings } = props\n const api = useOutreachApi(orgId)\n const { enqueueSnackbar } = useSnackbar()\n const { data: user } = useUser()\n const uid = user?.uid ?? null\n const mountedHosts = orgMount?.hosts\n const hosts = useMemo(() => mountedHosts ?? [], [mountedHosts])\n const allowedCountries = settings.settings?.allowedCountries\n const orgCountries = useMemo(\n () => allowedCountries ?? ['US'],\n [allowedCountries],\n )\n\n const [draft, setDraft] = useState<OutreachSequenceDraft>(() =>\n sequence\n ? outreachSequenceDraftOf(sequence)\n : emptyOutreachSequenceDraft({\n hostId: hosts.length === 1 ? hosts[0].id : '',\n allowedCountries: orgCountries,\n }),\n )\n const [attempted, setAttempted] = useState(false)\n const [serverIssues, setServerIssues] = useState<OutreachSequenceIssue[]>([])\n const [saving, setSaving] = useState(false)\n // The step whose test-send dialog is open (AGL-3325), by index.\n const [testing, setTesting] = useState<number | null>(null)\n // The draft as last drawn, which the handlers that build on it read.\n const drawn = useRef(draft)\n drawn.current = draft\n\n // A stored sequence the listener updated (a save, a status change) is the\n // new starting point.\n useEffect(() => {\n if (sequence) setDraft(outreachSequenceDraftOf(sequence))\n }, [sequence])\n // A new sequence starts on the organization's countries once they are read.\n const settingsCountries = settings.settings?.allowedCountries.join(',')\n useEffect(() => {\n if (!sequence && settingsCountries) {\n setDraft((previous) => ({\n ...previous,\n settings: {\n ...previous.settings,\n allowedCountries: settingsCountries.split(','),\n },\n }))\n }\n }, [sequence, settingsCountries])\n useEffect(() => {\n if (!sequence && !draft.hostId && hosts.length === 1)\n setDraft((previous) => ({ ...previous, hostId: hosts[0].id }))\n }, [sequence, draft.hostId, hosts])\n\n // A new sequence starts on the member's own mailbox when they have exactly\n // one that is sending.\n const ownSending = props.mailboxes.mailboxes.filter(\n (entry) => entry.connectedByUid === uid && entry.status === 'connected',\n )\n const onlyOwnMailbox = ownSending.length === 1 ? ownSending[0].id : ''\n useEffect(() => {\n if (!sequence && !draft.mailboxId && onlyOwnMailbox)\n setDraft((previous) => ({ ...previous, mailboxId: onlyOwnMailbox }))\n }, [sequence, draft.mailboxId, onlyOwnMailbox])\n\n const templates = useOutreachEmailTemplates(orgId, draft.hostId || null, uid)\n const mailbox =\n props.mailboxes.mailboxes.find((entry) => entry.id === draft.mailboxId) ??\n null\n const sender = useMemo(\n () =>\n mailbox\n ? { name: mailbox.displayName, email: mailbox.sendAs || mailbox.email }\n : null,\n [mailbox],\n )\n const archived = sequence?.status === 'archived'\n /*\n * The org's campaigns, for the picker (AGL-3254): read while the editor\n * is open. A sequence is the organization's record and so is a campaign,\n * so the choice is every campaign in the org, whichever site the\n * sequence sends as.\n */\n const campaigns = useOrgContainerOptions('campaign', orgId, { enabled: true })\n\n const issues = useMemo(() => {\n const judged = [\n ...validateOutreachSequence(draft),\n ...serverIssues,\n ] as OutreachSequenceIssue[]\n return judged.filter(\n (issue) =>\n issue.severity === 'warning' ||\n attempted ||\n serverIssues.includes(issue),\n )\n }, [draft, serverIssues, attempted])\n const issueAt = (path: string) =>\n issues.find((issue) => issue.path === path)?.message\n const stepIssues = useStepIssues(issues, draft.steps.length)\n const listIssue = issueAt('steps')\n const countriesIssue = issues.find((issue) =>\n issue.path.startsWith('settings.allowedCountries'),\n )?.message\n\n // An edit clears what the last save refused. The empty list stays the same\n // list, so an edit with nothing to clear does not change that state.\n const clearServerIssues = useCallback(\n () => setServerIssues((previous) => (previous.length ? [] : previous)),\n [],\n )\n const update = useCallback(\n (next: Partial<OutreachSequenceDraft>) => {\n clearServerIssues()\n setDraft((previous) => ({ ...previous, ...next }))\n },\n [clearServerIssues],\n )\n // Stable, for the memoized countries field: it reads the settings it\n // replaces from the draft it is given rather than from this render's.\n const setCountries = useCallback(\n (next: string[]) => {\n clearServerIssues()\n setDraft((previous) => ({\n ...previous,\n settings: { ...previous.settings, allowedCountries: next },\n }))\n },\n [clearServerIssues],\n )\n const setName = useCallback(\n (event: FieldChange) => update({ name: event.target.value }),\n [update],\n )\n const setHost = useCallback(\n (event: FieldChange) => update({ hostId: event.target.value }),\n [update],\n )\n const setMailbox = useCallback(\n (mailboxId: string) => update({ mailboxId }),\n [update],\n )\n const setCampaigns = useCallback(\n (campaignIds: string[]) => update({ campaignIds }),\n [update],\n )\n const setSettings = useCallback(\n (patch: Partial<SequenceSettings>) =>\n update({ settings: { ...drawn.current.settings, ...patch } }),\n [update],\n )\n const setStep = useCallback(\n (index: number, step: OutreachSequenceStep) =>\n update({\n steps: drawn.current.steps.map((entry, position) =>\n position === index ? step : entry,\n ),\n }),\n [update],\n )\n const moveStep = useCallback(\n (index: number, direction: -1 | 1) => {\n const steps = [...drawn.current.steps]\n const [moved] = steps.splice(index, 1)\n steps.splice(index + direction, 0, moved)\n update({ steps })\n },\n [update],\n )\n const removeStep = useCallback(\n (index: number) =>\n update({\n steps: drawn.current.steps.filter(\n (_entry, position) => position !== index,\n ),\n }),\n [update],\n )\n const addEmail = useCallback(\n () =>\n update({\n steps: [\n ...drawn.current.steps,\n newOutreachEmailStep(drawn.current.steps),\n ],\n }),\n [update],\n )\n const addTask = useCallback(\n (kind: OutreachTaskKind) =>\n update({\n steps: [\n ...drawn.current.steps,\n newOutreachTaskStep(drawn.current.steps, kind),\n ],\n }),\n [update],\n )\n const emailCount = draft.steps.filter((step) => step.kind === 'email').length\n const full = draft.steps.length >= OUTREACH_MAX_STEPS\n const firstEmail = draft.steps.findIndex((entry) => entry.kind === 'email')\n const threadSubject = (index: number) => {\n for (let position = index - 1; position >= 0; position -= 1) {\n const step = draft.steps[position]\n if (step.kind === 'email' && !isInThreadEmailStep(draft.steps, position))\n return step.subject\n }\n return ''\n }\n\n const save = async () => {\n setAttempted(true)\n if (\n validateOutreachSequence(draft).some(\n (issue) => issue.severity === 'error',\n )\n )\n return\n setSaving(true)\n try {\n const answer = await api.saveSequence(sequence?.id ?? null, draft)\n setServerIssues([])\n setAttempted(false)\n enqueueSnackbar(\n answer.created ? 'Sequence created.' : 'Sequence saved.',\n { variant: 'success' },\n )\n props.onSaved(answer.sequence)\n } catch (error) {\n if (error instanceof OutreachRouteError && error.issues.length) {\n setServerIssues(\n error.issues.filter(\n (issue): issue is OutreachSequenceIssue => 'path' in issue,\n ),\n )\n }\n enqueueSnackbar((error as Error).message, {\n variant: 'error',\n allowDuplicate: true,\n })\n } finally {\n setSaving(false)\n }\n }\n // The save as last drawn, behind a handler that keeps its identity.\n const drawnSave = useRef(save)\n drawnSave.current = save\n const onSave = useCallback(() => void drawnSave.current(), [])\n\n const siteName = hosts.find((host) => host.id === draft.hostId)?.name ?? ''\n const draftCountries = draft.settings.allowedCountries\n const countryOptions = useMemo(\n () => [...new Set([...orgCountries, ...draftCountries])],\n [orgCountries, draftCountries],\n )\n /*\n * A test sends the step as STORED (AGL-3325): the route reads the saved\n * sequence, so a new one has nothing to send yet and an edited one sends\n * its last save — said on the button and in the dialog rather than found\n * out in the inbox.\n */\n const unsaved =\n sequence !== null &&\n JSON.stringify(draft) !== JSON.stringify(outreachSequenceDraftOf(sequence))\n const sendTestDisabledReason = !sequence\n ? 'Create the sequence to send a test of it.'\n : !draft.mailboxId\n ? 'Choose a mailbox to send the test from.'\n : null\n\n return (\n <Grid container spacing={2}>\n <Grid size={{ xs: 12, md: 7 }}>\n <Stack spacing={2}>\n {archived ? (\n <Alert severity=\"info\">\n This sequence is archived, so it can’t be edited.\n </Alert>\n ) : null}\n <SequenceCard\n orgId={orgId}\n uid={uid}\n name={draft.name}\n hostId={draft.hostId}\n mailboxId={draft.mailboxId}\n campaignIds={draft.campaignIds}\n hosts={hosts}\n mailboxes={props.mailboxes}\n mailboxesPath={props.mailboxesPath}\n campaignOptions={campaigns.options}\n noCampaigns={campaigns.ready && !campaigns.options.length}\n nameIssue={issueAt('name')}\n hostIssue={issueAt('hostId')}\n mailboxIssue={issueAt('mailboxId')}\n campaignsIssue={issueAt('campaignIds')}\n archived={archived}\n saving={saving}\n saveLabel={sequence ? 'Save' : 'Create sequence'}\n onCancel={props.onCancel}\n onSave={onSave}\n onName={setName}\n onHost={setHost}\n onMailbox={setMailbox}\n onCampaigns={setCampaigns}\n />\n\n {listIssue ? <Alert severity=\"error\">{listIssue}</Alert> : null}\n {draft.steps.map((step, index) => {\n const inThread = isInThreadEmailStep(draft.steps, index)\n return (\n <SequenceStepItem\n key={step.id || `step-${index}`}\n step={step}\n index={index}\n count={draft.steps.length}\n firstEmail={firstEmail === index}\n inThread={inThread}\n // Read only by an email sent in the thread, so no other\n // step is redrawn as the subject it replies to is typed.\n threadSubject={inThread ? threadSubject(index) : ''}\n templates={templates.data}\n issues={stepIssues[index]}\n disabled={archived}\n canSendTest={step.kind === 'email' && !archived}\n sendTestDisabledReason={sendTestDisabledReason}\n onStepChange={setStep}\n onStepMove={moveStep}\n onStepRemove={removeStep}\n onStepTest={setTesting}\n />\n )\n })}\n <SequenceAddStep\n emailDisabled={\n archived || full || emailCount >= OUTREACH_MAX_EMAIL_STEPS\n }\n taskDisabled={archived || full}\n onAddEmail={addEmail}\n onAddTask={addTask}\n />\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Up to ${OUTREACH_MAX_STEPS} steps, ${OUTREACH_MAX_EMAIL_STEPS} of them emails.`}\n </Typography>\n\n <SequenceSendingCard\n settings={draft.settings}\n countryOptions={countryOptions}\n countriesIssue={countriesIssue}\n daysIssue={issueAt('settings.window.days')}\n hoursIssue={issueAt('settings.window')}\n disabled={archived}\n onCountries={setCountries}\n onSettings={setSettings}\n />\n\n </Stack>\n </Grid>\n {sequence && testing !== null ? (\n <OutreachStepTestDialog\n open\n onClose={() => setTesting(null)}\n orgId={orgId}\n sequence={sequence}\n stepIndex={testing}\n contactGroupId={\n sequence.hostId\n ? consentGroupForHost(props.org ?? null, sequence.hostId).groupId\n : ''\n }\n selfEmail={user?.email ?? null}\n unsaved={unsaved}\n api={api}\n />\n ) : null}\n <Grid size={{ xs: 12, md: 5 }}>\n <Box sx={PREVIEW_COLUMN}>\n <MemoSequencePreview\n steps={draft.steps}\n orgSettings={settings.settings}\n orgSettingsStatus={settings.status}\n templates={templates.data}\n sender={sender}\n siteName={siteName}\n />\n </Box>\n </Grid>\n </Grid>\n )\n}\nOutreachSequenceEditor.displayName = 'OutreachSequenceEditor'\n\nexport default OutreachSequenceEditor\n"],"names":["pluginDocsHelp","consentGroupForHost","mdiPlus","CardDisplay","MdiIcon","ContainerPicker","useSnackbar","useOrgContainerOptions","useUser","Alert","Autocomplete","Box","Button","Chip","FormControlLabel","Grid","Menu","MenuItem","Stack","Switch","TextField","Typography","memo","useCallback","useEffect","useMemo","useRef","useState","isInThreadEmailStep","OUTREACH_SEQUENCE_NAME_MAX","validateOutreachSequence","outreachCountryLabel","OUTREACH_MAX_EMAIL_STEPS","OUTREACH_MAX_STEPS","OUTREACH_TASK_KIND_LABELS","OUTREACH_TASK_KINDS","emptyOutreachSequenceDraft","newOutreachEmailStep","newOutreachTaskStep","outreachSequenceDraftOf","OutreachSendWindowFields","OutreachSequenceMailboxPicker","OutreachSequencePreview","OutreachStepCard","OutreachStepTestDialog","OutreachRouteError","useOutreachApi","useOutreachEmailTemplates","stepIssueKey","path","match","exec","field","index","Number","OutreachCountriesField","props","multiple","options","value","getOptionLabel","onChange","_event","next","disabled","renderValue","getItemProps","map","code","key","item","size","label","renderInput","params","error","Boolean","issue","helperText","useStepIssues","issues","count","held","found","Array","from","length","record","undefined","message","current","JSON","stringify","before","entry","MemoTextField","MemoMailboxPicker","MemoContainerPicker","MemoSendWindowFields","MemoSequencePreview","NAME_SLOT_PROPS","htmlInput","maxLength","ADD_STEP_ROW","flexWrap","rowGap","PREVIEW_COLUMN","position","md","top","SequenceSiteField","hosts","select","some","host","id","fullWidth","name","SequenceSaveActions","direction","spacing","onCancel","variant","onClick","saving","onSave","saveLabel","SequenceCard","archived","header","help","anchor","HeaderProps","action","contentGutterX","contentGutterY","onName","nameIssue","slotProps","hostId","hostIssue","onHost","orgId","uid","mailboxes","mailboxesPath","mailboxId","onMailbox","mailboxIssue","kind","campaignOptions","campaignIds","onCampaigns","campaignsIssue","empty","noCampaigns","emptyText","SequenceStepItem","canSendTest","onStepChange","onStepMove","onStepRemove","onStepTest","card","step","onMove","onRemove","onSendTest","SequenceAddStep","onAddTask","taskAnchor","setTaskAnchor","sx","startIcon","fontSize","emailDisabled","onAddEmail","taskDisabled","event","currentTarget","aria-haspopup","anchorEl","open","onClose","SequenceSendingCard","settings","onSettings","setAllowCustomers","allowCustomers","target","checked","setTrackClicks","trackClicks","setCountOpens","countOpens","setListUnsubscribe","listUnsubscribe","setWindow","window","excerpt","countryOptions","allowedCountries","onCountries","countriesIssue","control","color","daysIssue","hoursIssue","OutreachSequenceEditor","orgMount","sequence","api","enqueueSnackbar","data","user","mountedHosts","orgCountries","draft","setDraft","attempted","setAttempted","serverIssues","setServerIssues","setSaving","testing","setTesting","drawn","settingsCountries","join","previous","split","ownSending","filter","connectedByUid","status","onlyOwnMailbox","templates","mailbox","find","sender","displayName","email","sendAs","campaigns","enabled","judged","severity","includes","issueAt","stepIssues","steps","listIssue","startsWith","clearServerIssues","update","setCountries","setName","setHost","setMailbox","setCampaigns","setSettings","patch","setStep","moveStep","moved","splice","removeStep","_entry","addEmail","addTask","emailCount","full","firstEmail","findIndex","threadSubject","subject","save","answer","saveSequence","created","onSaved","allowDuplicate","drawnSave","siteName","draftCountries","Set","unsaved","sendTestDisabledReason","container","xs","ready","inThread","stepIndex","contactGroupId","org","groupId","selfEmail","orgSettings","orgSettingsStatus"],"mappings":"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"}
|
|
@@ -18,10 +18,25 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
18
18
|
import { pluginDocsHelp } from "@aglyn/aglyn";
|
|
19
19
|
import { crmMergeUnresolvedMessage } from "@aglyn/aglyn/app-utils/crm-email-templates";
|
|
20
20
|
import { CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { useContentStable } from "@aglyn/shared-ui-jsx/hooks/use-content-stable";
|
|
21
22
|
import { Alert, Divider, MenuItem, Paper, Stack, TextField, Typography } from "@mui/material";
|
|
22
|
-
import { useMemo, useState } from "react";
|
|
23
|
+
import { memo, useMemo, useState } from "react";
|
|
23
24
|
import { OUTREACH_SAMPLE_PERSON, outreachSampleMergeContext, previewOutreachStep } from "../model/step-preview.js";
|
|
24
25
|
import { OutreachLoading } from "./outreach-ui.js";
|
|
26
|
+
/** Which email the preview shows, when the sequence has more than one. */ const PreviewEmailPicker = /*#__PURE__*/ memo(function PreviewEmailPicker(props) {
|
|
27
|
+
const { onChange } = props;
|
|
28
|
+
return /*#__PURE__*/ _jsx(TextField, {
|
|
29
|
+
select: true,
|
|
30
|
+
size: "small",
|
|
31
|
+
label: "Email",
|
|
32
|
+
value: props.value,
|
|
33
|
+
onChange: (event)=>onChange(Number(event.target.value)),
|
|
34
|
+
children: props.choices.map((entry, position)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
35
|
+
value: position,
|
|
36
|
+
children: `Email ${position + 1} · step ${entry.index + 1}`
|
|
37
|
+
}, entry.id))
|
|
38
|
+
});
|
|
39
|
+
});
|
|
25
40
|
/**
|
|
26
41
|
* The editor's live preview (AGL-2980): each email as it would reach a
|
|
27
42
|
* sample contact, written by the engine's own composer as the rep types —
|
|
@@ -36,6 +51,12 @@ import { OutreachLoading } from "./outreach-ui.js";
|
|
|
36
51
|
})).filter(({ step })=>step.kind === 'email');
|
|
37
52
|
const [chosen, setChosen] = useState(0);
|
|
38
53
|
const selected = emails[Math.min(chosen, emails.length - 1)];
|
|
54
|
+
// Held by content: typing into a step leaves the emails to pick from as
|
|
55
|
+
// they were, and the picker undrawn (AGL-3423).
|
|
56
|
+
const choices = useContentStable(emails.map(({ step, index })=>({
|
|
57
|
+
id: step.id,
|
|
58
|
+
index
|
|
59
|
+
})));
|
|
39
60
|
const result = useMemo(()=>{
|
|
40
61
|
var _ref;
|
|
41
62
|
var _props_templates_find;
|
|
@@ -81,16 +102,10 @@ import { OutreachLoading } from "./outreach-ui.js";
|
|
|
81
102
|
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
82
103
|
spacing: 1.5,
|
|
83
104
|
children: [
|
|
84
|
-
emails.length > 1 ? /*#__PURE__*/ _jsx(
|
|
85
|
-
|
|
86
|
-
size: "small",
|
|
87
|
-
label: "Email",
|
|
105
|
+
emails.length > 1 ? /*#__PURE__*/ _jsx(PreviewEmailPicker, {
|
|
106
|
+
choices: choices,
|
|
88
107
|
value: Math.min(chosen, emails.length - 1),
|
|
89
|
-
onChange:
|
|
90
|
-
children: emails.map((entry, position)=>/*#__PURE__*/ _jsx(MenuItem, {
|
|
91
|
-
value: position,
|
|
92
|
-
children: `Email ${position + 1} · step ${entry.index + 1}`
|
|
93
|
-
}, entry.step.id))
|
|
108
|
+
onChange: setChosen
|
|
94
109
|
}) : null,
|
|
95
110
|
(result == null ? void 0 : result.email) ? /*#__PURE__*/ _jsx(Paper, {
|
|
96
111
|
variant: "outlined",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-preview.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 { crmMergeUnresolvedMessage } from '@aglyn/aglyn/app-utils/crm-email-templates'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport {\n Alert,\n Divider,\n MenuItem,\n Paper,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { useMemo, useState } from 'react'\nimport type {\n OutreachComplianceSettingsDocument,\n OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n OUTREACH_SAMPLE_PERSON,\n outreachSampleMergeContext,\n previewOutreachStep,\n} from '../model/step-preview'\nimport { OutreachLoading } from './outreach-ui'\nimport type { OutreachTemplateOption } from './use-outreach-crm'\nimport type { OutreachLoadStatus } from './use-outreach-data'\n\nexport interface OutreachSequencePreviewProps {\n steps: readonly OutreachSequenceStep[]\n /** The organization's footer fields; `null` until they are read. */\n orgSettings: OutreachComplianceSettingsDocument | null\n orgSettingsStatus: OutreachLoadStatus\n templates: readonly OutreachTemplateOption[]\n /** Who the email is from, as the mailbox says it. */\n sender: { name: string; email: string } | null\n siteName: string\n}\n\n/**\n * The editor's live preview (AGL-2980): each email as it would reach a\n * sample contact, written by the engine's own composer as the rep types —\n * merge fields filled, the sample's personal line in place, and the real\n * footer from the organization's compliance settings at the end.\n */\nexport function OutreachSequencePreview(props: OutreachSequencePreviewProps) {\n const emails = props.steps\n .map((step, index) => ({ step, index }))\n .filter(({ step }) => step.kind === 'email')\n const [chosen, setChosen] = useState(0)\n const selected = emails[Math.min(chosen, emails.length - 1)]\n\n const result = useMemo(() => {\n if (!selected) return null\n const step = selected.step\n return previewOutreachStep({\n steps: props.steps,\n stepIndex: selected.index,\n orgSettings: props.orgSettings,\n merge: outreachSampleMergeContext({\n sender: props.sender,\n siteName: props.siteName,\n }),\n email: OUTREACH_SAMPLE_PERSON.email,\n personalLine: OUTREACH_SAMPLE_PERSON.personalLine,\n templateBody:\n step.kind === 'email' && step.templateId\n ? (props.templates.find((template) => template.id === step.templateId)\n ?.body ?? null)\n : null,\n })\n }, [\n selected,\n props.steps,\n props.orgSettings,\n props.sender,\n props.siteName,\n props.templates,\n ])\n\n return (\n <CardDisplay\n header=\"Preview\"\n subheader={`As ${OUTREACH_SAMPLE_PERSON.name} of ${OUTREACH_SAMPLE_PERSON.companyName} would get it`}\n help={pluginDocsHelp('sequences', { anchor: '#build-a-sequence' })}\n contentGutterX\n contentGutterY\n >\n {!selected ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Add an email step to preview it.\n </Typography>\n ) : props.orgSettingsStatus === 'loading' ? (\n <OutreachLoading label=\"Loading your footer…\" />\n ) : props.orgSettingsStatus !== 'ready' ? (\n <Alert severity=\"info\">\n The preview needs your compliance settings, which couldn’t be read. It\n shows once they can be.\n </Alert>\n ) : (\n <Stack spacing={1.5}>\n {emails.length > 1 ? (\n <TextField\n select\n size=\"small\"\n label=\"Email\"\n value={Math.min(chosen, emails.length - 1)}\n onChange={(event) => setChosen(Number(event.target.value))}\n >\n {emails.map((entry, position) => (\n <MenuItem key={entry.step.id} value={position}>\n {`Email ${position + 1} · step ${entry.index + 1}`}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {result?.email ? (\n <Paper\n variant=\"outlined\"\n sx={{ p: 1.5 }}\n aria-label=\"Email preview\"\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\">\n <Typography\n component=\"span\"\n variant=\"body2\"\n color=\"text.secondary\"\n >\n Subject:{' '}\n </Typography>\n {result.email.subject}\n </Typography>\n <Divider />\n <Typography\n variant=\"body2\"\n sx={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}\n >\n {result.email.text}\n </Typography>\n </Stack>\n </Paper>\n ) : (\n <Alert severity=\"info\">\n {result?.error?.message ?? 'This email can’t be previewed yet.'}\n </Alert>\n )}\n {result?.email && result.unresolvedFields.length ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${crmMergeUnresolvedMessage(result.unresolvedFields)}. They're sent empty.`}\n </Typography>\n ) : null}\n </Stack>\n )}\n </CardDisplay>\n )\n}\nOutreachSequencePreview.displayName = 'OutreachSequencePreview'\n\nexport default OutreachSequencePreview\n"],"names":["pluginDocsHelp","crmMergeUnresolvedMessage","CardDisplay","Alert","Divider","MenuItem","Paper","Stack","TextField","Typography","useMemo","useState","OUTREACH_SAMPLE_PERSON","outreachSampleMergeContext","previewOutreachStep","OutreachLoading","OutreachSequencePreview","props","result","emails","steps","map","step","index","filter","kind","chosen","setChosen","selected","Math","min","length","stepIndex","orgSettings","merge","sender","siteName","email","personalLine","templateBody","templateId","templates","find","template","id","body","header","subheader","name","companyName","help","anchor","contentGutterX","contentGutterY","variant","color","orgSettingsStatus","label","severity","spacing","select","size","value","onChange","event","Number","target","entry","position","sx","p","aria-label","component","subject","whiteSpace","wordBreak","text","error","message","unresolvedFields","displayName"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/outreach/src/lib/components/sequence-preview.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 { crmMergeUnresolvedMessage } from '@aglyn/aglyn/app-utils/crm-email-templates'\nimport { CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { useContentStable } from '@aglyn/shared-ui-jsx/hooks/use-content-stable'\nimport {\n Alert,\n Divider,\n MenuItem,\n Paper,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport { memo, useMemo, useState } from 'react'\nimport type {\n OutreachComplianceSettingsDocument,\n OutreachSequenceStep,\n} from '../model/outreach.types'\nimport {\n OUTREACH_SAMPLE_PERSON,\n outreachSampleMergeContext,\n previewOutreachStep,\n} from '../model/step-preview'\nimport { OutreachLoading } from './outreach-ui'\nimport type { OutreachTemplateOption } from './use-outreach-crm'\nimport type { OutreachLoadStatus } from './use-outreach-data'\n\nexport interface OutreachSequencePreviewProps {\n steps: readonly OutreachSequenceStep[]\n /** The organization's footer fields; `null` until they are read. */\n orgSettings: OutreachComplianceSettingsDocument | null\n orgSettingsStatus: OutreachLoadStatus\n templates: readonly OutreachTemplateOption[]\n /** Who the email is from, as the mailbox says it. */\n sender: { name: string; email: string } | null\n siteName: string\n}\n\n/** One email the preview can show: its step's id, and the step's position. */\ninterface PreviewChoice {\n id: string\n index: number\n}\n\n/** Which email the preview shows, when the sequence has more than one. */\nconst PreviewEmailPicker = memo(function PreviewEmailPicker(props: {\n choices: readonly PreviewChoice[]\n value: number\n onChange(position: number): void\n}) {\n const { onChange } = props\n return (\n <TextField\n select\n size=\"small\"\n label=\"Email\"\n value={props.value}\n onChange={(event) => onChange(Number(event.target.value))}\n >\n {props.choices.map((entry, position) => (\n <MenuItem key={entry.id} value={position}>\n {`Email ${position + 1} · step ${entry.index + 1}`}\n </MenuItem>\n ))}\n </TextField>\n )\n})\n\n/**\n * The editor's live preview (AGL-2980): each email as it would reach a\n * sample contact, written by the engine's own composer as the rep types —\n * merge fields filled, the sample's personal line in place, and the real\n * footer from the organization's compliance settings at the end.\n */\nexport function OutreachSequencePreview(props: OutreachSequencePreviewProps) {\n const emails = props.steps\n .map((step, index) => ({ step, index }))\n .filter(({ step }) => step.kind === 'email')\n const [chosen, setChosen] = useState(0)\n const selected = emails[Math.min(chosen, emails.length - 1)]\n // Held by content: typing into a step leaves the emails to pick from as\n // they were, and the picker undrawn (AGL-3423).\n const choices = useContentStable(\n emails.map(({ step, index }) => ({ id: step.id, index })),\n )\n\n const result = useMemo(() => {\n if (!selected) return null\n const step = selected.step\n return previewOutreachStep({\n steps: props.steps,\n stepIndex: selected.index,\n orgSettings: props.orgSettings,\n merge: outreachSampleMergeContext({\n sender: props.sender,\n siteName: props.siteName,\n }),\n email: OUTREACH_SAMPLE_PERSON.email,\n personalLine: OUTREACH_SAMPLE_PERSON.personalLine,\n templateBody:\n step.kind === 'email' && step.templateId\n ? (props.templates.find((template) => template.id === step.templateId)\n ?.body ?? null)\n : null,\n })\n }, [\n selected,\n props.steps,\n props.orgSettings,\n props.sender,\n props.siteName,\n props.templates,\n ])\n\n return (\n <CardDisplay\n header=\"Preview\"\n subheader={`As ${OUTREACH_SAMPLE_PERSON.name} of ${OUTREACH_SAMPLE_PERSON.companyName} would get it`}\n help={pluginDocsHelp('sequences', { anchor: '#build-a-sequence' })}\n contentGutterX\n contentGutterY\n >\n {!selected ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n Add an email step to preview it.\n </Typography>\n ) : props.orgSettingsStatus === 'loading' ? (\n <OutreachLoading label=\"Loading your footer…\" />\n ) : props.orgSettingsStatus !== 'ready' ? (\n <Alert severity=\"info\">\n The preview needs your compliance settings, which couldn’t be read. It\n shows once they can be.\n </Alert>\n ) : (\n <Stack spacing={1.5}>\n {emails.length > 1 ? (\n <PreviewEmailPicker\n choices={choices}\n value={Math.min(chosen, emails.length - 1)}\n onChange={setChosen}\n />\n ) : null}\n {result?.email ? (\n <Paper\n variant=\"outlined\"\n sx={{ p: 1.5 }}\n aria-label=\"Email preview\"\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\">\n <Typography\n component=\"span\"\n variant=\"body2\"\n color=\"text.secondary\"\n >\n Subject:{' '}\n </Typography>\n {result.email.subject}\n </Typography>\n <Divider />\n <Typography\n variant=\"body2\"\n sx={{ whiteSpace: 'pre-wrap', wordBreak: 'break-word' }}\n >\n {result.email.text}\n </Typography>\n </Stack>\n </Paper>\n ) : (\n <Alert severity=\"info\">\n {result?.error?.message ?? 'This email can’t be previewed yet.'}\n </Alert>\n )}\n {result?.email && result.unresolvedFields.length ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${crmMergeUnresolvedMessage(result.unresolvedFields)}. They're sent empty.`}\n </Typography>\n ) : null}\n </Stack>\n )}\n </CardDisplay>\n )\n}\nOutreachSequencePreview.displayName = 'OutreachSequencePreview'\n\nexport default OutreachSequencePreview\n"],"names":["pluginDocsHelp","crmMergeUnresolvedMessage","CardDisplay","useContentStable","Alert","Divider","MenuItem","Paper","Stack","TextField","Typography","memo","useMemo","useState","OUTREACH_SAMPLE_PERSON","outreachSampleMergeContext","previewOutreachStep","OutreachLoading","PreviewEmailPicker","props","onChange","select","size","label","value","event","Number","target","choices","map","entry","position","index","id","OutreachSequencePreview","result","emails","steps","step","filter","kind","chosen","setChosen","selected","Math","min","length","stepIndex","orgSettings","merge","sender","siteName","email","personalLine","templateBody","templateId","templates","find","template","body","header","subheader","name","companyName","help","anchor","contentGutterX","contentGutterY","variant","color","orgSettingsStatus","severity","spacing","sx","p","aria-label","component","subject","whiteSpace","wordBreak","text","error","message","unresolvedFields","displayName"],"mappings":"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"}
|
|
@@ -30,6 +30,16 @@ export interface OutreachStepCardProps {
|
|
|
30
30
|
/**
|
|
31
31
|
* One step of the sequence editor (AGL-2980): an email — subject, body or a
|
|
32
32
|
* CRM template, merge fields, the wait before it — or a task for the rep.
|
|
33
|
+
*
|
|
34
|
+
* ## Each field redraws on its own (AGL-3423)
|
|
35
|
+
*
|
|
36
|
+
* The editor keeps the whole sequence in one draft, so a letter typed into a
|
|
37
|
+
* step hands its card a new step. The card's fields are memoized, with
|
|
38
|
+
* handlers that keep their identity and write the step as last drawn, so the
|
|
39
|
+
* letter redraws the field it lands in rather than the wait, the subject, the
|
|
40
|
+
* template picker and the body together. A keystroke that costs one field
|
|
41
|
+
* leaves typed input nothing to queue behind, which is what kept the
|
|
42
|
+
* countries chips from being redrawn by fifty keystrokes in a row (#185).
|
|
33
43
|
*/
|
|
34
44
|
export declare function OutreachStepCard(props: OutreachStepCardProps): import("react").JSX.Element;
|
|
35
45
|
export declare namespace OutreachStepCard {
|