@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.
- package/LICENSE +201 -0
- package/README.md +7 -0
- package/package.json +56 -0
- package/src/index.d.ts +19 -0
- package/src/index.js +20 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/announcement-bar-card.component.d.ts +20 -0
- package/src/lib/components/announcement-bar-card.component.js +292 -0
- package/src/lib/components/announcement-bar-card.component.js.map +1 -0
- package/src/lib/components/campaign-composer.d.ts +84 -0
- package/src/lib/components/campaign-composer.js +1643 -0
- package/src/lib/components/campaign-composer.js.map +1 -0
- package/src/lib/components/campaign-conversions-card.d.ts +22 -0
- package/src/lib/components/campaign-conversions-card.js +642 -0
- package/src/lib/components/campaign-conversions-card.js.map +1 -0
- package/src/lib/components/campaign-detail-card.d.ts +55 -0
- package/src/lib/components/campaign-detail-card.js +771 -0
- package/src/lib/components/campaign-detail-card.js.map +1 -0
- package/src/lib/components/campaign-edit-drawer.d.ts +60 -0
- package/src/lib/components/campaign-edit-drawer.js +252 -0
- package/src/lib/components/campaign-edit-drawer.js.map +1 -0
- package/src/lib/components/campaign-email-zones.d.ts +111 -0
- package/src/lib/components/campaign-email-zones.js +54 -0
- package/src/lib/components/campaign-email-zones.js.map +1 -0
- package/src/lib/components/campaign-glance-card.component.d.ts +18 -0
- package/src/lib/components/campaign-glance-card.component.js +151 -0
- package/src/lib/components/campaign-glance-card.component.js.map +1 -0
- package/src/lib/components/campaign-members-section.d.ts +87 -0
- package/src/lib/components/campaign-members-section.js +499 -0
- package/src/lib/components/campaign-members-section.js.map +1 -0
- package/src/lib/components/campaign-reach-sections.d.ts +137 -0
- package/src/lib/components/campaign-reach-sections.js +694 -0
- package/src/lib/components/campaign-reach-sections.js.map +1 -0
- package/src/lib/components/campaign-report-card.d.ts +91 -0
- package/src/lib/components/campaign-report-card.js +667 -0
- package/src/lib/components/campaign-report-card.js.map +1 -0
- package/src/lib/components/campaign-test-send-drawer.d.ts +45 -0
- package/src/lib/components/campaign-test-send-drawer.js +238 -0
- package/src/lib/components/campaign-test-send-drawer.js.map +1 -0
- package/src/lib/components/campaigns-card.d.ts +31 -0
- package/src/lib/components/campaigns-card.js +667 -0
- package/src/lib/components/campaigns-card.js.map +1 -0
- package/src/lib/components/conversion-attribution.component.d.ts +62 -0
- package/src/lib/components/conversion-attribution.component.js +127 -0
- package/src/lib/components/conversion-attribution.component.js.map +1 -0
- package/src/lib/components/email-compose-card.d.ts +12 -0
- package/src/lib/components/email-compose-card.js +157 -0
- package/src/lib/components/email-compose-card.js.map +1 -0
- package/src/lib/components/email-detail.d.ts +40 -0
- package/src/lib/components/email-detail.js +1180 -0
- package/src/lib/components/email-detail.js.map +1 -0
- package/src/lib/components/email-edit-drawer.d.ts +54 -0
- package/src/lib/components/email-edit-drawer.js +172 -0
- package/src/lib/components/email-edit-drawer.js.map +1 -0
- package/src/lib/components/email-messages-widget.d.ts +22 -0
- package/src/lib/components/email-messages-widget.js +49 -0
- package/src/lib/components/email-messages-widget.js.map +1 -0
- package/src/lib/components/email-recipients-card.d.ts +50 -0
- package/src/lib/components/email-recipients-card.js +338 -0
- package/src/lib/components/email-recipients-card.js.map +1 -0
- package/src/lib/components/emails-list-card.d.ts +36 -0
- package/src/lib/components/emails-list-card.js +544 -0
- package/src/lib/components/emails-list-card.js.map +1 -0
- package/src/lib/components/experiment-zones.d.ts +78 -0
- package/src/lib/components/experiment-zones.js +65 -0
- package/src/lib/components/experiment-zones.js.map +1 -0
- package/src/lib/components/host-experiments-card.component.d.ts +17 -0
- package/src/lib/components/host-experiments-card.component.js +887 -0
- package/src/lib/components/host-experiments-card.component.js.map +1 -0
- package/src/lib/components/host-marketing-summary-card.component.d.ts +15 -0
- package/src/lib/components/host-marketing-summary-card.component.js +161 -0
- package/src/lib/components/host-marketing-summary-card.component.js.map +1 -0
- package/src/lib/components/host-overlays-card.component.d.ts +19 -0
- package/src/lib/components/host-overlays-card.component.js +783 -0
- package/src/lib/components/host-overlays-card.component.js.map +1 -0
- package/src/lib/components/marketing-console-page.d.ts +28 -0
- package/src/lib/components/marketing-console-page.js +166 -0
- package/src/lib/components/marketing-console-page.js.map +1 -0
- package/src/lib/components/marketing-console-sections.d.ts +34 -0
- package/src/lib/components/marketing-console-sections.js +77 -0
- package/src/lib/components/marketing-console-sections.js.map +1 -0
- package/src/lib/components/overlay-stats-row.component.d.ts +15 -0
- package/src/lib/components/overlay-stats-row.component.js +78 -0
- package/src/lib/components/overlay-stats-row.component.js.map +1 -0
- package/src/lib/components/popup-card.component.d.ts +18 -0
- package/src/lib/components/popup-card.component.js +462 -0
- package/src/lib/components/popup-card.component.js.map +1 -0
- package/src/lib/components/record-attribution-widget.d.ts +35 -0
- package/src/lib/components/record-attribution-widget.js +33 -0
- package/src/lib/components/record-attribution-widget.js.map +1 -0
- package/src/lib/components/site-runtime.d.ts +9 -0
- package/src/lib/components/site-runtime.js +1081 -0
- package/src/lib/components/site-runtime.js.map +1 -0
- package/src/lib/components/use-campaign-send-api.d.ts +35 -0
- package/src/lib/components/use-campaign-send-api.js +99 -0
- package/src/lib/components/use-campaign-send-api.js.map +1 -0
- package/src/lib/components/use-campaign-topic-options.d.ts +20 -0
- package/src/lib/components/use-campaign-topic-options.js +44 -0
- package/src/lib/components/use-campaign-topic-options.js.map +1 -0
- package/src/lib/components/use-emails-hub-path.d.ts +39 -0
- package/src/lib/components/use-emails-hub-path.js +70 -0
- package/src/lib/components/use-emails-hub-path.js.map +1 -0
- package/src/lib/components/use-marketing-hub-path.d.ts +21 -0
- package/src/lib/components/use-marketing-hub-path.js +50 -0
- package/src/lib/components/use-marketing-hub-path.js.map +1 -0
- package/src/lib/components/use-overlay-copy-editor.d.ts +79 -0
- package/src/lib/components/use-overlay-copy-editor.js +110 -0
- package/src/lib/components/use-overlay-copy-editor.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +8 -0
- package/src/lib/constants/bundle-common.js +9 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/model/campaign-membership-figures.d.ts +109 -0
- package/src/lib/model/campaign-membership-figures.js +113 -0
- package/src/lib/model/campaign-membership-figures.js.map +1 -0
- package/src/lib/model/compile-client-automations.d.ts +65 -0
- package/src/lib/model/compile-client-automations.js +95 -0
- package/src/lib/model/compile-client-automations.js.map +1 -0
- package/src/lib/model/experiments.d.ts +130 -0
- package/src/lib/model/experiments.js +186 -0
- package/src/lib/model/experiments.js.map +1 -0
- package/src/lib/model/index.d.ts +26 -0
- package/src/lib/model/index.js +26 -0
- package/src/lib/model/index.js.map +1 -0
- package/src/lib/model/marketing-record-routes.d.ts +6 -0
- package/src/lib/model/marketing-record-routes.js +46 -0
- package/src/lib/model/marketing-record-routes.js.map +1 -0
- package/src/lib/model/overlay-copy.d.ts +62 -0
- package/src/lib/model/overlay-copy.js +78 -0
- package/src/lib/model/overlay-copy.js.map +1 -0
- package/src/lib/model/overlays.d.ts +113 -0
- package/src/lib/model/overlays.js +93 -0
- package/src/lib/model/overlays.js.map +1 -0
- package/src/lib/model/site-contract.d.ts +100 -0
- package/src/lib/model/site-contract.js +23 -0
- package/src/lib/model/site-contract.js.map +1 -0
- package/src/lib/plugin.d.ts +27 -0
- package/src/lib/plugin.js +168 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/preview-props.d.ts +31 -0
- package/src/lib/preview-props.js +54 -0
- package/src/lib/preview-props.js.map +1 -0
- package/src/lib/server/campaign-manage.d.ts +62 -0
- package/src/lib/server/campaign-manage.js +829 -0
- package/src/lib/server/campaign-manage.js.map +1 -0
- package/src/lib/server/campaign-process-scheduled.d.ts +26 -0
- package/src/lib/server/campaign-process-scheduled.js +198 -0
- package/src/lib/server/campaign-process-scheduled.js.map +1 -0
- package/src/lib/server/campaign-recipients.d.ts +55 -0
- package/src/lib/server/campaign-recipients.js +219 -0
- package/src/lib/server/campaign-recipients.js.map +1 -0
- package/src/lib/server/campaign-send.d.ts +487 -0
- package/src/lib/server/campaign-send.js +3157 -0
- package/src/lib/server/campaign-send.js.map +1 -0
- package/src/lib/server/email-campaign-reach.d.ts +174 -0
- package/src/lib/server/email-campaign-reach.js +254 -0
- package/src/lib/server/email-campaign-reach.js.map +1 -0
- package/src/lib/server/email-events.d.ts +27 -0
- package/src/lib/server/email-events.js +714 -0
- package/src/lib/server/email-events.js.map +1 -0
- package/src/lib/server/get-client-automations.d.ts +60 -0
- package/src/lib/server/get-client-automations.js +87 -0
- package/src/lib/server/get-client-automations.js.map +1 -0
- package/src/lib/server/get-overlays.d.ts +28 -0
- package/src/lib/server/get-overlays.js +65 -0
- package/src/lib/server/get-overlays.js.map +1 -0
- package/src/lib/server/get-screen-experiments.d.ts +42 -0
- package/src/lib/server/get-screen-experiments.js +82 -0
- package/src/lib/server/get-screen-experiments.js.map +1 -0
- package/src/lib/server/lists-materialize.d.ts +18 -0
- package/src/lib/server/lists-materialize.js +130 -0
- package/src/lib/server/lists-materialize.js.map +1 -0
- package/src/lib/server/marketing-figures.d.ts +37 -0
- package/src/lib/server/marketing-figures.js +248 -0
- package/src/lib/server/marketing-figures.js.map +1 -0
- package/src/lib/server/site-page-enricher.d.ts +26 -0
- package/src/lib/server/site-page-enricher.js +210 -0
- package/src/lib/server/site-page-enricher.js.map +1 -0
- package/src/lib/server.d.ts +20 -0
- package/src/lib/server.js +152 -0
- package/src/lib/server.js.map +1 -0
- package/src/lib/site.d.ts +22 -0
- package/src/lib/site.js +48 -0
- 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;
|