@aglyn/plugins-marketing 1.0.0-beta.173 → 1.0.0-beta.175

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 (79) hide show
  1. package/README.md +2 -1
  2. package/package.json +15 -15
  3. package/src/lib/components/campaign-detail-card.d.ts +12 -1
  4. package/src/lib/components/campaign-detail-card.js +146 -35
  5. package/src/lib/components/campaign-detail-card.js.map +1 -1
  6. package/src/lib/components/campaign-edit-drawer.d.ts +13 -0
  7. package/src/lib/components/campaign-edit-drawer.js +56 -4
  8. package/src/lib/components/campaign-edit-drawer.js.map +1 -1
  9. package/src/lib/components/campaign-glance-card.component.js +10 -2
  10. package/src/lib/components/campaign-glance-card.component.js.map +1 -1
  11. package/src/lib/components/campaign-members-section.d.ts +6 -0
  12. package/src/lib/components/campaign-members-section.js +2 -2
  13. package/src/lib/components/campaign-members-section.js.map +1 -1
  14. package/src/lib/components/campaign-queries.d.ts +58 -0
  15. package/src/lib/components/campaign-queries.js +72 -0
  16. package/src/lib/components/campaign-queries.js.map +1 -0
  17. package/src/lib/components/campaign-reach-sections.d.ts +21 -7
  18. package/src/lib/components/campaign-reach-sections.js +20 -17
  19. package/src/lib/components/campaign-reach-sections.js.map +1 -1
  20. package/src/lib/components/campaign-report-card.d.ts +8 -1
  21. package/src/lib/components/campaign-report-card.js +33 -16
  22. package/src/lib/components/campaign-report-card.js.map +1 -1
  23. package/src/lib/components/campaigns-card.d.ts +10 -1
  24. package/src/lib/components/campaigns-card.js +102 -19
  25. package/src/lib/components/campaigns-card.js.map +1 -1
  26. package/src/lib/components/email-compose-card.d.ts +3 -2
  27. package/src/lib/components/email-compose-card.js +18 -7
  28. package/src/lib/components/email-compose-card.js.map +1 -1
  29. package/src/lib/components/email-detail.d.ts +11 -2
  30. package/src/lib/components/email-detail.js +50 -23
  31. package/src/lib/components/email-detail.js.map +1 -1
  32. package/src/lib/components/emails-list-card.d.ts +10 -1
  33. package/src/lib/components/emails-list-card.js +129 -23
  34. package/src/lib/components/emails-list-card.js.map +1 -1
  35. package/src/lib/components/host-marketing-summary-card.component.js +9 -1
  36. package/src/lib/components/host-marketing-summary-card.component.js.map +1 -1
  37. package/src/lib/components/marketing-console-page.js +81 -4
  38. package/src/lib/components/marketing-console-page.js.map +1 -1
  39. package/src/lib/components/marketing-console-sections.d.ts +16 -1
  40. package/src/lib/components/marketing-console-sections.js +25 -2
  41. package/src/lib/components/marketing-console-sections.js.map +1 -1
  42. package/src/lib/components/marketing-org-mount.d.ts +71 -0
  43. package/src/lib/components/marketing-org-mount.js +84 -0
  44. package/src/lib/components/marketing-org-mount.js.map +1 -0
  45. package/src/lib/components/use-campaign-send-api.d.ts +2 -2
  46. package/src/lib/components/use-campaign-send-api.js +22 -6
  47. package/src/lib/components/use-campaign-send-api.js.map +1 -1
  48. package/src/lib/components/use-emails-hub-path.d.ts +13 -3
  49. package/src/lib/components/use-emails-hub-path.js +25 -4
  50. package/src/lib/components/use-emails-hub-path.js.map +1 -1
  51. package/src/lib/components/use-marketing-hub-path.d.ts +19 -16
  52. package/src/lib/components/use-marketing-hub-path.js +6 -32
  53. package/src/lib/components/use-marketing-hub-path.js.map +1 -1
  54. package/src/lib/model/marketing-record-routes.js +11 -4
  55. package/src/lib/model/marketing-record-routes.js.map +1 -1
  56. package/src/lib/plugin.js +31 -1
  57. package/src/lib/plugin.js.map +1 -1
  58. package/src/lib/server/campaign-manage.d.ts +31 -2
  59. package/src/lib/server/campaign-manage.js +365 -137
  60. package/src/lib/server/campaign-manage.js.map +1 -1
  61. package/src/lib/server/campaign-org-refs.d.ts +45 -0
  62. package/src/lib/server/campaign-org-refs.js +83 -0
  63. package/src/lib/server/campaign-org-refs.js.map +1 -0
  64. package/src/lib/server/campaign-process-scheduled.d.ts +32 -2
  65. package/src/lib/server/campaign-process-scheduled.js +79 -9
  66. package/src/lib/server/campaign-process-scheduled.js.map +1 -1
  67. package/src/lib/server/campaign-recipients.js +18 -4
  68. package/src/lib/server/campaign-recipients.js.map +1 -1
  69. package/src/lib/server/campaign-send.js +139 -55
  70. package/src/lib/server/campaign-send.js.map +1 -1
  71. package/src/lib/server/email-campaign-reach.d.ts +6 -5
  72. package/src/lib/server/email-campaign-reach.js +14 -14
  73. package/src/lib/server/email-campaign-reach.js.map +1 -1
  74. package/src/lib/server/email-events.js +48 -17
  75. package/src/lib/server/email-events.js.map +1 -1
  76. package/src/lib/server/marketing-figures.js +12 -1
  77. package/src/lib/server/marketing-figures.js.map +1 -1
  78. package/src/lib/server.js +7 -3
  79. package/src/lib/server.js.map +1 -1
@@ -33,27 +33,48 @@ import { useParams } from "next/navigation";
33
33
  * open of a campaign, to render a link.
34
34
  *
35
35
  * `null` before the params resolve, so callers render plain text rather than
36
- * a link to nowhere.
36
+ * a link to nowhere — and on the org Marketing hub, where there is no site
37
+ * and so no Emails console; a card there builds its message links from the
38
+ * org mount instead.
37
39
  */ export function useEmailsHubPath() {
38
40
  return useConsoleHubPath('emails');
39
41
  }
40
42
  /**
41
- * The MARKETING console's URL under the site being read.
43
+ * The MARKETING console's URL: under the site being read, or the
44
+ * organization's own Marketing hub on a page that names no site.
42
45
  *
43
46
  * Named for the same reason the Emails one is, from the opposite direction: a
44
47
  * converted record — a form submission in the Inbox, a contact in Contacts —
45
48
  * links to the campaign it came from, and those surfaces are handed their own
46
49
  * hub's `basePath`, not this one's.
50
+ *
51
+ * Unlike the Emails hub it has an answer with no site in the URL. Campaigns
52
+ * belong to the organization, so the org-level CRM and the org Marketing hub
53
+ * itself link a campaign to `/[orgSlug]/marketing/campaigns/{id}`.
47
54
  */ export function useMarketingHubPath() {
48
- return useConsoleHubPath('marketing');
55
+ const params = useParams();
56
+ const orgSlug = params == null ? void 0 : params.orgSlug;
57
+ if (!orgSlug) return null;
58
+ return (params == null ? void 0 : params.host) ? buildRoute(Route.HOST_PLUGIN, {
59
+ orgSlug,
60
+ host: params.host,
61
+ pluginSlug: MARKETING_SLUG
62
+ }) : buildRoute(Route.ORG_PLUGIN, {
63
+ orgSlug,
64
+ pluginSlug: MARKETING_SLUG
65
+ });
49
66
  }
