@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 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/campaign-email-zones.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 { useConsoleWidgetSlot } from '@aglyn/aglyn/app-utils/console-widget-slot-context'\nimport { definePluginZone } from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport type { SendingIdentityView } from '@aglyn/tenant-feature-instance/hooks/use-sending-identity-api'\nimport { Suspense, type ReactElement } from 'react'\n\n/**\n * WHAT A CAMPAIGN EMAIL ASKS OF WHICHEVER PLUGIN KEEPS THE MAIL ITSELF.\n *\n * A campaign is this plugin's: who it goes to, when, under which container,\n * and what came of it. The mail it rides on is not. The catalog of streams a\n * recipient can leave, the identities a site may send as, the design document\n * a message is built from and the renderer that turns one into what an inbox\n * receives all belong to the plugin that keeps email, and a composer that\n * imported them would make this package uninstallable without that one.\n *\n * So each is a zone this plugin hosts. It hands over what it holds — the\n * site, the value, a callback — and draws whatever a widget there draws. A\n * widget never writes a campaign: it reports a choice through the callback\n * and the composer does the write, because the record is this plugin's.\n *\n * Outside a console shell, and in a workspace with no plugin filling a zone,\n * each of these draws nothing.\n */\n\n/** The stream this email belongs to, chosen before it is sent. */\nexport interface CampaignTopicSelectZoneProps {\n hostId: string\n /** The chosen topic id; empty until the widget settles it on a real one. */\n value: string\n onChange: (topicId: string) => void\n disabled?: boolean\n}\n\nexport const CAMPAIGN_TOPIC_SELECT_ZONE =\n definePluginZone<CampaignTopicSelectZoneProps>('campaignTopicSelect')\n\n/** One stream a campaign can open on. */\nexport interface CampaignTopicOption {\n id: string\n name: string\n}\n\n/**\n * The streams a campaign can open on, REPORTED rather than drawn.\n *\n * The two drawers that collect a campaign's topic are generic forms whose\n * select takes a list of options, so what they need from the catalog's owner\n * is the list. A widget here draws nothing: it reads the catalog while\n * `enabled` and answers through `onTopics`. `enabled` is the read gate —\n * the catalog is the section's largest read and only an open drawer needs it.\n */\nexport interface CampaignTopicOptionsZoneProps {\n hostId: string\n enabled: boolean\n onTopics: (topics: readonly CampaignTopicOption[]) => void\n}\n\nexport const CAMPAIGN_TOPIC_OPTIONS_ZONE =\n definePluginZone<CampaignTopicOptionsZoneProps>('campaignTopicOptions')\n\n/**\n * Adding a sender without leaving the email being written. The widget is the\n * editor, opened in its ADD mode; `onSaved` names the sender it created so\n * the composer can select it once its own read shows the row.\n */\nexport interface CampaignSenderEditorZoneProps {\n hostId: string\n view: SendingIdentityView | null\n onClose: () => void\n onSaved: (senderId?: string) => void\n}\n\nexport const CAMPAIGN_SENDER_EDITOR_ZONE =\n definePluginZone<CampaignSenderEditorZoneProps>('campaignSenderEditor')\n\n/**\n * A design document of this email's own. The widget is the control that mints\n * one; `onCreated` names it, and the composer records the choice on the\n * campaign and opens the editor.\n */\nexport interface CampaignDesignCreateZoneProps {\n hostId: string\n /** What to call the design, so the picker does not list \"Untitled email\". */\n name?: string\n onCreated: (design: { screenId: string; versionId: string }) => void\n onError: (error: unknown) => void\n}\n\nexport const CAMPAIGN_DESIGN_CREATE_ZONE =\n definePluginZone<CampaignDesignCreateZoneProps>('campaignDesignCreate')\n\n/** The message as an inbox receives it, on the message's own page. */\nexport interface CampaignDesignPreviewZoneProps {\n hostId: string\n /** The version document's raw `nodes` field, in any of its stored forms. */\n nodes: unknown\n /** The plain-text body, for a message composed without a design. */\n text?: string\n loading?: boolean\n subject?: string\n preheader?: string\n emptyMessage: string\n note?: string\n}\n\nexport const CAMPAIGN_DESIGN_PREVIEW_ZONE =\n definePluginZone<CampaignDesignPreviewZoneProps>('campaignDesignPreview')\n\n/**\n * Draws one of the zones above where the shell provides a renderer.\n *\n * Under its own \\`Suspense\\`, with nothing as the fallback. A widget is a lazy\n * chunk, and several of these mount in answer to a click — the sender editor,\n * the topics behind an opening drawer. Without a boundary here the suspension\n * would climb to the page's, and the form being filled in would be swapped\n * for a loading state while one drawer's code arrives.\n */\nfunction zoneHost<Props extends object>(zone: { id: string }, name: string) {\n function Host(props: Props): ReactElement | null {\n const Zone = useConsoleWidgetSlot()\n return Zone ? (\n <Suspense fallback={null}>\n <Zone slot={zone.id} {...props} />\n </Suspense>\n ) : null\n }\n Host.displayName = name\n return Host\n}\n\nexport const CampaignTopicSelect = zoneHost<CampaignTopicSelectZoneProps>(\n CAMPAIGN_TOPIC_SELECT_ZONE,\n 'CampaignTopicSelect',\n)\nexport const CampaignTopicOptions = zoneHost<CampaignTopicOptionsZoneProps>(\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n 'CampaignTopicOptions',\n)\nexport const CampaignSenderEditor = zoneHost<CampaignSenderEditorZoneProps>(\n CAMPAIGN_SENDER_EDITOR_ZONE,\n 'CampaignSenderEditor',\n)\nexport const CampaignDesignCreate = zoneHost<CampaignDesignCreateZoneProps>(\n CAMPAIGN_DESIGN_CREATE_ZONE,\n 'CampaignDesignCreate',\n)\nexport const CampaignDesignPreview = zoneHost<CampaignDesignPreviewZoneProps>(\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n 'CampaignDesignPreview',\n)\n"],"names":["useConsoleWidgetSlot","definePluginZone","Suspense","CAMPAIGN_TOPIC_SELECT_ZONE","CAMPAIGN_TOPIC_OPTIONS_ZONE","CAMPAIGN_SENDER_EDITOR_ZONE","CAMPAIGN_DESIGN_CREATE_ZONE","CAMPAIGN_DESIGN_PREVIEW_ZONE","zoneHost","zone","name","Host","props","Zone","fallback","slot","id","displayName","CampaignTopicSelect","CampaignTopicOptions","CampaignSenderEditor","CampaignDesignCreate","CampaignDesignPreview"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,oBAAoB,QAAQ,qDAAoD;AACzF,SAASC,gBAAgB,QAAQ,2CAA0C;AAE3E,SAASC,QAAQ,QAA2B,QAAO;AA8BnD,OAAO,MAAMC,6BACXF,iBAA+C,uBAAsB;AAuBvE,OAAO,MAAMG,8BACXH,iBAAgD,wBAAuB;AAczE,OAAO,MAAMI,8BACXJ,iBAAgD,wBAAuB;AAezE,OAAO,MAAMK,8BACXL,iBAAgD,wBAAuB;AAgBzE,OAAO,MAAMM,+BACXN,iBAAiD,yBAAwB;AAE3E;;;;;;;;CAQC,GACD,SAASO,SAA+BC,IAAoB,EAAEC,IAAY;IACxE,SAASC,KAAKC,KAAY;QACxB,MAAMC,OAAOb;QACb,OAAOa,qBACL,KAACX;YAASY,UAAU;sBAClB,cAAA,KAACD;gBAAKE,MAAMN,KAAKO,EAAE;eAAMJ;aAEzB;IACN;IACAD,KAAKM,WAAW,GAAGP;IACnB,OAAOC;AACT;AAEA,OAAO,MAAMO,sBAAsBV,SACjCL,4BACA,uBACD;AACD,OAAO,MAAMgB,uBAAuBX,SAClCJ,6BACA,wBACD;AACD,OAAO,MAAMgB,uBAAuBZ,SAClCH,6BACA,wBACD;AACD,OAAO,MAAMgB,uBAAuBb,SAClCF,6BACA,wBACD;AACD,OAAO,MAAMgB,wBAAwBd,SACnCD,8BACA,yBACD"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Last campaign at a glance (AGL-353): sent, opens and clicks for the most
3
+ * recent send.
4
+ *
5
+ * Registered by the marketing plugin rather than imported by the dashboard.
6
+ * Campaigns are managed from the Marketing page, so a workspace with the
7
+ * marketing plugin switched off has no campaigns page to open and the card
8
+ * could only ever be blank there. The header links to the surface that owns
9
+ * the history for the same reason: it is the one this widget's own plugin
10
+ * guarantees exists.
11
+ */
12
+ export declare function CampaignGlanceCard(props: {
13
+ hostId: string;
14
+ }): import("react").JSX.Element;
15
+ export declare namespace CampaignGlanceCard {
16
+ var displayName: string;
17
+ }
18
+ export default CampaignGlanceCard;
@@ -0,0 +1,151 @@
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 { buildRoute, pluginDocsHelp, Route } from "@aglyn/aglyn";
20
+ import { AppLink, CardDisplay } from "@aglyn/shared-ui-jsx";
21
+ import { Button, Stack, Typography } from "@mui/material";
22
+ import { collection, limit, query } from "firebase/firestore";
23
+ import { useParams } from "next/navigation";
24
+ import { useMemo } from "react";
25
+ import { useFirestore, useFirestoreCollection } from "@aglyn/tenant-feature-instance";
26
+ /**
27
+ * How many campaigns the card reads to find the last SENT one.
28
+ *
29
+ * A ceiling on a search, not a page: the newest send is what the card is
30
+ * about, and a site whose thirty most recent campaigns are all drafts simply
31
+ * has nothing to show — the card renders nothing at all in that case, which
32
+ * is the same answer it gives a site that has never sent.
33
+ */ const SEARCH_CEILING = 30;
34
+ /**
35
+ * Last campaign at a glance (AGL-353): sent, opens and clicks for the most
36
+ * recent send.
37
+ *
38
+ * Registered by the marketing plugin rather than imported by the dashboard.
39
+ * Campaigns are managed from the Marketing page, so a workspace with the
40
+ * marketing plugin switched off has no campaigns page to open and the card
41
+ * could only ever be blank there. The header links to the surface that owns
42
+ * the history for the same reason: it is the one this widget's own plugin
43
+ * guarantees exists.
44
+ */ export function CampaignGlanceCard(props) {
45
+ var _ref, _ref1, _ref2;
46
+ var _lastSent_stats, _lastSent_stats1, _lastSent_stats2;
47
+ const { hostId } = props;
48
+ const firestore = useFirestore();
49
+ const { orgSlug, host } = useParams();
50
+ const { data: campaignDocs } = useFirestoreCollection(()=>query(collection(firestore, 'hosts', hostId, 'campaigns'), limit(SEARCH_CEILING)), [
51
+ firestore,
52
+ hostId
53
+ ], {
54
+ idField: '$id'
55
+ });
56
+ const lastSent = useMemo(()=>[
57
+ ...campaignDocs != null ? campaignDocs : []
58
+ ].filter((campaign)=>campaign.status === 'sent').sort((a, b)=>{
59
+ var _ref, _ref1;
60
+ var _b_sentAt, _a_sentAt;
61
+ return ((_ref = (_b_sentAt = b.sentAt) == null ? void 0 : _b_sentAt.seconds) != null ? _ref : 0) - ((_ref1 = (_a_sentAt = a.sentAt) == null ? void 0 : _a_sentAt.seconds) != null ? _ref1 : 0);
62
+ })[0], [
63
+ campaignDocs
64
+ ]);
65
+ if (!lastSent) return null;
66
+ return /*#__PURE__*/ _jsx(CardDisplay, {
67
+ header: 'Last campaign',
68
+ help: pluginDocsHelp('emailCampaigns', {
69
+ anchor: '#opens--clicks',
70
+ excerpt: 'Sent, opens, and clicks for your most recent campaign — open ' + 'Campaigns for the full history.'
71
+ }),
72
+ contentGutterX: true,
73
+ contentGutterY: true,
74
+ HeaderProps: {
75
+ action: /*#__PURE__*/ _jsx(Button, _extends({
76
+ component: AppLink
77
+ }, {
78
+ componentVariant: 'naked',
79
+ nativeButton: false
80
+ }, {
81
+ href: `${buildRoute(Route.HOST_PLUGIN, {
82
+ orgSlug,
83
+ host,
84
+ pluginSlug: 'marketing'
85
+ })}/campaigns`,
86
+ size: "small",
87
+ color: "primary",
88
+ children: 'Campaigns'
89
+ }))
90
+ },
91
+ children: /*#__PURE__*/ _jsxs(Stack, {
92
+ spacing: 1,
93
+ children: [
94
+ /*#__PURE__*/ _jsx(Typography, {
95
+ variant: "body2",
96
+ noWrap: true,
97
+ children: lastSent.subject
98
+ }),
99
+ /*#__PURE__*/ _jsxs(Stack, {
100
+ direction: "row",
101
+ spacing: 3,
102
+ children: [
103
+ /*#__PURE__*/ _jsxs(Stack, {
104
+ children: [
105
+ /*#__PURE__*/ _jsx(Typography, {
106
+ variant: "h6",
107
+ children: (_ref = (_lastSent_stats = lastSent.stats) == null ? void 0 : _lastSent_stats.sent) != null ? _ref : 0
108
+ }),
109
+ /*#__PURE__*/ _jsx(Typography, {
110
+ variant: "caption",
111
+ color: "text.secondary",
112
+ children: 'Sent'
113
+ })
114
+ ]
115
+ }),
116
+ /*#__PURE__*/ _jsxs(Stack, {
117
+ children: [
118
+ /*#__PURE__*/ _jsx(Typography, {
119
+ variant: "h6",
120
+ children: (_ref1 = (_lastSent_stats1 = lastSent.stats) == null ? void 0 : _lastSent_stats1.opens) != null ? _ref1 : 0
121
+ }),
122
+ /*#__PURE__*/ _jsx(Typography, {
123
+ variant: "caption",
124
+ color: "text.secondary",
125
+ children: 'Opens'
126
+ })
127
+ ]
128
+ }),
129
+ /*#__PURE__*/ _jsxs(Stack, {
130
+ children: [
131
+ /*#__PURE__*/ _jsx(Typography, {
132
+ variant: "h6",
133
+ children: (_ref2 = (_lastSent_stats2 = lastSent.stats) == null ? void 0 : _lastSent_stats2.clicks) != null ? _ref2 : 0
134
+ }),
135
+ /*#__PURE__*/ _jsx(Typography, {
136
+ variant: "caption",
137
+ color: "text.secondary",
138
+ children: 'Clicks'
139
+ })
140
+ ]
141
+ })
142
+ ]
143
+ })
144
+ ]
145
+ })
146
+ });
147
+ }
148
+ CampaignGlanceCard.displayName = 'CampaignGlanceCard';
149
+ export default CampaignGlanceCard;
150
+
151
+ //# sourceMappingURL=campaign-glance-card.component.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/campaign-glance-card.component.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 { AppLink, CardDisplay } from '@aglyn/shared-ui-jsx'\nimport { Button, Stack, Typography } from '@mui/material'\nimport { collection, limit, query } from 'firebase/firestore'\nimport { useParams } from 'next/navigation'\nimport { useMemo } from 'react'\nimport { useFirestore, useFirestoreCollection } from '@aglyn/tenant-feature-instance'\n\n/**\n * How many campaigns the card reads to find the last SENT one.\n *\n * A ceiling on a search, not a page: the newest send is what the card is\n * about, and a site whose thirty most recent campaigns are all drafts simply\n * has nothing to show — the card renders nothing at all in that case, which\n * is the same answer it gives a site that has never sent.\n */\nconst SEARCH_CEILING = 30\n\n/**\n * Last campaign at a glance (AGL-353): sent, opens and clicks for the most\n * recent send.\n *\n * Registered by the marketing plugin rather than imported by the dashboard.\n * Campaigns are managed from the Marketing page, so a workspace with the\n * marketing plugin switched off has no campaigns page to open and the card\n * could only ever be blank there. The header links to the surface that owns\n * the history for the same reason: it is the one this widget's own plugin\n * guarantees exists.\n */\nexport function CampaignGlanceCard(props: { hostId: string }) {\n const { hostId } = props\n const firestore = useFirestore()\n const { orgSlug, host } = useParams<{ orgSlug: string; host: string }>()\n const { data: campaignDocs } = useFirestoreCollection<any>(\n () =>\n query(\n collection(firestore, 'hosts', hostId, 'campaigns'),\n limit(SEARCH_CEILING),\n ),\n [firestore, hostId],\n { idField: '$id' },\n )\n const lastSent = useMemo(\n () =>\n [...(campaignDocs ?? [])]\n .filter((campaign: any) => campaign.status === 'sent')\n .sort(\n (a: any, b: any) =>\n (b.sentAt?.seconds ?? 0) - (a.sentAt?.seconds ?? 0),\n )[0],\n [campaignDocs],\n )\n\n if (!lastSent) return null\n\n return (\n <CardDisplay\n header={'Last campaign'}\n help={pluginDocsHelp('emailCampaigns', {\n anchor: '#opens--clicks',\n excerpt:\n 'Sent, opens, and clicks for your most recent campaign — open ' +\n 'Campaigns for the full history.',\n })}\n contentGutterX\n contentGutterY\n HeaderProps={{\n action: (\n <Button\n component={AppLink as any}\n {...({ componentVariant: 'naked', nativeButton: false } as any)}\n href={`${buildRoute(Route.HOST_PLUGIN, {\n orgSlug,\n host,\n pluginSlug: 'marketing',\n })}/campaigns`}\n size=\"small\"\n color=\"primary\"\n >\n {'Campaigns'}\n </Button>\n ),\n }}\n >\n <Stack spacing={1}>\n <Typography variant=\"body2\" noWrap>\n {lastSent.subject}\n </Typography>\n <Stack direction=\"row\" spacing={3}>\n <Stack>\n <Typography variant=\"h6\">{lastSent.stats?.sent ?? 0}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Sent'}\n </Typography>\n </Stack>\n <Stack>\n <Typography variant=\"h6\">{lastSent.stats?.opens ?? 0}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Opens'}\n </Typography>\n </Stack>\n <Stack>\n <Typography variant=\"h6\">{lastSent.stats?.clicks ?? 0}</Typography>\n <Typography variant=\"caption\" color=\"text.secondary\">\n {'Clicks'}\n </Typography>\n </Stack>\n </Stack>\n </Stack>\n </CardDisplay>\n )\n}\nCampaignGlanceCard.displayName = 'CampaignGlanceCard'\n\nexport default CampaignGlanceCard\n"],"names":["buildRoute","pluginDocsHelp","Route","AppLink","CardDisplay","Button","Stack","Typography","collection","limit","query","useParams","useMemo","useFirestore","useFirestoreCollection","SEARCH_CEILING","CampaignGlanceCard","props","lastSent","hostId","firestore","orgSlug","host","data","campaignDocs","idField","filter","campaign","status","sort","a","b","sentAt","seconds","header","help","anchor","excerpt","contentGutterX","contentGutterY","HeaderProps","action","component","componentVariant","nativeButton","href","HOST_PLUGIN","pluginSlug","size","color","spacing","variant","noWrap","subject","direction","stats","sent","opens","clicks","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;AAEA,SAASA,UAAU,EAAEC,cAAc,EAAEC,KAAK,QAAQ,eAAc;AAChE,SAASC,OAAO,EAAEC,WAAW,QAAQ,uBAAsB;AAC3D,SAASC,MAAM,EAAEC,KAAK,EAAEC,UAAU,QAAQ,gBAAe;AACzD,SAASC,UAAU,EAAEC,KAAK,EAAEC,KAAK,QAAQ,qBAAoB;AAC7D,SAASC,SAAS,QAAQ,kBAAiB;AAC3C,SAASC,OAAO,QAAQ,QAAO;AAC/B,SAASC,YAAY,EAAEC,sBAAsB,QAAQ,iCAAgC;AAErF;;;;;;;CAOC,GACD,MAAMC,iBAAiB;AAEvB;;;;;;;;;;CAUC,GACD,OAAO,SAASC,mBAAmBC,KAAyB;;QA6DtBC,iBAMAA,kBAMAA;IAxEpC,MAAM,EAAEC,MAAM,EAAE,GAAGF;IACnB,MAAMG,YAAYP;IAClB,MAAM,EAAEQ,OAAO,EAAEC,IAAI,EAAE,GAAGX;IAC1B,MAAM,EAAEY,MAAMC,YAAY,EAAE,GAAGV,uBAC7B,IACEJ,MACEF,WAAWY,WAAW,SAASD,QAAQ,cACvCV,MAAMM,kBAEV;QAACK;QAAWD;KAAO,EACnB;QAAEM,SAAS;IAAM;IAEnB,MAAMP,WAAWN,QACf,IACE;eAAKY,uBAAAA,eAAgB,EAAE;SAAE,CACtBE,MAAM,CAAC,CAACC,WAAkBA,SAASC,MAAM,KAAK,QAC9CC,IAAI,CACH,CAACC,GAAQC;;gBACNA,WAA2BD;mBAA5B,UAACC,YAAAA,EAAEC,MAAM,qBAARD,UAAUE,OAAO,mBAAI,gBAAMH,YAAAA,EAAEE,MAAM,qBAARF,UAAUG,OAAO,oBAAI;UACpD,CAAC,EAAE,EACR;QAACT;KAAa;IAGhB,IAAI,CAACN,UAAU,OAAO;IAEtB,qBACE,KAACd;QACC8B,QAAQ;QACRC,MAAMlC,eAAe,kBAAkB;YACrCmC,QAAQ;YACRC,SACE,kEACA;QACJ;QACAC,cAAc;QACdC,cAAc;QACdC,aAAa;YACXC,sBACE,KAACpC;gBACCqC,WAAWvC;eACN;gBAAEwC,kBAAkB;gBAASC,cAAc;YAAM;gBACtDC,MAAM,GAAG7C,WAAWE,MAAM4C,WAAW,EAAE;oBACrCzB;oBACAC;oBACAyB,YAAY;gBACd,GAAG,UAAU,CAAC;gBACdC,MAAK;gBACLC,OAAM;0BAEL;;QAGP;kBAEA,cAAA,MAAC3C;YAAM4C,SAAS;;8BACd,KAAC3C;oBAAW4C,SAAQ;oBAAQC,MAAM;8BAC/BlC,SAASmC,OAAO;;8BAEnB,MAAC/C;oBAAMgD,WAAU;oBAAMJ,SAAS;;sCAC9B,MAAC5C;;8CACC,KAACC;oCAAW4C,SAAQ;uDAAMjC,kBAAAA,SAASqC,KAAK,qBAAdrC,gBAAgBsC,IAAI,mBAAI;;8CAClD,KAACjD;oCAAW4C,SAAQ;oCAAUF,OAAM;8CACjC;;;;sCAGL,MAAC3C;;8CACC,KAACC;oCAAW4C,SAAQ;wDAAMjC,mBAAAA,SAASqC,KAAK,qBAAdrC,iBAAgBuC,KAAK,oBAAI;;8CACnD,KAAClD;oCAAW4C,SAAQ;oCAAUF,OAAM;8CACjC;;;;sCAGL,MAAC3C;;8CACC,KAACC;oCAAW4C,SAAQ;wDAAMjC,mBAAAA,SAASqC,KAAK,qBAAdrC,iBAAgBwC,MAAM,oBAAI;;8CACpD,KAACnD;oCAAW4C,SAAQ;oCAAUF,OAAM;8CACjC;;;;;;;;;AAOf;AACAjC,mBAAmB2C,WAAW,GAAG;AAEjC,eAAe3C,mBAAkB"}
@@ -0,0 +1,87 @@
1
+ export interface CampaignMembersSectionProps {
2
+ hostId: string;
3
+ campaignId: string;
4
+ /** The campaign's first day, when it has one. */
5
+ startAtMs?: number | null;
6
+ /** The campaign's last day, when it has one. */
7
+ endAtMs?: number | null;
8
+ }
9
+ /**
10
+ * WHAT ELSE IS IN THIS CAMPAIGN — the landing pages and forms assigned to it,
11
+ * and what those forms have collected.
12
+ *
13
+ * ## Declared, and therefore different from every section above it
14
+ *
15
+ * The conversions, revenue and destinations sections are EVIDENCE: they join
16
+ * on the campaign's send ids and report what visitors did BECAUSE of this
17
+ * campaign's mail. This one is a statement the merchant made — a screen or a
18
+ * form carries this campaign's id in {@link CAMPAIGN_MEMBERSHIP_FIELD} — plus
19
+ * the counters those records carry on their own documents.
20
+ *
21
+ * The distinction survives into the wording, because the arithmetic will not
22
+ * enforce it. A form's submissions are the form's; filing the form under a
23
+ * campaign does not make them the campaign's, and a heading that said
24
+ * "this campaign produced 40 leads" over a number meaning "the forms filed
25
+ * here have 40 leads" would be a worse page than one showing nothing. So the
26
+ * figures are labeled as what the forms HOLD, they sit in their own block
27
+ * under their own disclaimer, and no total on this page adds them to an
28
+ * attributed one.
29
+ *
30
+ * ## Three ways the forms' counters could mislead, each answered
31
+ *
32
+ * 1. **They are lifetime.** `stats.submissions` has counted since the form
33
+ * existed, including every submission from before anybody filed it here.
34
+ * Where the campaign carries dates, the figures are summed from the
35
+ * monthly `stats.periods` series over those months instead, and the
36
+ * caption says which of the two a reader is looking at. Months are whole,
37
+ * so a campaign starting mid-month includes that month entire — stated,
38
+ * not rounded away.
39
+ * 2. **A form can be in twenty campaigns.** `campaignIds` is an array, so
40
+ * the same submissions count toward every campaign the form is filed
41
+ * under. Rows say so and the total refuses to present itself as
42
+ * exclusive.
43
+ * 3. **A counter nobody wrote is not a zero.** `stats.leads` is incremented
44
+ * only for a form whose `routing.lead` is set, and `stats.views` only by
45
+ * the analytics beacon. Both reach the shared `Figure`, which draws an em
46
+ * dash and the words "not recorded" rather than a confident 0.
47
+ *
48
+ * ## Screens carry no counter, and this section will not invent one
49
+ *
50
+ * A form's counters ride on the form document, so the query this section
51
+ * already makes pays for them. A screen's traffic does not: it is one
52
+ * document per screen per day in `hosts/{hostId}/screenAnalytics`, with no
53
+ * lifetime total anywhere, so a views column here would cost a range read
54
+ * across screens times days on every open of this page — and the display of
55
+ * those figures is a paid entitlement this section does not resolve. The
56
+ * screens block names the absence and links to the surface that does measure
57
+ * it, which is a smaller lie than a number nobody can stand behind.
58
+ *
59
+ * ## The join is `array-contains`, on the member's own document
60
+ *
61
+ * A campaign holds no member list. Each record names the campaigns it is in,
62
+ * which is what makes deleting one record leave nothing behind and what lets
63
+ * a record's own page draw its campaigns without a query. The query here is
64
+ * the other direction of the same field, served by Firestore's automatic
65
+ * single-field index.
66
+ *
67
+ * `orderBy(documentId())`, like every other bounded list in this console: a
68
+ * form carries `updatedAt` only if some writer stamped one, and ordering on a
69
+ * field a writer may omit does not mis-sort the list, it drops rows from it.
70
+ *
71
+ * ## Contacts are named here and NOT listed, and the reason is a query limit
72
+ *
73
+ * A contact is org-scoped and shared between the sites that captured it, so
74
+ * every client read of the collection has to carry
75
+ * `visibleTo array-contains-any` — that is what makes the read provable
76
+ * per-document under the rules. Firestore permits ONE array clause per query,
77
+ * and that is it: there is no client query that filters contacts by campaign
78
+ * as well, so there is no pre-filtered address to link to either. The link
79
+ * offered goes to the Contacts page and says it arrives unfiltered; the
80
+ * assignment is real, is set from the contact's own drawer, and a count of it
81
+ * would cost a scan of the collection the customer is billed on.
82
+ */
83
+ export declare function CampaignMembersSection(props: CampaignMembersSectionProps): import("react").JSX.Element;
84
+ export declare namespace CampaignMembersSection {
85
+ var displayName: string;
86
+ }
87
+ export default CampaignMembersSection;