@aglyn/plugins-email 1.0.0-beta.230 → 1.0.0-beta.232

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/list-detail-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\n/**\n * ONE AUDIENCE: who it is, how it is built, and every person on it.\n *\n * ## Why a route rather than an expanded row\n *\n * The membership used to unfold inside the list table, which cost three\n * things a merchant actually uses. A list was not linkable, so \"check the\n * Wholesale audience\" could not be sent to anybody. The back button walked out\n * of the whole surface rather than out of the list. And the list's own\n * settings had to live under a table they were not about.\n *\n * ## What it reads\n *\n * The list document — one document, whatever the audience's size — plus one\n * server aggregate for the subscriber total, plus the paged membership window\n * the table below draws. The membership is PII and there is one document per\n * subscriber, so it is read HERE, where somebody asked for it, and nowhere\n * that merely lists audiences.\n *\n * ## The rule is stated, not just flagged\n *\n * A dynamic list is a claim about who is on it, and \"Rule\" as a chip is not\n * that claim. The summary spells out every dimension the rule actually\n * carries, so the answer to \"why is this person not in the audience\" is on the\n * screen the question gets asked on.\n *\n * ## On the organization's page, the site it is worked AS\n *\n * The list is the organization's, but adding somebody to it is done as one\n * site — the enrollment and the consent it records are that site's — and the\n * Consent column reads what each person agreed to with one site's group. So\n * with no site in the URL the page asks which, defaulting to the site the\n * list's filters draw from, then the reader's pick this session, then the\n * org's only site. Nothing below the picker is built until there is an\n * answer: a consent column for no site would be a column about nobody.\n */\n\nimport { TransferResumeImport } from '@aglyn/aglyn/app-utils/transfer-resume-import'\nimport {\n consentGroupForHost,\n normalizeDynamicListRule,\n PageHeaderRecord,\n pluginDocsHelp,\n type ConsentGroup,\n} from '@aglyn/aglyn'\nimport { useTransferLauncher } from '@aglyn/aglyn/app-utils/transfer-launcher-context'\nimport {\n mdiPencilOutline,\n mdiTrayArrowDown,\n mdiTrayArrowUp,\n} from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport { collection, doc, getCountFromServer } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { describeDynamicListRule } from './dynamic-list-rule-fields'\nimport {\n OrgSiteSelect,\n useEmailDataScope,\n useEmailOrgMount,\n} from './email-org-mount'\nimport { listMembersResourceKey } from '../transfer/email-transfer-catalog'\nimport ListMembersPanel, { type ListMembersFilter } from './list-members-panel'\n\nexport interface ListDetailCardProps {\n /** The site, or `null` on the organization's Emails page. */\n hostId: string | null\n /**\n * The controller this audience is being read AS, resolved by the page from\n * the org document it already holds — under a site.\n *\n * Passed through rather than resolved here: the group is a fact about the\n * ORG, one lookup serves every section, and a card that resolved its own\n * would be a second place for the answer to come from.\n */\n consentGroup?: ConsentGroup\n /**\n * The org document, on the organization's page, where the group can only be\n * resolved once the reader has said which site they are working as.\n */\n org?: Record<string, unknown>\n listId: string\n /** The emails hub URL, for the way back to the audiences list. */\n basePath: string\n}\n\nexport function ListDetailCard(props: ListDetailCardProps) {\n const { hostId, listId, basePath, org } = props\n const firestore = useFirestore()\n const orgMount = useEmailOrgMount()\n const { scope } = useEmailDataScope(hostId)\n\n const { data: list, status } = useFirestoreDoc<Record<string, any>>(\n () =>\n scope ? doc(firestore, scope[0], scope[1], 'lists', listId) : null,\n [firestore, scope, listId],\n )\n\n /*\n * The subscriber total, as a server aggregate.\n *\n * Not the length of the page below: a count beside a paged table is exactly\n * where a page length gets mistaken for a total. Taken once, and re-taken\n * only when a membership is added or removed — a figure that disagreed with\n * the table directly under it would read as the add having failed.\n */\n const [count, setCount] = useState<number | null>(null)\n const [membershipVersion, setMembershipVersion] = useState(0)\n useEffect(() => {\n if (!scope) return undefined\n let active = true\n void getCountFromServer(\n collection(firestore, scope[0], scope[1], 'lists', listId, 'members'),\n )\n .then((snapshot) => {\n if (active) setCount(snapshot.data().count)\n })\n .catch(() => {\n if (active) setCount(null)\n })\n return () => {\n active = false\n }\n }, [firestore, scope, listId, membershipVersion])\n\n const onMembershipChanged = useCallback(\n () => setMembershipVersion((version) => version + 1),\n [],\n )\n\n /*\n * THE SITE THIS AUDIENCE IS WORKED AS. Under a site, that site. On the\n * organization's page, the reader's choice — else the site the filters draw\n * from, else the session's pick, else the only site.\n */\n const [chosenHostId, setChosenHostId] = useState('')\n const listHostId = String(list?.['hostId'] ?? '')\n const enrollHostId: string =\n hostId ??\n (chosenHostId ||\n (orgMount?.hosts.some((site) => site.id === listHostId)\n ? listHostId\n : '') ||\n orgMount?.pickedHostId ||\n '')\n const consentGroup = useMemo<ConsentGroup | null>(\n () =>\n hostId != null\n ? (props.consentGroup ?? null)\n : enrollHostId\n ? consentGroupForHost(org, enrollHostId)\n : null,\n [hostId, props.consentGroup, enrollHostId, org],\n )\n\n /*\n * IMPORT AND EXPORT open the console's transfer wizard and export dialog\n * (AGL-3529) on this list — `email.list-members:<listId>`, one list at a\n * time — worked as the site chosen above. The wizard carries everything the import's\n * controls are: the preview, the screening, the statement of permission,\n * the staged and resumable write, the undo. Outside the console shell\n * there is no launcher, and the buttons are not drawn.\n */\n const transfer = useTransferLauncher()\n const [memberFilter, setMemberFilter] = useState<ListMembersFilter | null>(\n null,\n )\n const listName = String(list?.['name'] ?? '')\n // Each button only for whom the route takes it (AGL-3554), on the site the\n // list enrolls as — the workspace's verdict until one is chosen.\n const transferTarget = {\n resource: listMembersResourceKey(listId),\n scope: 'host' as const,\n hostId: enrollHostId || null,\n }\n const canExportMembers = Boolean(transfer?.can('export', transferTarget))\n const canImportMembers = Boolean(transfer?.can('import', transferTarget))\n\n const audiencesHref = `${basePath}/audiences`\n const headerActions = (\n <Stack direction=\"row\" spacing={1}>\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={audiencesHref}\n size=\"small\"\n color=\"primary\"\n >\n {'All audiences'}\n </Button>\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={`${audiencesHref}/${listId}/edit`}\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPencilOutline.path} size={0.8} />}\n >\n {'Edit list'}\n </Button>\n {transfer && canExportMembers ? (\n <Button\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiTrayArrowDown.path} size={0.8} />}\n // Consent is read as a site; on the org page one has to be chosen.\n disabled={!enrollHostId}\n onClick={() =>\n transfer.openExport({\n resource: listMembersResourceKey(listId),\n scope: 'host',\n hostId: enrollHostId,\n ...(listName ? { title: `Export ${listName}` } : {}),\n ...(memberFilter ? { filter: memberFilter } : {}),\n })\n }\n >\n {'Export'}\n </Button>\n ) : null}\n {/* An import left unfinished, reopened where it stopped (AGL-3549). */}\n {transfer && enrollHostId ? (\n <TransferResumeImport\n target={transferTarget}\n {...(listName ? { title: `Import into ${listName}` } : {})}\n onFinished={onMembershipChanged}\n />\n ) : null}\n {transfer && canImportMembers ? (\n <Button\n size=\"small\"\n color=\"primary\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiTrayArrowUp.path} size={0.8} />}\n // An import enrolls as a site; on the org page one has to be chosen.\n disabled={!enrollHostId}\n onClick={() =>\n transfer.openImport({\n resource: listMembersResourceKey(listId),\n scope: 'host',\n hostId: enrollHostId,\n ...(listName ? { title: `Import into ${listName}` } : {}),\n onFinished: onMembershipChanged,\n })\n }\n >\n {'Import'}\n </Button>\n ) : null}\n </Stack>\n )\n\n if (!list) {\n /*\n * Loading and MISSING are different answers, and the difference matters\n * more here than almost anywhere. Not \"no members\" — a list that cannot be\n * read is a different situation from an empty one, and rendering an empty\n * membership for the first is how somebody comes to believe an audience\n * they built has lost everybody. Reading `status` rather than the absence\n * of data is also what stops the refusal being flashed on every arrival,\n * before the org scope has even resolved.\n */\n const settled = Boolean(scope) && status !== 'loading'\n return (\n <CardDisplay\n header={'Audience'}\n help={pluginDocsHelp('emailCampaigns', { anchor: '#email-lists' })}\n contentGutterX\n contentGutterY\n HeaderProps={{ action: headerActions }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {settled\n ? 'This list could not be loaded. It may have been deleted.'\n : 'Loading this audience…'}\n </Typography>\n </CardDisplay>\n )\n }\n\n const dynamic = list['kind'] === 'dynamic'\n /*\n * The filters, whichever kind of list this is.\n *\n * On a live list they are what the sweep evaluates. On a fixed one they are\n * a saved SEARCH — the same filters, used to find people to add rather than\n * to decide membership — so they are read and described either way, and only\n * the sentence about what happens next differs.\n */\n const rule = normalizeDynamicListRule(list['rule'])\n const hasFilters = rule.sources.length > 0\n const summary = hasFilters ? describeDynamicListRule(rule) : []\n\n /* The card, named so the page chrome above it is a plain list of\n what this surface publishes upward. */\n const card = (\n <CardDisplay\n header={'Audience'}\n help={pluginDocsHelp('emailCampaigns', { anchor: '#list-members' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{ action: headerActions }}\n >\n <Stack spacing={2}>\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap', alignItems: 'center' }}\n >\n {dynamic ? (\n <Chip\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n label={\n list['lastEvaluatedAt']?.toDate\n ? `Rule · ${list['lastEvaluatedAt']\n .toDate()\n .toLocaleString()}`\n : 'Rule · not yet evaluated'\n }\n />\n ) : (\n <Chip size=\"small\" variant=\"outlined\" label=\"Fixed membership\" />\n )}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {count === null\n ? 'Counting subscribers…'\n : count === 1\n ? '1 subscriber'\n : `${count.toLocaleString()} subscribers`}\n </Typography>\n </Stack>\n\n {/*\n A live list's filters are stated HERE rather than only on the edit\n page: they are the answer to \"why is this person not in the\n audience\", which is a question asked while looking at the membership.\n A fixed list states them inside the find-people control instead,\n beside the button that acts on them.\n */}\n {dynamic && hasFilters ? (\n <Stack spacing={0.5}>\n {summary.map((clause) => (\n <Typography key={clause} variant=\"body2\" color=\"text.secondary\">\n {clause}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n {orgMount ? (\n <OrgSiteSelect\n mount={orgMount}\n label=\"Enroll as\"\n value={enrollHostId}\n onChange={setChosenHostId}\n helperText={\n 'People you add are enrolled as this site, and the Consent ' +\n 'column reads what they agreed to with it.'\n }\n />\n ) : null}\n\n {orgMount && !enrollHostId ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Choose the site to work this audience as. Whether someone may ' +\n 'be emailed depends on what they agreed to with a site, so the ' +\n 'members and their consent are read for one site at a time.'}\n </Typography>\n ) : scope && consentGroup ? (\n <ListMembersPanel\n key={enrollHostId}\n hostId={enrollHostId}\n consentGroup={consentGroup}\n scope={scope as readonly [string, string]}\n listId={listId}\n listName={String(list['name'] ?? '')}\n onMembershipChanged={onMembershipChanged}\n /*\n Offered only on a FIXED list. A live list's membership is the\n sweep's to decide, and hand-adding its own matches would write\n `via: 'manual'` copies of rows the sweep already owns — rows it\n then may not reconcile away when the person stops matching.\n */\n findRule={dynamic || !hasFilters ? null : rule}\n ruleSummary={summary}\n onFilterChange={setMemberFilter}\n />\n ) : null}\n </Stack>\n </CardDisplay>\n )\n\n return (\n <>\n {/* The page heading and the trail name the audience; this card is\n then free to say what it holds rather than repeating the title. */}\n <PageHeaderRecord title={String(list['name'] || listId)} />\n {card}\n </>\n )\n}\nListDetailCard.displayName = 'ListDetailCard'\n\nexport default ListDetailCard\n"],"names":["TransferResumeImport","consentGroupForHost","normalizeDynamicListRule","PageHeaderRecord","pluginDocsHelp","useTransferLauncher","mdiPencilOutline","mdiTrayArrowDown","mdiTrayArrowUp","AppLink","CardDisplay","MdiIcon","Button","Chip","Stack","Typography","collection","doc","getCountFromServer","useCallback","useEffect","useMemo","useState","useFirestore","useFirestoreDoc","describeDynamicListRule","OrgSiteSelect","useEmailDataScope","useEmailOrgMount","listMembersResourceKey","ListMembersPanel","ListDetailCard","props","list","hostId","listId","basePath","org","firestore","orgMount","scope","data","status","count","setCount","membershipVersion","setMembershipVersion","undefined","active","then","snapshot","catch","onMembershipChanged","version","chosenHostId","setChosenHostId","listHostId","String","enrollHostId","hosts","some","site","id","pickedHostId","consentGroup","transfer","memberFilter","setMemberFilter","listName","transferTarget","resource","canExportMembers","Boolean","can","canImportMembers","audiencesHref","headerActions","direction","spacing","component","componentVariant","nativeButton","href","size","color","variant","startIcon","path","disabled","onClick","openExport","title","filter","target","onFinished","openImport","settled","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","dynamic","rule","hasFilters","sources","length","summary","card","contentBordered","useFlexGap","sx","flexWrap","alignItems","label","toDate","toLocaleString","map","clause","mount","value","onChange","helperText","findRule","ruleSummary","onFilterChange","displayName"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/list-detail-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\n/**\n * ONE AUDIENCE: who it is, how it is built, and every person on it.\n *\n * ## Why a route rather than an expanded row\n *\n * The membership used to unfold inside the list table, which cost three\n * things a merchant actually uses. A list was not linkable, so \"check the\n * Wholesale audience\" could not be sent to anybody. The back button walked out\n * of the whole surface rather than out of the list. And the list's own\n * settings had to live under a table they were not about.\n *\n * ## What it reads\n *\n * The list document — one document, whatever the audience's size — plus one\n * server aggregate for the subscriber total, plus the paged membership window\n * the table below draws. The membership is PII and there is one document per\n * subscriber, so it is read HERE, where somebody asked for it, and nowhere\n * that merely lists audiences.\n *\n * ## The rule is stated, not just flagged\n *\n * A dynamic list is a claim about who is on it, and \"Rule\" as a chip is not\n * that claim. The summary spells out every dimension the rule actually\n * carries, so the answer to \"why is this person not in the audience\" is on the\n * screen the question gets asked on.\n *\n * ## On the organization's page, the site it is worked AS\n *\n * The list is the organization's, but adding somebody to it is done as one\n * site — the enrollment and the consent it records are that site's — and the\n * Consent column reads what each person agreed to with one site's group. So\n * with no site in the URL the page asks which, defaulting to the site the\n * list's filters draw from, then the reader's pick this session, then the\n * org's only site. Nothing below the picker is built until there is an\n * answer: a consent column for no site would be a column about nobody.\n */\n\nimport { TransferResumeImport } from '@aglyn/aglyn/app-utils/transfer-resume-import'\nimport {\n consentGroupForHost,\n normalizeDynamicListRule,\n PageHeaderRecord,\n pluginDocsHelp,\n type ConsentGroup,\n} from '@aglyn/aglyn'\nimport { useTransferLauncher } from '@aglyn/aglyn/app-utils/transfer-launcher-context'\nimport {\n mdiPencilOutline,\n mdiTrayArrowDown,\n mdiTrayArrowUp,\n} from '@aglyn/shared-data-mdi'\nimport { AppLink, CardDisplay, MdiIcon } from '@aglyn/shared-ui-jsx'\nimport { Button, Chip, Stack, Typography } from '@mui/material'\nimport { collection, doc, getCountFromServer } from 'firebase/firestore'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport { useFirestore, useFirestoreDoc } from '@aglyn/tenant-feature-instance'\nimport { describeDynamicListRule } from './dynamic-list-rule-fields'\nimport {\n OrgSiteSelect,\n useEmailDataScope,\n useEmailOrgMount,\n} from './email-org-mount'\nimport { listMembersResourceKey } from '../transfer/email-transfer-catalog'\nimport ListMembersPanel, { type ListMembersFilter } from './list-members-panel'\n\nexport interface ListDetailCardProps {\n /** The site, or `null` on the organization's Emails page. */\n hostId: string | null\n /**\n * The controller this audience is being read AS, resolved by the page from\n * the org document it already holds — under a site.\n *\n * Passed through rather than resolved here: the group is a fact about the\n * ORG, one lookup serves every section, and a card that resolved its own\n * would be a second place for the answer to come from.\n */\n consentGroup?: ConsentGroup\n /**\n * The org document, on the organization's page, where the group can only be\n * resolved once the reader has said which site they are working as.\n */\n org?: Record<string, unknown>\n listId: string\n /** The emails hub URL, for the way back to the audiences list. */\n basePath: string\n}\n\nexport function ListDetailCard(props: ListDetailCardProps) {\n const { hostId, listId, basePath, org } = props\n const firestore = useFirestore()\n const orgMount = useEmailOrgMount()\n const { scope } = useEmailDataScope(hostId)\n\n const { data: list, status } = useFirestoreDoc<Record<string, any>>(\n () =>\n scope ? doc(firestore, scope[0], scope[1], 'lists', listId) : null,\n [firestore, scope, listId],\n )\n\n /*\n * The subscriber total, as a server aggregate.\n *\n * Not the length of the page below: a count beside a paged table is exactly\n * where a page length gets mistaken for a total. Taken once, and re-taken\n * only when a membership is added or removed — a figure that disagreed with\n * the table directly under it would read as the add having failed.\n */\n const [count, setCount] = useState<number | null>(null)\n const [membershipVersion, setMembershipVersion] = useState(0)\n useEffect(() => {\n if (!scope) return undefined\n let active = true\n void getCountFromServer(\n collection(firestore, scope[0], scope[1], 'lists', listId, 'members'),\n )\n .then((snapshot) => {\n if (active) setCount(snapshot.data().count)\n })\n .catch(() => {\n if (active) setCount(null)\n })\n return () => {\n active = false\n }\n }, [firestore, scope, listId, membershipVersion])\n\n const onMembershipChanged = useCallback(\n () => setMembershipVersion((version) => version + 1),\n [],\n )\n\n /*\n * THE SITE THIS AUDIENCE IS WORKED AS. Under a site, that site. On the\n * organization's page, the reader's choice — else the site the filters draw\n * from, else the session's pick, else the only site.\n */\n const [chosenHostId, setChosenHostId] = useState('')\n const listHostId = String(list?.['hostId'] ?? '')\n const enrollHostId: string =\n hostId ??\n (chosenHostId ||\n (orgMount?.hosts.some((site) => site.id === listHostId)\n ? listHostId\n : '') ||\n orgMount?.pickedHostId ||\n '')\n const consentGroup = useMemo<ConsentGroup | null>(\n () =>\n hostId != null\n ? (props.consentGroup ?? null)\n : enrollHostId\n ? consentGroupForHost(org, enrollHostId)\n : null,\n [hostId, props.consentGroup, enrollHostId, org],\n )\n\n /*\n * IMPORT AND EXPORT open the console's transfer wizard and export dialog\n * (AGL-3529) on this list — `email.list-members:<listId>`, one list at a\n * time — worked as the site chosen above. The wizard carries everything the import's\n * controls are: the preview, the screening, the statement of permission,\n * the staged and resumable write, the undo. Outside the console shell\n * there is no launcher, and the buttons are not drawn.\n */\n const transfer = useTransferLauncher()\n const [memberFilter, setMemberFilter] = useState<ListMembersFilter | null>(\n null,\n )\n const listName = String(list?.['name'] ?? '')\n // Each button only for whom the route takes it (AGL-3554), on the site the\n // list enrolls as — the workspace's verdict until one is chosen.\n const transferTarget = {\n resource: listMembersResourceKey(listId),\n scope: 'host' as const,\n hostId: enrollHostId || null,\n }\n const canExportMembers = Boolean(transfer?.can('export', transferTarget))\n const canImportMembers = Boolean(transfer?.can('import', transferTarget))\n\n const audiencesHref = `${basePath}/audiences`\n const headerActions = (\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap' }}\n >\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={audiencesHref}\n size=\"small\"\n color=\"primary\"\n >\n {'All audiences'}\n </Button>\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={`${audiencesHref}/${listId}/edit`}\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiPencilOutline.path} size={0.8} />}\n >\n {'Edit list'}\n </Button>\n {transfer && canExportMembers ? (\n <Button\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiTrayArrowDown.path} size={0.8} />}\n // Consent is read as a site; on the org page one has to be chosen.\n disabled={!enrollHostId}\n onClick={() =>\n transfer.openExport({\n resource: listMembersResourceKey(listId),\n scope: 'host',\n hostId: enrollHostId,\n ...(listName ? { title: `Export ${listName}` } : {}),\n ...(memberFilter ? { filter: memberFilter } : {}),\n })\n }\n >\n {'Export'}\n </Button>\n ) : null}\n {/* An import left unfinished, reopened where it stopped (AGL-3549). */}\n {transfer && enrollHostId ? (\n <TransferResumeImport\n target={transferTarget}\n {...(listName ? { title: `Import into ${listName}` } : {})}\n onFinished={onMembershipChanged}\n />\n ) : null}\n {transfer && canImportMembers ? (\n <Button\n size=\"small\"\n color=\"primary\"\n variant=\"contained\"\n startIcon={<MdiIcon path={mdiTrayArrowUp.path} size={0.8} />}\n // An import enrolls as a site; on the org page one has to be chosen.\n disabled={!enrollHostId}\n onClick={() =>\n transfer.openImport({\n resource: listMembersResourceKey(listId),\n scope: 'host',\n hostId: enrollHostId,\n ...(listName ? { title: `Import into ${listName}` } : {}),\n onFinished: onMembershipChanged,\n })\n }\n >\n {'Import'}\n </Button>\n ) : null}\n </Stack>\n )\n\n if (!list) {\n /*\n * Loading and MISSING are different answers, and the difference matters\n * more here than almost anywhere. Not \"no members\" — a list that cannot be\n * read is a different situation from an empty one, and rendering an empty\n * membership for the first is how somebody comes to believe an audience\n * they built has lost everybody. Reading `status` rather than the absence\n * of data is also what stops the refusal being flashed on every arrival,\n * before the org scope has even resolved.\n */\n const settled = Boolean(scope) && status !== 'loading'\n return (\n <CardDisplay\n header={'Audience'}\n help={pluginDocsHelp('emailCampaigns', { anchor: '#email-lists' })}\n contentGutterX\n contentGutterY\n HeaderProps={{ action: headerActions }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {settled\n ? 'This list could not be loaded. It may have been deleted.'\n : 'Loading this audience…'}\n </Typography>\n </CardDisplay>\n )\n }\n\n const dynamic = list['kind'] === 'dynamic'\n /*\n * The filters, whichever kind of list this is.\n *\n * On a live list they are what the sweep evaluates. On a fixed one they are\n * a saved SEARCH — the same filters, used to find people to add rather than\n * to decide membership — so they are read and described either way, and only\n * the sentence about what happens next differs.\n */\n const rule = normalizeDynamicListRule(list['rule'])\n const hasFilters = rule.sources.length > 0\n const summary = hasFilters ? describeDynamicListRule(rule) : []\n\n /* The card, named so the page chrome above it is a plain list of\n what this surface publishes upward. */\n const card = (\n <CardDisplay\n header={'Audience'}\n help={pluginDocsHelp('emailCampaigns', { anchor: '#list-members' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{ action: headerActions }}\n >\n <Stack spacing={2}>\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ flexWrap: 'wrap', alignItems: 'center' }}\n >\n {dynamic ? (\n <Chip\n size=\"small\"\n color=\"primary\"\n variant=\"outlined\"\n label={\n list['lastEvaluatedAt']?.toDate\n ? `Rule · ${list['lastEvaluatedAt']\n .toDate()\n .toLocaleString()}`\n : 'Rule · not yet evaluated'\n }\n />\n ) : (\n <Chip size=\"small\" variant=\"outlined\" label=\"Fixed membership\" />\n )}\n <Typography variant=\"body2\" color=\"text.secondary\">\n {count === null\n ? 'Counting subscribers…'\n : count === 1\n ? '1 subscriber'\n : `${count.toLocaleString()} subscribers`}\n </Typography>\n </Stack>\n\n {/*\n A live list's filters are stated HERE rather than only on the edit\n page: they are the answer to \"why is this person not in the\n audience\", which is a question asked while looking at the membership.\n A fixed list states them inside the find-people control instead,\n beside the button that acts on them.\n */}\n {dynamic && hasFilters ? (\n <Stack spacing={0.5}>\n {summary.map((clause) => (\n <Typography key={clause} variant=\"body2\" color=\"text.secondary\">\n {clause}\n </Typography>\n ))}\n </Stack>\n ) : null}\n\n {orgMount ? (\n <OrgSiteSelect\n mount={orgMount}\n label=\"Enroll as\"\n value={enrollHostId}\n onChange={setChosenHostId}\n helperText={\n 'People you add are enrolled as this site, and the Consent ' +\n 'column reads what they agreed to with it.'\n }\n />\n ) : null}\n\n {orgMount && !enrollHostId ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Choose the site to work this audience as. Whether someone may ' +\n 'be emailed depends on what they agreed to with a site, so the ' +\n 'members and their consent are read for one site at a time.'}\n </Typography>\n ) : scope && consentGroup ? (\n <ListMembersPanel\n key={enrollHostId}\n hostId={enrollHostId}\n consentGroup={consentGroup}\n scope={scope as readonly [string, string]}\n listId={listId}\n listName={String(list['name'] ?? '')}\n onMembershipChanged={onMembershipChanged}\n /*\n Offered only on a FIXED list. A live list's membership is the\n sweep's to decide, and hand-adding its own matches would write\n `via: 'manual'` copies of rows the sweep already owns — rows it\n then may not reconcile away when the person stops matching.\n */\n findRule={dynamic || !hasFilters ? null : rule}\n ruleSummary={summary}\n onFilterChange={setMemberFilter}\n />\n ) : null}\n </Stack>\n </CardDisplay>\n )\n\n return (\n <>\n {/* The page heading and the trail name the audience; this card is\n then free to say what it holds rather than repeating the title. */}\n <PageHeaderRecord title={String(list['name'] || listId)} />\n {card}\n </>\n )\n}\nListDetailCard.displayName = 'ListDetailCard'\n\nexport default ListDetailCard\n"],"names":["TransferResumeImport","consentGroupForHost","normalizeDynamicListRule","PageHeaderRecord","pluginDocsHelp","useTransferLauncher","mdiPencilOutline","mdiTrayArrowDown","mdiTrayArrowUp","AppLink","CardDisplay","MdiIcon","Button","Chip","Stack","Typography","collection","doc","getCountFromServer","useCallback","useEffect","useMemo","useState","useFirestore","useFirestoreDoc","describeDynamicListRule","OrgSiteSelect","useEmailDataScope","useEmailOrgMount","listMembersResourceKey","ListMembersPanel","ListDetailCard","props","list","hostId","listId","basePath","org","firestore","orgMount","scope","data","status","count","setCount","membershipVersion","setMembershipVersion","undefined","active","then","snapshot","catch","onMembershipChanged","version","chosenHostId","setChosenHostId","listHostId","String","enrollHostId","hosts","some","site","id","pickedHostId","consentGroup","transfer","memberFilter","setMemberFilter","listName","transferTarget","resource","canExportMembers","Boolean","can","canImportMembers","audiencesHref","headerActions","direction","spacing","useFlexGap","sx","flexWrap","component","componentVariant","nativeButton","href","size","color","variant","startIcon","path","disabled","onClick","openExport","title","filter","target","onFinished","openImport","settled","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","dynamic","rule","hasFilters","sources","length","summary","card","contentBordered","alignItems","label","toDate","toLocaleString","map","clause","mount","value","onChange","helperText","findRule","ruleSummary","onFilterChange","displayName"],"mappings":"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"}
@@ -460,6 +460,7 @@ function OrgSendingDomainPage(props) {
460
460
  ]
461
461
  }) : null,
