@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.
Files changed (144) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +58 -0
  4. package/src/index.d.ts +35 -0
  5. package/src/index.js +35 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/campaign-design-create-widget.d.ts +21 -0
  8. package/src/lib/components/campaign-design-create-widget.js +61 -0
  9. package/src/lib/components/campaign-design-create-widget.js.map +1 -0
  10. package/src/lib/components/campaign-sender-editor-widget.d.ts +18 -0
  11. package/src/lib/components/campaign-sender-editor-widget.js +39 -0
  12. package/src/lib/components/campaign-sender-editor-widget.js.map +1 -0
  13. package/src/lib/components/campaign-topic-options-widget.d.ts +19 -0
  14. package/src/lib/components/campaign-topic-options-widget.js +48 -0
  15. package/src/lib/components/campaign-topic-options-widget.js.map +1 -0
  16. package/src/lib/components/campaign-topic-select.d.ts +32 -0
  17. package/src/lib/components/campaign-topic-select.js +87 -0
  18. package/src/lib/components/campaign-topic-select.js.map +1 -0
  19. package/src/lib/components/dynamic-list-rule-fields.d.ts +173 -0
  20. package/src/lib/components/dynamic-list-rule-fields.js +1473 -0
  21. package/src/lib/components/dynamic-list-rule-fields.js.map +1 -0
  22. package/src/lib/components/email-blocks.d.ts +111 -0
  23. package/src/lib/components/email-blocks.js +875 -0
  24. package/src/lib/components/email-blocks.js.map +1 -0
  25. package/src/lib/components/email-design-preview.d.ts +62 -0
  26. package/src/lib/components/email-design-preview.js +174 -0
  27. package/src/lib/components/email-design-preview.js.map +1 -0
  28. package/src/lib/components/email-screens-card.d.ts +42 -0
  29. package/src/lib/components/email-screens-card.js +277 -0
  30. package/src/lib/components/email-screens-card.js.map +1 -0
  31. package/src/lib/components/email-template-detail.d.ts +48 -0
  32. package/src/lib/components/email-template-detail.js +681 -0
  33. package/src/lib/components/email-template-detail.js.map +1 -0
  34. package/src/lib/components/email-topic-detail.d.ts +32 -0
  35. package/src/lib/components/email-topic-detail.js +293 -0
  36. package/src/lib/components/email-topic-detail.js.map +1 -0
  37. package/src/lib/components/email-topics-card.d.ts +46 -0
  38. package/src/lib/components/email-topics-card.js +327 -0
  39. package/src/lib/components/email-topics-card.js.map +1 -0
  40. package/src/lib/components/email-zones.d.ts +28 -0
  41. package/src/lib/components/email-zones.js +20 -0
  42. package/src/lib/components/email-zones.js.map +1 -0
  43. package/src/lib/components/emails-console-page.d.ts +32 -0
  44. package/src/lib/components/emails-console-page.js +229 -0
  45. package/src/lib/components/emails-console-page.js.map +1 -0
  46. package/src/lib/components/emails-console-sections.d.ts +36 -0
  47. package/src/lib/components/emails-console-sections.js +108 -0
  48. package/src/lib/components/emails-console-sections.js.map +1 -0
  49. package/src/lib/components/list-detail-card.d.ts +47 -0
  50. package/src/lib/components/list-detail-card.js +273 -0
  51. package/src/lib/components/list-detail-card.js.map +1 -0
  52. package/src/lib/components/list-edit-card.d.ts +11 -0
  53. package/src/lib/components/list-edit-card.js +287 -0
  54. package/src/lib/components/list-edit-card.js.map +1 -0
  55. package/src/lib/components/list-import-drawer.d.ts +22 -0
  56. package/src/lib/components/list-import-drawer.js +662 -0
  57. package/src/lib/components/list-import-drawer.js.map +1 -0
  58. package/src/lib/components/list-members-panel.d.ts +94 -0
  59. package/src/lib/components/list-members-panel.js +686 -0
  60. package/src/lib/components/list-members-panel.js.map +1 -0
  61. package/src/lib/components/lists-card.d.ts +28 -0
  62. package/src/lib/components/lists-card.js +377 -0
  63. package/src/lib/components/lists-card.js.map +1 -0
  64. package/src/lib/components/sending-domain-detail.d.ts +26 -0
  65. package/src/lib/components/sending-domain-detail.js +496 -0
  66. package/src/lib/components/sending-domain-detail.js.map +1 -0
  67. package/src/lib/components/sending-domains-card.d.ts +33 -0
  68. package/src/lib/components/sending-domains-card.js +962 -0
  69. package/src/lib/components/sending-domains-card.js.map +1 -0
  70. package/src/lib/components/sending-sender-drawer.d.ts +94 -0
  71. package/src/lib/components/sending-sender-drawer.js +543 -0
  72. package/src/lib/components/sending-sender-drawer.js.map +1 -0
  73. package/src/lib/components/suppressions-card.d.ts +49 -0
  74. package/src/lib/components/suppressions-card.js +639 -0
  75. package/src/lib/components/suppressions-card.js.map +1 -0
  76. package/src/lib/components/use-org-email-topics.d.ts +79 -0
  77. package/src/lib/components/use-org-email-topics.js +111 -0
  78. package/src/lib/components/use-org-email-topics.js.map +1 -0
  79. package/src/lib/constants/bundle-common.d.ts +18 -0
  80. package/src/lib/constants/bundle-common.js +18 -0
  81. package/src/lib/constants/bundle-common.js.map +1 -0
  82. package/src/lib/hooks/use-org-company-options.d.ts +20 -0
  83. package/src/lib/hooks/use-org-company-options.js +138 -0
  84. package/src/lib/hooks/use-org-company-options.js.map +1 -0
  85. package/src/lib/hooks/use-org-contact-fields.d.ts +40 -0
  86. package/src/lib/hooks/use-org-contact-fields.js +91 -0
  87. package/src/lib/hooks/use-org-contact-fields.js.map +1 -0
  88. package/src/lib/hooks/use-org-contact-segments.d.ts +16 -0
  89. package/src/lib/hooks/use-org-contact-segments.js +55 -0
  90. package/src/lib/hooks/use-org-contact-segments.js.map +1 -0
  91. package/src/lib/hooks/use-org-crm-views.d.ts +8 -0
  92. package/src/lib/hooks/use-org-crm-views.js +74 -0
  93. package/src/lib/hooks/use-org-crm-views.js.map +1 -0
  94. package/src/lib/hooks/use-org-lists.d.ts +8 -0
  95. package/src/lib/hooks/use-org-lists.js +47 -0
  96. package/src/lib/hooks/use-org-lists.js.map +1 -0
  97. package/src/lib/model/email-design-document.d.ts +52 -0
  98. package/src/lib/model/email-design-document.js +62 -0
  99. package/src/lib/model/email-design-document.js.map +1 -0
  100. package/src/lib/model/index.d.ts +64 -0
  101. package/src/lib/model/index.js +71 -0
  102. package/src/lib/model/index.js.map +1 -0
  103. package/src/lib/model/sending-domain-status.d.ts +99 -0
  104. package/src/lib/model/sending-domain-status.js +196 -0
  105. package/src/lib/model/sending-domain-status.js.map +1 -0
  106. package/src/lib/model/template-provenance.d.ts +113 -0
  107. package/src/lib/model/template-provenance.js +107 -0
  108. package/src/lib/model/template-provenance.js.map +1 -0
  109. package/src/lib/model/template-report.d.ts +158 -0
  110. package/src/lib/model/template-report.js +249 -0
  111. package/src/lib/model/template-report.js.map +1 -0
  112. package/src/lib/plugin.d.ts +27 -0
  113. package/src/lib/plugin.js +163 -0
  114. package/src/lib/plugin.js.map +1 -0
  115. package/src/lib/server-console.d.ts +116 -0
  116. package/src/lib/server-console.js +422 -0
  117. package/src/lib/server-console.js.map +1 -0
  118. package/src/lib/server-email-drafts.d.ts +104 -0
  119. package/src/lib/server-email-drafts.js +381 -0
  120. package/src/lib/server-email-drafts.js.map +1 -0
  121. package/src/lib/server-list-gate.d.ts +183 -0
  122. package/src/lib/server-list-gate.js +365 -0
  123. package/src/lib/server-list-gate.js.map +1 -0
  124. package/src/lib/server-list-import.d.ts +199 -0
  125. package/src/lib/server-list-import.js +632 -0
  126. package/src/lib/server-list-import.js.map +1 -0
  127. package/src/lib/server-suppressions.d.ts +135 -0
  128. package/src/lib/server-suppressions.js +295 -0
  129. package/src/lib/server-suppressions.js.map +1 -0
  130. package/src/lib/server.d.ts +19 -0
  131. package/src/lib/server.js +834 -0
  132. package/src/lib/server.js.map +1 -0
  133. package/src/lib/site.d.ts +26 -0
  134. package/src/lib/site.js +81 -0
  135. package/src/lib/site.js.map +1 -0
  136. package/src/lib/unsubscribe-link.d.ts +311 -0
  137. package/src/lib/unsubscribe-link.js +398 -0
  138. package/src/lib/unsubscribe-link.js.map +1 -0
  139. package/src/lib/utils/create-email-screen.d.ts +59 -0
  140. package/src/lib/utils/create-email-screen.js +59 -0
  141. package/src/lib/utils/create-email-screen.js.map +1 -0
  142. package/src/lib/utils/generate-preset-id.d.ts +19 -0
  143. package/src/lib/utils/generate-preset-id.js +25 -0
  144. package/src/lib/utils/generate-preset-id.js.map +1 -0
