@c15t/react 3.0.0-alpha.2 → 3.0.0-alpha.4
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/AGENTS.md +91 -67
- package/README.md +21 -17
- package/SKILL.md +34 -0
- package/dist/aggregate-components.js +1 -1
- package/dist/chunk-warming.js +1 -1
- package/dist/component-hooks/use-headless-consent-ui.js +1 -1
- package/dist/component-hooks/use-headless-iab-consent-ui.js +1 -1
- package/dist/component-hooks/use-panel-trigger.js +1 -1
- package/dist/components/consent-gate/atoms.js +1 -1
- package/dist/components/consent-gate/consent-gate.js +1 -1
- package/dist/components/consent-preferences-link/consent-preferences-link.js +1 -1
- package/dist/components/iab-panel/atoms/card.js +1 -1
- package/dist/components/iab-panel/atoms/feature-item.js +2 -0
- package/dist/components/iab-panel/atoms/header.js +1 -1
- package/dist/components/iab-panel/atoms/purpose-item.js +1 -1
- package/dist/components/iab-panel/atoms/stack-item.js +1 -1
- package/dist/components/iab-panel/iab-panel.js +1 -1
- package/dist/components/iab-panel/index.js +1 -1
- package/dist/components/iab-prompt/atoms/overlay.js +1 -1
- package/dist/components/iab-prompt/atoms/root.js +1 -1
- package/dist/components/panel/atoms/card.js +1 -1
- package/dist/components/panel/atoms/overlay.js +1 -1
- package/dist/components/panel/atoms/root.js +1 -1
- package/dist/components/panel/index.js +1 -1
- package/dist/components/panel/panel.js +1 -1
- package/dist/components/panel-trigger/atoms/button.js +1 -1
- package/dist/components/panel-trigger/atoms/icon.js +1 -1
- package/dist/components/panel-trigger/atoms/root.js +1 -1
- package/dist/components/panel-trigger/atoms/toolbar.js +1 -1
- package/dist/components/panel-trigger/atoms/trigger-icons.js +1 -0
- package/dist/components/panel-trigger/panel-trigger-toolbar.js +1 -1
- package/dist/components/preferences/atoms/accordion.js +1 -1
- package/dist/components/preferences/atoms/root.js +1 -1
- package/dist/components/preferences/atoms/vendor-list.js +1 -1
- package/dist/components/preferences/index.js +1 -1
- package/dist/components/prompt/atoms/overlay.js +1 -1
- package/dist/components/prompt/atoms/root.js +1 -1
- package/dist/components/prompt/components.js +1 -1
- package/dist/components/prompt/policy-actions.js +1 -1
- package/dist/components/shared/libs/compose-refs.js +1 -0
- package/dist/components/shared/libs/slot.js +1 -1
- package/dist/components/shared/primitives/button.js +1 -1
- package/dist/components/shared/ui/accordion/accordion.js +1 -1
- package/dist/components/shared/ui/collapsible/collapsible.js +1 -1
- package/dist/components/shared/ui/dialog/dialog.js +1 -1
- package/dist/components/shared/ui/icon.js +1 -1
- package/dist/components/shared/ui/logo.js +1 -1
- package/dist/components/shared/ui/preference-item/preference-item.js +1 -1
- package/dist/components/shared/ui/switch/switch.js +1 -1
- package/dist/components/shared/ui/tabs/tabs.js +1 -1
- package/dist/consent-theme.js +1 -0
- package/dist/context/consent-tracking-context.js +1 -1
- package/dist/devtools.js +1 -1
- package/dist/draft.js +1 -1
- package/dist/external-iab-context.js +1 -1
- package/dist/headless.js +1 -1
- package/dist/hooks/use-committed-ref.js +2 -0
- package/dist/hooks/use-focus-trap.js +1 -1
- package/dist/hooks.js +1 -1
- package/dist/iab/styles.tw3.css +5 -45
- package/dist/iab-context.js +1 -1
- package/dist/index.js +1 -1
- package/dist/kernel-selector.js +1 -0
- package/dist/module-hooks/iframe-blocker.js +1 -1
- package/dist/module-hooks/network-blocker.js +1 -1
- package/dist/module-hooks/network-hold.js +2 -0
- package/dist/module-hooks/persistence.js +1 -1
- package/dist/panel.js +1 -1
- package/dist/postcss-tailwind3.js +1 -0
- package/dist/preferences.js +1 -2
- package/dist/provider.js +1 -1
- package/dist/server/fetch-ssr-data.js +1 -1
- package/dist/server/headers.js +1 -1
- package/dist/server/normalize-url.js +1 -1
- package/dist/styles.tw3.css +16 -61
- package/dist/transports/offline.js +1 -1
- package/dist/ui-save.js +1 -1
- package/dist/utils/deferred-module.js +1 -1
- package/dist/utils/merge-slot-props.js +1 -1
- package/dist/utils/theme-utils.js +1 -1
- package/dist/version.js +1 -1
- package/dist-types/chunk-warming.d.ts +41 -8
- package/dist-types/component-hooks/use-headless-consent-ui.d.ts +1 -1
- package/dist-types/component-hooks/use-headless-iab-consent-ui.d.ts +1 -1
- package/dist-types/components/iab-panel/atoms/feature-item.d.ts +28 -0
- package/dist-types/components/iab-panel/atoms/purpose-item.d.ts +4 -1
- package/dist-types/components/iab-panel/atoms/stack-item.d.ts +6 -1
- package/dist-types/components/iab-panel/iab-panel.d.ts +2 -2
- package/dist-types/components/iab-panel/index.d.ts +6 -2
- package/dist-types/components/iab-panel/use-iab-translations.d.ts +4 -0
- package/dist-types/components/iab-prompt/iab-prompt.d.ts +2 -2
- package/dist-types/components/iab-prompt/index.d.ts +2 -2
- package/dist-types/components/panel/atoms/card.d.ts +1 -0
- package/dist-types/components/panel/index.d.ts +1 -0
- package/dist-types/components/panel/panel.d.ts +1 -1
- package/dist-types/components/panel-trigger/atoms/trigger-icons.d.ts +20 -0
- package/dist-types/components/preferences/index.d.ts +1 -0
- package/dist-types/components/prompt/components.d.ts +6 -0
- package/dist-types/components/shared/libs/compose-refs.d.ts +20 -0
- package/dist-types/components/shared/ui/accordion/accordion.d.ts +1 -0
- package/dist-types/components/shared/ui/collapsible/collapsible.d.ts +1 -0
- package/dist-types/components/shared/ui/icon.d.ts +2 -1
- package/dist-types/components/shared/ui/logo.d.ts +0 -10
- package/dist-types/components/shared/ui/preference-item/preference-item.d.ts +14 -0
- package/dist-types/components/shared/ui/switch/switch.d.ts +1 -0
- package/dist-types/components/shared/ui/tabs/tabs.d.ts +1 -0
- package/dist-types/consent-theme.d.ts +38 -0
- package/dist-types/context/consent-tracking-context.d.ts +10 -0
- package/dist-types/context/theme-context.d.ts +2 -2
- package/dist-types/context.d.ts +6 -0
- package/dist-types/devtools.d.ts +13 -2
- package/dist-types/draft.d.ts +50 -7
- package/dist-types/headless.d.ts +0 -1
- package/dist-types/hooks/use-committed-ref.d.ts +24 -0
- package/dist-types/hooks/use-focus-trap.d.ts +3 -1
- package/dist-types/hooks.d.ts +56 -13
- package/dist-types/iab.d.ts +1 -1
- package/dist-types/index.d.ts +4 -3
- package/dist-types/kernel-selector.d.ts +66 -0
- package/dist-types/module-hooks/iframe-blocker.d.ts +27 -0
- package/dist-types/module-hooks/network-blocker.d.ts +23 -0
- package/dist-types/module-hooks/network-hold.d.ts +47 -0
- package/dist-types/panel.d.ts +13 -1
- package/dist-types/postcss-tailwind3.d.ts +21 -0
- package/dist-types/preferences.d.ts +12 -1
- package/dist-types/provider.d.ts +62 -12
- package/dist-types/server/headers.d.ts +14 -4
- package/dist-types/server/normalize-url.d.ts +14 -7
- package/dist-types/server/types.d.ts +12 -1
- package/dist-types/transports/offline.d.ts +7 -16
- package/dist-types/ui-config-context.d.ts +9 -1
- package/dist-types/ui-save.d.ts +21 -1
- package/dist-types/utils/deferred-module.d.ts +3 -1
- package/dist-types/utils/merge-slot-props.d.ts +15 -1
- package/dist-types/utils/theme-utils.d.ts +17 -2
- package/dist-types/version.d.ts +1 -1
- package/docs/README.md +91 -67
- package/docs/assets/v3/bottom-bar.png +0 -0
- package/docs/assets/v3/brand-card.png +0 -0
- package/docs/assets/v3/brand-preferences.png +0 -0
- package/docs/assets/v3/choice-wall.png +0 -0
- package/docs/assets/v3/headless-bar-html.png +0 -0
- package/docs/assets/v3/headless-bar-mobile.png +0 -0
- package/docs/assets/v3/headless-bar.png +0 -0
- package/docs/assets/v3/slim-bar.png +0 -0
- package/docs/concepts/choose-your-setup.md +87 -0
- package/docs/{frameworks/react/concepts → concepts}/consent-categories.md +30 -8
- package/docs/concepts/consent-state.md +357 -0
- package/docs/{guides → concepts}/data-fetching.md +31 -27
- package/docs/concepts/how-consent-works.md +123 -0
- package/docs/{frameworks/react/concepts/policy-presets.md → concepts/policies.md} +7 -59
- package/docs/customization/class-names.md +202 -0
- package/docs/customization/dark-mode.md +157 -0
- package/docs/customization/motion.md +119 -0
- package/docs/customization/overview.md +67 -33
- package/docs/customization/recipes.md +839 -47
- package/docs/customization/slots.md +216 -35
- package/docs/customization/stylesheets.md +147 -0
- package/docs/customization/tailwind.md +842 -0
- package/docs/customization/tokens.md +166 -36
- package/docs/customization/translations.md +61 -3
- package/docs/frameworks/react/callbacks.md +118 -0
- package/docs/frameworks/react/clear-on-revocation.md +106 -0
- package/docs/frameworks/react/components/consent-banner.md +42 -28
- package/docs/frameworks/react/components/consent-dialog-link.md +13 -11
- package/docs/frameworks/react/components/consent-dialog-trigger.md +81 -76
- package/docs/frameworks/react/components/consent-dialog.md +100 -55
- package/docs/frameworks/react/components/consent-gate.md +45 -24
- package/docs/frameworks/react/components/consent-provider.md +126 -0
- package/docs/frameworks/react/components/consent-widget.md +32 -20
- package/docs/frameworks/react/components/dev-tools.md +52 -84
- package/docs/frameworks/react/components.md +105 -0
- package/docs/frameworks/react/compose.md +354 -0
- package/docs/frameworks/react/content-security-policy.md +126 -0
- package/docs/frameworks/react/customize.md +250 -0
- package/docs/frameworks/react/embeds.md +89 -0
- package/docs/frameworks/react/hooks.md +65 -0
- package/docs/frameworks/react/iab.md +222 -0
- package/docs/frameworks/react/network-blocker.md +140 -0
- package/docs/frameworks/react/quickstart.md +139 -101
- package/docs/frameworks/react/rendering.md +128 -0
- package/docs/frameworks/react/scripts.md +115 -0
- package/docs/frameworks/react/translations.md +155 -0
- package/docs/frameworks/react/troubleshooting.md +92 -47
- package/docs/frameworks/react/vendor-consent.md +170 -0
- package/docs/guides/banner-experiments.md +654 -0
- package/docs/guides/troubleshooting.md +120 -47
- package/docs/guides/verify-consent.md +81 -49
- package/docs/integrations/adobe-analytics.md +168 -160
- package/docs/integrations/ahrefs-analytics.md +153 -155
- package/docs/integrations/amplitude.md +163 -157
- package/docs/integrations/building-integrations.md +136 -37
- package/docs/integrations/clearbit.md +155 -155
- package/docs/integrations/cloudflare-web-analytics.md +157 -157
- package/docs/integrations/cloudflare-zaraz.md +210 -262
- package/docs/integrations/crisp.md +165 -159
- package/docs/integrations/databuddy.md +158 -174
- package/docs/integrations/fathom-analytics.md +159 -157
- package/docs/integrations/front-chat.md +322 -0
- package/docs/integrations/google-maps.md +119 -84
- package/docs/integrations/google-tag-manager.md +179 -164
- package/docs/integrations/google-tag.md +164 -161
- package/docs/integrations/heap.md +164 -156
- package/docs/integrations/hightouch.md +162 -158
- package/docs/integrations/hotjar.md +160 -156
- package/docs/integrations/intercom.md +184 -154
- package/docs/integrations/klaviyo.md +486 -0
- package/docs/integrations/linkedin-insights.md +175 -151
- package/docs/integrations/logrocket.md +161 -157
- package/docs/integrations/matomo-analytics.md +189 -179
- package/docs/integrations/meta-pixel.md +189 -151
- package/docs/integrations/microsoft-clarity.md +164 -156
- package/docs/integrations/microsoft-uet.md +149 -155
- package/docs/integrations/mixpanel-analytics.md +156 -161
- package/docs/integrations/one-dollar-stats.md +306 -0
- package/docs/integrations/openai-pixel.md +205 -302
- package/docs/integrations/overview.md +143 -83
- package/docs/integrations/pinterest-tag.md +329 -0
- package/docs/integrations/pirsch.md +170 -160
- package/docs/integrations/plausible-analytics.md +173 -159
- package/docs/integrations/posthog.md +227 -242
- package/docs/integrations/promptwatch.md +155 -155
- package/docs/integrations/reddit-pixel.md +186 -158
- package/docs/integrations/rudderstack.md +202 -187
- package/docs/integrations/rybbit-analytics.md +172 -161
- package/docs/integrations/segment.md +183 -155
- package/docs/integrations/snapchat-pixel.md +187 -157
- package/docs/integrations/tiktok-pixel.md +172 -151
- package/docs/integrations/umami-analytics.md +164 -159
- package/docs/integrations/vercel-analytics.md +167 -158
- package/docs/integrations/x-pixel.md +177 -151
- package/docs/integrations/youtube.md +122 -87
- package/docs/upgrade-v3.md +490 -354
- package/package.json +15 -9
- package/dist/component-hooks/use-manager.js +0 -2
- package/dist-types/component-hooks/use-manager.d.ts +0 -45
- package/docs/assets/v3/brand-bar.png +0 -0
- package/docs/assets/v3/mobile-card.png +0 -0
- package/docs/assets/v3/preferences.png +0 -0
- package/docs/frameworks/react/components/consent-manager-provider.md +0 -75
- package/docs/frameworks/react/hooks/use-consent-manager/overview.md +0 -58
- package/docs/frameworks/react/iab/overview.md +0 -53
- package/docs/frameworks/react/script-loader.md +0 -63
- package/docs/frameworks/react/styling/overview.md +0 -217
- package/docs/guides/consent-state.md +0 -60
- package/docs/guides/deployment-modes.md +0 -75
- package/docs/integrations/clear-on-revocation.md +0 -167
- package/docs/integrations/granular-consent.md +0 -208
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Scripts
|
|
3
|
+
description: Load vendor scripts by consent category in a React app with
|
|
4
|
+
ConsentProvider, and clear stored data or reload the page when a visitor
|
|
5
|
+
withdraws consent.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Register vendor scripts
|
|
10
|
+
|
|
11
|
+
Pass vendor loaders to `ConsentProvider` through `options.scripts`. The
|
|
12
|
+
provider loads each script when its category is allowed and removes it when
|
|
13
|
+
the category is denied. The [quickstart](./quickstart.md)
|
|
14
|
+
registers PostHog and X Pixel this way:
|
|
15
|
+
|
|
16
|
+
```ts title="src/scripts.ts"
|
|
17
|
+
import { posthog } from '@c15t/integrations/posthog';
|
|
18
|
+
import { xPixel } from '@c15t/integrations/x-pixel';
|
|
19
|
+
|
|
20
|
+
export const scripts = [
|
|
21
|
+
posthog({
|
|
22
|
+
id: 'phc_your_project_key',
|
|
23
|
+
initOptions: { cookieless_mode: 'never' },
|
|
24
|
+
loadMode: 'after-consent',
|
|
25
|
+
}),
|
|
26
|
+
xPixel({ pixelId: 'your-pixel-id' }),
|
|
27
|
+
];
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The provider in `src/consent.tsx` receives them:
|
|
31
|
+
|
|
32
|
+
```tsx title="src/consent.tsx"
|
|
33
|
+
import {
|
|
34
|
+
ConsentBanner,
|
|
35
|
+
ConsentDialog,
|
|
36
|
+
ConsentDialogLink,
|
|
37
|
+
ConsentProvider,
|
|
38
|
+
hosted,
|
|
39
|
+
} from 'c15t/react';
|
|
40
|
+
import type { ReactNode } from 'react';
|
|
41
|
+
|
|
42
|
+
import { scripts } from './scripts';
|
|
43
|
+
|
|
44
|
+
import 'c15t/react/styles.css';
|
|
45
|
+
|
|
46
|
+
const mode = hosted({ url: 'https://your-project.inth.app' });
|
|
47
|
+
|
|
48
|
+
export const Consent = ({ children }: { children: ReactNode }) => (
|
|
49
|
+
<ConsentProvider options={{ mode, scripts }}>
|
|
50
|
+
{children}
|
|
51
|
+
<ConsentBanner />
|
|
52
|
+
<ConsentDialog />
|
|
53
|
+
<footer>
|
|
54
|
+
<ConsentDialogLink>Privacy settings</ConsentDialogLink>
|
|
55
|
+
</footer>
|
|
56
|
+
</ConsentProvider>
|
|
57
|
+
);
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Each helper from `@c15t/integrations` sets its own category and a stable `id`.
|
|
61
|
+
Remove every other loader for the same vendor, such as a `<script>` tag in
|
|
62
|
+
`index.html` or an SDK you initialize at module level, so the vendor loads
|
|
63
|
+
once and only through c15t. [Integrations](../../integrations/overview.md) lists
|
|
64
|
+
every helper, and [building integrations](../../integrations/building-integrations.md)
|
|
65
|
+
covers a vendor without one.
|
|
66
|
+
|
|
67
|
+
## What happens when consent changes
|
|
68
|
+
|
|
69
|
+
* **Allowed.** The script loads, or its SDK is started, the first time its
|
|
70
|
+
category is allowed.
|
|
71
|
+
* **Denied later.** The provider removes the script element. Code that already
|
|
72
|
+
ran keeps running, so after the visitor turns off a category they had
|
|
73
|
+
allowed, the provider reloads the page once the save finishes. Set
|
|
74
|
+
`reloadOnConsentRevoked: false` only if every gated vendor stops itself.
|
|
75
|
+
* **`alwaysLoad` helpers.** Some integrations, such as Google Consent Mode,
|
|
76
|
+
load before consent and pass the visitor's choice to the vendor. Read the
|
|
77
|
+
vendor's guide; a category on a script does not always mean zero requests.
|
|
78
|
+
|
|
79
|
+
## Embeds and other requests
|
|
80
|
+
|
|
81
|
+
Scripts cover vendor code c15t loads for you. For the rest:
|
|
82
|
+
|
|
83
|
+
* [Embeds](./embeds.md) keeps iframes out of the page with
|
|
84
|
+
`ConsentGate` or the iframe blocker.
|
|
85
|
+
* [Network blocker](./network-blocker.md) holds `fetch` and
|
|
86
|
+
XHR calls that match a rule until their category is allowed.
|
|
87
|
+
|
|
88
|
+
## Clear stored data after revocation
|
|
89
|
+
|
|
90
|
+
Removing a script does not delete the cookies or storage entries it wrote. Add
|
|
91
|
+
`clearOnRevocation` to the provider options to delete the entries you list for
|
|
92
|
+
each category when it is denied. The option is read once, when the provider
|
|
93
|
+
mounts. [Clear on revocation](./clear-on-revocation.md) covers
|
|
94
|
+
configuration and browser limits.
|
|
95
|
+
|
|
96
|
+
## Let visitors turn off one vendor
|
|
97
|
+
|
|
98
|
+
A visitor can allow marketing and still switch off one vendor in it. Pass
|
|
99
|
+
`vendors` to the provider options. Helpers from `@c15t/integrations` already carry
|
|
100
|
+
their vendor slug. See [vendor consent](./vendor-consent.md).
|
|
101
|
+
|
|
102
|
+
To send your own events only to allowed integrations, see
|
|
103
|
+
[send events only to allowed integrations](../../integrations/overview.md#send-events-only-to-allowed-integrations).
|
|
104
|
+
|
|
105
|
+
## Check the scripts
|
|
106
|
+
|
|
107
|
+
Open DevTools Network in a private window, filter by each vendor's domain and
|
|
108
|
+
reload.
|
|
109
|
+
|
|
110
|
+
1. Before a choice under an opt-in policy, no vendor script loads.
|
|
111
|
+
2. Allow one category. Only that category's vendors load.
|
|
112
|
+
3. Turn the category off. The page reloads and the vendor stays absent.
|
|
113
|
+
4. Reload again. The rejection holds.
|
|
114
|
+
|
|
115
|
+
[Verify consent](../../guides/verify-consent.md) has the full checklist.
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Translations
|
|
3
|
+
description: Where c15t banner and dialog copy comes from in a React app, how
|
|
4
|
+
the browser picks the visitor's language, and how to override copy and switch
|
|
5
|
+
languages with the provider's i18n option and hooks.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Where the copy comes from
|
|
10
|
+
|
|
11
|
+
The banner, dialog, widget and gate placeholder read their text from one set
|
|
12
|
+
of translations for the visitor's language. Which set depends on the
|
|
13
|
+
provider's `mode`:
|
|
14
|
+
|
|
15
|
+
| Mode | Copy comes from |
|
|
16
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
17
|
+
| `hosted()` | The backend's `/init` response for the visitor's language, from the translations in your Inth project or your self-hosted backend's configuration, with your `i18n` messages for that language on top |
|
|
18
|
+
| `offline()` | Your `i18n` messages, merged over bundled English |
|
|
19
|
+
|
|
20
|
+
With a backend, edit wording in your Inth project first, so it changes without
|
|
21
|
+
a deploy. Code can override that copy in two places:
|
|
22
|
+
|
|
23
|
+
* `i18n` in the `ConsentProvider` options changes a message everywhere it
|
|
24
|
+
appears.
|
|
25
|
+
* Text props on `ConsentBanner` change one banner.
|
|
26
|
+
|
|
27
|
+
[Copy and translations](../../customization/translations.md) lists the message
|
|
28
|
+
keys and explains how code messages combine with the project's copy.
|
|
29
|
+
|
|
30
|
+
## How c15t picks the language
|
|
31
|
+
|
|
32
|
+
In `hosted()` mode the browser's `/init` request carries its own
|
|
33
|
+
`Accept-Language` header, and the backend answers with copy in that language.
|
|
34
|
+
When your project has no copy for it, the backend returns its fallback
|
|
35
|
+
language. Set `overrides.language` in the provider options to request a
|
|
36
|
+
specific language instead.
|
|
37
|
+
|
|
38
|
+
In `offline()` mode, `i18n.locale` picks the language, `en` by default. The
|
|
39
|
+
browser's language is not read.
|
|
40
|
+
|
|
41
|
+
The banner, dialog and widget set `dir` from the active language. Right-to-left
|
|
42
|
+
languages such as Arabic and Hebrew render with `dir="rtl"`, and a floating
|
|
43
|
+
banner you did not position yourself moves to the opposite side.
|
|
44
|
+
|
|
45
|
+
## Change the copy in code
|
|
46
|
+
|
|
47
|
+
Keep the messages in their own module:
|
|
48
|
+
|
|
49
|
+
```ts title="src/consent-i18n.ts"
|
|
50
|
+
import type { ConsentProviderOptions } from 'c15t/react';
|
|
51
|
+
|
|
52
|
+
// Keys you leave out keep the copy from your Inth project.
|
|
53
|
+
export const i18n = {
|
|
54
|
+
messages: {
|
|
55
|
+
de: {
|
|
56
|
+
common: { rejectAll: 'Optionale ablehnen' },
|
|
57
|
+
cookieBanner: { title: 'Cookies auf dieser Website' },
|
|
58
|
+
},
|
|
59
|
+
en: {
|
|
60
|
+
common: { rejectAll: 'Reject optional' },
|
|
61
|
+
cookieBanner: { title: 'Cookies on this site' },
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
} satisfies ConsentProviderOptions['i18n'];
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Pass them to the provider in the file that renders it, such as
|
|
68
|
+
`src/consent.tsx` from the [quickstart](./quickstart.md):
|
|
69
|
+
|
|
70
|
+
```tsx title="src/consent.tsx"
|
|
71
|
+
<ConsentProvider options={{ mode, scripts, i18n }}>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For the visitor's language, your messages replace the backend's copy key by
|
|
75
|
+
key. Keys you leave out keep the project's wording, and messages for other
|
|
76
|
+
languages are not applied. In `offline()` mode c15t bundles only English, so
|
|
77
|
+
keys you leave out for another language show in English.
|
|
78
|
+
|
|
79
|
+
The provider reads `i18n` once, when it mounts. In development it logs a
|
|
80
|
+
warning if `i18n` changes later. Define the object at module level, as above,
|
|
81
|
+
rather than inside a component.
|
|
82
|
+
|
|
83
|
+
## Change one banner's copy
|
|
84
|
+
|
|
85
|
+
`ConsentBanner` takes `title`, `description`, `acceptButtonText`,
|
|
86
|
+
`rejectButtonText`, `customizeButtonText` and `dismissButtonText`. The props
|
|
87
|
+
replace the text in every language, so pass values in the page's language:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
<ConsentBanner
|
|
91
|
+
title="Cookies on this site"
|
|
92
|
+
rejectButtonText="Reject optional"
|
|
93
|
+
/>;
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
## Switch the language at runtime
|
|
97
|
+
|
|
98
|
+
`useSetLanguage()` stores the new language, and `useInit()` resolves the policy
|
|
99
|
+
again, which fetches the copy for that language. Call both from your language
|
|
100
|
+
switcher:
|
|
101
|
+
|
|
102
|
+
```tsx title="src/language-switcher.tsx"
|
|
103
|
+
import { useInit, useSetLanguage, useSnapshot } from 'c15t/react';
|
|
104
|
+
|
|
105
|
+
const languages = [
|
|
106
|
+
{ code: 'en', label: 'English' },
|
|
107
|
+
{ code: 'de', label: 'Deutsch' },
|
|
108
|
+
];
|
|
109
|
+
|
|
110
|
+
export const LanguageSwitcher = () => {
|
|
111
|
+
const setLanguage = useSetLanguage();
|
|
112
|
+
const init = useInit();
|
|
113
|
+
const language = useSnapshot().translations?.language ?? 'en';
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<select
|
|
117
|
+
aria-label="Consent language"
|
|
118
|
+
value={language}
|
|
119
|
+
onChange={(event) => {
|
|
120
|
+
setLanguage(event.target.value);
|
|
121
|
+
// Setting the language alone keeps the current copy. Init fetches
|
|
122
|
+
// the policy and copy for the new language.
|
|
123
|
+
void init();
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
{languages.map(({ code, label }) => (
|
|
127
|
+
<option key={code} value={code}>
|
|
128
|
+
{label}
|
|
129
|
+
</option>
|
|
130
|
+
))}
|
|
131
|
+
</select>
|
|
132
|
+
);
|
|
133
|
+
};
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Setting the language without calling `init()` changes nothing on screen. Your
|
|
137
|
+
`i18n` messages for the new language apply on top of the copy that arrives.
|
|
138
|
+
Render the switcher anywhere inside `ConsentProvider`.
|
|
139
|
+
|
|
140
|
+
Switching needs a backend. In `offline()` mode the copy stays in the
|
|
141
|
+
`i18n.locale` language.
|
|
142
|
+
|
|
143
|
+
If your site changes language through its own i18n library, you can instead
|
|
144
|
+
pass its locale as `overrides.language` in the provider options. When that
|
|
145
|
+
value changes, the provider resolves the policy again in the new language.
|
|
146
|
+
|
|
147
|
+
## Verify
|
|
148
|
+
|
|
149
|
+
1. Set your browser language to one your project translates, clear site data
|
|
150
|
+
and reload under a policy that shows a banner. DevTools Network shows the
|
|
151
|
+
`/init` request with that `Accept-Language` header, and the banner uses
|
|
152
|
+
that language.
|
|
153
|
+
2. Check that a message you set in `i18n` shows in the banner.
|
|
154
|
+
3. Switch the language with your switcher. A new `/init` request runs, and the
|
|
155
|
+
banner and dialog text change without a reload.
|
|
@@ -1,67 +1,112 @@
|
|
|
1
1
|
---
|
|
2
|
-
title:
|
|
3
|
-
description:
|
|
2
|
+
title: Troubleshooting
|
|
3
|
+
description: Fix a missing banner, early vendor requests, unstyled components
|
|
4
|
+
and theme warnings in a React app that uses c15t/react.
|
|
4
5
|
group: frameworks
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
##
|
|
8
|
+
## The /init request goes to your-project.inth.app
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
requires no prompt can all produce no banner for different reasons. A missing
|
|
12
|
-
stylesheet can also make rendered controls appear incorrectly.
|
|
10
|
+
**Check.** In DevTools Network, the `GET` request to `/init` goes to
|
|
11
|
+
`your-project.inth.app` and fails.
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
the
|
|
16
|
-
|
|
13
|
+
**Fix.** Replace the placeholder URL in `hosted({ url })` in `src/consent.tsx`
|
|
14
|
+
with the backend URL from your Inth project, exactly as Inth shows it, then
|
|
15
|
+
rebuild.
|
|
17
16
|
|
|
18
|
-
##
|
|
17
|
+
## There is no banner
|
|
19
18
|
|
|
20
|
-
Check
|
|
21
|
-
|
|
22
|
-
`consent-dialog-*.js`, which some cookie-annoyance lists block. Update the c15t
|
|
23
|
-
packages and rebuild the app. Current component modules use neutral filenames;
|
|
24
|
-
public component imports stay the same. This also covers Vite development
|
|
25
|
-
requests used by TanStack Start and other Vite integrations.
|
|
19
|
+
**Check.** In DevTools Network, find the `GET` request to `/init` on your
|
|
20
|
+
backend URL.
|
|
26
21
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
| What you see | Cause | Fix |
|
|
23
|
+
| ------------------------------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
24
|
+
| No `/init` request | `ConsentProvider` is not mounted, or an error stopped `src/consent.tsx` from rendering | Check the console for errors and render one provider at the app root, as in the [quickstart](./quickstart.md) |
|
|
25
|
+
| `/init` fails with a CORS error | Your site's origin is not trusted | Add the exact origin, including port in development, to the trusted origins in your Inth project |
|
|
26
|
+
| `/init` returns 200 | The policy for this visitor asks for no banner, or a stored choice is still valid | Inspect the active policy with the component below, or clear site data |
|
|
30
27
|
|
|
31
|
-
|
|
28
|
+
A policy with `prompt: 'none'` shows no banner by design, and so does a
|
|
29
|
+
returning visitor with a valid stored choice. [Policies](../../concepts/policies.md)
|
|
30
|
+
explains why a region may not show one.
|
|
32
31
|
|
|
33
|
-
|
|
34
|
-
for that vendor. Remove unconditional script tags, framework analytics plugins
|
|
35
|
-
and duplicate tag-manager entries. c15t's script registration only controls the
|
|
36
|
-
scripts registered with it.
|
|
32
|
+
## Inspect the active policy
|
|
37
33
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
is no request at all, do not use an always-loaded helper unchanged.
|
|
34
|
+
Render this component inside `ConsentProvider`. It reads state and records no
|
|
35
|
+
choice:
|
|
41
36
|
|
|
42
|
-
|
|
37
|
+
```tsx title="src/consent-policy-debug.tsx"
|
|
38
|
+
import { useSnapshot } from 'c15t/react';
|
|
43
39
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
Do not "repair" persistence by saving permissions automatically on mount.
|
|
40
|
+
export function ConsentPolicyDebug() {
|
|
41
|
+
const snapshot = useSnapshot();
|
|
42
|
+
const resolution = snapshot.resolution;
|
|
48
43
|
|
|
49
|
-
|
|
44
|
+
return (
|
|
45
|
+
<pre>
|
|
46
|
+
{JSON.stringify(
|
|
47
|
+
{
|
|
48
|
+
status: resolution.status,
|
|
49
|
+
policy: resolution.status === 'matched' ? resolution.policy.id : null,
|
|
50
|
+
prompt: snapshot.promptRequirement,
|
|
51
|
+
permissions: snapshot.effectivePermissions,
|
|
52
|
+
location: snapshot.location,
|
|
53
|
+
privacySignals: snapshot.privacySignals,
|
|
54
|
+
},
|
|
55
|
+
null,
|
|
56
|
+
2
|
|
57
|
+
)}
|
|
58
|
+
</pre>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
```
|
|
50
62
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
inputs. A module-level mutable runtime on a server can share one visitor's state
|
|
54
|
-
with another request; create request-owned state instead.
|
|
63
|
+
The component re-renders on every consent change. Remove it after you finish.
|
|
64
|
+
For a feature gate, use `useConsent('marketing')` or the category you need.
|
|
55
65
|
|
|
56
|
-
##
|
|
66
|
+
## A vendor loads before the visitor chooses
|
|
57
67
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
production output with a static file server, not the framework dev server.
|
|
68
|
+
**Check.** Search `index.html`, your entry module and any tag manager for the
|
|
69
|
+
vendor's script or SDK initializer.
|
|
61
70
|
|
|
62
|
-
|
|
71
|
+
**Fix.** Remove every loader except the one in `options.scripts`. c15t only
|
|
72
|
+
gates scripts registered with it, and the network blocker cannot stop code that
|
|
73
|
+
runs before the provider renders. [Scripts and embeds](./scripts.md)
|
|
74
|
+
covers the options.
|
|
63
75
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
76
|
+
## The banner has no styles
|
|
77
|
+
|
|
78
|
+
**Check.** In DevTools Elements, the banner has classes but no rules apply.
|
|
79
|
+
|
|
80
|
+
**Fix.** Import `c15t/react/styles.css` once in the module that renders the
|
|
81
|
+
provider. With Tailwind 3, keep that import above the `@tailwind` directives
|
|
82
|
+
and add `c15t/postcss-tailwind3` before `tailwindcss` in your PostCSS
|
|
83
|
+
config. Without the plugin, Tailwind 3 drops c15t's rules or fails the build
|
|
84
|
+
with `` `@layer components` is used but no matching `@tailwind components` directive is present ``.
|
|
85
|
+
See [Tailwind CSS](../../customization/tailwind.md#set-up-tailwind-css-3).
|
|
86
|
+
|
|
87
|
+
## The console warns that theme tokens are no longer turned into CSS
|
|
88
|
+
|
|
89
|
+
**Check.** `options.theme` holds colors, radius or other tokens, and the page
|
|
90
|
+
has no `c15t-theme` style element. The provider only reads `consentActions`
|
|
91
|
+
and slot styles from `theme`.
|
|
92
|
+
|
|
93
|
+
**Fix.** Move the tokens to your CSS or render `ConsentTheme`, as shown in
|
|
94
|
+
[customize](./customize.md#change-colors-fonts-and-radius).
|
|
95
|
+
|
|
96
|
+
## The banner is missing from server-rendered HTML
|
|
97
|
+
|
|
98
|
+
**Check.** Your app uses React Router framework mode, Remix or another
|
|
99
|
+
server-rendered setup without a c15t adapter. There this is expected: the
|
|
100
|
+
browser resolves consent after hydration.
|
|
101
|
+
|
|
102
|
+
**Fix.** None is needed for consent to work.
|
|
103
|
+
[Rendering](./rendering.md#react-router-remix-and-other-server-rendered-apps)
|
|
104
|
+
explains the options.
|
|
105
|
+
|
|
106
|
+
## More help
|
|
107
|
+
|
|
108
|
+
[Troubleshoot consent](../../guides/troubleshooting.md) covers problems shared by
|
|
109
|
+
every framework, such as a missing banner, analytics that load before a choice,
|
|
110
|
+
imports that fail because npm installed c15t v2, choices that disappear on
|
|
111
|
+
reload, server HTML that differs from the browser, static builds that fail and
|
|
112
|
+
content blockers that hide the consent UI.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Vendor consent
|
|
3
|
+
description: Let visitors allow a category such as marketing in a React app and
|
|
4
|
+
still turn off one vendor in it, with the vendors option on ConsentProvider
|
|
5
|
+
and useVendorAllowed.
|
|
6
|
+
group: frameworks
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## How vendor consent works
|
|
10
|
+
|
|
11
|
+
A visitor allows marketing, then switches off X Pixel. Other marketing
|
|
12
|
+
scripts load; X Pixel does not. c15t loads a script, network request or
|
|
13
|
+
iframe that names a vendor only when both are true:
|
|
14
|
+
|
|
15
|
+
* Its category is allowed.
|
|
16
|
+
* The visitor has not switched its vendor off.
|
|
17
|
+
|
|
18
|
+
A vendor switch never grants a category. With marketing denied, X Pixel stays
|
|
19
|
+
blocked whatever its own switch says. You do not need IAB TCF for this. Under
|
|
20
|
+
an `iab` policy, c15t ignores vendor slugs and takes vendor consent from the
|
|
21
|
+
TC string instead; see [IAB TCF](./iab.md).
|
|
22
|
+
|
|
23
|
+
## Declare the vendors
|
|
24
|
+
|
|
25
|
+
Declare the vendors next to the scripts in `src/scripts.ts`, using the `vendor`
|
|
26
|
+
slug each script already carries:
|
|
27
|
+
|
|
28
|
+
```ts title="src/scripts.ts (partial)"
|
|
29
|
+
import type { Vendor } from 'c15t';
|
|
30
|
+
|
|
31
|
+
export const vendors: Vendor[] = [
|
|
32
|
+
{
|
|
33
|
+
id: 'x-pixel',
|
|
34
|
+
name: 'X Pixel',
|
|
35
|
+
category: 'marketing',
|
|
36
|
+
privacyPolicyUrl: 'https://x.com/privacy',
|
|
37
|
+
},
|
|
38
|
+
];
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
Then add them to the provider options in `src/consent.tsx`:
|
|
42
|
+
|
|
43
|
+
```tsx title="src/consent.tsx (partial)"
|
|
44
|
+
import { scripts, vendors } from './scripts';
|
|
45
|
+
|
|
46
|
+
<ConsentProvider options={{ mode, scripts, vendors }}>
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`vendors` is a live option, like `scripts`: passing a new list replaces the
|
|
50
|
+
declared vendors. A provider that borrows a `runtime` does not accept
|
|
51
|
+
`vendors`; declare them in `createConsentRuntime({ vendors })` instead.
|
|
52
|
+
|
|
53
|
+
## Vendor fields
|
|
54
|
+
|
|
55
|
+
| Field | Required | Behavior |
|
|
56
|
+
| ----------------------------------------- | -------- | --------------------------------------------------------------------------- |
|
|
57
|
+
| `id` | Yes | Lowercase slug of up to 64 characters: letters, digits, `.`, `_` and `-`. |
|
|
58
|
+
| `name` | Yes | Name shown in the preference dialog. |
|
|
59
|
+
| `category` | Yes | A category, or a condition such as `{ or: ['measurement', 'marketing'] }`. |
|
|
60
|
+
| `privacyPolicyUrl` | Yes | Link shown next to the vendor. |
|
|
61
|
+
| `description`, `legalName`, `homepageUrl` | No | Extra detail shown on the vendor card. |
|
|
62
|
+
| `disabled` | No | List the vendor without a switch. A stored denial for it no longer applies. |
|
|
63
|
+
|
|
64
|
+
The `id` must match the `vendor` slug on the script. Every `@c15t/integrations`
|
|
65
|
+
helper sets `vendor` to its script ID, so `xPixel()` is `x-pixel`, `gtag()`
|
|
66
|
+
is `gtag` and `cloudflareZaraz()` is `cloudflare-zaraz`. Each
|
|
67
|
+
[vendor guide](../../integrations/overview.md) names its slug.
|
|
68
|
+
|
|
69
|
+
A self-hosted backend can declare vendors too. `/init` returns them and c15t
|
|
70
|
+
merges them with the vendors in code. When both declare the same `id`, the
|
|
71
|
+
code declaration wins.
|
|
72
|
+
|
|
73
|
+
## Gate scripts, requests and iframes by vendor
|
|
74
|
+
|
|
75
|
+
Put the vendor slug on each target:
|
|
76
|
+
|
|
77
|
+
| Target | Field |
|
|
78
|
+
| -------------------- | ----------------------------------------- |
|
|
79
|
+
| Script configuration | `vendor: 'x-pixel'` |
|
|
80
|
+
| Network blocker rule | `vendor: 'x-pixel'` |
|
|
81
|
+
| Iframe blocker | `data-vendor="x-pixel"` on the `<iframe>` |
|
|
82
|
+
|
|
83
|
+
An iframe with `data-vendor` and no `data-category` is gated on the vendor
|
|
84
|
+
alone. A script with `alwaysLoad` still loads when its vendor is off. Its
|
|
85
|
+
callbacks receive `info.vendor.granted`, so the SDK can stop itself. The
|
|
86
|
+
Google Consent Mode, RudderStack and Cloudflare Zaraz helpers do this for
|
|
87
|
+
you. While their vendor is off, they send every optional category as denied.
|
|
88
|
+
|
|
89
|
+
`ConsentGate` checks a category only. To render a component only while one
|
|
90
|
+
vendor is allowed, read `useVendorAllowed('youtube')` and render it when the
|
|
91
|
+
hook returns `true`. [Embeds](./embeds.md) covers the iframe
|
|
92
|
+
blocker's `data-vendor` attribute.
|
|
93
|
+
|
|
94
|
+
## What the preference dialog shows
|
|
95
|
+
|
|
96
|
+
`ConsentDialog` and `ConsentWidget` list each declared vendor under its
|
|
97
|
+
category, with its own switch.
|
|
98
|
+
|
|
99
|
+
The dialog lists only vendors that have a `name` and a `privacyPolicyUrl`.
|
|
100
|
+
A vendor's switch is disabled while its category is off. Save records the
|
|
101
|
+
vendors the visitor changed. Accept All and Reject All clear every vendor
|
|
102
|
+
denial, so each vendor follows its category again. The labels come from
|
|
103
|
+
`consentManagerDialog.vendors` in the translations: `title`,
|
|
104
|
+
`privacyPolicy`, `disabledByCategory` and `switchLabel`.
|
|
105
|
+
|
|
106
|
+
## Build your own vendor switch
|
|
107
|
+
|
|
108
|
+
`useVendorDraft()` from `c15t/react` reads and stages vendor switches on the
|
|
109
|
+
same draft the category switches use, so one save records both.
|
|
110
|
+
`useVendorAllowed(id)` returns whether a vendor may run now: it is declared,
|
|
111
|
+
its category is allowed and the visitor has not switched it off.
|
|
112
|
+
|
|
113
|
+
```tsx title="src/x-pixel-switch.tsx (partial)"
|
|
114
|
+
import { useVendorAllowed, useVendorDraft } from 'c15t/react';
|
|
115
|
+
|
|
116
|
+
const { vendors, setVendor, save } = useVendorDraft();
|
|
117
|
+
const xPixelAllowed = useVendorAllowed('x-pixel');
|
|
118
|
+
|
|
119
|
+
<input
|
|
120
|
+
type="checkbox"
|
|
121
|
+
checked={vendors['x-pixel'] ?? true}
|
|
122
|
+
onChange={(event) => setVendor('x-pixel', event.target.checked)}
|
|
123
|
+
/>
|
|
124
|
+
<button type="button" onClick={() => save()}>Save</button>
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
`save()` resolves `{ ok: false }` without recording anything when the policy
|
|
128
|
+
or the vendor list changed under the edit. Call `reset()` to start again.
|
|
129
|
+
`useDeclaredVendors()` returns the declared vendors, and `useVendorChoice()`
|
|
130
|
+
returns the recorded vendor decision, or `null` before the visitor made one.
|
|
131
|
+
|
|
132
|
+
A vendor reads as allowed only when it is declared, its category is allowed
|
|
133
|
+
and the visitor has not switched it off. An id that nothing declares, in
|
|
134
|
+
`vendors`, on a script or iframe, or from the backend, reads as not allowed.
|
|
135
|
+
A typo such as `x-pixle` or a vendor you forgot to declare never looks like
|
|
136
|
+
consent. In development, c15t logs a console warning that names the id.
|
|
137
|
+
|
|
138
|
+
## What c15t stores
|
|
139
|
+
|
|
140
|
+
c15t stores only the vendors a visitor switched off, and sends the full
|
|
141
|
+
vendor map to the backend with the consent record. Under an opt-out policy,
|
|
142
|
+
every vendor starts on. Turning a vendor off that was on reloads the page,
|
|
143
|
+
the same as withdrawing a category, so code the vendor already ran stops.
|
|
144
|
+
|
|
145
|
+
## What switching a vendor off does not do
|
|
146
|
+
|
|
147
|
+
* It does not delete cookies the vendor already set.
|
|
148
|
+
[Clear on revocation](./clear-on-revocation.md) does not run, because the
|
|
149
|
+
category stays allowed. Delete the vendor's cookies from the script's
|
|
150
|
+
`onConsentChange` when `info.vendor.granted` is `false`.
|
|
151
|
+
* It does not expire. The switch stays off until the visitor changes it or
|
|
152
|
+
uses Accept All or Reject All, even across a policy change.
|
|
153
|
+
* Adding a vendor does not ask returning visitors again. A new vendor starts
|
|
154
|
+
on inside an allowed category. Change the policy's `copyRevision` if a new
|
|
155
|
+
vendor should prompt again.
|
|
156
|
+
|
|
157
|
+
## Verify vendor consent
|
|
158
|
+
|
|
159
|
+
Test the production build in a private window with DevTools Network open and
|
|
160
|
+
filtered to `ads-twitter.com`:
|
|
161
|
+
|
|
162
|
+
1. Declare `x-pixel` and register `xPixel()`. Open Privacy settings. The
|
|
163
|
+
marketing row lists X Pixel with a switch.
|
|
164
|
+
2. Allow marketing and save. `uwt.js` loads.
|
|
165
|
+
3. Open Privacy settings, switch X Pixel off and save. c15t reloads the
|
|
166
|
+
page. After the reload there is no `uwt.js` request, and other marketing
|
|
167
|
+
scripts still load.
|
|
168
|
+
4. Reload again. The X Pixel switch is still off.
|
|
169
|
+
5. Turn marketing off. The X Pixel switch is disabled.
|
|
170
|
+
6. Open Privacy settings and click Accept All. `uwt.js` loads again.
|