@c15t/nextjs 2.0.0-rc.3 → 2.0.0-rc.5
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/client/components/consent-dialog-link.js +3 -0
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/dist/libs/browser-initial-data.cjs +1 -0
- package/dist/libs/browser-initial-data.js +1 -0
- package/dist/libs/initial-data.cjs +1 -1
- package/dist/libs/initial-data.js +1 -1
- package/dist/version.cjs +1 -1
- package/dist/version.js +1 -1
- package/dist-types/headless.d.ts +1 -0
- package/{dist → dist-types}/index.d.ts +4 -3
- package/dist-types/libs/browser-initial-data.d.ts +9 -0
- package/{dist → dist-types}/libs/initial-data.d.ts +1 -2
- package/dist-types/types.d.ts +38 -0
- package/dist-types/version.d.ts +1 -0
- package/docs/README.md +73 -0
- package/docs/building-headless-components.md +250 -0
- package/docs/callbacks.md +117 -0
- package/docs/components/consent-banner.md +174 -0
- package/docs/components/consent-dialog-link.md +59 -0
- package/docs/components/consent-dialog-trigger.md +103 -0
- package/docs/components/consent-dialog.md +137 -0
- package/docs/components/consent-manager-provider.md +422 -0
- package/docs/components/consent-widget.md +78 -0
- package/docs/components/dev-tools.md +63 -0
- package/docs/components/frame.md +73 -0
- package/docs/concepts/client-modes.md +163 -0
- package/docs/concepts/consent-categories.md +97 -0
- package/docs/concepts/consent-models.md +116 -0
- package/docs/concepts/cookie-management.md +122 -0
- package/docs/concepts/glossary.md +23 -0
- package/docs/concepts/initialization-flow.md +141 -0
- package/docs/concepts/policy-packs.md +229 -0
- package/docs/headless.md +184 -0
- package/docs/hooks/use-color-scheme.md +40 -0
- package/docs/hooks/use-consent-manager/checking-consent.md +94 -0
- package/docs/hooks/use-consent-manager/location-info.md +95 -0
- package/docs/hooks/use-consent-manager/overview.md +390 -0
- package/docs/hooks/use-consent-manager/setting-consent.md +92 -0
- package/docs/hooks/use-draggable.md +57 -0
- package/docs/hooks/use-focus-trap.md +41 -0
- package/docs/hooks/use-reduced-motion.md +35 -0
- package/docs/hooks/use-ssr-status.md +31 -0
- package/docs/hooks/use-text-direction.md +49 -0
- package/docs/hooks/use-translations.md +116 -0
- package/docs/iab/consent-banner.md +95 -0
- package/docs/iab/consent-dialog.md +135 -0
- package/docs/iab/overview.md +119 -0
- package/docs/iab/use-gvl-data.md +208 -0
- package/docs/iframe-blocking.md +107 -0
- package/docs/integrations/databuddy.md +186 -0
- package/docs/integrations/google-tag-manager.md +153 -0
- package/docs/integrations/google-tag.md +149 -0
- package/docs/integrations/linkedin-insights.md +109 -0
- package/docs/integrations/meta-pixel.md +342 -0
- package/docs/integrations/microsoft-uet.md +112 -0
- package/docs/integrations/overview.md +89 -0
- package/docs/integrations/posthog.md +177 -0
- package/docs/integrations/tiktok-pixel.md +113 -0
- package/docs/integrations/x-pixel.md +143 -0
- package/docs/internationalization.md +197 -0
- package/docs/network-blocker.md +178 -0
- package/docs/optimization.md +156 -0
- package/docs/policy-packs.md +246 -0
- package/docs/quickstart.md +145 -0
- package/docs/script-loader.md +300 -0
- package/docs/server-side.md +171 -0
- package/docs/styling/classnames.md +84 -0
- package/docs/styling/color-scheme.md +82 -0
- package/docs/styling/css-variables.md +92 -0
- package/docs/styling/overview.md +312 -0
- package/docs/styling/slots.md +93 -0
- package/docs/styling/tailwind.md +86 -0
- package/docs/styling/tokens.md +214 -0
- package/docs/troubleshooting.md +146 -0
- package/package.json +20 -10
- package/dist/headless.d.ts +0 -2
- package/dist/headless.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/libs/initial-data.d.ts.map +0 -1
- package/dist/types.d.ts +0 -16
- package/dist/types.d.ts.map +0 -1
- package/dist/version.d.ts +0 -2
- package/dist/version.d.ts.map +0 -1
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Client Modes
|
|
3
|
+
description: Choose how c15t connects to its backend - full hosted integration, offline-only, or bring your own backend.
|
|
4
|
+
---
|
|
5
|
+
c15t supports three client modes that determine how consent data is stored and synchronized. Choose the mode that matches your infrastructure:
|
|
6
|
+
|
|
7
|
+
* **Hosted mode** - Full backend integration with geolocation, API sync, and analytics
|
|
8
|
+
* **Offline mode** - Local-only storage with no network requests
|
|
9
|
+
* **Custom mode** - Bring your own backend with custom endpoint handlers
|
|
10
|
+
|
|
11
|
+
> ℹ️ **Info:**
|
|
12
|
+
> Offline mode is browser-only storage. If browser storage is blocked or cleared, consent cannot be remembered and prior choices cannot be verified.
|
|
13
|
+
|
|
14
|
+
<span id="c15t-mode" />
|
|
15
|
+
|
|
16
|
+
## Hosted Mode (Recommended)
|
|
17
|
+
|
|
18
|
+
The default mode. Connects to a c15t backend for full consent lifecycle management. We recommend using [consent.io](https://consent.io) for a fully managed experience, but you can [self-host](/docs/self-host) as well.
|
|
19
|
+
|
|
20
|
+
> ℹ️ **Info:**
|
|
21
|
+
> mode: 'hosted' is the preferred value. The legacy alias mode: 'c15t' is still supported for backward compatibility.
|
|
22
|
+
|
|
23
|
+
**What happens:**
|
|
24
|
+
|
|
25
|
+
1. On page load, the client calls `/init` to fetch geolocation, jurisdiction, localized translation strings
|
|
26
|
+
2. When the user grants or changes consent, it is saved locally before being synced to the backend.
|
|
27
|
+
3. If the backend is unreachable, it falls back to Offline mode and re-syncs with the backend when it's available
|
|
28
|
+
|
|
29
|
+
**Configuration:**
|
|
30
|
+
|
|
31
|
+
* `backendURL` (required) - API endpoint path
|
|
32
|
+
|
|
33
|
+
**Best for:** Production apps that need geolocation-based jurisdiction detection, consent record storage, and compliance audit trails.
|
|
34
|
+
|
|
35
|
+
```tsx
|
|
36
|
+
import { type ReactNode } from 'react';
|
|
37
|
+
import { ConsentManagerProvider, ConsentBanner } from '@c15t/nextjs';
|
|
38
|
+
|
|
39
|
+
export function ConsentManager({ children }: { children: ReactNode }) {
|
|
40
|
+
return (
|
|
41
|
+
<ConsentManagerProvider
|
|
42
|
+
options={{
|
|
43
|
+
mode: 'hosted',
|
|
44
|
+
backendURL: '/api/c15t',
|
|
45
|
+
}}
|
|
46
|
+
>
|
|
47
|
+
<ConsentBanner />
|
|
48
|
+
{children}
|
|
49
|
+
</ConsentManagerProvider>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Offline Mode
|
|
55
|
+
|
|
56
|
+
No network requests. Consent is stored entirely in the browser using localStorage and cookies.
|
|
57
|
+
|
|
58
|
+
**What happens:**
|
|
59
|
+
|
|
60
|
+
1. Default jurisdiction is GDPR unless manually set via `overrides`
|
|
61
|
+
2. Consent preferences persist locally only
|
|
62
|
+
3. No server-side consent records or analytics
|
|
63
|
+
|
|
64
|
+
> ℹ️ **Info:**
|
|
65
|
+
> Important: Offline mode still stores consent locally (cookie + localStorage). It only skips backend storage and sync.
|
|
66
|
+
|
|
67
|
+
### Consequences of Browser-Only Storage
|
|
68
|
+
|
|
69
|
+
If consent is not stored at all (for example, storage is blocked or frequently cleared):
|
|
70
|
+
|
|
71
|
+
* Users are treated as new visitors and must re-consent repeatedly
|
|
72
|
+
* Preferences are lost across browser resets, private sessions, and device changes
|
|
73
|
+
* You have no reliable audit evidence to prove prior consent choices
|
|
74
|
+
* Support and compliance teams have no centralized visibility into consent history by default
|
|
75
|
+
* Server-side systems cannot apply prior consent decisions before client initialization
|
|
76
|
+
|
|
77
|
+
**Trade-offs:**
|
|
78
|
+
|
|
79
|
+
* No automatic geolocation or jurisdiction detection
|
|
80
|
+
* No consent audit trail
|
|
81
|
+
* No cross-device sync
|
|
82
|
+
* Works without any backend infrastructure
|
|
83
|
+
|
|
84
|
+
**Best for:** Static sites, development/testing, or as a starting point before setting up a backend.
|
|
85
|
+
|
|
86
|
+
```tsx
|
|
87
|
+
import { type ReactNode } from 'react';
|
|
88
|
+
import { ConsentManagerProvider, ConsentBanner } from '@c15t/nextjs';
|
|
89
|
+
|
|
90
|
+
export function ConsentManager({ children }: { children: ReactNode }) {
|
|
91
|
+
return (
|
|
92
|
+
<ConsentManagerProvider
|
|
93
|
+
options={{
|
|
94
|
+
mode: 'offline',
|
|
95
|
+
overrides: {
|
|
96
|
+
country: 'DE', // Override country to trigger GDPR jurisdiction
|
|
97
|
+
},
|
|
98
|
+
}}
|
|
99
|
+
>
|
|
100
|
+
<ConsentBanner />
|
|
101
|
+
{children}
|
|
102
|
+
</ConsentManagerProvider>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
## Custom Mode
|
|
108
|
+
|
|
109
|
+
Bring your own backend. You provide handler functions for each consent endpoint, and c15t calls them instead of making HTTP requests.
|
|
110
|
+
|
|
111
|
+
**What happens:**
|
|
112
|
+
|
|
113
|
+
1. On page load, the client calls your endpoint handler to fetch geolocation, jurisdiction, localized translation strings
|
|
114
|
+
2. When the user grants or changes consent, it is saved locally before being synced to the backend.
|
|
115
|
+
3. If one of your handlers fails, c15t returns a handler error and keeps local consent state, but automatic offline fallback and retry queue behavior is not provided for custom handlers by default
|
|
116
|
+
|
|
117
|
+
This lets you integrate c15t with any existing API - your CRM, your own consent database, or a third-party compliance service.
|
|
118
|
+
|
|
119
|
+
**Best for:** Teams with existing consent infrastructure that want c15t's frontend without its backend.
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
import { type ReactNode } from 'react';
|
|
123
|
+
import { ConsentManagerProvider, ConsentBanner } from '@c15t/nextjs';
|
|
124
|
+
|
|
125
|
+
export function ConsentManager({ children }: { children: ReactNode }) {
|
|
126
|
+
return (
|
|
127
|
+
<ConsentManagerProvider
|
|
128
|
+
options={{
|
|
129
|
+
mode: 'custom',
|
|
130
|
+
endpointHandlers: {
|
|
131
|
+
async init() {
|
|
132
|
+
const res = await fetch('/my-api/consent/init');
|
|
133
|
+
const data = await res.json();
|
|
134
|
+
return { data, response: res, error: null };
|
|
135
|
+
},
|
|
136
|
+
async setConsent(options) {
|
|
137
|
+
const res = await fetch('/my-api/consent', {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: { 'Content-Type': 'application/json' },
|
|
140
|
+
body: JSON.stringify(options?.body),
|
|
141
|
+
});
|
|
142
|
+
return { data: await res.json(), response: res, error: null };
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
}}
|
|
146
|
+
>
|
|
147
|
+
<ConsentBanner />
|
|
148
|
+
{children}
|
|
149
|
+
</ConsentManagerProvider>
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
## Choosing a Mode
|
|
155
|
+
|
|
156
|
+
|Feature|Hosted|Offline|Custom|
|
|
157
|
+
|--|--|--|--|
|
|
158
|
+
|Geolocation|Automatic|Manual via overrides|Your implementation|
|
|
159
|
+
|Consent sync|API|Local only|Your implementation|
|
|
160
|
+
|SSR data|Supported|Not available|Your implementation|
|
|
161
|
+
|Analytics|Built-in|Not available|Your implementation|
|
|
162
|
+
|Infrastructure|c15t backend|None|Your backend|
|
|
163
|
+
|Setup effort|Minimal|Zero|Moderate|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Consent Categories
|
|
3
|
+
description: How c15t organizes tracking technologies into five consent categories.
|
|
4
|
+
---
|
|
5
|
+
c15t organizes tracking technologies into five consent categories that align with GDPR and ePrivacy Directive requirements. Rather than asking users to approve or deny individual cookies or scripts, each category groups related tracking purposes together so users can make meaningful, informed choices about how their data is used.
|
|
6
|
+
|
|
7
|
+
> ℹ️ **Info:**
|
|
8
|
+
> Why categories, not cookie lists? Many consent banners list individual cookie names like \_ga, \_gid, or \_fbp. This is counterproductive:Technical names are meaningless to users — nobody knows what \_gid does by reading its name.Information overload drives "accept all" — a wall of cookie names pushes users toward dismissing the banner as fast as possible, which is the opposite of informed consent.Purpose is what matters — privacy regulations (GDPR, ePrivacy) require clear information about the purposes of data processing, not a cookie-by-cookie inventory.Cookie lists go stale — third-party scripts change their cookie names across versions, creating a maintenance burden that provides no real transparency.c15t's category-based approach — "measurement", "marketing", "experience" — communicates purpose directly. Users understand why data is collected, not how it is stored.
|
|
9
|
+
|
|
10
|
+
Configure which categories your app supports in the provider:
|
|
11
|
+
|
|
12
|
+
```tsx
|
|
13
|
+
import { type ReactNode } from 'react';
|
|
14
|
+
import { ConsentManagerProvider } from '@c15t/nextjs';
|
|
15
|
+
|
|
16
|
+
export function ConsentManager({ children }: { children: ReactNode }) {
|
|
17
|
+
return (
|
|
18
|
+
<ConsentManagerProvider
|
|
19
|
+
options={{
|
|
20
|
+
mode: 'hosted',
|
|
21
|
+
backendURL: '/api/c15t',
|
|
22
|
+
consentCategories: ['necessary', 'measurement', 'marketing'],
|
|
23
|
+
}}
|
|
24
|
+
>
|
|
25
|
+
{children}
|
|
26
|
+
</ConsentManagerProvider>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## The Five Categories
|
|
32
|
+
|
|
33
|
+
|Category|Default|Toggleable|Description|
|
|
34
|
+
|--|--|--|--|
|
|
35
|
+
|`necessary`|`true`|No|Strictly necessary to operate or deliver the service|
|
|
36
|
+
|`functionality`|`false`|Yes|Basic interactions and functionalities|
|
|
37
|
+
|`experience`|`false`|Yes|Improve quality of user experience|
|
|
38
|
+
|`measurement`|`false`|Yes|Measure traffic and analyze behavior|
|
|
39
|
+
|`marketing`|`false`|Yes|Deliver personalized ads or marketing content|
|
|
40
|
+
|
|
41
|
+
The `necessary` category has `disabled: true` set internally, which prevents the user from toggling it off in the consent UI. All other categories can be freely toggled by the user.
|
|
42
|
+
|
|
43
|
+
Note that all categories except `necessary` have `display: false` by default. A category appears in the consent UI only if you include it in `consentCategories`. Categories not listed are hidden from the UI, but they still exist in consent state and may be affected by model-level behavior (for example, auto-grant in `opt-out` or `null` model flows).
|
|
44
|
+
|
|
45
|
+
Check if a specific category has consent using the `has()` method:
|
|
46
|
+
|
|
47
|
+
```tsx
|
|
48
|
+
import { useConsentManager } from '@c15t/nextjs';
|
|
49
|
+
|
|
50
|
+
function AnalyticsLoader() {
|
|
51
|
+
const { has } = useConsentManager();
|
|
52
|
+
|
|
53
|
+
if (has('measurement')) {
|
|
54
|
+
// Safe to load analytics scripts
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Configuring Categories
|
|
62
|
+
|
|
63
|
+
The `consentCategories` array controls which consent categories are presented to the user in the consent UI. Only categories you list in this array will appear as toggleable options in the consent banner or modal.
|
|
64
|
+
|
|
65
|
+
The `necessary` category is always implicitly included even if you do not add it to the array. You never need to worry about accidentally omitting it -- c15t ensures it is always present and always enabled.
|
|
66
|
+
|
|
67
|
+
For example, if you set:
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
consentCategories: ['necessary', 'measurement', 'marketing']
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
then only those three categories will show toggles in the consent UI. The `functionality` and `experience` categories will not appear as user-configurable toggles.
|
|
74
|
+
|
|
75
|
+
In `opt-in`/`iab` flows, hidden categories usually remain `false` unless you explicitly set them. In `opt-out`/`null` flows, categories may be auto-granted even when hidden.
|
|
76
|
+
|
|
77
|
+
This gives you precise control over which consent choices to present to your users. A simple blog that only runs an analytics script might only need `measurement`. A media site with ad integrations would include `marketing`. A SaaS application with personalization features might add `experience` and `functionality` as well. You choose what is relevant to your site and c15t handles the rest - storing consent state, exposing it through hooks, and ensuring the right categories are active based on the user's choices.
|
|
78
|
+
|
|
79
|
+
You can dynamically update which categories are active:
|
|
80
|
+
|
|
81
|
+
```tsx
|
|
82
|
+
import { useConsentManager } from '@c15t/nextjs';
|
|
83
|
+
|
|
84
|
+
function CategoryManager() {
|
|
85
|
+
const { consentCategories, setConsentCategories } = useConsentManager();
|
|
86
|
+
|
|
87
|
+
const addExperienceCategory = () => {
|
|
88
|
+
setConsentCategories([...consentCategories, 'experience']);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
return (
|
|
92
|
+
<button onClick={addExperienceCategory}>
|
|
93
|
+
Enable experience features
|
|
94
|
+
</button>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
```
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Consent Models
|
|
3
|
+
description: How c15t determines consent behavior based on legal jurisdiction.
|
|
4
|
+
---
|
|
5
|
+
c15t supports four consent models that control how consent defaults, banner visibility, and category gating behave:
|
|
6
|
+
|
|
7
|
+
|Model|Philosophy|Banner|Categories default to|
|
|
8
|
+
|--|--|--|--|
|
|
9
|
+
|`opt-in`|Explicit consent required|Blocking banner|`false` (except `necessary`)|
|
|
10
|
+
|`opt-out`|Processing allowed by default|Non-blocking notice|`true` (all granted)|
|
|
11
|
+
|`iab`|IAB TCF 2.3 for programmatic ads|TCF banner|Managed by TCF framework|
|
|
12
|
+
|`null`|No regulation detected|No banner|`true` (all auto-granted)|
|
|
13
|
+
|
|
14
|
+
There are two ways c15t determines which model applies:
|
|
15
|
+
|
|
16
|
+
1. **Policy packs** (recommended) — you explicitly set the model per region in a `PolicyConfig`. This gives you full control over which regions get which model. See [Policy Packs](/docs/frameworks/react/concepts/policy-packs).
|
|
17
|
+
|
|
18
|
+
2. **Automatic jurisdiction mapping** (legacy default) — when no policy pack is configured, c15t detects the visitor's jurisdiction via geolocation and maps it to a model using the table below. This still works but gives you less control over categories, UI, and scope.
|
|
19
|
+
|
|
20
|
+
> ℹ️ **Info:**
|
|
21
|
+
> When using policy packs, the consent.model field in each policy directly sets the model — the automatic jurisdiction mapping is bypassed for that request.
|
|
22
|
+
|
|
23
|
+
Read the current consent model from the hook:
|
|
24
|
+
|
|
25
|
+
```tsx
|
|
26
|
+
'use client';
|
|
27
|
+
|
|
28
|
+
import { useConsentManager } from '@c15t/nextjs';
|
|
29
|
+
|
|
30
|
+
function ConsentStatus() {
|
|
31
|
+
const { model, policy } = useConsentManager();
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<p>
|
|
35
|
+
Current consent model: {model ?? 'detecting...'}
|
|
36
|
+
{policy && ` (from policy: ${policy.id})`}
|
|
37
|
+
</p>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## The Four Models
|
|
43
|
+
|
|
44
|
+
### Opt-in
|
|
45
|
+
|
|
46
|
+
The strictest consent model, used for GDPR and similar regulations that require explicit, affirmative consent before any non-essential data processing occurs. All consent categories except `necessary` default to `false`. A consent banner must be shown before any tracking scripts load.
|
|
47
|
+
|
|
48
|
+
Applies to: EU (GDPR), UK (UK GDPR), Switzerland, Brazil (LGPD), Japan (APPI), South Korea (PIPA), Quebec (Law 25). Also the fallback for unknown jurisdiction codes.
|
|
49
|
+
|
|
50
|
+
### Opt-out
|
|
51
|
+
|
|
52
|
+
Used for CCPA-style regulations where data processing is permitted by default until the user exercises their right to opt out. All consent categories default to `true`. A blocking banner is not required — the typical pattern is a non-intrusive notice or footer link.
|
|
53
|
+
|
|
54
|
+
Applies to: California (CCPA), Canada (PIPEDA), Australia.
|
|
55
|
+
|
|
56
|
+
When the policy has `consent.gpc: true`, the browser's Global Privacy Control signal (`Sec-GPC: 1` or `navigator.globalPrivacyControl`) is respected — `marketing` and `measurement` are denied while other categories remain granted. The built-in California presets enable this by default. See [Policy Packs — GPC](/docs/frameworks/react/concepts/policy-packs#gpc) for details.
|
|
57
|
+
|
|
58
|
+
### IAB
|
|
59
|
+
|
|
60
|
+
IAB Transparency and Consent Framework (TCF) 2.3 mode for programmatic advertising compliance. Only activates when two conditions are met: the jurisdiction is `GDPR` or `UK_GDPR`, and `iab.enabled` is `true` in your configuration.
|
|
61
|
+
|
|
62
|
+
When active, IAB mode generates TC strings, registers the `__tcfapi` CMP API, and works with the Global Vendor List (GVL) for machine-readable consent signals. If `iab.enabled` is not set, GDPR jurisdictions fall back to standard opt-in.
|
|
63
|
+
|
|
64
|
+
### null
|
|
65
|
+
|
|
66
|
+
Returned when no jurisdiction is detected (`NONE` or `null`). No banner is displayed. On first visit, all categories are auto-granted.
|
|
67
|
+
|
|
68
|
+
## Jurisdiction Mapping
|
|
69
|
+
|
|
70
|
+
When no policy pack is configured, c15t maps jurisdictions to models automatically:
|
|
71
|
+
|
|
72
|
+
|Jurisdiction Code|Region|Consent Model|
|
|
73
|
+
|--|--|--|
|
|
74
|
+
|`GDPR`|European Union|opt-in|
|
|
75
|
+
|`UK_GDPR`|United Kingdom|opt-in|
|
|
76
|
+
|`CH`|Switzerland|opt-in|
|
|
77
|
+
|`BR`|Brazil (LGPD)|opt-in|
|
|
78
|
+
|`APPI`|Japan|opt-in|
|
|
79
|
+
|`PIPA`|South Korea|opt-in|
|
|
80
|
+
|`PIPEDA`|Canada (excl. Quebec)|opt-out|
|
|
81
|
+
|`QC_LAW25`|Quebec, Canada|opt-in|
|
|
82
|
+
|`CCPA`|California, USA|opt-out|
|
|
83
|
+
|`AU`|Australia|opt-out|
|
|
84
|
+
|`NONE`|No jurisdiction|null|
|
|
85
|
+
|*(unknown)*|Any other|opt-in|
|
|
86
|
+
|
|
87
|
+
**IAB override:** If `iab.enabled: true` and the jurisdiction is `GDPR` or `UK_GDPR`, the model becomes `'iab'` instead of `'opt-in'`. This override only applies to those two jurisdictions.
|
|
88
|
+
|
|
89
|
+
> ℹ️ **Info:**
|
|
90
|
+
> With policy packs, you set the model explicitly per policy — the automatic mapping above is only used as a fallback when no policy pack is configured, or for non-policy-pack features like auto-granting in opt-out jurisdictions.
|
|
91
|
+
|
|
92
|
+
Override the detected jurisdiction for testing:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
'use client';
|
|
96
|
+
|
|
97
|
+
import { useConsentManager } from '@c15t/nextjs';
|
|
98
|
+
|
|
99
|
+
function JurisdictionTester() {
|
|
100
|
+
const { setOverrides } = useConsentManager();
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
<div>
|
|
104
|
+
<button onClick={() => setOverrides({ country: 'DE' })}>
|
|
105
|
+
Test as EU visitor
|
|
106
|
+
</button>
|
|
107
|
+
<button onClick={() => setOverrides({ country: 'US', region: 'CA' })}>
|
|
108
|
+
Test as California visitor
|
|
109
|
+
</button>
|
|
110
|
+
</div>
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
> ℹ️ **Info:**
|
|
116
|
+
> For full control over which model applies where — plus UI customization, category scoping, and re-prompting — see Policy Packs.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Cookie Management
|
|
3
|
+
description: How c15t manages cookies through script, iframe, and network gating
|
|
4
|
+
lastModified: 2026-02-10
|
|
5
|
+
---
|
|
6
|
+
Cookie management is the most commonly misunderstood part of consent. A critical distinction: **c15t does not manage all cookies on your site.** It controls what scripts, iframes, and network requests are allowed to load - and those third-party resources are what set most cookies.
|
|
7
|
+
|
|
8
|
+
Understanding this distinction is key to building a compliant consent flow: c15t gates the sources of cookies, not the cookies themselves.
|
|
9
|
+
|
|
10
|
+
## What Actually Sets Cookies
|
|
11
|
+
|
|
12
|
+
There are four sources of cookies on a typical website:
|
|
13
|
+
|
|
14
|
+
**1. c15t itself**
|
|
15
|
+
c15t stores the user's consent state in a `c15t` cookie (and mirrors it to localStorage). This cookie records which categories the user consented to, when they consented, and their subject ID. This is a strictly necessary cookie - it exists so the site remembers the user's consent choice.
|
|
16
|
+
|
|
17
|
+
**2. Third-party scripts**
|
|
18
|
+
When you load a tracking script (Google Analytics, Meta Pixel, etc.), that script sets its own cookies. Google Analytics creates `_ga` and `_gid` cookies. Meta creates `_fbp` and `_fbc`. These cookies are set by the script's code running in the browser - c15t prevents them by not loading the script until the user consents.
|
|
19
|
+
|
|
20
|
+
**3. Embedded iframes**
|
|
21
|
+
YouTube embeds, social media widgets, and other iframes can set cookies via their embedded content. c15t's iframe blocker replaces iframes with placeholders until consent is granted, preventing these cookies from being set.
|
|
22
|
+
|
|
23
|
+
**4. Network requests**
|
|
24
|
+
Server responses can include `Set-Cookie` headers. If your page makes requests to third-party APIs or CDNs, those responses may set cookies. c15t's network blocker can intercept `fetch` and `XMLHttpRequest` calls to prevent these requests from happening without consent.
|
|
25
|
+
|
|
26
|
+
> ℹ️ **Info:**
|
|
27
|
+
> Don't list these cookies in your banner. The cookie names above (\_ga, \_gid, \_fbp, \_fbc) are implementation details — they matter for developers understanding how tracking works, but they should not be exposed to end users in your consent UI. Users don't know what \_ga means, and listing it doesn't help them make an informed choice. Instead, use purpose-based consent categories like "measurement" or "marketing" that communicate why data is collected, not how it is stored.
|
|
28
|
+
|
|
29
|
+
## Why Revoking Consent Requires a Page Reload
|
|
30
|
+
|
|
31
|
+
When a user revokes consent for a category (e.g., turns off "measurement" after previously granting it), c15t reloads the page by default. This is not a limitation - it's the only reliable approach.
|
|
32
|
+
|
|
33
|
+
**Why you can't just delete third-party cookies from JavaScript:**
|
|
34
|
+
|
|
35
|
+
* **`httpOnly` cookies** - Many tracking cookies are set with the `httpOnly` flag, which prevents JavaScript from reading or deleting them. Only the server that set them can remove them.
|
|
36
|
+
* **Domain restrictions** - Cookies set on `.google.com` or `.facebook.com` can only be deleted by those domains. Your JavaScript running on `yourdomain.com` has no access.
|
|
37
|
+
* **Alternative storage** - Some scripts also write to `localStorage`, `sessionStorage`, `IndexedDB`, or even Web Workers. Cleaning up all possible storage locations is impractical.
|
|
38
|
+
* **In-memory state** - A loaded script has already executed. Its event listeners, timers, and in-memory data persist until the page unloads. You can't "unrun" JavaScript.
|
|
39
|
+
|
|
40
|
+
**The reliable solution: don't load the scripts in the first place.** A page reload creates a fresh execution context. On the fresh page, c15t reads the updated consent state and simply never loads the scripts that lost consent. No script means no cookies, no tracking, no in-memory state.
|
|
41
|
+
|
|
42
|
+
**When reload does NOT happen:**
|
|
43
|
+
|
|
44
|
+
* When a user is declining consent for the **first time** (no prior consent existed, so no scripts were loaded to clean up)
|
|
45
|
+
* When `reloadOnConsentRevoked` is set to `false`
|
|
46
|
+
* When the user is only **adding** consent (no revocations)
|
|
47
|
+
|
|
48
|
+
Configure reload behavior in the provider:
|
|
49
|
+
|
|
50
|
+
```tsx
|
|
51
|
+
import { type ReactNode } from 'react';
|
|
52
|
+
import { ConsentManagerProvider } from '@c15t/nextjs';
|
|
53
|
+
|
|
54
|
+
function ConsentManager({ children }: { children: ReactNode }) {
|
|
55
|
+
return (
|
|
56
|
+
<ConsentManagerProvider
|
|
57
|
+
options={{
|
|
58
|
+
mode: 'hosted',
|
|
59
|
+
backendURL: '/api/c15t',
|
|
60
|
+
reloadOnConsentRevoked: true, // default: true
|
|
61
|
+
callbacks: {
|
|
62
|
+
onBeforeConsentRevocationReload: ({ preferences }) => {
|
|
63
|
+
// Run cleanup before the page reloads
|
|
64
|
+
// e.g., flush pending analytics events
|
|
65
|
+
console.log('Reloading due to consent revocation:', preferences);
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
{children}
|
|
71
|
+
</ConsentManagerProvider>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
> ℹ️ **Info:**
|
|
77
|
+
> The reload and deferred sync are part of the broader initialization flow. See Initialization Flow for the full sequence.
|
|
78
|
+
>
|
|
79
|
+
> ℹ️ **Info:**
|
|
80
|
+
> Setting reloadOnConsentRevoked: false means previously-loaded scripts will continue running after consent is revoked. Only disable this if you have a specific strategy for handling script cleanup.
|
|
81
|
+
|
|
82
|
+
## The Revocation Flow
|
|
83
|
+
|
|
84
|
+
When a user revokes consent, the following sequence occurs:
|
|
85
|
+
|
|
86
|
+
**Simplified**
|
|
87
|
+
|
|
88
|
+
1. **User revokes consent** — e.g. turns off "measurement" in the consent dialog
|
|
89
|
+
2. **New consent saved** — updated preferences are written to cookies and localStorage
|
|
90
|
+
3. **Pending sync stored** — the API update is deferred to localStorage (`c15t:pending-consent-sync`)
|
|
91
|
+
4. **Page reloads** — a fresh execution context ensures revoked scripts never load
|
|
92
|
+
5. **Fresh init** — c15t reads updated consent; scripts without consent are never loaded
|
|
93
|
+
6. **Deferred API sync** — the pending consent change is sent to the backend and cleared from localStorage
|
|
94
|
+
|
|
95
|
+
**Sequence Diagram**
|
|
96
|
+
|
|
97
|
+
```mermaid
|
|
98
|
+
sequenceDiagram
|
|
99
|
+
participant User
|
|
100
|
+
participant UI as Consent UI
|
|
101
|
+
participant Store as Consent Store
|
|
102
|
+
participant Storage as Cookie + localStorage
|
|
103
|
+
participant API as c15t Backend
|
|
104
|
+
participant Browser
|
|
105
|
+
|
|
106
|
+
User->>UI: Revokes "measurement" consent
|
|
107
|
+
UI->>Store: saveConsents({ type: 'custom' })
|
|
108
|
+
Store->>Store: shouldReloadOnConsentChange() → true
|
|
109
|
+
Store->>Storage: Save new consent state
|
|
110
|
+
Store->>Storage: Store PendingConsentSync in localStorage
|
|
111
|
+
Store->>Browser: onBeforeConsentRevocationReload callback
|
|
112
|
+
Store->>Browser: window.location.reload()
|
|
113
|
+
Browser->>Browser: Fresh page load
|
|
114
|
+
Browser->>Storage: Read consent state
|
|
115
|
+
Browser->>Store: Initialize with updated consents
|
|
116
|
+
Note over Store: Scripts without consent are never loaded
|
|
117
|
+
Store->>Storage: Read PendingConsentSync
|
|
118
|
+
Store->>API: Sync consent to backend
|
|
119
|
+
Store->>Storage: Clear PendingConsentSync
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Key detail:** The API sync happens *after* the reload, not before. This ensures the page reloads as fast as possible. The pending sync data is stored in localStorage under the key `c15t:pending-consent-sync` and is picked up by the fresh page's initialization.
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Glossary
|
|
3
|
+
description: Key terms used throughout the c15t documentation.
|
|
4
|
+
---
|
|
5
|
+
### Core Terms
|
|
6
|
+
|
|
7
|
+
|Term|Definition|
|
|
8
|
+
|--|--|
|
|
9
|
+
|`subjectId`|Client-generated device/browser identifier (`sub_xxx`). Stored in the `c15t` cookie. Created on first consent save.|
|
|
10
|
+
|`externalId`|Your authenticated user's ID (from Clerk, Auth0, etc.), linked via `identifyUser()`. Connects a device to a user account.|
|
|
11
|
+
|`consents`|The **saved** consent state — `Record<string, boolean>` mapping categories to granted/denied. Used for gating scripts, iframes, and network requests.|
|
|
12
|
+
|`selectedConsents`|**Unsaved** toggle state — what the user has toggled in the dialog but hasn't submitted yet. Becomes `consents` after save.|
|
|
13
|
+
|`mode`|Client operating mode: `'hosted'` (full backend), `'offline'` (local only), or `'custom'` (bring your own handlers). Legacy alias: `'c15t'`. Set once in provider config. See [Client Modes](/docs/frameworks/react/concepts/client-modes).|
|
|
14
|
+
|`model`|Consent regulatory model derived from jurisdiction: `'opt-in'`, `'opt-out'`, `'iab'`, or `null`. See [Consent Models](/docs/frameworks/react/concepts/consent-models).|
|
|
15
|
+
|
|
16
|
+
### Regulatory Terms
|
|
17
|
+
|
|
18
|
+
|Term|Definition|
|
|
19
|
+
|--|--|
|
|
20
|
+
|`jurisdiction`|A privacy regulation code (e.g., `GDPR`, `CCPA`, `PIPEDA`, `QC_LAW25`) detected from the user's geolocation. Maps to a consent model. See [Consent Models](/docs/frameworks/react/concepts/consent-models) for the full mapping table.|
|
|
21
|
+
|GPC|Global Privacy Control — a browser signal (`Sec-GPC: 1`) indicating the user opts out of data sale/sharing. Honored when a policy sets `consent.gpc: true` (enabled by default in California presets).|
|
|
22
|
+
|TCF|IAB Transparency and Consent Framework (v2.3) — a standard for programmatic advertising consent in GDPR jurisdictions. See [IAB TCF](/docs/frameworks/react/iab/overview).|
|
|
23
|
+
|GVL|Global Vendor List — an IAB-maintained registry of ad-tech vendors and their declared purposes, used by the TCF consent flow.|
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Initialization Flow
|
|
3
|
+
description: What happens from provider mount to first render — the full consent lifecycle.
|
|
4
|
+
---
|
|
5
|
+
When the consent provider mounts, it creates a cached consent runtime, reads any stored consent from the browser, fetches the resolved policy from the backend (or uses SSR/offline data), and decides whether to show the banner. This entire sequence completes before the first meaningful consent-aware render.
|
|
6
|
+
|
|
7
|
+
## Lifecycle Sequence
|
|
8
|
+
|
|
9
|
+
**Simplified**
|
|
10
|
+
|
|
11
|
+
1. **Provider mounts** — creates (or retrieves from cache) a consent runtime and store
|
|
12
|
+
2. **Check stored consent** — reads existing consent from cookies / localStorage; if found and the policy fingerprint hasn't changed, the banner stays hidden
|
|
13
|
+
3. **Fetch init data** — calls the backend `GET /init` (or uses SSR/offline data) for the resolved policy, location, and translations
|
|
14
|
+
4. **Apply resolved policy** — the backend resolves the policy from your [policy pack](/docs/frameworks/react/concepts/policy-packs) based on visitor geo (region → country → fallback → default). The response includes the consent model, categories, UI mode, and a material fingerprint. If no policy pack is configured, the legacy jurisdiction-to-model mapping is used instead.
|
|
15
|
+
5. **Decide banner visibility** — shows the banner only if no prior consent exists, the resolved policy requires it (`ui.mode` is `banner` or `dialog`), or the policy fingerprint changed since last consent
|
|
16
|
+
6. **Gating enforced** — scripts, iframes, and network requests tagged with a consent category are blocked until that category is granted
|
|
17
|
+
7. **User interacts** — choices are persisted to storage, synced to the backend (with `policySnapshotToken` if configured), and blocked scripts/iframes load immediately after consent is granted
|
|
18
|
+
|
|
19
|
+
**Sequence Diagram**
|
|
20
|
+
|
|
21
|
+
```mermaid
|
|
22
|
+
sequenceDiagram
|
|
23
|
+
participant Provider as ConsentManagerProvider
|
|
24
|
+
participant Store as Consent Store
|
|
25
|
+
participant Storage as localStorage / Cookie
|
|
26
|
+
participant API as c15t Backend
|
|
27
|
+
participant UI as Banner / Dialog
|
|
28
|
+
participant Scripts as Script Loader
|
|
29
|
+
|
|
30
|
+
Provider->>Store: getOrCreateConsentRuntime()
|
|
31
|
+
Store->>Storage: getStoredConsent()
|
|
32
|
+
alt Stored consent exists
|
|
33
|
+
Storage-->>Store: consentInfo + consents
|
|
34
|
+
Store->>Store: activeUI = 'none'
|
|
35
|
+
else No stored consent
|
|
36
|
+
Store->>Store: isLoadingConsentInfo = true
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
Store->>Store: initConsentManager()
|
|
40
|
+
Store->>Storage: Check pending consent sync
|
|
41
|
+
opt Pending sync from revocation reload
|
|
42
|
+
Store->>API: Deferred setConsent() (non-blocking)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
alt SSR data provided
|
|
46
|
+
Store->>Store: tryUseSSRData()
|
|
47
|
+
else No SSR data
|
|
48
|
+
Store->>API: GET /init
|
|
49
|
+
API-->>Store: policy, policyDecision, location, translations
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
Store->>Store: Apply resolved policy (model, categories, ui.mode)
|
|
53
|
+
Store->>Store: Check fingerprint change → re-prompt if needed
|
|
54
|
+
Store->>Store: Set activeUI, auto-grant if opt-out/none
|
|
55
|
+
|
|
56
|
+
alt User has no prior consent or policy changed
|
|
57
|
+
UI->>UI: Banner / Dialog appears (per policy ui.mode)
|
|
58
|
+
UI->>Store: saveConsents({ type })
|
|
59
|
+
Store->>Storage: Persist consent + subjectId + fingerprint
|
|
60
|
+
Store->>Scripts: updateScripts(), updateIframes(), updateNetwork()
|
|
61
|
+
Store->>API: POST /subjects + policySnapshotToken (non-blocking)
|
|
62
|
+
end
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## How It Works
|
|
66
|
+
|
|
67
|
+
**Mount** — When the provider renders, it creates (or retrieves from cache) a consent runtime and store. Any existing consent is read from localStorage/cookies immediately. If consent already exists and the policy fingerprint matches, the banner stays hidden and gating rules apply right away. See [Client Modes](/docs/frameworks/react/concepts/client-modes) for how the mode affects runtime creation.
|
|
68
|
+
|
|
69
|
+
**Init** — The store fetches the resolved policy, location, and translation data. In hosted mode this calls `GET /init` on your backend; in offline mode it resolves from `offlinePolicy.policyPacks` locally. If SSR data was passed to the provider, the network fetch is skipped entirely. See [Server-Side Utilities](/docs/frameworks/react/server-side) for SSR setup.
|
|
70
|
+
|
|
71
|
+
**Policy resolution** — When [policy packs](/docs/frameworks/react/concepts/policy-packs) are configured, the backend resolves the right policy for the visitor based on their geo-location (region → country → fallback → default). The resolved policy determines the consent model (`opt-in`, `opt-out`, `iab`, or `none`), which categories are in scope, and what UI to show. For `opt-out` and `none` models, all categories are auto-granted — unless the resolved policy has `consent.gpc: true` and the browser sends a Global Privacy Control signal, in which case `marketing` and `measurement` are denied. If no policy pack is configured, the legacy jurisdiction-to-model mapping is used instead. See [Consent Models](/docs/frameworks/react/concepts/consent-models) for details.
|
|
72
|
+
|
|
73
|
+
**Re-prompting** — If the resolved policy's material fingerprint differs from the fingerprint stored with the user's last consent, the banner is shown again. This happens automatically when you change consent-affecting fields (model, categories, scope mode, allowed actions). Presentation-only changes do not trigger re-prompts. See [Policy Packs — Re-Prompting](/docs/frameworks/react/concepts/policy-packs#re-prompting) for details.
|
|
74
|
+
|
|
75
|
+
**Save** — When the user interacts with the banner or dialog, their choices are persisted to localStorage/cookies and synced to the backend (along with the `policySnapshotToken` if snapshot signing is configured). Script, iframe, and network gating rules update immediately based on the new consent state. See the [Script Loader](/docs/frameworks/react/script-loader), [Iframe Blocking](/docs/frameworks/react/iframe-blocking), and [Network Blocker](/docs/frameworks/react/network-blocker) guides for gating details.
|
|
76
|
+
|
|
77
|
+
**Revocation** — If a user revokes a previously granted category, the page reloads by default to ensure a clean execution environment. The API sync is deferred to the fresh page load. See [Cookie Management](/docs/frameworks/react/concepts/cookie-management) for revocation and persistence details.
|
|
78
|
+
|
|
79
|
+
## When Does the Banner Show?
|
|
80
|
+
|
|
81
|
+
The banner appears when any of these conditions are true:
|
|
82
|
+
|
|
83
|
+
1. **No existing consent** — the user has never consented (or their consent was cleared), **and** the resolved policy requires a UI (`ui.mode` is `banner` or `dialog`, or the model is `opt-in` or `iab`)
|
|
84
|
+
2. **Policy changed** — the material policy fingerprint differs from the fingerprint stored with the user's last consent (re-prompting)
|
|
85
|
+
3. **Storage is accessible** — the browser allows localStorage (not blocked in private mode)
|
|
86
|
+
|
|
87
|
+
If the resolved model is `none` or `opt-out` (and `ui.mode` is `none`), consents are auto-granted and the banner never appears. See [Consent Models](/docs/frameworks/react/concepts/consent-models) and [Policy Packs](/docs/frameworks/react/concepts/policy-packs) for details.
|
|
88
|
+
|
|
89
|
+
## Debugging the Lifecycle
|
|
90
|
+
|
|
91
|
+
Use the DevTools panel and callbacks to inspect each step of the initialization flow:
|
|
92
|
+
|
|
93
|
+
|Step|DevTools Panel|Callback|What to check|
|
|
94
|
+
|--|--|--|--|
|
|
95
|
+
|Init / SSR hydration|Location|`onBannerFetched`|jurisdiction, countryCode, regionCode populated?|
|
|
96
|
+
|Policy resolution|Policy|`onBannerFetched`|`policyId`, `matchedBy`, `fingerprint` in policyDecision|
|
|
97
|
+
|Model resolution|Location|`onBannerFetched`|`model` value matches the resolved policy|
|
|
98
|
+
|Banner visibility|Consents|—|`activeUI` in store state; does policy `ui.mode` require it?|
|
|
99
|
+
|Re-prompting|Policy|—|Fingerprint mismatch between stored and resolved policy?|
|
|
100
|
+
|Consent save|Consents + Events|`onConsentSet`|`preferences` object in callback payload|
|
|
101
|
+
|Script loading|Scripts|`onConsentSet`|Script IDs and their load/blocked status|
|
|
102
|
+
|Reload on revocation|Events|`onBeforeConsentRevocationReload`|Fires before reload; check localStorage for `c15t:pending-consent-sync`|
|
|
103
|
+
|Deferred sync|Events|`onError` (if sync fails)|After reload, check Events panel for successful API call|
|
|
104
|
+
|
|
105
|
+
Configure callbacks in the provider to log lifecycle events, and add DevTools for a visual inspector:
|
|
106
|
+
|
|
107
|
+
```tsx
|
|
108
|
+
import { type ReactNode } from 'react';
|
|
109
|
+
import { ConsentManagerProvider, ConsentBanner, ConsentDialog } from '@c15t/nextjs';
|
|
110
|
+
import { DevTools } from '@c15t/dev-tools/react';
|
|
111
|
+
|
|
112
|
+
export default function ConsentManager({ children }: { children: ReactNode }) {
|
|
113
|
+
return (
|
|
114
|
+
<ConsentManagerProvider
|
|
115
|
+
options={{
|
|
116
|
+
mode: 'hosted',
|
|
117
|
+
backendURL: '/api/c15t',
|
|
118
|
+
callbacks: {
|
|
119
|
+
onBannerFetched: ({ jurisdiction, location }) => {
|
|
120
|
+
console.log('Init complete:', { jurisdiction, location });
|
|
121
|
+
},
|
|
122
|
+
onConsentSet: ({ preferences }) => {
|
|
123
|
+
console.log('Consent saved:', preferences);
|
|
124
|
+
},
|
|
125
|
+
onBeforeConsentRevocationReload: ({ preferences }) => {
|
|
126
|
+
console.log('Reloading due to revocation:', preferences);
|
|
127
|
+
},
|
|
128
|
+
onError: ({ error }) => {
|
|
129
|
+
console.error('Consent error:', error);
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
}}
|
|
133
|
+
>
|
|
134
|
+
<ConsentBanner />
|
|
135
|
+
<ConsentDialog />
|
|
136
|
+
{process.env.NODE_ENV !== 'production' && <DevTools />}
|
|
137
|
+
{children}
|
|
138
|
+
</ConsentManagerProvider>
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
```
|