@@ -0,0 +1,681 @@
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, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
19
+ import { buildRoute, PageHeaderRecord, pluginDocsHelp, Route } from "@aglyn/aglyn";
20
+ import { ICON_VARIANT_BESIGNER } from "@aglyn/shared-data-enums";
21
+ import { mdiBullhornOutline, mdiEyeOutline } from "@aglyn/shared-data-mdi";
22
+ import { AppLink, CardDisplay, MdiIcon } from "@aglyn/shared-ui-jsx";
23
+ import RowActionsMenu from "@aglyn/shared-ui-jsx/components/row-actions-menu.component";
24
+ import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
25
+ import { useConsoleHostRoute, useFirestore, useFirestoreCollection, useFirestoreDoc } from "@aglyn/tenant-feature-instance";
26
+ import { Alert, Button, Chip, Divider, Stack, TableBody, TableCell, TableHead, TableRow, TextField, Typography } from "@mui/material";
27
+ import { collection, doc, documentId, limit, orderBy, query, updateDoc, where } from "firebase/firestore";
28
+ import { useRouter } from "next/navigation";
29
+ import { Suspense, useMemo, useState } from "react";
30
+ import { useSnackbar } from "@aglyn/shared-ui-snackstack";
31
+ import { CAMPAIGN_SEND_CONTAINER_FIELD } from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
32
+ import { emailSendTimeMs } from "@aglyn/shared-ui-email-campaigns/model/email-record";
33
+ import { campaignSendDisplay } from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
34
+ import { templateProvenance } from "../model/template-provenance.js";
35
+ import { templateReport } from "../model/template-report.js";
36
+ import EmailDesignPreview from "./email-design-preview.js";
37
+ import { EMAIL_TEMPLATE_RECIPIENTS_ZONE } from "./email-zones.js";
38
+ import { Figure, RateRow, Section } from "@aglyn/shared-ui-email-campaigns/components/report-figures";
39
+ import { useConsoleWidgetSlot } from "@aglyn/aglyn/app-utils/console-widget-slot-context";
40
+ import { pluginRecordHref } from "@aglyn/aglyn/plugin-manager/plugin-record-routes";
41
+ /**
42
+ * How many of a template's messages one read covers.
43
+ *
44
+ * A CEILING on the read, not a page size: the report is a single figure per
45
+ * quantity, so there is nothing to page through. Past it every total is a
46
+ * floor and the report says so — the same treatment `audienceSizeTruncated`
47
+ * gets, and for the same reason.
48
+ */ export const TEMPLATE_CAMPAIGN_CEILING = 100;
49
+ const previewDocsHelp = pluginDocsHelp('designedEmails', {
50
+ anchor: '#send-it',
51
+ excerpt: 'The template as an inbox receives it, drawn by the same renderer the ' + 'send path uses. Merge tokens are left standing — a real send fills them ' + 'from each recipient.'
52
+ });
53
+ const templateDocsHelp = pluginDocsHelp('designedEmails', {
54
+ anchor: '#send-it',
55
+ excerpt: 'One email template: what it looks like, every message sent from it, ' + 'and what those messages did — each rate over the population it was ' + 'measured against.'
56
+ });
57
+ /**
58
+ * ONE EMAIL TEMPLATE: what it looks like, and what it has done.
59
+ *
60
+ * ## What was missing
61
+ *
62
+ * A template had a row in a list and no page. The only way to see one was to
63
+ * open the besigner, which is the editor — it draws canvas nodes in a
64
+ * browser, not the HTML an inbox receives — and the only way to see how it
65
+ * performed was to find each message sent from it and read those reports one
66
+ * at a time. A template used by six messages had its engagement in six places
67
+ * and nowhere.
68
+ *
69
+ * ## Not necessarily this org's template
70
+ *
71
+ * A template can be installed from a marketplace listing, in which case its
72
+ * content is versioned by somebody else and can be withdrawn. Provenance and
73
+ * standing are read off the document itself — see `template-provenance.ts` —
74
+ * so this page costs no marketplace read and still never presents an
75
+ * installed template as locally authored.
76
+ *
77
+ * ## What this page reads
78
+ *
79
+ * The screen document, its published version, and the messages naming this
80
+ * template — bounded at {@link TEMPLATE_CAMPAIGN_CEILING}. The recipients
81
+ * table is its own card and its own request, so a reader who came for the
82
+ * preview does not pay for the delivery-log query.
83
+ */ export function EmailTemplateDetail(props) {
84
+ var _ref, _ref1, _ref2, _ref3;
85
+ const { hostId, screenId, basePath } = props;
86
+ const { orgSlug, subdomain } = useConsoleHostRoute(hostId);
87
+ /*
88
+ * A campaign's page is another plugin's, so its address is asked for: the
89
+ * owner publishes where a `campaign` is read and this page links there.
90
+ * Null where nobody does, or before the console's URL has resolved.
91
+ */ const campaignHref = (campaignId)=>orgSlug && subdomain ? pluginRecordHref('campaign', {
92
+ orgSlug,
93
+ host: subdomain
94
+ }, campaignId) : null;
95
+ const RecipientsZone = useConsoleWidgetSlot();
96
+ const firestore = useFirestore();
97
+ const router = useRouter();
98
+ const { enqueueSnackbar } = useSnackbar();
99
+ /*==========================================
100
+ * RENAMING THE DESIGN, on its own page.
101
+ *
102
+ * The name is the only thing that tells one design from another in the
103
+ * campaign composer's picker, and until now nothing could change it — so a
104
+ * site accumulated indistinguishable "Untitled email" rows. Edit lives
105
+ * here, on the record's detail page, like every other record edit.
106
+ *
107
+ * `null` means "not editing": the field shows the live document's name
108
+ * until the reader types, so an untouched field never writes a stale seed
109
+ * back — only a value the reader actually entered is ever saved, and only
110
+ * the one field.
111
+ *=========================================*/ const [nameDraft, setNameDraft] = useState(null);
112
+ const [renaming, setRenaming] = useState(false);
113
+ const handleRename = async ()=>{
114
+ const next = (nameDraft != null ? nameDraft : '').trim();
115
+ if (!next || renaming) return;
116
+ setRenaming(true);
117
+ try {
118
+ await updateDoc(doc(firestore, 'hosts', hostId, 'screens', screenId), {
119
+ displayName: next
120
+ });
121
+ setNameDraft(null);
122
+ enqueueSnackbar('Design renamed', {
123
+ variant: 'success',
124
+ persist: false
125
+ });
126
+ } catch (error) {
127
+ console.error(error);
128
+ enqueueSnackbar('The design could not be renamed', {
129
+ variant: 'error'
130
+ });
131
+ } finally{
132
+ setRenaming(false);
133
+ }
134
+ };
135
+ const { data: screen, status } = useFirestoreDoc(()=>doc(firestore, 'hosts', hostId, 'screens', screenId), [
136
+ firestore,
137
+ hostId,
138
+ screenId
139
+ ], {
140
+ idField: '$id'
141
+ });
142
+ // Three states, not two: a document still loading and one that does not
143
+ // exist both arrive as `undefined`, and rendering an empty report for the
144
+ // second is how a mistyped id reads as a template nobody ever sent.
145
+ const notFound = status !== 'loading' && !screen;
146
+ const versionId = screen == null ? void 0 : screen.versionId;
147
+ const { data: version } = useFirestoreDoc(()=>versionId ? doc(firestore, 'hosts', hostId, 'screens', screenId, 'versions', versionId) : null, [
148
+ firestore,
149
+ hostId,
150
+ screenId,
151
+ versionId
152
+ ]);
153
+ /*
154
+ * The messages sent from this template, ordered by DOCUMENT ID.
155
+ *
156
+ * Not by date, and that is forced rather than chosen: a sent message
157
+ * carries `sentAt`, a scheduled one carries `sendAtMs`, and no writer
158
+ * stamps a `createdAt` — so `orderBy` on either would not mis-sort this
159
+ * list, it would DROP every message written by the other branch. Ordering
160
+ * on the document id is the one ordering every message satisfies, it needs
161
+ * no composite index beside the equality filter, and the rows are sorted by
162
+ * date below over a window this component already holds.
163
+ *
164
+ * One MORE than the ceiling, so truncation is a fact rather than a guess.
165
+ */ const { data: messageDocs } = useFirestoreCollection(()=>query(collection(firestore, 'hosts', hostId, 'campaigns'), where('templateScreenId', '==', screenId), orderBy(documentId()), limit(TEMPLATE_CAMPAIGN_CEILING + 1)), [
166
+ firestore,
167
+ hostId,
168
+ screenId
169
+ ], {
170
+ idField: '$id'
171
+ });
172
+ const truncated = ((_ref = messageDocs == null ? void 0 : messageDocs.length) != null ? _ref : 0) > TEMPLATE_CAMPAIGN_CEILING;
173
+ const messages = useMemo(()=>(messageDocs != null ? messageDocs : []).slice(0, TEMPLATE_CAMPAIGN_CEILING).map((message)=>{
174
+ var _message_status, _message_status1, _message_audience;
175
+ return _extends({
176
+ campaignId: String(message.$id),
177
+ subject: String(message.subject || 'Untitled email'),
178
+ /*
179
+ * A message that has not gone out has NO send time, and that is
180
+ * what `null` means here — not "unknown". The report measures
181
+ * engagement over sent messages only, so a scheduled one
182
+ * contributes to the list below and to no figure above it.
183
+ */ sentAtMs: String((_message_status = message.status) != null ? _message_status : '') === 'sent' ? emailSendTimeMs(message) || null : null,
184
+ status: String((_message_status1 = message.status) != null ? _message_status1 : 'sent'),
185
+ audience: String((_message_audience = message.audience) != null ? _message_audience : '')
186
+ }, message.listId ? {
187
+ listId: String(message.listId)
188
+ } : {}, message.listName ? {
189
+ listName: String(message.listName)
190
+ } : {}, {
191
+ stats: message.stats,
192
+ scheduledForMs: emailSendTimeMs(message)
193
+ }, message[CAMPAIGN_SEND_CONTAINER_FIELD] ? {
194
+ emailCampaignId: String(message[CAMPAIGN_SEND_CONTAINER_FIELD])
195
+ } : {});
196
+ }), [
197
+ messageDocs
198
+ ]);
199
+ const orderedMessages = useMemo(()=>[
200
+ ...messages
201
+ ].sort((a, b)=>b.scheduledForMs - a.scheduledForMs), [
202
+ messages
203
+ ]);
204
+ const report = useMemo(()=>templateReport(messages, truncated), [
205
+ messages,
206
+ truncated
207
+ ]);
208
+ const provenance = useMemo(()=>templateProvenance(screen), [
209
+ screen
210
+ ]);
211
+ const messageHref = (message)=>`${basePath}/messages/${message.campaignId}`;
212
+ /**
213
+ * What one message sent from this template can be opened into.
214
+ *
215
+ * Its own report, and the campaign that grouped it. The template is the page
216
+ * the reader is standing on, so it is not offered a third time. A message
217
+ * sent before campaigns existed belongs to no container, and the item says
218
+ * so rather than vanishing.
219
+ */ const messageActions = (message)=>[
220
+ {
221
+ key: 'details',
222
+ label: 'Open report',
223
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
224
+ path: mdiEyeOutline.path,
225
+ size: 0.8
226
+ }),
227
+ href: messageHref(message)
228
+ },
229
+ {
230
+ key: 'campaign',
231
+ label: 'Open its campaign',
232
+ icon: /*#__PURE__*/ _jsx(MdiIcon, {
233
+ path: mdiBullhornOutline.path,
234
+ size: 0.8
235
+ }),
236
+ // The one href here that is not under this surface's own base path.
237
+ href: message.emailCampaignId && campaignHref(message.emailCampaignId) || undefined,
238
+ disabled: !message.emailCampaignId || !campaignHref(message.emailCampaignId),
239
+ disabledReason: message.emailCampaignId ? 'This site’s console URL has not resolved yet' : 'Sent before campaigns grouped their emails, so it belongs to none'
240
+ }
241
+ ];
242
+ const listUrl = `${basePath}/templates`;
243
+ const besignerUrl = orgSlug && subdomain && versionId ? buildRoute(Route.SCREEN_BESIGNER, {
244
+ orgSlug,
245
+ host: subdomain,
246
+ screenId,
247
+ versionId
248
+ }) : '';
249
+ /*
250
+ * The header's actions: the way back, and the way in.
251
+ *
252
+ * The same pair the screen, component, template and layout detail pages
253
+ * carry — a link to the listing and a contained button into the besigner
254
+ * wearing the besigner icon. It hangs off `CardDisplay`'s header rather
255
+ * than the page header because Emails is a surface with a SECTION RAIL: an
256
+ * action beside the page title would read as the whole surface's, and this
257
+ * one belongs to the template the section is showing. The rule is the
258
+ * console's own — page header where there are no sections, section card
259
+ * header where there are — and it is why the Forms detail surface, which
260
+ * has no rail, publishes its besigner button upward instead.
261
+ */ const headerActions = /*#__PURE__*/ _jsxs(Stack, {
262
+ direction: "row",
263
+ spacing: 1,
264
+ sx: {
265
+ alignItems: 'center'
266
+ },
267
+ children: [
268
+ /*#__PURE__*/ _jsx(Button, _extends({
269
+ component: AppLink
270
+ }, {
271
+ componentVariant: 'naked',
272
+ nativeButton: false
273
+ }, {
274
+ href: listUrl,
275
+ size: "small",
276
+ color: "primary",
277
+ children: 'All templates'
278
+ })),
279
+ besignerUrl ? /*#__PURE__*/ _jsx(Button, _extends({
280
+ component: AppLink
281
+ }, {
282
+ componentVariant: 'naked',
283
+ nativeButton: false
284
+ }, {
285
+ href: besignerUrl,
286
+ size: "small",
287
+ variant: "contained",
288
+ startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
289
+ color: "inherit",
290
+ path: ICON_VARIANT_BESIGNER.path
291
+ }),
292
+ children: 'Edit in besigner'
293
+ })) : /*#__PURE__*/ _jsx(Button, {
294
+ size: "small",
295
+ variant: "contained",
296
+ disabled: true,
297
+ startIcon: /*#__PURE__*/ _jsx(MdiIcon, {
298
+ color: "inherit",
299
+ path: ICON_VARIANT_BESIGNER.path
300
+ }),
301
+ children: 'Edit in besigner'
302
+ })
303
+ ]
304
+ });
305
+ if (notFound) {
306
+ return /*#__PURE__*/ _jsx(CardDisplay, {
307
+ header: 'Email template',
308
+ help: templateDocsHelp,
309
+ contentGutterX: true,
310
+ contentGutterY: true,
311
+ HeaderProps: {
312
+ action: headerActions
313
+ },
314
+ children: /*#__PURE__*/ _jsx(Typography, {
315
+ variant: "body2",
316
+ color: "text.secondary",
317
+ children: 'This template could not be loaded. It may have been deleted.'
318
+ })
319
+ });
320
+ }
321
+ return /*#__PURE__*/ _jsxs(Stack, {
322
+ spacing: 3,
323
+ children: [
324
+ /*#__PURE__*/ _jsx(PageHeaderRecord, {
325
+ title: screen ? screen.displayName || screenId : undefined
326
+ }),
327
+ /*#__PURE__*/ _jsx(CardDisplay, {
328
+ header: 'Email template',
329
+ help: templateDocsHelp,
330
+ contentGutterX: true,
331
+ contentGutterY: true,
332
+ HeaderProps: {
333
+ action: headerActions
334
+ },
335
+ children: /*#__PURE__*/ _jsxs(Stack, {
336
+ spacing: 3,
337
+ children: [
338
+ provenance.note ? /*#__PURE__*/ _jsx(Alert, {
339
+ severity: provenance.warn ? 'warning' : 'info',
340
+ children: provenance.note
341
+ }) : null,
342
+ report.caveats.map((caveat)=>/*#__PURE__*/ _jsx(Alert, {
343
+ severity: "info",
344
+ children: caveat.message
345
+ }, caveat.id)),
346
+ /*#__PURE__*/ _jsx(Section, {
347
+ title: "Name",
348
+ children: /*#__PURE__*/ _jsxs(Stack, {
349
+ direction: "row",
350
+ spacing: 1,
351
+ sx: {
352
+ alignItems: 'flex-start'
353
+ },
354
+ children: [
355
+ /*#__PURE__*/ _jsx(TextField, {
356
+ label: "Design name",
357
+ size: "small",
358
+ value: nameDraft != null ? nameDraft : String((_ref1 = screen == null ? void 0 : screen.displayName) != null ? _ref1 : ''),
359
+ onChange: (event)=>setNameDraft(event.target.value),
360
+ sx: {
361
+ minWidth: 280
362
+ },
363
+ helperText: "What the campaign composer's design picker shows"
364
+ }),
365
+ /*#__PURE__*/ _jsx(Button, {
366
+ size: "small",
367
+ disabled: nameDraft === null || !nameDraft.trim() || renaming,
368
+ onClick: ()=>void handleRename(),
369
+ children: renaming ? 'Saving…' : 'Rename'
370
+ })
371
+ ]
372
+ })
373
+ }),
374
+ /*#__PURE__*/ _jsx(Divider, {}),
375
+ /*#__PURE__*/ _jsx(Section, {
376
+ title: "Delivery",
377
+ children: /*#__PURE__*/ _jsxs(Stack, {
378
+ direction: "row",
379
+ spacing: 4,
380
+ useFlexGap: true,
381
+ sx: {
382
+ flexWrap: 'wrap'
383
+ },
384
+ children: [
385
+ /*#__PURE__*/ _jsx(Figure, {
386
+ label: "Emails",
387
+ value: report.sentCampaigns,
388
+ note: "sent from this template"
389
+ }),
390
+ /*#__PURE__*/ _jsx(Figure, {
391
+ label: "Addressed",
392
+ value: report.recipients,
393
+ note: "after each send's cap"
394
+ }),
395
+ /*#__PURE__*/ _jsx(Figure, {
396
+ label: "Sent",
397
+ value: report.sent,
398
+ note: "accepted by the provider"
399
+ }),
400
+ /*#__PURE__*/ _jsx(Figure, {
401
+ label: "Delivered",
402
+ value: report.delivered,
403
+ note: "accepted by the receiving server"
404
+ }),
405
+ /*#__PURE__*/ _jsx(Figure, {
406
+ label: "Bounced",
407
+ value: report.bounced,
408
+ note: "of sent"
409
+ }),
410
+ /*#__PURE__*/ _jsx(Figure, {
411
+ label: "Marked as spam",
412
+ value: report.complained,
413
+ note: "of delivered"
414
+ })
415
+ ]
416
+ })
417
+ }),
418
+ /*#__PURE__*/ _jsx(Divider, {}),
419
+ /*#__PURE__*/ _jsx(Section, {
420
+ title: "Engagement",
421
+ children: /*#__PURE__*/ _jsxs(Stack, {
422
+ direction: "row",
423
+ spacing: 4,
424
+ useFlexGap: true,
425
+ sx: {
426
+ flexWrap: 'wrap'
427
+ },
428
+ children: [
429
+ /*#__PURE__*/ _jsx(Figure, {
430
+ label: "Opens",
431
+ value: report.opens,
432
+ note: "every open, repeats included"
433
+ }),
434
+ /*#__PURE__*/ _jsx(Figure, {
435
+ label: "Readers who opened",
436
+ value: report.uniqueOpens,
437
+ note: "distinct recipients"
438
+ }),
439
+ /*#__PURE__*/ _jsx(Figure, {
440
+ label: "Clicks",
441
+ value: report.clicks,
442
+ note: "every click, repeats included"
443
+ }),
444
+ /*#__PURE__*/ _jsx(Figure, {
445
+ label: "Readers who clicked",
446
+ value: report.uniqueClicks,
447
+ note: "distinct recipients"
448
+ }),
449
+ /*#__PURE__*/ _jsx(Figure, {
450
+ label: "Unsubscribed",
451
+ value: report.unsubscribes,
452
+ note: "through these emails' links"
453
+ })
454
+ ]
455
+ })
456
+ }),
457
+ /*#__PURE__*/ _jsx(Divider, {}),
458
+ /*#__PURE__*/ _jsx(Section, {
459
+ title: "Rates",
460
+ children: /*#__PURE__*/ _jsxs(Stack, {
461
+ spacing: 1,
462
+ children: [
463
+ /*#__PURE__*/ _jsx(RateRow, {
464
+ label: "Delivery rate",
465
+ rate: report.rates.delivery
466
+ }),
467
+ /*#__PURE__*/ _jsx(RateRow, {
468
+ label: "Open rate",
469
+ rate: report.rates.open
470
+ }),
471
+ /*#__PURE__*/ _jsx(RateRow, {
472
+ label: "Click rate",
473
+ rate: report.rates.click
474
+ }),
475
+ /*#__PURE__*/ _jsx(RateRow, {
476
+ label: "Click-to-open rate",
477
+ rate: report.rates.clickToOpen
478
+ }),
479
+ /*#__PURE__*/ _jsx(RateRow, {
480
+ label: "Bounce rate",
481
+ rate: report.rates.bounce
482
+ }),
483
+ /*#__PURE__*/ _jsx(RateRow, {
484
+ label: "Complaint rate",
485
+ rate: report.rates.complaint
486
+ }),
487
+ /*#__PURE__*/ _jsx(RateRow, {
488
+ label: "Unsubscribe rate",
489
+ rate: report.rates.unsubscribe
490
+ })
491
+ ]
492
+ })
493
+ }),
494
+ /*#__PURE__*/ _jsx(Divider, {}),
495
+ /*#__PURE__*/ _jsx(Section, {
496
+ title: "Who this went to",
497
+ children: report.audiences.length ? /*#__PURE__*/ _jsxs(_Fragment, {
498
+ children: [
499
+ /*#__PURE__*/ _jsxs(ScrollTable, {
500
+ size: "small",
501
+ children: [
502
+ /*#__PURE__*/ _jsx(TableHead, {
503
+ children: /*#__PURE__*/ _jsxs(TableRow, {
504
+ children: [
505
+ /*#__PURE__*/ _jsx(TableCell, {
506
+ children: 'Audience'
507
+ }),
508
+ /*#__PURE__*/ _jsx(TableCell, {
509
+ align: "right",
510
+ children: 'Emails'
511
+ }),
512
+ /*#__PURE__*/ _jsx(TableCell, {
513
+ align: "right",
514
+ children: 'Addressed'
515
+ })
516
+ ]
517
+ })
518
+ }),
519
+ /*#__PURE__*/ _jsx(TableBody, {
520
+ children: report.audiences.map((audience)=>/*#__PURE__*/ _jsxs(TableRow, {
521
+ children: [
522
+ /*#__PURE__*/ _jsx(TableCell, {
523
+ children: audience.label
524
+ }),
525
+ /*#__PURE__*/ _jsx(TableCell, {
526
+ align: "right",
527
+ children: audience.campaigns.toLocaleString()
528
+ }),
529
+ /*#__PURE__*/ _jsx(TableCell, {
530
+ align: "right",
531
+ sx: {
532
+ fontWeight: 'bold'
533
+ },
534
+ children: audience.addressed.toLocaleString()
535
+ })
536
+ ]
537
+ }, audience.id))
538
+ })
539
+ ]
540
+ }),
541
+ /*#__PURE__*/ _jsx(Typography, {
542
+ variant: "caption",
543
+ color: "text.secondary",
544
+ children: 'Each list is named as it was when the email was sent, so ' + 'renaming or deleting a list does not rewrite what a past ' + 'send went to.'
545
+ })
546
+ ]
547
+ }) : /*#__PURE__*/ _jsx(Typography, {
548
+ variant: "body2",
549
+ color: "text.secondary",
550
+ children: 'No email sent from this template has gone out yet.'
551
+ })
552
+ }),
553
+ orderedMessages.length ? /*#__PURE__*/ _jsxs(_Fragment, {
554
+ children: [
555
+ /*#__PURE__*/ _jsx(Divider, {}),
556
+ /*#__PURE__*/ _jsx(Section, {
557
+ title: "Emails using this template",
558
+ children: /*#__PURE__*/ _jsxs(ScrollTable, {
559
+ size: "small",
560
+ children: [
561
+ /*#__PURE__*/ _jsx(TableHead, {
562
+ children: /*#__PURE__*/ _jsxs(TableRow, {
563
+ children: [
564
+ /*#__PURE__*/ _jsx(TableCell, {
565
+ children: 'Subject'
566
+ }),
567
+ /*#__PURE__*/ _jsx(TableCell, {
568
+ children: 'State'
569
+ }),
570
+ /*#__PURE__*/ _jsx(TableCell, {
571
+ children: 'When'
572
+ }),
573
+ /*#__PURE__*/ _jsx(TableCell, {
574
+ align: "right",
575
+ children: 'Addressed'
576
+ }),
577
+ /*#__PURE__*/ _jsx(TableCell, {
578
+ align: "right",
579
+ children: 'Opens'
580
+ }),
581
+ /*#__PURE__*/ _jsx(TableCell, {
582
+ align: "right",
583
+ children: 'Clicks'
584
+ }),
585
+ /*#__PURE__*/ _jsx(TableCell, {
586
+ align: "right"
587
+ })
588
+ ]
589
+ })
590
+ }),
591
+ /*#__PURE__*/ _jsx(TableBody, {
592
+ children: orderedMessages.map((message)=>{
593
+ var _ref, _ref1, _ref2;
594
+ var _message_stats, _message_stats1, _message_stats2;
595
+ return /*#__PURE__*/ _jsxs(TableRow, {
596
+ hover: true,
597
+ onClick: ()=>router.push(messageHref(message)),
598
+ sx: {
599
+ cursor: 'pointer'
600
+ },
601
+ children: [
602
+ /*#__PURE__*/ _jsx(TableCell, {
603
+ children: /*#__PURE__*/ _jsx(AppLink, {
604
+ href: messageHref(message),
605
+ onClick: (event)=>event.stopPropagation(),
606
+ children: message.subject
607
+ })
608
+ }),
609
+ /*#__PURE__*/ _jsx(TableCell, {
610
+ children: /*#__PURE__*/ _jsx(Chip, {
611
+ size: "small",
612
+ label: campaignSendDisplay(message).label
613
+ })
614
+ }),
615
+ /*#__PURE__*/ _jsx(TableCell, {
616
+ children: message.scheduledForMs ? new Date(message.scheduledForMs).toLocaleString() : '—'
617
+ }),
618
+ /*#__PURE__*/ _jsx(TableCell, {
619
+ align: "right",
620
+ children: Number((_ref = (_message_stats = message.stats) == null ? void 0 : _message_stats.recipients) != null ? _ref : 0).toLocaleString()
621
+ }),
622
+ /*#__PURE__*/ _jsx(TableCell, {
623
+ align: "right",
624
+ children: Number((_ref1 = (_message_stats1 = message.stats) == null ? void 0 : _message_stats1.opens) != null ? _ref1 : 0).toLocaleString()
625
+ }),
626
+ /*#__PURE__*/ _jsx(TableCell, {
627
+ align: "right",
628
+ children: Number((_ref2 = (_message_stats2 = message.stats) == null ? void 0 : _message_stats2.clicks) != null ? _ref2 : 0).toLocaleString()
629
+ }),
630
+ /*#__PURE__*/ _jsx(TableCell, {
631
+ align: "right",
632
+ sx: {
633
+ width: 56
634
+ },
635
+ onClick: (event)=>event.stopPropagation(),
636
+ children: /*#__PURE__*/ _jsx(RowActionsMenu, {
637
+ label: String(message.subject),
638
+ items: messageActions(message)
639
+ })
640
+ })
641
+ ]
642
+ }, message.campaignId);
643
+ })
644
+ })
645
+ ]
646
+ })
647
+ })
648
+ ]
649
+ }) : null
650
+ ]
651
+ })
652
+ }),
653
+ RecipientsZone ? /*#__PURE__*/ _jsx(Suspense, {
654
+ fallback: null,
655
+ children: /*#__PURE__*/ _jsx(RecipientsZone, {
656
+ slot: EMAIL_TEMPLATE_RECIPIENTS_ZONE.id,
657
+ hostId: hostId,
658
+ screenId: screenId
659
+ })
660
+ }) : null,
661
+ /*#__PURE__*/ _jsx(CardDisplay, {
662
+ header: 'Preview',
663
+ help: previewDocsHelp,
664
+ contentGutterX: true,
665
+ contentGutterY: true,
666
+ children: /*#__PURE__*/ _jsx(EmailDesignPreview, {
667
+ hostId: hostId,
668
+ nodes: version == null ? void 0 : version.nodes,
669
+ loading: version === undefined && Boolean(versionId),
670
+ subject: String((_ref2 = screen == null ? void 0 : screen.emailSubject) != null ? _ref2 : ''),
671
+ preheader: String((_ref3 = screen == null ? void 0 : screen.emailPreheader) != null ? _ref3 : ''),
672
+ emptyMessage: 'This template has nothing in it yet. Open it in the ' + 'besigner to build the email.'
673
+ })
674
+ })
675
+ ]
676
+ });
677
+ }
678
+ EmailTemplateDetail.displayName = 'EmailTemplateDetail';
679
+ export default EmailTemplateDetail;
680
+
681
+ //# sourceMappingURL=email-template-detail.js.map