67
+ /** The Marketing hub's URL slug, under a site and under the org alike. */ const MARKETING_SLUG = 'marketing';
50
68
  /**
51
- * Any sibling hub's URL, by plugin slug.
69
+ * Any sibling hub's URL under the site being read, by plugin slug.
52
70
  *
53
71
  * One implementation, because "read the org slug and the subdomain off the
54
72
  * route rather than resolving the host document" is the property that keeps
55
73
  * these links free, and a second copy of it is where somebody pays for the
56
74
  * two `getDoc`s instead.
75
+ *
76
+ * `null` on a page that names no site: a site's hub is the only thing this
77
+ * builds.
57
78
  */ export function useConsoleHubPath(pluginSlug) {
58
79
  const params = useParams();
59
80
  const orgSlug = params == null ? void 0 : params.orgSlug;
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/use-emails-hub-path.ts"],"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, Route } from '@aglyn/aglyn'\nimport { useParams } from 'next/navigation'\n\n/**\n * The Emails console's URL under the site being read.\n *\n * A campaign's page links to two records it does not own — the message that\n * was sent inside it, and the template that message was built from — and both\n * keep their pages on the Emails console. The shell hands a plugin page only\n * its OWN `basePath`, so the sibling hub is named here by its SLUG rather\n * than by editing the last segment off the current path: a slug is a string\n * this file can be searched for when a surface moves, and string surgery on\n * somebody else's path is a link that keeps resolving to the wrong place.\n *\n * Free, deliberately. The org slug and the subdomain are already in the URL\n * the console is on, so this reads the route rather than resolving the host\n * document — the two `getDoc`s that resolution costs would be paid on every\n * open of a campaign, to render a link.\n *\n * `null` before the params resolve, so callers render plain text rather than\n * a link to nowhere.\n */\nexport function useEmailsHubPath(): string | null {\n return useConsoleHubPath('emails')\n}\n\n/**\n * The MARKETING console's URL under the site being read.\n *\n * Named for the same reason the Emails one is, from the opposite direction: a\n * converted record — a form submission in the Inbox, a contact in Contacts —\n * links to the campaign it came from, and those surfaces are handed their own\n * hub's `basePath`, not this one's.\n */\nexport function useMarketingHubPath(): string | null {\n return useConsoleHubPath('marketing')\n}\n\n/**\n * Any sibling hub's URL, by plugin slug.\n *\n * One implementation, because \"read the org slug and the subdomain off the\n * route rather than resolving the host document\" is the property that keeps\n * these links free, and a second copy of it is where somebody pays for the\n * two `getDoc`s instead.\n */\nexport function useConsoleHubPath(pluginSlug: string): string | null {\n const params = useParams<{ orgSlug: string; host: string }>()\n const orgSlug = params?.orgSlug\n const host = params?.host\n if (!orgSlug || !host) return null\n return buildRoute(Route.HOST_PLUGIN, { orgSlug, host, pluginSlug })\n}\n\nexport default useEmailsHubPath\n"],"names":["buildRoute","Route","useParams","useEmailsHubPath","useConsoleHubPath","useMarketingHubPath","pluginSlug","params","orgSlug","host","HOST_PLUGIN"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA,SAASA,UAAU,EAAEC,KAAK,QAAQ,eAAc;AAChD,SAASC,SAAS,QAAQ,kBAAiB;AAE3C;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC;IACd,OAAOC,kBAAkB;AAC3B;AAEA;;;;;;;CAOC,GACD,OAAO,SAASC;IACd,OAAOD,kBAAkB;AAC3B;AAEA;;;;;;;CAOC,GACD,OAAO,SAASA,kBAAkBE,UAAkB;IAClD,MAAMC,SAASL;IACf,MAAMM,UAAUD,0BAAAA,OAAQC,OAAO;IAC/B,MAAMC,OAAOF,0BAAAA,OAAQE,IAAI;IACzB,IAAI,CAACD,WAAW,CAACC,MAAM,OAAO;IAC9B,OAAOT,WAAWC,MAAMS,WAAW,EAAE;QAAEF;QAASC;QAAMH;IAAW;AACnE;AAEA,eAAeH,iBAAgB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/use-emails-hub-path.ts"],"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, Route } from '@aglyn/aglyn'\nimport { useParams } from 'next/navigation'\n\n/**\n * The Emails console's URL under the site being read.\n *\n * A campaign's page links to two records it does not own — the message that\n * was sent inside it, and the template that message was built from — and both\n * keep their pages on the Emails console. The shell hands a plugin page only\n * its OWN `basePath`, so the sibling hub is named here by its SLUG rather\n * than by editing the last segment off the current path: a slug is a string\n * this file can be searched for when a surface moves, and string surgery on\n * somebody else's path is a link that keeps resolving to the wrong place.\n *\n * Free, deliberately. The org slug and the subdomain are already in the URL\n * the console is on, so this reads the route rather than resolving the host\n * document — the two `getDoc`s that resolution costs would be paid on every\n * open of a campaign, to render a link.\n *\n * `null` before the params resolve, so callers render plain text rather than\n * a link to nowhere — and on the org Marketing hub, where there is no site\n * and so no Emails console; a card there builds its message links from the\n * org mount instead.\n */\nexport function useEmailsHubPath(): string | null {\n return useConsoleHubPath('emails')\n}\n\n/**\n * The MARKETING console's URL: under the site being read, or the\n * organization's own Marketing hub on a page that names no site.\n *\n * Named for the same reason the Emails one is, from the opposite direction: a\n * converted record — a form submission in the Inbox, a contact in Contacts —\n * links to the campaign it came from, and those surfaces are handed their own\n * hub's `basePath`, not this one's.\n *\n * Unlike the Emails hub it has an answer with no site in the URL. Campaigns\n * belong to the organization, so the org-level CRM and the org Marketing hub\n * itself link a campaign to `/[orgSlug]/marketing/campaigns/{id}`.\n */\nexport function useMarketingHubPath(): string | null {\n const params = useParams<{ orgSlug: string; host: string }>()\n const orgSlug = params?.orgSlug\n if (!orgSlug) return null\n return params?.host\n ? buildRoute(Route.HOST_PLUGIN, {\n orgSlug,\n host: params.host,\n pluginSlug: MARKETING_SLUG,\n })\n : buildRoute(Route.ORG_PLUGIN, { orgSlug, pluginSlug: MARKETING_SLUG })\n}\n\n/** The Marketing hub's URL slug, under a site and under the org alike. */\nconst MARKETING_SLUG = 'marketing'\n\n/**\n * Any sibling hub's URL under the site being read, by plugin slug.\n *\n * One implementation, because \"read the org slug and the subdomain off the\n * route rather than resolving the host document\" is the property that keeps\n * these links free, and a second copy of it is where somebody pays for the\n * two `getDoc`s instead.\n *\n * `null` on a page that names no site: a site's hub is the only thing this\n * builds.\n */\nexport function useConsoleHubPath(pluginSlug: string): string | null {\n const params = useParams<{ orgSlug: string; host: string }>()\n const orgSlug = params?.orgSlug\n const host = params?.host\n if (!orgSlug || !host) return null\n return buildRoute(Route.HOST_PLUGIN, { orgSlug, host, pluginSlug })\n}\n\nexport default useEmailsHubPath\n"],"names":["buildRoute","Route","useParams","useEmailsHubPath","useConsoleHubPath","useMarketingHubPath","params","orgSlug","host","HOST_PLUGIN","pluginSlug","MARKETING_SLUG","ORG_PLUGIN"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA,SAASA,UAAU,EAAEC,KAAK,QAAQ,eAAc;AAChD,SAASC,SAAS,QAAQ,kBAAiB;AAE3C;;;;;;;;;;;;;;;;;;;;CAoBC,GACD,OAAO,SAASC;IACd,OAAOC,kBAAkB;AAC3B;AAEA;;;;;;;;;;;;CAYC,GACD,OAAO,SAASC;IACd,MAAMC,SAASJ;IACf,MAAMK,UAAUD,0BAAAA,OAAQC,OAAO;IAC/B,IAAI,CAACA,SAAS,OAAO;IACrB,OAAOD,CAAAA,0BAAAA,OAAQE,IAAI,IACfR,WAAWC,MAAMQ,WAAW,EAAE;QAC5BF;QACAC,MAAMF,OAAOE,IAAI;QACjBE,YAAYC;IACd,KACAX,WAAWC,MAAMW,UAAU,EAAE;QAAEL;QAASG,YAAYC;IAAe;AACzE;AAEA,wEAAwE,GACxE,MAAMA,iBAAiB;AAEvB;;;;;;;;;;CAUC,GACD,OAAO,SAASP,kBAAkBM,UAAkB;IAClD,MAAMJ,SAASJ;IACf,MAAMK,UAAUD,0BAAAA,OAAQC,OAAO;IAC/B,MAAMC,OAAOF,0BAAAA,OAAQE,IAAI;IACzB,IAAI,CAACD,WAAW,CAACC,MAAM,OAAO;IAC9B,OAAOR,WAAWC,MAAMQ,WAAW,EAAE;QAAEF;QAASC;QAAME;IAAW;AACnE;AAEA,eAAeP,iBAAgB"}
@@ -1,21 +1,24 @@
1
1
  /**
2
- * The Marketing console's URL under the site being read.
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
3
4
  *
4
- * Three surfaces here name a CAMPAIGN — a message's own page, a template's
5
- * messages table, and the emails list's row menu — and a campaign's pages
6
- * belong to the Marketing console. The shell hands a plugin page only its OWN
7
- * `basePath`, so the sibling hub is named here by its SLUG rather than by
8
- * editing the last segment off the current path: a slug is a string this file
9
- * can be searched for when a surface moves, and string surgery on somebody
10
- * else's path is a link that keeps resolving to the wrong place.
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
11
8
  *
12
- * Free, deliberately. The org slug and the subdomain are already in the URL
13
- * the console is on, so this reads the route rather than resolving the host
14
- * document — the two `getDoc`s that resolution costs would be paid on every
15
- * open of every email, to render one link.
9
+ * http://www.apache.org/licenses/LICENSE-2.0
16
10
  *
17
- * `null` before the params resolve, so callers render plain text rather than
18
- * a link to nowhere.
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.
19
16
  */
20
- export declare function useMarketingHubPath(): string | null;
21
- export default useMarketingHubPath;
17
+ /**
18
+ * The Marketing console's URL, re-exported under the name the cards import.
19
+ *
20
+ * One implementation lives beside the Emails hub's in `use-emails-hub-path`;
21
+ * this module is the address the Marketing cards have always imported it
22
+ * from, kept so neither name can drift into a second copy of the rule.
23
+ */
24
+ export { useMarketingHubPath, useMarketingHubPath as default } from './use-emails-hub-path';
@@ -13,38 +13,12 @@
13
13
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
- */ 'use client';
17
- import { buildRoute, Route } from "@aglyn/aglyn";
18
- import { useParams } from "next/navigation";
19
- /**
20
- * The Marketing console's URL under the site being read.
21
- *
22
- * Three surfaces here name a CAMPAIGN — a message's own page, a template's
23
- * messages table, and the emails list's row menu — and a campaign's pages
24
- * belong to the Marketing console. The shell hands a plugin page only its OWN
25
- * `basePath`, so the sibling hub is named here by its SLUG rather than by
26
- * editing the last segment off the current path: a slug is a string this file
27
- * can be searched for when a surface moves, and string surgery on somebody
28
- * else's path is a link that keeps resolving to the wrong place.
29
- *
30
- * Free, deliberately. The org slug and the subdomain are already in the URL
31
- * the console is on, so this reads the route rather than resolving the host
32
- * document — the two `getDoc`s that resolution costs would be paid on every
33
- * open of every email, to render one link.
16
+ */ /**
17
+ * The Marketing console's URL, re-exported under the name the cards import.
34
18
  *
35
- * `null` before the params resolve, so callers render plain text rather than
36
- * a link to nowhere.
37
- */ export function useMarketingHubPath() {
38
- const params = useParams();
39
- const orgSlug = params == null ? void 0 : params.orgSlug;
40
- const host = params == null ? void 0 : params.host;
41
- if (!orgSlug || !host) return null;
42
- return buildRoute(Route.HOST_PLUGIN, {
43
- orgSlug,
44
- host,
45
- pluginSlug: 'marketing'
46
- });
47
- }
48
- export default useMarketingHubPath;
19
+ * One implementation lives beside the Emails hub's in `use-emails-hub-path`;
20
+ * this module is the address the Marketing cards have always imported it
21
+ * from, kept so neither name can drift into a second copy of the rule.
22
+ */ export { useMarketingHubPath, useMarketingHubPath as default } from "./use-emails-hub-path.js";
49
23
 
50
24
  //# sourceMappingURL=use-marketing-hub-path.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/use-marketing-hub-path.ts"],"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, Route } from '@aglyn/aglyn'\nimport { useParams } from 'next/navigation'\n\n/**\n * The Marketing console's URL under the site being read.\n *\n * Three surfaces here name a CAMPAIGN — a message's own page, a template's\n * messages table, and the emails list's row menu — and a campaign's pages\n * belong to the Marketing console. The shell hands a plugin page only its OWN\n * `basePath`, so the sibling hub is named here by its SLUG rather than by\n * editing the last segment off the current path: a slug is a string this file\n * can be searched for when a surface moves, and string surgery on somebody\n * else's path is a link that keeps resolving to the wrong place.\n *\n * Free, deliberately. The org slug and the subdomain are already in the URL\n * the console is on, so this reads the route rather than resolving the host\n * document — the two `getDoc`s that resolution costs would be paid on every\n * open of every email, to render one link.\n *\n * `null` before the params resolve, so callers render plain text rather than\n * a link to nowhere.\n */\nexport function useMarketingHubPath(): string | null {\n const params = useParams<{ orgSlug: string; host: string }>()\n const orgSlug = params?.orgSlug\n const host = params?.host\n if (!orgSlug || !host) return null\n return buildRoute(Route.HOST_PLUGIN, {\n orgSlug,\n host,\n pluginSlug: 'marketing',\n })\n}\n\nexport default useMarketingHubPath\n"],"names":["buildRoute","Route","useParams","useMarketingHubPath","params","orgSlug","host","HOST_PLUGIN","pluginSlug"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;AAEA,SAASA,UAAU,EAAEC,KAAK,QAAQ,eAAc;AAChD,SAASC,SAAS,QAAQ,kBAAiB;AAE3C;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC;IACd,MAAMC,SAASF;IACf,MAAMG,UAAUD,0BAAAA,OAAQC,OAAO;IAC/B,MAAMC,OAAOF,0BAAAA,OAAQE,IAAI;IACzB,IAAI,CAACD,WAAW,CAACC,MAAM,OAAO;IAC9B,OAAON,WAAWC,MAAMM,WAAW,EAAE;QACnCF;QACAC;QACAE,YAAY;IACd;AACF;AAEA,eAAeL,oBAAmB"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/use-marketing-hub-path.ts"],"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/**\n * The Marketing console's URL, re-exported under the name the cards import.\n *\n * One implementation lives beside the Emails hub's in `use-emails-hub-path`;\n * this module is the address the Marketing cards have always imported it\n * from, kept so neither name can drift into a second copy of the rule.\n */\nexport { useMarketingHubPath, useMarketingHubPath as default } from './use-emails-hub-path'\n"],"names":["useMarketingHubPath","default"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;;;;;CAMC,GACD,SAASA,mBAAmB,EAAEA,uBAAuBC,OAAO,QAAQ,2BAAuB"}
@@ -28,18 +28,25 @@ import { BUNDLE_ID } from "../constants/bundle-common.js";
28
28
  * calls the registrar directly.
29
29
  */ export function registerMarketingRecordRoutes() {
30
30
  registerPluginRecordRoute('campaign', {
31
- // Campaigns belong to one site, so the organization has no address.
32
- list: (context)=>context.host ? `${hub(context.orgSlug, context.host)}/campaigns` : null,
33
- record: (context, id)=>context.host ? `${hub(context.orgSlug, context.host)}/campaigns/${id}` : null
31
+ /*
32
+ * Campaigns belong to the organization, so both levels have an address:
33
+ * a site's hub lists the campaigns placed on it, and the org hub lists
34
+ * every one. A caller on a site page is sent to that site's copy of the
35
+ * page so it keeps the site's rail and trail around it.
36
+ */ list: (context)=>`${hub(context.orgSlug, context.host)}/campaigns`,
37
+ record: (context, id)=>`${hub(context.orgSlug, context.host)}/campaigns/${id}`
34
38
  }, {
35
39
  pluginId: BUNDLE_ID
36
40
  });
37
41
  }
38
42
  function hub(orgSlug, host) {
39
- return buildRoute(Route.HOST_PLUGIN, {
43
+ return host ? buildRoute(Route.HOST_PLUGIN, {
40
44
  orgSlug,
41
45
  host,
42
46
  pluginSlug: MARKETING_SLUG
47
+ }) : buildRoute(Route.ORG_PLUGIN, {
48
+ orgSlug,
49
+ pluginSlug: MARKETING_SLUG
43
50
  });
44
51
  }
45
52
 
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/model/marketing-record-routes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\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 * http://www.apache.org/licenses/LICENSE-2.0\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 */\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { registerPluginRecordRoute } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * Where a campaign is read, published for every other surface.\n *\n * A message's template lists the campaigns its sends belonged to, and a\n * campaign's pages are this plugin's. A surface elsewhere asks the\n * record-route registry for `campaign` rather than spelling this plugin's nav\n * slug, and gets `null` — text instead of a link — where this plugin is not\n * loaded.\n */\n\n/** The nav slug the shell resolves this plugin's hub by. */\nconst MARKETING_SLUG = 'marketing'\n\n/**\n * Called from the console registrar. The owner is named rather than read off\n * the loader's marker, so the route registers the same way under a spec that\n * calls the registrar directly.\n */\nexport function registerMarketingRecordRoutes(): void {\n registerPluginRecordRoute(\n 'campaign',\n {\n // Campaigns belong to one site, so the organization has no address.\n list: (context) =>\n context.host\n ? `${hub(context.orgSlug, context.host)}/campaigns`\n : null,\n record: (context, id) =>\n context.host\n ? `${hub(context.orgSlug, context.host)}/campaigns/${id}`\n : null,\n },\n { pluginId: BUNDLE_ID },\n )\n}\n\nfunction hub(orgSlug: string, host: string): string {\n return buildRoute(Route.HOST_PLUGIN, {\n orgSlug,\n host,\n pluginSlug: MARKETING_SLUG,\n })\n}\n"],"names":["buildRoute","Route","registerPluginRecordRoute","BUNDLE_ID","MARKETING_SLUG","registerMarketingRecordRoutes","list","context","host","hub","orgSlug","record","id","pluginId","HOST_PLUGIN","pluginSlug"],"mappings":"AAAA;;;;;;;;;;;;CAYC,GACD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SAASC,yBAAyB,QAAQ,mDAAkD;AAC5F,SAASC,SAAS,QAAQ,gCAA4B;AAEtD;;;;;;;;CAQC,GAED,0DAA0D,GAC1D,MAAMC,iBAAiB;AAEvB;;;;CAIC,GACD,OAAO,SAASC;IACdH,0BACE,YACA;QACE,oEAAoE;QACpEI,MAAM,CAACC,UACLA,QAAQC,IAAI,GACR,GAAGC,IAAIF,QAAQG,OAAO,EAAEH,QAAQC,IAAI,EAAE,UAAU,CAAC,GACjD;QACNG,QAAQ,CAACJ,SAASK,KAChBL,QAAQC,IAAI,GACR,GAAGC,IAAIF,QAAQG,OAAO,EAAEH,QAAQC,IAAI,EAAE,WAAW,EAAEI,IAAI,GACvD;IACR,GACA;QAAEC,UAAUV;IAAU;AAE1B;AAEA,SAASM,IAAIC,OAAe,EAAEF,IAAY;IACxC,OAAOR,WAAWC,MAAMa,WAAW,EAAE;QACnCJ;QACAF;QACAO,YAAYX;IACd;AACF"}
1
+ {"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/model/marketing-record-routes.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\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 * http://www.apache.org/licenses/LICENSE-2.0\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 */\nimport { buildRoute, Route } from '@aglyn/aglyn/app-utils/console-routes'\nimport { registerPluginRecordRoute } from '@aglyn/aglyn/plugin-manager/plugin-record-routes'\nimport { BUNDLE_ID } from '../constants/bundle-common'\n\n/**\n * Where a campaign is read, published for every other surface.\n *\n * A message's template lists the campaigns its sends belonged to, and a\n * campaign's pages are this plugin's. A surface elsewhere asks the\n * record-route registry for `campaign` rather than spelling this plugin's nav\n * slug, and gets `null` — text instead of a link — where this plugin is not\n * loaded.\n */\n\n/** The nav slug the shell resolves this plugin's hub by. */\nconst MARKETING_SLUG = 'marketing'\n\n/**\n * Called from the console registrar. The owner is named rather than read off\n * the loader's marker, so the route registers the same way under a spec that\n * calls the registrar directly.\n */\nexport function registerMarketingRecordRoutes(): void {\n registerPluginRecordRoute(\n 'campaign',\n {\n /*\n * Campaigns belong to the organization, so both levels have an address:\n * a site's hub lists the campaigns placed on it, and the org hub lists\n * every one. A caller on a site page is sent to that site's copy of the\n * page so it keeps the site's rail and trail around it.\n */\n list: (context) => `${hub(context.orgSlug, context.host)}/campaigns`,\n record: (context, id) =>\n `${hub(context.orgSlug, context.host)}/campaigns/${id}`,\n },\n { pluginId: BUNDLE_ID },\n )\n}\n\nfunction hub(orgSlug: string, host: string | null): string {\n return host\n ? buildRoute(Route.HOST_PLUGIN, {\n orgSlug,\n host,\n pluginSlug: MARKETING_SLUG,\n })\n : buildRoute(Route.ORG_PLUGIN, { orgSlug, pluginSlug: MARKETING_SLUG })\n}\n"],"names":["buildRoute","Route","registerPluginRecordRoute","BUNDLE_ID","MARKETING_SLUG","registerMarketingRecordRoutes","list","context","hub","orgSlug","host","record","id","pluginId","HOST_PLUGIN","pluginSlug","ORG_PLUGIN"],"mappings":"AAAA;;;;;;;;;;;;CAYC,GACD,SAASA,UAAU,EAAEC,KAAK,QAAQ,wCAAuC;AACzE,SAASC,yBAAyB,QAAQ,mDAAkD;AAC5F,SAASC,SAAS,QAAQ,gCAA4B;AAEtD;;;;;;;;CAQC,GAED,0DAA0D,GAC1D,MAAMC,iBAAiB;AAEvB;;;;CAIC,GACD,OAAO,SAASC;IACdH,0BACE,YACA;QACE;;;;;OAKC,GACDI,MAAM,CAACC,UAAY,GAAGC,IAAID,QAAQE,OAAO,EAAEF,QAAQG,IAAI,EAAE,UAAU,CAAC;QACpEC,QAAQ,CAACJ,SAASK,KAChB,GAAGJ,IAAID,QAAQE,OAAO,EAAEF,QAAQG,IAAI,EAAE,WAAW,EAAEE,IAAI;IAC3D,GACA;QAAEC,UAAUV;IAAU;AAE1B;AAEA,SAASK,IAAIC,OAAe,EAAEC,IAAmB;IAC/C,OAAOA,OACHV,WAAWC,MAAMa,WAAW,EAAE;QAC5BL;QACAC;QACAK,YAAYX;IACd,KACAJ,WAAWC,MAAMe,UAAU,EAAE;QAAEP;QAASM,YAAYX;IAAe;AACzE"}
package/src/lib/plugin.js CHANGED
@@ -19,7 +19,7 @@ import { mdiBullhornOutline } from "@aglyn/shared-data-mdi";
19
19
  import { lazy } from "react";
20
20
  import { CAMPAIGN_DESIGN_CREATE_ZONE, CAMPAIGN_DESIGN_PREVIEW_ZONE, CAMPAIGN_SENDER_EDITOR_ZONE, CAMPAIGN_TOPIC_OPTIONS_ZONE, CAMPAIGN_TOPIC_SELECT_ZONE } from "./components/campaign-email-zones.js";
21
21
  import { EXPERIMENT_RESULT_ZONE, EXPERIMENT_VARIANTS_ZONE } from "./components/experiment-zones.js";
22
- import { MARKETING_CONSOLE_SECTIONS } from "./components/marketing-console-sections.js";
22
+ import { MARKETING_CONSOLE_SECTIONS, MARKETING_ORG_CONSOLE_SECTIONS } from "./components/marketing-console-sections.js";
23
23
  import { BUNDLE_ID } from "./constants/bundle-common.js";
24
24
  import { registerMarketingRecordRoutes } from "./model/marketing-record-routes.js";
25
25
  /** Code-split: the Marketing console page only loads when opened. */ const MarketingConsolePage = lazy(()=>import("./components/marketing-console-page.js"));
@@ -160,6 +160,36 @@ import { registerMarketingRecordRoutes } from "./model/marketing-record-routes.j
160
160
  },
161
161
  Component: MarketingConsolePage
162
162
  }
