@aglyn/plugins-email 1.0.0-beta.231 → 1.0.0-beta.233
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +16 -16
- package/src/lib/components/consent-group-change-progress.js +4 -0
- package/src/lib/components/consent-group-change-progress.js.map +1 -1
- package/src/lib/components/consent-group-site-picker.js +1 -0
- package/src/lib/components/consent-group-site-picker.js.map +1 -1
- package/src/lib/components/email-screens-card.js +33 -6
- package/src/lib/components/email-screens-card.js.map +1 -1
- package/src/lib/components/email-template-detail.js +3 -1
- package/src/lib/components/email-template-detail.js.map +1 -1
- package/src/lib/components/email-topics-card.js +1 -0
- package/src/lib/components/email-topics-card.js.map +1 -1
- package/src/lib/components/list-detail-card.js +4 -0
- package/src/lib/components/list-detail-card.js.map +1 -1
- package/src/lib/components/list-members-panel.js +38 -7
- package/src/lib/components/list-members-panel.js.map +1 -1
- package/src/lib/components/lists-card.js +35 -7
- package/src/lib/components/lists-card.js.map +1 -1
- package/src/lib/components/org-email-templates-card.js +34 -7
- package/src/lib/components/org-email-templates-card.js.map +1 -1
- package/src/lib/components/org-sending-domain-detail.js +1 -0
- package/src/lib/components/org-sending-domain-detail.js.map +1 -1
- package/src/lib/components/sending-domain-detail.js +1 -0
- package/src/lib/components/sending-domain-detail.js.map +1 -1
- package/src/lib/components/suppressions-card.js +25 -6
- package/src/lib/components/suppressions-card.js.map +1 -1
- package/src/lib/constants/list-queries.js +73 -9
- package/src/lib/constants/list-queries.js.map +1 -1
- package/src/lib/model/email-design-document.d.ts +1 -1
- package/src/lib/model/email-design-document.js +1 -1
- package/src/lib/model/email-design-document.js.map +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/org-email-templates-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { buildRoute, pluginDocsHelp, Route } from '@aglyn/aglyn'\nimport {\n mdiDeleteOutline,\n mdiEyeOutline,\n mdiPencilOutline,\n} from '@aglyn/shared-data-mdi'\nimport {\n AppLink,\n CardDisplay,\n MdiIcon,\n useConfirmationContext,\n} from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport {\n type ListFilterClause,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport {\n type ListQueryPlan,\n planListQuery,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport { nameSearchNormalizers } from '@aglyn/aglyn/app-utils/name-search'\nimport {\n TABLE_PAGE_SIZE_DEFAULT,\n TABLE_ROW_HEIGHT,\n} from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { ceilingedWindow } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { listQueryConstraints } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostResourceApi,\n useHostVersionApi,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection, doc, limit, query, updateDoc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n EMAIL_TEMPLATE_BASE,\n EMAIL_TEMPLATE_FILTER_HEADERS,\n EMAIL_TEMPLATE_QUERY,\n} from '../constants/list-queries'\nimport { templateProvenance } from '../model/template-provenance'\nimport { createEmailScreen } from '../utils/create-email-screen'\nimport { emailTemplateSoftDelete } from '../utils/email-template-soft-delete'\nimport {\n OrgSiteSelect,\n orgSiteEmailsPath,\n orgSiteName,\n orgSitePageLabel,\n useEmailOrgMount,\n useOrgSitePage,\n type EmailOrgMount,\n} from './email-org-mount'\n\n/**\n * How many of one site's templates the organization's list reads.\n *\n * Lower than the site's own list, because this one reads a page of sites at\n * once: a site with more than this is named under the table, with a link to\n * its own Templates page, which reads its whole set.\n */\nexport const ORG_TEMPLATE_CEILING = 50\n\n/*\n * What the organization's templates grid's Filters panel offers (AGL-3321).\n *\n * The template clauses and the search are the site list's own\n * (`EMAIL_TEMPLATE_QUERY`): ONE plan, put on EVERY site's query, so each\n * site's read is already the answer and nothing is matched in the browser.\n * Site is not a field of a screen but a choice of WHICH sites' queries run:\n * a Site clause reads exactly the sites it names, whichever page of sites\n * they are on. Origin (yours or installed) is not offered, for the reason the\n * declaration gives: a query cannot ask for a field to be absent.\n */\nconst SITE_FIELD: ListFilterField = {\n column: 'site',\n kind: 'exact',\n path: 'hostId',\n operators: ['equals', 'isAnyOf'],\n}\nconst ORG_TEMPLATE_FILTER_FIELDS: readonly ListFilterField[] = [\n ...EMAIL_TEMPLATE_QUERY.fields,\n SITE_FIELD,\n]\nconst ORG_TEMPLATE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n ...EMAIL_TEMPLATE_FILTER_HEADERS,\n site: 'Site',\n}\n\n/** The sites a Site clause names, or null when none is in force. */\nexport function orgTemplateSiteScope(\n clauses: readonly ListFilterClause[],\n): string[] | null {\n const named = clauses\n .filter((clause) => clause.field === SITE_FIELD.column)\n .map((clause) =>\n clause.value\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean),\n )\n if (!named.length) return null\n // Several Site clauses all hold: a site must be named by each.\n return named.reduce((kept, next) => kept.filter((id) => next.includes(id)))\n}\n\n/** One site's read, as the table assembles it. */\ninterface SiteRead {\n rows: Array<Record<string, any>>\n /** The site holds more templates than {@link ORG_TEMPLATE_CEILING}. */\n truncated: boolean\n /** The read has answered — rows or none. */\n settled: boolean\n}\n\n/**\n * ONE SITE'S EMAIL TEMPLATES, read while the organization's list is on screen.\n *\n * A component rather than a hook in a loop, because the number of sites\n * changes and a hook's call count may not: each mounted reader is one\n * listener, and a page of sites mounts one per site on it.\n *\n * Every predicate is on the QUERY — the email scope, every clause and the\n * search word, one plan for all the sites (AGL-3321) — so a site's read is\n * its first templates THAT MATCH, by name, and a site with a thousand pages\n * reads its matching emails and nothing else. It reads one past the ceiling,\n * so the table can say when a site holds more matches than it listed.\n */\nfunction SiteTemplatesReader(props: {\n hostId: string\n plan: ListQueryPlan\n onRead: (hostId: string, read: SiteRead) => void\n}) {\n const { hostId, plan, onRead } = props\n const firestore = useFirestore()\n const planKey = JSON.stringify({ filters: plan.filters, orderBy: plan.orderBy })\n const { data, status } = useFirestoreCollection<Record<string, any>>(\n () =>\n query(\n collection(firestore, 'hosts', hostId, 'screens'),\n ...listQueryConstraints(plan),\n limit(ORG_TEMPLATE_CEILING + 1),\n ),\n [firestore, hostId, planKey],\n { idField: '$id' },\n )\n // One window per snapshot, so a re-render that brings no new snapshot hands\n // the table the same rows it already holds.\n const window = useMemo(\n () => ceilingedWindow(data, ORG_TEMPLATE_CEILING),\n [data],\n )\n useEffect(() => {\n onRead(hostId, {\n rows: window.rows,\n truncated: window.truncated,\n settled: status !== 'loading',\n })\n }, [window, status, hostId, onRead])\n return null\n}\nSiteTemplatesReader.displayName = 'SiteTemplatesReader'\n\n/**\n * THE ORGANIZATION'S TEMPLATES: every site's reusable email designs, in one\n * table.\n *\n * A template is a screen on ONE site — its besigner, its assets and the\n * messages built from it are that site's — so this table reads each site in\n * turn and names the site on every row. A row opens the template's page on\n * its own site, where its report and its recipients are; nothing here is an\n * organization-level template page, because there is no such thing.\n *\n * The reads are one listener per site, over one page of sites at a time —\n * ordered by name — each its site's query for the clauses and search in\n * force, capped at {@link ORG_TEMPLATE_CEILING} matches. The shared footer\n * pages the matches in hand; an organization with more sites than one page\n * chooses which page of sites is read with the Sites picker above the\n * table, or names sites with the Site filter. Both caps say so on screen when\n * they bite, so a missing template is never mistaken for one that does not\n * exist.\n *\n * \"New template\" asks which site the design is made on — the besigner opens\n * on a site — and skips the question when the org has one site.\n */\nexport function OrgEmailTemplatesCard() {\n const mount = useEmailOrgMount()\n return mount ? <OrgEmailTemplatesTable mount={mount} /> : null\n}\nOrgEmailTemplatesCard.displayName = 'OrgEmailTemplatesCard'\n\nfunction OrgEmailTemplatesTable(props: { mount: EmailOrgMount }) {\n const { mount } = props\n const firestore = useFirestore()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const createHostResource = useHostResourceApi()\n const createHostVersion = useHostVersionApi()\n const sitePage = useOrgSitePage(mount)\n const gridFilter = useListGridFilter({ selectFields: [SITE_FIELD.column] })\n /*\n * Which sites are read: the ones a Site clause names, else the page of\n * sites. The other clauses and the search are ONE plan every site's query\n * carries.\n */\n const siteScope = useMemo(\n () => orgTemplateSiteScope(gridFilter.clauses),\n [gridFilter.clauses],\n )\n const sites = useMemo(\n () =>\n siteScope\n ? mount.hosts.filter((host) => siteScope.includes(host.id))\n : sitePage.sites,\n [siteScope, mount.hosts, sitePage.sites],\n )\n const plan = useMemo(\n () =>\n planListQuery(\n EMAIL_TEMPLATE_QUERY,\n {\n clauses: gridFilter.clauses.filter(\n (clause) => clause.field !== SITE_FIELD.column,\n ),\n search: gridFilter.searchWords,\n base: EMAIL_TEMPLATE_BASE,\n },\n nameSearchNormalizers,\n ),\n [gridFilter.clauses, gridFilter.searchWords],\n )\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n const refusals = useMemo(\n () =>\n listQueryRefusals(plan.refused, {\n fields: ORG_TEMPLATE_FILTER_FIELDS,\n headers: ORG_TEMPLATE_FILTER_HEADERS,\n }),\n [plan.refused],\n )\n\n const [reads, setReads] = useState<Record<string, SiteRead>>({})\n const onRead = useCallback(\n (hostId: string, read: SiteRead) =>\n setReads((current) => {\n const previous = current[hostId]\n // An answer the table already holds changes nothing to draw.\n if (\n previous &&\n previous.rows === read.rows &&\n previous.truncated === read.truncated &&\n previous.settled === read.settled\n ) {\n return current\n }\n return { ...current, [hostId]: read }\n }),\n [],\n )\n\n /*\n * One row per template, named by site. The row id joins the site to the\n * screen id: two sites can hold a screen with the same id, and a grid that\n * keyed on the screen alone would draw one of them twice. Every row is an\n * answer some site's query gave; the sort only interleaves the sites'\n * answers by the name each was already ordered by.\n */\n const rows = useMemo(\n () =>\n sites\n .flatMap((site) =>\n (reads[site.id]?.rows ?? []).map((screen) => ({\n ...screen,\n $id: `${site.id}:${screen['$id']}`,\n screenId: String(screen['$id']),\n hostId: site.id,\n siteName: orgSiteName(mount, site.id),\n })),\n )\n .sort(\n (a, b) =>\n String(a['nameLower'] ?? '').localeCompare(String(b['nameLower'] ?? '')) ||\n orgSiteName(mount, a.hostId).localeCompare(\n orgSiteName(mount, b.hostId),\n ),\n ),\n [sites, reads, mount],\n )\n const settled = sites.every((site) => reads[site.id]?.settled)\n const crowded = sites.filter((site) => reads[site.id]?.truncated)\n\n /*\n * The matches in hand, a page at a time on the shared footer. Choosing\n * another page of sites, or another question, starts from the first page:\n * page four of one answer is not a position in another.\n */\n const filterOptions = useMemo(\n () => ({\n // Every site the organization has: a Site clause reads the sites it\n // names wherever they sit in the page of sites.\n site: mount.hosts.map((site) => ({ value: site.id, label: orgSiteName(mount, site.id) })),\n }),\n [mount],\n )\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const planKey = JSON.stringify({ filters: plan.filters, orderBy: plan.orderBy })\n const siteKey = sites.map((site) => site.id).join(',')\n // A new answer starts again at its first page.\n useEffect(() => setPage(0), [siteKey, planKey])\n const shown = useMemo(\n () => rows.slice(page * pageSize, page * pageSize + pageSize),\n [rows, page, pageSize],\n )\n const sitePageCount = Math.ceil(sitePage.count / sitePage.pageSize)\n\n const templateName = (row: Record<string, any>) =>\n String(row['displayName'] ?? 'Untitled template')\n /** The template's own page, on the site that holds it. */\n const templateHref = (row: Record<string, any>) =>\n orgSiteEmailsPath(mount, row['hostId'], `templates/${row['screenId']}`)\n const siteSubdomain = (hostId: string) =>\n mount.hosts.find((site) => site.id === hostId)?.subdomain ?? null\n const besignerHref = (\n hostId: string,\n screenId: string,\n versionId: unknown,\n ) => {\n const subdomain = siteSubdomain(hostId)\n return subdomain && versionId\n ? buildRoute(Route.SCREEN_BESIGNER, {\n orgSlug: mount.orgSlug,\n host: subdomain,\n screenId,\n versionId: String(versionId),\n })\n : null\n }\n\n const handleDelete = async (row: Record<string, any>) => {\n const confirmed = await confirm({\n title: 'Delete this template?',\n description:\n `\"${templateName(row)}\" will be removed from ` +\n `${orgSiteName(mount, row['hostId'])}. Emails already sent from it ` +\n 'keep their reports.',\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await updateDoc(\n doc(firestore, 'hosts', row['hostId'], 'screens', row['screenId']),\n emailTemplateSoftDelete(),\n )\n }\n\n const rowActions = (row: Record<string, any>): RowActionsMenuItem[] => {\n const page = templateHref(row)\n const editor = besignerHref(\n row['hostId'],\n row['screenId'],\n row['versionId'],\n )\n const unlinkedReason =\n 'This site has no console address yet, so it cannot be linked to.'\n return [\n {\n key: 'details',\n label: 'Open details',\n icon: <MdiIcon path={mdiEyeOutline.path} size={0.8} />,\n href: page ?? undefined,\n disabled: !page,\n disabledReason: unlinkedReason,\n },\n {\n key: 'besigner',\n label: 'Edit in besigner',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n href: editor ?? undefined,\n disabled: !editor,\n disabledReason: row['versionId']\n ? unlinkedReason\n : 'This template has never been saved in the besigner. Open it from its page.',\n },\n {\n key: 'delete',\n label: 'Delete',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleDelete(row),\n },\n ]\n }\n\n const columns: GridColDef[] = [\n {\n field: 'displayName',\n headerName: 'Template',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) => templateName(row),\n renderCell: ({ row, value }) => {\n const href = templateHref(row)\n return href ? (\n <AppLink\n href={href}\n // The row's own handler would fire too and push the same route\n // twice — one history entry per back press.\n onClick={(event: { stopPropagation: () => void }) =>\n event.stopPropagation()\n }\n >\n {value}\n </AppLink>\n ) : (\n value\n )\n },\n },\n {\n field: 'site',\n headerName: 'Site',\n width: 170,\n valueGetter: (_value, row) => row.hostId,\n renderCell: ({ row }) => row.siteName,\n },\n {\n // Whose template this is, as on a site's own list.\n field: 'origin',\n headerName: 'Origin',\n width: 130,\n valueGetter: (_value, row) =>\n templateProvenance(row).origin === 'installed' ? 'installed' : 'local',\n renderCell: ({ value }) =>\n value === 'installed' ? (\n <Chip size=\"small\" label=\"Installed\" />\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Yours'}\n </Typography>\n ),\n },\n listActionsColumn(\n (row) => (\n <ListRowActions label={templateName(row)} items={rowActions(row)} />\n ),\n { width: 72 },\n ),\n ]\n\n /*==========================================\n * CREATE, ON A SITE.\n *\n * The same document a site's own list creates — `createEmailScreen`\n * through the quota-enforcing resources route — made as the chosen site,\n * then straight into that site's besigner. One site answers the question\n * itself; otherwise the dialog asks, defaulting to the session's pick.\n *=========================================*/\n const [choosing, setChoosing] = useState(false)\n const [createHostId, setCreateHostId] = useState('')\n const [creating, setCreating] = useState(false)\n\n const create = async (hostId: string) => {\n if (creating || !hostId) return\n setCreating(true)\n try {\n const { screenId, versionId } = await createEmailScreen(\n hostId,\n createHostResource,\n createHostVersion,\n )\n setChoosing(false)\n const editor = besignerHref(hostId, screenId, versionId)\n if (editor) {\n void router.push(editor)\n } else {\n enqueueSnackbar(\n `Template created on ${orgSiteName(mount, hostId)}. Open it from ` +\n 'that site’s Emails page.',\n { variant: 'success' },\n )\n }\n } catch (error: any) {\n console.error(error)\n enqueueSnackbar(error?.message ?? 'Creating the template failed', {\n variant: 'error',\n })\n } finally {\n setCreating(false)\n }\n }\n\n const handleNew = () => {\n if (mount.hosts.length === 1) return void create(mount.hosts[0].id)\n setCreateHostId(mount.pickedHostId ?? '')\n setChoosing(true)\n }\n\n const noSites = mount.hostsReady && mount.hosts.length === 0\n\n return (\n <CardDisplay\n header={'Templates'}\n help={pluginDocsHelp('designedEmails', { anchor: '#find-a-template' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={noSites || creating}\n onClick={handleNew}\n >\n {creating ? 'Creating…' : 'New template'}\n </Button>\n ),\n }}\n >\n {sites.map((site) => (\n <SiteTemplatesReader\n key={site.id}\n hostId={site.id}\n plan={plan}\n onRead={onRead}\n />\n ))}\n <Stack spacing={1.5}>\n {/*\n WHICH SITES' TEMPLATES ARE LISTED, when there are more sites than\n one page holds. Only the chosen page's sites are read; a template on\n a site of another page is one choice away, not missing. It picks\n what is read, a scope, so it is not one of the grid's filters.\n */}\n {sitePageCount > 1 && !siteScope ? (\n <TextField\n select\n size=\"small\"\n label=\"Sites\"\n value={sitePage.page}\n onChange={(event) => sitePage.setPage(Number(event.target.value))}\n sx={{ maxWidth: 260 }}\n >\n {Array.from({ length: sitePageCount }, (_, index) => (\n <MenuItem key={index} value={index}>\n {orgSitePageLabel({\n from: index * sitePage.pageSize + 1,\n to: Math.min((index + 1) * sitePage.pageSize, sitePage.count),\n count: sitePage.count,\n })}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {noSites ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet. A template is designed on ' +\n 'a site, in its besigner, so it appears here once there is one.'}\n </Typography>\n ) : rows.length === 0 && !filtering ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {settled\n ? 'None of these sites has an email template yet. Design one ' +\n 'here, then send it from a campaign — a new template opens in ' +\n 'the besigner with email-safe components only.'\n : 'Loading templates…'}\n </Typography>\n ) : (\n <>\n <ListFilterChips\n fields={ORG_TEMPLATE_FILTER_FIELDS}\n headers={ORG_TEMPLATE_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n <ListQueryNotices refused={refusals} notices={plan.notices} />\n <ListTable\n aria-label=\"Email templates\"\n rows={shown}\n columns={listFilterGridColumns(\n columns,\n ORG_TEMPLATE_FILTER_FIELDS,\n filterOptions,\n ORG_TEMPLATE_FILTER_HEADERS,\n )}\n rowHeight={TABLE_ROW_HEIGHT}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n // The panel and the search go to every site's query; the grid\n // neither filters nor sorts the rows it holds.\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n disableColumnSorting\n noRowsLabel=\"No templates match these filters\"\n onOpen={(_id, row) => {\n const href = templateHref(row)\n if (href) router.push(href)\n }}\n />\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={shown.length}\n count={rows.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </>\n )}\n {crowded.map((site) => {\n const href = orgSiteEmailsPath(mount, site.id, 'templates')\n return (\n <Alert key={site.id} severity=\"info\">\n {`${orgSiteName(mount, site.id)} has more than ` +\n `${ORG_TEMPLATE_CEILING} ${filtering ? 'matching templates' : 'templates'}, ` +\n 'and only the first of them are listed here. '}\n {href ? (\n <AppLink href={href}>{'See all of its templates'}</AppLink>\n ) : null}\n </Alert>\n )\n })}\n </Stack>\n\n <Dialog\n open={choosing}\n onClose={creating ? undefined : () => setChoosing(false)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>{'New template'}</DialogTitle>\n <DialogContent>\n <DialogContentText sx={{ mb: 2 }}>\n {'A template is designed on one site, in its besigner, and is ' +\n 'sent from that site’s campaigns.'}\n </DialogContentText>\n <OrgSiteSelect\n mount={mount}\n label=\"Site\"\n value={createHostId}\n onChange={setCreateHostId}\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setChoosing(false)} disabled={creating}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={!createHostId || creating}\n onClick={() => void create(createHostId)}\n >\n {creating ? 'Creating…' : 'Create and open'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nOrgEmailTemplatesTable.displayName = 'OrgEmailTemplatesTable'\n\nexport default OrgEmailTemplatesCard\n"],"names":["buildRoute","pluginDocsHelp","Route","mdiDeleteOutline","mdiEyeOutline","mdiPencilOutline","AppLink","CardDisplay","MdiIcon","useConfirmationContext","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListRowActions","ListTable","listActionsColumn","listFilterGridColumns","planListQuery","useListGridFilter","nameSearchNormalizers","TABLE_PAGE_SIZE_DEFAULT","TABLE_ROW_HEIGHT","useSnackbar","ceilingedWindow","listQueryConstraints","useFirestore","useFirestoreCollection","useHostResourceApi","useHostVersionApi","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","MenuItem","Stack","TextField","Typography","collection","doc","limit","query","updateDoc","useRouter","useCallback","useEffect","useMemo","useState","EMAIL_TEMPLATE_BASE","EMAIL_TEMPLATE_FILTER_HEADERS","EMAIL_TEMPLATE_QUERY","templateProvenance","createEmailScreen","emailTemplateSoftDelete","OrgSiteSelect","orgSiteEmailsPath","orgSiteName","orgSitePageLabel","useEmailOrgMount","useOrgSitePage","ORG_TEMPLATE_CEILING","SITE_FIELD","column","kind","path","operators","ORG_TEMPLATE_FILTER_FIELDS","fields","ORG_TEMPLATE_FILTER_HEADERS","site","orgTemplateSiteScope","clauses","named","filter","clause","field","map","value","split","entry","trim","Boolean","length","reduce","kept","next","id","includes","SiteTemplatesReader","props","hostId","plan","onRead","firestore","planKey","JSON","stringify","filters","orderBy","data","status","idField","window","rows","truncated","settled","displayName","OrgEmailTemplatesCard","mount","OrgEmailTemplatesTable","router","enqueueSnackbar","confirm","createHostResource","createHostVersion","sitePage","gridFilter","selectFields","siteScope","sites","hosts","host","search","searchWords","base","filtering","some","word","refusals","refused","headers","reads","setReads","read","current","previous","flatMap","screen","$id","screenId","String","siteName","sort","a","b","localeCompare","every","crowded","filterOptions","label","page","setPage","pageSize","setPageSize","siteKey","join","shown","slice","sitePageCount","Math","ceil","count","templateName","row","templateHref","siteSubdomain","find","subdomain","besignerHref","versionId","SCREEN_BESIGNER","orgSlug","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","rowActions","editor","unlinkedReason","key","icon","size","href","undefined","disabled","disabledReason","destructive","onClick","columns","headerName","flex","minWidth","valueGetter","_value","renderCell","event","stopPropagation","width","origin","variant","items","choosing","setChoosing","createHostId","setCreateHostId","creating","setCreating","create","push","error","console","message","handleNew","pickedHostId","noSites","hostsReady","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","spacing","select","onChange","Number","target","sx","maxWidth","Array","from","_","index","to","min","setClauses","options","notices","aria-label","rowHeight","hideFooter","filterMode","filterModel","onFilterModelChange","quickFilter","disableColumnSorting","noRowsLabel","onOpen","_id","rowCount","onPageChange","onPageSizeChange","severity","open","onClose","fullWidth","mb"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/org-email-templates-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { buildRoute, pluginDocsHelp, Route } from '@aglyn/aglyn'\nimport {\n mdiDeleteOutline,\n mdiEyeOutline,\n mdiPencilOutline,\n} from '@aglyn/shared-data-mdi'\nimport {\n AppLink,\n CardDisplay,\n MdiIcon,\n useConfirmationContext,\n} from '@aglyn/shared-ui-jsx'\nimport ListFilterChips from '@aglyn/shared-ui-jsx/components/list-filter-chips.component'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport ListQueryNotices, {\n listQueryRefusals,\n} from '@aglyn/shared-ui-jsx/components/list-query-notices.component'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport type { ListFilterField } from '@aglyn/shared-ui-jsx/const/list-filter'\nimport {\n type ListFilterClause,\n listFilterGridColumns,\n} from '@aglyn/shared-ui-jsx/const/list-grid-filter'\nimport {\n type ListQueryPlan,\n planListQuery,\n} from '@aglyn/shared-ui-jsx/const/list-query-plan'\nimport { useListGridFilter } from '@aglyn/shared-ui-jsx/hooks/use-list-grid-filter'\nimport { nameSearchNormalizers } from '@aglyn/aglyn/app-utils/name-search'\nimport {\n TABLE_PAGE_SIZE_DEFAULT,\n TABLE_ROW_HEIGHT,\n} from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport { ceilingedWindow } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { listQueryConstraints } from '@aglyn/tenant-feature-instance/hooks/use-list-query'\nimport {\n useFirestore,\n useFirestoreCollection,\n useHostResourceApi,\n useHostVersionApi,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Chip,\n Dialog,\n DialogActions,\n DialogContent,\n DialogContentText,\n DialogTitle,\n MenuItem,\n Stack,\n TextField,\n Typography,\n} from '@mui/material'\nimport type { GridColDef, GridSortModel } from '@mui/x-data-grid'\nimport {\n type ListPageSort,\n sortListRows,\n} from '@aglyn/shared-util-tools/list-query/list-column-sort'\nimport { collection, doc, limit, query, updateDoc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useEffect, useMemo, useState } from 'react'\nimport {\n EMAIL_TEMPLATE_BASE,\n EMAIL_TEMPLATE_FILTER_HEADERS,\n EMAIL_TEMPLATE_QUERY,\n} from '../constants/list-queries'\nimport { templateProvenance } from '../model/template-provenance'\nimport { createEmailScreen } from '../utils/create-email-screen'\nimport { emailTemplateSoftDelete } from '../utils/email-template-soft-delete'\nimport {\n OrgSiteSelect,\n orgSiteEmailsPath,\n orgSiteName,\n orgSitePageLabel,\n useEmailOrgMount,\n useOrgSitePage,\n type EmailOrgMount,\n} from './email-org-mount'\n\n/**\n * How many of one site's templates the organization's list reads.\n *\n * Lower than the site's own list, because this one reads a page of sites at\n * once: a site with more than this is named under the table, with a link to\n * its own Templates page, which reads its whole set.\n */\nexport const ORG_TEMPLATE_CEILING = 50\n\n/*\n * What the organization's templates grid's Filters panel offers (AGL-3321).\n *\n * The template clauses and the search are the site list's own\n * (`EMAIL_TEMPLATE_QUERY`): ONE plan, put on EVERY site's query, so each\n * site's read is already the answer and nothing is matched in the browser.\n * Site is not a field of a screen but a choice of WHICH sites' queries run:\n * a Site clause reads exactly the sites it names, whichever page of sites\n * they are on. Origin (yours or installed) is not offered, for the reason the\n * declaration gives: a query cannot ask for a field to be absent.\n */\nconst SITE_FIELD: ListFilterField = {\n column: 'site',\n kind: 'exact',\n path: 'hostId',\n operators: ['equals', 'isAnyOf'],\n}\nconst ORG_TEMPLATE_FILTER_FIELDS: readonly ListFilterField[] = [\n ...EMAIL_TEMPLATE_QUERY.fields,\n SITE_FIELD,\n]\nconst ORG_TEMPLATE_FILTER_HEADERS: Readonly<Record<string, string>> = {\n ...EMAIL_TEMPLATE_FILTER_HEADERS,\n site: 'Site',\n}\n\n/** The sites a Site clause names, or null when none is in force. */\nexport function orgTemplateSiteScope(\n clauses: readonly ListFilterClause[],\n): string[] | null {\n const named = clauses\n .filter((clause) => clause.field === SITE_FIELD.column)\n .map((clause) =>\n clause.value\n .split(',')\n .map((entry) => entry.trim())\n .filter(Boolean),\n )\n if (!named.length) return null\n // Several Site clauses all hold: a site must be named by each.\n return named.reduce((kept, next) => kept.filter((id) => next.includes(id)))\n}\n\n/** How each header orders the matches in hand (AGL-3680). */\nconst ORG_TEMPLATE_SORTS: Readonly<Record<string, ListPageSort<Record<string, any>>>> = {\n displayName: (row) => String(row['displayName'] ?? 'Untitled template'),\n site: (row) => row['siteName'],\n origin: (row) =>\n templateProvenance(row).origin === 'installed' ? 'installed' : 'local',\n}\n\n/** One site's read, as the table assembles it. */\ninterface SiteRead {\n rows: Array<Record<string, any>>\n /** The site holds more templates than {@link ORG_TEMPLATE_CEILING}. */\n truncated: boolean\n /** The read has answered — rows or none. */\n settled: boolean\n}\n\n/**\n * ONE SITE'S EMAIL TEMPLATES, read while the organization's list is on screen.\n *\n * A component rather than a hook in a loop, because the number of sites\n * changes and a hook's call count may not: each mounted reader is one\n * listener, and a page of sites mounts one per site on it.\n *\n * Every predicate is on the QUERY — the email scope, every clause and the\n * search word, one plan for all the sites (AGL-3321) — so a site's read is\n * its first templates THAT MATCH, by name, and a site with a thousand pages\n * reads its matching emails and nothing else. It reads one past the ceiling,\n * so the table can say when a site holds more matches than it listed.\n */\nfunction SiteTemplatesReader(props: {\n hostId: string\n plan: ListQueryPlan\n onRead: (hostId: string, read: SiteRead) => void\n}) {\n const { hostId, plan, onRead } = props\n const firestore = useFirestore()\n const planKey = JSON.stringify({ filters: plan.filters, orderBy: plan.orderBy })\n const { data, status } = useFirestoreCollection<Record<string, any>>(\n () =>\n query(\n collection(firestore, 'hosts', hostId, 'screens'),\n ...listQueryConstraints(plan),\n limit(ORG_TEMPLATE_CEILING + 1),\n ),\n [firestore, hostId, planKey],\n { idField: '$id' },\n )\n // One window per snapshot, so a re-render that brings no new snapshot hands\n // the table the same rows it already holds.\n const window = useMemo(\n () => ceilingedWindow(data, ORG_TEMPLATE_CEILING),\n [data],\n )\n useEffect(() => {\n onRead(hostId, {\n rows: window.rows,\n truncated: window.truncated,\n settled: status !== 'loading',\n })\n }, [window, status, hostId, onRead])\n return null\n}\nSiteTemplatesReader.displayName = 'SiteTemplatesReader'\n\n/**\n * THE ORGANIZATION'S TEMPLATES: every site's reusable email designs, in one\n * table.\n *\n * A template is a screen on ONE site — its besigner, its assets and the\n * messages built from it are that site's — so this table reads each site in\n * turn and names the site on every row. A row opens the template's page on\n * its own site, where its report and its recipients are; nothing here is an\n * organization-level template page, because there is no such thing.\n *\n * The reads are one listener per site, over one page of sites at a time —\n * ordered by name — each its site's query for the clauses and search in\n * force, capped at {@link ORG_TEMPLATE_CEILING} matches. The shared footer\n * pages the matches in hand; an organization with more sites than one page\n * chooses which page of sites is read with the Sites picker above the\n * table, or names sites with the Site filter. Both caps say so on screen when\n * they bite, so a missing template is never mistaken for one that does not\n * exist.\n *\n * \"New template\" asks which site the design is made on — the besigner opens\n * on a site — and skips the question when the org has one site.\n */\nexport function OrgEmailTemplatesCard() {\n const mount = useEmailOrgMount()\n return mount ? <OrgEmailTemplatesTable mount={mount} /> : null\n}\nOrgEmailTemplatesCard.displayName = 'OrgEmailTemplatesCard'\n\nfunction OrgEmailTemplatesTable(props: { mount: EmailOrgMount }) {\n const { mount } = props\n const firestore = useFirestore()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const createHostResource = useHostResourceApi()\n const createHostVersion = useHostVersionApi()\n const sitePage = useOrgSitePage(mount)\n const gridFilter = useListGridFilter({ selectFields: [SITE_FIELD.column] })\n /*\n * Which sites are read: the ones a Site clause names, else the page of\n * sites. The other clauses and the search are ONE plan every site's query\n * carries.\n */\n const siteScope = useMemo(\n () => orgTemplateSiteScope(gridFilter.clauses),\n [gridFilter.clauses],\n )\n const sites = useMemo(\n () =>\n siteScope\n ? mount.hosts.filter((host) => siteScope.includes(host.id))\n : sitePage.sites,\n [siteScope, mount.hosts, sitePage.sites],\n )\n const plan = useMemo(\n () =>\n planListQuery(\n EMAIL_TEMPLATE_QUERY,\n {\n clauses: gridFilter.clauses.filter(\n (clause) => clause.field !== SITE_FIELD.column,\n ),\n search: gridFilter.searchWords,\n base: EMAIL_TEMPLATE_BASE,\n },\n nameSearchNormalizers,\n ),\n [gridFilter.clauses, gridFilter.searchWords],\n )\n const filtering =\n gridFilter.clauses.length > 0 || gridFilter.searchWords.some((word) => word.trim())\n const refusals = useMemo(\n () =>\n listQueryRefusals(plan.refused, {\n fields: ORG_TEMPLATE_FILTER_FIELDS,\n headers: ORG_TEMPLATE_FILTER_HEADERS,\n }),\n [plan.refused],\n )\n\n const [reads, setReads] = useState<Record<string, SiteRead>>({})\n const onRead = useCallback(\n (hostId: string, read: SiteRead) =>\n setReads((current) => {\n const previous = current[hostId]\n // An answer the table already holds changes nothing to draw.\n if (\n previous &&\n previous.rows === read.rows &&\n previous.truncated === read.truncated &&\n previous.settled === read.settled\n ) {\n return current\n }\n return { ...current, [hostId]: read }\n }),\n [],\n )\n\n /*\n * One row per template, named by site. The row id joins the site to the\n * screen id: two sites can hold a screen with the same id, and a grid that\n * keyed on the screen alone would draw one of them twice. Every row is an\n * answer some site's query gave; the sort only interleaves the sites'\n * answers by the name each was already ordered by.\n */\n const rows = useMemo(\n () =>\n sites\n .flatMap((site) =>\n (reads[site.id]?.rows ?? []).map((screen) => ({\n ...screen,\n $id: `${site.id}:${screen['$id']}`,\n screenId: String(screen['$id']),\n hostId: site.id,\n siteName: orgSiteName(mount, site.id),\n })),\n )\n .sort(\n (a, b) =>\n String(a['nameLower'] ?? '').localeCompare(String(b['nameLower'] ?? '')) ||\n orgSiteName(mount, a.hostId).localeCompare(\n orgSiteName(mount, b.hostId),\n ),\n ),\n [sites, reads, mount],\n )\n const settled = sites.every((site) => reads[site.id]?.settled)\n const crowded = sites.filter((site) => reads[site.id]?.truncated)\n\n /*\n * The matches in hand, a page at a time on the shared footer. Choosing\n * another page of sites, or another question, starts from the first page:\n * page four of one answer is not a position in another.\n */\n const filterOptions = useMemo(\n () => ({\n // Every site the organization has: a Site clause reads the sites it\n // names wherever they sit in the page of sites.\n site: mount.hosts.map((site) => ({ value: site.id, label: orgSiteName(mount, site.id) })),\n }),\n [mount],\n )\n /*\n * EVERY HEADER SORTS (AGL-3680), over EVERY match in hand rather than the\n * page on screen: the rows are all loaded (each site capped, and the cap\n * says so above), so the sort is exact and the footer pages the sorted\n * whole. Site sorts by the name the column shows, not the site id.\n */\n const [sortModel, setSortModel] = useState<GridSortModel>([])\n const sorted = useMemo(() => {\n const [first] = sortModel\n const value = first?.sort ? ORG_TEMPLATE_SORTS[first.field] : undefined\n return value && first?.sort ? sortListRows(rows, value, first.sort) : rows\n }, [rows, sortModel])\n const [page, setPage] = useState(0)\n const [pageSize, setPageSize] = useState(TABLE_PAGE_SIZE_DEFAULT)\n const planKey = JSON.stringify({ filters: plan.filters, orderBy: plan.orderBy })\n const siteKey = sites.map((site) => site.id).join(',')\n const sortKey = JSON.stringify(sortModel)\n // A new answer, or a new order, starts again at its first page.\n useEffect(() => setPage(0), [siteKey, planKey, sortKey])\n const shown = useMemo(\n () => sorted.slice(page * pageSize, page * pageSize + pageSize),\n [sorted, page, pageSize],\n )\n const sitePageCount = Math.ceil(sitePage.count / sitePage.pageSize)\n\n const templateName = (row: Record<string, any>) =>\n String(row['displayName'] ?? 'Untitled template')\n /** The template's own page, on the site that holds it. */\n const templateHref = (row: Record<string, any>) =>\n orgSiteEmailsPath(mount, row['hostId'], `templates/${row['screenId']}`)\n const siteSubdomain = (hostId: string) =>\n mount.hosts.find((site) => site.id === hostId)?.subdomain ?? null\n const besignerHref = (\n hostId: string,\n screenId: string,\n versionId: unknown,\n ) => {\n const subdomain = siteSubdomain(hostId)\n return subdomain && versionId\n ? buildRoute(Route.SCREEN_BESIGNER, {\n orgSlug: mount.orgSlug,\n host: subdomain,\n screenId,\n versionId: String(versionId),\n })\n : null\n }\n\n const handleDelete = async (row: Record<string, any>) => {\n const confirmed = await confirm({\n title: 'Delete this template?',\n description:\n `\"${templateName(row)}\" will be removed from ` +\n `${orgSiteName(mount, row['hostId'])}. Emails already sent from it ` +\n 'keep their reports.',\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await updateDoc(\n doc(firestore, 'hosts', row['hostId'], 'screens', row['screenId']),\n emailTemplateSoftDelete(),\n )\n }\n\n const rowActions = (row: Record<string, any>): RowActionsMenuItem[] => {\n const page = templateHref(row)\n const editor = besignerHref(\n row['hostId'],\n row['screenId'],\n row['versionId'],\n )\n const unlinkedReason =\n 'This site has no console address yet, so it cannot be linked to.'\n return [\n {\n key: 'details',\n label: 'Open details',\n icon: <MdiIcon path={mdiEyeOutline.path} size={0.8} />,\n href: page ?? undefined,\n disabled: !page,\n disabledReason: unlinkedReason,\n },\n {\n key: 'besigner',\n label: 'Edit in besigner',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n href: editor ?? undefined,\n disabled: !editor,\n disabledReason: row['versionId']\n ? unlinkedReason\n : 'This template has never been saved in the besigner. Open it from its page.',\n },\n {\n key: 'delete',\n label: 'Delete',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleDelete(row),\n },\n ]\n }\n\n const columns: GridColDef[] = [\n {\n field: 'displayName',\n headerName: 'Template',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) => templateName(row),\n renderCell: ({ row, value }) => {\n const href = templateHref(row)\n return href ? (\n <AppLink\n href={href}\n // The row's own handler would fire too and push the same route\n // twice — one history entry per back press.\n onClick={(event: { stopPropagation: () => void }) =>\n event.stopPropagation()\n }\n >\n {value}\n </AppLink>\n ) : (\n value\n )\n },\n },\n {\n field: 'site',\n headerName: 'Site',\n width: 170,\n valueGetter: (_value, row) => row.hostId,\n renderCell: ({ row }) => row.siteName,\n },\n {\n // Whose template this is, as on a site's own list.\n field: 'origin',\n headerName: 'Origin',\n width: 130,\n valueGetter: (_value, row) =>\n templateProvenance(row).origin === 'installed' ? 'installed' : 'local',\n renderCell: ({ value }) =>\n value === 'installed' ? (\n <Chip size=\"small\" label=\"Installed\" />\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Yours'}\n </Typography>\n ),\n },\n listActionsColumn(\n (row) => (\n <ListRowActions label={templateName(row)} items={rowActions(row)} />\n ),\n { width: 72 },\n ),\n ]\n\n /*==========================================\n * CREATE, ON A SITE.\n *\n * The same document a site's own list creates — `createEmailScreen`\n * through the quota-enforcing resources route — made as the chosen site,\n * then straight into that site's besigner. One site answers the question\n * itself; otherwise the dialog asks, defaulting to the session's pick.\n *=========================================*/\n const [choosing, setChoosing] = useState(false)\n const [createHostId, setCreateHostId] = useState('')\n const [creating, setCreating] = useState(false)\n\n const create = async (hostId: string) => {\n if (creating || !hostId) return\n setCreating(true)\n try {\n const { screenId, versionId } = await createEmailScreen(\n hostId,\n createHostResource,\n createHostVersion,\n )\n setChoosing(false)\n const editor = besignerHref(hostId, screenId, versionId)\n if (editor) {\n void router.push(editor)\n } else {\n enqueueSnackbar(\n `Template created on ${orgSiteName(mount, hostId)}. Open it from ` +\n 'that site’s Emails page.',\n { variant: 'success' },\n )\n }\n } catch (error: any) {\n console.error(error)\n enqueueSnackbar(error?.message ?? 'Creating the template failed', {\n variant: 'error',\n })\n } finally {\n setCreating(false)\n }\n }\n\n const handleNew = () => {\n if (mount.hosts.length === 1) return void create(mount.hosts[0].id)\n setCreateHostId(mount.pickedHostId ?? '')\n setChoosing(true)\n }\n\n const noSites = mount.hostsReady && mount.hosts.length === 0\n\n return (\n <CardDisplay\n header={'Templates'}\n help={pluginDocsHelp('designedEmails', { anchor: '#find-a-template' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n size=\"small\"\n variant=\"contained\"\n disabled={noSites || creating}\n onClick={handleNew}\n >\n {creating ? 'Creating…' : 'New template'}\n </Button>\n ),\n }}\n >\n {sites.map((site) => (\n <SiteTemplatesReader\n key={site.id}\n hostId={site.id}\n plan={plan}\n onRead={onRead}\n />\n ))}\n <Stack spacing={1.5}>\n {/*\n WHICH SITES' TEMPLATES ARE LISTED, when there are more sites than\n one page holds. Only the chosen page's sites are read; a template on\n a site of another page is one choice away, not missing. It picks\n what is read, a scope, so it is not one of the grid's filters.\n */}\n {sitePageCount > 1 && !siteScope ? (\n <TextField\n select\n size=\"small\"\n label=\"Sites\"\n value={sitePage.page}\n onChange={(event) => sitePage.setPage(Number(event.target.value))}\n sx={{ maxWidth: 260 }}\n >\n {Array.from({ length: sitePageCount }, (_, index) => (\n <MenuItem key={index} value={index}>\n {orgSitePageLabel({\n from: index * sitePage.pageSize + 1,\n to: Math.min((index + 1) * sitePage.pageSize, sitePage.count),\n count: sitePage.count,\n })}\n </MenuItem>\n ))}\n </TextField>\n ) : null}\n {noSites ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet. A template is designed on ' +\n 'a site, in its besigner, so it appears here once there is one.'}\n </Typography>\n ) : rows.length === 0 && !filtering ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {settled\n ? 'None of these sites has an email template yet. Design one ' +\n 'here, then send it from a campaign — a new template opens in ' +\n 'the besigner with email-safe components only.'\n : 'Loading templates…'}\n </Typography>\n ) : (\n <>\n <ListFilterChips\n fields={ORG_TEMPLATE_FILTER_FIELDS}\n headers={ORG_TEMPLATE_FILTER_HEADERS}\n clauses={gridFilter.clauses}\n onChange={gridFilter.setClauses}\n options={filterOptions}\n />\n <ListQueryNotices refused={refusals} notices={plan.notices} />\n <ListTable\n aria-label=\"Email templates\"\n rows={shown}\n columns={listFilterGridColumns(\n columns,\n ORG_TEMPLATE_FILTER_FIELDS,\n filterOptions,\n ORG_TEMPLATE_FILTER_HEADERS,\n )}\n rowHeight={TABLE_ROW_HEIGHT}\n // Paged by the footer below, so the grid must not also slice.\n hideFooter\n // The panel and the search go to every site's query; a header\n // sorts every match in hand before the footer pages it.\n filterMode=\"server\"\n filterModel={gridFilter.filterModel}\n onFilterModelChange={gridFilter.onFilterModelChange}\n quickFilter\n sortingMode=\"server\"\n sortModel={sortModel}\n onSortModelChange={setSortModel}\n noRowsLabel=\"No templates match these filters\"\n onOpen={(_id, row) => {\n const href = templateHref(row)\n if (href) router.push(href)\n }}\n />\n <ListPagination\n page={page}\n pageSize={pageSize}\n rowCount={shown.length}\n count={rows.length}\n onPageChange={setPage}\n onPageSizeChange={setPageSize}\n />\n </>\n )}\n {crowded.map((site) => {\n const href = orgSiteEmailsPath(mount, site.id, 'templates')\n return (\n <Alert key={site.id} severity=\"info\">\n {`${orgSiteName(mount, site.id)} has more than ` +\n `${ORG_TEMPLATE_CEILING} ${filtering ? 'matching templates' : 'templates'}, ` +\n 'and only the first of them are listed here. '}\n {href ? (\n <AppLink href={href}>{'See all of its templates'}</AppLink>\n ) : null}\n </Alert>\n )\n })}\n </Stack>\n\n <Dialog\n open={choosing}\n onClose={creating ? undefined : () => setChoosing(false)}\n fullWidth\n maxWidth=\"xs\"\n >\n <DialogTitle>{'New template'}</DialogTitle>\n <DialogContent>\n <DialogContentText sx={{ mb: 2 }}>\n {'A template is designed on one site, in its besigner, and is ' +\n 'sent from that site’s campaigns.'}\n </DialogContentText>\n <OrgSiteSelect\n mount={mount}\n label=\"Site\"\n value={createHostId}\n onChange={setCreateHostId}\n />\n </DialogContent>\n <DialogActions>\n <Button onClick={() => setChoosing(false)} disabled={creating}>\n {'Cancel'}\n </Button>\n <Button\n variant=\"contained\"\n disabled={!createHostId || creating}\n onClick={() => void create(createHostId)}\n >\n {creating ? 'Creating…' : 'Create and open'}\n </Button>\n </DialogActions>\n </Dialog>\n </CardDisplay>\n )\n}\nOrgEmailTemplatesTable.displayName = 'OrgEmailTemplatesTable'\n\nexport default OrgEmailTemplatesCard\n"],"names":["buildRoute","pluginDocsHelp","Route","mdiDeleteOutline","mdiEyeOutline","mdiPencilOutline","AppLink","CardDisplay","MdiIcon","useConfirmationContext","ListFilterChips","ListPagination","ListQueryNotices","listQueryRefusals","ListRowActions","ListTable","listActionsColumn","listFilterGridColumns","planListQuery","useListGridFilter","nameSearchNormalizers","TABLE_PAGE_SIZE_DEFAULT","TABLE_ROW_HEIGHT","useSnackbar","ceilingedWindow","listQueryConstraints","useFirestore","useFirestoreCollection","useHostResourceApi","useHostVersionApi","Alert","Button","Chip","Dialog","DialogActions","DialogContent","DialogContentText","DialogTitle","MenuItem","Stack","TextField","Typography","sortListRows","collection","doc","limit","query","updateDoc","useRouter","useCallback","useEffect","useMemo","useState","EMAIL_TEMPLATE_BASE","EMAIL_TEMPLATE_FILTER_HEADERS","EMAIL_TEMPLATE_QUERY","templateProvenance","createEmailScreen","emailTemplateSoftDelete","OrgSiteSelect","orgSiteEmailsPath","orgSiteName","orgSitePageLabel","useEmailOrgMount","useOrgSitePage","ORG_TEMPLATE_CEILING","SITE_FIELD","column","kind","path","operators","ORG_TEMPLATE_FILTER_FIELDS","fields","ORG_TEMPLATE_FILTER_HEADERS","site","orgTemplateSiteScope","clauses","named","filter","clause","field","map","value","split","entry","trim","Boolean","length","reduce","kept","next","id","includes","ORG_TEMPLATE_SORTS","displayName","row","String","origin","SiteTemplatesReader","props","hostId","plan","onRead","firestore","planKey","JSON","stringify","filters","orderBy","data","status","idField","window","rows","truncated","settled","OrgEmailTemplatesCard","mount","OrgEmailTemplatesTable","router","enqueueSnackbar","confirm","createHostResource","createHostVersion","sitePage","gridFilter","selectFields","siteScope","sites","hosts","host","search","searchWords","base","filtering","some","word","refusals","refused","headers","reads","setReads","read","current","previous","flatMap","screen","$id","screenId","siteName","sort","a","b","localeCompare","every","crowded","filterOptions","label","sortModel","setSortModel","sorted","first","undefined","page","setPage","pageSize","setPageSize","siteKey","join","sortKey","shown","slice","sitePageCount","Math","ceil","count","templateName","templateHref","siteSubdomain","find","subdomain","besignerHref","versionId","SCREEN_BESIGNER","orgSlug","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","rowActions","editor","unlinkedReason","key","icon","size","href","disabled","disabledReason","destructive","onClick","columns","headerName","flex","minWidth","valueGetter","_value","renderCell","event","stopPropagation","width","variant","items","choosing","setChoosing","createHostId","setCreateHostId","creating","setCreating","create","push","error","console","message","handleNew","pickedHostId","noSites","hostsReady","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","spacing","select","onChange","Number","target","sx","maxWidth","Array","from","_","index","to","min","setClauses","options","notices","aria-label","rowHeight","hideFooter","filterMode","filterModel","onFilterModelChange","quickFilter","sortingMode","onSortModelChange","noRowsLabel","onOpen","_id","rowCount","onPageChange","onPageSizeChange","severity","open","onClose","fullWidth","mb"],"mappings":"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"}
|
|
@@ -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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"}
|
|
@@ -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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"}
|