@aglyn/plugins-marketing 1.0.0-beta.143

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +7 -0
  3. package/package.json +56 -0
  4. package/src/index.d.ts +19 -0
  5. package/src/index.js +20 -0
  6. package/src/index.js.map +1 -0
  7. package/src/lib/components/announcement-bar-card.component.d.ts +20 -0
  8. package/src/lib/components/announcement-bar-card.component.js +292 -0
  9. package/src/lib/components/announcement-bar-card.component.js.map +1 -0
  10. package/src/lib/components/campaign-composer.d.ts +84 -0
  11. package/src/lib/components/campaign-composer.js +1643 -0
  12. package/src/lib/components/campaign-composer.js.map +1 -0
  13. package/src/lib/components/campaign-conversions-card.d.ts +22 -0
  14. package/src/lib/components/campaign-conversions-card.js +642 -0
  15. package/src/lib/components/campaign-conversions-card.js.map +1 -0
  16. package/src/lib/components/campaign-detail-card.d.ts +55 -0
  17. package/src/lib/components/campaign-detail-card.js +771 -0
  18. package/src/lib/components/campaign-detail-card.js.map +1 -0
  19. package/src/lib/components/campaign-edit-drawer.d.ts +60 -0
  20. package/src/lib/components/campaign-edit-drawer.js +252 -0
  21. package/src/lib/components/campaign-edit-drawer.js.map +1 -0
  22. package/src/lib/components/campaign-email-zones.d.ts +111 -0
  23. package/src/lib/components/campaign-email-zones.js +54 -0
  24. package/src/lib/components/campaign-email-zones.js.map +1 -0
  25. package/src/lib/components/campaign-glance-card.component.d.ts +18 -0
  26. package/src/lib/components/campaign-glance-card.component.js +151 -0
  27. package/src/lib/components/campaign-glance-card.component.js.map +1 -0
  28. package/src/lib/components/campaign-members-section.d.ts +87 -0
  29. package/src/lib/components/campaign-members-section.js +499 -0
  30. package/src/lib/components/campaign-members-section.js.map +1 -0
  31. package/src/lib/components/campaign-reach-sections.d.ts +137 -0
  32. package/src/lib/components/campaign-reach-sections.js +694 -0
  33. package/src/lib/components/campaign-reach-sections.js.map +1 -0
  34. package/src/lib/components/campaign-report-card.d.ts +91 -0
  35. package/src/lib/components/campaign-report-card.js +667 -0
  36. package/src/lib/components/campaign-report-card.js.map +1 -0
  37. package/src/lib/components/campaign-test-send-drawer.d.ts +45 -0
  38. package/src/lib/components/campaign-test-send-drawer.js +238 -0
  39. package/src/lib/components/campaign-test-send-drawer.js.map +1 -0
  40. package/src/lib/components/campaigns-card.d.ts +31 -0
  41. package/src/lib/components/campaigns-card.js +667 -0
  42. package/src/lib/components/campaigns-card.js.map +1 -0
  43. package/src/lib/components/conversion-attribution.component.d.ts +62 -0
  44. package/src/lib/components/conversion-attribution.component.js +127 -0
  45. package/src/lib/components/conversion-attribution.component.js.map +1 -0
  46. package/src/lib/components/email-compose-card.d.ts +12 -0
  47. package/src/lib/components/email-compose-card.js +157 -0
  48. package/src/lib/components/email-compose-card.js.map +1 -0
  49. package/src/lib/components/email-detail.d.ts +40 -0
  50. package/src/lib/components/email-detail.js +1180 -0
  51. package/src/lib/components/email-detail.js.map +1 -0
  52. package/src/lib/components/email-edit-drawer.d.ts +54 -0
  53. package/src/lib/components/email-edit-drawer.js +172 -0
  54. package/src/lib/components/email-edit-drawer.js.map +1 -0
  55. package/src/lib/components/email-messages-widget.d.ts +22 -0
  56. package/src/lib/components/email-messages-widget.js +49 -0
  57. package/src/lib/components/email-messages-widget.js.map +1 -0
  58. package/src/lib/components/email-recipients-card.d.ts +50 -0
  59. package/src/lib/components/email-recipients-card.js +338 -0
  60. package/src/lib/components/email-recipients-card.js.map +1 -0
  61. package/src/lib/components/emails-list-card.d.ts +36 -0
  62. package/src/lib/components/emails-list-card.js +544 -0
  63. package/src/lib/components/emails-list-card.js.map +1 -0
  64. package/src/lib/components/experiment-zones.d.ts +78 -0
  65. package/src/lib/components/experiment-zones.js +65 -0
  66. package/src/lib/components/experiment-zones.js.map +1 -0
  67. package/src/lib/components/host-experiments-card.component.d.ts +17 -0
  68. package/src/lib/components/host-experiments-card.component.js +887 -0
  69. package/src/lib/components/host-experiments-card.component.js.map +1 -0
  70. package/src/lib/components/host-marketing-summary-card.component.d.ts +15 -0
  71. package/src/lib/components/host-marketing-summary-card.component.js +161 -0
  72. package/src/lib/components/host-marketing-summary-card.component.js.map +1 -0
  73. package/src/lib/components/host-overlays-card.component.d.ts +19 -0
  74. package/src/lib/components/host-overlays-card.component.js +783 -0
  75. package/src/lib/components/host-overlays-card.component.js.map +1 -0
  76. package/src/lib/components/marketing-console-page.d.ts +28 -0
  77. package/src/lib/components/marketing-console-page.js +166 -0
  78. package/src/lib/components/marketing-console-page.js.map +1 -0
  79. package/src/lib/components/marketing-console-sections.d.ts +34 -0
  80. package/src/lib/components/marketing-console-sections.js +77 -0
  81. package/src/lib/components/marketing-console-sections.js.map +1 -0
  82. package/src/lib/components/overlay-stats-row.component.d.ts +15 -0
  83. package/src/lib/components/overlay-stats-row.component.js +78 -0
  84. package/src/lib/components/overlay-stats-row.component.js.map +1 -0
  85. package/src/lib/components/popup-card.component.d.ts +18 -0
  86. package/src/lib/components/popup-card.component.js +462 -0
  87. package/src/lib/components/popup-card.component.js.map +1 -0
  88. package/src/lib/components/record-attribution-widget.d.ts +35 -0
  89. package/src/lib/components/record-attribution-widget.js +33 -0
  90. package/src/lib/components/record-attribution-widget.js.map +1 -0
  91. package/src/lib/components/site-runtime.d.ts +9 -0
  92. package/src/lib/components/site-runtime.js +1081 -0
  93. package/src/lib/components/site-runtime.js.map +1 -0
  94. package/src/lib/components/use-campaign-send-api.d.ts +35 -0
  95. package/src/lib/components/use-campaign-send-api.js +99 -0
  96. package/src/lib/components/use-campaign-send-api.js.map +1 -0
  97. package/src/lib/components/use-campaign-topic-options.d.ts +20 -0
  98. package/src/lib/components/use-campaign-topic-options.js +44 -0
  99. package/src/lib/components/use-campaign-topic-options.js.map +1 -0
  100. package/src/lib/components/use-emails-hub-path.d.ts +39 -0
  101. package/src/lib/components/use-emails-hub-path.js +70 -0
  102. package/src/lib/components/use-emails-hub-path.js.map +1 -0
  103. package/src/lib/components/use-marketing-hub-path.d.ts +21 -0
  104. package/src/lib/components/use-marketing-hub-path.js +50 -0
  105. package/src/lib/components/use-marketing-hub-path.js.map +1 -0
  106. package/src/lib/components/use-overlay-copy-editor.d.ts +79 -0
  107. package/src/lib/components/use-overlay-copy-editor.js +110 -0
  108. package/src/lib/components/use-overlay-copy-editor.js.map +1 -0
  109. package/src/lib/constants/bundle-common.d.ts +8 -0
  110. package/src/lib/constants/bundle-common.js +9 -0
  111. package/src/lib/constants/bundle-common.js.map +1 -0
  112. package/src/lib/model/campaign-membership-figures.d.ts +109 -0
  113. package/src/lib/model/campaign-membership-figures.js +113 -0
  114. package/src/lib/model/campaign-membership-figures.js.map +1 -0
  115. package/src/lib/model/compile-client-automations.d.ts +65 -0
  116. package/src/lib/model/compile-client-automations.js +95 -0
  117. package/src/lib/model/compile-client-automations.js.map +1 -0
  118. package/src/lib/model/experiments.d.ts +130 -0
  119. package/src/lib/model/experiments.js +186 -0
  120. package/src/lib/model/experiments.js.map +1 -0
  121. package/src/lib/model/index.d.ts +26 -0
  122. package/src/lib/model/index.js +26 -0
  123. package/src/lib/model/index.js.map +1 -0
  124. package/src/lib/model/marketing-record-routes.d.ts +6 -0
  125. package/src/lib/model/marketing-record-routes.js +46 -0
  126. package/src/lib/model/marketing-record-routes.js.map +1 -0
  127. package/src/lib/model/overlay-copy.d.ts +62 -0
  128. package/src/lib/model/overlay-copy.js +78 -0
  129. package/src/lib/model/overlay-copy.js.map +1 -0
  130. package/src/lib/model/overlays.d.ts +113 -0
  131. package/src/lib/model/overlays.js +93 -0
  132. package/src/lib/model/overlays.js.map +1 -0
  133. package/src/lib/model/site-contract.d.ts +100 -0
  134. package/src/lib/model/site-contract.js +23 -0
  135. package/src/lib/model/site-contract.js.map +1 -0
  136. package/src/lib/plugin.d.ts +27 -0
  137. package/src/lib/plugin.js +168 -0
  138. package/src/lib/plugin.js.map +1 -0
  139. package/src/lib/preview-props.d.ts +31 -0
  140. package/src/lib/preview-props.js +54 -0
  141. package/src/lib/preview-props.js.map +1 -0
  142. package/src/lib/server/campaign-manage.d.ts +62 -0
  143. package/src/lib/server/campaign-manage.js +829 -0
  144. package/src/lib/server/campaign-manage.js.map +1 -0
  145. package/src/lib/server/campaign-process-scheduled.d.ts +26 -0
  146. package/src/lib/server/campaign-process-scheduled.js +198 -0
  147. package/src/lib/server/campaign-process-scheduled.js.map +1 -0
  148. package/src/lib/server/campaign-recipients.d.ts +55 -0
  149. package/src/lib/server/campaign-recipients.js +219 -0
  150. package/src/lib/server/campaign-recipients.js.map +1 -0
  151. package/src/lib/server/campaign-send.d.ts +487 -0
  152. package/src/lib/server/campaign-send.js +3157 -0
  153. package/src/lib/server/campaign-send.js.map +1 -0
  154. package/src/lib/server/email-campaign-reach.d.ts +174 -0
  155. package/src/lib/server/email-campaign-reach.js +254 -0
  156. package/src/lib/server/email-campaign-reach.js.map +1 -0
  157. package/src/lib/server/email-events.d.ts +27 -0
  158. package/src/lib/server/email-events.js +714 -0
  159. package/src/lib/server/email-events.js.map +1 -0
  160. package/src/lib/server/get-client-automations.d.ts +60 -0
  161. package/src/lib/server/get-client-automations.js +87 -0
  162. package/src/lib/server/get-client-automations.js.map +1 -0
  163. package/src/lib/server/get-overlays.d.ts +28 -0
  164. package/src/lib/server/get-overlays.js +65 -0
  165. package/src/lib/server/get-overlays.js.map +1 -0
  166. package/src/lib/server/get-screen-experiments.d.ts +42 -0
  167. package/src/lib/server/get-screen-experiments.js +82 -0
  168. package/src/lib/server/get-screen-experiments.js.map +1 -0
  169. package/src/lib/server/lists-materialize.d.ts +18 -0
  170. package/src/lib/server/lists-materialize.js +130 -0
  171. package/src/lib/server/lists-materialize.js.map +1 -0
  172. package/src/lib/server/marketing-figures.d.ts +37 -0
  173. package/src/lib/server/marketing-figures.js +248 -0
  174. package/src/lib/server/marketing-figures.js.map +1 -0
  175. package/src/lib/server/site-page-enricher.d.ts +26 -0
  176. package/src/lib/server/site-page-enricher.js +210 -0
  177. package/src/lib/server/site-page-enricher.js.map +1 -0
  178. package/src/lib/server.d.ts +20 -0
  179. package/src/lib/server.js +152 -0
  180. package/src/lib/server.js.map +1 -0
  181. package/src/lib/site.d.ts +22 -0
  182. package/src/lib/site.js +48 -0
  183. package/src/lib/site.js.map +1 -0