462
462
  /*#__PURE__*/ _jsxs(Stack, {
463
+ useFlexGap: true,
463
464
  direction: "row",
464
465
  spacing: 1,
465
466
  sx: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/org-sending-domain-detail.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { PageHeaderRecord, pluginDocsHelp } from '@aglyn/aglyn'\nimport { mdiDeleteOutline, mdiEmailCheckOutline } from '@aglyn/shared-data-mdi'\nimport {\n AppLink,\n CardDisplay,\n MdiIcon,\n useConfirmationContext,\n} from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport SenderReadinessPanel from '@aglyn/tenant-feature-instance/components/sender-readiness-panel'\nimport {\n useSendingApi,\n type SendingDomainView,\n} from '@aglyn/tenant-feature-instance/hooks/use-sending-identity-api'\nimport { Alert, Button, Chip, Divider, Stack, Typography } from '@mui/material'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n describeOrgSendingDomainRemoval,\n describeSendingDomain,\n INCONCLUSIVE_CHECK,\n} from '../model/sending-domain-status'\nimport {\n OrgSiteSelect,\n orgSiteName,\n useEmailOrgMount,\n useOrgSitePage,\n type EmailOrgMount,\n} from './email-org-mount'\nimport { RecordRow } from './sending-domain-detail'\nimport { useOrgSendingViews } from './use-org-sending-views'\n\nexport interface OrgSendingDomainDetailProps {\n domain: string\n /** The organization's Emails page, for the way back to Sending. */\n basePath: string\n}\n\n/**\n * ONE SENDING DOMAIN, from the organization's page —\n * `/[orgSlug]/emails/sending/{domain}`.\n *\n * The domain is the organization's, so its page reads the same as it does\n * from a site's: what to publish, whether a lookup can see it, and the checks\n * that move it on. What changes is the half that is a site's choice. Under a\n * site this page asks whether THIS site sends as the domain; over the org it\n * lists the sites that do, and offers to move another site onto it.\n *\n * The records come from the domains route, which is owner-or-admin. An editor\n * still sees the domain's state and the sites sending as it — both come back\n * on each site's own identity read, which an editor may make — and is told\n * why the records are not shown rather than handed an error.\n *\n * `inconclusive` is held apart from the record exactly as on the site's page:\n * a lookup nobody answered changed nothing.\n */\nexport function OrgSendingDomainDetail(props: OrgSendingDomainDetailProps) {\n const mount = useEmailOrgMount()\n return mount ? <OrgSendingDomainPage mount={mount} {...props} /> : null\n}\nOrgSendingDomainDetail.displayName = 'OrgSendingDomainDetail'\n\nfunction OrgSendingDomainPage(\n props: OrgSendingDomainDetailProps & { mount: EmailOrgMount },\n) {\n const { mount, domain, basePath } = props\n const call = useSendingApi()\n const router = useRouter()\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n // Held in a ref: `load` depends on it and the hook hands a fresh function\n // back per render, which would re-run the fetch on every render.\n const notifyRef = useRef(enqueueSnackbar)\n notifyRef.current = enqueueSnackbar\n\n // The first page of sites by name: enough to say which of them send as\n // this domain, and bounded however many sites the organization runs.\n const { sites } = useOrgSitePage(mount)\n const {\n views,\n reload,\n first,\n loading: viewsLoading,\n } = useOrgSendingViews(sites)\n const canManage = first?.canManage === true\n\n const [record, setRecord] = useState<SendingDomainView | null>(null)\n const [refusal, setRefusal] = useState('')\n const [loading, setLoading] = useState(true)\n const [busy, setBusy] = useState(false)\n const [unreachable, setUnreachable] = useState(false)\n const [assignHostId, setAssignHostId] = useState('')\n\n const load = useCallback(async () => {\n setLoading(true)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'GET',\n query: { orgId: mount.orgId },\n })\n setLoading(false)\n if (!response.ok) {\n setRecord(null)\n setRefusal(payload?.error ?? 'This domain’s records could not be read.')\n return\n }\n setRefusal('')\n setRecord(\n (payload?.domains ?? []).find(\n (one: SendingDomainView) => one.domain === domain,\n ) ?? null,\n )\n }, [call, mount.orgId, domain])\n\n useEffect(() => {\n void load()\n }, [load])\n\n /** The domain as the list knows it, for a reader the records route refused. */\n const listed = first?.domains.find((one) => one.domain === domain) ?? null\n const status = record?.status ?? listed?.status ?? null\n const using = mount.hosts.filter(\n (site) => views[site.id]?.selected === domain,\n )\n /** Sites whose identity this page has not read, so cannot speak for. */\n const unchecked = mount.hosts.filter((site) => !views[site.id]).length\n const offered = mount.hosts.filter(\n (site) => views[site.id]?.selected !== domain,\n )\n\n const handleVerify = useCallback(async () => {\n if (busy) return\n setBusy(true)\n setUnreachable(false)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'POST',\n body: { orgId: mount.orgId, domain, action: 'verify' },\n })\n setBusy(false)\n if (response.status === 503) {\n setUnreachable(true)\n return\n }\n if (!response.ok) {\n return void notifyRef.current(\n payload?.error ?? 'The check could not run',\n {\n variant: 'warning',\n },\n )\n }\n await Promise.all([load(), reload()])\n notifyRef.current(\n payload?.verified\n ? 'Verified — this domain can send'\n : 'Checked. Some records are still missing.',\n { variant: payload?.verified ? 'success' : 'info' },\n )\n }, [busy, call, domain, load, mount.orgId, reload])\n\n const handleRequestRecords = useCallback(async () => {\n if (busy) return\n setBusy(true)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'POST',\n body: { orgId: mount.orgId, domain, action: 'request' },\n })\n setBusy(false)\n if (!response.ok) {\n return void notifyRef.current(\n payload?.error ?? 'Could not request records',\n { variant: 'warning' },\n )\n }\n await load()\n }, [busy, call, domain, load, mount.orgId])\n\n /** Moves one site onto this domain, or off it with `'platform'`. */\n const handleUse = useCallback(\n async (hostId: string, next: string) => {\n if (busy || !hostId) return\n setBusy(true)\n const { response, payload } = await call({\n path: 'sending-identity',\n method: 'POST',\n body: { hostId, domain: next },\n })\n setBusy(false)\n if (!response.ok) {\n return void notifyRef.current(\n payload?.error ?? 'Could not change the identity',\n { variant: 'warning' },\n )\n }\n setAssignHostId('')\n await reload(hostId)\n // The address the ROUTE settled on — see the site's page.\n const site = orgSiteName(mount, hostId)\n notifyRef.current(\n payload?.from\n ? `${site} now sends as ${payload.from}`\n : `${site}’s sending address was changed`,\n { variant: 'success' },\n )\n },\n [busy, call, mount, reload],\n )\n\n const handleRelease = useCallback(async () => {\n const ok = await confirm({\n ...describeOrgSendingDomainRemoval({\n domain,\n sites: using.map((site) => ({\n name: orgSiteName(mount, site.id),\n issued: views[site.id]?.platformDomain === domain,\n })),\n unchecked,\n }),\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!ok) return\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'DELETE',\n query: { orgId: mount.orgId, domain },\n })\n if (!response.ok) {\n return void notifyRef.current(\n payload?.error ?? 'Could not remove the domain',\n { variant: 'warning' },\n )\n }\n router.push(`${basePath}/sending`)\n }, [basePath, call, confirm, domain, mount, router, unchecked, using, views])\n\n const state = status\n ? describeSendingDomain({\n status,\n pendingProvider: record?.pendingProvider ?? status === 'requested',\n issueError: record?.lastIssueError ?? listed?.lastIssueError ?? null,\n missing: record?.lastMissing ?? null,\n })\n : null\n const missing = new Set(record?.lastMissing ?? [])\n const actions: RowActionsMenuItem[] =\n canManage && record\n ? [\n {\n key: 'release',\n label: 'Remove domain',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleRelease(),\n },\n ]\n : []\n\n const card = (\n <CardDisplay\n header={'Sending domain'}\n help={pluginDocsHelp('emailCampaigns', { anchor: '#sending-domains' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={`${basePath}/sending`}\n size=\"small\"\n color=\"primary\"\n >\n {'All of Sending'}\n </Button>\n {actions.length ? <RowActionsMenu items={actions} /> : null}\n </Stack>\n ),\n }}\n >\n {(loading || viewsLoading) && !state ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : !state ? (\n <Alert severity=\"warning\">\n {refusal ||\n `${domain} is not claimed by this organization. It may have been ` +\n 'removed — go back and add it again if you still want it.'}\n </Alert>\n ) : (\n <Stack spacing={2}>\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Chip\n label={state.label}\n color={state.color}\n variant={state.sending ? 'filled' : 'outlined'}\n />\n </Stack>\n\n <Alert severity={state.severity}>{state.text}</Alert>\n\n {unreachable ? (\n <Alert severity={INCONCLUSIVE_CHECK.severity} icon={false}>\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {INCONCLUSIVE_CHECK.label}\n </Typography>\n <Typography variant=\"body2\">{INCONCLUSIVE_CHECK.text}</Typography>\n </Alert>\n ) : null}\n\n {/*\n THE SITES SENDING AS IT, of those this page read. Each is the\n site's own choice, and moving one off it is the org twin of the\n \"stop sending\" control on a site's page.\n */}\n <Divider />\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {'Sites sending as this domain'}\n </Typography>\n {using.length ? (\n <Stack spacing={1}>\n {using.map((site) => (\n <Stack\n key={site.id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"body2\">\n {orgSiteName(mount, site.id)}\n </Typography>\n {canManage ? (\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => void handleUse(site.id, 'platform')}\n >\n {'Stop sending as this domain'}\n </Button>\n ) : null}\n </Stack>\n ))}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {unchecked\n ? 'None of the sites checked sends as it.'\n : 'No site sends as it.'}\n </Typography>\n )}\n {unchecked && !viewsLoading ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${unchecked} more ${unchecked === 1 ? 'site was' : 'sites were'} ` +\n 'not checked; the Sending page reads them a page at a time.'}\n </Typography>\n ) : null}\n {canManage && status === 'verified' && offered.length ? (\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <OrgSiteSelect\n mount={mount}\n label=\"Send a site’s email as this domain\"\n value={assignHostId}\n onChange={setAssignHostId}\n sites={offered}\n />\n <Button\n variant=\"outlined\"\n startIcon={\n <MdiIcon path={mdiEmailCheckOutline.path} size={0.8} />\n }\n disabled={busy || !assignHostId}\n onClick={() => void handleUse(assignHostId, domain)}\n >\n {'Send as this domain'}\n </Button>\n </Stack>\n ) : null}\n\n {/*\n THE RECORDS, which only the domains route returns. A reader it\n refused is told why, in place of the records, rather than shown an\n empty section that reads as \"nothing to publish\".\n */}\n <Divider />\n {refusal ? (\n <Alert severity=\"info\">\n {canManage\n ? refusal\n : 'The records to publish, and the checks that verify them, ' +\n 'are shown to the organization’s owners and admins.'}\n </Alert>\n ) : record ? (\n <>\n {record.dmarc ? (\n <Alert\n severity={\n record.dmarc.policy === 'reject' &&\n record.status !== 'verified'\n ? 'error'\n : 'info'\n }\n >\n {record.dmarc.consequence}\n </Alert>\n ) : null}\n {/*\n SPF, DKIM and DMARC as a receiver reads them, and whether they\n align with the From domain (AGL-3328). The records table below is\n what to publish; this is what the published zone adds up to.\n */}\n {record.readiness ? (\n <SenderReadinessPanel readiness={record.readiness} />\n ) : null}\n {record.records?.some((entry) => entry.value) ? (\n <>\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {'Publish these records'}\n </Typography>\n <Stack spacing={2}>\n {record.records\n .filter((entry) => entry.value)\n .map((entry) => (\n <RecordRow\n key={`${entry.type}:${entry.name}`}\n record={entry}\n missing={missing.has(`${entry.type}:${entry.name}`)}\n />\n ))}\n {record.dmarcSuggestion &&\n record.dmarc?.policy === 'absent' ? (\n <RecordRow\n record={record.dmarcSuggestion}\n missing={false}\n />\n ) : null}\n </Stack>\n </>\n ) : null}\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {record.status === 'requested' && canManage ? (\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={() => void handleRequestRecords()}\n >\n {busy ? 'Working…' : 'Request records'}\n </Button>\n ) : null}\n {record.status !== 'requested' ? (\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={() => void handleVerify()}\n >\n {busy ? 'Checking…' : 'Check DNS'}\n </Button>\n ) : null}\n </Stack>\n </>\n ) : null}\n </Stack>\n )}\n </CardDisplay>\n )\n\n return (\n <>\n <PageHeaderRecord title={domain} />\n {card}\n </>\n )\n}\nOrgSendingDomainPage.displayName = 'OrgSendingDomainPage'\n\nexport default OrgSendingDomainDetail\n"],"names":["PageHeaderRecord","pluginDocsHelp","mdiDeleteOutline","mdiEmailCheckOutline","AppLink","CardDisplay","MdiIcon","useConfirmationContext","RowActionsMenu","useSnackbar","SenderReadinessPanel","useSendingApi","Alert","Button","Chip","Divider","Stack","Typography","useRouter","useCallback","useEffect","useRef","useState","describeOrgSendingDomainRemoval","describeSendingDomain","INCONCLUSIVE_CHECK","OrgSiteSelect","orgSiteName","useEmailOrgMount","useOrgSitePage","RecordRow","useOrgSendingViews","OrgSendingDomainDetail","props","mount","OrgSendingDomainPage","displayName","record","domain","basePath","call","router","confirm","enqueueSnackbar","notifyRef","current","sites","views","reload","first","loading","viewsLoading","canManage","setRecord","refusal","setRefusal","setLoading","busy","setBusy","unreachable","setUnreachable","assignHostId","setAssignHostId","load","response","payload","path","method","query","orgId","ok","error","domains","find","one","listed","status","using","hosts","filter","site","id","selected","unchecked","length","offered","handleVerify","body","action","variant","Promise","all","verified","handleRequestRecords","handleUse","hostId","next","from","handleRelease","map","name","issued","platformDomain","confirmationButtonProps","color","then","catch","push","state","pendingProvider","issueError","lastIssueError","missing","lastMissing","Set","actions","key","label","icon","size","destructive","onClick","card","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","direction","spacing","sx","alignItems","component","componentVariant","nativeButton","href","items","severity","useFlexGap","flexWrap","sending","text","fontWeight","disabled","value","onChange","startIcon","dmarc","policy","consequence","readiness","records","some","entry","has","type","dmarcSuggestion","gap","title"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/org-sending-domain-detail.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { PageHeaderRecord, pluginDocsHelp } from '@aglyn/aglyn'\nimport { mdiDeleteOutline, mdiEmailCheckOutline } from '@aglyn/shared-data-mdi'\nimport {\n AppLink,\n CardDisplay,\n MdiIcon,\n useConfirmationContext,\n} from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport SenderReadinessPanel from '@aglyn/tenant-feature-instance/components/sender-readiness-panel'\nimport {\n useSendingApi,\n type SendingDomainView,\n} from '@aglyn/tenant-feature-instance/hooks/use-sending-identity-api'\nimport { Alert, Button, Chip, Divider, Stack, Typography } from '@mui/material'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n describeOrgSendingDomainRemoval,\n describeSendingDomain,\n INCONCLUSIVE_CHECK,\n} from '../model/sending-domain-status'\nimport {\n OrgSiteSelect,\n orgSiteName,\n useEmailOrgMount,\n useOrgSitePage,\n type EmailOrgMount,\n} from './email-org-mount'\nimport { RecordRow } from './sending-domain-detail'\nimport { useOrgSendingViews } from './use-org-sending-views'\n\nexport interface OrgSendingDomainDetailProps {\n domain: string\n /** The organization's Emails page, for the way back to Sending. */\n basePath: string\n}\n\n/**\n * ONE SENDING DOMAIN, from the organization's page —\n * `/[orgSlug]/emails/sending/{domain}`.\n *\n * The domain is the organization's, so its page reads the same as it does\n * from a site's: what to publish, whether a lookup can see it, and the checks\n * that move it on. What changes is the half that is a site's choice. Under a\n * site this page asks whether THIS site sends as the domain; over the org it\n * lists the sites that do, and offers to move another site onto it.\n *\n * The records come from the domains route, which is owner-or-admin. An editor\n * still sees the domain's state and the sites sending as it — both come back\n * on each site's own identity read, which an editor may make — and is told\n * why the records are not shown rather than handed an error.\n *\n * `inconclusive` is held apart from the record exactly as on the site's page:\n * a lookup nobody answered changed nothing.\n */\nexport function OrgSendingDomainDetail(props: OrgSendingDomainDetailProps) {\n const mount = useEmailOrgMount()\n return mount ? <OrgSendingDomainPage mount={mount} {...props} /> : null\n}\nOrgSendingDomainDetail.displayName = 'OrgSendingDomainDetail'\n\nfunction OrgSendingDomainPage(\n props: OrgSendingDomainDetailProps & { mount: EmailOrgMount },\n) {\n const { mount, domain, basePath } = props\n const call = useSendingApi()\n const router = useRouter()\n const { confirm } = useConfirmationContext()\n const { enqueueSnackbar } = useSnackbar()\n // Held in a ref: `load` depends on it and the hook hands a fresh function\n // back per render, which would re-run the fetch on every render.\n const notifyRef = useRef(enqueueSnackbar)\n notifyRef.current = enqueueSnackbar\n\n // The first page of sites by name: enough to say which of them send as\n // this domain, and bounded however many sites the organization runs.\n const { sites } = useOrgSitePage(mount)\n const {\n views,\n reload,\n first,\n loading: viewsLoading,\n } = useOrgSendingViews(sites)\n const canManage = first?.canManage === true\n\n const [record, setRecord] = useState<SendingDomainView | null>(null)\n const [refusal, setRefusal] = useState('')\n const [loading, setLoading] = useState(true)\n const [busy, setBusy] = useState(false)\n const [unreachable, setUnreachable] = useState(false)\n const [assignHostId, setAssignHostId] = useState('')\n\n const load = useCallback(async () => {\n setLoading(true)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'GET',\n query: { orgId: mount.orgId },\n })\n setLoading(false)\n if (!response.ok) {\n setRecord(null)\n setRefusal(payload?.error ?? 'This domain’s records could not be read.')\n return\n }\n setRefusal('')\n setRecord(\n (payload?.domains ?? []).find(\n (one: SendingDomainView) => one.domain === domain,\n ) ?? null,\n )\n }, [call, mount.orgId, domain])\n\n useEffect(() => {\n void load()\n }, [load])\n\n /** The domain as the list knows it, for a reader the records route refused. */\n const listed = first?.domains.find((one) => one.domain === domain) ?? null\n const status = record?.status ?? listed?.status ?? null\n const using = mount.hosts.filter(\n (site) => views[site.id]?.selected === domain,\n )\n /** Sites whose identity this page has not read, so cannot speak for. */\n const unchecked = mount.hosts.filter((site) => !views[site.id]).length\n const offered = mount.hosts.filter(\n (site) => views[site.id]?.selected !== domain,\n )\n\n const handleVerify = useCallback(async () => {\n if (busy) return\n setBusy(true)\n setUnreachable(false)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'POST',\n body: { orgId: mount.orgId, domain, action: 'verify' },\n })\n setBusy(false)\n if (response.status === 503) {\n setUnreachable(true)\n return\n }\n if (!response.ok) {\n return void notifyRef.current(\n payload?.error ?? 'The check could not run',\n {\n variant: 'warning',\n },\n )\n }\n await Promise.all([load(), reload()])\n notifyRef.current(\n payload?.verified\n ? 'Verified — this domain can send'\n : 'Checked. Some records are still missing.',\n { variant: payload?.verified ? 'success' : 'info' },\n )\n }, [busy, call, domain, load, mount.orgId, reload])\n\n const handleRequestRecords = useCallback(async () => {\n if (busy) return\n setBusy(true)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'POST',\n body: { orgId: mount.orgId, domain, action: 'request' },\n })\n setBusy(false)\n if (!response.ok) {\n return void notifyRef.current(\n payload?.error ?? 'Could not request records',\n { variant: 'warning' },\n )\n }\n await load()\n }, [busy, call, domain, load, mount.orgId])\n\n /** Moves one site onto this domain, or off it with `'platform'`. */\n const handleUse = useCallback(\n async (hostId: string, next: string) => {\n if (busy || !hostId) return\n setBusy(true)\n const { response, payload } = await call({\n path: 'sending-identity',\n method: 'POST',\n body: { hostId, domain: next },\n })\n setBusy(false)\n if (!response.ok) {\n return void notifyRef.current(\n payload?.error ?? 'Could not change the identity',\n { variant: 'warning' },\n )\n }\n setAssignHostId('')\n await reload(hostId)\n // The address the ROUTE settled on — see the site's page.\n const site = orgSiteName(mount, hostId)\n notifyRef.current(\n payload?.from\n ? `${site} now sends as ${payload.from}`\n : `${site}’s sending address was changed`,\n { variant: 'success' },\n )\n },\n [busy, call, mount, reload],\n )\n\n const handleRelease = useCallback(async () => {\n const ok = await confirm({\n ...describeOrgSendingDomainRemoval({\n domain,\n sites: using.map((site) => ({\n name: orgSiteName(mount, site.id),\n issued: views[site.id]?.platformDomain === domain,\n })),\n unchecked,\n }),\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel.\n .then(() => true)\n .catch(() => false)\n if (!ok) return\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'DELETE',\n query: { orgId: mount.orgId, domain },\n })\n if (!response.ok) {\n return void notifyRef.current(\n payload?.error ?? 'Could not remove the domain',\n { variant: 'warning' },\n )\n }\n router.push(`${basePath}/sending`)\n }, [basePath, call, confirm, domain, mount, router, unchecked, using, views])\n\n const state = status\n ? describeSendingDomain({\n status,\n pendingProvider: record?.pendingProvider ?? status === 'requested',\n issueError: record?.lastIssueError ?? listed?.lastIssueError ?? null,\n missing: record?.lastMissing ?? null,\n })\n : null\n const missing = new Set(record?.lastMissing ?? [])\n const actions: RowActionsMenuItem[] =\n canManage && record\n ? [\n {\n key: 'release',\n label: 'Remove domain',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleRelease(),\n },\n ]\n : []\n\n const card = (\n <CardDisplay\n header={'Sending domain'}\n help={pluginDocsHelp('emailCampaigns', { anchor: '#sending-domains' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: (\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={`${basePath}/sending`}\n size=\"small\"\n color=\"primary\"\n >\n {'All of Sending'}\n </Button>\n {actions.length ? <RowActionsMenu items={actions} /> : null}\n </Stack>\n ),\n }}\n >\n {(loading || viewsLoading) && !state ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : !state ? (\n <Alert severity=\"warning\">\n {refusal ||\n `${domain} is not claimed by this organization. It may have been ` +\n 'removed — go back and add it again if you still want it.'}\n </Alert>\n ) : (\n <Stack spacing={2}>\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <Chip\n label={state.label}\n color={state.color}\n variant={state.sending ? 'filled' : 'outlined'}\n />\n </Stack>\n\n <Alert severity={state.severity}>{state.text}</Alert>\n\n {unreachable ? (\n <Alert severity={INCONCLUSIVE_CHECK.severity} icon={false}>\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {INCONCLUSIVE_CHECK.label}\n </Typography>\n <Typography variant=\"body2\">{INCONCLUSIVE_CHECK.text}</Typography>\n </Alert>\n ) : null}\n\n {/*\n THE SITES SENDING AS IT, of those this page read. Each is the\n site's own choice, and moving one off it is the org twin of the\n \"stop sending\" control on a site's page.\n */}\n <Divider />\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {'Sites sending as this domain'}\n </Typography>\n {using.length ? (\n <Stack spacing={1}>\n {using.map((site) => (\n <Stack\n key={site.id}\n direction=\"row\"\n spacing={1}\n sx={{ alignItems: 'center' }}\n >\n <Typography variant=\"body2\">\n {orgSiteName(mount, site.id)}\n </Typography>\n {canManage ? (\n <Button\n size=\"small\"\n disabled={busy}\n onClick={() => void handleUse(site.id, 'platform')}\n >\n {'Stop sending as this domain'}\n </Button>\n ) : null}\n </Stack>\n ))}\n </Stack>\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {unchecked\n ? 'None of the sites checked sends as it.'\n : 'No site sends as it.'}\n </Typography>\n )}\n {unchecked && !viewsLoading ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`${unchecked} more ${unchecked === 1 ? 'site was' : 'sites were'} ` +\n 'not checked; the Sending page reads them a page at a time.'}\n </Typography>\n ) : null}\n {canManage && status === 'verified' && offered.length ? (\n <Stack\n direction=\"row\"\n spacing={1}\n useFlexGap\n sx={{ alignItems: 'center', flexWrap: 'wrap' }}\n >\n <OrgSiteSelect\n mount={mount}\n label=\"Send a site’s email as this domain\"\n value={assignHostId}\n onChange={setAssignHostId}\n sites={offered}\n />\n <Button\n variant=\"outlined\"\n startIcon={\n <MdiIcon path={mdiEmailCheckOutline.path} size={0.8} />\n }\n disabled={busy || !assignHostId}\n onClick={() => void handleUse(assignHostId, domain)}\n >\n {'Send as this domain'}\n </Button>\n </Stack>\n ) : null}\n\n {/*\n THE RECORDS, which only the domains route returns. A reader it\n refused is told why, in place of the records, rather than shown an\n empty section that reads as \"nothing to publish\".\n */}\n <Divider />\n {refusal ? (\n <Alert severity=\"info\">\n {canManage\n ? refusal\n : 'The records to publish, and the checks that verify them, ' +\n 'are shown to the organization’s owners and admins.'}\n </Alert>\n ) : record ? (\n <>\n {record.dmarc ? (\n <Alert\n severity={\n record.dmarc.policy === 'reject' &&\n record.status !== 'verified'\n ? 'error'\n : 'info'\n }\n >\n {record.dmarc.consequence}\n </Alert>\n ) : null}\n {/*\n SPF, DKIM and DMARC as a receiver reads them, and whether they\n align with the From domain (AGL-3328). The records table below is\n what to publish; this is what the published zone adds up to.\n */}\n {record.readiness ? (\n <SenderReadinessPanel readiness={record.readiness} />\n ) : null}\n {record.records?.some((entry) => entry.value) ? (\n <>\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {'Publish these records'}\n </Typography>\n <Stack spacing={2}>\n {record.records\n .filter((entry) => entry.value)\n .map((entry) => (\n <RecordRow\n key={`${entry.type}:${entry.name}`}\n record={entry}\n missing={missing.has(`${entry.type}:${entry.name}`)}\n />\n ))}\n {record.dmarcSuggestion &&\n record.dmarc?.policy === 'absent' ? (\n <RecordRow\n record={record.dmarcSuggestion}\n missing={false}\n />\n ) : null}\n </Stack>\n </>\n ) : null}\n <Stack\n useFlexGap\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', gap: 1 }}\n >\n {record.status === 'requested' && canManage ? (\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={() => void handleRequestRecords()}\n >\n {busy ? 'Working…' : 'Request records'}\n </Button>\n ) : null}\n {record.status !== 'requested' ? (\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={() => void handleVerify()}\n >\n {busy ? 'Checking…' : 'Check DNS'}\n </Button>\n ) : null}\n </Stack>\n </>\n ) : null}\n </Stack>\n )}\n </CardDisplay>\n )\n\n return (\n <>\n <PageHeaderRecord title={domain} />\n {card}\n </>\n )\n}\nOrgSendingDomainPage.displayName = 'OrgSendingDomainPage'\n\nexport default OrgSendingDomainDetail\n"],"names":["PageHeaderRecord","pluginDocsHelp","mdiDeleteOutline","mdiEmailCheckOutline","AppLink","CardDisplay","MdiIcon","useConfirmationContext","RowActionsMenu","useSnackbar","SenderReadinessPanel","useSendingApi","Alert","Button","Chip","Divider","Stack","Typography","useRouter","useCallback","useEffect","useRef","useState","describeOrgSendingDomainRemoval","describeSendingDomain","INCONCLUSIVE_CHECK","OrgSiteSelect","orgSiteName","useEmailOrgMount","useOrgSitePage","RecordRow","useOrgSendingViews","OrgSendingDomainDetail","props","mount","OrgSendingDomainPage","displayName","record","domain","basePath","call","router","confirm","enqueueSnackbar","notifyRef","current","sites","views","reload","first","loading","viewsLoading","canManage","setRecord","refusal","setRefusal","setLoading","busy","setBusy","unreachable","setUnreachable","assignHostId","setAssignHostId","load","response","payload","path","method","query","orgId","ok","error","domains","find","one","listed","status","using","hosts","filter","site","id","selected","unchecked","length","offered","handleVerify","body","action","variant","Promise","all","verified","handleRequestRecords","handleUse","hostId","next","from","handleRelease","map","name","issued","platformDomain","confirmationButtonProps","color","then","catch","push","state","pendingProvider","issueError","lastIssueError","missing","lastMissing","Set","actions","key","label","icon","size","destructive","onClick","card","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","direction","spacing","sx","alignItems","component","componentVariant","nativeButton","href","items","severity","useFlexGap","flexWrap","sending","text","fontWeight","disabled","value","onChange","startIcon","dmarc","policy","consequence","readiness","records","some","entry","has","type","dmarcSuggestion","gap","title"],"mappings":"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"}
@@ -441,6 +441,7 @@ RecordRow.displayName = 'RecordRow';
441
441
  }) : null,