163
+ ],
164
+ /*
165
+ * The ORGANIZATION's Marketing hub, at `/[orgSlug]/marketing`. Campaigns,
166
+ * their emails and their sequence rollups belong to the organization, so
167
+ * this is where one campaign is placed across several sites and where
168
+ * every site's mail is read at once. The page is the same component;
169
+ * handed no site, it renders the org sections.
170
+ *
171
+ * It carries the SITE tab's id on purpose, the way the org CRM tab does:
172
+ * `release_marketing` names `nav-tab-marketing`, so one flag holds both
173
+ * halves of the surface, and the org half cannot ship while the site half
174
+ * is switched off.
175
+ */ orgNavItems: [
176
+ {
177
+ label: 'Marketing',
178
+ href: '/marketing',
179
+ sections: MARKETING_ORG_CONSOLE_SECTIONS,
180
+ navTabId: 'nav-tab-marketing',
181
+ icon: {
182
+ path: mdiBullhornOutline.path
183
+ },
184
+ header: {
185
+ title: 'Marketing',
186
+ icon: {
187
+ path: mdiBullhornOutline.path
188
+ },
189
+ docsTopic: 'emailCampaigns'
190
+ },
191
+ Component: MarketingConsolePage
192
+ }
163
193
  ]
164
194
  });