@@ -0,0 +1,667 @@
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 { PageHeaderRecord, pluginDocsHelp } from "@aglyn/aglyn";
20
+ import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
21
+ import { ScrollTable } from "@aglyn/shared-ui-jsx/components/scroll-table.component";
22
+ import { Alert, Box, Button, Chip, Divider, Stack, TableBody, TableCell, TableHead, TableRow, Typography } from "@mui/material";
23
+ import { doc } from "firebase/firestore";
24
+ import { useFirestore, useFirestoreDoc } from "@aglyn/tenant-feature-instance";
25
+ import { campaignLinkReport, campaignReport } from "@aglyn/shared-ui-email-campaigns/model/campaign-report";
26
+ import { campaignRevenueReport } from "@aglyn/shared-ui-email-campaigns/model/campaign-revenue";
27
+ import { campaignConversionsReport } from "@aglyn/shared-ui-email-campaigns/model/campaign-conversions";
28
+ /*
29
+ * The three renderers every email report shares. Imported rather than kept
30
+ * here, so "a rate prints its denominator" is one implementation and not a
31
+ * convention each new card has to remember.
32
+ */ import { Figure, MoneyFigure, MoneyPerMessageRow, percent, RateRow, Section } from "@aglyn/shared-ui-email-campaigns/components/report-figures";
33
+ import { emailSendTimeMs } from "@aglyn/shared-ui-email-campaigns/model/email-record";
34
+ import { campaignSendDisplay, campaignSendProgress } from "@aglyn/shared-ui-email-campaigns/model/campaign-container";
35
+ import { useEmailsHubPath } from "./use-emails-hub-path.js";
36
+ /**
37
+ * The help affordance, hoisted so BOTH headers carry it.
38
+ *
39
+ * The unreadable-campaign branch below renders its own `CardDisplay`, and a
40
+ * header without help is a header a reader has no way out of — which is
41
+ * exactly the state that branch describes.
42
+ */ const reportDocsHelp = pluginDocsHelp('emailCampaigns', {
43
+ anchor: '#the-campaign-report',
44
+ excerpt: 'What one campaign did: delivery, opens, clicks, bounces, complaints, ' + 'unsubscribes and the revenue it was credited with, each with the ' + 'population it is measured against.'
45
+ });
46
+ /**
47
+ * ONE CAMPAIGN, MEASURED — and every denominator on screen.
48
+ *
49
+ * ## What was missing
50
+ *
51
+ * Opens and clicks have been recorded on the campaign document since AGL-268
52
+ * and displayed as two words in a history row: `12 opens · 3 clicks`. Nothing
53
+ * anywhere showed a rate, a delivery count, a bounce, a complaint or an
54
+ * unsubscribe, and the populations the send itself measured — how large the
55
+ * audience was, how many the consent rule refused, how many were already
56
+ * suppressed — were computed on every send and written nowhere.
57
+ *
58
+ * ## The rule this screen is built around
59
+ *
60
+ * **Every rate names its denominator, in the same breath as the number.**
61
+ *
62
+ * That is not decoration. An open rate over `sent` and an open rate over
63
+ * `delivered` are different numbers that share a label, and the difference
64
+ * between them is exactly the mail that bounced — so a report that showed one
65
+ * while the reader assumed the other would be wrong by however bad the
66
+ * address list is, which is the case where being wrong matters most. Worse,
67
+ * "click rate" is used in the wild for two unrelated quantities — clicks over
68
+ * delivered, and clicks over openers — and they differ by a factor of three
69
+ * or four on ordinary mail.
70
+ *
71
+ * So the arithmetic is not done here at all. `campaign-report.ts` returns
72
+ * each rate as a triple carrying `denominatorLabel`, this file renders that
73
+ * label, and a rate whose denominator is zero or unrecorded comes back `null`
74
+ * and is drawn as "—" with a caveat naming the reason. There is no path
75
+ * through this component that prints a percentage without saying what it is a
76
+ * percentage of.
77
+ *
78
+ * ## What the page is about comes before how the mail did
79
+ *
80
+ * A campaign of one email is the ordinary shape, and this card is the page it
81
+ * gets: the URL names a send, so there is no container above it and no list of
82
+ * emails to draw. That collapse is honest, but it decided what the page was
83
+ * ABOUT — delivery, engagement and rates are mechanics of a message, and a
84
+ * page that led with them at length read as a mail report wearing the word
85
+ * campaign.
86
+ *
87
+ * So the outcomes lead: what it caused, what it earned, where it sent people.
88
+ * Two of those headings are the ones `campaign-reach-sections.tsx` puts at the
89
+ * top of a campaign holding SEVERAL emails, and they are the same words here
90
+ * deliberately — a merchant who opens a campaign of one and a campaign of six
91
+ * is asking the same question and should not have to learn two vocabularies to
92
+ * find the answer. The email's own figures follow, under a row that names the
93
+ * message they belong to.
94
+ *
95
+ * Nothing about the reads changed with the order. The conversions, revenue and
96
+ * destination figures were already on this page and already came from the four
97
+ * documents below; they were simply underneath the mail.
98
+ *
99
+ * ## Three documents, whatever the campaign's size
100
+ *
101
+ * The whole screen is one campaign document plus one link-rollup document
102
+ * plus one revenue-rollup document. All three are single-document listens, so
103
+ * the cost does not move with the audience — a report over a 50,000-recipient
104
+ * send reads the same three documents as one over ten. Aggregating the
105
+ * per-recipient delivery log on mount would have been the obvious
106
+ * implementation and would have read one document per recipient, every time
107
+ * anybody opened the page; joining orders to campaigns at read time would
108
+ * have been the same mistake one collection along, which is why the join is
109
+ * done once at the sale and stored.
110
+ *
111
+ * ## No per-recipient view here, deliberately
112
+ *
113
+ * Every number on this screen is an aggregate that carries no address, and
114
+ * that is what makes it three single-document reads. A per-recipient list is a
115
+ * different read and a different question, so it lives where it is asked:
116
+ * the design report's recipients table, which resolves the campaigns from the
117
+ * design on a server that has already checked the reader's site role, and
118
+ * returns only rows tagged with that site.
119
+ *
120
+ * The other per-recipient view — the staff delivery log on the user detail
121
+ * page — spans every site on the install and is behind a staff claim that
122
+ * records who looked. Neither reaches this card: adding a recipient list here
123
+ * would put an unbounded read behind a widget whose whole cost model is that
124
+ * it has none.
125
+ */ export function CampaignReportCard(props) {
126
+ var _ref;
127
+ const { hostId, campaignId, basePath } = props;
128
+ // The sibling hub: the message's own page belongs to the Emails console.
129
+ const emailsHub = useEmailsHubPath();
130
+ const firestore = useFirestore();
131
+ const { data: campaign } = useFirestoreDoc(()=>doc(firestore, 'hosts', hostId, 'campaigns', campaignId), [
132
+ firestore,
133
+ hostId,
134
+ campaignId
135
+ ]);
136
+ /*
137
+ * The link rollup, its own document rather than a field on the campaign.
138
+ *
139
+ * A map of destinations grows with the campaign's content and a campaign
140
+ * document is read by the history list, the glance widget and the send
141
+ * path; putting an unbounded map on it would make every one of those reads
142
+ * larger. Split, the rollup is read by exactly the screen that renders it.
143
+ */ const { data: links } = useFirestoreDoc(()=>doc(firestore, 'hosts', hostId, 'campaigns', campaignId, 'reports', 'links'), [
144
+ firestore,
145
+ hostId,
146
+ campaignId
147
+ ]);
148
+ /*
149
+ * The revenue rollup, a third single-document listen.
150
+ *
151
+ * Its own document beside the link rollup rather than a field on the
152
+ * campaign, for the reason the link rollup is: the campaign document is
153
+ * read by the history list, the glance widget and the send path, and a map
154
+ * that grows with the campaign's sales would enlarge every one of those
155
+ * reads. The cost model in this file's header is unchanged in the way that
156
+ * matters — the page still reads a fixed number of documents whatever the
157
+ * audience, and no per-order or per-recipient read exists anywhere on it.
158
+ */ const { data: revenue } = useFirestoreDoc(()=>doc(firestore, 'hosts', hostId, 'campaigns', campaignId, 'reports', 'revenue'), [
159
+ firestore,
160
+ hostId,
161
+ campaignId
162
+ ]);
163
+ /*
164
+ * The conversions rollup, a fourth single-document listen.
165
+ *
166
+ * Beside `reports/revenue` and read the same way, because it is the same
167
+ * join answering the other half of the question: revenue is what a campaign
168
+ * EARNED from people who named themselves at checkout, and this is what it
169
+ * CAUSED among people who were anonymous until the moment they submitted,
170
+ * signed up or booked. The cost model in this file's header is unchanged in
171
+ * the way that matters — a fixed number of documents whatever the audience,
172
+ * and no per-conversion read anywhere on the page.
173
+ */ const { data: conversions } = useFirestoreDoc(()=>doc(firestore, 'hosts', hostId, 'campaigns', campaignId, 'reports', 'conversions'), [
174
+ firestore,
175
+ hostId,
176
+ campaignId
177
+ ]);
178
+ const report = campaignReport(campaign == null ? void 0 : campaign.stats);
179
+ const linkReport = campaignLinkReport(links);
180
+ const revenueReport = campaignRevenueReport({
181
+ rollup: revenue,
182
+ /*
183
+ * The DENOMINATOR, handed over from the same document read at the same
184
+ * instant rather than re-read here. `report.delivered` is `null` when no
185
+ * delivery event was ever recorded, and it is passed as `null` so the
186
+ * revenue figures over it are withheld for exactly the reason every other
187
+ * rate over it is.
188
+ */ delivered: report.delivered,
189
+ midFlight: campaign ? campaignSendProgress(campaign).state === 'sending' : false
190
+ });
191
+ /*
192
+ * No denominator is handed in, unlike the revenue report. A conversion RATE
193
+ * over delivered messages would repeat the defect the revenue section
194
+ * refuses for orders — one visitor can submit two forms, so the quotient
195
+ * passes 100% without anything being wrong — and counting distinct people
196
+ * instead needs a document per person per campaign, which is the
197
+ * per-recipient read this screen's cost model exists to refuse.
198
+ */ const conversionsReport = campaignConversionsReport({
199
+ rollup: conversions
200
+ });
201
+ const subject = String((_ref = campaign == null ? void 0 : campaign.subject) != null ? _ref : 'Campaign');
202
+ const sendTimeMs = campaign ? emailSendTimeMs(campaign) : 0;
203
+ const backButton = /*#__PURE__*/ _jsx(Button, _extends({
204
+ component: AppLink
205
+ }, {
206
+ componentVariant: 'naked',
207
+ nativeButton: false
208
+ }, {
209
+ href: `${basePath}/campaigns`,
210
+ size: "small",
211
+ color: "primary",
212
+ children: 'All campaigns'
213
+ }));
214
+ if (!campaign) {
215
+ return /*#__PURE__*/ _jsx(CardDisplay, {
216
+ header: 'Campaign report',
217
+ help: reportDocsHelp,
218
+ contentGutterX: true,
219
+ contentGutterY: true,
220
+ HeaderProps: {
221
+ action: backButton
222
+ },
223
+ children: /*#__PURE__*/ _jsx(Typography, {
224
+ variant: "body2",
225
+ color: "text.secondary",
226
+ children: 'This campaign could not be loaded. It may have been deleted.'
227
+ })
228
+ });
229
+ }
230
+ /* The card, named so the page chrome above it is a plain list of
231
+ what this surface publishes upward. */ const card = /*#__PURE__*/ _jsx(CardDisplay, {
232
+ header: 'Campaign report',
233
+ help: reportDocsHelp,
234
+ contentGutterX: true,
235
+ contentGutterY: true,
236
+ HeaderProps: {
237
+ action: backButton
238
+ },
239
+ children: /*#__PURE__*/ _jsxs(Stack, {
240
+ spacing: 3,
241
+ children: [
242
+ report.caveats.map((caveat)=>/*#__PURE__*/ _jsx(Alert, {
243
+ severity: "info",
244
+ children: caveat.message
245
+ }, caveat.id)),
246
+ /*#__PURE__*/ _jsxs(Section, {
247
+ title: "What it caused",
248
+ children: [
249
+ conversionsReport.caveats.map((caveat)=>/*#__PURE__*/ _jsx(Alert, {
250
+ severity: "info",
251
+ children: caveat.message
252
+ }, caveat.id)),
253
+ conversionsReport.any ? /*#__PURE__*/ _jsxs(Stack, {
254
+ spacing: 1,
255
+ children: [
256
+ /*#__PURE__*/ _jsx(Stack, {
257
+ direction: "row",
258
+ spacing: 4,
259
+ useFlexGap: true,
260
+ sx: {
261
+ flexWrap: 'wrap'
262
+ },
263
+ children: conversionsReport.kinds.map((entry)=>/*#__PURE__*/ _jsx(Figure, {
264
+ label: entry.label,
265
+ value: entry.value,
266
+ note: entry.note
267
+ }, entry.kind))
268
+ }),
269
+ /*#__PURE__*/ _jsx(Typography, {
270
+ variant: "caption",
271
+ color: "text.secondary",
272
+ children: `Credited ${conversionsReport.model === 'last-click' ? 'to the last campaign whose link the visitor clicked' : `under the ${conversionsReport.model} model`}, ` + `within ${conversionsReport.windowDays} days of that click. ` + 'Somebody who converted without ever following a campaign ' + 'link is credited to no campaign at all — nothing is ' + 'inferred from a referrer — so these are a floor rather ' + 'than everything this campaign influenced. The ones ' + 'credited to nobody are counted under Conversions in the ' + 'marketing console.'
273
+ }),
274
+ /*#__PURE__*/ _jsx(Box, {
275
+ children: /*#__PURE__*/ _jsx(Button, _extends({
276
+ component: AppLink
277
+ }, {
278
+ componentVariant: 'naked',
279
+ nativeButton: false
280
+ }, {
281
+ href: `${basePath}/conversions/${campaignId}`,
282
+ size: "small",
283
+ color: "primary",
284
+ children: 'See these conversions'
285
+ }))
286
+ })
287
+ ]
288
+ }) : /*#__PURE__*/ _jsx(Typography, {
289
+ variant: "body2",
290
+ color: "text.secondary",
291
+ children: conversionsReport.recorded ? 'Nothing has been credited to this campaign yet.' : 'No conversions have been attributed to this campaign. A ' + 'form submission, lead, contact or booking is credited to ' + 'the last campaign whose link the visitor clicked, within ' + `${conversionsReport.windowDays} days — a campaign sent ` + 'before that was recorded will never show a figure here.'
292
+ })
293
+ ]
294
+ }),
295
+ /*#__PURE__*/ _jsx(Divider, {}),
296
+ /*#__PURE__*/ _jsxs(Section, {
297
+ title: "Revenue",
298
+ children: [
299
+ revenueReport.caveats.map((caveat)=>/*#__PURE__*/ _jsx(Alert, {
300
+ severity: "info",
301
+ children: caveat.message
302
+ }, caveat.id)),
303
+ revenueReport.currencies.length ? /*#__PURE__*/ _jsxs(Stack, {
304
+ spacing: 3,
305
+ children: [
306
+ revenueReport.currencies.map((entry)=>/*#__PURE__*/ _jsxs(Stack, {
307
+ spacing: 1,
308
+ children: [
309
+ revenueReport.multiCurrency ? /*#__PURE__*/ _jsx(Typography, {
310
+ variant: "subtitle2",
311
+ children: entry.currency.toUpperCase()
312
+ }) : null,
313
+ /*#__PURE__*/ _jsxs(Stack, {
314
+ direction: "row",
315
+ spacing: 4,
316
+ useFlexGap: true,
317
+ sx: {
318
+ flexWrap: 'wrap'
319
+ },
320
+ children: [
321
+ /*#__PURE__*/ _jsx(MoneyFigure, {
322
+ label: "Net revenue",
323
+ cents: entry.netCents,
324
+ currency: entry.currency,
325
+ note: "after refunds"
326
+ }),
327
+ /*#__PURE__*/ _jsx(MoneyFigure, {
328
+ label: "Gross revenue",
329
+ cents: entry.grossCents,
330
+ currency: entry.currency,
331
+ note: "as charged"
332
+ }),
333
+ /*#__PURE__*/ _jsx(MoneyFigure, {
334
+ label: "Refunded",
335
+ cents: entry.refundedCents,
336
+ currency: entry.currency,
337
+ note: "handed back"
338
+ }),
339
+ /*#__PURE__*/ _jsx(Figure, {
340
+ label: "Orders",
341
+ value: entry.orders,
342
+ note: "credited to this campaign"
343
+ }),
344
+ /*#__PURE__*/ _jsx(Figure, {
345
+ label: "Fully refunded",
346
+ value: entry.refundedOrders,
347
+ note: "of those orders"
348
+ })
349
+ ]
350
+ }),
351
+ /*#__PURE__*/ _jsx(MoneyPerMessageRow, {
352
+ label: "Net revenue per delivered message",
353
+ figure: entry.netPerDelivered
354
+ })
355
+ ]
356
+ }, entry.currency)),
357
+ /*#__PURE__*/ _jsx(Typography, {
358
+ variant: "caption",
359
+ color: "text.secondary",
360
+ children: `Credited ${revenueReport.model === 'last-click' ? 'to the last campaign whose link the buyer clicked' : `under the ${revenueReport.model} model`}, ` + `within ${revenueReport.windowDays} days of that click. ` + 'Clicks only — an open is not treated as evidence that ' + 'anybody read the email. An order placed by somebody who ' + 'never clicked, or who checked out without giving an ' + 'address, is credited to no campaign, so this is a floor ' + 'rather than every sale this campaign influenced.'
361
+ })
362
+ ]
363
+ }) : /*#__PURE__*/ _jsx(Typography, {
364
+ variant: "body2",
365
+ color: "text.secondary",
366
+ children: revenueReport.recorded ? 'No orders have been credited to this campaign.' : 'No revenue has been attributed to this campaign. Orders ' + 'are credited to the last campaign whose link the buyer ' + `clicked, within ${revenueReport.windowDays} days — a ` + 'campaign sent before that was recorded, or one on a site ' + 'with no store, will never show a figure here.'
367
+ })
368
+ ]
369
+ }),
370
+ /*#__PURE__*/ _jsx(Divider, {}),
371
+ /*#__PURE__*/ _jsxs(Section, {
372
+ title: "Where it sent people",
373
+ children: [
374
+ /*#__PURE__*/ _jsx(Typography, {
375
+ variant: "body2",
376
+ color: "text.secondary",
377
+ children: 'The pages this campaign’s links were followed to.'
378
+ }),
379
+ linkReport.rows.length ? /*#__PURE__*/ _jsxs(_Fragment, {
380
+ children: [
381
+ /*#__PURE__*/ _jsxs(ScrollTable, {
382
+ size: "small",
383
+ children: [
384
+ /*#__PURE__*/ _jsx(TableHead, {
385
+ children: /*#__PURE__*/ _jsxs(TableRow, {
386
+ children: [
387
+ /*#__PURE__*/ _jsx(TableCell, {
388
+ children: 'Destination'
389
+ }),
390
+ /*#__PURE__*/ _jsx(TableCell, {
391
+ align: "right",
392
+ children: 'Clicks'
393
+ }),
394
+ /*#__PURE__*/ _jsx(TableCell, {
395
+ align: "right",
396
+ children: 'Share'
397
+ })
398
+ ]
399
+ })
400
+ }),
401
+ /*#__PURE__*/ _jsx(TableBody, {
402
+ children: linkReport.rows.map((row)=>/*#__PURE__*/ _jsxs(TableRow, {
403
+ children: [
404
+ /*#__PURE__*/ _jsx(TableCell, {
405
+ sx: {
406
+ wordBreak: 'break-all'
407
+ },
408
+ children: row.url
409
+ }),
410
+ /*#__PURE__*/ _jsx(TableCell, {
411
+ align: "right",
412
+ sx: {
413
+ fontWeight: 'bold'
414
+ },
415
+ children: row.clicks.toLocaleString()
416
+ }),
417
+ /*#__PURE__*/ _jsx(TableCell, {
418
+ align: "right",
419
+ children: row.share ? `${percent(row.share.value)} of ${row.share.denominator.toLocaleString()} ${row.share.denominatorLabel}` : '—'
420
+ })
421
+ ]
422
+ }, row.url))
423
+ })
424
+ ]
425
+ }),
426
+ /*#__PURE__*/ _jsx(Typography, {
427
+ variant: "caption",
428
+ color: "text.secondary",
429
+ children: 'Counted by address and path — query strings are dropped, so ' + 'two links to the same page with different tracking ' + 'parameters count as one row.'
430
+ }),
431
+ linkReport.unattributedClicks ? /*#__PURE__*/ _jsx(Typography, {
432
+ variant: "caption",
433
+ color: "text.secondary",
434
+ children: `${linkReport.unattributedClicks.toLocaleString()} clicks ` + 'arrived without a destination and are not in this table.'
435
+ }) : null,
436
+ linkReport.overflowClicks ? /*#__PURE__*/ _jsx(Alert, {
437
+ severity: "info",
438
+ children: `This campaign has more distinct destinations than the ` + `rollup keeps. ${linkReport.overflowClicks.toLocaleString()} ` + 'clicks landed on links past that limit and are counted ' + 'in the click total above but not in this table.'
439
+ }) : null
440
+ ]
441
+ }) : /*#__PURE__*/ _jsx(Typography, {
442
+ variant: "body2",
443
+ color: "text.secondary",
444
+ children: report.clicks ? 'Clicks were recorded for this campaign, but none of them ' + 'carried a destination, so there is nothing to break down ' + 'by link.' : 'No link clicks have been recorded for this campaign.'
445
+ })
446
+ ]
447
+ }),
448
+ /*#__PURE__*/ _jsx(Divider, {}),
449
+ /*#__PURE__*/ _jsx(Section, {
450
+ title: "The email in this campaign",
451
+ children: /*#__PURE__*/ _jsxs(Stack, {
452
+ spacing: 1,
453
+ children: [
454
+ /*#__PURE__*/ _jsxs(Stack, {
455
+ direction: "row",
456
+ spacing: 1,
457
+ useFlexGap: true,
458
+ sx: {
459
+ alignItems: 'center',
460
+ flexWrap: 'wrap'
461
+ },
462
+ children: [
463
+ emailsHub ? /*#__PURE__*/ _jsx(AppLink, {
464
+ href: `${emailsHub}/messages/${campaignId}`,
465
+ children: subject
466
+ }) : /*#__PURE__*/ _jsx(Typography, {
467
+ variant: "body2",
468
+ children: subject
469
+ }),
470
+ /*#__PURE__*/ _jsx(Chip, {
471
+ size: "small",
472
+ label: campaignSendDisplay(campaign).label
473
+ }),
474
+ /*#__PURE__*/ _jsx(Chip, {
475
+ size: "small",
476
+ variant: "outlined",
477
+ label: "Single send",
478
+ title: 'One email, sent on its own rather than as part of a ' + 'campaign of several. Its report and its unsubscribe links ' + 'are unchanged.'
479
+ }),
480
+ /*#__PURE__*/ _jsx(Typography, {
481
+ variant: "caption",
482
+ color: "text.secondary",
483
+ children: sendTimeMs ? new Date(sendTimeMs).toLocaleString() : 'not recorded'
484
+ })
485
+ ]
486
+ }),
487
+ /*#__PURE__*/ _jsx(Typography, {
488
+ variant: "caption",
489
+ color: "text.secondary",
490
+ children: 'This campaign is one email, so the sections above are what ' + 'that one email caused. What follows is the mail itself — ' + 'how it was delivered and how it was read. Open the message ' + 'for its body and the people it reached.'
491
+ })
492
+ ]
493
+ })
494
+ }),
495
+ /*#__PURE__*/ _jsx(Divider, {}),
496
+ /*#__PURE__*/ _jsx(Section, {
497
+ title: "Delivery",
498
+ children: /*#__PURE__*/ _jsxs(Stack, {
499
+ direction: "row",
500
+ spacing: 4,
501
+ useFlexGap: true,
502
+ sx: {
503
+ flexWrap: 'wrap'
504
+ },
505
+ children: [
506
+ /*#__PURE__*/ _jsx(Figure, {
507
+ label: "Addressed",
508
+ value: report.recipients,
509
+ note: "after the per-send cap"
510
+ }),
511
+ /*#__PURE__*/ _jsx(Figure, {
512
+ label: "Sent",
513
+ value: report.sent,
514
+ note: "accepted by the provider"
515
+ }),
516
+ /*#__PURE__*/ _jsx(Figure, {
517
+ label: "Delivered",
518
+ value: report.delivered,
519
+ note: "accepted by the receiving server"
520
+ }),
521
+ /*#__PURE__*/ _jsx(Figure, {
522
+ label: "Bounced",
523
+ value: report.bounced,
524
+ note: "of sent"
525
+ }),
526
+ /*#__PURE__*/ _jsx(Figure, {
527
+ label: "Marked as spam",
528
+ value: report.complained,
529
+ note: "of delivered"
530
+ })
531
+ ]
532
+ })
533
+ }),
534
+ /*#__PURE__*/ _jsx(Divider, {}),
535
+ /*#__PURE__*/ _jsx(Section, {
536
+ title: "Engagement",
537
+ children: /*#__PURE__*/ _jsxs(Stack, {
538
+ direction: "row",
539
+ spacing: 4,
540
+ useFlexGap: true,
541
+ sx: {
542
+ flexWrap: 'wrap'
543
+ },
544
+ children: [
545
+ /*#__PURE__*/ _jsx(Figure, {
546
+ label: "Opens",
547
+ value: report.opens,
548
+ note: "every open, repeats included"
549
+ }),
550
+ /*#__PURE__*/ _jsx(Figure, {
551
+ label: "Readers who opened",
552
+ value: report.uniqueOpens,
553
+ note: "distinct recipients"
554
+ }),
555
+ /*#__PURE__*/ _jsx(Figure, {
556
+ label: "Clicks",
557
+ value: report.clicks,
558
+ note: "every click, repeats included"
559
+ }),
560
+ /*#__PURE__*/ _jsx(Figure, {
561
+ label: "Readers who clicked",
562
+ value: report.uniqueClicks,
563
+ note: "distinct recipients"
564
+ }),
565
+ /*#__PURE__*/ _jsx(Figure, {
566
+ label: "Unsubscribed",
567
+ value: report.unsubscribes,
568
+ note: "through this campaign's link"
569
+ })
570
+ ]
571
+ })
572
+ }),
573
+ /*#__PURE__*/ _jsx(Divider, {}),
574
+ /*#__PURE__*/ _jsx(Section, {
575
+ title: "Rates",
576
+ children: /*#__PURE__*/ _jsxs(Stack, {
577
+ spacing: 1,
578
+ children: [
579
+ /*#__PURE__*/ _jsx(RateRow, {
580
+ label: "Delivery rate",
581
+ rate: report.rates.delivery
582
+ }),
583
+ /*#__PURE__*/ _jsx(RateRow, {
584
+ label: "Open rate",
585
+ rate: report.rates.open
586
+ }),
587
+ /*#__PURE__*/ _jsx(RateRow, {
588
+ label: "Click rate",
589
+ rate: report.rates.click
590
+ }),
591
+ /*#__PURE__*/ _jsx(RateRow, {
592
+ label: "Click-to-open rate",
593
+ rate: report.rates.clickToOpen
594
+ }),
595
+ /*#__PURE__*/ _jsx(RateRow, {
596
+ label: "Bounce rate",
597
+ rate: report.rates.bounce
598
+ }),
599
+ /*#__PURE__*/ _jsx(RateRow, {
600
+ label: "Complaint rate",
601
+ rate: report.rates.complaint
602
+ }),
603
+ /*#__PURE__*/ _jsx(RateRow, {
604
+ label: "Unsubscribe rate",
605
+ rate: report.rates.unsubscribe
606
+ })
607
+ ]
608
+ })
609
+ }),
610
+ report.populations.length ? /*#__PURE__*/ _jsxs(_Fragment, {
611
+ children: [
612
+ /*#__PURE__*/ _jsx(Divider, {}),
613
+ /*#__PURE__*/ _jsxs(Section, {
614
+ title: "Who this went to",
615
+ children: [
616
+ /*#__PURE__*/ _jsx(Typography, {
617
+ variant: "body2",
618
+ color: "text.secondary",
619
+ children: 'Measured when this campaign was sent, and stored as it was ' + 'then. These figures describe the send, not the audience ' + 'as it stands today.'
620
+ }),
621
+ /*#__PURE__*/ _jsx(ScrollTable, {
622
+ size: "small",
623
+ children: /*#__PURE__*/ _jsx(TableBody, {
624
+ children: report.populations.map((population)=>/*#__PURE__*/ _jsxs(TableRow, {
625
+ children: [
626
+ /*#__PURE__*/ _jsx(TableCell, {
627
+ children: population.label
628
+ }),
629
+ /*#__PURE__*/ _jsx(TableCell, {
630
+ align: "right",
631
+ sx: {
632
+ fontWeight: 'bold'
633
+ },
634
+ children: population.count.toLocaleString()
635
+ }),
636
+ /*#__PURE__*/ _jsx(TableCell, {
637
+ align: "right",
638
+ children: /*#__PURE__*/ _jsx(Typography, {
639
+ variant: "caption",
640
+ color: "text.secondary",
641
+ children: `of ${population.of.toLocaleString()} ${population.ofLabel}`
642
+ })
643
+ })
644
+ ]
645
+ }, population.id))
646
+ })
647
+ })
648
+ ]
649
+ })
650
+ ]
651
+ }) : null
652
+ ]
653
+ })
654
+ });
655
+ return /*#__PURE__*/ _jsxs(_Fragment, {
656
+ children: [
657
+ /*#__PURE__*/ _jsx(PageHeaderRecord, {
658
+ title: campaign ? String(campaign.subject || '') || campaignId : undefined
659
+ }),
660
+ card
661
+ ]
662
+ });
663
+ }
664
+ CampaignReportCard.displayName = 'CampaignReportCard';
665
+ export default CampaignReportCard;
666
+
667
+ //# sourceMappingURL=campaign-report-card.js.map