@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,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
|