@aglyn/plugins-email 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +58 -0
- package/src/index.d.ts +35 -0
- package/src/index.js +35 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/campaign-design-create-widget.d.ts +21 -0
- package/src/lib/components/campaign-design-create-widget.js +61 -0
- package/src/lib/components/campaign-design-create-widget.js.map +1 -0
- package/src/lib/components/campaign-sender-editor-widget.d.ts +18 -0
- package/src/lib/components/campaign-sender-editor-widget.js +39 -0
- package/src/lib/components/campaign-sender-editor-widget.js.map +1 -0
- package/src/lib/components/campaign-topic-options-widget.d.ts +19 -0
- package/src/lib/components/campaign-topic-options-widget.js +48 -0
- package/src/lib/components/campaign-topic-options-widget.js.map +1 -0
- package/src/lib/components/campaign-topic-select.d.ts +32 -0
- package/src/lib/components/campaign-topic-select.js +87 -0
- package/src/lib/components/campaign-topic-select.js.map +1 -0
- package/src/lib/components/dynamic-list-rule-fields.d.ts +173 -0
- package/src/lib/components/dynamic-list-rule-fields.js +1473 -0
- package/src/lib/components/dynamic-list-rule-fields.js.map +1 -0
- package/src/lib/components/email-blocks.d.ts +111 -0
- package/src/lib/components/email-blocks.js +875 -0
- package/src/lib/components/email-blocks.js.map +1 -0
- package/src/lib/components/email-design-preview.d.ts +62 -0
- package/src/lib/components/email-design-preview.js +174 -0
- package/src/lib/components/email-design-preview.js.map +1 -0
- package/src/lib/components/email-screens-card.d.ts +42 -0
- package/src/lib/components/email-screens-card.js +277 -0
- package/src/lib/components/email-screens-card.js.map +1 -0
- package/src/lib/components/email-template-detail.d.ts +48 -0
- package/src/lib/components/email-template-detail.js +681 -0
- package/src/lib/components/email-template-detail.js.map +1 -0
- package/src/lib/components/email-topic-detail.d.ts +32 -0
- package/src/lib/components/email-topic-detail.js +293 -0
- package/src/lib/components/email-topic-detail.js.map +1 -0
- package/src/lib/components/email-topics-card.d.ts +46 -0
- package/src/lib/components/email-topics-card.js +327 -0
- package/src/lib/components/email-topics-card.js.map +1 -0
- package/src/lib/components/email-zones.d.ts +28 -0
- package/src/lib/components/email-zones.js +20 -0
- package/src/lib/components/email-zones.js.map +1 -0
- package/src/lib/components/emails-console-page.d.ts +32 -0
- package/src/lib/components/emails-console-page.js +229 -0
- package/src/lib/components/emails-console-page.js.map +1 -0
- package/src/lib/components/emails-console-sections.d.ts +36 -0
- package/src/lib/components/emails-console-sections.js +108 -0
- package/src/lib/components/emails-console-sections.js.map +1 -0
- package/src/lib/components/list-detail-card.d.ts +47 -0
- package/src/lib/components/list-detail-card.js +273 -0
- package/src/lib/components/list-detail-card.js.map +1 -0
- package/src/lib/components/list-edit-card.d.ts +11 -0
- package/src/lib/components/list-edit-card.js +287 -0
- package/src/lib/components/list-edit-card.js.map +1 -0
- package/src/lib/components/list-import-drawer.d.ts +22 -0
- package/src/lib/components/list-import-drawer.js +662 -0
- package/src/lib/components/list-import-drawer.js.map +1 -0
- package/src/lib/components/list-members-panel.d.ts +94 -0
- package/src/lib/components/list-members-panel.js +686 -0
- package/src/lib/components/list-members-panel.js.map +1 -0
- package/src/lib/components/lists-card.d.ts +28 -0
- package/src/lib/components/lists-card.js +377 -0
- package/src/lib/components/lists-card.js.map +1 -0
- package/src/lib/components/sending-domain-detail.d.ts +26 -0
- package/src/lib/components/sending-domain-detail.js +496 -0
- package/src/lib/components/sending-domain-detail.js.map +1 -0
- package/src/lib/components/sending-domains-card.d.ts +33 -0
- package/src/lib/components/sending-domains-card.js +962 -0
- package/src/lib/components/sending-domains-card.js.map +1 -0
- package/src/lib/components/sending-sender-drawer.d.ts +94 -0
- package/src/lib/components/sending-sender-drawer.js +543 -0
- package/src/lib/components/sending-sender-drawer.js.map +1 -0
- package/src/lib/components/suppressions-card.d.ts +49 -0
- package/src/lib/components/suppressions-card.js +639 -0
- package/src/lib/components/suppressions-card.js.map +1 -0
- package/src/lib/components/use-org-email-topics.d.ts +79 -0
- package/src/lib/components/use-org-email-topics.js +111 -0
- package/src/lib/components/use-org-email-topics.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +18 -0
- package/src/lib/constants/bundle-common.js +18 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/hooks/use-org-company-options.d.ts +20 -0
- package/src/lib/hooks/use-org-company-options.js +138 -0
- package/src/lib/hooks/use-org-company-options.js.map +1 -0
- package/src/lib/hooks/use-org-contact-fields.d.ts +40 -0
- package/src/lib/hooks/use-org-contact-fields.js +91 -0
- package/src/lib/hooks/use-org-contact-fields.js.map +1 -0
- package/src/lib/hooks/use-org-contact-segments.d.ts +16 -0
- package/src/lib/hooks/use-org-contact-segments.js +55 -0
- package/src/lib/hooks/use-org-contact-segments.js.map +1 -0
- package/src/lib/hooks/use-org-crm-views.d.ts +8 -0
- package/src/lib/hooks/use-org-crm-views.js +74 -0
- package/src/lib/hooks/use-org-crm-views.js.map +1 -0
- package/src/lib/hooks/use-org-lists.d.ts +8 -0
- package/src/lib/hooks/use-org-lists.js +47 -0
- package/src/lib/hooks/use-org-lists.js.map +1 -0
- package/src/lib/model/email-design-document.d.ts +52 -0
- package/src/lib/model/email-design-document.js +62 -0
- package/src/lib/model/email-design-document.js.map +1 -0
- package/src/lib/model/index.d.ts +64 -0
- package/src/lib/model/index.js +71 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/sending-domain-status.d.ts +99 -0
- package/src/lib/model/sending-domain-status.js +196 -0
- package/src/lib/model/sending-domain-status.js.map +1 -0
- package/src/lib/model/template-provenance.d.ts +113 -0
- package/src/lib/model/template-provenance.js +107 -0
- package/src/lib/model/template-provenance.js.map +1 -0
- package/src/lib/model/template-report.d.ts +158 -0
- package/src/lib/model/template-report.js +249 -0
- package/src/lib/model/template-report.js.map +1 -0
- package/src/lib/plugin.d.ts +27 -0
- package/src/lib/plugin.js +163 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/server-console.d.ts +116 -0
- package/src/lib/server-console.js +422 -0
- package/src/lib/server-console.js.map +1 -0
- package/src/lib/server-email-drafts.d.ts +104 -0
- package/src/lib/server-email-drafts.js +381 -0
- package/src/lib/server-email-drafts.js.map +1 -0
- package/src/lib/server-list-gate.d.ts +183 -0
- package/src/lib/server-list-gate.js +365 -0
- package/src/lib/server-list-gate.js.map +1 -0
- package/src/lib/server-list-import.d.ts +199 -0
- package/src/lib/server-list-import.js +632 -0
- package/src/lib/server-list-import.js.map +1 -0
- package/src/lib/server-suppressions.d.ts +135 -0
- package/src/lib/server-suppressions.js +295 -0
- package/src/lib/server-suppressions.js.map +1 -0
- package/src/lib/server.d.ts +19 -0
- package/src/lib/server.js +834 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +26 -0
- package/src/lib/site.js +81 -0
- package/src/lib/site.js.map +1 -0
- package/src/lib/unsubscribe-link.d.ts +311 -0
- package/src/lib/unsubscribe-link.js +398 -0
- package/src/lib/unsubscribe-link.js.map +1 -0
- package/src/lib/utils/create-email-screen.d.ts +59 -0
- package/src/lib/utils/create-email-screen.js +59 -0
- package/src/lib/utils/create-email-screen.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +19 -0
- package/src/lib/utils/generate-preset-id.js +25 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { _ as _extends } from "@swc/helpers/_/_extends";
|
|
18
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
19
|
+
import { consentGroupForHost } from "@aglyn/aglyn";
|
|
20
|
+
import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
|
|
21
|
+
import { HubSections } from "@aglyn/shared-ui-next";
|
|
22
|
+
import { useMemo } from "react";
|
|
23
|
+
import EmailScreensCard from "./email-screens-card.js";
|
|
24
|
+
import EmailTemplateDetail from "./email-template-detail.js";
|
|
25
|
+
import EmailTopicDetail from "./email-topic-detail.js";
|
|
26
|
+
import EmailTopicsCard from "./email-topics-card.js";
|
|
27
|
+
import ListDetailCard from "./list-detail-card.js";
|
|
28
|
+
import ListEditCard from "./list-edit-card.js";
|
|
29
|
+
import ListsCard from "./lists-card.js";
|
|
30
|
+
import SendingDomainDetail from "./sending-domain-detail.js";
|
|
31
|
+
import SendingDomainsCard from "./sending-domains-card.js";
|
|
32
|
+
import SuppressionsCard from "./suppressions-card.js";
|
|
33
|
+
import { EMAIL_MESSAGES_ZONE } from "./email-zones.js";
|
|
34
|
+
/**
|
|
35
|
+
* The Messages section: a zone, drawn by whichever plugin owns campaigns.
|
|
36
|
+
*
|
|
37
|
+
* Its own component because a zone's renderer comes from a hook and
|
|
38
|
+
* `sectionBody` is a plain function — and so that, like every other branch
|
|
39
|
+
* there, it is constructed only while Messages is the section being read.
|
|
40
|
+
*/ function EmailMessagesSection(props) {
|
|
41
|
+
const Zone = useConsoleWidgetSlot();
|
|
42
|
+
return Zone ? /*#__PURE__*/ _jsx(Zone, _extends({
|
|
43
|
+
slot: EMAIL_MESSAGES_ZONE.id
|
|
44
|
+
}, props)) : null;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The body of one emails section, built only when that section is the one
|
|
48
|
+
* being read (AGL-2501).
|
|
49
|
+
*
|
|
50
|
+
* A function rather than a map of nodes on purpose: a `Record<id, ReactNode>`
|
|
51
|
+
* would CONSTRUCT every section on every render, and each card opens its
|
|
52
|
+
* Firestore listens on mount — which is the entire cost this page exists to
|
|
53
|
+
* stop paying. Only the returned branch is ever built.
|
|
54
|
+
*/ function sectionBody(section, hostId, /**
|
|
55
|
+
* The section's OWN segments — `segments[1]` onward, already sliced by the
|
|
56
|
+
* caller. A section that owns deeper routes reads them here; one that does
|
|
57
|
+
* not simply ignores them.
|
|
58
|
+
*/ detail, basePath, /**
|
|
59
|
+
* The controller the surface is being viewed as, resolved once by the page.
|
|
60
|
+
*
|
|
61
|
+
* Only the audience detail needs it today — it is what its membership table
|
|
62
|
+
* reads consent FOR — but it is resolved at the page because the answer is a
|
|
63
|
+
* property of the org and this is the one place holding the org document.
|
|
64
|
+
*/ consentGroup) {
|
|
65
|
+
switch(section){
|
|
66
|
+
case 'messages':
|
|
67
|
+
/*
|
|
68
|
+
* `/emails/messages/{emailId}` is ONE MESSAGE — the thing that was or
|
|
69
|
+
* will be sent, as against the campaign that groups messages and the
|
|
70
|
+
* template they are built from. A ROUTE rather than an expanded row: it
|
|
71
|
+
* is linkable, which is what a merchant wants to paste into a message
|
|
72
|
+
* about last week's send, and its preview, link table and recipient
|
|
73
|
+
* list are reads the list above it must not pay for.
|
|
74
|
+
*
|
|
75
|
+
* The page links OUT to the campaign this message belongs to, which is
|
|
76
|
+
* a section of the Marketing console.
|
|
77
|
+
*/ /*
|
|
78
|
+
* `…/{emailId}/edit` WRITES the email and `…/{emailId}` reports on
|
|
79
|
+
* it — two jobs with two shapes, and creating stays a drawer on the
|
|
80
|
+
* list. Which of the three is drawn is decided from `detail` by the
|
|
81
|
+
* widget, which builds only the one it is asked for: the composer's
|
|
82
|
+
* listens are not paid for by somebody reading a report.
|
|
83
|
+
*/ return /*#__PURE__*/ _jsx(EmailMessagesSection, {
|
|
84
|
+
hostId: hostId,
|
|
85
|
+
basePath: basePath,
|
|
86
|
+
detail: detail
|
|
87
|
+
});
|
|
88
|
+
case 'templates':
|
|
89
|
+
/*
|
|
90
|
+
* `/emails/templates/{screenId}` is one TEMPLATE's page.
|
|
91
|
+
*
|
|
92
|
+
* A route for the same reasons a message's page is one: it is
|
|
93
|
+
* linkable, and the listing above it is a cheaper surface a reader who
|
|
94
|
+
* came for one template should not have to mount. The preview, the
|
|
95
|
+
* aggregate figures and the recipients table all hang off this branch,
|
|
96
|
+
* so none of them is constructed while the list is what is being read.
|
|
97
|
+
*/ return detail[0] ? /*#__PURE__*/ _jsx(EmailTemplateDetail, {
|
|
98
|
+
hostId: hostId,
|
|
99
|
+
screenId: detail[0],
|
|
100
|
+
basePath: basePath
|
|
101
|
+
}) : /*#__PURE__*/ _jsx(EmailScreensCard, {
|
|
102
|
+
hostId: hostId,
|
|
103
|
+
basePath: basePath
|
|
104
|
+
});
|
|
105
|
+
case 'audiences':
|
|
106
|
+
/*
|
|
107
|
+
* A list is a resource with its own pages, on the same terms a message
|
|
108
|
+
* is: `/emails/audiences/{listId}` is one audience, and `…/edit` is its
|
|
109
|
+
* settings.
|
|
110
|
+
*
|
|
111
|
+
* The membership used to unfold inside the audiences table. That made a
|
|
112
|
+
* list unlinkable, put the back button one press from leaving the whole
|
|
113
|
+
* surface, and asked the reader of a list of lists to hold the table
|
|
114
|
+
* that lists them AND the table of one list's subscribers on the same
|
|
115
|
+
* screen. The subscribers are also the expensive read here — one PII
|
|
116
|
+
* document per person — so putting them behind a route is what stops
|
|
117
|
+
* them being paid for by somebody who came to see which audiences exist.
|
|
118
|
+
*
|
|
119
|
+
* Ternaries rather than a lookup: only the branch taken is CONSTRUCTED,
|
|
120
|
+
* which is the cost this whole function is shaped around.
|
|
121
|
+
*/ return detail[0] ? detail[1] === 'edit' ? /*#__PURE__*/ _jsx(ListEditCard, {
|
|
122
|
+
hostId: hostId,
|
|
123
|
+
listId: detail[0],
|
|
124
|
+
basePath: basePath
|
|
125
|
+
}) : /*#__PURE__*/ _jsx(ListDetailCard, {
|
|
126
|
+
hostId: hostId,
|
|
127
|
+
consentGroup: consentGroup,
|
|
128
|
+
listId: detail[0],
|
|
129
|
+
basePath: basePath
|
|
130
|
+
}) : /*#__PURE__*/ _jsx(ListsCard, {
|
|
131
|
+
hostId: hostId,
|
|
132
|
+
basePath: basePath
|
|
133
|
+
});
|
|
134
|
+
case 'topics':
|
|
135
|
+
// Create is a drawer on the list; EDIT is the topic's own route, which
|
|
136
|
+
// is the section owning its own subtree exactly as `messages` does.
|
|
137
|
+
return detail[0] ? /*#__PURE__*/ _jsx(EmailTopicDetail, {
|
|
138
|
+
hostId: hostId,
|
|
139
|
+
topicId: detail[0],
|
|
140
|
+
basePath: basePath
|
|
141
|
+
}) : /*#__PURE__*/ _jsx(EmailTopicsCard, {
|
|
142
|
+
hostId: hostId,
|
|
143
|
+
basePath: basePath
|
|
144
|
+
});
|
|
145
|
+
case 'sending':
|
|
146
|
+
/*
|
|
147
|
+
* `/emails/sending/{domain}` is one domain's page, and it is a route
|
|
148
|
+
* for the reason every other detail here is: the DNS records, the DMARC
|
|
149
|
+
* read and the verification button are what somebody came for, and a
|
|
150
|
+
* link to them is what they paste to whoever actually edits the zone.
|
|
151
|
+
*
|
|
152
|
+
* The domain is the document id, so it is also the segment. It is
|
|
153
|
+
* decoded because a URL carries it encoded and the record is keyed on
|
|
154
|
+
* the bare name.
|
|
155
|
+
*/ return detail[0] ? /*#__PURE__*/ _jsx(SendingDomainDetail, {
|
|
156
|
+
hostId: hostId,
|
|
157
|
+
domain: decodeURIComponent(detail[0]),
|
|
158
|
+
basePath: basePath
|
|
159
|
+
}) : /*#__PURE__*/ _jsx(SendingDomainsCard, {
|
|
160
|
+
hostId: hostId,
|
|
161
|
+
basePath: basePath
|
|
162
|
+
});
|
|
163
|
+
case 'suppressions':
|
|
164
|
+
return /*#__PURE__*/ _jsx(SuppressionsCard, {
|
|
165
|
+
hostId: hostId
|
|
166
|
+
});
|
|
167
|
+
default:
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Emails page (AGL-395): the console surface owned by the email plugin,
|
|
173
|
+
* rendered by the shell's generic plugin route.
|
|
174
|
+
*
|
|
175
|
+
* Two of its sections are two different things a merchant calls "an email":
|
|
176
|
+
* an EMAIL is one message that was or will be sent, and a TEMPLATE is the
|
|
177
|
+
* reusable besigner document a message is built from. Keeping them apart is
|
|
178
|
+
* what lets each carry its own report — a message's own numbers, and a
|
|
179
|
+
* template's summed across every message sent from it. Audience lists, the
|
|
180
|
+
* topic catalog, the sending identities and the suppression list complete the
|
|
181
|
+
* surface.
|
|
182
|
+
*
|
|
183
|
+
* The CAMPAIGN that groups messages is not here. It is a container with a
|
|
184
|
+
* window of dates, a set of lists, a topic and revenue attribution — a
|
|
185
|
+
* marketing object that reaches people by email — so it is a section of the
|
|
186
|
+
* Marketing console, and a message's page links out to the campaign it
|
|
187
|
+
* belongs to.
|
|
188
|
+
*
|
|
189
|
+
* Sections are ROUTES (AGL-2501). `HubTabs lazy` already mounted one panel, so
|
|
190
|
+
* this is not a read saving — `emails-console-read-cost.spec.tsx` was written
|
|
191
|
+
* BEFORE the conversion precisely to hold that line, and reports the same
|
|
192
|
+
* counts after. What routing adds is that the URL names the section: it is
|
|
193
|
+
* linkable, the back button walks sections, the breadcrumb says where you are,
|
|
194
|
+
* and "mount only what is open" is structural rather than a `lazy` flag
|
|
195
|
+
* somebody has to remember on the next surface.
|
|
196
|
+
*/ export function EmailsConsolePage(props) {
|
|
197
|
+
const { hostId, org, section, sections, basePath, segments } = props;
|
|
198
|
+
/*==========================================
|
|
199
|
+
* THE CONTROLLER THIS SURFACE IS BEING VIEWED AS.
|
|
200
|
+
*
|
|
201
|
+
* The declared group of sites that are one sender, or this site alone. Every
|
|
202
|
+
* consent question asked below is asked FOR a controller and not for a site:
|
|
203
|
+
* a grant is looked up under the asking site, a refusal is honored across
|
|
204
|
+
* the whole group, and a grant held by a site outside it belongs to somebody
|
|
205
|
+
* else. It is the same group `performCampaignSend` resolves, so the audience
|
|
206
|
+
* table and the send agree about the same document by construction.
|
|
207
|
+
*
|
|
208
|
+
* Pure, from the org document the shell already passed, so it costs no read.
|
|
209
|
+
* An absent org resolves to the group of one, which is the narrow answer.
|
|
210
|
+
*=========================================*/ const consentGroup = useMemo(()=>consentGroupForHost(org, hostId), [
|
|
211
|
+
org,
|
|
212
|
+
hostId
|
|
213
|
+
]);
|
|
214
|
+
/*
|
|
215
|
+
* Nothing, deliberately, while the redirect is in flight. Rendering the
|
|
216
|
+
* default section here would issue its listens on a URL about to be
|
|
217
|
+
* replaced — on every arrival at `/emails`, which is every nav-tab click.
|
|
218
|
+
*/ if (!section || !(sections == null ? void 0 : sections.length) || !basePath) return null;
|
|
219
|
+
return /*#__PURE__*/ _jsx(HubSections, {
|
|
220
|
+
sections: sections,
|
|
221
|
+
children: sectionBody(section, hostId, (// `segments[0]` IS the section — the shell resolved it into `section`
|
|
222
|
+
// already — so what a section owns is everything after it.
|
|
223
|
+
segments != null ? segments : []).slice(1), basePath, consentGroup)
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
EmailsConsolePage.displayName = 'EmailsConsolePage';
|
|
227
|
+
export default EmailsConsolePage;
|
|
228
|
+
|
|
229
|
+
//# sourceMappingURL=emails-console-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/emails-console-page.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 {\n consentGroupForHost,\n type ConsentGroup,\n type ConsolePluginPageProps,\n} from '@aglyn/aglyn'\nimport { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport { HubSections } from '@aglyn/shared-ui-next'\nimport { useMemo, type ReactNode } from 'react'\nimport EmailScreensCard from './email-screens-card'\nimport EmailTemplateDetail from './email-template-detail'\nimport EmailTopicDetail from './email-topic-detail'\nimport EmailTopicsCard from './email-topics-card'\nimport ListDetailCard from './list-detail-card'\nimport ListEditCard from './list-edit-card'\nimport ListsCard from './lists-card'\nimport SendingDomainDetail from './sending-domain-detail'\nimport SendingDomainsCard from './sending-domains-card'\nimport SuppressionsCard from './suppressions-card'\nimport type { EmailsConsoleSectionId } from './emails-console-sections'\nimport { EMAIL_MESSAGES_ZONE, type EmailMessagesZoneProps } from './email-zones'\n\n/**\n * The Messages section: a zone, drawn by whichever plugin owns campaigns.\n *\n * Its own component because a zone's renderer comes from a hook and\n * `sectionBody` is a plain function — and so that, like every other branch\n * there, it is constructed only while Messages is the section being read.\n */\nfunction EmailMessagesSection(props: EmailMessagesZoneProps) {\n const Zone = useConsoleWidgetSlot()\n return Zone ? <Zone slot={EMAIL_MESSAGES_ZONE.id} {...props} /> : null\n}\n\n/**\n * The body of one emails section, built only when that section is the one\n * being read (AGL-2501).\n *\n * A function rather than a map of nodes on purpose: a `Record<id, ReactNode>`\n * would CONSTRUCT every section on every render, and each card opens its\n * Firestore listens on mount — which is the entire cost this page exists to\n * stop paying. Only the returned branch is ever built.\n */\nfunction sectionBody(\n section: EmailsConsoleSectionId,\n hostId: string,\n /**\n * The section's OWN segments — `segments[1]` onward, already sliced by the\n * caller. A section that owns deeper routes reads them here; one that does\n * not simply ignores them.\n */\n detail: readonly string[],\n basePath: string,\n /**\n * The controller the surface is being viewed as, resolved once by the page.\n *\n * Only the audience detail needs it today — it is what its membership table\n * reads consent FOR — but it is resolved at the page because the answer is a\n * property of the org and this is the one place holding the org document.\n */\n consentGroup: ConsentGroup,\n): ReactNode {\n switch (section) {\n case 'messages':\n /*\n * `/emails/messages/{emailId}` is ONE MESSAGE — the thing that was or\n * will be sent, as against the campaign that groups messages and the\n * template they are built from. A ROUTE rather than an expanded row: it\n * is linkable, which is what a merchant wants to paste into a message\n * about last week's send, and its preview, link table and recipient\n * list are reads the list above it must not pay for.\n *\n * The page links OUT to the campaign this message belongs to, which is\n * a section of the Marketing console.\n */\n /*\n * `…/{emailId}/edit` WRITES the email and `…/{emailId}` reports on\n * it — two jobs with two shapes, and creating stays a drawer on the\n * list. Which of the three is drawn is decided from `detail` by the\n * widget, which builds only the one it is asked for: the composer's\n * listens are not paid for by somebody reading a report.\n */\n return (\n <EmailMessagesSection\n hostId={hostId}\n basePath={basePath}\n detail={detail}\n />\n )\n case 'templates':\n /*\n * `/emails/templates/{screenId}` is one TEMPLATE's page.\n *\n * A route for the same reasons a message's page is one: it is\n * linkable, and the listing above it is a cheaper surface a reader who\n * came for one template should not have to mount. The preview, the\n * aggregate figures and the recipients table all hang off this branch,\n * so none of them is constructed while the list is what is being read.\n */\n return detail[0] ? (\n <EmailTemplateDetail\n hostId={hostId}\n screenId={detail[0]}\n basePath={basePath}\n />\n ) : (\n <EmailScreensCard hostId={hostId} basePath={basePath} />\n )\n case 'audiences':\n /*\n * A list is a resource with its own pages, on the same terms a message\n * is: `/emails/audiences/{listId}` is one audience, and `…/edit` is its\n * settings.\n *\n * The membership used to unfold inside the audiences table. That made a\n * list unlinkable, put the back button one press from leaving the whole\n * surface, and asked the reader of a list of lists to hold the table\n * that lists them AND the table of one list's subscribers on the same\n * screen. The subscribers are also the expensive read here — one PII\n * document per person — so putting them behind a route is what stops\n * them being paid for by somebody who came to see which audiences exist.\n *\n * Ternaries rather than a lookup: only the branch taken is CONSTRUCTED,\n * which is the cost this whole function is shaped around.\n */\n return detail[0] ? (\n detail[1] === 'edit' ? (\n <ListEditCard\n hostId={hostId}\n listId={detail[0]}\n basePath={basePath}\n />\n ) : (\n <ListDetailCard\n hostId={hostId}\n consentGroup={consentGroup}\n listId={detail[0]}\n basePath={basePath}\n />\n )\n ) : (\n <ListsCard hostId={hostId} basePath={basePath} />\n )\n case 'topics':\n // Create is a drawer on the list; EDIT is the topic's own route, which\n // is the section owning its own subtree exactly as `messages` does.\n return detail[0] ? (\n <EmailTopicDetail\n hostId={hostId}\n topicId={detail[0]}\n basePath={basePath}\n />\n ) : (\n <EmailTopicsCard hostId={hostId} basePath={basePath} />\n )\n case 'sending':\n /*\n * `/emails/sending/{domain}` is one domain's page, and it is a route\n * for the reason every other detail here is: the DNS records, the DMARC\n * read and the verification button are what somebody came for, and a\n * link to them is what they paste to whoever actually edits the zone.\n *\n * The domain is the document id, so it is also the segment. It is\n * decoded because a URL carries it encoded and the record is keyed on\n * the bare name.\n */\n return detail[0] ? (\n <SendingDomainDetail\n hostId={hostId}\n domain={decodeURIComponent(detail[0])}\n basePath={basePath}\n />\n ) : (\n <SendingDomainsCard hostId={hostId} basePath={basePath} />\n )\n case 'suppressions':\n return <SuppressionsCard hostId={hostId} />\n default:\n return null\n }\n}\n\n/**\n * Emails page (AGL-395): the console surface owned by the email plugin,\n * rendered by the shell's generic plugin route.\n *\n * Two of its sections are two different things a merchant calls \"an email\":\n * an EMAIL is one message that was or will be sent, and a TEMPLATE is the\n * reusable besigner document a message is built from. Keeping them apart is\n * what lets each carry its own report — a message's own numbers, and a\n * template's summed across every message sent from it. Audience lists, the\n * topic catalog, the sending identities and the suppression list complete the\n * surface.\n *\n * The CAMPAIGN that groups messages is not here. It is a container with a\n * window of dates, a set of lists, a topic and revenue attribution — a\n * marketing object that reaches people by email — so it is a section of the\n * Marketing console, and a message's page links out to the campaign it\n * belongs to.\n *\n * Sections are ROUTES (AGL-2501). `HubTabs lazy` already mounted one panel, so\n * this is not a read saving — `emails-console-read-cost.spec.tsx` was written\n * BEFORE the conversion precisely to hold that line, and reports the same\n * counts after. What routing adds is that the URL names the section: it is\n * linkable, the back button walks sections, the breadcrumb says where you are,\n * and \"mount only what is open\" is structural rather than a `lazy` flag\n * somebody has to remember on the next surface.\n */\nexport function EmailsConsolePage(props: ConsolePluginPageProps) {\n const { hostId, org, section, sections, basePath, segments } = props\n\n /*==========================================\n * THE CONTROLLER THIS SURFACE IS BEING VIEWED AS.\n *\n * The declared group of sites that are one sender, or this site alone. Every\n * consent question asked below is asked FOR a controller and not for a site:\n * a grant is looked up under the asking site, a refusal is honored across\n * the whole group, and a grant held by a site outside it belongs to somebody\n * else. It is the same group `performCampaignSend` resolves, so the audience\n * table and the send agree about the same document by construction.\n *\n * Pure, from the org document the shell already passed, so it costs no read.\n * An absent org resolves to the group of one, which is the narrow answer.\n *=========================================*/\n const consentGroup = useMemo(\n () => consentGroupForHost(org as Record<string, unknown>, hostId),\n [org, hostId],\n )\n\n /*\n * Nothing, deliberately, while the redirect is in flight. Rendering the\n * default section here would issue its listens on a URL about to be\n * replaced — on every arrival at `/emails`, which is every nav-tab click.\n */\n if (!section || !sections?.length || !basePath) return null\n\n return (\n <HubSections sections={sections}>\n {sectionBody(\n section as EmailsConsoleSectionId,\n hostId,\n // `segments[0]` IS the section — the shell resolved it into `section`\n // already — so what a section owns is everything after it.\n (segments ?? []).slice(1),\n basePath,\n consentGroup,\n )}\n </HubSections>\n )\n}\nEmailsConsolePage.displayName = 'EmailsConsolePage'\n\nexport default EmailsConsolePage\n"],"names":["consentGroupForHost","useConsoleWidgetSlot","HubSections","useMemo","EmailScreensCard","EmailTemplateDetail","EmailTopicDetail","EmailTopicsCard","ListDetailCard","ListEditCard","ListsCard","SendingDomainDetail","SendingDomainsCard","SuppressionsCard","EMAIL_MESSAGES_ZONE","EmailMessagesSection","props","Zone","slot","id","sectionBody","section","hostId","detail","basePath","consentGroup","screenId","listId","topicId","domain","decodeURIComponent","EmailsConsolePage","org","sections","segments","length","slice","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { ConsoleNavSection } from '@aglyn/aglyn';
|
|
18
|
+
export type EmailsConsoleSectionId = 'messages' | 'templates' | 'audiences' | 'topics' | 'sending' | 'suppressions';
|
|
19
|
+
/**
|
|
20
|
+
* The emails console's sections, in rail order (AGL-2501).
|
|
21
|
+
*
|
|
22
|
+
* One list, read twice and never copied: `plugin.ts` registers it on the nav
|
|
23
|
+
* item so the shell can route and gate each section, and the page switches its
|
|
24
|
+
* body on the id the shell resolves back.
|
|
25
|
+
*
|
|
26
|
+
* Ids appear in links people keep — treat them as persisted, and prefer
|
|
27
|
+
* renaming a LABEL over an id. Where the two have to move together, the old
|
|
28
|
+
* path is kept alive by a redirect rather than by an id that no longer says
|
|
29
|
+
* what the section is. `audiences` and `suppressions` are also the `?tab=`
|
|
30
|
+
* ids this page deep-linked by before its sections became routes, so a
|
|
31
|
+
* bookmark that named one of those tabs still names the same section.
|
|
32
|
+
*
|
|
33
|
+
* No `navTabId` on any of them: every section ships with the surface, so they
|
|
34
|
+
* inherit the Emails nav item's gate.
|
|
35
|
+
*/
|
|
36
|
+
export declare const EMAILS_CONSOLE_SECTIONS: readonly ConsoleNavSection[];
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* The emails console's sections, in rail order (AGL-2501).
|
|
18
|
+
*
|
|
19
|
+
* One list, read twice and never copied: `plugin.ts` registers it on the nav
|
|
20
|
+
* item so the shell can route and gate each section, and the page switches its
|
|
21
|
+
* body on the id the shell resolves back.
|
|
22
|
+
*
|
|
23
|
+
* Ids appear in links people keep — treat them as persisted, and prefer
|
|
24
|
+
* renaming a LABEL over an id. Where the two have to move together, the old
|
|
25
|
+
* path is kept alive by a redirect rather than by an id that no longer says
|
|
26
|
+
* what the section is. `audiences` and `suppressions` are also the `?tab=`
|
|
27
|
+
* ids this page deep-linked by before its sections became routes, so a
|
|
28
|
+
* bookmark that named one of those tabs still names the same section.
|
|
29
|
+
*
|
|
30
|
+
* No `navTabId` on any of them: every section ships with the surface, so they
|
|
31
|
+
* inherit the Emails nav item's gate.
|
|
32
|
+
*/ export const EMAILS_CONSOLE_SECTIONS = [
|
|
33
|
+
/*
|
|
34
|
+
* The individual messages: one row per email that was or will be sent, each
|
|
35
|
+
* with its own report.
|
|
36
|
+
*
|
|
37
|
+
* A message is what this surface is about, and this section is named for
|
|
38
|
+
* the record rather than for the surface holding it. The shell prints the
|
|
39
|
+
* surface and the section together — in the header, in the breadcrumb and
|
|
40
|
+
* in the browser tab — so a section whose name repeats its surface's says
|
|
41
|
+
* nothing about which of the six is open, which is the one job a section
|
|
42
|
+
* name has. `messages` is also the word the rest of this surface already
|
|
43
|
+
* uses for the record: a message's own page, a template's sends table and
|
|
44
|
+
* the campaign detail all call it that.
|
|
45
|
+
*
|
|
46
|
+
* `/emails/emails/{id}` is answered by a permanent redirect in the console's
|
|
47
|
+
* `next.config.js`, on the terms the Workflows → Automation move set: a URL
|
|
48
|
+
* somebody pasted keeps resolving, and it resolves to the one address the
|
|
49
|
+
* console generates.
|
|
50
|
+
*
|
|
51
|
+
* The CAMPAIGN that groups messages is a marketing object — a window of
|
|
52
|
+
* dates, a set of lists, a topic and revenue attribution, which happens to
|
|
53
|
+
* reach people by email — so it is a section of the Marketing console and a
|
|
54
|
+
* message's page links out to it.
|
|
55
|
+
*/ {
|
|
56
|
+
id: 'messages',
|
|
57
|
+
label: 'Messages'
|
|
58
|
+
},
|
|
59
|
+
/*
|
|
60
|
+
* The reusable besigner documents a message is built from.
|
|
61
|
+
*
|
|
62
|
+
* Id and label say the same word on purpose: a template is the vocabulary
|
|
63
|
+
* everywhere else in this surface — an email names the template it renders,
|
|
64
|
+
* and the marketplace publishes them — so a URL saying anything else would
|
|
65
|
+
* be the one place the reader has to translate.
|
|
66
|
+
*/ {
|
|
67
|
+
id: 'templates',
|
|
68
|
+
label: 'Templates'
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
id: 'audiences',
|
|
72
|
+
label: 'Audiences'
|
|
73
|
+
},
|
|
74
|
+
// Between the audiences and the suppressions, which is where a topic sits
|
|
75
|
+
// conceptually: an audience is who you may reach, a suppression is who you
|
|
76
|
+
// may not, and a topic is the stream a recipient can leave without becoming
|
|
77
|
+
// either.
|
|
78
|
+
{
|
|
79
|
+
id: 'topics',
|
|
80
|
+
label: 'Topics'
|
|
81
|
+
},
|
|
82
|
+
/*
|
|
83
|
+
* WHO THE MAIL COMES FROM, as against who it goes to.
|
|
84
|
+
*
|
|
85
|
+
* After the three audience sections and before Suppressions, which is where
|
|
86
|
+
* the question sits: everything above decides who is reached, and this one
|
|
87
|
+
* decides what they see in the `From:` line. It is also the section a
|
|
88
|
+
* merchant is sent to from the composer when a send is refused for an
|
|
89
|
+
* unverified identity, so it has to be a route of its own.
|
|
90
|
+
*/ {
|
|
91
|
+
id: 'sending',
|
|
92
|
+
label: 'Sending'
|
|
93
|
+
},
|
|
94
|
+
// Beside the audiences rather than inside them (AGL-2410): a suppression is
|
|
95
|
+
// not a list you build, it is the reason a list you built did not all get
|
|
96
|
+
// mailed.
|
|
97
|
+
{
|
|
98
|
+
id: 'suppressions',
|
|
99
|
+
label: 'Suppressions'
|
|
100
|
+
}
|
|
101
|
+
] /*
|
|
102
|
+
* Rail ORDER decides where `/emails` lands: the shell redirects a bare hub
|
|
103
|
+
* URL to the first section in this list the reader may open (AGL-2501). There
|
|
104
|
+
* is deliberately no separate default constant — a second place to say which
|
|
105
|
+
* section is first is a second place for it to disagree with the rail.
|
|
106
|
+
*/ ;
|
|
107
|
+
|
|
108
|
+
//# sourceMappingURL=emails-console-sections.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/emails-console-sections.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport type { ConsoleNavSection } from '@aglyn/aglyn'\n\nexport type EmailsConsoleSectionId =\n | 'messages'\n | 'templates'\n | 'audiences'\n | 'topics'\n | 'sending'\n | 'suppressions'\n\n/**\n * The emails console's sections, in rail order (AGL-2501).\n *\n * One list, read twice and never copied: `plugin.ts` registers it on the nav\n * item so the shell can route and gate each section, and the page switches its\n * body on the id the shell resolves back.\n *\n * Ids appear in links people keep — treat them as persisted, and prefer\n * renaming a LABEL over an id. Where the two have to move together, the old\n * path is kept alive by a redirect rather than by an id that no longer says\n * what the section is. `audiences` and `suppressions` are also the `?tab=`\n * ids this page deep-linked by before its sections became routes, so a\n * bookmark that named one of those tabs still names the same section.\n *\n * No `navTabId` on any of them: every section ships with the surface, so they\n * inherit the Emails nav item's gate.\n */\nexport const EMAILS_CONSOLE_SECTIONS: readonly ConsoleNavSection[] = [\n /*\n * The individual messages: one row per email that was or will be sent, each\n * with its own report.\n *\n * A message is what this surface is about, and this section is named for\n * the record rather than for the surface holding it. The shell prints the\n * surface and the section together — in the header, in the breadcrumb and\n * in the browser tab — so a section whose name repeats its surface's says\n * nothing about which of the six is open, which is the one job a section\n * name has. `messages` is also the word the rest of this surface already\n * uses for the record: a message's own page, a template's sends table and\n * the campaign detail all call it that.\n *\n * `/emails/emails/{id}` is answered by a permanent redirect in the console's\n * `next.config.js`, on the terms the Workflows → Automation move set: a URL\n * somebody pasted keeps resolving, and it resolves to the one address the\n * console generates.\n *\n * The CAMPAIGN that groups messages is a marketing object — a window of\n * dates, a set of lists, a topic and revenue attribution, which happens to\n * reach people by email — so it is a section of the Marketing console and a\n * message's page links out to it.\n */\n { id: 'messages', label: 'Messages' },\n /*\n * The reusable besigner documents a message is built from.\n *\n * Id and label say the same word on purpose: a template is the vocabulary\n * everywhere else in this surface — an email names the template it renders,\n * and the marketplace publishes them — so a URL saying anything else would\n * be the one place the reader has to translate.\n */\n { id: 'templates', label: 'Templates' },\n { id: 'audiences', label: 'Audiences' },\n // Between the audiences and the suppressions, which is where a topic sits\n // conceptually: an audience is who you may reach, a suppression is who you\n // may not, and a topic is the stream a recipient can leave without becoming\n // either.\n { id: 'topics', label: 'Topics' },\n /*\n * WHO THE MAIL COMES FROM, as against who it goes to.\n *\n * After the three audience sections and before Suppressions, which is where\n * the question sits: everything above decides who is reached, and this one\n * decides what they see in the `From:` line. It is also the section a\n * merchant is sent to from the composer when a send is refused for an\n * unverified identity, so it has to be a route of its own.\n */\n { id: 'sending', label: 'Sending' },\n // Beside the audiences rather than inside them (AGL-2410): a suppression is\n // not a list you build, it is the reason a list you built did not all get\n // mailed.\n { id: 'suppressions', label: 'Suppressions' },\n]\n\n/*\n * Rail ORDER decides where `/emails` lands: the shell redirects a bare hub\n * URL to the first section in this list the reader may open (AGL-2501). There\n * is deliberately no separate default constant — a second place to say which\n * section is first is a second place for it to disagree with the rail.\n */\n"],"names":["EMAILS_CONSOLE_SECTIONS","id","label"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAYD;;;;;;;;;;;;;;;;CAgBC,GACD,OAAO,MAAMA,0BAAwD;IACnE;;;;;;;;;;;;;;;;;;;;;;GAsBC,GACD;QAAEC,IAAI;QAAYC,OAAO;IAAW;IACpC;;;;;;;GAOC,GACD;QAAED,IAAI;QAAaC,OAAO;IAAY;IACtC;QAAED,IAAI;QAAaC,OAAO;IAAY;IACtC,0EAA0E;IAC1E,2EAA2E;IAC3E,4EAA4E;IAC5E,UAAU;IACV;QAAED,IAAI;QAAUC,OAAO;IAAS;IAChC;;;;;;;;GAQC,GACD;QAAED,IAAI;QAAWC,OAAO;IAAU;IAClC,4EAA4E;IAC5E,0EAA0E;IAC1E,UAAU;IACV;QAAED,IAAI;QAAgBC,OAAO;IAAe;CAC7C,CAED;;;;;CAKC,IAPA"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ONE AUDIENCE: who it is, how it is built, and every person on it.
|
|
3
|
+
*
|
|
4
|
+
* ## Why a route rather than an expanded row
|
|
5
|
+
*
|
|
6
|
+
* The membership used to unfold inside the list table, which cost three
|
|
7
|
+
* things a merchant actually uses. A list was not linkable, so "check the
|
|
8
|
+
* Wholesale audience" could not be sent to anybody. The back button walked out
|
|
9
|
+
* of the whole surface rather than out of the list. And the list's own
|
|
10
|
+
* settings had to live under a table they were not about.
|
|
11
|
+
*
|
|
12
|
+
* ## What it reads
|
|
13
|
+
*
|
|
14
|
+
* The list document — one document, whatever the audience's size — plus one
|
|
15
|
+
* server aggregate for the subscriber total, plus the paged membership window
|
|
16
|
+
* the table below draws. The membership is PII and there is one document per
|
|
17
|
+
* subscriber, so it is read HERE, where somebody asked for it, and nowhere
|
|
18
|
+
* that merely lists audiences.
|
|
19
|
+
*
|
|
20
|
+
* ## The rule is stated, not just flagged
|
|
21
|
+
*
|
|
22
|
+
* A dynamic list is a claim about who is on it, and "Rule" as a chip is not
|
|
23
|
+
* that claim. The summary spells out every dimension the rule actually
|
|
24
|
+
* carries, so the answer to "why is this person not in the audience" is on the
|
|
25
|
+
* screen the question gets asked on.
|
|
26
|
+
*/
|
|
27
|
+
import { type ConsentGroup } from '@aglyn/aglyn';
|
|
28
|
+
export interface ListDetailCardProps {
|
|
29
|
+
hostId: string;
|
|
30
|
+
/**
|
|
31
|
+
* The controller this audience is being read AS, resolved by the page from
|
|
32
|
+
* the org document it already holds.
|
|
33
|
+
*
|
|
34
|
+
* Passed through rather than resolved here: the group is a fact about the
|
|
35
|
+
* ORG, one lookup serves every section, and a card that resolved its own
|
|
36
|
+
* would be a second place for the answer to come from.
|
|
37
|
+
*/
|
|
38
|
+
consentGroup: ConsentGroup;
|
|
39
|
+
listId: string;
|
|
40
|
+
/** The emails hub URL, for the way back to the audiences list. */
|
|
41
|
+
basePath: string;
|
|
42
|
+
}
|
|
43
|
+
export declare function ListDetailCard(props: ListDetailCardProps): import("react").JSX.Element;
|
|
44
|
+
export declare namespace ListDetailCard {
|
|
45
|
+
var displayName: string;
|
|
46
|
+
}
|
|
47
|
+
export default ListDetailCard;
|