442
442
  /*#__PURE__*/ _jsx(Divider, {}),
443
443
  /*#__PURE__*/ _jsxs(Stack, {
444
+ useFlexGap: true,
444
445
  direction: "row",
445
446
  spacing: 1,
446
447
  sx: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/sending-domain-detail.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { PageHeaderRecord, pluginDocsHelp } from '@aglyn/aglyn'\nimport { mdiDeleteOutline, mdiEmailCheckOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n Divider,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n describeSendingDomain,\n describeSendingDomainRemoval,\n INCONCLUSIVE_CHECK,\n} from '../model/sending-domain-status'\nimport SenderReadinessPanel from '@aglyn/tenant-feature-instance/components/sender-readiness-panel'\nimport {\n useSendingApi,\n type SendingDnsRecordView,\n type SendingDomainView,\n type SendingIdentityView,\n} from '@aglyn/tenant-feature-instance/hooks/use-sending-identity-api'\n\nexport interface SendingDomainDetailProps {\n hostId: string\n domain: string\n basePath: string\n}\n\n/**\n * The record block, in the shape a registrar's own form asks for. Shared with\n * the organization's page for a domain, which prints the same records.\n */\nexport function RecordRow(props: {\n record: SendingDnsRecordView\n missing: boolean\n}) {\n const { record, missing } = props\n const target = record.priority\n ? `${record.priority} ${record.value}`\n : record.value\n return (\n <Stack spacing={0.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" label={record.type} variant=\"outlined\" />\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {record.purpose === 'spf'\n ? 'Authorization'\n : record.purpose === 'dkim'\n ? 'Signing key'\n : record.purpose === 'return-path'\n ? 'Bounce routing'\n : 'DMARC policy'}\n </Typography>\n {record.required ? null : (\n <Chip size=\"small\" label=\"Optional\" variant=\"outlined\" />\n )}\n {missing ? <Chip size=\"small\" label=\"Not seen yet\" color=\"warning\" /> : null}\n </Stack>\n <Typography\n variant=\"body2\"\n component=\"code\"\n sx={{\n p: 1,\n bgcolor: 'action.hover',\n borderRadius: 1,\n fontFamily: 'monospace',\n whiteSpace: 'pre-wrap',\n overflowWrap: 'anywhere',\n }}\n >\n {`${record.type} ${record.name} → ${target || '(not issued yet)'}`}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {record.note}\n </Typography>\n </Stack>\n )\n}\nRecordRow.displayName = 'RecordRow'\n\n/**\n * ONE SENDING DOMAIN: what to publish, whether we can see it, and whether\n * this site sends as it.\n *\n * ## `inconclusive` is not a failure, and this page is where that is felt\n *\n * Pressing Check DNS has three outcomes, not two. The third is that nobody\n * answered — a resolver outage, a timeout — and the route says so with a\n * `503` rather than with `verified: false`. It is held HERE, in the surface's\n * own state, next to a status the record still holds, because the record was\n * not changed and saying otherwise would send a customer whose DNS is perfect\n * to go and edit a zone that has nothing wrong with it.\n *\n * That is why `lastCheck` is a separate piece of state from `record.status`\n * and never assigned into it.\n */\nexport function SendingDomainDetail(props: SendingDomainDetailProps) {\n const { hostId, domain, basePath } = props\n const call = useSendingApi()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n /*\n * The snackbar held in a REF, for the reason `use-campaign-send-api` holds\n * the user in one: `load` is a dependency of the effect that runs it, so\n * anything whose identity changes per render re-runs the fetch — and a\n * hook returning a fresh object each render turns that into a loop that\n * never settles. Reading it through a ref makes the effect depend on the\n * things that actually identify the request.\n */\n const notifyRef = useRef(enqueueSnackbar)\n notifyRef.current = enqueueSnackbar\n const { confirm } = useConfirmationContext()\n\n const [identity, setIdentity] = useState<SendingIdentityView | null>(null)\n const [record, setRecord] = useState<SendingDomainView | null>(null)\n const [loading, setLoading] = useState(true)\n const [busy, setBusy] = useState(false)\n /**\n * The outcome of the last check the reader ran, and ONLY when it was\n * inconclusive. Never merged into the record: an unanswered lookup is\n * evidence of nothing, and the record's own status is still the last thing\n * we actually established.\n */\n const [unreachable, setUnreachable] = useState(false)\n\n const load = useCallback(async () => {\n setLoading(true)\n const view = await call({\n path: 'sending-identity',\n method: 'GET',\n query: { hostId },\n })\n if (!view.response.ok) {\n setLoading(false)\n return void notifyRef.current(\n view.payload?.error ?? 'Could not read the sending identity',\n { variant: 'warning' },\n )\n }\n setIdentity(view.payload as SendingIdentityView)\n const orgId = String(view.payload?.orgId ?? '')\n const domains = orgId\n ? await call({\n path: 'sending-domains',\n method: 'GET',\n query: { orgId },\n })\n : null\n setLoading(false)\n const found = (domains?.payload?.domains ?? []).find(\n (one: SendingDomainView) => one.domain === domain,\n )\n setRecord(found ?? null)\n }, [call, hostId, domain])\n\n useEffect(() => {\n void load()\n }, [load])\n\n const handleVerify = useCallback(async () => {\n if (busy || !identity?.orgId) return\n setBusy(true)\n setUnreachable(false)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'POST',\n body: { orgId: identity.orgId, domain, action: 'verify' },\n })\n setBusy(false)\n /*\n * THE THIRD OUTCOME.\n *\n * A `503` here means the lookup got no answer. Nothing about the domain\n * changed, so nothing on screen changes either except a notice saying the\n * question could not be asked. Treating it as a failed check — which is\n * what a plain `!response.ok` branch would do — is how a customer ends up\n * rewriting records that were already correct.\n */\n if (response.status === 503) {\n setUnreachable(true)\n return\n }\n if (!response.ok) {\n return void notifyRef.current(payload?.error ?? 'The check could not run', {\n variant: 'warning',\n })\n }\n await load()\n notifyRef.current(\n payload?.verified\n ? 'Verified — this domain can send'\n : 'Checked. Some records are still missing.',\n { variant: payload?.verified ? 'success' : 'info' },\n )\n }, [busy, call, identity?.orgId, domain, load])\n\n const handleRequestRecords = useCallback(async () => {\n if (busy || !identity?.orgId) return\n setBusy(true)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'POST',\n body: { orgId: identity.orgId, domain, action: 'request' },\n })\n setBusy(false)\n if (!response.ok) {\n return void notifyRef.current(payload?.error ?? 'Could not request records', {\n variant: 'warning',\n })\n }\n await load()\n }, [busy, call, identity?.orgId, domain, load])\n\n const handleUse = useCallback(\n async (next: string) => {\n if (busy) return\n setBusy(true)\n const { response, payload } = await call({\n path: 'sending-identity',\n method: 'POST',\n body: { hostId, domain: next },\n })\n setBusy(false)\n if (!response.ok) {\n return void notifyRef.current(payload?.error ?? 'Could not change the identity', {\n variant: 'warning',\n })\n }\n await load()\n /*\n * The address the ROUTE settled on, not the one this click asked for.\n *\n * Clearing the selection does not name a domain — it hands the site back\n * to the one provisioned for it — so only the response knows what the\n * `From:` line now says. A message composed from `next` would announce\n * an address chosen here rather than the one recipients will see.\n */\n notifyRef.current(\n payload?.from\n ? `This site now sends as ${payload.from}`\n : 'This site’s sending address was changed',\n { variant: 'success' },\n )\n },\n [busy, call, hostId, load],\n )\n\n const handleRelease = useCallback(async () => {\n if (!identity?.orgId) return\n /*\n * The consequence stated rather than implied, and it is not one\n * consequence — see `describeSendingDomainRemoval`, which holds all three.\n *\n * Taken from the model rather than written here because the LIST asks to\n * remove the same record from its row menu. Two confirmations describing\n * one action differently is how a merchant learns to dismiss the harsher\n * of them.\n */\n const ok = await confirm({\n ...describeSendingDomainRemoval({\n domain,\n selected: identity.selected,\n platformDomain: identity.platformDomain,\n }),\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel, so the\n // resolved value alone can never gate this.\n .then(() => true)\n .catch(() => false)\n if (!ok) return\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'DELETE',\n query: { orgId: identity.orgId, domain },\n })\n if (!response.ok) {\n return void notifyRef.current(payload?.error ?? 'Could not remove the domain', {\n variant: 'warning',\n })\n }\n router.push(`${basePath}/sending`)\n }, [confirm, call, identity, domain, router, basePath])\n\n const state = describeSendingDomain({\n status: record?.status ?? 'requested',\n pendingProvider: record?.pendingProvider ?? record?.status === 'requested',\n issueError: record?.lastIssueError ?? null,\n missing: record?.lastMissing ?? null,\n })\n const missing = new Set(record?.lastMissing ?? [])\n const canManage = identity?.canManage === true\n const isSelected = identity?.selected === domain\n\n const actions: RowActionsMenuItem[] = canManage\n ? [\n {\n key: 'release',\n label: 'Remove domain',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleRelease(),\n },\n ]\n : []\n\n /* The card, named so the page chrome above it is a plain list of\n what this surface publishes upward. */\n const card = (\n <CardDisplay\n header={'Sending domain'}\n help={pluginDocsHelp('emailCampaigns', {\n anchor: '#sending-domains',\n })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: actions.length ? <RowActionsMenu items={actions} /> : null,\n }}\n >\n {loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : !record ? (\n <Alert severity=\"warning\">\n {`${domain} is not claimed by this workspace. It may have been ` +\n `removed — go back and add it again if you still want it.`}\n </Alert>\n ) : (\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip\n label={state.label}\n color={state.color}\n variant={state.sending ? 'filled' : 'outlined'}\n />\n {isSelected ? (\n <Chip label=\"This site sends as this\" color=\"primary\" size=\"small\" />\n ) : null}\n </Stack>\n\n <Alert severity={state.severity}>{state.text}</Alert>\n\n {/*\n The unanswered check, BESIDE the state above and never instead of\n it. Both are true at once: the record still says what it said, and\n the last thing we tried could not be completed.\n */}\n {unreachable ? (\n <Alert severity={INCONCLUSIVE_CHECK.severity} icon={false}>\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {INCONCLUSIVE_CHECK.label}\n </Typography>\n <Typography variant=\"body2\">{INCONCLUSIVE_CHECK.text}</Typography>\n </Alert>\n ) : null}\n\n {/*\n The customer's DMARC policy, read and never written. It decides\n whether unauthenticated mail from this domain is filed as spam or\n refused outright, which is something a person publishing these\n records should know before they finish rather than after.\n */}\n {record.dmarc ? (\n <Alert\n severity={\n record.dmarc.policy === 'reject' && record.status !== 'verified'\n ? 'error'\n : 'info'\n }\n >\n {record.dmarc.consequence}\n </Alert>\n ) : null}\n\n {/*\n SPF, DKIM and DMARC as a receiver reads them, and whether they\n align with the From domain (AGL-3328). The records table below is\n what to publish; this is what the published zone adds up to.\n */}\n {record.readiness ? (\n <SenderReadinessPanel readiness={record.readiness} />\n ) : null}\n {record.records?.length &&\n record.records.some((entry) => entry.value) ? (\n <>\n <Divider />\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {'Publish these records'}\n </Typography>\n <Stack spacing={2}>\n {record.records\n .filter((entry) => entry.value)\n .map((entry) => (\n <RecordRow\n key={`${entry.type}:${entry.name}`}\n record={entry}\n missing={missing.has(`${entry.type}:${entry.name}`)}\n />\n ))}\n {record.dmarcSuggestion &&\n record.dmarc?.policy === 'absent' ? (\n <RecordRow record={record.dmarcSuggestion} missing={false} />\n ) : null}\n </Stack>\n </>\n ) : null}\n\n <Divider />\n <Stack direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', gap: 1 }}>\n {record.status === 'requested' && canManage ? (\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={() => void handleRequestRecords()}\n >\n {busy ? 'Working…' : 'Request records'}\n </Button>\n ) : null}\n {record.status !== 'requested' ? (\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={() => void handleVerify()}\n >\n {busy ? 'Checking…' : 'Check DNS'}\n </Button>\n ) : null}\n {canManage && record.status === 'verified' && !isSelected ? (\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiEmailCheckOutline.path} size={0.8} />}\n disabled={busy}\n onClick={() => void handleUse(domain)}\n >\n {'Send this site’s email as this domain'}\n </Button>\n ) : null}\n {canManage && isSelected ? (\n /*\n * Clearing the selection returns the site to the domain\n * PROVISIONED for it — `{label}.mail.aglyn.app` — not to the\n * shared pool, and the label says so. A site whose plan carries\n * no such domain is refused here with the reason, which is why\n * the control is worded as leaving this domain rather than\n * promising a destination it may not reach.\n */\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void handleUse('platform')}\n >\n {'Stop sending this site’s email as this domain'}\n </Button>\n ) : null}\n </Stack>\n </Stack>\n )}\n </CardDisplay>\n )\n\n return (\n <>\n {/* The page heading and the trail name the domain; this card is\n then free to say what it holds rather than repeating the title. */}\n <PageHeaderRecord title={domain} />\n {card}\n </>\n )\n}\nSendingDomainDetail.displayName = 'SendingDomainDetail'\n\nexport default SendingDomainDetail\n"],"names":["PageHeaderRecord","pluginDocsHelp","mdiDeleteOutline","mdiEmailCheckOutline","CardDisplay","MdiIcon","useConfirmationContext","RowActionsMenu","useSnackbar","Alert","Button","Chip","Divider","Stack","Typography","useRouter","useCallback","useEffect","useRef","useState","describeSendingDomain","describeSendingDomainRemoval","INCONCLUSIVE_CHECK","SenderReadinessPanel","useSendingApi","RecordRow","props","record","missing","target","priority","value","spacing","direction","sx","alignItems","size","label","type","variant","fontWeight","purpose","required","color","component","p","bgcolor","borderRadius","fontFamily","whiteSpace","overflowWrap","name","note","displayName","SendingDomainDetail","hostId","domain","basePath","call","router","enqueueSnackbar","notifyRef","current","confirm","identity","setIdentity","setRecord","loading","setLoading","busy","setBusy","unreachable","setUnreachable","load","view","domains","path","method","query","response","ok","payload","error","orgId","String","found","find","one","handleVerify","body","action","status","verified","handleRequestRecords","handleUse","next","from","handleRelease","selected","platformDomain","confirmationButtonProps","then","catch","push","state","pendingProvider","issueError","lastIssueError","lastMissing","Set","canManage","isSelected","actions","key","icon","destructive","onClick","card","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","length","items","severity","sending","text","dmarc","policy","consequence","readiness","records","some","entry","filter","map","has","dmarcSuggestion","flexWrap","gap","disabled","startIcon","title"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/sending-domain-detail.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { PageHeaderRecord, pluginDocsHelp } from '@aglyn/aglyn'\nimport { mdiDeleteOutline, mdiEmailCheckOutline } from '@aglyn/shared-data-mdi'\nimport { CardDisplay, MdiIcon, useConfirmationContext } from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Button,\n Chip,\n Divider,\n Stack,\n Typography,\n} from '@mui/material'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n describeSendingDomain,\n describeSendingDomainRemoval,\n INCONCLUSIVE_CHECK,\n} from '../model/sending-domain-status'\nimport SenderReadinessPanel from '@aglyn/tenant-feature-instance/components/sender-readiness-panel'\nimport {\n useSendingApi,\n type SendingDnsRecordView,\n type SendingDomainView,\n type SendingIdentityView,\n} from '@aglyn/tenant-feature-instance/hooks/use-sending-identity-api'\n\nexport interface SendingDomainDetailProps {\n hostId: string\n domain: string\n basePath: string\n}\n\n/**\n * The record block, in the shape a registrar's own form asks for. Shared with\n * the organization's page for a domain, which prints the same records.\n */\nexport function RecordRow(props: {\n record: SendingDnsRecordView\n missing: boolean\n}) {\n const { record, missing } = props\n const target = record.priority\n ? `${record.priority} ${record.value}`\n : record.value\n return (\n <Stack spacing={0.5}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip size=\"small\" label={record.type} variant=\"outlined\" />\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {record.purpose === 'spf'\n ? 'Authorization'\n : record.purpose === 'dkim'\n ? 'Signing key'\n : record.purpose === 'return-path'\n ? 'Bounce routing'\n : 'DMARC policy'}\n </Typography>\n {record.required ? null : (\n <Chip size=\"small\" label=\"Optional\" variant=\"outlined\" />\n )}\n {missing ? <Chip size=\"small\" label=\"Not seen yet\" color=\"warning\" /> : null}\n </Stack>\n <Typography\n variant=\"body2\"\n component=\"code\"\n sx={{\n p: 1,\n bgcolor: 'action.hover',\n borderRadius: 1,\n fontFamily: 'monospace',\n whiteSpace: 'pre-wrap',\n overflowWrap: 'anywhere',\n }}\n >\n {`${record.type} ${record.name} → ${target || '(not issued yet)'}`}\n </Typography>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {record.note}\n </Typography>\n </Stack>\n )\n}\nRecordRow.displayName = 'RecordRow'\n\n/**\n * ONE SENDING DOMAIN: what to publish, whether we can see it, and whether\n * this site sends as it.\n *\n * ## `inconclusive` is not a failure, and this page is where that is felt\n *\n * Pressing Check DNS has three outcomes, not two. The third is that nobody\n * answered — a resolver outage, a timeout — and the route says so with a\n * `503` rather than with `verified: false`. It is held HERE, in the surface's\n * own state, next to a status the record still holds, because the record was\n * not changed and saying otherwise would send a customer whose DNS is perfect\n * to go and edit a zone that has nothing wrong with it.\n *\n * That is why `lastCheck` is a separate piece of state from `record.status`\n * and never assigned into it.\n */\nexport function SendingDomainDetail(props: SendingDomainDetailProps) {\n const { hostId, domain, basePath } = props\n const call = useSendingApi()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n /*\n * The snackbar held in a REF, for the reason `use-campaign-send-api` holds\n * the user in one: `load` is a dependency of the effect that runs it, so\n * anything whose identity changes per render re-runs the fetch — and a\n * hook returning a fresh object each render turns that into a loop that\n * never settles. Reading it through a ref makes the effect depend on the\n * things that actually identify the request.\n */\n const notifyRef = useRef(enqueueSnackbar)\n notifyRef.current = enqueueSnackbar\n const { confirm } = useConfirmationContext()\n\n const [identity, setIdentity] = useState<SendingIdentityView | null>(null)\n const [record, setRecord] = useState<SendingDomainView | null>(null)\n const [loading, setLoading] = useState(true)\n const [busy, setBusy] = useState(false)\n /**\n * The outcome of the last check the reader ran, and ONLY when it was\n * inconclusive. Never merged into the record: an unanswered lookup is\n * evidence of nothing, and the record's own status is still the last thing\n * we actually established.\n */\n const [unreachable, setUnreachable] = useState(false)\n\n const load = useCallback(async () => {\n setLoading(true)\n const view = await call({\n path: 'sending-identity',\n method: 'GET',\n query: { hostId },\n })\n if (!view.response.ok) {\n setLoading(false)\n return void notifyRef.current(\n view.payload?.error ?? 'Could not read the sending identity',\n { variant: 'warning' },\n )\n }\n setIdentity(view.payload as SendingIdentityView)\n const orgId = String(view.payload?.orgId ?? '')\n const domains = orgId\n ? await call({\n path: 'sending-domains',\n method: 'GET',\n query: { orgId },\n })\n : null\n setLoading(false)\n const found = (domains?.payload?.domains ?? []).find(\n (one: SendingDomainView) => one.domain === domain,\n )\n setRecord(found ?? null)\n }, [call, hostId, domain])\n\n useEffect(() => {\n void load()\n }, [load])\n\n const handleVerify = useCallback(async () => {\n if (busy || !identity?.orgId) return\n setBusy(true)\n setUnreachable(false)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'POST',\n body: { orgId: identity.orgId, domain, action: 'verify' },\n })\n setBusy(false)\n /*\n * THE THIRD OUTCOME.\n *\n * A `503` here means the lookup got no answer. Nothing about the domain\n * changed, so nothing on screen changes either except a notice saying the\n * question could not be asked. Treating it as a failed check — which is\n * what a plain `!response.ok` branch would do — is how a customer ends up\n * rewriting records that were already correct.\n */\n if (response.status === 503) {\n setUnreachable(true)\n return\n }\n if (!response.ok) {\n return void notifyRef.current(payload?.error ?? 'The check could not run', {\n variant: 'warning',\n })\n }\n await load()\n notifyRef.current(\n payload?.verified\n ? 'Verified — this domain can send'\n : 'Checked. Some records are still missing.',\n { variant: payload?.verified ? 'success' : 'info' },\n )\n }, [busy, call, identity?.orgId, domain, load])\n\n const handleRequestRecords = useCallback(async () => {\n if (busy || !identity?.orgId) return\n setBusy(true)\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'POST',\n body: { orgId: identity.orgId, domain, action: 'request' },\n })\n setBusy(false)\n if (!response.ok) {\n return void notifyRef.current(payload?.error ?? 'Could not request records', {\n variant: 'warning',\n })\n }\n await load()\n }, [busy, call, identity?.orgId, domain, load])\n\n const handleUse = useCallback(\n async (next: string) => {\n if (busy) return\n setBusy(true)\n const { response, payload } = await call({\n path: 'sending-identity',\n method: 'POST',\n body: { hostId, domain: next },\n })\n setBusy(false)\n if (!response.ok) {\n return void notifyRef.current(payload?.error ?? 'Could not change the identity', {\n variant: 'warning',\n })\n }\n await load()\n /*\n * The address the ROUTE settled on, not the one this click asked for.\n *\n * Clearing the selection does not name a domain — it hands the site back\n * to the one provisioned for it — so only the response knows what the\n * `From:` line now says. A message composed from `next` would announce\n * an address chosen here rather than the one recipients will see.\n */\n notifyRef.current(\n payload?.from\n ? `This site now sends as ${payload.from}`\n : 'This site’s sending address was changed',\n { variant: 'success' },\n )\n },\n [busy, call, hostId, load],\n )\n\n const handleRelease = useCallback(async () => {\n if (!identity?.orgId) return\n /*\n * The consequence stated rather than implied, and it is not one\n * consequence — see `describeSendingDomainRemoval`, which holds all three.\n *\n * Taken from the model rather than written here because the LIST asks to\n * remove the same record from its row menu. Two confirmations describing\n * one action differently is how a merchant learns to dismiss the harsher\n * of them.\n */\n const ok = await confirm({\n ...describeSendingDomainRemoval({\n domain,\n selected: identity.selected,\n platformDomain: identity.platformDomain,\n }),\n confirmationButtonProps: { color: 'error' },\n })\n // `confirm` resolves with no value and REJECTS on cancel, so the\n // resolved value alone can never gate this.\n .then(() => true)\n .catch(() => false)\n if (!ok) return\n const { response, payload } = await call({\n path: 'sending-domains',\n method: 'DELETE',\n query: { orgId: identity.orgId, domain },\n })\n if (!response.ok) {\n return void notifyRef.current(payload?.error ?? 'Could not remove the domain', {\n variant: 'warning',\n })\n }\n router.push(`${basePath}/sending`)\n }, [confirm, call, identity, domain, router, basePath])\n\n const state = describeSendingDomain({\n status: record?.status ?? 'requested',\n pendingProvider: record?.pendingProvider ?? record?.status === 'requested',\n issueError: record?.lastIssueError ?? null,\n missing: record?.lastMissing ?? null,\n })\n const missing = new Set(record?.lastMissing ?? [])\n const canManage = identity?.canManage === true\n const isSelected = identity?.selected === domain\n\n const actions: RowActionsMenuItem[] = canManage\n ? [\n {\n key: 'release',\n label: 'Remove domain',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleRelease(),\n },\n ]\n : []\n\n /* The card, named so the page chrome above it is a plain list of\n what this surface publishes upward. */\n const card = (\n <CardDisplay\n header={'Sending domain'}\n help={pluginDocsHelp('emailCampaigns', {\n anchor: '#sending-domains',\n })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: actions.length ? <RowActionsMenu items={actions} /> : null,\n }}\n >\n {loading ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Loading…'}\n </Typography>\n ) : !record ? (\n <Alert severity=\"warning\">\n {`${domain} is not claimed by this workspace. It may have been ` +\n `removed — go back and add it again if you still want it.`}\n </Alert>\n ) : (\n <Stack spacing={2}>\n <Stack direction=\"row\" spacing={1} sx={{ alignItems: 'center' }}>\n <Chip\n label={state.label}\n color={state.color}\n variant={state.sending ? 'filled' : 'outlined'}\n />\n {isSelected ? (\n <Chip label=\"This site sends as this\" color=\"primary\" size=\"small\" />\n ) : null}\n </Stack>\n\n <Alert severity={state.severity}>{state.text}</Alert>\n\n {/*\n The unanswered check, BESIDE the state above and never instead of\n it. Both are true at once: the record still says what it said, and\n the last thing we tried could not be completed.\n */}\n {unreachable ? (\n <Alert severity={INCONCLUSIVE_CHECK.severity} icon={false}>\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {INCONCLUSIVE_CHECK.label}\n </Typography>\n <Typography variant=\"body2\">{INCONCLUSIVE_CHECK.text}</Typography>\n </Alert>\n ) : null}\n\n {/*\n The customer's DMARC policy, read and never written. It decides\n whether unauthenticated mail from this domain is filed as spam or\n refused outright, which is something a person publishing these\n records should know before they finish rather than after.\n */}\n {record.dmarc ? (\n <Alert\n severity={\n record.dmarc.policy === 'reject' && record.status !== 'verified'\n ? 'error'\n : 'info'\n }\n >\n {record.dmarc.consequence}\n </Alert>\n ) : null}\n\n {/*\n SPF, DKIM and DMARC as a receiver reads them, and whether they\n align with the From domain (AGL-3328). The records table below is\n what to publish; this is what the published zone adds up to.\n */}\n {record.readiness ? (\n <SenderReadinessPanel readiness={record.readiness} />\n ) : null}\n {record.records?.length &&\n record.records.some((entry) => entry.value) ? (\n <>\n <Divider />\n <Typography variant=\"body2\" sx={{ fontWeight: 'bold' }}>\n {'Publish these records'}\n </Typography>\n <Stack spacing={2}>\n {record.records\n .filter((entry) => entry.value)\n .map((entry) => (\n <RecordRow\n key={`${entry.type}:${entry.name}`}\n record={entry}\n missing={missing.has(`${entry.type}:${entry.name}`)}\n />\n ))}\n {record.dmarcSuggestion &&\n record.dmarc?.policy === 'absent' ? (\n <RecordRow record={record.dmarcSuggestion} missing={false} />\n ) : null}\n </Stack>\n </>\n ) : null}\n\n <Divider />\n <Stack useFlexGap direction=\"row\" spacing={1} sx={{ flexWrap: 'wrap', gap: 1 }}>\n {record.status === 'requested' && canManage ? (\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={() => void handleRequestRecords()}\n >\n {busy ? 'Working…' : 'Request records'}\n </Button>\n ) : null}\n {record.status !== 'requested' ? (\n <Button\n variant=\"contained\"\n disabled={busy}\n onClick={() => void handleVerify()}\n >\n {busy ? 'Checking…' : 'Check DNS'}\n </Button>\n ) : null}\n {canManage && record.status === 'verified' && !isSelected ? (\n <Button\n variant=\"outlined\"\n startIcon={<MdiIcon path={mdiEmailCheckOutline.path} size={0.8} />}\n disabled={busy}\n onClick={() => void handleUse(domain)}\n >\n {'Send this site’s email as this domain'}\n </Button>\n ) : null}\n {canManage && isSelected ? (\n /*\n * Clearing the selection returns the site to the domain\n * PROVISIONED for it — `{label}.mail.aglyn.app` — not to the\n * shared pool, and the label says so. A site whose plan carries\n * no such domain is refused here with the reason, which is why\n * the control is worded as leaving this domain rather than\n * promising a destination it may not reach.\n */\n <Button\n variant=\"outlined\"\n disabled={busy}\n onClick={() => void handleUse('platform')}\n >\n {'Stop sending this site’s email as this domain'}\n </Button>\n ) : null}\n </Stack>\n </Stack>\n )}\n </CardDisplay>\n )\n\n return (\n <>\n {/* The page heading and the trail name the domain; this card is\n then free to say what it holds rather than repeating the title. */}\n <PageHeaderRecord title={domain} />\n {card}\n </>\n )\n}\nSendingDomainDetail.displayName = 'SendingDomainDetail'\n\nexport default SendingDomainDetail\n"],"names":["PageHeaderRecord","pluginDocsHelp","mdiDeleteOutline","mdiEmailCheckOutline","CardDisplay","MdiIcon","useConfirmationContext","RowActionsMenu","useSnackbar","Alert","Button","Chip","Divider","Stack","Typography","useRouter","useCallback","useEffect","useRef","useState","describeSendingDomain","describeSendingDomainRemoval","INCONCLUSIVE_CHECK","SenderReadinessPanel","useSendingApi","RecordRow","props","record","missing","target","priority","value","spacing","direction","sx","alignItems","size","label","type","variant","fontWeight","purpose","required","color","component","p","bgcolor","borderRadius","fontFamily","whiteSpace","overflowWrap","name","note","displayName","SendingDomainDetail","hostId","domain","basePath","call","router","enqueueSnackbar","notifyRef","current","confirm","identity","setIdentity","setRecord","loading","setLoading","busy","setBusy","unreachable","setUnreachable","load","view","domains","path","method","query","response","ok","payload","error","orgId","String","found","find","one","handleVerify","body","action","status","verified","handleRequestRecords","handleUse","next","from","handleRelease","selected","platformDomain","confirmationButtonProps","then","catch","push","state","pendingProvider","issueError","lastIssueError","lastMissing","Set","canManage","isSelected","actions","key","icon","destructive","onClick","card","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","length","items","severity","sending","text","dmarc","policy","consequence","readiness","records","some","entry","filter","map","has","dmarcSuggestion","useFlexGap","flexWrap","gap","disabled","startIcon","title"],"mappings":"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"}
@@ -551,6 +551,7 @@ const SUPPRESSION_SELECT_FIELDS = [
551
551
  }) : /*#__PURE__*/ _jsxs(_Fragment, {
552
552
  children: [
553
553
  /*#__PURE__*/ _jsx(Stack, {
554
+ useFlexGap: true,
554
555
  direction: "row",
555
556
  spacing: 1,
556
557
  sx: {