@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/site-runtime.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2022 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 * as Aglyn from '@aglyn/aglyn'\nimport { sendAnalyticsBeacon } from '@aglyn/aglyn/app-utils/analytics-beacon'\nimport {\n trackAuthoredEvent,\n trackEvent,\n} from '@aglyn/aglyn/app-utils/analytics-events'\nimport { campaignTouchField } from '@aglyn/aglyn/app-utils/campaign-touch'\nimport { type CSSProperties, useEffect, useRef, useState } from 'react'\nimport type { SiteRuntimeProps } from '@aglyn/aglyn'\nimport * as MarketingModel from '../model'\nimport type {\n AnnouncementBarData,\n ClientAutomation,\n PopupData,\n ScreenExperiment,\n} from '../model/site-contract'\n\n/**\n * Overlay metrics beacon (AGL-200): fire-and-forget, never blocks UX.\n * With an `overlayId` (marketing-hub docs, AGL-271) the collector also\n * increments that overlay's own stats counters.\n */\nfunction sendOverlayBeacon(\n hostId: string | undefined,\n overlay: string,\n overlayId?: string,\n) {\n if (!hostId) return\n // Best-effort, and refused outright from a non-production build or a browser\n // marked ours — the same gate the pageview beacon passes. The GA mirror\n // below is NOT gated here: `trackEvent` carries the taxonomy's own\n // environment check and stamps `traffic_type` rather than dropping the hit.\n sendAnalyticsBeacon({\n hostId,\n overlay,\n ...(overlayId ? { overlayId } : {}),\n })\n // GA mirror (wave v8): sites with Analytics configured see overlay\n // engagement in their own property; no-op without gtag.\n //\n // Through the taxonomy since AGL-1591 rather than raw gtag. `trackEvent`\n // already swallows everything and never throws, so the try/catch this used\n // to need is gone rather than kept as decoration.\n trackEvent('aglyn_overlay', {\n overlay_action: overlay,\n ...(overlayId ? { overlay_id: overlayId } : {}),\n })\n}\n\n/**\n * Site-wide announcement bar (AGL-195). Dismissal is per content-hash in\n * localStorage (no cookies): editing the text re-shows the bar for\n * everyone who hid the old one.\n */\n/**\n * Experiments runner (AGL-253): deterministic per-visitor assignment for\n * the screen's experiments. The first active experiment wins (one per\n * screen); its variant tree — composed server-side per variant version —\n * swaps in via canvas.setNodes on hydration. Exposures beacon on\n * assignment; the conversion listener matches the experiment's goal and\n * beacons once per pageview.\n */\nfunction ExperimentsRunner(props: {\n hostId?: string\n experiments: ScreenExperiment[]\n /** Host consent config (AGL-1498); decides where the visitor id may live. */\n consentHost?: Aglyn.VisitorConsentHost | null\n}) {\n const { hostId, experiments, consentHost } = props\n\n useEffect(() => {\n if (!hostId || !experiments.length) return undefined\n const experiment = experiments[0]\n // Visitor-id persistence honors the consent gate (AGL-1498): without an\n // analytics yes on a consent-gated site, the id lives in sessionStorage —\n // variants stay stable within the visit, and the indefinite identifier\n // is not kept. The id never leaves the browser either way (the beacons\n // below carry no visitor id), which is why this is a persistence\n // downgrade rather than a kill switch on experiments.\n const persistence = Aglyn.resolveVisitorIdPersistence(\n consentHost,\n Aglyn.readStoredVisitorConsent(hostId),\n )\n let visitorId: string\n try {\n const storage =\n persistence === 'local' ? localStorage : sessionStorage\n // Read across BOTH stores so the assignment survives a consent move:\n // a session id promoted to local (consent granted mid-visit) and a\n // legacy local id migrating down to session both keep their variant.\n visitorId =\n storage.getItem(Aglyn.VISITOR_ID_STORAGE_KEY) ??\n sessionStorage.getItem(Aglyn.VISITOR_ID_STORAGE_KEY) ??\n localStorage.getItem(Aglyn.VISITOR_ID_STORAGE_KEY) ??\n ''\n if (!visitorId) {\n visitorId = `v-${Math.random().toString(36).slice(2)}${Date.now().toString(36)}`\n }\n storage.setItem(Aglyn.VISITOR_ID_STORAGE_KEY, visitorId)\n // A pre-consent-tool id may still sit in localStorage from before the\n // gate existed; while un-consented it must not persist.\n if (persistence === 'session') {\n localStorage.removeItem(Aglyn.VISITOR_ID_STORAGE_KEY)\n }\n } catch {\n return undefined // No storage (privacy mode) — serve the default.\n }\n const variant = MarketingModel.assignExperimentVariant(\n // Wire-typed props (AGL-418); the plugin narrows its own enum.\n { status: experiment.status as never, variants: experiment.variants,\n ...(experiment.winnerVariantId\n ? { winnerVariantId: experiment.winnerVariantId }\n : {}),\n // End dates (AGL-273): past-end experiments serve the default.\n ...(experiment.endAtMs ? { endAtMs: experiment.endAtMs } : {}) },\n experiment.id,\n visitorId,\n )\n if (!variant) return undefined\n const payload = experiment.payloads[variant.id]\n if (payload) Aglyn.canvas.setNodes(payload as any)\n\n const beacon = (kind: 'exposure' | 'conversion') => {\n void fetch('/api/experiments/track', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n experimentId: experiment.id,\n variantId: variant.id,\n kind,\n }),\n keepalive: true,\n }).catch(() => undefined)\n // GA mirror (wave v8): exposure/conversion events land in the\n // site's own Analytics property when configured. Through the taxonomy\n // since AGL-1591 — which also reserves the name against an authored\n // `trackGaEvent` step, so nothing hand-written can add itself to the\n // counts that decide which variant wins.\n trackEvent('aglyn_experiment', {\n experiment_id: experiment.id,\n variant_id: variant.id,\n experiment_action: kind,\n })\n }\n // Finished experiments serve the winner without counting stats.\n if (experiment.status !== 'running') return undefined\n beacon('exposure')\n\n // Goal listener — client-observable goals only; server-side goals\n // (bookings etc.) count through their own pipelines in a follow-up.\n const cleanups: Array<() => void> = []\n let converted = false\n const convert = () => {\n if (converted) return\n converted = true\n beacon('conversion')\n }\n const goal = experiment.goal?.event ?? 'formSubmission'\n if (goal === 'formSubmission') {\n const onSubmit = () => convert()\n document.addEventListener('submit', onSubmit, true)\n cleanups.push(() => document.removeEventListener('submit', onSubmit, true))\n } else if (goal === 'elementClick') {\n const selector = experiment.goal?.filter?.trim() || 'a, button'\n const onClick = (event: MouseEvent) => {\n const target = event.target as Element | null\n if (target?.closest?.(selector)) convert()\n }\n document.addEventListener('click', onClick, true)\n cleanups.push(() => document.removeEventListener('click', onClick, true))\n } else if (goal === 'scrollDepth') {\n const onScroll = () => {\n const doc = document.documentElement\n const max = doc.scrollHeight - window.innerHeight\n if (max <= 0 || (window.scrollY / max) * 100 >= 50) {\n convert()\n window.removeEventListener('scroll', onScroll)\n }\n }\n window.addEventListener('scroll', onScroll, { passive: true })\n cleanups.push(() => window.removeEventListener('scroll', onScroll))\n } else if (goal === 'timeOnPage') {\n const timer = setTimeout(convert, 30_000)\n cleanups.push(() => clearTimeout(timer))\n }\n return () => {\n for (const cleanup of cleanups) cleanup()\n }\n // Experiments are static per pageview (ISR props).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hostId, experiments.length && experiments[0]?.id])\n\n return null\n}\n\n/**\n * Site-event automations engine (AGL-256/257): arms the page's triggers\n * (scroll depth, element visibility/clicks, exit intent, dwell time),\n * runs each fired action's CLIENT steps in the browser, and dispatches\n * actions with server steps to /api/events/dispatch. Every trigger fires\n * at most once per pageview.\n */\nfunction AutomationsEngine(props: {\n hostId?: string\n automations: ClientAutomation[]\n /** Host routing map (screen id → slug) for rename-safe redirects. */\n screens?: Record<string, string>\n onShowOverlay: (overlayId: string) => void\n}) {\n const { hostId, automations, screens, onShowOverlay } = props\n const showOverlayRef = useRef(onShowOverlay)\n showOverlayRef.current = onShowOverlay\n\n useEffect(() => {\n if (!hostId || !automations.length) return undefined\n const fired = new Set<string>()\n const cleanups: Array<() => void> = []\n\n const showToast = (\n message: string,\n severity: 'info' | 'success' | 'warning' | 'error',\n ) => {\n const toast = document.createElement('div')\n toast.textContent = message\n toast.setAttribute('role', 'status')\n Object.assign(toast.style, {\n position: 'fixed',\n bottom: '16px',\n left: '50%',\n transform: 'translateX(-50%)',\n zIndex: '2147483000',\n padding: '10px 16px',\n borderRadius: '8px',\n fontSize: '14px',\n fontFamily: 'system-ui, sans-serif',\n color: '#fff',\n backgroundColor:\n severity === 'error'\n ? '#c62828'\n : severity === 'warning'\n ? '#e65100'\n : severity === 'success'\n ? '#2e7d32'\n : 'rgba(0, 0, 0, 0.85)',\n maxWidth: '90vw',\n })\n document.body.appendChild(toast)\n setTimeout(() => toast.remove(), 6000)\n }\n\n const runClientSteps = (automation: ClientAutomation) => {\n for (const step of automation.steps) {\n try {\n if (\n step.type === 'addClass' ||\n step.type === 'removeClass' ||\n step.type === 'toggleClass'\n ) {\n // Expand the raw-id selector to also match the layout-composed\n // live id (AGL-573); non-leaf selectors are unchanged.\n const targets = document.querySelectorAll(\n Aglyn.expandLeafSelector(step.selector),\n )\n targets.forEach((element) => {\n if (step.type === 'addClass') {\n element.classList.add(step.className)\n } else if (step.type === 'removeClass') {\n element.classList.remove(step.className)\n } else {\n element.classList.toggle(step.className)\n }\n })\n } else if (\n step.type === 'setAttribute' ||\n step.type === 'removeAttribute'\n ) {\n /*\n The semantics half of a hand-built disclosure (AGL-2546).\n\n Deliberately in THIS loop rather than behind a guard of its own:\n it then inherits exactly the gating the visibility steps have,\n including which surfaces mount the runtime at all. A separate\n path reaching `document.querySelector` directly is how this\n would come to run somewhere `showElement` does not.\n\n The name is re-checked here and not only in the editor. The\n editor is a convenience; a step can reach this runtime from a\n document written before the allowlist existed, or by any route\n that does not pass through the console UI.\n\n A rejected name is a NO-OP, not a throw — the `catch` around\n this loop would otherwise let one bad attribute name silently\n drop every step after it in the same automation.\n */\n if (Aglyn.isInteractionAttributeAllowed(step.name)) {\n const name = step.name.trim().toLowerCase()\n const targets = document.querySelectorAll(\n Aglyn.expandLeafSelector(step.selector),\n )\n targets.forEach((element) => {\n if (step.type === 'setAttribute') {\n element.setAttribute(name, step.value ?? '')\n } else {\n element.removeAttribute(name)\n }\n })\n }\n } else if (step.type === 'scrollTo') {\n // One target, the first one on screen (AGL-2867). A target that\n // is missing, deleted or hidden makes the step a no-op, and the\n // steps after it in this automation still run.\n Aglyn.runScrollToStep(Aglyn.expandLeafSelector(step.selector), {\n behavior: step.behavior,\n offsetPx: step.offsetPx,\n })\n } else if (step.type === 'playVideo') {\n // The Video element's own poster press, sent to the element: the\n // player loads only now, exactly as it would for a click on the\n // poster, and under the same consent rules.\n Aglyn.runPlayVideoStep(Aglyn.expandLeafSelector(step.selector))\n } else if (step.type === 'stickyNav') {\n const target = document.querySelector(\n Aglyn.expandLeafSelector(step.selector?.trim() || 'header, nav'),\n )\n if (target instanceof HTMLElement) {\n target.style.position = 'sticky'\n target.style.top = '0'\n target.style.zIndex = '1100'\n }\n } else if (step.type === 'showHtml') {\n const container = document.createElement('div')\n // Sanitize (AGL-504): unlike runJs, showHtml is NOT dropped by the\n // JS entitlement gate, so raw innerHTML would let any automations-\n // tier author execute event-handler attributes (<img onerror>).\n //\n // ONE policy for the surface (AGL-2486). AGL-1901 moved rich text\n // and Custom HTML onto `sanitizeAuthorHtml`; this call was the\n // last raw DOMPurify left on a published page, and two sanitizers\n // with different rules on one surface is worse than the weaker of\n // them — a reviewer reads the strict one and assumes it covers\n // both. It did not: DOMPurify filters NO CSS, so every `style`\n // attribute arriving here reached the visitor unexamined while\n // the Custom HTML block beside it went through `sanitizeAuthorCss`.\n //\n // Nothing this used to refuse is now allowed. `sanitizeAuthorHtml`\n // is a strict SUBSET of the DOMPurify config it replaces, and that\n // is measured rather than asserted: `rich-text-hydration.spec.tsx`\n // holds this exact former config as `referenceShowHtmlPurify` and\n // runs the shared corpus through both. So the change is only in\n // what it ADDITIONALLY refuses:\n //\n // - `style` now gets the CSS rule — `http:` and unknown-scheme\n // `url()` neutered to `about:invalid`, and `@import`,\n // `expression()`, `behavior:` and `-moz-binding` dropping the\n // declaration whole. Character references are decoded BEFORE\n // the scheme is judged, which the old config did not do: it\n // emitted `url(http://…)` as a literal `http://`.\n // - `<form>` stops being an element. The former FORBID_TAGS here\n // listed `script, iframe, object, embed, base` and omitted\n // `form` — alone among this repo's author-HTML configs, both of\n // which forbade it — so a password field on the owner's live\n // page was one paste away.\n //\n // What is deliberately NOT refused, on either side of the change:\n // a third-party `https:` `url()`, and `position:fixed` / `z-index`.\n // The first is AGL-1725's actor analysis (the site owner chooses\n // recipients for their own visitors); the second is a capability\n // the Styles panel already gives the same author as a first-class\n // field, so refusing it only here would move nothing.\n container.innerHTML = Aglyn.sanitizeAuthorHtml(step.html)\n document.body.appendChild(container)\n } else if (step.type === 'runJs') {\n // Business-gated server-side (dropped from props otherwise);\n // the site owner's own code running on their own page.\n // eslint-disable-next-line no-new-func\n new Function(step.code)()\n } else if (step.type === 'redirect') {\n // Screen targets resolve through the routing map (AGL-339) so\n // slug renames never break the interaction; url is the manual\n // fallback.\n const screenSlug =\n (step as any).screenId != null\n ? screens?.[(step as any).screenId]\n : undefined\n const destination =\n screenSlug != null\n ? Aglyn.screenRoutePathToUrl(screenSlug)\n : step.url\n if (destination) window.location.assign(destination)\n } else if (\n step.type === 'showElement' ||\n step.type === 'hideElement' ||\n step.type === 'toggleElement'\n ) {\n // Element choreography (AGL-562): class-based so \"show\"\n // reveals targets the author started hidden. The tenant page\n // ships the hidden-class rule in its SSR HTML; ensure is the\n // fallback for other embeddings. Grace delays + Escape/\n // outside-click dismissal ride the same step (AGL-589).\n Aglyn.ensureElementHiddenStyle()\n Aglyn.runElementVisibilityStep(\n step.type === 'showElement'\n ? 'show'\n : step.type === 'hideElement'\n ? 'hide'\n : 'toggle',\n // Expand so a show/hide/toggle authored against the raw\n // canvas id still reaches the layout-composed node (AGL-573).\n Aglyn.expandLeafSelector(step.selector),\n {\n delayMs: (step as { delayMs?: number }).delayMs,\n dismissOn: (\n step as { dismissOn?: Aglyn.ElementDismissOption[] }\n ).dismissOn,\n },\n )\n } else if (\n step.type === 'openDrawer' ||\n step.type === 'closeDrawer' ||\n step.type === 'toggleDrawer'\n ) {\n // Drawer commands (AGL-562) ride the window event bus; the\n // muiDrawer element answers (first drawer for broadcasts).\n Aglyn.dispatchDrawerCommand(\n step.type === 'openDrawer'\n ? 'open'\n : step.type === 'closeDrawer'\n ? 'close'\n : 'toggle',\n step.drawerNodeId || undefined,\n )\n } else if (\n step.type === 'openMenu' ||\n step.type === 'closeMenu' ||\n step.type === 'toggleMenu'\n ) {\n // Menu commands (AGL-568) ride their own event bus like\n // drawers; the nav-menu elements answer (first menu for\n // broadcasts). A hover-enter trigger stamps the hover flag\n // so a menu opened this way closes itself when the pointer\n // leaves the trigger + panel surface.\n Aglyn.dispatchMenuCommand(\n step.type === 'openMenu'\n ? 'open'\n : step.type === 'closeMenu'\n ? 'close'\n : 'toggle',\n step.menuNodeId || undefined,\n { hover: automation.event === 'elementHoverEnter' },\n )\n } else if (step.type === 'trackGaEvent') {\n // AGL-1587: the name and the params are AUTHORED, which makes\n // this the least trustworthy analytics input in the repo — a\n // param could carry a form field's value. It goes through the\n // shared sanitizer and the reserved-name refusal like everything\n // else, never raw gtag.\n trackAuthoredEvent(step.eventName, step.params)\n } else if (step.type === 'siteAlert') {\n showToast(\n String(step.message ?? '').slice(0, 300),\n step.severity ?? 'info',\n )\n } else if (step.type === 'showOverlay') {\n if (step.overlayId) showOverlayRef.current(step.overlayId)\n }\n } catch (error) {\n console.error('automation step failed', error)\n }\n }\n }\n\n const fire = (automation: ClientAutomation) => {\n // Repeatable UI choreography (AGL-562): everyTime automations skip\n // the once-per-pageview gate (explicit caps below still apply).\n if (!automation.everyTime) {\n if (fired.has(automation.id)) return\n fired.add(automation.id)\n }\n // Once per visitor (AGL-266): a persisted flag outlives the pageview.\n if (automation.oncePerVisitor) {\n const key = `aglyn:auto:${automation.id}`\n try {\n if (localStorage.getItem(key)) return\n localStorage.setItem(key, '1')\n } catch {\n // Storage unavailable — degrade to once per pageview.\n }\n } else if (automation.oncePerSession) {\n // Once per session (AGL-274): survives navigation, not the tab.\n const key = `aglyn:auto:${automation.id}`\n try {\n if (sessionStorage.getItem(key)) return\n sessionStorage.setItem(key, '1')\n } catch {\n // Storage unavailable — degrade to once per pageview.\n }\n } else if (automation.cooldownMinutes) {\n // Cooldown (AGL-274): a timestamp gates re-fires for the visitor.\n const key = `aglyn:auto:${automation.id}:at`\n try {\n const last = Number(localStorage.getItem(key) ?? 0)\n if (\n last &&\n Date.now() - last < automation.cooldownMinutes * 60_000\n ) {\n return\n }\n localStorage.setItem(key, String(Date.now()))\n } catch {\n // Storage unavailable — degrade to once per pageview.\n }\n }\n runClientSteps(automation)\n if (automation.hasServerSteps) {\n void fetch('/api/events/dispatch', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n actionId: automation.id,\n event: automation.event,\n payload: { path: window.location.pathname },\n }),\n keepalive: true,\n }).catch(() => undefined)\n }\n }\n\n /**\n * Elements that arrive AFTER this effect binds (AGL-2512).\n *\n * `elementVisible` and `scrollToElement` are the only two triggers that\n * resolve their targets by querying the DOM — every other one listens on\n * `document` and so needs no element to exist yet. A single query at bind\n * time made them dead on the two pages where the tree lands late: a\n * deferred lazy panel patched back in on interaction (AGL-1285), and a\n * gated screen's tree swapped in once the visitor is past the gate\n * (AGL-2510).\n *\n * One MutationObserver for all of them, not one each, and it stops the\n * moment nothing is left watching — these triggers fire once per pageview\n * by construction, so a page whose targets have all been seen pays\n * nothing for the rest of the visit. A page with no such automation never\n * starts one at all.\n */\n const pendingObservers: Array<() => void> = []\n let domObserver: MutationObserver | null = null\n const rescan = () => {\n for (const attach of [...pendingObservers]) attach()\n if (!pendingObservers.length) {\n domObserver?.disconnect()\n domObserver = null\n }\n }\n const watchLateElements = (attach: () => void) => {\n pendingObservers.push(attach)\n if (domObserver || typeof MutationObserver === 'undefined') return\n domObserver = new MutationObserver(rescan)\n domObserver.observe(document.body, { childList: true, subtree: true })\n }\n const stopWatching = (attach: () => void) => {\n const index = pendingObservers.indexOf(attach)\n if (index >= 0) pendingObservers.splice(index, 1)\n if (!pendingObservers.length) {\n domObserver?.disconnect()\n domObserver = null\n }\n }\n cleanups.push(() => {\n pendingObservers.length = 0\n domObserver?.disconnect()\n domObserver = null\n })\n\n for (const automation of automations) {\n const { event, threshold } = automation\n // Composition namespaces a node's live `data-aglyn` id, but the\n // interaction builder records the RAW canvas id in the selector.\n // Expand it so the trigger still matches the live element — layouts\n // (AGL-573) and reusable-component grafts (AGL-1229), the latter being\n // why every interaction authored inside the Site nav bound to nothing.\n // Hand-typed CSS selectors pass through untouched.\n const selector = automation.selector\n ? Aglyn.expandLeafSelector(automation.selector)\n : automation.selector\n if (event === 'pageVisit') {\n fire(automation)\n } else if (event === 'timeOnPage') {\n const timer = setTimeout(\n () => fire(automation),\n Math.max(1, threshold ?? 5) * 1000,\n )\n cleanups.push(() => clearTimeout(timer))\n } else if (event === 'scrollDepth') {\n const onScroll = () => {\n const doc = document.documentElement\n const max = doc.scrollHeight - window.innerHeight\n const percent = max > 0 ? (window.scrollY / max) * 100 : 100\n if (percent >= Math.min(100, Math.max(1, threshold ?? 50))) {\n fire(automation)\n window.removeEventListener('scroll', onScroll)\n }\n }\n window.addEventListener('scroll', onScroll, { passive: true })\n cleanups.push(() => window.removeEventListener('scroll', onScroll))\n } else if (event === 'elementVisible' || event === 'scrollToElement') {\n if (!selector) continue\n let fired = false\n const observer = new IntersectionObserver((entries) => {\n if (fired || !entries.some((entry) => entry.isIntersecting)) return\n fired = true\n fire(automation)\n observer.disconnect()\n stopWatching(attachTargets)\n })\n // Re-entrant: called again on every DOM change until the trigger\n // fires, and observing an element twice is a no-op, so a rescan costs\n // one `querySelectorAll` and nothing else.\n const attachTargets = () => {\n if (fired) return stopWatching(attachTargets)\n document\n .querySelectorAll(selector)\n .forEach((target) => observer.observe(target))\n }\n attachTargets()\n watchLateElements(attachTargets)\n cleanups.push(() => observer.disconnect())\n } else if (event === 'elementClick') {\n if (!selector) continue\n const onClick = (mouseEvent: MouseEvent) => {\n const target = mouseEvent.target as Element | null\n if (target?.closest?.(selector)) fire(automation)\n }\n document.addEventListener('click', onClick, true)\n cleanups.push(() =>\n document.removeEventListener('click', onClick, true),\n )\n } else if (\n event === 'elementHoverEnter' ||\n event === 'elementHoverLeave'\n ) {\n // Hover choreography (AGL-562): delegated mouseover/mouseout with\n // enter/leave semantics — moves WITHIN the matched element (its\n // own children) never re-fire.\n if (!selector) continue\n const onHover = (mouseEvent: MouseEvent) => {\n const target = mouseEvent.target as Element | null\n const match = target?.closest?.(selector)\n if (!match) return\n const related = mouseEvent.relatedTarget as Element | null\n if (related && match.contains(related)) return\n fire(automation)\n }\n const type = event === 'elementHoverEnter' ? 'mouseover' : 'mouseout'\n document.addEventListener(type, onHover, true)\n cleanups.push(() =>\n document.removeEventListener(type, onHover, true),\n )\n } else if (event === 'exitIntent') {\n const onLeave = (mouseEvent: MouseEvent) => {\n if (mouseEvent.clientY <= 0) fire(automation)\n }\n document.addEventListener('mouseleave', onLeave)\n cleanups.push(() => document.removeEventListener('mouseleave', onLeave))\n }\n }\n return () => {\n for (const cleanup of cleanups) cleanup()\n }\n // Automations are static per pageview (ISR props).\n // eslint-disable-next-line react-hooks/exhaustive-deps\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [\n hostId,\n screens,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n JSON.stringify(automations.map((automation) => automation.id)),\n ])\n\n return null\n}\n\nfunction AnnouncementBar(props: {\n bar: AnnouncementBarData\n hostId?: string\n}) {\n const { bar, hostId } = props\n const storageKey = `aglyn-abar-${bar.contentHash}`\n const [hidden, setHidden] = useState(true)\n useEffect(() => {\n try {\n setHidden(Boolean(window.localStorage.getItem(storageKey)))\n } catch {\n setHidden(false)\n }\n }, [storageKey])\n // Impression (AGL-271): once per pageview, only when actually shown.\n useEffect(() => {\n if (!hidden) sendOverlayBeacon(hostId, 'barImpression', bar.overlayId)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hidden])\n if (hidden) return null\n const style: CSSProperties = {\n position: 'relative',\n zIndex: 1300,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n gap: 8,\n padding: '8px 40px 8px 16px',\n fontSize: 14,\n fontFamily: 'system-ui, sans-serif',\n textAlign: 'center',\n color: bar.textColor || '#fff',\n backgroundColor: bar.backgroundColor || '#111827',\n }\n const text = bar.href ? (\n <a\n href={bar.href}\n onClick={() => sendOverlayBeacon(hostId, 'barClick', bar.overlayId)}\n style={{ color: 'inherit', textDecoration: 'underline' }}\n >\n {bar.text}\n </a>\n ) : (\n <span>{bar.text}</span>\n )\n return (\n <div role=\"region\" aria-label=\"Announcement\" style={style}>\n {text}\n {bar.dismissible ? (\n <button\n type=\"button\"\n aria-label=\"Dismiss announcement\"\n onClick={() => {\n try {\n window.localStorage.setItem(storageKey, '1')\n } catch {\n // Storage may be unavailable (private mode); hide anyway.\n }\n sendOverlayBeacon(hostId, 'barDismiss', bar.overlayId)\n setHidden(true)\n }}\n style={{\n position: 'absolute',\n right: 8,\n top: '50%',\n transform: 'translateY(-50%)',\n border: 'none',\n background: 'transparent',\n color: 'inherit',\n fontSize: 16,\n lineHeight: 1,\n cursor: 'pointer',\n padding: 6,\n }}\n >\n {'✕'}\n </button>\n ) : null}\n </div>\n )\n}\n\n/**\n * Promotional popup (AGL-196). Client owns: schedule-window re-check (the\n * page is ISR-cached), trigger timing (delay/scroll/exit), and frequency\n * capping via a per-content-hash localStorage stamp (no cookies). ESC and\n * backdrop close; reduced-motion preferences skip the entrance ease.\n */\nfunction PopupOverlay(props: {\n popup: PopupData\n hostId?: string\n}) {\n const { popup, hostId } = props\n const storageKey = `aglyn-popup-${popup.contentHash}`\n const [open, setOpen] = useState(false)\n // Email capture (AGL-200): submits into the forms pipeline (inbox +\n // contacts); success suppresses the popup for ~10 years.\n const [email, setEmail] = useState('')\n // The honeypot's value, held in state so whatever a bot typed into the\n // hidden input below is what gets POSTed (AGL-1665). This used to be a\n // hardcoded `website: ''` in the request body: the route drops a\n // submission only when the field is NON-empty, so a constant empty string\n // never failed the check — it removed it. There was no field on the page\n // to fill and no value a script could set, which made the popup the one\n // form surface on the site with no honeypot at all.\n const [website, setWebsite] = useState('')\n const [submitted, setSubmitted] = useState(false)\n\n useEffect(() => {\n if (open) sendOverlayBeacon(hostId, 'popupImpression', popup.overlayId)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open, hostId])\n\n useEffect(() => {\n const now = Date.now()\n if (popup.startAtMs && now < popup.startAtMs) return\n if (popup.endAtMs && now > popup.endAtMs) return\n try {\n // The decision itself is a pure function in the model (AGL-2174) —\n // which store to read is the whole feature, and it has to agree with\n // what `close` writes.\n const suppressed = MarketingModel.popupSuppressed(\n popup,\n MarketingModel.popupCapStore(popup) === 'session'\n ? { sessionFlag: window.sessionStorage.getItem(storageKey) }\n : { lastShownMs: Number(window.localStorage.getItem(storageKey) ?? 0) },\n now,\n )\n if (suppressed) return\n } catch {\n // Storage unavailable: show at most per-pageview.\n }\n let cleanup: (() => void) | undefined\n if (popup.trigger === 'scroll') {\n const threshold = Math.min(100, Math.max(0, popup.triggerValue)) / 100\n const onScroll = () => {\n const height =\n document.documentElement.scrollHeight - window.innerHeight\n if (height <= 0 || window.scrollY / height >= threshold) {\n setOpen(true)\n }\n }\n window.addEventListener('scroll', onScroll, { passive: true })\n cleanup = () => window.removeEventListener('scroll', onScroll)\n } else if (popup.trigger === 'exit') {\n const onLeave = (event: MouseEvent) => {\n if (event.clientY <= 0) setOpen(true)\n }\n document.addEventListener('mouseout', onLeave)\n cleanup = () => document.removeEventListener('mouseout', onLeave)\n } else {\n const timer = window.setTimeout(\n () => setOpen(true),\n Math.max(0, popup.triggerValue) * 1000,\n )\n cleanup = () => window.clearTimeout(timer)\n }\n return cleanup\n }, [popup, storageKey])\n\n const close = (kind: 'popupDismiss' | 'popupClick' = 'popupDismiss') => {\n try {\n // Written to whichever store the cap READS (AGL-2174). Stamping\n // `localStorage` for a session-capped popup would leave a timestamp\n // nothing reads, outliving the cap it was meant to record.\n if (MarketingModel.popupCapStore(popup) === 'session') {\n window.sessionStorage.setItem(storageKey, '1')\n } else {\n window.localStorage.setItem(storageKey, String(Date.now()))\n }\n } catch {\n // Private mode etc. — closing still works for this pageview.\n }\n sendOverlayBeacon(hostId, kind, popup.overlayId)\n setOpen(false)\n }\n\n const handleEmailSubmit = async () => {\n const value = email.trim()\n if (!value || submitted) return\n try {\n const response = await fetch('/api/forms/submit', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n // The door this capture comes through (AGL-3029): the popup is a\n // Marketing element, so a site that switched Forms off still\n // collects the address it shows the popup for.\n door: 'popup',\n formName: 'Popup',\n fields: { email: value },\n path: window.location.pathname,\n // Submitted AS-IS (AGL-1665) — the point of a honeypot is that the\n // client never judges it. `libs/plugins/mui/.../form.tsx` reads its\n // own hidden `website` input off the FormData and forwards it the\n // same way; `/api/forms/submit` is the single place that decides.\n website,\n // The campaign this visitor came from, when they came from one. A\n // popup capture is often a site's highest-converting door, so it\n // reports its campaign for the same reason it reports its lead.\n ...campaignTouchField(),\n }),\n })\n // The popup's email capture is a lead, and it reported none: an\n // announcement popup is often a site's highest-converting capture, and\n // the merchant saw the impression and the dismiss (`aglyn_overlay`) with\n // nothing in between. The generic form block has sent `generate_lead`\n // since AGL-1561; this door was simply never wired to it.\n //\n // Gated on `response.ok`, unlike the visitor-facing thank-you below.\n // The two disagree deliberately: a spam-refused or rate-limited capture\n // is still shown the thank-you — telling a real visitor to try again\n // after a honeypot verdict would leak the honeypot — but it is NOT a\n // lead, and counting it would inflate the only number the merchant uses\n // to judge whether the popup is worth interrupting people with.\n //\n // `form_name` is the block's own name, matching the `formName` on the\n // wire; the address the visitor just typed never leaves this function.\n if (response.ok) {\n trackEvent('generate_lead', {\n form_name: 'Popup',\n form_location: window.location.pathname,\n })\n }\n } catch {\n // Best-effort — still thank the visitor.\n }\n setSubmitted(true)\n try {\n // Far-future stamp: a successful capture never re-prompts.\n window.localStorage.setItem(\n storageKey,\n String(Date.now() + 315360000000),\n )\n } catch {\n // Ignore.\n }\n }\n\n useEffect(() => {\n if (!open) return\n const onKey = (event: KeyboardEvent) => {\n if (event.key === 'Escape') close()\n }\n document.addEventListener('keydown', onKey)\n return () => document.removeEventListener('keydown', onKey)\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [open])\n\n if (!open) return null\n return (\n <div\n onClick={() => close()}\n style={{\n position: 'fixed',\n inset: 0,\n zIndex: 2147483200,\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n backgroundColor: 'rgba(0, 0, 0, 0.5)',\n padding: 16,\n }}\n >\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label={popup.headline || 'Announcement'}\n onClick={(event) => event.stopPropagation()}\n style={{\n maxWidth: 420,\n width: '100%',\n background: '#fff',\n color: '#111827',\n borderRadius: 12,\n overflow: 'hidden',\n boxShadow: '0 24px 64px rgba(0, 0, 0, 0.35)',\n fontFamily: 'system-ui, sans-serif',\n position: 'relative',\n }}\n >\n <button\n type=\"button\"\n aria-label=\"Close\"\n onClick={() => close()}\n style={{\n position: 'absolute',\n top: 8,\n right: 8,\n border: 'none',\n background: 'rgba(255, 255, 255, 0.85)',\n borderRadius: '50%',\n width: 28,\n height: 28,\n cursor: 'pointer',\n fontSize: 14,\n lineHeight: 1,\n }}\n >\n {'✕'}\n </button>\n {/* The popup image is a free-text console field rendered verbatim —\n one of AGL-1725's raw author sinks. Scheme rule only, never a\n host check: `http:` and unknown schemes render nothing;\n https/relative (the media picker's forms) render as stored. */}\n {popup.imageUrl && !Aglyn.isRefusedAuthorImageSrc(popup.imageUrl) ? (\n // eslint-disable-next-line @next/next/no-img-element\n <img\n src={popup.imageUrl}\n // AGL-1896. This was a hardcoded `alt=\"\"` with no field behind\n // it. Still empty when the author left it empty — a decorative\n // banner beside its own headline is the case `alt=\"\"` exists for\n // — but it is now their choice rather than ours, and picking the\n // image from the library fills it in from the asset.\n alt={popup.imageAlt ?? ''}\n style={{\n width: '100%',\n maxHeight: 200,\n objectFit: 'cover',\n display: 'block',\n }}\n />\n ) : null}\n <div style={{ padding: 24, textAlign: 'center' }}>\n {popup.headline ? (\n <h2 style={{ margin: '0 0 8px', fontSize: 20 }}>\n {popup.headline}\n </h2>\n ) : null}\n <p style={{ margin: 0, fontSize: 14, whiteSpace: 'pre-wrap' }}>\n {popup.body}\n </p>\n {popup.collectEmail ? (\n <div style={{ marginTop: 16 }}>\n {submitted ? (\n <p style={{ margin: 0, fontSize: 14, fontWeight: 600 }}>\n {'Thanks — you are on the list!'}\n </p>\n ) : (\n <form\n onSubmit={(event) => {\n event.preventDefault()\n void handleEmailSubmit()\n }}\n style={{ display: 'flex', gap: 8, justifyContent: 'center' }}\n >\n <input\n type=\"email\"\n required\n placeholder=\"you@example.com\"\n // Same nameless-input defect as the newsletter block\n // (AGL-2392). A popup is modal-ish and arrives unbidden,\n // so a screen-reader user meets this field with no\n // surrounding context at all.\n aria-label=\"Email address\"\n value={email}\n onChange={(event) => setEmail(event.target.value)}\n style={{\n flex: 1,\n maxWidth: 240,\n padding: '8px 12px',\n borderRadius: 8,\n border: '1px solid #d1d5db',\n fontSize: 14,\n }}\n />\n {/* Honeypot: humans never see or fill this (AGL-1665).\n Off-screen rather than `display:none`/`hidden`, and\n named `website`, so the naive form-filling bots the\n route's check is aimed at treat it as an ordinary\n field — the same bait, presented the same way, as the\n site form's. Out of flow, so the flex row above is\n unchanged. */}\n <input\n type=\"text\"\n name=\"website\"\n tabIndex={-1}\n autoComplete=\"off\"\n aria-hidden=\"true\"\n value={website}\n onChange={(event) => setWebsite(event.target.value)}\n style={{\n position: 'absolute',\n left: '-5000px',\n height: 0,\n width: 0,\n }}\n />\n <button\n type=\"submit\"\n style={{\n padding: '8px 16px',\n borderRadius: 8,\n border: 'none',\n background: '#111827',\n color: '#fff',\n fontSize: 14,\n cursor: 'pointer',\n }}\n >\n {'Sign up'}\n </button>\n </form>\n )}\n </div>\n ) : null}\n {popup.ctaHref && popup.ctaLabel ? (\n <a\n href={popup.ctaHref}\n onClick={() => close('popupClick')}\n style={{\n display: 'inline-block',\n marginTop: 16,\n padding: '10px 24px',\n borderRadius: 8,\n background: '#111827',\n color: '#fff',\n textDecoration: 'none',\n fontSize: 14,\n }}\n >\n {popup.ctaLabel}\n </a>\n ) : null}\n </div>\n </div>\n </div>\n )\n}\n\n\n/**\n * The marketing plugin's site runtime (AGL-419), relocated verbatim from\n * the tenant catch-all: announcement bar + popup rendering (AGL-195/200),\n * the experiments runner (AGL-253), the site-event automations engine\n * (AGL-256/257), and automation-triggered overlays. Reads back exactly the\n * page-props slices its server enricher wrote (site-contract shapes).\n */\nexport function MarketingSiteRuntime({ hostId, screens, page }: SiteRuntimeProps) {\n const [automationOverlay, setAutomationOverlay] = useState<any>(null)\n const announcementBar = page['announcementBar'] as AnnouncementBarData | null\n const popup = page['popup'] as PopupData | null\n const experiments = (page['experiments'] ?? []) as ScreenExperiment[]\n const clientAutomations = (page['clientAutomations'] ?? []) as ClientAutomation[]\n const automationOverlays = page['automationOverlays'] as Record<string, any> | null\n\n return (\n <>\n {announcementBar ? (\n <AnnouncementBar bar={announcementBar} hostId={hostId} />\n ) : null}\n {popup ? <PopupOverlay popup={popup} hostId={hostId} /> : null}\n {/* Screen/section experiments (AGL-253). */}\n {experiments.length ? (\n <ExperimentsRunner\n hostId={hostId}\n experiments={experiments}\n consentHost={page['data']?.host as Aglyn.VisitorConsentHost | null}\n />\n ) : null}\n {/* Site-event automations (AGL-256/257). */}\n {clientAutomations.length ? (\n <AutomationsEngine\n hostId={hostId}\n automations={clientAutomations}\n screens={screens}\n onShowOverlay={(overlayId) => {\n const overlay = automationOverlays?.[overlayId]\n if (overlay) setAutomationOverlay({ id: overlayId, ...overlay })\n }}\n />\n ) : null}\n {automationOverlay?.kind === 'bar' && automationOverlay.bar?.text ? (\n <AnnouncementBar\n bar={{\n ...automationOverlay.bar,\n text: String(automationOverlay.bar.text),\n dismissible: automationOverlay.bar.dismissible !== false,\n contentHash: `automation-${automationOverlay.id}`,\n }}\n hostId={hostId}\n />\n ) : null}\n {automationOverlay?.kind === 'popup' && automationOverlay.popup?.body ? (\n <PopupOverlay\n popup={{\n ...automationOverlay.popup,\n body: String(automationOverlay.popup.body),\n trigger: 'delay',\n triggerValue: 0,\n frequencyDays: Math.max(\n 1,\n Number(automationOverlay.popup.frequencyDays ?? 1),\n ),\n contentHash: `automation-${automationOverlay.id}`,\n }}\n hostId={hostId}\n />\n ) : null}\n </>\n )\n}\n"],"names":["Aglyn","sendAnalyticsBeacon","trackAuthoredEvent","trackEvent","campaignTouchField","useEffect","useRef","useState","MarketingModel","sendOverlayBeacon","hostId","overlay","overlayId","overlay_action","overlay_id","ExperimentsRunner","props","experiments","consentHost","experiment","length","undefined","persistence","resolveVisitorIdPersistence","readStoredVisitorConsent","visitorId","storage","localStorage","sessionStorage","getItem","VISITOR_ID_STORAGE_KEY","Math","random","toString","slice","Date","now","setItem","removeItem","variant","assignExperimentVariant","status","variants","winnerVariantId","endAtMs","id","payload","payloads","canvas","setNodes","beacon","kind","fetch","method","headers","body","JSON","stringify","experimentId","variantId","keepalive","catch","experiment_id","variant_id","experiment_action","cleanups","converted","convert","goal","event","onSubmit","document","addEventListener","push","removeEventListener","selector","filter","trim","onClick","target","closest","onScroll","doc","documentElement","max","scrollHeight","window","innerHeight","scrollY","passive","timer","setTimeout","clearTimeout","cleanup","AutomationsEngine","automations","screens","onShowOverlay","showOverlayRef","current","fired","Set","showToast","message","severity","toast","createElement","textContent","setAttribute","Object","assign","style","position","bottom","left","transform","zIndex","padding","borderRadius","fontSize","fontFamily","color","backgroundColor","maxWidth","appendChild","remove","runClientSteps","automation","step","steps","type","targets","querySelectorAll","expandLeafSelector","forEach","element","classList","add","className","toggle","isInteractionAttributeAllowed","name","toLowerCase","value","removeAttribute","runScrollToStep","behavior","offsetPx","runPlayVideoStep","querySelector","HTMLElement","top","container","innerHTML","sanitizeAuthorHtml","html","Function","code","screenSlug","screenId","destination","screenRoutePathToUrl","url","location","ensureElementHiddenStyle","runElementVisibilityStep","delayMs","dismissOn","dispatchDrawerCommand","drawerNodeId","dispatchMenuCommand","menuNodeId","hover","eventName","params","String","error","console","fire","everyTime","has","oncePerVisitor","key","oncePerSession","cooldownMinutes","last","Number","hasServerSteps","actionId","path","pathname","pendingObservers","domObserver","rescan","attach","disconnect","watchLateElements","MutationObserver","observe","childList","subtree","stopWatching","index","indexOf","splice","threshold","percent","min","observer","IntersectionObserver","entries","some","entry","isIntersecting","attachTargets","mouseEvent","onHover","match","related","relatedTarget","contains","onLeave","clientY","map","AnnouncementBar","bar","storageKey","contentHash","hidden","setHidden","Boolean","display","alignItems","justifyContent","gap","textAlign","textColor","text","href","a","textDecoration","span","div","role","aria-label","dismissible","button","right","border","background","lineHeight","cursor","PopupOverlay","popup","open","setOpen","email","setEmail","website","setWebsite","submitted","setSubmitted","startAtMs","suppressed","popupSuppressed","popupCapStore","sessionFlag","lastShownMs","trigger","triggerValue","height","close","handleEmailSubmit","response","door","formName","fields","ok","form_name","form_location","onKey","inset","aria-modal","headline","stopPropagation","width","overflow","boxShadow","imageUrl","isRefusedAuthorImageSrc","img","src","alt","imageAlt","maxHeight","objectFit","h2","margin","p","whiteSpace","collectEmail","marginTop","fontWeight","form","preventDefault","input","required","placeholder","onChange","flex","tabIndex","autoComplete","aria-hidden","ctaHref","ctaLabel","MarketingSiteRuntime","page","automationOverlay","setAutomationOverlay","announcementBar","clientAutomations","automationOverlays","host","frequencyDays"],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** What one call to the campaign API answered with. */
|
|
2
|
+
export interface CampaignSendApiResult {
|
|
3
|
+
response: Response;
|
|
4
|
+
payload: Record<string, any>;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* ONE AUTHORIZED POST TO THE CAMPAIGN API.
|
|
8
|
+
*
|
|
9
|
+
* Every action the surface takes against a send — the audience count, the
|
|
10
|
+
* message render, the test send, the send itself, and asking an email that
|
|
11
|
+
* has already gone out to reach more people — is the same request with a
|
|
12
|
+
* different `action`, so it is one caller rather than one per screen. The
|
|
13
|
+
* route decides authorization from the token; nothing here does.
|
|
14
|
+
*
|
|
15
|
+
* ## The user is read through a REF, and that is load-bearing
|
|
16
|
+
*
|
|
17
|
+
* This callback is a dependency of the composer's two preview effects, so
|
|
18
|
+
* anything that changes its identity re-issues their requests — and the user
|
|
19
|
+
* object is a new object on most renders. Depending on it directly made the
|
|
20
|
+
* count effect fire on every keystroke: clear the count, resolve the whole
|
|
21
|
+
* audience again, for a number that cannot have moved. The token is fetched
|
|
22
|
+
* at call time either way, so the ref costs nothing and the callback depends
|
|
23
|
+
* on the one thing that really identifies the request.
|
|
24
|
+
*/
|
|
25
|
+
export declare function useCampaignSendApi(hostId: string): (payload: Record<string, unknown>) => Promise<CampaignSendApiResult>;
|
|
26
|
+
/**
|
|
27
|
+
* ONE AUTHORIZED POST TO THE CAMPAIGN MANAGEMENT API.
|
|
28
|
+
*
|
|
29
|
+
* A second endpoint rather than a second action on the send route, because
|
|
30
|
+
* nothing it does sends: it removes a campaign container, or discards a draft
|
|
31
|
+
* that was never mailed. The route is what decides whether either is allowed
|
|
32
|
+
* — a draft's state and a container's emails are read there — so this is the
|
|
33
|
+
* same one-POST caller pointed at a different path.
|
|
34
|
+
*/
|
|
35
|
+
export declare function useCampaignManageApi(hostId: string): (payload: Record<string, unknown>) => Promise<CampaignSendApiResult>;
|
|
@@ -0,0 +1,99 @@
|
|
|
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 { useUser } from "@aglyn/tenant-feature-instance";
|
|
19
|
+
import { useCallback, useRef } from "react";
|
|
20
|
+
import { resolveIdToken } from "@aglyn/shared-util-http/authorized-token";
|
|
21
|
+
/**
|
|
22
|
+
* ONE AUTHORIZED POST TO THE CAMPAIGN API.
|
|
23
|
+
*
|
|
24
|
+
* Every action the surface takes against a send — the audience count, the
|
|
25
|
+
* message render, the test send, the send itself, and asking an email that
|
|
26
|
+
* has already gone out to reach more people — is the same request with a
|
|
27
|
+
* different `action`, so it is one caller rather than one per screen. The
|
|
28
|
+
* route decides authorization from the token; nothing here does.
|
|
29
|
+
*
|
|
30
|
+
* ## The user is read through a REF, and that is load-bearing
|
|
31
|
+
*
|
|
32
|
+
* This callback is a dependency of the composer's two preview effects, so
|
|
33
|
+
* anything that changes its identity re-issues their requests — and the user
|
|
34
|
+
* object is a new object on most renders. Depending on it directly made the
|
|
35
|
+
* count effect fire on every keystroke: clear the count, resolve the whole
|
|
36
|
+
* audience again, for a number that cannot have moved. The token is fetched
|
|
37
|
+
* at call time either way, so the ref costs nothing and the callback depends
|
|
38
|
+
* on the one thing that really identifies the request.
|
|
39
|
+
*/ export function useCampaignSendApi(hostId) {
|
|
40
|
+
return useCampaignApi(hostId, '/api/campaigns/send');
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* ONE AUTHORIZED POST TO THE CAMPAIGN MANAGEMENT API.
|
|
44
|
+
*
|
|
45
|
+
* A second endpoint rather than a second action on the send route, because
|
|
46
|
+
* nothing it does sends: it removes a campaign container, or discards a draft
|
|
47
|
+
* that was never mailed. The route is what decides whether either is allowed
|
|
48
|
+
* — a draft's state and a container's emails are read there — so this is the
|
|
49
|
+
* same one-POST caller pointed at a different path.
|
|
50
|
+
*/ export function useCampaignManageApi(hostId) {
|
|
51
|
+
return useCampaignApi(hostId, '/api/campaigns/manage');
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* The POST both callers above make: the site id, the caller's ID token, and
|
|
55
|
+
* whatever the action carries.
|
|
56
|
+
*
|
|
57
|
+
* The user is read through a ref for the reason given above, and the path is
|
|
58
|
+
* a plain argument so the two hooks are one implementation. A hook taking the
|
|
59
|
+
* URL from a component would be a way for a caller to post the console's
|
|
60
|
+
* credentials somewhere the console does not own, which is why neither
|
|
61
|
+
* exported hook accepts one.
|
|
62
|
+
*
|
|
63
|
+
* ## The token is obtained under a deadline, and its absence is an ERROR
|
|
64
|
+
*
|
|
65
|
+
* `resolveIdToken` throws rather than answering with nothing, and it gives up
|
|
66
|
+
* rather than waiting forever. Both matter here because the token is awaited
|
|
67
|
+
* IN FRONT of the request: a refresh that never answers means this call never
|
|
68
|
+
* reaches `fetch` at all — no request, no response, no failure to report —
|
|
69
|
+
* and a token that came back empty would otherwise post the send
|
|
70
|
+
* unauthenticated and turn "you are signed out" into a refusal from the
|
|
71
|
+
* route. Callers are expected to let the error reach the person.
|
|
72
|
+
*/ function useCampaignApi(hostId, path) {
|
|
73
|
+
const { data: user } = useUser();
|
|
74
|
+
const userRef = useRef(user);
|
|
75
|
+
userRef.current = user;
|
|
76
|
+
return useCallback(async (payload)=>{
|
|
77
|
+
const idToken = await resolveIdToken(userRef.current);
|
|
78
|
+
const response = await fetch(path, {
|
|
79
|
+
method: 'POST',
|
|
80
|
+
headers: {
|
|
81
|
+
'Content-Type': 'application/json',
|
|
82
|
+
Authorization: `Bearer ${idToken}`
|
|
83
|
+
},
|
|
84
|
+
body: JSON.stringify(_extends({
|
|
85
|
+
hostId
|
|
86
|
+
}, payload))
|
|
87
|
+
});
|
|
88
|
+
const json = await response.json().catch(()=>({}));
|
|
89
|
+
return {
|
|
90
|
+
response,
|
|
91
|
+
payload: json
|
|
92
|
+
};
|
|
93
|
+
}, [
|
|
94
|
+
hostId,
|
|
95
|
+
path
|
|
96
|
+
]);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
//# sourceMappingURL=use-campaign-send-api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/use-campaign-send-api.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 { useUser } from '@aglyn/tenant-feature-instance'\nimport { useCallback, useRef } from 'react'\nimport {\n resolveIdToken,\n type TokenSource,\n} from '@aglyn/shared-util-http/authorized-token'\n\n/** What one call to the campaign API answered with. */\nexport interface CampaignSendApiResult {\n response: Response\n payload: Record<string, any>\n}\n\n/**\n * ONE AUTHORIZED POST TO THE CAMPAIGN API.\n *\n * Every action the surface takes against a send — the audience count, the\n * message render, the test send, the send itself, and asking an email that\n * has already gone out to reach more people — is the same request with a\n * different `action`, so it is one caller rather than one per screen. The\n * route decides authorization from the token; nothing here does.\n *\n * ## The user is read through a REF, and that is load-bearing\n *\n * This callback is a dependency of the composer's two preview effects, so\n * anything that changes its identity re-issues their requests — and the user\n * object is a new object on most renders. Depending on it directly made the\n * count effect fire on every keystroke: clear the count, resolve the whole\n * audience again, for a number that cannot have moved. The token is fetched\n * at call time either way, so the ref costs nothing and the callback depends\n * on the one thing that really identifies the request.\n */\nexport function useCampaignSendApi(hostId: string) {\n return useCampaignApi(hostId, '/api/campaigns/send')\n}\n\n/**\n * ONE AUTHORIZED POST TO THE CAMPAIGN MANAGEMENT API.\n *\n * A second endpoint rather than a second action on the send route, because\n * nothing it does sends: it removes a campaign container, or discards a draft\n * that was never mailed. The route is what decides whether either is allowed\n * — a draft's state and a container's emails are read there — so this is the\n * same one-POST caller pointed at a different path.\n */\nexport function useCampaignManageApi(hostId: string) {\n return useCampaignApi(hostId, '/api/campaigns/manage')\n}\n\n/**\n * The POST both callers above make: the site id, the caller's ID token, and\n * whatever the action carries.\n *\n * The user is read through a ref for the reason given above, and the path is\n * a plain argument so the two hooks are one implementation. A hook taking the\n * URL from a component would be a way for a caller to post the console's\n * credentials somewhere the console does not own, which is why neither\n * exported hook accepts one.\n *\n * ## The token is obtained under a deadline, and its absence is an ERROR\n *\n * `resolveIdToken` throws rather than answering with nothing, and it gives up\n * rather than waiting forever. Both matter here because the token is awaited\n * IN FRONT of the request: a refresh that never answers means this call never\n * reaches `fetch` at all — no request, no response, no failure to report —\n * and a token that came back empty would otherwise post the send\n * unauthenticated and turn \"you are signed out\" into a refusal from the\n * route. Callers are expected to let the error reach the person.\n */\nfunction useCampaignApi(hostId: string, path: string) {\n const { data: user } = useUser()\n const userRef = useRef(user)\n userRef.current = user\n return useCallback(\n async (payload: Record<string, unknown>): Promise<CampaignSendApiResult> => {\n const idToken = await resolveIdToken(\n userRef.current as TokenSource | null | undefined,\n )\n const response = await fetch(path, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${idToken}`,\n },\n body: JSON.stringify({ hostId, ...payload }),\n })\n const json = await response.json().catch(() => ({}))\n return { response, payload: json as Record<string, any> }\n },\n [hostId, path],\n )\n}\n"],"names":["useUser","useCallback","useRef","resolveIdToken","useCampaignSendApi","hostId","useCampaignApi","useCampaignManageApi","path","data","user","userRef","current","payload","idToken","response","fetch","method","headers","Authorization","body","JSON","stringify","json","catch"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,OAAO,QAAQ,iCAAgC;AACxD,SAASC,WAAW,EAAEC,MAAM,QAAQ,QAAO;AAC3C,SACEC,cAAc,QAET,2CAA0C;AAQjD;;;;;;;;;;;;;;;;;;CAkBC,GACD,OAAO,SAASC,mBAAmBC,MAAc;IAC/C,OAAOC,eAAeD,QAAQ;AAChC;AAEA;;;;;;;;CAQC,GACD,OAAO,SAASE,qBAAqBF,MAAc;IACjD,OAAOC,eAAeD,QAAQ;AAChC;AAEA;;;;;;;;;;;;;;;;;;;CAmBC,GACD,SAASC,eAAeD,MAAc,EAAEG,IAAY;IAClD,MAAM,EAAEC,MAAMC,IAAI,EAAE,GAAGV;IACvB,MAAMW,UAAUT,OAAOQ;IACvBC,QAAQC,OAAO,GAAGF;IAClB,OAAOT,YACL,OAAOY;QACL,MAAMC,UAAU,MAAMX,eACpBQ,QAAQC,OAAO;QAEjB,MAAMG,WAAW,MAAMC,MAAMR,MAAM;YACjCS,QAAQ;YACRC,SAAS;gBACP,gBAAgB;gBAChBC,eAAe,CAAC,OAAO,EAAEL,SAAS;YACpC;YACAM,MAAMC,KAAKC,SAAS,CAAC;gBAAEjB;eAAWQ;QACpC;QACA,MAAMU,OAAO,MAAMR,SAASQ,IAAI,GAAGC,KAAK,CAAC,IAAO,CAAA,CAAC,CAAA;QACjD,OAAO;YAAET;YAAUF,SAASU;QAA4B;IAC1D,GACA;QAAClB;QAAQG;KAAK;AAElB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type ReactNode } from 'react';
|
|
2
|
+
import { type CampaignTopicOption } from './campaign-email-zones';
|
|
3
|
+
/**
|
|
4
|
+
* The streams a campaign can open on, for a drawer's topic field.
|
|
5
|
+
*
|
|
6
|
+
* The catalog is another plugin's, so it is not read here: `source` is the
|
|
7
|
+
* zone a widget reports the list through, and it must be rendered for
|
|
8
|
+
* `topics` to fill. It draws nothing. `enabled` reaches the widget as its
|
|
9
|
+
* read gate, so a shut drawer costs no read.
|
|
10
|
+
*
|
|
11
|
+
* Empty where nothing fills the zone. A campaign may carry no topic — the send
|
|
12
|
+
* resolves the default — so a drawer with no options is still a working one.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useCampaignTopicOptions(hostId: string, options?: {
|
|
15
|
+
enabled?: boolean;
|
|
16
|
+
}): {
|
|
17
|
+
topics: readonly CampaignTopicOption[];
|
|
18
|
+
source: ReactNode;
|
|
19
|
+
};
|
|
20
|
+
export default useCampaignTopicOptions;
|
|
@@ -0,0 +1,44 @@
|
|
|
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 { jsx as _jsx } from "react/jsx-runtime";
|
|
18
|
+
import { useState } from "react";
|
|
19
|
+
import { CampaignTopicOptions } from "./campaign-email-zones.js";
|
|
20
|
+
/**
|
|
21
|
+
* The streams a campaign can open on, for a drawer's topic field.
|
|
22
|
+
*
|
|
23
|
+
* The catalog is another plugin's, so it is not read here: `source` is the
|
|
24
|
+
* zone a widget reports the list through, and it must be rendered for
|
|
25
|
+
* `topics` to fill. It draws nothing. `enabled` reaches the widget as its
|
|
26
|
+
* read gate, so a shut drawer costs no read.
|
|
27
|
+
*
|
|
28
|
+
* Empty where nothing fills the zone. A campaign may carry no topic — the send
|
|
29
|
+
* resolves the default — so a drawer with no options is still a working one.
|
|
30
|
+
*/ export function useCampaignTopicOptions(hostId, options) {
|
|
31
|
+
var _ref;
|
|
32
|
+
const [topics, setTopics] = useState([]);
|
|
33
|
+
return {
|
|
34
|
+
topics,
|
|
35
|
+
source: /*#__PURE__*/ _jsx(CampaignTopicOptions, {
|
|
36
|
+
hostId: hostId,
|
|
37
|
+
enabled: (_ref = options == null ? void 0 : options.enabled) != null ? _ref : true,
|
|
38
|
+
onTopics: setTopics
|
|
39
|
+
})
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
export default useCampaignTopicOptions;
|
|
43
|
+
|
|
44
|
+
//# sourceMappingURL=use-campaign-topic-options.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/marketing/src/lib/components/use-campaign-topic-options.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 { useState, type ReactNode } from 'react'\nimport {\n CampaignTopicOptions,\n type CampaignTopicOption,\n} from './campaign-email-zones'\n\n/**\n * The streams a campaign can open on, for a drawer's topic field.\n *\n * The catalog is another plugin's, so it is not read here: `source` is the\n * zone a widget reports the list through, and it must be rendered for\n * `topics` to fill. It draws nothing. `enabled` reaches the widget as its\n * read gate, so a shut drawer costs no read.\n *\n * Empty where nothing fills the zone. A campaign may carry no topic — the send\n * resolves the default — so a drawer with no options is still a working one.\n */\nexport function useCampaignTopicOptions(\n hostId: string,\n options?: { enabled?: boolean },\n): { topics: readonly CampaignTopicOption[]; source: ReactNode } {\n const [topics, setTopics] = useState<readonly CampaignTopicOption[]>([])\n return {\n topics,\n source: (\n <CampaignTopicOptions\n hostId={hostId}\n enabled={options?.enabled ?? true}\n onTopics={setTopics}\n />\n ),\n }\n}\n\nexport default useCampaignTopicOptions\n"],"names":["useState","CampaignTopicOptions","useCampaignTopicOptions","hostId","options","topics","setTopics","source","enabled","onTopics"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAEA,SAASA,QAAQ,QAAwB,QAAO;AAChD,SACEC,oBAAoB,QAEf,4BAAwB;AAE/B;;;;;;;;;;CAUC,GACD,OAAO,SAASC,wBACdC,MAAc,EACdC,OAA+B;;IAE/B,MAAM,CAACC,QAAQC,UAAU,GAAGN,SAAyC,EAAE;IACvE,OAAO;QACLK;QACAE,sBACE,KAACN;YACCE,QAAQA;YACRK,OAAO,UAAEJ,2BAAAA,QAASI,OAAO,mBAAI;YAC7BC,UAAUH;;IAGhB;AACF;AAEA,eAAeJ,wBAAuB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Emails console's URL under the site being read.
|
|
3
|
+
*
|
|
4
|
+
* A campaign's page links to two records it does not own — the message that
|
|
5
|
+
* was sent inside it, and the template that message was built from — and both
|
|
6
|
+
* keep their pages on the Emails console. The shell hands a plugin page only
|
|
7
|
+
* its OWN `basePath`, so the sibling hub is named here by its SLUG rather
|
|
8
|
+
* than by editing the last segment off the current path: a slug is a string
|
|
9
|
+
* this file can be searched for when a surface moves, and string surgery on
|
|
10
|
+
* somebody else's path is a link that keeps resolving to the wrong place.
|
|
11
|
+
*
|
|
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 a campaign, to render a link.
|
|
16
|
+
*
|
|
17
|
+
* `null` before the params resolve, so callers render plain text rather than
|
|
18
|
+
* a link to nowhere.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useEmailsHubPath(): string | null;
|
|
21
|
+
/**
|
|
22
|
+
* The MARKETING console's URL under the site being read.
|
|
23
|
+
*
|
|
24
|
+
* Named for the same reason the Emails one is, from the opposite direction: a
|
|
25
|
+
* converted record — a form submission in the Inbox, a contact in Contacts —
|
|
26
|
+
* links to the campaign it came from, and those surfaces are handed their own
|
|
27
|
+
* hub's `basePath`, not this one's.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useMarketingHubPath(): string | null;
|
|
30
|
+
/**
|
|
31
|
+
* Any sibling hub's URL, by plugin slug.
|
|
32
|
+
*
|
|
33
|
+
* One implementation, because "read the org slug and the subdomain off the
|
|
34
|
+
* route rather than resolving the host document" is the property that keeps
|
|
35
|
+
* these links free, and a second copy of it is where somebody pays for the
|
|
36
|
+
* two `getDoc`s instead.
|
|
37
|
+
*/
|
|
38
|
+
export declare function useConsoleHubPath(pluginSlug: string): string | null;
|
|
39
|
+
export default useEmailsHubPath;
|
|
@@ -0,0 +1,70 @@
|
|
|
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 { buildRoute, Route } from "@aglyn/aglyn";
|
|
18
|
+
import { useParams } from "next/navigation";
|
|
19
|
+
/**
|
|
20
|
+
* The Emails console's URL under the site being read.
|
|
21
|
+
*
|
|
22
|
+
* A campaign's page links to two records it does not own — the message that
|
|
23
|
+
* was sent inside it, and the template that message was built from — and both
|
|
24
|
+
* keep their pages on the Emails console. The shell hands a plugin page only
|
|
25
|
+
* its OWN `basePath`, so the sibling hub is named here by its SLUG rather
|
|
26
|
+
* than by editing the last segment off the current path: a slug is a string
|
|
27
|
+
* this file can be searched for when a surface moves, and string surgery on
|
|
28
|
+
* somebody 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 a campaign, to render a link.
|
|
34
|
+
*
|
|
35
|
+
* `null` before the params resolve, so callers render plain text rather than
|
|
36
|
+
* a link to nowhere.
|
|
37
|
+
*/ export function useEmailsHubPath() {
|
|
38
|
+
return useConsoleHubPath('emails');
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The MARKETING console's URL under the site being read.
|
|
42
|
+
*
|
|
43
|
+
* Named for the same reason the Emails one is, from the opposite direction: a
|
|
44
|
+
* converted record — a form submission in the Inbox, a contact in Contacts —
|
|
45
|
+
* links to the campaign it came from, and those surfaces are handed their own
|
|
46
|
+
* hub's `basePath`, not this one's.
|
|
47
|
+
*/ export function useMarketingHubPath() {
|
|
48
|
+
return useConsoleHubPath('marketing');
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Any sibling hub's URL, by plugin slug.
|
|
52
|
+
*
|
|
53
|
+
* One implementation, because "read the org slug and the subdomain off the
|
|
54
|
+
* route rather than resolving the host document" is the property that keeps
|
|
55
|
+
* these links free, and a second copy of it is where somebody pays for the
|
|
56
|
+
* two `getDoc`s instead.
|
|
57
|
+
*/ export function useConsoleHubPath(pluginSlug) {
|
|
58
|
+
const params = useParams();
|
|
59
|
+
const orgSlug = params == null ? void 0 : params.orgSlug;
|
|
60
|
+
const host = params == null ? void 0 : params.host;
|
|
61
|
+
if (!orgSlug || !host) return null;
|
|
62
|
+
return buildRoute(Route.HOST_PLUGIN, {
|
|
63
|
+
orgSlug,
|
|
64
|
+
host,
|
|
65
|
+
pluginSlug
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
export default useEmailsHubPath;
|
|
69
|
+
|
|
70
|
+
//# sourceMappingURL=use-emails-hub-path.js.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The Marketing console's URL under the site being read.
|
|
3
|
+
*
|
|
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.
|
|
11
|
+
*
|
|
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.
|
|
16
|
+
*
|
|
17
|
+
* `null` before the params resolve, so callers render plain text rather than
|
|
18
|
+
* a link to nowhere.
|
|
19
|
+
*/
|
|
20
|
+
export declare function useMarketingHubPath(): string | null;
|
|
21
|
+
export default useMarketingHubPath;
|
|
@@ -0,0 +1,50 @@
|
|
|
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 { 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.
|
|
34
|
+
*
|
|
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;
|
|
49
|
+
|
|
50
|
+
//# sourceMappingURL=use-marketing-hub-path.js.map
|
|
@@ -0,0 +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"}
|