165
195
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../../../libs/plugins/marketing/src/lib/plugin.ts"],"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\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n registerPluginZone,\n type PluginZone,\n} from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { mdiBullhornOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport {\n CAMPAIGN_DESIGN_CREATE_ZONE,\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n CAMPAIGN_SENDER_EDITOR_ZONE,\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n CAMPAIGN_TOPIC_SELECT_ZONE,\n} from './components/campaign-email-zones'\nimport {\n EXPERIMENT_RESULT_ZONE,\n EXPERIMENT_VARIANTS_ZONE,\n} from './components/experiment-zones'\nimport { MARKETING_CONSOLE_SECTIONS } from './components/marketing-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { registerMarketingRecordRoutes } from './model/marketing-record-routes'\n\n/** Code-split: the Marketing console page only loads when opened. */\nconst MarketingConsolePage = lazy(\n () => import('./components/marketing-console-page'),\n)\n\n/** Dashboard glance card, loaded only where the shell renders the slot. */\nconst CampaignGlanceCard = lazy(\n () => import('./components/campaign-glance-card.component'),\n)\n\n/** The site's campaigns, drawn in the Inbox's Campaigns section. */\nconst HostCampaignsCard = lazy(() => import('./components/campaigns-card'))\n\n/** The Emails page's Messages section: the list, one message, its composer. */\nconst EmailMessagesWidget = lazy(\n () => import('./components/email-messages-widget'),\n)\n\n/** Who received the sends built from one template, on the template's page. */\nconst EmailRecipientsCard = lazy(\n () => import('./components/email-recipients-card'),\n)\n\n/** Loaded where a record page draws one of the attribution zones. */\nconst RecordAttributionWidget = lazy(\n () => import('./components/record-attribution-widget'),\n)\n\n/**\n * Marketing feature plugin (AGL-395). Console-only — overlays and popups\n * render on published sites through the tenant runtime, not a canvas\n * element of their own, so there is no UI bundle. The console half declares\n * the Marketing nav + page through the ConsoleExtension registry (always-on;\n * the surface itself is not release-flagged — its overlays/A-B cards run\n * their own per-plan checks off the passed `org`). The popup image picker\n * uses the shell's media browser via `useMediaPicker`.\n */\nexport function registerMarketingConsole(): void {\n /*\n * The two positions the A/B testing card hosts (AGL-2914), declared before\n * the extension that draws the card. An id another plugin has already taken\n * is refused, naming both. What each zone hands a widget is carried on its\n * token, in `components/experiment-zones`, so a widget elsewhere is written\n * against the same shape without importing this plugin.\n */\n registerPluginZone(\n {\n zone: EXPERIMENT_VARIANTS_ZONE,\n label: 'A/B test variants',\n surface: 'console',\n description:\n 'In the experiment editor, beside the variants. A widget here proposes copy for the variants the editor holds; the dialog’s Save is the write, and nothing a widget returns starts or changes a test.',\n },\n // Named rather than left to the loader's marker, because this function is\n // also called directly — by a spec, and by an app that loads the plugin\n // without the loader — and a zone with no owner is refused.\n { pluginId: BUNDLE_ID },\n )\n registerPluginZone(\n {\n zone: EXPERIMENT_RESULT_ZONE,\n label: 'A/B test result',\n surface: 'console',\n description:\n 'Below the results of one test. A widget here explains what the figures show. There is nothing to apply, and a widget may not declare a winner the card has not.',\n },\n { pluginId: BUNDLE_ID },\n )\n /*\n * What a campaign email asks of whichever plugin keeps the mail itself —\n * the topic catalog, the sending identities, the design document and its\n * renderer. Each is a zone the composer or a message's page hosts; see\n * `components/campaign-email-zones` for what each hands a widget. All are\n * `bare`: a widget there is a field, a button, a drawer or nothing at all,\n * and the stack's spacing around it would be a gap in a form.\n */\n const bareZone = <Props,>(\n zone: PluginZone<Props>,\n label: string,\n description: string,\n ) =>\n registerPluginZone(\n { zone, label, description, surface: 'console', layout: 'bare' },\n { pluginId: BUNDLE_ID },\n )\n bareZone(\n CAMPAIGN_TOPIC_SELECT_ZONE,\n 'An email’s topic',\n 'In the campaign composer’s field stack. A widget here is the picker for the stream the email belongs to: it is handed the chosen id and reports a new one through `onChange`. The composer records it; the widget writes nothing.',\n )\n bareZone(\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n 'The topics a campaign can open on',\n 'Beside the drawers that create and edit a campaign. A widget here draws nothing: while `enabled` it reads the streams a recipient can still leave and answers through `onTopics`.',\n )\n bareZone(\n CAMPAIGN_SENDER_EDITOR_ZONE,\n 'Add a sender while composing',\n 'Mounted by the campaign composer once its author asks to add a sender. A widget here is that editor, open in its add mode; `onSaved` names the sender it created.',\n )\n bareZone(\n CAMPAIGN_DESIGN_CREATE_ZONE,\n 'A design for one email',\n 'In the campaign composer, beside the design picker. A widget here is the control that mints a design document and names it through `onCreated`; the composer records the choice on the campaign and opens the editor.',\n )\n bareZone(\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n 'A sent email as an inbox receives it',\n 'On one message’s page. A widget here renders the stored design, or the plain-text body, through the renderer the send path uses.',\n )\n registerMarketingRecordRoutes()\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Marketing',\n /*\n * The host dashboard's `Last campaign` card (AGL-433). A widget rather\n * than a direct import by the dashboard page, so it appears only on\n * workspaces that have the plugin that owns the campaign history — a card\n * about campaigns on a console with no campaigns page could only ever be\n * blank, and its header links straight into `/marketing/campaigns`.\n */\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n // The id a reader's dashboard preferences already name — it is what\n // `isDashboardWidgetHidden` and the customize order are keyed on, so\n // it is persisted and does not follow the plugin.\n widgetId: 'email-campaign-glance',\n title: 'Last campaign',\n Component: CampaignGlanceCard,\n },\n // Which campaign or link brought a person, drawn where the plugins that\n // keep and list people host a zone for it. They hand over what the\n // record is and its id; neither imports this plugin.\n {\n slot: 'crmRecordAttribution',\n widgetId: 'marketing-crm-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n // The Emails page hosts its Messages section as a zone: a message is\n // one send of a campaign, and every action on it is this plugin's route.\n {\n slot: 'emailMessages',\n widgetId: 'marketing-email-messages',\n title: 'Messages',\n Component: EmailMessagesWidget,\n },\n {\n slot: 'emailTemplateRecipients',\n widgetId: 'marketing-email-template-recipients',\n title: 'Recipients',\n Component: EmailRecipientsCard,\n },\n // The Inbox's Campaigns tab is a zone; this is the card that fills it.\n {\n slot: 'inboxCampaigns',\n widgetId: 'marketing-inbox-campaigns',\n title: 'Campaigns',\n Component: HostCampaignsCard,\n },\n {\n slot: 'inboxRecordAttribution',\n widgetId: 'marketing-inbox-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n ],\n navItems: [\n {\n label: 'Marketing',\n href: '/marketing',\n // Sections as ROUTES (AGL-2501): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read.\n sections: MARKETING_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-marketing',\n icon: { path: mdiBullhornOutline.path },\n header: {\n title: 'Marketing',\n icon: { path: mdiBullhornOutline.path },\n docsTopic: 'marketingOverlays',\n },\n Component: MarketingConsolePage,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","registerPluginZone","mdiBullhornOutline","lazy","CAMPAIGN_DESIGN_CREATE_ZONE","CAMPAIGN_DESIGN_PREVIEW_ZONE","CAMPAIGN_SENDER_EDITOR_ZONE","CAMPAIGN_TOPIC_OPTIONS_ZONE","CAMPAIGN_TOPIC_SELECT_ZONE","EXPERIMENT_RESULT_ZONE","EXPERIMENT_VARIANTS_ZONE","MARKETING_CONSOLE_SECTIONS","BUNDLE_ID","registerMarketingRecordRoutes","MarketingConsolePage","CampaignGlanceCard","HostCampaignsCard","EmailMessagesWidget","EmailRecipientsCard","RecordAttributionWidget","registerMarketingConsole","zone","label","surface","description","pluginId","bareZone","layout","registerConsoleExtension","displayName","widgets","slot","CONSOLE_WIDGET_SLOTS","hostDashboard","widgetId","title","Component","navItems","href","sections","navTabId","icon","path","header","docsTopic"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../../../libs/plugins/marketing/src/lib/plugin.ts"],"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\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n registerPluginZone,\n type PluginZone,\n} from '@aglyn/aglyn/plugin-manager/plugin-zones'\nimport { mdiBullhornOutline } from '@aglyn/shared-data-mdi'\nimport { lazy } from 'react'\nimport {\n CAMPAIGN_DESIGN_CREATE_ZONE,\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n CAMPAIGN_SENDER_EDITOR_ZONE,\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n CAMPAIGN_TOPIC_SELECT_ZONE,\n} from './components/campaign-email-zones'\nimport {\n EXPERIMENT_RESULT_ZONE,\n EXPERIMENT_VARIANTS_ZONE,\n} from './components/experiment-zones'\nimport {\n MARKETING_CONSOLE_SECTIONS,\n MARKETING_ORG_CONSOLE_SECTIONS,\n} from './components/marketing-console-sections'\nimport { BUNDLE_ID } from './constants/bundle-common'\nimport { registerMarketingRecordRoutes } from './model/marketing-record-routes'\n\n/** Code-split: the Marketing console page only loads when opened. */\nconst MarketingConsolePage = lazy(\n () => import('./components/marketing-console-page'),\n)\n\n/** Dashboard glance card, loaded only where the shell renders the slot. */\nconst CampaignGlanceCard = lazy(\n () => import('./components/campaign-glance-card.component'),\n)\n\n/** The site's campaigns, drawn in the Inbox's Campaigns section. */\nconst HostCampaignsCard = lazy(() => import('./components/campaigns-card'))\n\n/** The Emails page's Messages section: the list, one message, its composer. */\nconst EmailMessagesWidget = lazy(\n () => import('./components/email-messages-widget'),\n)\n\n/** Who received the sends built from one template, on the template's page. */\nconst EmailRecipientsCard = lazy(\n () => import('./components/email-recipients-card'),\n)\n\n/** Loaded where a record page draws one of the attribution zones. */\nconst RecordAttributionWidget = lazy(\n () => import('./components/record-attribution-widget'),\n)\n\n/**\n * Marketing feature plugin (AGL-395). Console-only — overlays and popups\n * render on published sites through the tenant runtime, not a canvas\n * element of their own, so there is no UI bundle. The console half declares\n * the Marketing nav + page through the ConsoleExtension registry (always-on;\n * the surface itself is not release-flagged — its overlays/A-B cards run\n * their own per-plan checks off the passed `org`). The popup image picker\n * uses the shell's media browser via `useMediaPicker`.\n */\nexport function registerMarketingConsole(): void {\n /*\n * The two positions the A/B testing card hosts (AGL-2914), declared before\n * the extension that draws the card. An id another plugin has already taken\n * is refused, naming both. What each zone hands a widget is carried on its\n * token, in `components/experiment-zones`, so a widget elsewhere is written\n * against the same shape without importing this plugin.\n */\n registerPluginZone(\n {\n zone: EXPERIMENT_VARIANTS_ZONE,\n label: 'A/B test variants',\n surface: 'console',\n description:\n 'In the experiment editor, beside the variants. A widget here proposes copy for the variants the editor holds; the dialog’s Save is the write, and nothing a widget returns starts or changes a test.',\n },\n // Named rather than left to the loader's marker, because this function is\n // also called directly — by a spec, and by an app that loads the plugin\n // without the loader — and a zone with no owner is refused.\n { pluginId: BUNDLE_ID },\n )\n registerPluginZone(\n {\n zone: EXPERIMENT_RESULT_ZONE,\n label: 'A/B test result',\n surface: 'console',\n description:\n 'Below the results of one test. A widget here explains what the figures show. There is nothing to apply, and a widget may not declare a winner the card has not.',\n },\n { pluginId: BUNDLE_ID },\n )\n /*\n * What a campaign email asks of whichever plugin keeps the mail itself —\n * the topic catalog, the sending identities, the design document and its\n * renderer. Each is a zone the composer or a message's page hosts; see\n * `components/campaign-email-zones` for what each hands a widget. All are\n * `bare`: a widget there is a field, a button, a drawer or nothing at all,\n * and the stack's spacing around it would be a gap in a form.\n */\n const bareZone = <Props,>(\n zone: PluginZone<Props>,\n label: string,\n description: string,\n ) =>\n registerPluginZone(\n { zone, label, description, surface: 'console', layout: 'bare' },\n { pluginId: BUNDLE_ID },\n )\n bareZone(\n CAMPAIGN_TOPIC_SELECT_ZONE,\n 'An email’s topic',\n 'In the campaign composer’s field stack. A widget here is the picker for the stream the email belongs to: it is handed the chosen id and reports a new one through `onChange`. The composer records it; the widget writes nothing.',\n )\n bareZone(\n CAMPAIGN_TOPIC_OPTIONS_ZONE,\n 'The topics a campaign can open on',\n 'Beside the drawers that create and edit a campaign. A widget here draws nothing: while `enabled` it reads the streams a recipient can still leave and answers through `onTopics`.',\n )\n bareZone(\n CAMPAIGN_SENDER_EDITOR_ZONE,\n 'Add a sender while composing',\n 'Mounted by the campaign composer once its author asks to add a sender. A widget here is that editor, open in its add mode; `onSaved` names the sender it created.',\n )\n bareZone(\n CAMPAIGN_DESIGN_CREATE_ZONE,\n 'A design for one email',\n 'In the campaign composer, beside the design picker. A widget here is the control that mints a design document and names it through `onCreated`; the composer records the choice on the campaign and opens the editor.',\n )\n bareZone(\n CAMPAIGN_DESIGN_PREVIEW_ZONE,\n 'A sent email as an inbox receives it',\n 'On one message’s page. A widget here renders the stored design, or the plain-text body, through the renderer the send path uses.',\n )\n registerMarketingRecordRoutes()\n Aglyn.registerConsoleExtension({\n pluginId: BUNDLE_ID,\n displayName: 'Marketing',\n /*\n * The host dashboard's `Last campaign` card (AGL-433). A widget rather\n * than a direct import by the dashboard page, so it appears only on\n * workspaces that have the plugin that owns the campaign history — a card\n * about campaigns on a console with no campaigns page could only ever be\n * blank, and its header links straight into `/marketing/campaigns`.\n */\n widgets: [\n {\n slot: Aglyn.CONSOLE_WIDGET_SLOTS.hostDashboard,\n // The id a reader's dashboard preferences already name — it is what\n // `isDashboardWidgetHidden` and the customize order are keyed on, so\n // it is persisted and does not follow the plugin.\n widgetId: 'email-campaign-glance',\n title: 'Last campaign',\n Component: CampaignGlanceCard,\n },\n // Which campaign or link brought a person, drawn where the plugins that\n // keep and list people host a zone for it. They hand over what the\n // record is and its id; neither imports this plugin.\n {\n slot: 'crmRecordAttribution',\n widgetId: 'marketing-crm-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n // The Emails page hosts its Messages section as a zone: a message is\n // one send of a campaign, and every action on it is this plugin's route.\n {\n slot: 'emailMessages',\n widgetId: 'marketing-email-messages',\n title: 'Messages',\n Component: EmailMessagesWidget,\n },\n {\n slot: 'emailTemplateRecipients',\n widgetId: 'marketing-email-template-recipients',\n title: 'Recipients',\n Component: EmailRecipientsCard,\n },\n // The Inbox's Campaigns tab is a zone; this is the card that fills it.\n {\n slot: 'inboxCampaigns',\n widgetId: 'marketing-inbox-campaigns',\n title: 'Campaigns',\n Component: HostCampaignsCard,\n },\n {\n slot: 'inboxRecordAttribution',\n widgetId: 'marketing-inbox-record-attribution',\n title: 'Campaign attribution',\n Component: RecordAttributionWidget,\n },\n ],\n navItems: [\n {\n label: 'Marketing',\n href: '/marketing',\n // Sections as ROUTES (AGL-2501): each is a real URL the shell\n // resolves and gates, so the page mounts the one being read.\n sections: MARKETING_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-marketing',\n icon: { path: mdiBullhornOutline.path },\n header: {\n title: 'Marketing',\n icon: { path: mdiBullhornOutline.path },\n docsTopic: 'marketingOverlays',\n },\n Component: MarketingConsolePage,\n },\n ],\n /*\n * The ORGANIZATION's Marketing hub, at `/[orgSlug]/marketing`. Campaigns,\n * their emails and their sequence rollups belong to the organization, so\n * this is where one campaign is placed across several sites and where\n * every site's mail is read at once. The page is the same component;\n * handed no site, it renders the org sections.\n *\n * It carries the SITE tab's id on purpose, the way the org CRM tab does:\n * `release_marketing` names `nav-tab-marketing`, so one flag holds both\n * halves of the surface, and the org half cannot ship while the site half\n * is switched off.\n */\n orgNavItems: [\n {\n label: 'Marketing',\n href: '/marketing',\n sections: MARKETING_ORG_CONSOLE_SECTIONS,\n navTabId: 'nav-tab-marketing',\n icon: { path: mdiBullhornOutline.path },\n header: {\n title: 'Marketing',\n icon: { path: mdiBullhornOutline.path },\n docsTopic: 'emailCampaigns',\n },\n Component: MarketingConsolePage,\n },\n ],\n })\n}\n\nexport * from './site'\n"],"names":["Aglyn","registerPluginZone","mdiBullhornOutline","lazy","CAMPAIGN_DESIGN_CREATE_ZONE","CAMPAIGN_DESIGN_PREVIEW_ZONE","CAMPAIGN_SENDER_EDITOR_ZONE","CAMPAIGN_TOPIC_OPTIONS_ZONE","CAMPAIGN_TOPIC_SELECT_ZONE","EXPERIMENT_RESULT_ZONE","EXPERIMENT_VARIANTS_ZONE","MARKETING_CONSOLE_SECTIONS","MARKETING_ORG_CONSOLE_SECTIONS","BUNDLE_ID","registerMarketingRecordRoutes","MarketingConsolePage","CampaignGlanceCard","HostCampaignsCard","EmailMessagesWidget","EmailRecipientsCard","RecordAttributionWidget","registerMarketingConsole","zone","label","surface","description","pluginId","bareZone","layout","registerConsoleExtension","displayName","widgets","slot","CONSOLE_WIDGET_SLOTS","hostDashboard","widgetId","title","Component","navItems","href","sections","navTabId","icon","path","header","docsTopic","orgNavItems"],"mappings":"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"}
@@ -14,7 +14,7 @@
14
14
  * See the License for the specific language governing permissions and
15
15
  * limitations under the License.
16
16
  */
17
- import { type PluginApiHandler } from '@aglyn/aglyn/server';
17
+ import { type PluginApiHandler, type PluginApiRequestSubject } from '@aglyn/aglyn/server';
18
18
  import { type PluginResourceDraftWriter } from '@aglyn/aglyn/plugin-manager/plugin-resource-drafts';
19
19
  /** The resource name the campaign draft writer is registered under. */
20
20
  export declare const CAMPAIGN_DRAFT_RESOURCE = "campaign";
@@ -31,7 +31,7 @@ export declare const CAMPAIGN_DRAFT_ROLE_REFUSAL = "Not a site admin or editor";
31
31
  /** The id of the one email inside a drafted campaign. */
32
32
  export declare function campaignDraftEmailId(campaignId: string): string;
33
33
  /** Every field a drafted email is written with, and so every field it can hold. */
34
- export declare const CAMPAIGN_DRAFT_EMAIL_FIELDS: readonly ["subject", "preheader", "templateScreenId", "subjectVariants", "preheaderVariants", "emailCampaignId", "status", "createdAtMs", "draftedAt", "draftedBy"];
34
+ export declare const CAMPAIGN_DRAFT_EMAIL_FIELDS: readonly ["subject", "preheader", "templateScreenId", "subjectVariants", "preheaderVariants", "emailCampaignId", "hostId", "visibleTo", "status", "createdAtMs", "draftedAt", "draftedBy"];
35
35
  /** What a caller sends as `content`. */
36
36
  export interface CampaignDraftContent {
37
37
  /** The email design the email sends: a `kind: 'email'` screen on the site. */
@@ -51,6 +51,8 @@ export declare function readCampaignDraftContent(content: Readonly<Record<string
51
51
  export interface CampaignDraftWriterDeps {
52
52
  /** The Admin SDK handle; specs hand in a double. */
53
53
  firestore?: () => FirebaseFirestore.Firestore;
54
+ /** The site's organization, or null; specs hand in a double. */
55
+ resolveOrgId?: (hostId: string) => Promise<string | null>;
54
56
  }
55
57
  export declare function createCampaignDraftWriter(deps?: CampaignDraftWriterDeps): PluginResourceDraftWriter;
56
58
  export declare const campaignDraftWriter: PluginResourceDraftWriter;
@@ -59,4 +61,31 @@ export declare const campaignDraftWriter: PluginResourceDraftWriter;
59
61
  * runs AI jobs (AGL-3026). Idempotent: a second call replaces the first.
60
62
  */
61
63
  export declare function registerCampaignDraftWriter(): void;
64
+ /**
65
+ * The organization a `campaigns/manage` request from the organization hub is
66
+ * for, read off its JSON body, so the dispatcher's release gate asks about the
67
+ * right organization. Only consulted for a request that names no site.
68
+ * Unverified, exactly as a `hostId` is: the handler refuses anyone who is not
69
+ * an org-wide member of it.
70
+ */
71
+ export declare function campaignManageSubject(request: Request): Promise<PluginApiRequestSubject | null>;
72
+ /**
73
+ * Campaign management API: removing a campaign, discarding a draft, and
74
+ * copying an email.
75
+ *
76
+ * Separate from `campaigns/send` because nothing here sends, reserves
77
+ * allowance or moves a meter, and because the removals are about a record
78
+ * ceasing to exist — which is the one class of change that has to be read
79
+ * against what a merchant has already mailed.
80
+ *
81
+ * Reached through two doors — see the header. A site request carries
82
+ * `hostId` and is authorized by the caller's admin or editor role on that
83
+ * site; an organization request carries `orgId` and no `hostId`, and is
84
+ * authorized by an org-wide membership as owner, admin or editor.
85
+ *
86
+ * Editing a campaign is deliberately NOT here. The container is client-
87
+ * writable, its create already runs on the client SDK, and a second door to
88
+ * the same document would be a second place for the two to disagree about
89
+ * what a campaign may hold.
90
+ */
62
91
  export declare const campaignManageHandler: PluginApiHandler;