@gradial/aci 0.1.41 → 0.1.42
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/CHANGELOG.md +9 -13
- package/README.md +4 -4
- package/dist/astro/index.d.ts +1 -1
- package/dist/astro/index.js +1 -1
- package/dist/astro/page.d.ts +6 -0
- package/dist/astro/page.js +3 -2
- package/dist/cli/compile-registry.d.mts +2 -0
- package/dist/cli/compile-registry.mjs +759 -0
- package/dist/cli/materialize-template.d.mts +19 -0
- package/dist/cli/materialize-template.mjs +128 -0
- package/dist/cli/validate-content.d.mts +2 -0
- package/dist/cli/validate-content.mjs +426 -0
- package/dist/cli/verify-next-cdn-routes.d.mts +2 -0
- package/dist/cli/verify-next-cdn-routes.mjs +47 -0
- package/dist/collect/assignments.d.ts +65 -0
- package/dist/collect/assignments.js +138 -0
- package/dist/collect/batcher.d.ts +21 -0
- package/dist/collect/batcher.js +10 -0
- package/dist/collect/declarative.d.ts +1 -1
- package/dist/collect/declarative.js +7 -1
- package/dist/collect/index.js +30 -4
- package/dist/collect/pageviews.d.ts +15 -3
- package/dist/collect/pageviews.js +28 -13
- package/dist/collect/transport.d.ts +2 -0
- package/dist/collect/transport.js +32 -0
- package/dist/consent-banner/index.d.ts +33 -0
- package/dist/consent-banner/index.js +99 -0
- package/dist/content/personalization.d.ts +35 -0
- package/dist/content/personalization.js +57 -0
- package/dist/content/types.d.ts +7 -1
- package/dist/edge/personalization-core.d.ts +11 -0
- package/dist/edge/personalization-core.js +265 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/next/assignments-script.d.ts +14 -0
- package/dist/next/assignments-script.js +17 -0
- package/dist/next/collect-route.d.ts +19 -0
- package/dist/next/collect-route.js +351 -0
- package/dist/next/index.d.ts +1 -0
- package/dist/next/index.js +1 -0
- package/dist/next/middleware.d.ts +15 -1
- package/dist/next/middleware.js +71 -27
- package/dist/next/page.js +2 -1
- package/dist/next/personalization-host.d.ts +118 -0
- package/dist/next/personalization-host.js +276 -0
- package/dist/next/server.js +6 -6
- package/dist/next/vercel-personalization.d.ts +3 -0
- package/dist/next/vercel-personalization.js +3 -0
- package/dist/next/vercel-s3-personalization.d.ts +58 -0
- package/dist/next/vercel-s3-personalization.js +220 -0
- package/dist/providers/s3.d.ts +1 -1
- package/dist/providers/s3.js +1 -1
- package/dist/react/VideoPlayer.d.ts +6 -6
- package/dist/react/index.d.ts +6 -6
- package/dist/types/page.d.ts +1 -1
- package/package.json +19 -1
- package/src/astro/ConsentBanner.astro +62 -0
- package/src/cli/verify-next-cdn-routes.mjs +5 -4
- package/src/consent-banner/styles.css +110 -0
- package/src/types/page.ts +1 -1
- package/templates/astro/starter-content/src/cms/contracts/components/index.ts +2 -0
- package/templates/astro/starter-content/src/cms/contracts/components/personalizationChoice.contract.ts +17 -0
- package/templates/astro/starter-content/src/cms/registry.ts +3 -0
- package/templates/astro/starter-content/src/components/sections/PersonalizationChoice.astro +41 -0
- package/templates/astro/starter-content/src/render/RenderPage.astro +3 -2
- package/templates/astro/template/src/layouts/SiteLayout.astro +3 -1
- package/templates/astro/template/src/pages/[...slug].astro +1 -0
- package/templates/common/.content/audiences/preference-blue/_index.json +11 -0
- package/templates/common/.content/audiences/preference-red/_index.json +11 -0
- package/templates/common/.content/overlays/audience/preference-blue/pages/home/_index.json +13 -0
- package/templates/common/.content/overlays/audience/preference-red/pages/home/_index.json +13 -0
- package/templates/common/.content/pages/home/_index.json +114 -4
- package/templates/common/.content/segments/preference-blue/_index.json +20 -0
- package/templates/common/.content/segments/preference-red/_index.json +20 -0
- package/templates/common/contracts/components/index.ts +21 -1
- package/templates/common/contracts/components/personalizationChoice.contract.ts +17 -0
- package/templates/nextjs/starter-content/src/cms/contracts/components/index.ts +3 -1
- package/templates/nextjs/starter-content/src/cms/contracts/components/personalizationChoice.contract.ts +17 -0
- package/templates/nextjs/starter-content/src/cms/registry.ts +3 -0
- package/templates/nextjs/starter-content/src/components/sections/PersonalizationChoice.tsx +53 -0
- package/templates/nextjs/starter-content/src/middleware.ts +11 -0
- package/templates/nextjs/template/src/app/collect/route.ts +18 -0
- package/templates/nextjs/template/src/middleware.ts +6 -2
- package/templates/astro/template/.aci.yaml +0 -28
- package/templates/common/.content/.gradial-dam/blobs/sha256/7f/bf/7fbfc7fe230ff12688c496f660119e41d9725c4504d82e310c15665c73c09292 +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/a0/f5/a0f506689de9b15a7055f70103a3f02cd3ba105771ae2de6754df2ede1210d3a +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/dd/b7/ddb7e9642be08317bcd4b2bd26b58ba8d0d34dd7239e03e684a4f3634fa3845b +0 -0
- package/templates/common/.content/.gradial-dam/blobs/sha256/fb/32/fb3260e8286ea8231ea1c77b9c0be92d6edd9026565731eb14cfdcfd6b1b75b3 +0 -0
- package/templates/common/.content/config/robots.txt +0 -2
- package/templates/common/.content/config/site.json +0 -197
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/asset.json +0 -9
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/editorial.json +0 -14
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/governance.json +0 -23
- package/templates/common/.content/dam/folders/seed/ast_site_about_hero/versions/v0001.json +0 -37
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/asset.json +0 -9
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/editorial.json +0 -14
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/governance.json +0 -23
- package/templates/common/.content/dam/folders/seed/ast_site_home_hero/versions/v0001.json +0 -37
- package/templates/common/.content/fragments/navbar/_index.json +0 -25
- package/templates/common/.content/fragments/site-footer/_index.json +0 -62
- package/templates/common/.content/pages/about/_index.json +0 -51
- package/templates/nextjs/template/.aci.yaml +0 -26
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { ContentProps } from '../../cms/contracts/components/personalizationChoice.contract';
|
|
3
|
+
|
|
4
|
+
type Props = ContentProps;
|
|
5
|
+
|
|
6
|
+
const { title, description, redLabel, blueLabel, refreshLabel } = Astro.props;
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
<section class="border-y border-[var(--color-border)] bg-[var(--color-surface)] py-[var(--spacing-section-y)]">
|
|
10
|
+
<div class="mx-auto max-w-[var(--layout-content-max)] px-[var(--layout-gutter)] text-center">
|
|
11
|
+
<h2 class="text-[length:var(--text-h2)] font-[var(--font-weight-semibold)] text-[var(--color-fg)]">{title}</h2>
|
|
12
|
+
{description ? <p class="mx-auto mt-3 max-w-2xl text-[var(--color-fg-muted)]">{description}</p> : null}
|
|
13
|
+
<div class="mt-6 flex flex-wrap justify-center gap-3">
|
|
14
|
+
<button type="button" class="inline-flex min-h-10 items-center justify-center rounded-button border border-emerald-700 bg-emerald-600 px-[var(--spacing-button-x)] py-[var(--spacing-button-y)] font-[var(--font-weight-semibold)] text-white transition hover:bg-emerald-700" data-aci-consent-button>Enable personalization</button>
|
|
15
|
+
<button type="button" class="inline-flex min-h-10 items-center justify-center rounded-button border border-red-700 bg-red-600 px-[var(--spacing-button-x)] py-[var(--spacing-button-y)] font-[var(--font-weight-semibold)] text-white transition hover:bg-red-700" data-aci-event="preference" data-aci-prop-color="red">{redLabel}</button>
|
|
16
|
+
<button type="button" class="inline-flex min-h-10 items-center justify-center rounded-button border border-blue-700 bg-blue-600 px-[var(--spacing-button-x)] py-[var(--spacing-button-y)] font-[var(--font-weight-semibold)] text-white transition hover:bg-blue-700" data-aci-event="preference" data-aci-prop-color="blue">{blueLabel}</button>
|
|
17
|
+
<button type="button" class="inline-flex min-h-10 items-center justify-center rounded-button border border-[var(--button-outline-border)] bg-[var(--button-outline-bg)] px-[var(--spacing-button-x)] py-[var(--spacing-button-y)] font-[var(--font-weight-semibold)] text-[var(--button-outline-text)] transition hover:opacity-85" data-aci-refresh>{refreshLabel}</button>
|
|
18
|
+
</div>
|
|
19
|
+
<p class="mx-auto mt-4 max-w-2xl text-sm text-[var(--color-fg-muted)]" data-aci-cookie-status aria-live="polite">Consent: checking... | Browser marker: checking... | Identity and audience cookies: HttpOnly server-managed</p>
|
|
20
|
+
</div>
|
|
21
|
+
</section>
|
|
22
|
+
|
|
23
|
+
<script is:inline>
|
|
24
|
+
(() => {
|
|
25
|
+
const hasCookie = (name) => document.cookie.split('; ').some((value) => value.startsWith(`${name}=`));
|
|
26
|
+
const status = document.querySelector('[data-aci-cookie-status]');
|
|
27
|
+
const render = () => {
|
|
28
|
+
if (!status) return;
|
|
29
|
+
status.textContent = `Consent: ${hasCookie('aci_consent') ? 'granted' : 'not granted'} | Browser marker: ${hasCookie('aci_m') ? 'minted' : 'not minted'} | Identity and audience cookies: HttpOnly server-managed`;
|
|
30
|
+
};
|
|
31
|
+
document.querySelector('[data-aci-consent-button]')?.addEventListener('click', () => {
|
|
32
|
+
const envelope = encodeURIComponent(JSON.stringify({ v: 1, p: ['personalization'], t: new Date().toISOString(), src: 'demo-consent' }));
|
|
33
|
+
document.cookie = `aci_consent=${envelope}; Path=/; Max-Age=31536000; SameSite=Lax; Secure`;
|
|
34
|
+
window.aci?.('consent', { p: ['personalization'], src: 'demo-consent' });
|
|
35
|
+
render();
|
|
36
|
+
window.setTimeout(() => document.querySelector('[data-aci-refresh]')?.click(), 50);
|
|
37
|
+
}, true);
|
|
38
|
+
render();
|
|
39
|
+
window.setInterval(render, 1000);
|
|
40
|
+
})();
|
|
41
|
+
</script>
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
import SiteLayout from '../layouts/SiteLayout.astro';
|
|
3
3
|
import type { KernelPage, KernelSiteConfig } from '@gradial/aci/content';
|
|
4
|
-
import { registryLookup } from '@gradial/aci/astro';
|
|
4
|
+
import { registryLookup, collectScriptTagHTML } from '@gradial/aci/astro';
|
|
5
5
|
import { registry } from '../cms/registry';
|
|
6
6
|
import Header from '../components/chrome/Header.astro';
|
|
7
7
|
import Footer from '../components/chrome/Footer.astro';
|
|
@@ -24,12 +24,13 @@ const title = page.metadata?.title || siteConfig.seo?.title || siteConfig.title;
|
|
|
24
24
|
const description = page.metadata?.description || siteConfig.seo?.description || '';
|
|
25
25
|
const canonical = page.metadata?.canonical;
|
|
26
26
|
const siteName = siteConfig.seo?.siteName || siteConfig.title;
|
|
27
|
+
const collectTag = collectScriptTagHTML(siteConfig);
|
|
27
28
|
|
|
28
29
|
const footer = siteConfig.footer || {};
|
|
29
30
|
const blocks = page?.regions?.main || [];
|
|
30
31
|
---
|
|
31
32
|
|
|
32
|
-
<SiteLayout {title} {description} {canonical} {siteName}>
|
|
33
|
+
<SiteLayout {title} {description} {canonical} {siteName} {collectTag}>
|
|
33
34
|
<Header
|
|
34
35
|
brandHref={siteConfig.brandHref || '/'}
|
|
35
36
|
brandLabel={siteConfig.title || 'Gradial'}
|
|
@@ -14,9 +14,10 @@ interface Props {
|
|
|
14
14
|
* content).
|
|
15
15
|
*/
|
|
16
16
|
collectTag?: string;
|
|
17
|
+
assignmentsTag?: string;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
|
-
const { title, description, canonical, siteName = 'Gradial', collectTag } = Astro.props;
|
|
20
|
+
const { title, description, canonical, siteName = 'Gradial', collectTag, assignmentsTag } = Astro.props;
|
|
20
21
|
---
|
|
21
22
|
|
|
22
23
|
<!doctype html>
|
|
@@ -32,6 +33,7 @@ const { title, description, canonical, siteName = 'Gradial', collectTag } = Astr
|
|
|
32
33
|
<meta property="og:description" content={description} />
|
|
33
34
|
<meta property="og:site_name" content={siteName} />
|
|
34
35
|
{collectTag ? <Fragment set:html={collectTag} /> : null}
|
|
36
|
+
{assignmentsTag ? <Fragment set:html={assignmentsTag} /> : null}
|
|
35
37
|
</head>
|
|
36
38
|
<body>
|
|
37
39
|
<slot />
|
|
@@ -17,6 +17,7 @@ const renderChildren = createRenderChildren(registry);
|
|
|
17
17
|
canonical={data.meta.canonical}
|
|
18
18
|
siteName={data.meta.siteName}
|
|
19
19
|
collectTag={data.collectTag}
|
|
20
|
+
assignmentsTag={data.assignmentsTag}
|
|
20
21
|
>
|
|
21
22
|
{data.layout.slots.map((slot) => {
|
|
22
23
|
const blocks = data.slots[slot.name];
|
|
@@ -1,13 +1,123 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$type": "page",
|
|
3
|
-
"id": "home",
|
|
3
|
+
"id": "meridian-home",
|
|
4
4
|
"status": "published",
|
|
5
5
|
"layout": "default",
|
|
6
6
|
"metadata": {
|
|
7
|
-
"title": "
|
|
8
|
-
"description": "
|
|
7
|
+
"title": "Meridian — Global Satellite Internet",
|
|
8
|
+
"description": "Connect from anywhere on Earth. Low-latency satellite internet for homes, businesses, and beyond.",
|
|
9
|
+
"canonical": "https://www.meridian.local/"
|
|
9
10
|
},
|
|
10
11
|
"regions": {
|
|
11
|
-
"main": [
|
|
12
|
+
"main": [
|
|
13
|
+
{
|
|
14
|
+
"id": "hero",
|
|
15
|
+
"component": "home_hero",
|
|
16
|
+
"props": {
|
|
17
|
+
"eyebrow": "Global satellite internet",
|
|
18
|
+
"headline": "Connect from anywhere on Earth.",
|
|
19
|
+
"description": "Meridian delivers low-latency broadband to homes, businesses, and remote operations in over 40 countries. No ground infrastructure required.",
|
|
20
|
+
"ctaLabel": "Explore plans",
|
|
21
|
+
"ctaHref": "/plans/",
|
|
22
|
+
"image": {
|
|
23
|
+
"$type": "dam.assetRef",
|
|
24
|
+
"assetId": "ast_site_home_hero",
|
|
25
|
+
"alt": "Light abstract hero background with satellite constellation"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
"id": "why-meridian",
|
|
31
|
+
"component": "feature_grid",
|
|
32
|
+
"props": {
|
|
33
|
+
"title": "Why Meridian",
|
|
34
|
+
"subtitle": "Engineered for the edges of the world.",
|
|
35
|
+
"features": [
|
|
36
|
+
{
|
|
37
|
+
"id": "global-coverage",
|
|
38
|
+
"component": "feature_card",
|
|
39
|
+
"props": {
|
|
40
|
+
"icon": "globe",
|
|
41
|
+
"title": "Global coverage",
|
|
42
|
+
"description": "A constellation of low-orbit satellites keeps you connected across continents, oceans, and remote terrain."
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "low-latency",
|
|
47
|
+
"component": "feature_card",
|
|
48
|
+
"props": {
|
|
49
|
+
"icon": "speed",
|
|
50
|
+
"title": "Low latency",
|
|
51
|
+
"description": "Sub-50ms latency means video calls, gaming, and real-time control feel local, even in the middle of nowhere."
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "easy-setup",
|
|
56
|
+
"component": "feature_card",
|
|
57
|
+
"props": {
|
|
58
|
+
"icon": "setup",
|
|
59
|
+
"title": "Easy setup",
|
|
60
|
+
"description": "A compact terminal and a phone app. Point at the sky, power on, and get online in minutes."
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
]
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
"id": "personalization-choice",
|
|
68
|
+
"component": "personalization_choice",
|
|
69
|
+
"props": {
|
|
70
|
+
"title": "Choose your connection preference",
|
|
71
|
+
"description": "Select a preference, allow the update to process, then refresh personalization.",
|
|
72
|
+
"redLabel": "Choose Red",
|
|
73
|
+
"blueLabel": "Choose Blue",
|
|
74
|
+
"refreshLabel": "Refresh personalization"
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"id": "stats",
|
|
79
|
+
"component": "stats_bar",
|
|
80
|
+
"props": {
|
|
81
|
+
"stats": [
|
|
82
|
+
{ "label": "Network uptime", "value": "99.9", "suffix": "%" },
|
|
83
|
+
{ "label": "Average latency", "value": "<50", "suffix": "ms" },
|
|
84
|
+
{ "label": "Countries covered", "value": "40", "suffix": "+" },
|
|
85
|
+
{ "label": "Orbital satellites", "value": "2,400", "suffix": "" }
|
|
86
|
+
]
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
"id": "cta",
|
|
91
|
+
"component": "container",
|
|
92
|
+
"props": {
|
|
93
|
+
"theme": "light",
|
|
94
|
+
"className": "items-center justify-center text-center",
|
|
95
|
+
"content": [
|
|
96
|
+
{
|
|
97
|
+
"id": "cta-primary",
|
|
98
|
+
"component": "button",
|
|
99
|
+
"props": {
|
|
100
|
+
"label": "Explore plans",
|
|
101
|
+
"href": "/plans/",
|
|
102
|
+
"variant": "primary",
|
|
103
|
+
"icon": "arrow_forward",
|
|
104
|
+
"iconPosition": "after"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"id": "cta-secondary",
|
|
109
|
+
"component": "button",
|
|
110
|
+
"props": {
|
|
111
|
+
"label": "Contact sales",
|
|
112
|
+
"href": "/contact-sales/",
|
|
113
|
+
"variant": "outline",
|
|
114
|
+
"icon": "arrow_forward",
|
|
115
|
+
"iconPosition": "after"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
]
|
|
12
122
|
}
|
|
13
123
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$type": "segment",
|
|
3
|
+
"props": {
|
|
4
|
+
"id": "preference-blue",
|
|
5
|
+
"displayName": "Latest preference is Blue",
|
|
6
|
+
"status": "enabled",
|
|
7
|
+
"priority": 100,
|
|
8
|
+
"criteria": {
|
|
9
|
+
"all": [
|
|
10
|
+
{
|
|
11
|
+
"type": "event_latest",
|
|
12
|
+
"event": "custom.preference",
|
|
13
|
+
"properties": {
|
|
14
|
+
"color": "blue"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$type": "segment",
|
|
3
|
+
"props": {
|
|
4
|
+
"id": "preference-red",
|
|
5
|
+
"displayName": "Latest preference is Red",
|
|
6
|
+
"status": "enabled",
|
|
7
|
+
"priority": 100,
|
|
8
|
+
"criteria": {
|
|
9
|
+
"all": [
|
|
10
|
+
{
|
|
11
|
+
"type": "event_latest",
|
|
12
|
+
"event": "custom.preference",
|
|
13
|
+
"properties": {
|
|
14
|
+
"color": "red"
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
-
|
|
1
|
+
import { buttonContract } from './button.contract';
|
|
2
|
+
import { containerContract } from './container.contract';
|
|
3
|
+
import { featureCardContract } from './featureCard.contract';
|
|
4
|
+
import { featureGridContract } from './featureGrid.contract';
|
|
5
|
+
import { homeHeroContract } from './homeHero.contract';
|
|
6
|
+
import { personalizationChoiceContract } from './personalizationChoice.contract';
|
|
7
|
+
import { siteNavigationContract } from './siteNavigation.contract';
|
|
8
|
+
import { siteFooterContract } from './siteFooter.contract';
|
|
9
|
+
import { statsBarContract } from './statsBar.contract';
|
|
10
|
+
|
|
11
|
+
const componentContracts = [
|
|
12
|
+
homeHeroContract,
|
|
13
|
+
personalizationChoiceContract,
|
|
14
|
+
containerContract,
|
|
15
|
+
buttonContract,
|
|
16
|
+
siteNavigationContract,
|
|
17
|
+
siteFooterContract,
|
|
18
|
+
featureGridContract,
|
|
19
|
+
featureCardContract,
|
|
20
|
+
statsBarContract,
|
|
21
|
+
];
|
|
2
22
|
|
|
3
23
|
export default componentContracts;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineComponentContract } from '@gradial/aci';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export const schema = z.object({
|
|
5
|
+
title: z.string().min(1),
|
|
6
|
+
description: z.string().optional(),
|
|
7
|
+
redLabel: z.string().min(1),
|
|
8
|
+
blueLabel: z.string().min(1),
|
|
9
|
+
refreshLabel: z.string().min(1),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type ContentProps = z.infer<typeof schema>;
|
|
13
|
+
|
|
14
|
+
export const personalizationChoiceContract = defineComponentContract({
|
|
15
|
+
id: 'personalization_choice',
|
|
16
|
+
props: schema,
|
|
17
|
+
});
|
|
@@ -3,12 +3,14 @@ import { containerContract } from './container.contract';
|
|
|
3
3
|
import { featureCardContract } from './featureCard.contract';
|
|
4
4
|
import { featureGridContract } from './featureGrid.contract';
|
|
5
5
|
import { homeHeroContract } from './homeHero.contract';
|
|
6
|
-
import {
|
|
6
|
+
import { personalizationChoiceContract } from './personalizationChoice.contract';
|
|
7
7
|
import { siteNavigationContract } from './siteNavigation.contract';
|
|
8
|
+
import { siteFooterContract } from './siteFooter.contract';
|
|
8
9
|
import { statsBarContract } from './statsBar.contract';
|
|
9
10
|
|
|
10
11
|
const componentContracts = [
|
|
11
12
|
homeHeroContract,
|
|
13
|
+
personalizationChoiceContract,
|
|
12
14
|
containerContract,
|
|
13
15
|
buttonContract,
|
|
14
16
|
siteNavigationContract,
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { defineComponentContract } from '@gradial/aci';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
|
|
4
|
+
export const schema = z.object({
|
|
5
|
+
title: z.string().min(1),
|
|
6
|
+
description: z.string().optional(),
|
|
7
|
+
redLabel: z.string().min(1),
|
|
8
|
+
blueLabel: z.string().min(1),
|
|
9
|
+
refreshLabel: z.string().min(1),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export type ContentProps = z.infer<typeof schema>;
|
|
13
|
+
|
|
14
|
+
export const personalizationChoiceContract = defineComponentContract({
|
|
15
|
+
id: 'personalization_choice',
|
|
16
|
+
props: schema,
|
|
17
|
+
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createRegistry } from '@gradial/aci';
|
|
2
2
|
import { homeHeroContract } from './contracts/components/homeHero.contract';
|
|
3
|
+
import { personalizationChoiceContract } from './contracts/components/personalizationChoice.contract';
|
|
3
4
|
import { containerContract } from './contracts/components/container.contract';
|
|
4
5
|
import { buttonContract } from './contracts/components/button.contract';
|
|
5
6
|
import { siteNavigationContract } from './contracts/components/siteNavigation.contract';
|
|
@@ -8,6 +9,7 @@ import { featureGridContract } from './contracts/components/featureGrid.contract
|
|
|
8
9
|
import { featureCardContract } from './contracts/components/featureCard.contract';
|
|
9
10
|
import { statsBarContract } from './contracts/components/statsBar.contract';
|
|
10
11
|
import { HomeHero } from '@/components/sections/HomeHero';
|
|
12
|
+
import { PersonalizationChoice } from '@/components/sections/PersonalizationChoice';
|
|
11
13
|
import { ContainerSection } from '@/components/sections/ContainerSection';
|
|
12
14
|
import { ButtonBlock } from '@/components/elements/ButtonBlock';
|
|
13
15
|
import { Header } from '@/components/chrome/Header';
|
|
@@ -18,6 +20,7 @@ import { StatsBar } from '@/components/sections/StatsBar';
|
|
|
18
20
|
|
|
19
21
|
export const registry = createRegistry([
|
|
20
22
|
[homeHeroContract, HomeHero],
|
|
23
|
+
[personalizationChoiceContract, PersonalizationChoice],
|
|
21
24
|
[containerContract, ContainerSection],
|
|
22
25
|
[buttonContract, ButtonBlock],
|
|
23
26
|
[siteNavigationContract, Header],
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState } from 'react';
|
|
4
|
+
import type { ContentProps } from '@/cms/contracts/components/personalizationChoice.contract';
|
|
5
|
+
|
|
6
|
+
type AciWindow = Window & { aci?: (command: string, value: unknown) => void };
|
|
7
|
+
|
|
8
|
+
function hasCookie(name: string): boolean {
|
|
9
|
+
return document.cookie.split('; ').some((value) => value.startsWith(`${name}=`));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function PersonalizationChoice({
|
|
13
|
+
title,
|
|
14
|
+
description,
|
|
15
|
+
redLabel,
|
|
16
|
+
blueLabel,
|
|
17
|
+
refreshLabel,
|
|
18
|
+
}: ContentProps) {
|
|
19
|
+
const [consent, setConsent] = useState(false);
|
|
20
|
+
const [marker, setMarker] = useState(false);
|
|
21
|
+
|
|
22
|
+
useEffect(() => {
|
|
23
|
+
const update = () => {
|
|
24
|
+
setConsent(hasCookie('aci_consent'));
|
|
25
|
+
setMarker(hasCookie('aci_m'));
|
|
26
|
+
};
|
|
27
|
+
update();
|
|
28
|
+
const interval = window.setInterval(update, 1000);
|
|
29
|
+
return () => window.clearInterval(interval);
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
return (
|
|
33
|
+
<section className="border-y border-[var(--color-border)] bg-[var(--color-surface)] py-[var(--spacing-section-y)]">
|
|
34
|
+
<div className="mx-auto max-w-[var(--layout-content-max)] px-[var(--layout-gutter)] text-center">
|
|
35
|
+
<h2 className="text-[length:var(--text-h2)] font-[var(--font-weight-semibold)] text-[var(--color-fg)]">{title}</h2>
|
|
36
|
+
{description ? <p className="mx-auto mt-3 max-w-2xl text-[var(--color-fg-muted)]">{description}</p> : null}
|
|
37
|
+
<div className="mt-6 flex flex-wrap justify-center gap-3">
|
|
38
|
+
<button type="button" className="inline-flex min-h-10 items-center justify-center rounded-button border border-emerald-700 bg-emerald-600 px-[var(--spacing-button-x)] py-[var(--spacing-button-y)] font-[var(--font-weight-semibold)] text-white transition hover:bg-emerald-700" data-aci-consent onClick={() => {
|
|
39
|
+
const envelope = encodeURIComponent(JSON.stringify({ v: 1, p: ['personalization'], t: new Date().toISOString(), src: 'demo-consent' }));
|
|
40
|
+
document.cookie = `aci_consent=${envelope}; Path=/; Max-Age=31536000; SameSite=Lax; Secure`;
|
|
41
|
+
(window as AciWindow).aci?.('consent', { p: ['personalization'], src: 'demo-consent' });
|
|
42
|
+
setConsent(true);
|
|
43
|
+
window.setTimeout(() => document.querySelector('[data-aci-refresh]')?.dispatchEvent(new MouseEvent('click', { bubbles: true })), 50);
|
|
44
|
+
}}>Enable personalization</button>
|
|
45
|
+
<button type="button" className="inline-flex min-h-10 items-center justify-center rounded-button border border-red-700 bg-red-600 px-[var(--spacing-button-x)] py-[var(--spacing-button-y)] font-[var(--font-weight-semibold)] text-white transition hover:bg-red-700" data-aci-event="preference" data-aci-prop-color="red">{redLabel}</button>
|
|
46
|
+
<button type="button" className="inline-flex min-h-10 items-center justify-center rounded-button border border-blue-700 bg-blue-600 px-[var(--spacing-button-x)] py-[var(--spacing-button-y)] font-[var(--font-weight-semibold)] text-white transition hover:bg-blue-700" data-aci-event="preference" data-aci-prop-color="blue">{blueLabel}</button>
|
|
47
|
+
<button type="button" className="inline-flex min-h-10 items-center justify-center rounded-button border border-[var(--button-outline-border)] bg-[var(--button-outline-bg)] px-[var(--spacing-button-x)] py-[var(--spacing-button-y)] font-[var(--font-weight-semibold)] text-[var(--button-outline-text)] transition hover:opacity-85" data-aci-refresh>{refreshLabel}</button>
|
|
48
|
+
</div>
|
|
49
|
+
<p className="mx-auto mt-4 max-w-2xl text-sm text-[var(--color-fg-muted)]" data-aci-cookie-status aria-live="polite">Consent: {consent ? 'granted' : 'not granted'} | Browser marker: {marker ? 'minted' : 'not minted'} | Identity and audience cookies: HttpOnly server-managed</p>
|
|
50
|
+
</div>
|
|
51
|
+
</section>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createGradialMiddleware } from '@gradial/aci/next/middleware';
|
|
2
|
+
import { createVercelPersonalizationFromEnv } from '@gradial/aci/next/vercel-personalization';
|
|
3
|
+
|
|
4
|
+
export default createGradialMiddleware({
|
|
5
|
+
personalization: createVercelPersonalizationFromEnv(),
|
|
6
|
+
});
|
|
7
|
+
|
|
8
|
+
export const config = {
|
|
9
|
+
runtime: 'nodejs',
|
|
10
|
+
matcher: ['/((?!_next(?:/|$)|api(?:/|$)|collect(?:/|$)|gradial/assets(?:/|$)|favicon\\.ico$).*)'],
|
|
11
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createPersonalizationCollectRoute,
|
|
3
|
+
createVercelPersonalizationFromEnv,
|
|
4
|
+
} from '@gradial/aci/next/vercel-personalization';
|
|
5
|
+
|
|
6
|
+
export const runtime = 'nodejs';
|
|
7
|
+
|
|
8
|
+
const personalization = createVercelPersonalizationFromEnv();
|
|
9
|
+
const unavailable = async () => new Response('personalization host is not configured', {
|
|
10
|
+
status: 503,
|
|
11
|
+
headers: { 'Cache-Control': 'private, no-store' },
|
|
12
|
+
});
|
|
13
|
+
const collect = personalization
|
|
14
|
+
? createPersonalizationCollectRoute(personalization)
|
|
15
|
+
: unavailable;
|
|
16
|
+
|
|
17
|
+
export const POST = collect;
|
|
18
|
+
export const OPTIONS = collect;
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { createGradialMiddleware } from '@gradial/aci/next/middleware';
|
|
2
|
+
import { createVercelPersonalizationFromEnv } from '@gradial/aci/next/vercel-personalization';
|
|
2
3
|
|
|
3
|
-
export default createGradialMiddleware(
|
|
4
|
+
export default createGradialMiddleware({
|
|
5
|
+
personalization: createVercelPersonalizationFromEnv(),
|
|
6
|
+
});
|
|
4
7
|
|
|
5
8
|
export const config = {
|
|
6
|
-
|
|
9
|
+
runtime: 'nodejs',
|
|
10
|
+
matcher: ['/((?!_next(?:/|$)|api(?:/|$)|collect(?:/|$)|gradial/assets(?:/|$)|favicon\\.ico$).*)'],
|
|
7
11
|
};
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
version: "1"
|
|
2
|
-
siteId: "site_astro"
|
|
3
|
-
domain: "meridian.local"
|
|
4
|
-
framework: astro
|
|
5
|
-
|
|
6
|
-
source:
|
|
7
|
-
root: "./"
|
|
8
|
-
outDir: "dist"
|
|
9
|
-
publicDir: "public"
|
|
10
|
-
|
|
11
|
-
componentRegistry: ./src/cms/contracts/components/index.ts
|
|
12
|
-
layoutRegistry: ./src/cms/contracts/layouts/index.ts
|
|
13
|
-
|
|
14
|
-
capabilities:
|
|
15
|
-
staticRender: true
|
|
16
|
-
ssr: true
|
|
17
|
-
ssrIslands: true
|
|
18
|
-
clientIslands: true
|
|
19
|
-
fragmentRender: true
|
|
20
|
-
|
|
21
|
-
routes:
|
|
22
|
-
cmsManaged: "/[...slug]"
|
|
23
|
-
frameworkOwned:
|
|
24
|
-
- "/api/*"
|
|
25
|
-
- "/_astro/*"
|
|
26
|
-
- "/storybook/*"
|
|
27
|
-
|
|
28
|
-
rendererProtocol: stdio-json
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|