@aglyn/plugins-marketing 1.0.0-beta.179 → 1.0.0-beta.180
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/README.md +3 -3
- package/package.json +15 -15
- package/src/lib/components/campaign-conversions-card.js +22 -4
- package/src/lib/components/campaign-conversions-card.js.map +1 -1
- package/src/lib/components/campaign-detail-card.js +3 -4
- package/src/lib/components/campaign-detail-card.js.map +1 -1
- package/src/lib/components/campaign-report-card.js +3 -3
- package/src/lib/components/campaign-report-card.js.map +1 -1
- package/src/lib/components/email-compose-card.d.ts +5 -2
- package/src/lib/components/email-compose-card.js +2 -3
- package/src/lib/components/email-compose-card.js.map +1 -1
- package/src/lib/components/email-detail.d.ts +5 -2
- package/src/lib/components/email-detail.js +4 -4
- package/src/lib/components/email-detail.js.map +1 -1
- package/src/lib/components/email-messages-widget.d.ts +17 -2
- package/src/lib/components/email-messages-widget.js +33 -2
- package/src/lib/components/email-messages-widget.js.map +1 -1
- package/src/lib/components/emails-list-card.d.ts +9 -6
- package/src/lib/components/emails-list-card.js +9 -9
- package/src/lib/components/emails-list-card.js.map +1 -1
- package/src/lib/components/host-experiments-card.component.d.ts +2 -0
- package/src/lib/components/host-experiments-card.component.js +20 -25
- package/src/lib/components/host-experiments-card.component.js.map +1 -1
- package/src/lib/components/host-marketing-summary-card.component.d.ts +15 -0
- package/src/lib/components/host-marketing-summary-card.component.js +59 -68
- package/src/lib/components/host-marketing-summary-card.component.js.map +1 -1
- package/src/lib/components/host-overlays-card.component.js +15 -37
- package/src/lib/components/host-overlays-card.component.js.map +1 -1
- package/src/lib/components/inbox-campaigns-widget.d.ts +23 -0
- package/src/lib/components/inbox-campaigns-widget.js +63 -0
- package/src/lib/components/inbox-campaigns-widget.js.map +1 -0
- package/src/lib/components/marketing-aggregates.d.ts +95 -0
- package/src/lib/components/marketing-aggregates.js +114 -0
- package/src/lib/components/marketing-aggregates.js.map +1 -0
- package/src/lib/components/marketing-console-page.js +32 -27
- package/src/lib/components/marketing-console-page.js.map +1 -1
- package/src/lib/components/marketing-console-sections.d.ts +24 -13
- package/src/lib/components/marketing-console-sections.js +37 -12
- package/src/lib/components/marketing-console-sections.js.map +1 -1
- package/src/lib/components/org-conversions-section.d.ts +32 -0
- package/src/lib/components/org-conversions-section.js +130 -0
- package/src/lib/components/org-conversions-section.js.map +1 -0
- package/src/lib/components/org-experiments-card.d.ts +32 -0
- package/src/lib/components/org-experiments-card.js +407 -0
- package/src/lib/components/org-experiments-card.js.map +1 -0
- package/src/lib/components/org-marketing-overview-card.d.ts +34 -0
- package/src/lib/components/org-marketing-overview-card.js +342 -0
- package/src/lib/components/org-marketing-overview-card.js.map +1 -0
- package/src/lib/components/org-overlays-card.d.ts +35 -0
- package/src/lib/components/org-overlays-card.js +345 -0
- package/src/lib/components/org-overlays-card.js.map +1 -0
- package/src/lib/components/org-site-picker-button.d.ts +26 -0
- package/src/lib/components/org-site-picker-button.js +104 -0
- package/src/lib/components/org-site-picker-button.js.map +1 -0
- package/src/lib/components/overlay-labels.d.ts +38 -0
- package/src/lib/components/overlay-labels.js +55 -0
- package/src/lib/components/overlay-labels.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +17 -8
- package/src/lib/components/use-emails-hub-path.js +29 -11
- package/src/lib/components/use-emails-hub-path.js.map +1 -1
- package/src/lib/components/use-one-shot-reads.d.ts +55 -0
- package/src/lib/components/use-one-shot-reads.js +168 -0
- package/src/lib/components/use-one-shot-reads.js.map +1 -0
- package/src/lib/model/experiments.d.ts +35 -0
- package/src/lib/model/experiments.js +34 -0
- package/src/lib/model/experiments.js.map +1 -1
- package/src/lib/model/overlays.d.ts +13 -2
- package/src/lib/model/overlays.js +18 -6
- package/src/lib/model/overlays.js.map +1 -1
- package/src/lib/plugin.js +13 -8
- package/src/lib/plugin.js.map +1 -1
|
@@ -0,0 +1,345 @@
|
|
|
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 } from "react/jsx-runtime";
|
|
19
|
+
import { checkEntitlement, pluginDocsHelp } from "@aglyn/aglyn";
|
|
20
|
+
import { compareOverlayPrecedence, overlayStatus } from "../model/index.js";
|
|
21
|
+
import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
|
|
22
|
+
import { ListPagination } from "@aglyn/shared-ui-jsx/components/list-pagination.component";
|
|
23
|
+
import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
|
|
24
|
+
import { useSnackbar } from "@aglyn/shared-ui-snackstack";
|
|
25
|
+
import { Alert, Chip, Stack, Switch, TableBody, TableCell, TableHead, TableRow, Typography } from "@mui/material";
|
|
26
|
+
import { collection, doc, getDoc, getDocs, limit, orderBy, query, setDoc } from "firebase/firestore";
|
|
27
|
+
import { Fragment, useState } from "react";
|
|
28
|
+
import { useFirestore } from "@aglyn/tenant-feature-instance";
|
|
29
|
+
import { ceilingedWindow } from "@aglyn/tenant-feature-instance/hooks/host-collection-queries";
|
|
30
|
+
import { useMarketingOrgMount } from "./marketing-org-mount.js";
|
|
31
|
+
import { OrgSitePickerButton, orgSiteSectionHref } from "./org-site-picker-button.js";
|
|
32
|
+
import { overlayDisplayName, overlayEngagementLabel, overlayPagesLabel, overlayWindowLabel } from "./overlay-labels.js";
|
|
33
|
+
import { ORG_SITES_PER_PAGE, useOrgSiteReads } from "./use-one-shot-reads.js";
|
|
34
|
+
/**
|
|
35
|
+
* How many overlays are read for each site.
|
|
36
|
+
*
|
|
37
|
+
* Smaller than the site's own ceiling on purpose: a site runs a handful of
|
|
38
|
+
* bars and popups, a page here holds a page of sites' worth of them, and a
|
|
39
|
+
* site with more says so on its row and links to its own list, where every
|
|
40
|
+
* one of them is — and where the order they take can be changed.
|
|
41
|
+
*/ export const ORG_OVERLAYS_PER_SITE = 10;
|
|
42
|
+
/**
|
|
43
|
+
* One site's overlays and its defaults: the overlays ordered and ceilinged
|
|
44
|
+
* the way the site's own list reads them — `name` is on every overlay its
|
|
45
|
+
* only creator writes — and the host document for the defaults, which the
|
|
46
|
+
* console shell already listens to for its site list.
|
|
47
|
+
*/ async function readSiteOverlays(firestore, hostId) {
|
|
48
|
+
var _site_announcementBar, _site_announcementBar1, _site_popup, _site_popup1;
|
|
49
|
+
const [overlays, host] = await Promise.all([
|
|
50
|
+
getDocs(query(collection(firestore, 'hosts', hostId, 'overlays'), orderBy('name'), limit(ORG_OVERLAYS_PER_SITE + 1))),
|
|
51
|
+
getDoc(doc(firestore, 'hosts', hostId)).catch(()=>null)
|
|
52
|
+
]);
|
|
53
|
+
const { rows, truncated } = ceilingedWindow(overlays.docs.map((entry)=>_extends({}, entry.data(), {
|
|
54
|
+
$id: entry.id
|
|
55
|
+
})), ORG_OVERLAYS_PER_SITE);
|
|
56
|
+
const site = (host == null ? void 0 : host.exists()) ? host.data() : null;
|
|
57
|
+
return {
|
|
58
|
+
// In the order the site shows them. A window the ceiling cut short is
|
|
59
|
+
// the first overlays by name, and the site's line says so.
|
|
60
|
+
rows: [
|
|
61
|
+
...rows
|
|
62
|
+
].sort(compareOverlayPrecedence),
|
|
63
|
+
truncated,
|
|
64
|
+
// The same test the page enricher applies before it draws either one.
|
|
65
|
+
defaults: site ? {
|
|
66
|
+
bar: Boolean(((_site_announcementBar = site.announcementBar) == null ? void 0 : _site_announcementBar.enabled) && ((_site_announcementBar1 = site.announcementBar) == null ? void 0 : _site_announcementBar1.text)),
|
|
67
|
+
popup: Boolean(((_site_popup = site.popup) == null ? void 0 : _site_popup.enabled) && ((_site_popup1 = site.popup) == null ? void 0 : _site_popup1.body))
|
|
68
|
+
} : null
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const STATUS_CHIP = {
|
|
72
|
+
off: {
|
|
73
|
+
label: 'Off',
|
|
74
|
+
color: 'default'
|
|
75
|
+
},
|
|
76
|
+
scheduled: {
|
|
77
|
+
label: 'Scheduled',
|
|
78
|
+
color: 'info'
|
|
79
|
+
},
|
|
80
|
+
live: {
|
|
81
|
+
label: 'Live',
|
|
82
|
+
color: 'success'
|
|
83
|
+
}
|
|
84
|
+
};
|
|
85
|
+
/** Columns in a row, so a site's own line can span all of them. */ const COLUMNS = 7;
|
|
86
|
+
/**
|
|
87
|
+
* Every site's announcement bars and popups, on the organization's
|
|
88
|
+
* Marketing hub.
|
|
89
|
+
*
|
|
90
|
+
* READ-ONLY but for the switch. An overlay is drawn on one site's pages and
|
|
91
|
+
* written against that site's variables, screens and order, so its editor
|
|
92
|
+
* stays on the site: each row's Edit and the New button open the site's own
|
|
93
|
+
* Overlays section. What the org hub adds is the view across sites — which
|
|
94
|
+
* of them are showing what, and their lifetime engagement — and the one
|
|
95
|
+
* change that needs no editor, turning an overlay on or off.
|
|
96
|
+
*
|
|
97
|
+
* Grouped by site, each group in the order the site shows its overlays,
|
|
98
|
+
* under a line naming the site and whether its default bar and popup are
|
|
99
|
+
* on. No 14-day analytics row: that is a document read per day per site,
|
|
100
|
+
* and it lives on the site.
|
|
101
|
+
*/ export function OrgOverlaysCard(props) {
|
|
102
|
+
var _ref;
|
|
103
|
+
const { org } = props;
|
|
104
|
+
const mount = useMarketingOrgMount();
|
|
105
|
+
const firestore = useFirestore();
|
|
106
|
+
const { enqueueSnackbar } = useSnackbar();
|
|
107
|
+
const entitled = checkEntitlement(org, 'marketingOverlays');
|
|
108
|
+
const sites = (_ref = mount == null ? void 0 : mount.hosts) != null ? _ref : [];
|
|
109
|
+
const [page, setPage] = useState(0);
|
|
110
|
+
const pageSites = sites.slice(page * ORG_SITES_PER_PAGE, (page + 1) * ORG_SITES_PER_PAGE);
|
|
111
|
+
const { reads, patch } = useOrgSiteReads(entitled ? pageSites.map((site)=>site.id) : [], (hostId)=>readSiteOverlays(firestore, hostId), [
|
|
112
|
+
firestore,
|
|
113
|
+
entitled
|
|
114
|
+
]);
|
|
115
|
+
/**
|
|
116
|
+
* One field, merged, on the row's own site — the write the site's own
|
|
117
|
+
* switch makes, and one the rules allow every editor of that site.
|
|
118
|
+
*/ const handleToggle = async (hostId, overlay)=>{
|
|
119
|
+
const enabled = overlay.enabled === false;
|
|
120
|
+
try {
|
|
121
|
+
await setDoc(doc(firestore, 'hosts', hostId, 'overlays', overlay.$id), {
|
|
122
|
+
enabled
|
|
123
|
+
}, {
|
|
124
|
+
merge: true
|
|
125
|
+
});
|
|
126
|
+
patch(hostId, (site)=>_extends({}, site, {
|
|
127
|
+
rows: site.rows.map((row)=>row.$id === overlay.$id ? _extends({}, row, {
|
|
128
|
+
enabled
|
|
129
|
+
}) : row)
|
|
130
|
+
}));
|
|
131
|
+
} catch (error) {
|
|
132
|
+
console.error(error);
|
|
133
|
+
enqueueSnackbar('The overlay could not be switched', {
|
|
134
|
+
variant: 'error'
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
return /*#__PURE__*/ _jsx(CardDisplay, {
|
|
139
|
+
header: "Announcement bars & popups",
|
|
140
|
+
subheader: "Every site in this organization",
|
|
141
|
+
help: pluginDocsHelp('marketingOverlays', {
|
|
142
|
+
anchor: '#across-your-sites'
|
|
143
|
+
}),
|
|
144
|
+
contentGutterX: true,
|
|
145
|
+
contentGutterY: true,
|
|
146
|
+
contentBordered: "all",
|
|
147
|
+
children: !entitled ? /*#__PURE__*/ _jsx(Alert, {
|
|
148
|
+
severity: "info",
|
|
149
|
+
children: 'Announcement bars and popups are included from the Starter ' + 'plan — see Billing to upgrade.'
|
|
150
|
+
}) : !mount || !mount.hostsReady ? null : !sites.length ? /*#__PURE__*/ _jsx(Typography, {
|
|
151
|
+
variant: "body2",
|
|
152
|
+
color: "text.secondary",
|
|
153
|
+
children: 'This organization has no sites yet. Overlays are drawn on a ' + 'site’s pages, so they are created on a site.'
|
|
154
|
+
}) : /*#__PURE__*/ _jsxs(Stack, {
|
|
155
|
+
spacing: 2,
|
|
156
|
+
children: [
|
|
157
|
+
/*#__PURE__*/ _jsxs(Stack, {
|
|
158
|
+
direction: "row",
|
|
159
|
+
spacing: 2,
|
|
160
|
+
sx: {
|
|
161
|
+
alignItems: 'center',
|
|
162
|
+
justifyContent: 'space-between'
|
|
163
|
+
},
|
|
164
|
+
children: [
|
|
165
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
166
|
+
variant: "body2",
|
|
167
|
+
color: "text.secondary",
|
|
168
|
+
children: 'Each site shows the first matching bar and popup in its own ' + 'order. Edit and create them on the site; switch them on or ' + 'off here.'
|
|
169
|
+
}),
|
|
170
|
+
/*#__PURE__*/ _jsx(OrgSitePickerButton, {
|
|
171
|
+
mount: mount,
|
|
172
|
+
section: "overlays",
|
|
173
|
+
label: "New overlay"
|
|
174
|
+
})
|
|
175
|
+
]
|
|
176
|
+
}),
|
|
177
|
+
/*#__PURE__*/ _jsxs(ScrollTable, {
|
|
178
|
+
size: "small",
|
|
179
|
+
"aria-label": "Overlays by site",
|
|
180
|
+
children: [
|
|
181
|
+
/*#__PURE__*/ _jsx(TableHead, {
|
|
182
|
+
children: /*#__PURE__*/ _jsxs(TableRow, {
|
|
183
|
+
children: [
|
|
184
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
185
|
+
children: 'Overlay'
|
|
186
|
+
}),
|
|
187
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
188
|
+
children: 'Kind'
|
|
189
|
+
}),
|
|
190
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
191
|
+
children: 'Status'
|
|
192
|
+
}),
|
|
193
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
194
|
+
children: 'Window'
|
|
195
|
+
}),
|
|
196
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
197
|
+
children: 'Pages'
|
|
198
|
+
}),
|
|
199
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
200
|
+
children: 'Engagement'
|
|
201
|
+
}),
|
|
202
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
203
|
+
align: "right",
|
|
204
|
+
children: 'Actions'
|
|
205
|
+
})
|
|
206
|
+
]
|
|
207
|
+
})
|
|
208
|
+
}),
|
|
209
|
+
/*#__PURE__*/ _jsx(TableBody, {
|
|
210
|
+
children: pageSites.map((site)=>{
|
|
211
|
+
var _ref, _ref1;
|
|
212
|
+
const read = reads.get(site.id);
|
|
213
|
+
const answer = (_ref = read == null ? void 0 : read.value) != null ? _ref : null;
|
|
214
|
+
const href = orgSiteSectionHref(mount, site.id, 'overlays');
|
|
215
|
+
const name = site.name || site.subdomain || site.id;
|
|
216
|
+
return /*#__PURE__*/ _jsxs(Fragment, {
|
|
217
|
+
children: [
|
|
218
|
+
/*#__PURE__*/ _jsx(TableRow, {
|
|
219
|
+
children: /*#__PURE__*/ _jsx(TableCell, {
|
|
220
|
+
colSpan: COLUMNS,
|
|
221
|
+
children: /*#__PURE__*/ _jsxs(Stack, {
|
|
222
|
+
direction: "row",
|
|
223
|
+
spacing: 2,
|
|
224
|
+
sx: {
|
|
225
|
+
alignItems: 'baseline',
|
|
226
|
+
flexWrap: 'wrap'
|
|
227
|
+
},
|
|
228
|
+
children: [
|
|
229
|
+
/*#__PURE__*/ _jsx(Typography, {
|
|
230
|
+
variant: "subtitle2",
|
|
231
|
+
children: href ? /*#__PURE__*/ _jsx(AppLink, {
|
|
232
|
+
href: href,
|
|
233
|
+
children: name
|
|
234
|
+
}) : name
|
|
235
|
+
}),
|
|
236
|
+
(answer == null ? void 0 : answer.defaults) ? /*#__PURE__*/ _jsx(Typography, {
|
|
237
|
+
variant: "caption",
|
|
238
|
+
color: "text.secondary",
|
|
239
|
+
children: `Default bar ${answer.defaults.bar ? 'on' : 'off'} · ` + `default popup ${answer.defaults.popup ? 'on' : 'off'}`
|
|
240
|
+
}) : null,
|
|
241
|
+
(answer == null ? void 0 : answer.truncated) ? /*#__PURE__*/ _jsx(Typography, {
|
|
242
|
+
variant: "caption",
|
|
243
|
+
color: "text.secondary",
|
|
244
|
+
children: `More than ${ORG_OVERLAYS_PER_SITE} on this ` + 'site — these are the first by name; the ' + 'site’s own list has every one.'
|
|
245
|
+
}) : null
|
|
246
|
+
]
|
|
247
|
+
})
|
|
248
|
+
})
|
|
249
|
+
}),
|
|
250
|
+
(read == null ? void 0 : read.status) === 'error' ? /*#__PURE__*/ _jsx(TableRow, {
|
|
251
|
+
children: /*#__PURE__*/ _jsx(TableCell, {
|
|
252
|
+
colSpan: COLUMNS,
|
|
253
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
254
|
+
variant: "body2",
|
|
255
|
+
color: "text.secondary",
|
|
256
|
+
children: 'This site’s overlays could not be read.'
|
|
257
|
+
})
|
|
258
|
+
})
|
|
259
|
+
}) : answer && !answer.rows.length ? /*#__PURE__*/ _jsx(TableRow, {
|
|
260
|
+
children: /*#__PURE__*/ _jsx(TableCell, {
|
|
261
|
+
colSpan: COLUMNS,
|
|
262
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
263
|
+
variant: "body2",
|
|
264
|
+
color: "text.secondary",
|
|
265
|
+
children: 'No overlays on this site.'
|
|
266
|
+
})
|
|
267
|
+
})
|
|
268
|
+
}) : ((_ref1 = answer == null ? void 0 : answer.rows) != null ? _ref1 : []).map((overlay)=>{
|
|
269
|
+
const status = STATUS_CHIP[overlayStatus(overlay, Date.now())];
|
|
270
|
+
return /*#__PURE__*/ _jsxs(TableRow, {
|
|
271
|
+
children: [
|
|
272
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
273
|
+
children: overlayDisplayName(overlay)
|
|
274
|
+
}),
|
|
275
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
276
|
+
children: overlay.kind === 'bar' ? 'Bar' : 'Popup'
|
|
277
|
+
}),
|
|
278
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
279
|
+
children: /*#__PURE__*/ _jsx(Chip, {
|
|
280
|
+
size: "small",
|
|
281
|
+
color: status.color,
|
|
282
|
+
label: status.label
|
|
283
|
+
})
|
|
284
|
+
}),
|
|
285
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
286
|
+
children: overlayWindowLabel(overlay)
|
|
287
|
+
}),
|
|
288
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
289
|
+
children: overlayPagesLabel(overlay)
|
|
290
|
+
}),
|
|
291
|
+
/*#__PURE__*/ _jsx(TableCell, {
|
|
292
|
+
children: /*#__PURE__*/ _jsx(Typography, {
|
|
293
|
+
variant: "caption",
|
|
294
|
+
color: "text.secondary",
|
|
295
|
+
children: overlayEngagementLabel(overlay.stats)
|
|
296
|
+
})
|
|
297
|
+
}),
|
|
298
|
+
/*#__PURE__*/ _jsxs(TableCell, {
|
|
299
|
+
align: "right",
|
|
300
|
+
children: [
|
|
301
|
+
/*#__PURE__*/ _jsx(Switch, {
|
|
302
|
+
size: "small",
|
|
303
|
+
checked: overlay.enabled !== false,
|
|
304
|
+
onChange: ()=>void handleToggle(site.id, overlay),
|
|
305
|
+
slotProps: {
|
|
306
|
+
input: {
|
|
307
|
+
'aria-label': `Show ${overlayDisplayName(overlay)} on ${name}`
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}),
|
|
311
|
+
href ? /*#__PURE__*/ _jsx(AppLink, {
|
|
312
|
+
componentVariant: "button",
|
|
313
|
+
href: href,
|
|
314
|
+
size: "small",
|
|
315
|
+
color: "primary",
|
|
316
|
+
"aria-label": `Edit ${overlayDisplayName(overlay)} on ${name}`,
|
|
317
|
+
children: 'Edit'
|
|
318
|
+
}) : null
|
|
319
|
+
]
|
|
320
|
+
})
|
|
321
|
+
]
|
|
322
|
+
}, overlay.$id);
|
|
323
|
+
})
|
|
324
|
+
]
|
|
325
|
+
}, site.id);
|
|
326
|
+
})
|
|
327
|
+
})
|
|
328
|
+
]
|
|
329
|
+
}),
|
|
330
|
+
/*#__PURE__*/ _jsx(ListPagination, {
|
|
331
|
+
page: page,
|
|
332
|
+
pageSize: ORG_SITES_PER_PAGE,
|
|
333
|
+
rowCount: pageSites.length,
|
|
334
|
+
count: sites.length,
|
|
335
|
+
onPageChange: setPage,
|
|
336
|
+
labelDisplayedRows: ({ from, to, count })=>`Sites ${from}–${to} of ${count}`
|
|
337
|
+
})
|
|
338
|
+
]
|
|
339
|
+
})
|
|
340
|
+
});
|
|
341
|
+
}
|
|
342
|
+
OrgOverlaysCard.displayName = 'OrgOverlaysCard';
|
|
343
|
+
export default OrgOverlaysCard;
|
|
344
|
+
|
|
345
|
+
//# sourceMappingURL=org-overlays-card.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/org-overlays-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 type AglynOrgBilling,\n checkEntitlement,\n pluginDocsHelp,\n} from '@aglyn/aglyn'\nimport { compareOverlayPrecedence, overlayStatus, type HostOverlay } from '../model'\nimport { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { ListPagination } from '@aglyn/shared-ui-jsx/components/list-pagination.component'\nimport { ScrollTable } from '@aglyn/shared-ui-jsx/components/scroll-table.component'\nimport { useSnackbar } from '@aglyn/shared-ui-snackstack'\nimport {\n Alert,\n Chip,\n Stack,\n Switch,\n TableBody,\n TableCell,\n TableHead,\n TableRow,\n Typography,\n} from '@mui/material'\nimport {\n collection,\n doc,\n getDoc,\n getDocs,\n limit,\n orderBy,\n query,\n setDoc,\n type Firestore,\n} from 'firebase/firestore'\nimport { Fragment, useState } from 'react'\nimport { useFirestore } from '@aglyn/tenant-feature-instance'\nimport { ceilingedWindow } from '@aglyn/tenant-feature-instance/hooks/host-collection-queries'\nimport { useMarketingOrgMount } from './marketing-org-mount'\nimport {\n OrgSitePickerButton,\n orgSiteSectionHref,\n} from './org-site-picker-button'\nimport {\n overlayDisplayName,\n overlayEngagementLabel,\n overlayPagesLabel,\n overlayWindowLabel,\n} from './overlay-labels'\nimport { ORG_SITES_PER_PAGE, useOrgSiteReads } from './use-one-shot-reads'\n\n/**\n * How many overlays are read for each site.\n *\n * Smaller than the site's own ceiling on purpose: a site runs a handful of\n * bars and popups, a page here holds a page of sites' worth of them, and a\n * site with more says so on its row and links to its own list, where every\n * one of them is — and where the order they take can be changed.\n */\nexport const ORG_OVERLAYS_PER_SITE = 10\n\ntype OverlayRow = HostOverlay & { $id: string }\n\n/** What one site's group of rows is drawn from. */\ninterface SiteOverlays {\n /** In the order the site shows them: see `compareOverlayPrecedence`. */\n rows: OverlayRow[]\n /** The site has more overlays than were read. */\n truncated: boolean\n /**\n * Whether the site's always-on default bar and popup would show — the ones\n * a matching overlay takes priority over. `null` when the site document\n * could not be read, which is drawn as nothing rather than as \"off\".\n */\n defaults: { bar: boolean; popup: boolean } | null\n}\n\n/**\n * One site's overlays and its defaults: the overlays ordered and ceilinged\n * the way the site's own list reads them — `name` is on every overlay its\n * only creator writes — and the host document for the defaults, which the\n * console shell already listens to for its site list.\n */\nasync function readSiteOverlays(\n firestore: Firestore,\n hostId: string,\n): Promise<SiteOverlays> {\n const [overlays, host] = await Promise.all([\n getDocs(\n query(\n collection(firestore, 'hosts', hostId, 'overlays'),\n orderBy('name'),\n limit(ORG_OVERLAYS_PER_SITE + 1),\n ),\n ),\n getDoc(doc(firestore, 'hosts', hostId)).catch(() => null),\n ])\n const { rows, truncated } = ceilingedWindow(\n overlays.docs.map(\n (entry) => ({ ...(entry.data() as HostOverlay), $id: entry.id }) as OverlayRow,\n ),\n ORG_OVERLAYS_PER_SITE,\n )\n const site = host?.exists() ? (host.data() as Record<string, any>) : null\n return {\n // In the order the site shows them. A window the ceiling cut short is\n // the first overlays by name, and the site's line says so.\n rows: [...rows].sort(compareOverlayPrecedence),\n truncated,\n // The same test the page enricher applies before it draws either one.\n defaults: site\n ? {\n bar: Boolean(site.announcementBar?.enabled && site.announcementBar?.text),\n popup: Boolean(site.popup?.enabled && site.popup?.body),\n }\n : null,\n }\n}\n\nconst STATUS_CHIP: Record<\n ReturnType<typeof overlayStatus>,\n { label: string; color: 'default' | 'info' | 'success' }\n> = {\n off: { label: 'Off', color: 'default' },\n scheduled: { label: 'Scheduled', color: 'info' },\n live: { label: 'Live', color: 'success' },\n}\n\n/** Columns in a row, so a site's own line can span all of them. */\nconst COLUMNS = 7\n\nexport interface OrgOverlaysCardProps {\n /** The org's plan, for the `marketingOverlays` entitlement. */\n org?: Partial<AglynOrgBilling>\n}\n\n/**\n * Every site's announcement bars and popups, on the organization's\n * Marketing hub.\n *\n * READ-ONLY but for the switch. An overlay is drawn on one site's pages and\n * written against that site's variables, screens and order, so its editor\n * stays on the site: each row's Edit and the New button open the site's own\n * Overlays section. What the org hub adds is the view across sites — which\n * of them are showing what, and their lifetime engagement — and the one\n * change that needs no editor, turning an overlay on or off.\n *\n * Grouped by site, each group in the order the site shows its overlays,\n * under a line naming the site and whether its default bar and popup are\n * on. No 14-day analytics row: that is a document read per day per site,\n * and it lives on the site.\n */\nexport function OrgOverlaysCard(props: OrgOverlaysCardProps) {\n const { org } = props\n const mount = useMarketingOrgMount()\n const firestore = useFirestore()\n const { enqueueSnackbar } = useSnackbar()\n const entitled = checkEntitlement(org, 'marketingOverlays')\n\n const sites = mount?.hosts ?? []\n const [page, setPage] = useState(0)\n const pageSites = sites.slice(\n page * ORG_SITES_PER_PAGE,\n (page + 1) * ORG_SITES_PER_PAGE,\n )\n const { reads, patch } = useOrgSiteReads<SiteOverlays>(\n entitled ? pageSites.map((site) => site.id) : [],\n (hostId) => readSiteOverlays(firestore, hostId),\n [firestore, entitled],\n )\n\n /**\n * One field, merged, on the row's own site — the write the site's own\n * switch makes, and one the rules allow every editor of that site.\n */\n const handleToggle = async (hostId: string, overlay: OverlayRow) => {\n const enabled = overlay.enabled === false\n try {\n await setDoc(\n doc(firestore, 'hosts', hostId, 'overlays', overlay.$id),\n { enabled },\n { merge: true },\n )\n patch(hostId, (site) => ({\n ...site,\n rows: site.rows.map((row) =>\n row.$id === overlay.$id ? { ...row, enabled } : row,\n ),\n }))\n } catch (error) {\n console.error(error)\n enqueueSnackbar('The overlay could not be switched', {\n variant: 'error',\n })\n }\n }\n\n return (\n <CardDisplay\n header=\"Announcement bars & popups\"\n subheader=\"Every site in this organization\"\n help={pluginDocsHelp('marketingOverlays', {\n anchor: '#across-your-sites',\n })}\n contentGutterX\n contentGutterY\n contentBordered=\"all\"\n >\n {!entitled ? (\n <Alert severity=\"info\">\n {'Announcement bars and popups are included from the Starter ' +\n 'plan — see Billing to upgrade.'}\n </Alert>\n ) : !mount || !mount.hostsReady ? null : !sites.length ? (\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This organization has no sites yet. Overlays are drawn on a ' +\n 'site’s pages, so they are created on a site.'}\n </Typography>\n ) : (\n <Stack spacing={2}>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'center', justifyContent: 'space-between' }}\n >\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'Each site shows the first matching bar and popup in its own ' +\n 'order. Edit and create them on the site; switch them on or ' +\n 'off here.'}\n </Typography>\n <OrgSitePickerButton\n mount={mount}\n section=\"overlays\"\n label=\"New overlay\"\n />\n </Stack>\n <ScrollTable size=\"small\" aria-label=\"Overlays by site\">\n <TableHead>\n <TableRow>\n <TableCell>{'Overlay'}</TableCell>\n <TableCell>{'Kind'}</TableCell>\n <TableCell>{'Status'}</TableCell>\n <TableCell>{'Window'}</TableCell>\n <TableCell>{'Pages'}</TableCell>\n <TableCell>{'Engagement'}</TableCell>\n <TableCell align=\"right\">{'Actions'}</TableCell>\n </TableRow>\n </TableHead>\n <TableBody>\n {pageSites.map((site) => {\n const read = reads.get(site.id)\n const answer = read?.value ?? null\n const href = orgSiteSectionHref(mount, site.id, 'overlays')\n const name = site.name || site.subdomain || site.id\n return (\n <Fragment key={site.id}>\n {/* The site's own line: its name, its defaults, and what was not read. */}\n <TableRow>\n <TableCell colSpan={COLUMNS}>\n <Stack\n direction=\"row\"\n spacing={2}\n sx={{ alignItems: 'baseline', flexWrap: 'wrap' }}\n >\n <Typography variant=\"subtitle2\">\n {href ? <AppLink href={href}>{name}</AppLink> : name}\n </Typography>\n {answer?.defaults ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`Default bar ${answer.defaults.bar ? 'on' : 'off'} · ` +\n `default popup ${answer.defaults.popup ? 'on' : 'off'}`}\n </Typography>\n ) : null}\n {answer?.truncated ? (\n <Typography variant=\"caption\" color=\"text.secondary\">\n {`More than ${ORG_OVERLAYS_PER_SITE} on this ` +\n 'site — these are the first by name; the ' +\n 'site’s own list has every one.'}\n </Typography>\n ) : null}\n </Stack>\n </TableCell>\n </TableRow>\n {read?.status === 'error' ? (\n <TableRow>\n <TableCell colSpan={COLUMNS}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'This site’s overlays could not be read.'}\n </Typography>\n </TableCell>\n </TableRow>\n ) : answer && !answer.rows.length ? (\n <TableRow>\n <TableCell colSpan={COLUMNS}>\n <Typography variant=\"body2\" color=\"text.secondary\">\n {'No overlays on this site.'}\n </Typography>\n </TableCell>\n </TableRow>\n ) : (\n (answer?.rows ?? []).map((overlay) => {\n const status = STATUS_CHIP[overlayStatus(overlay, Date.now())]\n return (\n <TableRow key={overlay.$id}>\n <TableCell>{overlayDisplayName(overlay)}</TableCell>\n <TableCell>\n {overlay.kind === 'bar' ? 'Bar' : 'Popup'}\n </TableCell>\n <TableCell>\n <Chip\n size=\"small\"\n color={status.color}\n label={status.label}\n />\n </TableCell>\n <TableCell>{overlayWindowLabel(overlay)}</TableCell>\n <TableCell>{overlayPagesLabel(overlay)}</TableCell>\n <TableCell>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {overlayEngagementLabel(overlay.stats)}\n </Typography>\n </TableCell>\n <TableCell align=\"right\">\n <Switch\n size=\"small\"\n checked={overlay.enabled !== false}\n onChange={() => void handleToggle(site.id, overlay)}\n slotProps={{\n input: {\n 'aria-label': `Show ${overlayDisplayName(overlay)} on ${name}`,\n },\n }}\n />\n {/* A site the shell could not address has no Overlays section to open. */}\n {href ? (\n <AppLink\n componentVariant=\"button\"\n href={href}\n size=\"small\"\n color=\"primary\"\n aria-label={`Edit ${overlayDisplayName(overlay)} on ${name}`}\n >\n {'Edit'}\n </AppLink>\n ) : null}\n </TableCell>\n </TableRow>\n )\n })\n )}\n </Fragment>\n )\n })}\n </TableBody>\n </ScrollTable>\n <ListPagination\n page={page}\n pageSize={ORG_SITES_PER_PAGE}\n rowCount={pageSites.length}\n count={sites.length}\n onPageChange={setPage}\n labelDisplayedRows={({ from, to, count }) =>\n `Sites ${from}–${to} of ${count}`\n }\n />\n </Stack>\n )}\n </CardDisplay>\n )\n}\nOrgOverlaysCard.displayName = 'OrgOverlaysCard'\n\nexport default OrgOverlaysCard\n"],"names":["checkEntitlement","pluginDocsHelp","compareOverlayPrecedence","overlayStatus","AppLink","CardDisplay","ListPagination","ScrollTable","useSnackbar","Alert","Chip","Stack","Switch","TableBody","TableCell","TableHead","TableRow","Typography","collection","doc","getDoc","getDocs","limit","orderBy","query","setDoc","Fragment","useState","useFirestore","ceilingedWindow","useMarketingOrgMount","OrgSitePickerButton","orgSiteSectionHref","overlayDisplayName","overlayEngagementLabel","overlayPagesLabel","overlayWindowLabel","ORG_SITES_PER_PAGE","useOrgSiteReads","ORG_OVERLAYS_PER_SITE","readSiteOverlays","firestore","hostId","site","overlays","host","Promise","all","catch","rows","truncated","docs","map","entry","data","$id","id","exists","sort","defaults","bar","Boolean","announcementBar","enabled","text","popup","body","STATUS_CHIP","off","label","color","scheduled","live","COLUMNS","OrgOverlaysCard","props","org","mount","enqueueSnackbar","entitled","sites","hosts","page","setPage","pageSites","slice","reads","patch","handleToggle","overlay","merge","row","error","console","variant","header","subheader","help","anchor","contentGutterX","contentGutterY","contentBordered","severity","hostsReady","length","spacing","direction","sx","alignItems","justifyContent","section","size","aria-label","align","read","get","answer","value","href","name","subdomain","colSpan","flexWrap","status","Date","now","kind","stats","checked","onChange","slotProps","input","componentVariant","pageSize","rowCount","count","onPageChange","labelDisplayedRows","from","to","displayName"],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type MarketingOrgMount } from './marketing-org-mount';
|
|
2
|
+
/** A site's own Marketing hub at `section`, or `null` when it has no address. */
|
|
3
|
+
export declare function orgSiteSectionHref(mount: MarketingOrgMount, hostId: string, section: string): string | null;
|
|
4
|
+
export interface OrgSitePickerButtonProps {
|
|
5
|
+
mount: MarketingOrgMount;
|
|
6
|
+
/** The section of the site's Marketing hub to open — `overlays`, `experiments`. */
|
|
7
|
+
section: string;
|
|
8
|
+
label: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* The org hub's way into anything that is EDITED on a site: a button that
|
|
12
|
+
* opens one of the organization's sites at a section of its own Marketing
|
|
13
|
+
* hub.
|
|
14
|
+
*
|
|
15
|
+
* An overlay is drawn on one site's pages and an A/B test splits one site's
|
|
16
|
+
* traffic, so their editors belong to the site — the org hub lists them and
|
|
17
|
+
* sends the reader to the site to write. With one site there is nothing to
|
|
18
|
+
* choose and the button is the link; with several it opens a menu of them.
|
|
19
|
+
* A site the shell could not give a subdomain is listed and disabled: it has
|
|
20
|
+
* no hub address to open.
|
|
21
|
+
*/
|
|
22
|
+
export declare function OrgSitePickerButton(props: OrgSitePickerButtonProps): import("react").JSX.Element;
|
|
23
|
+
export declare namespace OrgSitePickerButton {
|
|
24
|
+
var displayName: string;
|
|
25
|
+
}
|
|
26
|
+
export default OrgSitePickerButton;
|
|
@@ -0,0 +1,104 @@
|
|
|
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 { AppLink } from "@aglyn/shared-ui-jsx";
|
|
20
|
+
import { Button, Menu, MenuItem } from "@mui/material";
|
|
21
|
+
import { forwardRef, useState } from "react";
|
|
22
|
+
import { orgSiteHubPath } from "./marketing-org-mount.js";
|
|
23
|
+
/** A site's own Marketing hub at `section`, or `null` when it has no address. */ export function orgSiteSectionHref(mount, hostId, section) {
|
|
24
|
+
const hub = orgSiteHubPath(mount, hostId, 'marketing');
|
|
25
|
+
return hub ? `${hub}/${section}` : null;
|
|
26
|
+
}
|
|
27
|
+
/** The anchor a site in the menu renders as — a real link, like every other. */ const MenuItemLink = /*#__PURE__*/ forwardRef((props, ref)=>/*#__PURE__*/ _jsx(AppLink, _extends({
|
|
28
|
+
ref: ref
|
|
29
|
+
}, props, {
|
|
30
|
+
componentVariant: 'naked'
|
|
31
|
+
})));
|
|
32
|
+
MenuItemLink.displayName = 'OrgSitePickerMenuItemLink';
|
|
33
|
+
/**
|
|
34
|
+
* The org hub's way into anything that is EDITED on a site: a button that
|
|
35
|
+
* opens one of the organization's sites at a section of its own Marketing
|
|
36
|
+
* hub.
|
|
37
|
+
*
|
|
38
|
+
* An overlay is drawn on one site's pages and an A/B test splits one site's
|
|
39
|
+
* traffic, so their editors belong to the site — the org hub lists them and
|
|
40
|
+
* sends the reader to the site to write. With one site there is nothing to
|
|
41
|
+
* choose and the button is the link; with several it opens a menu of them.
|
|
42
|
+
* A site the shell could not give a subdomain is listed and disabled: it has
|
|
43
|
+
* no hub address to open.
|
|
44
|
+
*/ export function OrgSitePickerButton(props) {
|
|
45
|
+
const { mount, section, label } = props;
|
|
46
|
+
const [anchorEl, setAnchorEl] = useState(null);
|
|
47
|
+
const sites = mount.hosts;
|
|
48
|
+
if (sites.length === 1) {
|
|
49
|
+
const href = orgSiteSectionHref(mount, sites[0].id, section);
|
|
50
|
+
return href ? /*#__PURE__*/ _jsx(AppLink, {
|
|
51
|
+
componentVariant: "button",
|
|
52
|
+
href: href,
|
|
53
|
+
size: "small",
|
|
54
|
+
color: "primary",
|
|
55
|
+
variant: "contained",
|
|
56
|
+
children: label
|
|
57
|
+
}) : /*#__PURE__*/ _jsx(Button, {
|
|
58
|
+
size: "small",
|
|
59
|
+
color: "primary",
|
|
60
|
+
variant: "contained",
|
|
61
|
+
disabled: true,
|
|
62
|
+
children: label
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
return /*#__PURE__*/ _jsxs(_Fragment, {
|
|
66
|
+
children: [
|
|
67
|
+
/*#__PURE__*/ _jsx(Button, {
|
|
68
|
+
size: "small",
|
|
69
|
+
color: "primary",
|
|
70
|
+
variant: "contained",
|
|
71
|
+
disabled: !sites.length,
|
|
72
|
+
"aria-haspopup": "true",
|
|
73
|
+
onClick: (event)=>setAnchorEl(event.currentTarget),
|
|
74
|
+
children: label
|
|
75
|
+
}),
|
|
76
|
+
/*#__PURE__*/ _jsx(Menu, {
|
|
77
|
+
anchorEl: anchorEl,
|
|
78
|
+
open: Boolean(anchorEl),
|
|
79
|
+
onClose: ()=>setAnchorEl(null),
|
|
80
|
+
slotProps: {
|
|
81
|
+
list: {
|
|
82
|
+
'aria-label': `${label} on which site`
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
children: sites.map((site)=>{
|
|
86
|
+
const href = orgSiteSectionHref(mount, site.id, section);
|
|
87
|
+
return /*#__PURE__*/ _jsx(MenuItem, _extends({}, href ? {
|
|
88
|
+
component: MenuItemLink,
|
|
89
|
+
href
|
|
90
|
+
} : {
|
|
91
|
+
disabled: true
|
|
92
|
+
}, {
|
|
93
|
+
onClick: ()=>setAnchorEl(null),
|
|
94
|
+
children: site.name || site.subdomain || site.id
|
|
95
|
+
}), site.id);
|
|
96
|
+
})
|
|
97
|
+
})
|
|
98
|
+
]
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
OrgSitePickerButton.displayName = 'OrgSitePickerButton';
|
|
102
|
+
export default OrgSitePickerButton;
|
|
103
|
+
|
|
104
|
+
//# sourceMappingURL=org-site-picker-button.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/org-site-picker-button.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 { AppLink, type AppLinkNakedLinkProps } from '@aglyn/shared-ui-jsx'\nimport { Button, Menu, MenuItem } from '@mui/material'\nimport { forwardRef, useState } from 'react'\nimport { orgSiteHubPath, type MarketingOrgMount } from './marketing-org-mount'\n\n/** A site's own Marketing hub at `section`, or `null` when it has no address. */\nexport function orgSiteSectionHref(\n mount: MarketingOrgMount,\n hostId: string,\n section: string,\n): string | null {\n const hub = orgSiteHubPath(mount, hostId, 'marketing')\n return hub ? `${hub}/${section}` : null\n}\n\n/** The anchor a site in the menu renders as — a real link, like every other. */\nconst MenuItemLink = forwardRef<any, AppLinkNakedLinkProps>((props, ref) => (\n <AppLink ref={ref} {...props} componentVariant={'naked'} />\n))\nMenuItemLink.displayName = 'OrgSitePickerMenuItemLink'\n\nexport interface OrgSitePickerButtonProps {\n mount: MarketingOrgMount\n /** The section of the site's Marketing hub to open — `overlays`, `experiments`. */\n section: string\n label: string\n}\n\n/**\n * The org hub's way into anything that is EDITED on a site: a button that\n * opens one of the organization's sites at a section of its own Marketing\n * hub.\n *\n * An overlay is drawn on one site's pages and an A/B test splits one site's\n * traffic, so their editors belong to the site — the org hub lists them and\n * sends the reader to the site to write. With one site there is nothing to\n * choose and the button is the link; with several it opens a menu of them.\n * A site the shell could not give a subdomain is listed and disabled: it has\n * no hub address to open.\n */\nexport function OrgSitePickerButton(props: OrgSitePickerButtonProps) {\n const { mount, section, label } = props\n const [anchorEl, setAnchorEl] = useState<HTMLElement | null>(null)\n const sites = mount.hosts\n\n if (sites.length === 1) {\n const href = orgSiteSectionHref(mount, sites[0].id, section)\n return href ? (\n <AppLink\n componentVariant=\"button\"\n href={href}\n size=\"small\"\n color=\"primary\"\n variant=\"contained\"\n >\n {label}\n </AppLink>\n ) : (\n <Button size=\"small\" color=\"primary\" variant=\"contained\" disabled>\n {label}\n </Button>\n )\n }\n\n return (\n <>\n <Button\n size=\"small\"\n color=\"primary\"\n variant=\"contained\"\n disabled={!sites.length}\n aria-haspopup=\"true\"\n onClick={(event) => setAnchorEl(event.currentTarget)}\n >\n {label}\n </Button>\n <Menu\n anchorEl={anchorEl}\n open={Boolean(anchorEl)}\n onClose={() => setAnchorEl(null)}\n slotProps={{ list: { 'aria-label': `${label} on which site` } }}\n >\n {sites.map((site) => {\n const href = orgSiteSectionHref(mount, site.id, section)\n return (\n <MenuItem\n key={site.id}\n {...((href\n ? { component: MenuItemLink, href }\n : { disabled: true }) as any)}\n onClick={() => setAnchorEl(null)}\n >\n {site.name || site.subdomain || site.id}\n </MenuItem>\n )\n })}\n </Menu>\n </>\n )\n}\nOrgSitePickerButton.displayName = 'OrgSitePickerButton'\n\nexport default OrgSitePickerButton\n"],"names":["AppLink","Button","Menu","MenuItem","forwardRef","useState","orgSiteHubPath","orgSiteSectionHref","mount","hostId","section","hub","MenuItemLink","props","ref","componentVariant","displayName","OrgSitePickerButton","label","anchorEl","setAnchorEl","sites","hosts","length","href","id","size","color","variant","disabled","aria-haspopup","onClick","event","currentTarget","open","Boolean","onClose","slotProps","list","map","site","component","name","subdomain"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,OAAO,QAAoC,uBAAsB;AAC1E,SAASC,MAAM,EAAEC,IAAI,EAAEC,QAAQ,QAAQ,gBAAe;AACtD,SAASC,UAAU,EAAEC,QAAQ,QAAQ,QAAO;AAC5C,SAASC,cAAc,QAAgC,2BAAuB;AAE9E,+EAA+E,GAC/E,OAAO,SAASC,mBACdC,KAAwB,EACxBC,MAAc,EACdC,OAAe;IAEf,MAAMC,MAAML,eAAeE,OAAOC,QAAQ;IAC1C,OAAOE,MAAM,GAAGA,IAAI,CAAC,EAAED,SAAS,GAAG;AACrC;AAEA,8EAA8E,GAC9E,MAAME,6BAAeR,WAAuC,CAACS,OAAOC,oBAClE,KAACd;QAAQc,KAAKA;OAASD;QAAOE,kBAAkB;;AAElDH,aAAaI,WAAW,GAAG;AAS3B;;;;;;;;;;;CAWC,GACD,OAAO,SAASC,oBAAoBJ,KAA+B;IACjE,MAAM,EAAEL,KAAK,EAAEE,OAAO,EAAEQ,KAAK,EAAE,GAAGL;IAClC,MAAM,CAACM,UAAUC,YAAY,GAAGf,SAA6B;IAC7D,MAAMgB,QAAQb,MAAMc,KAAK;IAEzB,IAAID,MAAME,MAAM,KAAK,GAAG;QACtB,MAAMC,OAAOjB,mBAAmBC,OAAOa,KAAK,CAAC,EAAE,CAACI,EAAE,EAAEf;QACpD,OAAOc,qBACL,KAACxB;YACCe,kBAAiB;YACjBS,MAAMA;YACNE,MAAK;YACLC,OAAM;YACNC,SAAQ;sBAEPV;2BAGH,KAACjB;YAAOyB,MAAK;YAAQC,OAAM;YAAUC,SAAQ;YAAYC,QAAQ;sBAC9DX;;IAGP;IAEA,qBACE;;0BACE,KAACjB;gBACCyB,MAAK;gBACLC,OAAM;gBACNC,SAAQ;gBACRC,UAAU,CAACR,MAAME,MAAM;gBACvBO,iBAAc;gBACdC,SAAS,CAACC,QAAUZ,YAAYY,MAAMC,aAAa;0BAElDf;;0BAEH,KAAChB;gBACCiB,UAAUA;gBACVe,MAAMC,QAAQhB;gBACdiB,SAAS,IAAMhB,YAAY;gBAC3BiB,WAAW;oBAAEC,MAAM;wBAAE,cAAc,GAAGpB,MAAM,cAAc,CAAC;oBAAC;gBAAE;0BAE7DG,MAAMkB,GAAG,CAAC,CAACC;oBACV,MAAMhB,OAAOjB,mBAAmBC,OAAOgC,KAAKf,EAAE,EAAEf;oBAChD,qBACE,KAACP,uBAEOqB,OACF;wBAAEiB,WAAW7B;wBAAcY;oBAAK,IAChC;wBAAEK,UAAU;oBAAK;wBACrBE,SAAS,IAAMX,YAAY;kCAE1BoB,KAAKE,IAAI,IAAIF,KAAKG,SAAS,IAAIH,KAAKf,EAAE;wBANlCe,KAAKf,EAAE;gBASlB;;;;AAIR;AACAR,oBAAoBD,WAAW,GAAG;AAElC,eAAeC,oBAAmB"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
import type { HostOverlay } from '../model';
|
|
18
|
+
/**
|
|
19
|
+
* How an overlay row reads, in the site's own list and in the
|
|
20
|
+
* organization's list of every site's overlays — one spelling for both, so
|
|
21
|
+
* the same overlay does not read two ways one click apart.
|
|
22
|
+
*/
|
|
23
|
+
/**
|
|
24
|
+
* The overlay's name, or the opening of its copy when nobody named it — the
|
|
25
|
+
* editor stores an empty name as `null` — or its id when it has neither.
|
|
26
|
+
*/
|
|
27
|
+
export declare function overlayDisplayName(overlay: HostOverlay & {
|
|
28
|
+
$id?: string;
|
|
29
|
+
}): string;
|
|
30
|
+
/** The showing window, open ends as `…`, or `Always` with neither end set. */
|
|
31
|
+
export declare function overlayWindowLabel(overlay: Pick<HostOverlay, 'startAtMs' | 'endAtMs'>): string;
|
|
32
|
+
/** The page patterns it shows on, or `All pages` with none. */
|
|
33
|
+
export declare function overlayPagesLabel(overlay: Pick<HostOverlay, 'pathPatterns'>): string;
|
|
34
|
+
/**
|
|
35
|
+
* Lifetime engagement (AGL-271) — the counters the beacon increments — or a
|
|
36
|
+
* dash for an overlay nobody has seen yet.
|
|
37
|
+
*/
|
|
38
|
+
export declare function overlayEngagementLabel(stats: HostOverlay['stats'] | undefined): string;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ /**
|
|
17
|
+
* How an overlay row reads, in the site's own list and in the
|
|
18
|
+
* organization's list of every site's overlays — one spelling for both, so
|
|
19
|
+
* the same overlay does not read two ways one click apart.
|
|
20
|
+
*/ /**
|
|
21
|
+
* The overlay's name, or the opening of its copy when nobody named it — the
|
|
22
|
+
* editor stores an empty name as `null` — or its id when it has neither.
|
|
23
|
+
*/ export function overlayDisplayName(overlay) {
|
|
24
|
+
var _overlay_bar, _overlay_popup;
|
|
25
|
+
if (overlay.name) return overlay.name;
|
|
26
|
+
const copy = overlay.kind === 'bar' ? (_overlay_bar = overlay.bar) == null ? void 0 : _overlay_bar.text : (_overlay_popup = overlay.popup) == null ? void 0 : _overlay_popup.headline;
|
|
27
|
+
return (copy != null ? copy : '').slice(0, 32) || overlay.$id || '';
|
|
28
|
+
}
|
|
29
|
+
/** The showing window, open ends as `…`, or `Always` with neither end set. */ export function overlayWindowLabel(overlay) {
|
|
30
|
+
if (!overlay.startAtMs && !overlay.endAtMs) return 'Always';
|
|
31
|
+
const day = (ms)=>ms ? new Date(ms).toLocaleDateString() : '…';
|
|
32
|
+
return `${day(overlay.startAtMs)} → ${day(overlay.endAtMs)}`;
|
|
33
|
+
}
|
|
34
|
+
/** The page patterns it shows on, or `All pages` with none. */ export function overlayPagesLabel(overlay) {
|
|
35
|
+
var _overlay_pathPatterns;
|
|
36
|
+
return ((_overlay_pathPatterns = overlay.pathPatterns) == null ? void 0 : _overlay_pathPatterns.length) ? overlay.pathPatterns.join(', ') : 'All pages';
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Lifetime engagement (AGL-271) — the counters the beacon increments — or a
|
|
40
|
+
* dash for an overlay nobody has seen yet.
|
|
41
|
+
*/ export function overlayEngagementLabel(stats) {
|
|
42
|
+
var _ref, _ref1, _ref2;
|
|
43
|
+
const impressions = (_ref = stats == null ? void 0 : stats.impressions) != null ? _ref : 0;
|
|
44
|
+
const clicks = (_ref1 = stats == null ? void 0 : stats.clicks) != null ? _ref1 : 0;
|
|
45
|
+
const dismissals = (_ref2 = stats == null ? void 0 : stats.dismissals) != null ? _ref2 : 0;
|
|
46
|
+
if (!impressions && !clicks && !dismissals) return '—';
|
|
47
|
+
const parts = [
|
|
48
|
+
`${impressions.toLocaleString()} views`
|
|
49
|
+
];
|
|
50
|
+
if (clicks) parts.push(`${clicks.toLocaleString()} clicks`);
|
|
51
|
+
if (dismissals) parts.push(`${dismissals.toLocaleString()} dismissed`);
|
|
52
|
+
return parts.join(' · ');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=overlay-labels.js.map
|