@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,327 @@
|
|
|
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 { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
18
|
+
import { createResourceUid, isEmailTopicId, pluginDocsHelp, DEFAULT_EMAIL_TOPICS, EMAIL_TOPICS_COLLECTION } from "@aglyn/aglyn";
|
|
19
|
+
import { mdiArchiveArrowUpOutline, mdiArchiveOutline, mdiPencilOutline } from "@aglyn/shared-data-mdi";
|
|
20
|
+
import { AppLink, CardDisplay, MdiIcon, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import RowActionsMenu from "@aglyn/shared-ui-jsx/components/row-actions-menu.component";
|
|
22
|
+
import { CreateArtifactDrawer } from "@aglyn/shared-ui-jsx-forms";
|
|
23
|
+
import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
|
|
24
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
25
|
+
import { Button, Chip, Stack, TableBody, TableCell, TableHead, TableRow, Typography } from "@mui/material";
|
|
26
|
+
import { doc, setDoc } from "firebase/firestore";
|
|
27
|
+
import { useRouter } from "next/navigation";
|
|
28
|
+
import { useCallback, useState } from "react";
|
|
29
|
+
import { useFirestore } from "@aglyn/tenant-feature-instance";
|
|
30
|
+
import { useOrgEmailTopics, writeEmailTopic } from "./use-org-email-topics.js";
|
|
31
|
+
/**
|
|
32
|
+
* Email topics: the streams a recipient can leave one at a time.
|
|
33
|
+
*
|
|
34
|
+
* ## What this closes
|
|
35
|
+
*
|
|
36
|
+
* `docs/specs/email-competitive-gaps.md` §1f is a table of what six
|
|
37
|
+
* competitors have, and the preference-center row was the one where every one
|
|
38
|
+
* of them had something and we had nothing: unsubscribe was all-or-nothing per
|
|
39
|
+
* site. This card is the merchant's half of the fix — the recipient's half is
|
|
40
|
+
* the hosted preference page the campaign footer now links to.
|
|
41
|
+
*
|
|
42
|
+
* ## Why the four built-ins are not documents
|
|
43
|
+
*
|
|
44
|
+
* Every org that exists today has an empty `emailTopics` collection, and a
|
|
45
|
+
* seeding migration would leave any org created while it ran with none. So
|
|
46
|
+
* `DEFAULT_EMAIL_TOPICS` is the FLOOR of the catalog rather than its initial
|
|
47
|
+
* contents: the four are present for every org with no write anywhere, and
|
|
48
|
+
* saving one on its detail page writes a document at the same id that renames
|
|
49
|
+
* or retires it. A built-in with no stored document is not a missing row, it
|
|
50
|
+
* is a row nobody has needed to change.
|
|
51
|
+
*
|
|
52
|
+
* ## Create is a drawer; edit is the topic's own page
|
|
53
|
+
*
|
|
54
|
+
* The console's standing shape for a list surface: creating opens the shared
|
|
55
|
+
* `CreateArtifactDrawer`, the same one Screens, Components, Layouts and
|
|
56
|
+
* Templates use, and changing an existing record happens on that record's
|
|
57
|
+
* page rather than in a form stacked above the table. So this card lists and
|
|
58
|
+
* routes, and `email-topic-detail.tsx` edits.
|
|
59
|
+
*
|
|
60
|
+
* ## The row grammar the rest of the surface uses
|
|
61
|
+
*
|
|
62
|
+
* The same one the audiences table sets: the row opens the topic, the topic's
|
|
63
|
+
* name is ALSO a real link so it can be middle-clicked and copied, and the
|
|
64
|
+
* secondary actions sit behind the shared `RowActionsMenu` rather than as text
|
|
65
|
+
* buttons in the row. Retire is the reason the menu has more than one entry —
|
|
66
|
+
* it is the only thing that can be done to a topic without opening it, and it
|
|
67
|
+
* is destructive enough that it should not sit one mis-click from the row's
|
|
68
|
+
* own open handler.
|
|
69
|
+
*/ export function EmailTopicsCard(props) {
|
|
70
|
+
const { hostId, basePath } = props;
|
|
71
|
+
const firestore = useFirestore();
|
|
72
|
+
const router = useRouter();
|
|
73
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
74
|
+
const { confirm } = useConfirmationContext();
|
|
75
|
+
const { topics, scope } = useOrgEmailTopics(hostId);
|
|
76
|
+
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
77
|
+
const [error, setError] = useState(null);
|
|
78
|
+
const [busyTopicId, setBusyTopicId] = useState('');
|
|
79
|
+
/** The topic's own page, which is also its edit page. */ const topicHref = useCallback((topicId)=>`${basePath}/topics/${topicId}`, [
|
|
80
|
+
basePath
|
|
81
|
+
]);
|
|
82
|
+
const create = useCallback(async (values)=>{
|
|
83
|
+
var _ref;
|
|
84
|
+
if (!scope) return;
|
|
85
|
+
const name = String((_ref = values == null ? void 0 : values.displayName) != null ? _ref : '').trim();
|
|
86
|
+
if (!name) return;
|
|
87
|
+
// `createResourceUid` is nanoid's `A-Za-z0-9_-`, so the id it mints
|
|
88
|
+
// always satisfies `isEmailTopicId`. Checked anyway, because this id is
|
|
89
|
+
// signed into every unsubscribe link the topic ever mints and the send
|
|
90
|
+
// path refuses one that is not usable — a topic nothing can be sent
|
|
91
|
+
// under is a row the merchant would have no way to diagnose.
|
|
92
|
+
const id = createResourceUid();
|
|
93
|
+
if (!isEmailTopicId(id)) {
|
|
94
|
+
return void enqueueSnackbar('Could not create the topic', {
|
|
95
|
+
variant: 'error'
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
var _ref1;
|
|
100
|
+
await setDoc(doc(firestore, scope[0], scope[1], EMAIL_TOPICS_COLLECTION, id), {
|
|
101
|
+
name,
|
|
102
|
+
description: String((_ref1 = values == null ? void 0 : values.description) != null ? _ref1 : '').trim(),
|
|
103
|
+
archived: false
|
|
104
|
+
});
|
|
105
|
+
setDrawerOpen(false);
|
|
106
|
+
setError(null);
|
|
107
|
+
enqueueSnackbar('Topic added', {
|
|
108
|
+
variant: 'success',
|
|
109
|
+
persist: false
|
|
110
|
+
});
|
|
111
|
+
// Straight to the record, which is where every further change is made.
|
|
112
|
+
router.push(topicHref(id));
|
|
113
|
+
} catch (caught) {
|
|
114
|
+
console.error(caught);
|
|
115
|
+
setError(caught);
|
|
116
|
+
}
|
|
117
|
+
}, [
|
|
118
|
+
scope,
|
|
119
|
+
firestore,
|
|
120
|
+
enqueueSnackbar,
|
|
121
|
+
topicHref,
|
|
122
|
+
router
|
|
123
|
+
]);
|
|
124
|
+
/**
|
|
125
|
+
* Retire a topic, or bring one back.
|
|
126
|
+
*
|
|
127
|
+
* Retire and not delete: campaigns already sent under a topic minted
|
|
128
|
+
* unsubscribe links carrying its id, those links are sitting in inboxes, and
|
|
129
|
+
* an id that stops resolving is a preference page that cannot name the
|
|
130
|
+
* message the recipient is holding. The Firestore rules refuse a delete for
|
|
131
|
+
* the same reason.
|
|
132
|
+
*
|
|
133
|
+
* Restoring is not asked about — it puts a choice back rather than taking
|
|
134
|
+
* one away, and it is undone by retiring again.
|
|
135
|
+
*/ const toggleArchived = useCallback(async (topic)=>{
|
|
136
|
+
if (!scope || busyTopicId) return;
|
|
137
|
+
if (!topic.archived) {
|
|
138
|
+
const accepted = await confirm({
|
|
139
|
+
title: `Retire “${topic.name}”?`,
|
|
140
|
+
description: 'It leaves the composer and the preference page, so nothing new ' + 'can be sent under it and recipients stop seeing it as a choice. ' + 'Campaigns already sent under it keep working — their ' + 'unsubscribe links still name this topic. You can bring it back ' + 'at any time.',
|
|
141
|
+
confirmationText: 'Retire'
|
|
142
|
+
})// `confirm` resolves with NO VALUE and REJECTS on cancel, so gating
|
|
143
|
+
// on the resolved value alone makes this always return (AGL-950).
|
|
144
|
+
.then(()=>true).catch(()=>false);
|
|
145
|
+
if (!accepted) return;
|
|
146
|
+
}
|
|
147
|
+
setBusyTopicId(topic.id);
|
|
148
|
+
try {
|
|
149
|
+
await writeEmailTopic(firestore, scope, {
|
|
150
|
+
id: topic.id,
|
|
151
|
+
name: topic.name,
|
|
152
|
+
description: topic.description,
|
|
153
|
+
archived: !topic.archived
|
|
154
|
+
});
|
|
155
|
+
enqueueSnackbar(topic.archived ? 'Topic restored' : 'Topic retired', {
|
|
156
|
+
variant: 'success',
|
|
157
|
+
persist: false
|
|
158
|
+
});
|
|
159
|
+
} catch (caught) {
|
|
160
|
+
console.error(caught);
|
|
161
|
+
enqueueSnackbar('An error has occurred', {
|
|
162
|
+
variant: 'error'
|
|
163
|
+
});
|
|
164
|
+
} finally{
|
|
165
|
+
setBusyTopicId('');
|
|
166
|
+
}
|
|
167
|
+
}, [
|
|
168
|
+
scope,
|
|
169
|
+
busyTopicId,
|
|
170
|
+
confirm,
|
|
171
|
+
firestore,
|
|
172
|
+
enqueueSnackbar
|
|
173
|
+
]);
|
|
174
|
+
const rowActions = (topic)=>[
|
|
175
|
+
{
|
|
176
|
+
key: 'edit',
|
|
177
|
+
label: 'Edit topic',
|
|
178
|
+
icon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
179
|
+
path: mdiPencilOutline.path,
|
|
180
|
+
size: 0.8
|
|
181
|
+
}),
|
|
182
|
+
href: topicHref(topic.id)
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
key: 'archive',
|
|
186
|
+
label: topic.archived ? 'Restore' : 'Retire',
|
|
187
|
+
icon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
188
|
+
path: topic.archived ? mdiArchiveArrowUpOutline.path : mdiArchiveOutline.path,
|
|
189
|
+
size: 0.8
|
|
190
|
+
}),
|
|
191
|
+
destructive: !topic.archived,
|
|
192
|
+
disabled: Boolean(busyTopicId),
|
|
193
|
+
disabledReason: busyTopicId ? 'Another topic is being saved' : undefined,
|
|
194
|
+
onClick: ()=>void toggleArchived(topic)
|
|
195
|
+
}
|
|
196
|
+
];
|
|
197
|
+
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
198
|
+
header: "Topics",
|
|
199
|
+
help: pluginDocsHelp('emailCampaigns', {
|
|
200
|
+
anchor: '#topics'
|
|
201
|
+
}),
|
|
202
|
+
contentGutterX: true,
|
|
203
|
+
contentGutterY: true,
|
|
204
|
+
contentBordered: "all",
|
|
205
|
+
HeaderProps: {
|
|
206
|
+
action: scope ? /*#__PURE__*/ _jsx(Button, {
|
|
207
|
+
variant: "contained",
|
|
208
|
+
onClick: ()=>setDrawerOpen(true),
|
|
209
|
+
children: 'New topic'
|
|
210
|
+
}) : null
|
|
211
|
+
},
|
|
212
|
+
children: [
|
|
213
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
214
|
+
spacing: 2,
|
|
215
|
+
children: [
|
|
216
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
217
|
+
variant: "body2",
|
|
218
|
+
color: "text.secondary",
|
|
219
|
+
children: 'The streams your recipients can leave one at a time. Every ' + 'campaign belongs to one, and the preference page linked from ' + 'each email lets someone stop that stream without stopping ' + 'everything. Topics are shared across every site in this ' + 'organization, the same way your lists are.'
|
|
220
|
+
}),
|
|
221
|
+
!scope ? /*#__PURE__*/ _jsx(Typography, {
|
|
222
|
+
variant: "body2",
|
|
223
|
+
color: "text.secondary",
|
|
224
|
+
children: 'This site has no organization, so it has no topic list.'
|
|
225
|
+
}) : /*#__PURE__*/ _jsxs(ScrollTable, {
|
|
226
|
+
size: "small",
|
|
227
|
+
children: [
|
|
228
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
229
|
+
children: /*#__PURE__*/ _jsxs(TableRow, {
|
|
230
|
+
children: [
|
|
231
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
232
|
+
children: 'Topic'
|
|
233
|
+
}),
|
|
234
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
235
|
+
children: 'What recipients are told they get'
|
|
236
|
+
}),
|
|
237
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
238
|
+
align: "right"
|
|
239
|
+
})
|
|
240
|
+
]
|
|
241
|
+
})
|
|
242
|
+
}),
|
|
243
|
+
/*#__PURE__*/ _jsx(TableBody, {
|
|
244
|
+
children: topics.map((topic)=>{
|
|
245
|
+
const href = topicHref(topic.id);
|
|
246
|
+
return /*#__PURE__*/ _jsxs(TableRow, {
|
|
247
|
+
hover: true,
|
|
248
|
+
onClick: ()=>router.push(href),
|
|
249
|
+
sx: {
|
|
250
|
+
cursor: 'pointer'
|
|
251
|
+
},
|
|
252
|
+
children: [
|
|
253
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
254
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
255
|
+
direction: "row",
|
|
256
|
+
spacing: 1,
|
|
257
|
+
sx: {
|
|
258
|
+
flexWrap: 'wrap',
|
|
259
|
+
alignItems: 'center'
|
|
260
|
+
},
|
|
261
|
+
children: [
|
|
262
|
+
/*#__PURE__*/ _jsx(AppLink, {
|
|
263
|
+
href: href,
|
|
264
|
+
onClick: (event)=>event.stopPropagation(),
|
|
265
|
+
children: topic.name
|
|
266
|
+
}),
|
|
267
|
+
topic.archived ? /*#__PURE__*/ _jsx(Chip, {
|
|
268
|
+
size: "small",
|
|
269
|
+
variant: "outlined",
|
|
270
|
+
color: "warning",
|
|
271
|
+
label: "Retired"
|
|
272
|
+
}) : null,
|
|
273
|
+
DEFAULT_EMAIL_TOPICS.some((it)=>it.id === topic.id) ? /*#__PURE__*/ _jsx(Typography, {
|
|
274
|
+
variant: "overline",
|
|
275
|
+
color: "text.secondary",
|
|
276
|
+
children: 'Built in'
|
|
277
|
+
}) : null
|
|
278
|
+
]
|
|
279
|
+
})
|
|
280
|
+
}),
|
|
281
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
282
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
283
|
+
variant: "body2",
|
|
284
|
+
color: "text.secondary",
|
|
285
|
+
children: topic.description || '—'
|
|
286
|
+
})
|
|
287
|
+
}),
|
|
288
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
289
|
+
align: "right",
|
|
290
|
+
sx: {
|
|
291
|
+
width: 56
|
|
292
|
+
},
|
|
293
|
+
onClick: (event)=>event.stopPropagation(),
|
|
294
|
+
children: /*#__PURE__*/ _jsx(RowActionsMenu, {
|
|
295
|
+
label: topic.name,
|
|
296
|
+
items: rowActions(topic)
|
|
297
|
+
})
|
|
298
|
+
})
|
|
299
|
+
]
|
|
300
|
+
}, topic.id);
|
|
301
|
+
})
|
|
302
|
+
})
|
|
303
|
+
]
|
|
304
|
+
})
|
|
305
|
+
]
|
|
306
|
+
}),
|
|
307
|
+
/*#__PURE__*/ _jsx(CreateArtifactDrawer, {
|
|
308
|
+
open: drawerOpen,
|
|
309
|
+
onClose: ()=>setDrawerOpen(false),
|
|
310
|
+
title: "New email topic",
|
|
311
|
+
submitLabel: "Create topic",
|
|
312
|
+
onSubmit: create,
|
|
313
|
+
errorSlot: error ? /*#__PURE__*/ _jsx(Typography, {
|
|
314
|
+
variant: "body2",
|
|
315
|
+
color: "error",
|
|
316
|
+
sx: {
|
|
317
|
+
mt: 2
|
|
318
|
+
},
|
|
319
|
+
children: 'Could not create the topic. Try again.'
|
|
320
|
+
}) : null
|
|
321
|
+
})
|
|
322
|
+
]
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
export default EmailTopicsCard;
|
|
326
|
+
|
|
327
|
+
//# sourceMappingURL=email-topics-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/email-topics-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport {\n createResourceUid,\n isEmailTopicId,\n pluginDocsHelp,\n DEFAULT_EMAIL_TOPICS,\n EMAIL_TOPICS_COLLECTION,\n} from '@aglyn/aglyn'\nimport {\n mdiArchiveArrowUpOutline,\n mdiArchiveOutline,\n mdiPencilOutline,\n} from '@aglyn/shared-data-mdi'\nimport {\n AppLink,\n CardDisplay,\n MdiIcon,\n useConfirmationContext,\n} from '@aglyn/shared-ui-jsx'\nimport RowActionsMenu, {\n type RowActionsMenuItem,\n} from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { CreateArtifactDrawer } from '@aglyn/shared-ui-jsx-forms'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Button,\n Chip,\n Stack,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport { doc, setDoc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useCallback, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { useOrgEmailTopics, writeEmailTopic } from './use-org-email-topics'\n\nexport interface EmailTopicsCardProps {\n hostId: string\n /** The emails hub URL, which every topic route hangs beneath. */\n basePath: string\n}\n\n/**\n * Email topics: the streams a recipient can leave one at a time.\n *\n * ## What this closes\n *\n * `docs/specs/email-competitive-gaps.md` §1f is a table of what six\n * competitors have, and the preference-center row was the one where every one\n * of them had something and we had nothing: unsubscribe was all-or-nothing per\n * site. This card is the merchant's half of the fix — the recipient's half is\n * the hosted preference page the campaign footer now links to.\n *\n * ## Why the four built-ins are not documents\n *\n * Every org that exists today has an empty `emailTopics` collection, and a\n * seeding migration would leave any org created while it ran with none. So\n * `DEFAULT_EMAIL_TOPICS` is the FLOOR of the catalog rather than its initial\n * contents: the four are present for every org with no write anywhere, and\n * saving one on its detail page writes a document at the same id that renames\n * or retires it. A built-in with no stored document is not a missing row, it\n * is a row nobody has needed to change.\n *\n * ## Create is a drawer; edit is the topic's own page\n *\n * The console's standing shape for a list surface: creating opens the shared\n * `CreateArtifactDrawer`, the same one Screens, Components, Layouts and\n * Templates use, and changing an existing record happens on that record's\n * page rather than in a form stacked above the table. So this card lists and\n * routes, and `email-topic-detail.tsx` edits.\n *\n * ## The row grammar the rest of the surface uses\n *\n * The same one the audiences table sets: the row opens the topic, the topic's\n * name is ALSO a real link so it can be middle-clicked and copied, and the\n * secondary actions sit behind the shared `RowActionsMenu` rather than as text\n * buttons in the row. Retire is the reason the menu has more than one entry —\n * it is the only thing that can be done to a topic without opening it, and it\n * is destructive enough that it should not sit one mis-click from the row's\n * own open handler.\n */\nexport function EmailTopicsCard(props: EmailTopicsCardProps) {\n const { hostId, basePath } = props\n const firestore = useFirestore()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n const { confirm } = useConfirmationContext()\n const { topics, scope } = useOrgEmailTopics(hostId)\n\n const [drawerOpen, setDrawerOpen] = useState(false)\n const [error, setError] = useState<unknown>(null)\n const [busyTopicId, setBusyTopicId] = useState('')\n\n /** The topic's own page, which is also its edit page. */\n const topicHref = useCallback(\n (topicId: string) => `${basePath}/topics/${topicId}`,\n [basePath],\n )\n\n const create = useCallback(\n async (values: Record<string, any>) => {\n if (!scope) return\n const name = String(values?.displayName ?? '').trim()\n if (!name) return\n // `createResourceUid` is nanoid's `A-Za-z0-9_-`, so the id it mints\n // always satisfies `isEmailTopicId`. Checked anyway, because this id is\n // signed into every unsubscribe link the topic ever mints and the send\n // path refuses one that is not usable — a topic nothing can be sent\n // under is a row the merchant would have no way to diagnose.\n const id = createResourceUid()\n if (!isEmailTopicId(id)) {\n return void enqueueSnackbar('Could not create the topic', {\n variant: 'error',\n })\n }\n try {\n await setDoc(\n doc(firestore, scope[0], scope[1], EMAIL_TOPICS_COLLECTION, id),\n {\n name,\n description: String(values?.description ?? '').trim(),\n archived: false,\n },\n )\n setDrawerOpen(false)\n setError(null)\n enqueueSnackbar('Topic added', { variant: 'success', persist: false })\n // Straight to the record, which is where every further change is made.\n router.push(topicHref(id))\n } catch (caught) {\n console.error(caught)\n setError(caught)\n }\n },\n [scope, firestore, enqueueSnackbar, topicHref, router],\n )\n\n /**\n * Retire a topic, or bring one back.\n *\n * Retire and not delete: campaigns already sent under a topic minted\n * unsubscribe links carrying its id, those links are sitting in inboxes, and\n * an id that stops resolving is a preference page that cannot name the\n * message the recipient is holding. The Firestore rules refuse a delete for\n * the same reason.\n *\n * Restoring is not asked about — it puts a choice back rather than taking\n * one away, and it is undone by retiring again.\n */\n const toggleArchived = useCallback(\n async (topic: { id: string; name: string; description: string; archived?: boolean }) => {\n if (!scope || busyTopicId) return\n if (!topic.archived) {\n const accepted = await confirm({\n title: `Retire “${topic.name}”?`,\n description:\n 'It leaves the composer and the preference page, so nothing new ' +\n 'can be sent under it and recipients stop seeing it as a choice. ' +\n 'Campaigns already sent under it keep working — their ' +\n 'unsubscribe links still name this topic. You can bring it back ' +\n 'at any time.',\n confirmationText: 'Retire',\n })\n // `confirm` resolves with NO VALUE and REJECTS on cancel, so gating\n // on the resolved value alone makes this always return (AGL-950).\n .then(() => true)\n .catch(() => false)\n if (!accepted) return\n }\n setBusyTopicId(topic.id)\n try {\n await writeEmailTopic(firestore, scope, {\n id: topic.id,\n name: topic.name,\n description: topic.description,\n archived: !topic.archived,\n })\n enqueueSnackbar(topic.archived ? 'Topic restored' : 'Topic retired', {\n variant: 'success',\n persist: false,\n })\n } catch (caught) {\n console.error(caught)\n enqueueSnackbar('An error has occurred', { variant: 'error' })\n } finally {\n setBusyTopicId('')\n }\n },\n [scope, busyTopicId, confirm, firestore, enqueueSnackbar],\n )\n\n const rowActions = (topic: {\n id: string\n name: string\n description: string\n archived?: boolean\n }): RowActionsMenuItem[] => [\n {\n key: 'edit',\n label: 'Edit topic',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n href: topicHref(topic.id),\n },\n {\n key: 'archive',\n label: topic.archived ? 'Restore' : 'Retire',\n icon: (\n <MdiIcon\n path={\n topic.archived\n ? mdiArchiveArrowUpOutline.path\n : mdiArchiveOutline.path\n }\n size={0.8}\n />\n ),\n destructive: !topic.archived,\n disabled: Boolean(busyTopicId),\n disabledReason: busyTopicId ? 'Another topic is being saved' : undefined,\n onClick: () => void toggleArchived(topic),\n },\n ]\n\n return (\n <CardDisplay\n header=\"Topics\"\n help={pluginDocsHelp('emailCampaigns', { anchor: '#topics' })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n HeaderProps={{\n action: scope ? (\n <Button variant=\"contained\" onClick={() => setDrawerOpen(true)}>\n {'New topic'}\n </Button>\n ) : null,\n }}\n >\n <Stack spacing={2}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'The streams your recipients can leave one at a time. Every ' +\n 'campaign belongs to one, and the preference page linked from ' +\n 'each email lets someone stop that stream without stopping ' +\n 'everything. Topics are shared across every site in this ' +\n 'organization, the same way your lists are.'}\n </Typography>\n {!scope ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This site has no organization, so it has no topic list.'}\n </Typography>\n ) : (\n <ScrollTable size=\"small\">\n <TableHead>\n <TableRow>\n <TableCell>{'Topic'}</TableCell>\n <TableCell>{'What recipients are told they get'}</TableCell>\n <TableCell align=\"right\" />\n </TableRow>\n </TableHead>\n <TableBody>\n {topics.map((topic) => {\n const href = topicHref(topic.id)\n return (\n <TableRow\n key={topic.id}\n hover\n onClick={() => router.push(href)}\n sx={{ cursor: 'pointer' }}\n >\n <TableCell>\n <Stack\n direction=\"row\"\n spacing={1}\n sx={{ flexWrap: 'wrap', alignItems: 'center' }}\n >\n {/*\n The row's own handler would fire too and push the\n same route twice — one history entry per back press.\n */}\n <AppLink\n href={href}\n onClick={(event: { stopPropagation: () => void }) =>\n event.stopPropagation()\n }\n >\n {topic.name}\n </AppLink>\n {topic.archived ? (\n <Chip\n size=\"small\"\n variant=\"outlined\"\n color=\"warning\"\n label=\"Retired\"\n />\n ) : null}\n {DEFAULT_EMAIL_TOPICS.some(\n (it) => it.id === topic.id,\n ) ? (\n <Typography\n variant=\"overline\"\n color=\"text.secondary\"\n >\n {'Built in'}\n </Typography>\n ) : null}\n </Stack>\n </TableCell>\n <TableCell>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {topic.description || '—'}\n </Typography>\n </TableCell>\n <TableCell\n align=\"right\"\n sx={{ width: 56 }}\n onClick={(event) => event.stopPropagation()}\n >\n <RowActionsMenu\n label={topic.name}\n items={rowActions(topic)}\n />\n </TableCell>\n </TableRow>\n )\n })}\n </TableBody>\n </ScrollTable>\n )}\n </Stack>\n <CreateArtifactDrawer\n open={drawerOpen}\n onClose={() => setDrawerOpen(false)}\n title=\"New email topic\"\n submitLabel=\"Create topic\"\n onSubmit={create}\n errorSlot={\n error ? (\n <Typography variant=\"body2\" color=\"error\" sx={{ mt: 2 }}>\n {'Could not create the topic. Try again.'}\n </Typography>\n ) : null\n }\n />\n </CardDisplay>\n )\n}\n\nexport default EmailTopicsCard\n"],"names":["createResourceUid","isEmailTopicId","pluginDocsHelp","DEFAULT_EMAIL_TOPICS","EMAIL_TOPICS_COLLECTION","mdiArchiveArrowUpOutline","mdiArchiveOutline","mdiPencilOutline","AppLink","CardDisplay","MdiIcon","useConfirmationContext","RowActionsMenu","CreateArtifactDrawer","ScrollTable","useSnackbar","Button","Chip","Stack","TableBody","TableCell","TableHead","TableRow","Typography","doc","setDoc","useRouter","useCallback","useState","useFirestore","useOrgEmailTopics","writeEmailTopic","EmailTopicsCard","props","hostId","basePath","firestore","router","enqueueSnackbar","confirm","topics","scope","drawerOpen","setDrawerOpen","error","setError","busyTopicId","setBusyTopicId","topicHref","topicId","create","values","name","String","displayName","trim","id","variant","description","archived","persist","push","caught","console","toggleArchived","topic","accepted","title","confirmationText","then","catch","rowActions","key","label","icon","path","size","href","destructive","disabled","Boolean","disabledReason","undefined","onClick","header","help","anchor","contentGutterX","contentGutterY","contentBordered","HeaderProps","action","spacing","color","align","map","hover","sx","cursor","direction","flexWrap","alignItems","event","stopPropagation","some","it","width","items","open","onClose","submitLabel","onSubmit","errorSlot","mt"],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Messages section of the Emails page.
|
|
3
|
+
*
|
|
4
|
+
* A message is one email that was or will be sent. Writing one, scheduling
|
|
5
|
+
* it, sending it and reporting what came of it are a campaign's work — the
|
|
6
|
+
* routes that do all of it are served by whichever plugin owns campaigns — so
|
|
7
|
+
* this plugin hosts the section and that plugin draws it. What is handed over
|
|
8
|
+
* is what the shell handed this page: the site, this page's own base path,
|
|
9
|
+
* and the segments under `messages` (`[]` the list, `[id]` one message,
|
|
10
|
+
* `[id, 'edit']` its composer).
|
|
11
|
+
*/
|
|
12
|
+
export interface EmailMessagesZoneProps {
|
|
13
|
+
hostId: string;
|
|
14
|
+
basePath: string;
|
|
15
|
+
detail: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
export declare const EMAIL_MESSAGES_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<EmailMessagesZoneProps>;
|
|
18
|
+
/**
|
|
19
|
+
* Who received the messages built from one template, under its report.
|
|
20
|
+
*
|
|
21
|
+
* The template is this plugin's; the recipients of a send are read through
|
|
22
|
+
* the campaign owner's route, so the table is that plugin's to draw.
|
|
23
|
+
*/
|
|
24
|
+
export interface EmailTemplateRecipientsZoneProps {
|
|
25
|
+
hostId: string;
|
|
26
|
+
screenId: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const EMAIL_TEMPLATE_RECIPIENTS_ZONE: import("@aglyn/aglyn/plugin-manager/plugin-zones").PluginZone<EmailTemplateRecipientsZoneProps>;
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
*/ import { definePluginZone } from "@aglyn/aglyn/plugin-manager/plugin-zones";
|
|
17
|
+
export const EMAIL_MESSAGES_ZONE = definePluginZone('emailMessages');
|
|
18
|
+
export const EMAIL_TEMPLATE_RECIPIENTS_ZONE = definePluginZone('emailTemplateRecipients');
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=email-zones.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/email-zones.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 */\nimport { definePluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\n\n/**\n * The Messages section of the Emails page.\n *\n * A message is one email that was or will be sent. Writing one, scheduling\n * it, sending it and reporting what came of it are a campaign's work — the\n * routes that do all of it are served by whichever plugin owns campaigns — so\n * this plugin hosts the section and that plugin draws it. What is handed over\n * is what the shell handed this page: the site, this page's own base path,\n * and the segments under `messages` (`[]` the list, `[id]` one message,\n * `[id, 'edit']` its composer).\n */\nexport interface EmailMessagesZoneProps {\n hostId: string\n basePath: string\n detail: readonly string[]\n}\n\nexport const EMAIL_MESSAGES_ZONE =\n definePluginZone<EmailMessagesZoneProps>('emailMessages')\n\n/**\n * Who received the messages built from one template, under its report.\n *\n * The template is this plugin's; the recipients of a send are read through\n * the campaign owner's route, so the table is that plugin's to draw.\n */\nexport interface EmailTemplateRecipientsZoneProps {\n hostId: string\n screenId: string\n}\n\nexport const EMAIL_TEMPLATE_RECIPIENTS_ZONE =\n definePluginZone<EmailTemplateRecipientsZoneProps>('emailTemplateRecipients')\n"],"names":["definePluginZone","EMAIL_MESSAGES_ZONE","EMAIL_TEMPLATE_RECIPIENTS_ZONE"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD,SAASA,gBAAgB,QAAQ,2CAA0C;AAmB3E,OAAO,MAAMC,sBACXD,iBAAyC,iBAAgB;AAa3D,OAAO,MAAME,iCACXF,iBAAmD,2BAA0B"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { type ConsolePluginPageProps } from '@aglyn/aglyn';
|
|
2
|
+
/**
|
|
3
|
+
* Emails page (AGL-395): the console surface owned by the email plugin,
|
|
4
|
+
* rendered by the shell's generic plugin route.
|
|
5
|
+
*
|
|
6
|
+
* Two of its sections are two different things a merchant calls "an email":
|
|
7
|
+
* an EMAIL is one message that was or will be sent, and a TEMPLATE is the
|
|
8
|
+
* reusable besigner document a message is built from. Keeping them apart is
|
|
9
|
+
* what lets each carry its own report — a message's own numbers, and a
|
|
10
|
+
* template's summed across every message sent from it. Audience lists, the
|
|
11
|
+
* topic catalog, the sending identities and the suppression list complete the
|
|
12
|
+
* surface.
|
|
13
|
+
*
|
|
14
|
+
* The CAMPAIGN that groups messages is not here. It is a container with a
|
|
15
|
+
* window of dates, a set of lists, a topic and revenue attribution — a
|
|
16
|
+
* marketing object that reaches people by email — so it is a section of the
|
|
17
|
+
* Marketing console, and a message's page links out to the campaign it
|
|
18
|
+
* belongs to.
|
|
19
|
+
*
|
|
20
|
+
* Sections are ROUTES (AGL-2501). `HubTabs lazy` already mounted one panel, so
|
|
21
|
+
* this is not a read saving — `emails-console-read-cost.spec.tsx` was written
|
|
22
|
+
* BEFORE the conversion precisely to hold that line, and reports the same
|
|
23
|
+
* counts after. What routing adds is that the URL names the section: it is
|
|
24
|
+
* linkable, the back button walks sections, the breadcrumb says where you are,
|
|
25
|
+
* and "mount only what is open" is structural rather than a `lazy` flag
|
|
26
|
+
* somebody has to remember on the next surface.
|
|
27
|
+
*/
|
|
28
|
+
export declare function EmailsConsolePage(props: ConsolePluginPageProps): import("react").JSX.Element;
|
|
29
|
+
export declare namespace EmailsConsolePage {
|
|
30
|
+
var displayName: string;
|
|
31
|
+
}
|
|
32
|
+
export default EmailsConsolePage;
|