@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,277 @@
|
|
|
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 { buildRoute, pluginDocsHelp, Route } from "@aglyn/aglyn";
|
|
19
|
+
import { mdiDeleteOutline, mdiContentCopy, mdiEyeOutline, mdiPencilOutline } from "@aglyn/shared-data-mdi";
|
|
20
|
+
import { AppLink, CardDisplay, MdiIcon, useConfirmationContext } from "@aglyn/shared-ui-jsx";
|
|
21
|
+
import { ListRowActions, ListTable, listActionsColumn } from "@aglyn/shared-ui-jsx/components/list-table.component";
|
|
22
|
+
import { TABLE_ROW_HEIGHT } from "@aglyn/shared-ui-jsx/const/table-pagination";
|
|
23
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
24
|
+
import { ceilingedWindow, collectionCeiling } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
|
|
25
|
+
import { DUPLICATE_MENU_LABEL, useConsoleHostRoute, useDuplicateResource, useFirestore, useFirestoreCollection, useHostResourceApi, useHostVersionApi } from "@aglyn/tenant-feature-instance";
|
|
26
|
+
import { Alert, Button, Chip, Stack, Typography } from "@mui/material";
|
|
27
|
+
import { collection, doc, Timestamp, updateDoc } from "firebase/firestore";
|
|
28
|
+
import { useRouter } from "next/navigation";
|
|
29
|
+
import { useMemo } from "react";
|
|
30
|
+
import { templateProvenance } from "../model/template-provenance.js";
|
|
31
|
+
import { createEmailScreen } from "../utils/create-email-screen.js";
|
|
32
|
+
/** How many of the site's screens one read of this list covers. */ const TEMPLATE_CEILING = 200;
|
|
33
|
+
// The besigner route is `/[orgSlug]/hosts/[host]/screens/[screenId]/
|
|
34
|
+
// versions/[versionId]/besigner`. This built `/{hostDocId}/screens/…`, the
|
|
35
|
+
// pre-AGL-621/622 shape — so every "Edit"/"Design" jump out of the Emails
|
|
36
|
+
// page landed on a 404, including the one right after creating a new email
|
|
37
|
+
// (AGL-685). Takes the resolved org slug + subdomain, not a host doc id.
|
|
38
|
+
const besignerHref = (orgSlug, host, screenId, versionId)=>buildRoute(Route.SCREEN_BESIGNER, {
|
|
39
|
+
orgSlug,
|
|
40
|
+
host,
|
|
41
|
+
screenId,
|
|
42
|
+
versionId
|
|
43
|
+
});
|
|
44
|
+
/**
|
|
45
|
+
* THE TEMPLATES: reusable besigner documents an email is built from.
|
|
46
|
+
*
|
|
47
|
+
* A template is a screen document with `kind: 'email'`, kept out of the main
|
|
48
|
+
* Screens list and opened in the besigner with only email-safe components on
|
|
49
|
+
* offer. It is not itself a message — a message is what a campaign sends, and
|
|
50
|
+
* one template can be behind many of them, which is why the row leads to the
|
|
51
|
+
* template's own page rather than straight into the editor.
|
|
52
|
+
*
|
|
53
|
+
* A template is not necessarily this org's. One installed from a marketplace
|
|
54
|
+
* listing appears here beside the locally authored ones, carries its
|
|
55
|
+
* publisher's provenance on the same document, and opens the same detail
|
|
56
|
+
* page; the chip beside its name is what distinguishes them.
|
|
57
|
+
*
|
|
58
|
+
* ## A table, on the surface's own row grammar
|
|
59
|
+
*
|
|
60
|
+
* The row opens the template, its name is ALSO a real link so it can be
|
|
61
|
+
* middle-clicked and copied, and editing and deleting live behind the shared
|
|
62
|
+
* overflow menu rather than as text buttons in the row — the same grammar the
|
|
63
|
+
* audiences table reads by. Delete in particular: it sat inline, one mis-click
|
|
64
|
+
* from the name beside it.
|
|
65
|
+
*
|
|
66
|
+
* ## Why the read is a CEILING with a probe
|
|
67
|
+
*
|
|
68
|
+
* `screens` holds every kind of screen the site has and the email ones are
|
|
69
|
+
* picked out here, so the cap cannot be a page: a page of the collection is
|
|
70
|
+
* not a page of this list. `collectionCeiling` orders on the document name,
|
|
71
|
+
* which is the one ordering that cannot drop a screen — `orderBy('displayName')`
|
|
72
|
+
* matches only documents that HAVE the field, so a screen created without a
|
|
73
|
+
* name would vanish rather than sort oddly. It reads one past the ceiling so
|
|
74
|
+
* the card can SAY when the site holds more than it drew, and the footer under
|
|
75
|
+
* the table pages the window the card already has.
|
|
76
|
+
*/ export function EmailScreensCard(props) {
|
|
77
|
+
const { hostId, basePath } = props;
|
|
78
|
+
const { orgSlug, subdomain } = useConsoleHostRoute(hostId);
|
|
79
|
+
const firestore = useFirestore();
|
|
80
|
+
const createHostResource = useHostResourceApi();
|
|
81
|
+
const createHostVersion = useHostVersionApi();
|
|
82
|
+
const router = useRouter();
|
|
83
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
84
|
+
// Duplicate (AGL-2936): the copy is a design of its own, sent by nothing
|
|
85
|
+
// until a campaign picks it.
|
|
86
|
+
const duplicate = useDuplicateResource({
|
|
87
|
+
hostId,
|
|
88
|
+
onDuplicated: (_kind, copy)=>enqueueSnackbar(`Duplicated as “${copy.name}”`, {
|
|
89
|
+
variant: 'success'
|
|
90
|
+
})
|
|
91
|
+
});
|
|
92
|
+
const { confirm } = useConfirmationContext();
|
|
93
|
+
const { data: screenDocs } = useFirestoreCollection(()=>collectionCeiling(collection(firestore, 'hosts', hostId, 'screens'), TEMPLATE_CEILING), [
|
|
94
|
+
firestore,
|
|
95
|
+
hostId
|
|
96
|
+
], {
|
|
97
|
+
idField: '$id'
|
|
98
|
+
});
|
|
99
|
+
const { rows: readScreens, truncated } = ceilingedWindow(screenDocs, TEMPLATE_CEILING);
|
|
100
|
+
const emailScreens = useMemo(()=>[
|
|
101
|
+
...readScreens
|
|
102
|
+
].filter((screen)=>!screen.deletedAt && screen.kind === 'email').sort((a, b)=>{
|
|
103
|
+
var _a_displayName, _b_displayName;
|
|
104
|
+
return String((_a_displayName = a.displayName) != null ? _a_displayName : '').localeCompare(String((_b_displayName = b.displayName) != null ? _b_displayName : ''));
|
|
105
|
+
}), [
|
|
106
|
+
readScreens
|
|
107
|
+
]);
|
|
108
|
+
const handleCreate = async ()=>{
|
|
109
|
+
try {
|
|
110
|
+
const { screenId, versionId } = await createEmailScreen(hostId, createHostResource, createHostVersion);
|
|
111
|
+
if (orgSlug && subdomain) {
|
|
112
|
+
void router.push(besignerHref(orgSlug, subdomain, screenId, versionId));
|
|
113
|
+
}
|
|
114
|
+
} catch (error) {
|
|
115
|
+
var _ref;
|
|
116
|
+
console.error(error);
|
|
117
|
+
enqueueSnackbar((_ref = error == null ? void 0 : error.message) != null ? _ref : 'Creating the template failed', {
|
|
118
|
+
variant: 'error'
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
const handleDelete = async (screen)=>{
|
|
123
|
+
var _screen_displayName;
|
|
124
|
+
const confirmed = await confirm({
|
|
125
|
+
title: 'Delete this template?',
|
|
126
|
+
description: `"${(_screen_displayName = screen.displayName) != null ? _screen_displayName : 'Untitled template'}" will be removed. ` + 'Emails already sent from it keep their reports.',
|
|
127
|
+
confirmationText: 'Delete',
|
|
128
|
+
confirmationButtonProps: {
|
|
129
|
+
color: 'error'
|
|
130
|
+
}
|
|
131
|
+
}).then(()=>true).catch(()=>false);
|
|
132
|
+
if (!confirmed) return;
|
|
133
|
+
await updateDoc(doc(firestore, 'hosts', hostId, 'screens', screen.$id), {
|
|
134
|
+
deletedAt: Timestamp.now()
|
|
135
|
+
});
|
|
136
|
+
};
|
|
137
|
+
const templateHref = (screen)=>`${basePath}/templates/${screen.$id}`;
|
|
138
|
+
const templateName = (screen)=>{
|
|
139
|
+
var _screen_displayName;
|
|
140
|
+
return String((_screen_displayName = screen.displayName) != null ? _screen_displayName : 'Untitled template');
|
|
141
|
+
};
|
|
142
|
+
const rowActions = (screen)=>[
|
|
143
|
+
{
|
|
144
|
+
key: 'details',
|
|
145
|
+
label: 'Open details',
|
|
146
|
+
icon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
147
|
+
path: mdiEyeOutline.path,
|
|
148
|
+
size: 0.8
|
|
149
|
+
}),
|
|
150
|
+
href: templateHref(screen)
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
key: 'besigner',
|
|
154
|
+
label: 'Edit in besigner',
|
|
155
|
+
icon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
156
|
+
path: mdiPencilOutline.path,
|
|
157
|
+
size: 0.8
|
|
158
|
+
}),
|
|
159
|
+
/*
|
|
160
|
+
The editor's URL needs the resolved org slug and subdomain. Until the
|
|
161
|
+
host route settles both are empty and a link built from them lands on
|
|
162
|
+
`/null/hosts/null`, so the item is shown DISABLED with the reason
|
|
163
|
+
rather than pointing at a 404.
|
|
164
|
+
*/ href: orgSlug && subdomain ? besignerHref(orgSlug, subdomain, screen.$id, screen.versionId) : undefined,
|
|
165
|
+
disabled: !orgSlug || !subdomain,
|
|
166
|
+
disabledReason: 'This site’s console URL has not resolved yet'
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
key: 'duplicate',
|
|
170
|
+
label: DUPLICATE_MENU_LABEL,
|
|
171
|
+
icon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
172
|
+
path: mdiContentCopy.path,
|
|
173
|
+
size: 0.8
|
|
174
|
+
}),
|
|
175
|
+
onClick: ()=>duplicate.request('emailDesign', {
|
|
176
|
+
id: screen.$id,
|
|
177
|
+
name: templateName(screen)
|
|
178
|
+
})
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
key: 'delete',
|
|
182
|
+
label: 'Delete',
|
|
183
|
+
icon: /*#__PURE__*/ _jsx(MdiIcon, {
|
|
184
|
+
path: mdiDeleteOutline.path,
|
|
185
|
+
size: 0.8
|
|
186
|
+
}),
|
|
187
|
+
destructive: true,
|
|
188
|
+
onClick: ()=>void handleDelete(screen)
|
|
189
|
+
}
|
|
190
|
+
];
|
|
191
|
+
/*
|
|
192
|
+
* The templates are a window this card already holds, so the grid pages
|
|
193
|
+
* it. The name is a link AND the row opens the template.
|
|
194
|
+
*/ const columns = [
|
|
195
|
+
{
|
|
196
|
+
field: 'displayName',
|
|
197
|
+
headerName: 'Template',
|
|
198
|
+
flex: 1,
|
|
199
|
+
minWidth: 220,
|
|
200
|
+
valueGetter: (_value, row)=>templateName(row),
|
|
201
|
+
renderCell: ({ row, value })=>/*#__PURE__*/ _jsx(AppLink, {
|
|
202
|
+
href: templateHref(row),
|
|
203
|
+
// The row's own handler would fire too and push the same route
|
|
204
|
+
// twice — one history entry per back press.
|
|
205
|
+
onClick: (event)=>event.stopPropagation(),
|
|
206
|
+
children: value
|
|
207
|
+
})
|
|
208
|
+
},
|
|
209
|
+
{
|
|
210
|
+
/*
|
|
211
|
+
WHOSE template this is, where the reader is choosing between them. An
|
|
212
|
+
installed one is versioned by its publisher and can be withdrawn, which
|
|
213
|
+
is not a property a name can carry.
|
|
214
|
+
*/ field: 'origin',
|
|
215
|
+
headerName: 'Origin',
|
|
216
|
+
width: 140,
|
|
217
|
+
valueGetter: (_value, row)=>templateProvenance(row).origin === 'installed' ? 'Installed' : 'Yours',
|
|
218
|
+
renderCell: ({ value })=>value === 'Installed' ? /*#__PURE__*/ _jsx(Chip, {
|
|
219
|
+
size: "small",
|
|
220
|
+
label: "Installed"
|
|
221
|
+
}) : /*#__PURE__*/ _jsx(Typography, {
|
|
222
|
+
variant: "body2",
|
|
223
|
+
color: "text.secondary",
|
|
224
|
+
children: 'Yours'
|
|
225
|
+
})
|
|
226
|
+
},
|
|
227
|
+
listActionsColumn((row)=>/*#__PURE__*/ _jsx(ListRowActions, {
|
|
228
|
+
label: templateName(row),
|
|
229
|
+
items: rowActions(row)
|
|
230
|
+
}), {
|
|
231
|
+
width: 72
|
|
232
|
+
})
|
|
233
|
+
];
|
|
234
|
+
return /*#__PURE__*/ _jsxs(CardDisplay, {
|
|
235
|
+
header: 'Templates',
|
|
236
|
+
help: pluginDocsHelp('designedEmails', {
|
|
237
|
+
anchor: '#create-a-template'
|
|
238
|
+
}),
|
|
239
|
+
contentGutterX: true,
|
|
240
|
+
contentGutterY: true,
|
|
241
|
+
HeaderProps: {
|
|
242
|
+
action: /*#__PURE__*/ _jsx(Button, {
|
|
243
|
+
size: "small",
|
|
244
|
+
variant: "contained",
|
|
245
|
+
onClick: ()=>void handleCreate(),
|
|
246
|
+
children: 'New template'
|
|
247
|
+
})
|
|
248
|
+
},
|
|
249
|
+
children: [
|
|
250
|
+
duplicate.dialog,
|
|
251
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
252
|
+
spacing: 1.5,
|
|
253
|
+
children: [
|
|
254
|
+
emailScreens.length === 0 ? /*#__PURE__*/ _jsx(Typography, {
|
|
255
|
+
variant: "body2",
|
|
256
|
+
color: "text.secondary",
|
|
257
|
+
children: 'Design a reusable email here, then send it from a campaign. A ' + 'new template opens in the besigner with email-safe components ' + 'only.'
|
|
258
|
+
}) : /*#__PURE__*/ _jsx(ListTable, {
|
|
259
|
+
"aria-label": "Email templates",
|
|
260
|
+
rows: emailScreens,
|
|
261
|
+
columns: columns,
|
|
262
|
+
rowHeight: TABLE_ROW_HEIGHT,
|
|
263
|
+
onOpen: (_id, row)=>router.push(templateHref(row))
|
|
264
|
+
}),
|
|
265
|
+
truncated ? /*#__PURE__*/ _jsx(Alert, {
|
|
266
|
+
severity: "info",
|
|
267
|
+
children: `This site holds more than ${TEMPLATE_CEILING} screens, and the ` + 'templates listed are drawn from the first of them. Any beyond ' + 'that are not in this list.'
|
|
268
|
+
}) : null
|
|
269
|
+
]
|
|
270
|
+
})
|
|
271
|
+
]
|
|
272
|
+
});
|
|
273
|
+
}
|
|
274
|
+
EmailScreensCard.displayName = 'EmailScreensCard';
|
|
275
|
+
export default EmailScreensCard;
|
|
276
|
+
|
|
277
|
+
//# sourceMappingURL=email-screens-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/email/src/lib/components/email-screens-card.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport { buildRoute, pluginDocsHelp, Route } from '@aglyn/aglyn'\nimport {\n mdiDeleteOutline,\n mdiContentCopy,\n mdiEyeOutline,\n mdiPencilOutline,\n} from '@aglyn/shared-data-mdi'\nimport {\n AppLink,\n CardDisplay,\n MdiIcon,\n useConfirmationContext,\n} from '@aglyn/shared-ui-jsx'\nimport {\n ListRowActions,\n ListTable,\n listActionsColumn,\n} from '@aglyn/shared-ui-jsx/components/list-table.component'\nimport type { RowActionsMenuItem } from '@aglyn/shared-ui-jsx/components/row-actions-menu.component'\nimport { TABLE_ROW_HEIGHT } from '@aglyn/shared-ui-jsx/const/table-pagination'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n ceilingedWindow,\n collectionCeiling,\n} from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport {\n DUPLICATE_MENU_LABEL,\n useConsoleHostRoute,\n useDuplicateResource,\n useFirestore,\n useFirestoreCollection,\n useHostResourceApi,\n useHostVersionApi,\n} from '@aglyn/tenant-feature-instance'\nimport {\n Alert,\n Button,\n Chip,\n Stack,\n Typography,\n} from '@mui/material'\nimport type { GridColDef } from '@mui/x-data-grid'\nimport { collection, doc, Timestamp, updateDoc } from 'firebase/firestore'\nimport { useRouter } from 'next/navigation'\nimport { useMemo } from 'react'\nimport { templateProvenance } from '../model/template-provenance'\nimport { createEmailScreen } from '../utils/create-email-screen'\n\n/** How many of the site's screens one read of this list covers. */\nconst TEMPLATE_CEILING = 200\n\n// The besigner route is `/[orgSlug]/hosts/[host]/screens/[screenId]/\n// versions/[versionId]/besigner`. This built `/{hostDocId}/screens/…`, the\n// pre-AGL-621/622 shape — so every \"Edit\"/\"Design\" jump out of the Emails\n// page landed on a 404, including the one right after creating a new email\n// (AGL-685). Takes the resolved org slug + subdomain, not a host doc id.\nconst besignerHref = (\n orgSlug: string,\n host: string,\n screenId: string,\n versionId: string,\n) =>\n buildRoute(Route.SCREEN_BESIGNER, { orgSlug, host, screenId, versionId })\n\n/**\n * THE TEMPLATES: reusable besigner documents an email is built from.\n *\n * A template is a screen document with `kind: 'email'`, kept out of the main\n * Screens list and opened in the besigner with only email-safe components on\n * offer. It is not itself a message — a message is what a campaign sends, and\n * one template can be behind many of them, which is why the row leads to the\n * template's own page rather than straight into the editor.\n *\n * A template is not necessarily this org's. One installed from a marketplace\n * listing appears here beside the locally authored ones, carries its\n * publisher's provenance on the same document, and opens the same detail\n * page; the chip beside its name is what distinguishes them.\n *\n * ## A table, on the surface's own row grammar\n *\n * The row opens the template, its name is ALSO a real link so it can be\n * middle-clicked and copied, and editing and deleting live behind the shared\n * overflow menu rather than as text buttons in the row — the same grammar the\n * audiences table reads by. Delete in particular: it sat inline, one mis-click\n * from the name beside it.\n *\n * ## Why the read is a CEILING with a probe\n *\n * `screens` holds every kind of screen the site has and the email ones are\n * picked out here, so the cap cannot be a page: a page of the collection is\n * not a page of this list. `collectionCeiling` orders on the document name,\n * which is the one ordering that cannot drop a screen — `orderBy('displayName')`\n * matches only documents that HAVE the field, so a screen created without a\n * name would vanish rather than sort oddly. It reads one past the ceiling so\n * the card can SAY when the site holds more than it drew, and the footer under\n * the table pages the window the card already has.\n */\nexport function EmailScreensCard(props: {\n hostId: string\n /** The emails hub URL, which every template route hangs beneath. */\n basePath: string\n}) {\n const { hostId, basePath } = props\n const { orgSlug, subdomain } = useConsoleHostRoute(hostId)\n const firestore = useFirestore()\n const createHostResource = useHostResourceApi()\n const createHostVersion = useHostVersionApi()\n const router = useRouter()\n const { enqueueSnackbar } = useSnackbar()\n // Duplicate (AGL-2936): the copy is a design of its own, sent by nothing\n // until a campaign picks it.\n const duplicate = useDuplicateResource({\n hostId,\n onDuplicated: (_kind, copy) =>\n enqueueSnackbar(`Duplicated as “${copy.name}”`, { variant: 'success' }),\n })\n const { confirm } = useConfirmationContext()\n\n const { data: screenDocs } = useFirestoreCollection<any>(\n () =>\n collectionCeiling(\n collection(firestore, 'hosts', hostId, 'screens'),\n TEMPLATE_CEILING,\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const { rows: readScreens, truncated } = ceilingedWindow<any>(\n screenDocs,\n TEMPLATE_CEILING,\n )\n const emailScreens = useMemo(\n () =>\n [...readScreens]\n .filter((screen: any) => !screen.deletedAt && screen.kind === 'email')\n .sort((a: any, b: any) =>\n String(a.displayName ?? '').localeCompare(\n String(b.displayName ?? ''),\n ),\n ),\n [readScreens],\n )\n\n const handleCreate = async () => {\n try {\n const { screenId, versionId } = await createEmailScreen(\n hostId,\n createHostResource,\n createHostVersion,\n )\n if (orgSlug && subdomain) {\n void router.push(besignerHref(orgSlug, subdomain, screenId, versionId))\n }\n } catch (error: any) {\n console.error(error)\n enqueueSnackbar(error?.message ?? 'Creating the template failed', {\n variant: 'error',\n })\n }\n }\n\n const handleDelete = async (screen: any) => {\n const confirmed = await confirm({\n title: 'Delete this template?',\n description:\n `\"${screen.displayName ?? 'Untitled template'}\" will be removed. ` +\n 'Emails already sent from it keep their reports.',\n confirmationText: 'Delete',\n confirmationButtonProps: { color: 'error' },\n })\n .then(() => true)\n .catch(() => false)\n if (!confirmed) return\n await updateDoc(doc(firestore, 'hosts', hostId, 'screens', screen.$id), {\n deletedAt: Timestamp.now(),\n })\n }\n\n const templateHref = (screen: any) => `${basePath}/templates/${screen.$id}`\n\n const templateName = (screen: any) =>\n String(screen.displayName ?? 'Untitled template')\n\n const rowActions = (screen: any): RowActionsMenuItem[] => [\n {\n key: 'details',\n label: 'Open details',\n icon: <MdiIcon path={mdiEyeOutline.path} size={0.8} />,\n href: templateHref(screen),\n },\n {\n key: 'besigner',\n label: 'Edit in besigner',\n icon: <MdiIcon path={mdiPencilOutline.path} size={0.8} />,\n /*\n The editor's URL needs the resolved org slug and subdomain. Until the\n host route settles both are empty and a link built from them lands on\n `/null/hosts/null`, so the item is shown DISABLED with the reason\n rather than pointing at a 404.\n */\n href:\n orgSlug && subdomain\n ? besignerHref(orgSlug, subdomain, screen.$id, screen.versionId)\n : undefined,\n disabled: !orgSlug || !subdomain,\n disabledReason: 'This site’s console URL has not resolved yet',\n },\n {\n key: 'duplicate',\n label: DUPLICATE_MENU_LABEL,\n icon: <MdiIcon path={mdiContentCopy.path} size={0.8} />,\n onClick: () =>\n duplicate.request('emailDesign', {\n id: screen.$id,\n name: templateName(screen),\n }),\n },\n {\n key: 'delete',\n label: 'Delete',\n icon: <MdiIcon path={mdiDeleteOutline.path} size={0.8} />,\n destructive: true,\n onClick: () => void handleDelete(screen),\n },\n ]\n\n /*\n * The templates are a window this card already holds, so the grid pages\n * it. The name is a link AND the row opens the template.\n */\n const columns: GridColDef[] = [\n {\n field: 'displayName',\n headerName: 'Template',\n flex: 1,\n minWidth: 220,\n valueGetter: (_value, row) => templateName(row),\n renderCell: ({ row, value }) => (\n <AppLink\n href={templateHref(row)}\n // The row's own handler would fire too and push the same route\n // twice — one history entry per back press.\n onClick={(event: { stopPropagation: () => void }) =>\n event.stopPropagation()\n }\n >\n {value}\n </AppLink>\n ),\n },\n {\n /*\n WHOSE template this is, where the reader is choosing between them. An\n installed one is versioned by its publisher and can be withdrawn, which\n is not a property a name can carry.\n */\n field: 'origin',\n headerName: 'Origin',\n width: 140,\n valueGetter: (_value, row) =>\n templateProvenance(row).origin === 'installed' ? 'Installed' : 'Yours',\n renderCell: ({ value }) =>\n value === 'Installed' ? (\n <Chip size=\"small\" label=\"Installed\" />\n ) : (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Yours'}\n </Typography>\n ),\n },\n listActionsColumn(\n (row) => (\n <ListRowActions label={templateName(row)} items={rowActions(row)} />\n ),\n { width: 72 },\n ),\n ]\n\n return (\n <CardDisplay\n header={'Templates'}\n help={pluginDocsHelp('designedEmails', { anchor: '#create-a-template' })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n size=\"small\"\n variant=\"contained\"\n onClick={() => void handleCreate()}\n >\n {'New template'}\n </Button>\n ),\n }}\n >\n {duplicate.dialog}\n <Stack spacing={1.5}>\n {emailScreens.length === 0 ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Design a reusable email here, then send it from a campaign. A ' +\n 'new template opens in the besigner with email-safe components ' +\n 'only.'}\n </Typography>\n ) : (\n <ListTable\n aria-label=\"Email templates\"\n rows={emailScreens}\n columns={columns}\n rowHeight={TABLE_ROW_HEIGHT}\n onOpen={(_id, row) => router.push(templateHref(row))}\n />\n )}\n {truncated ? (\n <Alert severity=\"info\">\n {`This site holds more than ${TEMPLATE_CEILING} screens, and the ` +\n 'templates listed are drawn from the first of them. Any beyond ' +\n 'that are not in this list.'}\n </Alert>\n ) : null}\n </Stack>\n </CardDisplay>\n )\n}\nEmailScreensCard.displayName = 'EmailScreensCard'\n\nexport default EmailScreensCard\n"],"names":["buildRoute","pluginDocsHelp","Route","mdiDeleteOutline","mdiContentCopy","mdiEyeOutline","mdiPencilOutline","AppLink","CardDisplay","MdiIcon","useConfirmationContext","ListRowActions","ListTable","listActionsColumn","TABLE_ROW_HEIGHT","useSnackbar","ceilingedWindow","collectionCeiling","DUPLICATE_MENU_LABEL","useConsoleHostRoute","useDuplicateResource","useFirestore","useFirestoreCollection","useHostResourceApi","useHostVersionApi","Alert","Button","Chip","Stack","Typography","collection","doc","Timestamp","updateDoc","useRouter","useMemo","templateProvenance","createEmailScreen","TEMPLATE_CEILING","besignerHref","orgSlug","host","screenId","versionId","SCREEN_BESIGNER","EmailScreensCard","props","hostId","basePath","subdomain","firestore","createHostResource","createHostVersion","router","enqueueSnackbar","duplicate","onDuplicated","_kind","copy","name","variant","confirm","data","screenDocs","idField","rows","readScreens","truncated","emailScreens","filter","screen","deletedAt","kind","sort","a","b","String","displayName","localeCompare","handleCreate","push","error","console","message","handleDelete","confirmed","title","description","confirmationText","confirmationButtonProps","color","then","catch","$id","now","templateHref","templateName","rowActions","key","label","icon","path","size","href","undefined","disabled","disabledReason","onClick","request","id","destructive","columns","field","headerName","flex","minWidth","valueGetter","_value","row","renderCell","value","event","stopPropagation","width","origin","items","header","help","anchor","contentGutterX","contentGutterY","HeaderProps","action","dialog","spacing","length","aria-label","rowHeight","onOpen","_id","severity"],"mappings":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many of a template's messages one read covers.
|
|
3
|
+
*
|
|
4
|
+
* A CEILING on the read, not a page size: the report is a single figure per
|
|
5
|
+
* quantity, so there is nothing to page through. Past it every total is a
|
|
6
|
+
* floor and the report says so — the same treatment `audienceSizeTruncated`
|
|
7
|
+
* gets, and for the same reason.
|
|
8
|
+
*/
|
|
9
|
+
export declare const TEMPLATE_CAMPAIGN_CEILING = 100;
|
|
10
|
+
export interface EmailTemplateDetailProps {
|
|
11
|
+
hostId: string;
|
|
12
|
+
/** The `kind: 'email'` screen document this page is about. */
|
|
13
|
+
screenId: string;
|
|
14
|
+
/** The emails hub URL, for the way back to the templates list. */
|
|
15
|
+
basePath: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* ONE EMAIL TEMPLATE: what it looks like, and what it has done.
|
|
19
|
+
*
|
|
20
|
+
* ## What was missing
|
|
21
|
+
*
|
|
22
|
+
* A template had a row in a list and no page. The only way to see one was to
|
|
23
|
+
* open the besigner, which is the editor — it draws canvas nodes in a
|
|
24
|
+
* browser, not the HTML an inbox receives — and the only way to see how it
|
|
25
|
+
* performed was to find each message sent from it and read those reports one
|
|
26
|
+
* at a time. A template used by six messages had its engagement in six places
|
|
27
|
+
* and nowhere.
|
|
28
|
+
*
|
|
29
|
+
* ## Not necessarily this org's template
|
|
30
|
+
*
|
|
31
|
+
* A template can be installed from a marketplace listing, in which case its
|
|
32
|
+
* content is versioned by somebody else and can be withdrawn. Provenance and
|
|
33
|
+
* standing are read off the document itself — see `template-provenance.ts` —
|
|
34
|
+
* so this page costs no marketplace read and still never presents an
|
|
35
|
+
* installed template as locally authored.
|
|
36
|
+
*
|
|
37
|
+
* ## What this page reads
|
|
38
|
+
*
|
|
39
|
+
* The screen document, its published version, and the messages naming this
|
|
40
|
+
* template — bounded at {@link TEMPLATE_CAMPAIGN_CEILING}. The recipients
|
|
41
|
+
* table is its own card and its own request, so a reader who came for the
|
|
42
|
+
* preview does not pay for the delivery-log query.
|
|
43
|
+
*/
|
|
44
|
+
export declare function EmailTemplateDetail(props: EmailTemplateDetailProps): import("react").JSX.Element;
|
|
45
|
+
export declare namespace EmailTemplateDetail {
|
|
46
|
+
var displayName: string;
|
|
47
|
+
}
|
|
48
|
+
export default EmailTemplateDetail;
|