Skip to content

Commit 8872327

Browse files
fiveonefour-github-botclaudecallicles
committed
chore(deps): migrate @c15t/nextjs to 2.x
Replaces dependabot PR #69, which failed because 2.x reorganized the package's exports. Bumps @c15t/nextjs from ^1.8.6 to ^2.1.0 and applies the 2.x API changes, verified against the installed package's types and bundled docs (node_modules/@c15t/nextjs/docs). API changes applied: - The "@c15t/nextjs/client" subpath is gone from the exports map. All components, hooks and types now come from the "@c15t/nextjs" root. This was the cause of the #69 build failure. - Components renamed: CookieBanner -> ConsentBanner and ConsentManagerDialog -> ConsentDialog. - Hook property hasConsentFor(category) -> has(category), which also supports and/or/not conditions. - Dialog visibility state isPrivacyDialogOpen / setIsPrivacyDialogOpen / setShowPopup were replaced by a single activeUI enum ("none" | "banner" | "dialog") plus setActiveUI(). Dismiss now maps to one setActiveUI("banner") call, which closes the dialog and re-shows the banner. - The per-component theme prop is gone; ConsentDialogProps declares theme?: never. Theme moves into ConsentManagerProvider options.theme. - The 1.x CSS-variable theme keys (--banner-*, --button-*, --dialog-*, --widget-*) no longer exist in the shipped stylesheet, so the theme is re-expressed with 2.x semantic design tokens (colors, radius, shadows, typography, consentActions) that resolve to --c15t-* properties. The accordion shades, which have no semantic token, are preserved via a scoped variable override on the consentWidgetAccordion slot. - mode: "c15t" -> mode: "hosted". Both are accepted by ClientMode and behave identically; "c15t" is documented as a legacy alias. - Branding/footer hiding previously done with display:none theme entries is now the hideBranding prop on both components. - 2.x ships prebuilt component styles as a separate stylesheet rather than bundling them in the JS, so "@c15t/nextjs/styles.css" is now imported. Without it the components render unstyled. - colorScheme is pinned to "dark" and the palette is applied to both colors and dark, because the app is dark-only and sets no .dark class that 2.x scheme detection could pick up. Provider options, theme values and consent gating are otherwise unchanged. next.config.mjs CSP is untouched; hosted endpoints are still *.c15t.dev. Verified: tsc --noEmit clean, biome clean on both touched files, 262/262 unit tests pass, and next build compiles with zero module resolution or export errors. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Co-authored-by: Nicolas Joseph <nicolas@fiveonefour.com>
1 parent 7fdafa2 commit 8872327

4 files changed

Lines changed: 135 additions & 2130 deletions

File tree

components/consent-analytics.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
"use client"
22

3-
import { useConsentManager } from "@c15t/nextjs/client"
3+
import { useConsentManager } from "@c15t/nextjs"
44
import { Analytics } from "@vercel/analytics/react"
55

66
export function ConsentAnalytics() {
7-
const { hasConsentFor } = useConsentManager()
7+
const { has } = useConsentManager()
88

9-
if (!hasConsentFor("measurement")) {
9+
if (!has("measurement")) {
1010
return null
1111
}
1212

components/consent-provider.tsx

Lines changed: 67 additions & 204 deletions
Original file line numberDiff line numberDiff line change
@@ -2,221 +2,84 @@
22

33
import type React from "react"
44
import { useEffect, useCallback } from "react"
5-
import { ConsentManagerProvider, useConsentManager } from "@c15t/nextjs/client"
6-
import { CookieBanner, ConsentManagerDialog } from "@c15t/nextjs"
5+
import {
6+
ConsentBanner,
7+
ConsentDialog,
8+
ConsentManagerProvider,
9+
type ConsentManagerOptions,
10+
type Theme,
11+
useConsentManager,
12+
} from "@c15t/nextjs"
13+
import "@c15t/nextjs/styles.css"
714

815
const backendURL = process.env.NEXT_PUBLIC_C15T_URL
916

10-
const darkButton = {
11-
"--button-background-color": "hsl(212 30% 15%)",
12-
"--button-background-color-hover": "hsl(212 30% 20%)",
13-
"--button-text": "hsl(0 0% 97%)",
14-
"--button-text-hover": "hsl(0 0% 97%)",
15-
"--button-border-color": "hsl(213 19% 25%)",
16-
"--button-border-radius": "0.625rem",
17-
"--button-neutral": "hsl(213 19% 25%)",
18-
"--button-neutral-hover": "hsl(213 19% 30%)",
19-
"--button-neutral-soft": "hsl(212 30% 15%)",
20-
"--button-neutral-soft-dark": "hsl(212 30% 15%)",
21-
"--button-shadow": "none",
22-
"--button-shadow-dark": "none",
23-
"--button-shadow-neutral": "none",
24-
"--button-shadow-neutral-dark": "none",
25-
"--button-shadow-neutral-hover": "none",
26-
"--button-shadow-neutral-hover-dark": "none",
27-
"--button-shadow-neutral-focus": "0 0 0 2px hsl(163 69% 47%)",
28-
"--button-shadow-neutral-focus-dark": "0 0 0 2px hsl(163 69% 47%)",
29-
"--button-focus-ring": "0 0 0 2px hsl(163 69% 47%)",
30-
} as const
31-
32-
const acceptButton = {
33-
"--button-background-color": "hsl(163 69% 47%)",
34-
"--button-background-color-dark": "hsl(163 69% 47%)",
35-
"--button-text": "hsl(204 100% 3%)",
36-
"--button-text-dark": "hsl(204 100% 3%)",
37-
"--button-text-hover": "hsl(204 100% 3%)",
38-
"--button-text-hover-dark": "hsl(204 100% 3%)",
39-
"--button-border-color": "hsl(163 69% 47%)",
40-
"--button-border-radius": "0.625rem",
41-
"--button-neutral-soft": "hsl(163 69% 47%)",
42-
"--button-neutral-soft-dark": "hsl(163 69% 47%)",
43-
"--button-shadow": "none",
44-
"--button-shadow-dark": "none",
45-
"--button-shadow-neutral": "none",
46-
"--button-shadow-neutral-dark": "none",
47-
"--button-shadow-neutral-hover": "none",
48-
"--button-shadow-neutral-hover-dark": "none",
49-
"--button-shadow-neutral-focus": "0 0 0 2px hsl(163 69% 47%)",
50-
"--button-shadow-neutral-focus-dark": "0 0 0 2px hsl(163 69% 47%)",
51-
"--button-focus-ring": "0 0 0 2px hsl(163 69% 47%)",
52-
} as const
53-
54-
const customizeButton = {
55-
"--button-background-color": "hsl(212 30% 15%)",
56-
"--button-background-color-dark": "hsl(212 30% 15%)",
57-
"--button-text": "hsl(213 14% 70%)",
58-
"--button-text-dark": "hsl(213 14% 70%)",
59-
"--button-border-color": "hsl(213 19% 25%)",
60-
"--button-border-radius": "0.625rem",
61-
"--button-primary": "hsl(213 14% 70%)",
62-
"--button-primary-dark": "hsl(213 14% 70%)",
63-
"--button-primary-hover": "hsl(213 14% 80%)",
64-
"--button-primary-hover-dark": "hsl(213 14% 80%)",
65-
"--button-shadow": "none",
66-
"--button-shadow-dark": "none",
67-
"--button-shadow-primary": "inset 0 0 0 1px hsl(213 19% 25%)",
68-
"--button-shadow-primary-dark": "inset 0 0 0 1px hsl(213 19% 25%)",
69-
"--button-shadow-primary-hover": "inset 0 0 0 1px hsl(213 19% 30%)",
70-
"--button-shadow-primary-hover-dark": "inset 0 0 0 1px hsl(213 19% 30%)",
71-
"--button-shadow-primary-focus": "0 0 0 2px hsl(163 69% 47%)",
72-
"--button-shadow-primary-focus-dark": "0 0 0 2px hsl(163 69% 47%)",
73-
"--button-focus-ring": "0 0 0 2px hsl(163 69% 47%)",
74-
} as const
75-
76-
const saveButton = {
77-
"--button-background-color": "hsl(163 69% 47%)",
78-
"--button-background-color-dark": "hsl(163 69% 47%)",
79-
"--button-text": "hsl(204 100% 3%)",
80-
"--button-text-dark": "hsl(204 100% 3%)",
81-
"--button-border-color": "hsl(163 69% 47%)",
82-
"--button-border-radius": "0.625rem",
83-
"--button-primary": "hsl(204 100% 3%)",
84-
"--button-primary-dark": "hsl(204 100% 3%)",
85-
"--button-primary-hover": "hsl(204 100% 3%)",
86-
"--button-primary-hover-dark": "hsl(204 100% 3%)",
87-
"--button-shadow": "none",
88-
"--button-shadow-dark": "none",
89-
"--button-shadow-primary": "none",
90-
"--button-shadow-primary-dark": "none",
91-
"--button-shadow-primary-hover": "none",
92-
"--button-shadow-primary-hover-dark": "none",
93-
"--button-shadow-primary-focus": "0 0 0 2px hsl(163 69% 47%)",
94-
"--button-shadow-primary-focus-dark": "0 0 0 2px hsl(163 69% 47%)",
95-
"--button-focus-ring": "0 0 0 2px hsl(163 69% 47%)",
17+
/**
18+
* Dark palette carried over from the c15t 1.x CSS-variable theme.
19+
*
20+
* 2.x replaced the per-slot `--banner-*` / `--button-*` / `--dialog-*` variables
21+
* with semantic design tokens that resolve to `--c15t-*` custom properties, so
22+
* the old values are re-expressed as tokens here.
23+
*/
24+
const palette = {
25+
primary: "hsl(163 69% 47%)",
26+
primaryHover: "hsl(163 69% 47%)",
27+
textOnPrimary: "hsl(204 100% 3%)",
28+
surface: "hsl(212 36% 10%)",
29+
surfaceHover: "hsl(212 36% 10%)",
30+
border: "hsl(213 19% 25%)",
31+
borderHover: "hsl(213 19% 30%)",
32+
text: "hsl(0 0% 97%)",
33+
textMuted: "hsl(213 14% 70%)",
34+
overlay: "rgba(0,0,0,0.6)",
35+
switchTrack: "hsl(213 19% 25%)",
36+
switchTrackActive: "hsl(163 69% 47%)",
9637
} as const
9738

39+
// The app renders a single dark theme and sets no `.dark` class, so the same
40+
// palette is applied to both schemes and the scheme is pinned to dark.
9841
const theme = {
99-
"banner.card": {
100-
style: {
101-
"--banner-background-color": "hsl(212 36% 10%)",
102-
"--banner-border-color": "hsl(213 19% 25%)",
103-
"--banner-border-radius": "0.875rem",
104-
"--banner-shadow": "0 8px 32px rgba(0,0,0,0.4)",
105-
},
106-
},
107-
"banner.header.title": {
108-
style: { "--banner-title-color": "hsl(0 0% 97%)" },
109-
},
110-
"banner.header.description": {
111-
style: { "--banner-description-color": "hsl(213 14% 70%)" },
42+
colors: palette,
43+
dark: palette,
44+
typography: { fontFamily: "var(--font-body), sans-serif" },
45+
radius: { md: "0.625rem", lg: "0.875rem" },
46+
shadows: { sm: "none", md: "none", lg: "0 8px 32px rgba(0,0,0,0.4)" },
47+
consentActions: {
48+
accept: { variant: "primary", mode: "filled" },
49+
reject: { variant: "neutral", mode: "stroke" },
50+
customize: { variant: "neutral", mode: "stroke" },
11251
},
113-
"banner.footer": {
114-
style: { "--banner-footer-background-color": "hsl(212 36% 10%)" },
115-
},
116-
"banner.footer.accept-button": { style: acceptButton },
117-
"banner.footer.reject-button": { style: darkButton },
118-
"banner.footer.customize-button": { style: customizeButton },
119-
"banner.overlay": {
120-
style: { "--banner-overlay-background-color": "rgba(0,0,0,0.6)" },
121-
},
122-
"dialog.root": {
123-
style: {
124-
"--dialog-font-family": "var(--font-body), sans-serif",
125-
"--dialog-background-color": "hsl(212 36% 10%)",
126-
"--dialog-foreground-color": "hsl(0 0% 97%)",
127-
"--dialog-border-color": "hsl(213 19% 25%)",
128-
"--dialog-stroke-color": "hsl(213 19% 25%)",
129-
"--dialog-muted-color": "hsl(213 14% 70%)",
130-
"--dialog-card-radius": "0.875rem",
131-
"--dialog-card-shadow": "0 8px 32px rgba(0,0,0,0.4)",
132-
"--dialog-overlay-background-color": "rgba(0,0,0,0.6)",
133-
"--dialog-link-text-color": "hsl(0 0% 97%)",
134-
"--widget-background-color": "hsl(212 36% 10%)",
135-
"--widget-border-color": "hsl(213 19% 25%)",
136-
"--widget-text-color": "hsl(0 0% 97%)",
137-
"--widget-text-muted-color": "hsl(213 14% 70%)",
138-
"--widget-link-text-color": "hsl(0 0% 97%)",
139-
"--widget-footer-background-color": "hsl(212 36% 10%)",
140-
"--widget-accordion-background-color": "hsl(213 27% 18%)",
141-
"--widget-accordion-background-hover": "hsl(213 27% 22%)",
142-
"--widget-accordion-border-color": "hsl(213 19% 25%)",
143-
"--widget-accordion-text-color": "hsl(0 0% 97%)",
144-
"--widget-accordion-content-color": "hsl(213 14% 70%)",
145-
"--widget-accordion-icon-color": "hsl(213 14% 70%)",
146-
"--widget-accordion-arrow-color": "hsl(213 14% 70%)",
147-
"--widget-accordion-focus-ring": "hsl(163 69% 47%)",
148-
"--widget-accordion-radius": "0.625rem",
52+
slots: {
53+
// Preserves the 1.x accordion shades, which have no semantic token, by
54+
// scoping the surface variables to the accordion subtree.
55+
consentWidgetAccordion: {
56+
style: {
57+
"--c15t-surface": "hsl(213 27% 18%)",
58+
"--c15t-surface-hover": "hsl(213 27% 22%)",
59+
},
14960
},
15061
},
151-
"widget.accordion": {
152-
style: {
153-
"--accordion-background-color": "hsl(213 27% 18%)",
154-
"--accordion-background-color-dark": "hsl(213 27% 18%)",
155-
"--accordion-background-hover": "hsl(213 27% 22%)",
156-
"--accordion-background-hover-dark": "hsl(213 27% 22%)",
157-
"--accordion-border-color": "hsl(213 19% 25%)",
158-
"--accordion-border-color-dark": "hsl(213 19% 25%)",
159-
"--accordion-text-color": "hsl(0 0% 97%)",
160-
"--accordion-text-color-dark": "hsl(0 0% 97%)",
161-
"--accordion-content-color": "hsl(213 14% 70%)",
162-
"--accordion-content-color-dark": "hsl(213 14% 70%)",
163-
"--accordion-icon-color": "hsl(213 14% 70%)",
164-
"--accordion-icon-color-dark": "hsl(213 14% 70%)",
165-
"--accordion-arrow-color": "hsl(213 14% 70%)",
166-
"--accordion-arrow-color-dark": "hsl(213 14% 70%)",
167-
"--accordion-focus-ring": "hsl(163 69% 47%)",
168-
"--accordion-focus-ring-dark": "hsl(163 69% 47%)",
169-
"--accordion-focus-shadow": "0 0 0 2px hsl(163 69% 47%)",
170-
"--accordion-focus-shadow-dark": "0 0 0 2px hsl(163 69% 47%)",
171-
"--accordion-radius": "0.625rem",
172-
},
173-
},
174-
"widget.accordion.item": {
175-
style: {
176-
"--accordion-background-color": "hsl(213 27% 18%)",
177-
"--accordion-background-color-dark": "hsl(213 27% 18%)",
178-
"--accordion-background-hover": "hsl(213 27% 22%)",
179-
"--accordion-background-hover-dark": "hsl(213 27% 22%)",
180-
"--accordion-border-color": "hsl(213 19% 25%)",
181-
"--accordion-border-color-dark": "hsl(213 19% 25%)",
182-
"--accordion-radius": "0.625rem",
183-
},
184-
},
185-
"widget.switch.track": {
186-
style: {
187-
"--switch-background-color": "hsl(213 19% 25%)",
188-
"--switch-background-color-checked": "hsl(163 69% 47%)",
189-
},
190-
},
191-
"widget.footer.accept-button": { style: acceptButton },
192-
"widget.footer.reject-button": { style: darkButton },
193-
"widget.footer.save-button": { style: saveButton },
194-
"widget.branding": {
195-
style: { display: "none" },
196-
},
197-
"dialog.footer": {
198-
style: { display: "none" },
199-
},
200-
} as const
62+
} satisfies Theme
20163

202-
const options = backendURL
203-
? {
204-
mode: "c15t" as const,
205-
backendURL,
206-
legalLinks: { privacyPolicy: { href: "/privacy" }, termsOfService: { href: "/terms" } },
207-
}
208-
: {
209-
mode: "offline" as const,
210-
legalLinks: { privacyPolicy: { href: "/privacy" }, termsOfService: { href: "/terms" } },
211-
}
64+
const uiOptions = {
65+
theme,
66+
colorScheme: "dark",
67+
legalLinks: { privacyPolicy: { href: "/privacy" }, termsOfService: { href: "/terms" } },
68+
} satisfies Partial<ConsentManagerOptions>
69+
70+
const options: ConsentManagerOptions = backendURL
71+
? { mode: "hosted", backendURL, ...uiOptions }
72+
: { mode: "offline", ...uiOptions }
21273

21374
function DialogDismissHandler() {
214-
const { isPrivacyDialogOpen, setIsPrivacyDialogOpen, setShowPopup } = useConsentManager()
75+
const { activeUI, setActiveUI } = useConsentManager()
76+
const isPrivacyDialogOpen = activeUI === "dialog"
21577

78+
// 1.x used setIsPrivacyDialogOpen(false) + setShowPopup(true); in 2.x a single
79+
// setActiveUI("banner") closes the dialog and re-shows the banner.
21680
const dismiss = useCallback(() => {
217-
setIsPrivacyDialogOpen(false)
218-
setShowPopup(true)
219-
}, [setIsPrivacyDialogOpen, setShowPopup])
81+
setActiveUI("banner")
82+
}, [setActiveUI])
22083

22184
useEffect(() => {
22285
if (!isPrivacyDialogOpen) return
@@ -230,7 +93,7 @@ function DialogDismissHandler() {
23093

23194
const handleClick = (e: MouseEvent) => {
23295
const target = e.target as HTMLElement
233-
if (target.dataset.testid === "consent-manager-dialog-overlay") {
96+
if (target.dataset.testid === "consent-dialog-overlay") {
23497
dismiss()
23598
}
23699
}
@@ -250,8 +113,8 @@ export function ConsentProvider({ children }: { children: React.ReactNode }) {
250113
return (
251114
<ConsentManagerProvider options={options}>
252115
{children}
253-
<CookieBanner theme={theme} />
254-
<ConsentManagerDialog theme={theme} />
116+
<ConsentBanner hideBranding />
117+
<ConsentDialog hideBranding />
255118
<DialogDismissHandler />
256119
</ConsentManagerProvider>
257120
)

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"dead-code": "knip"
2424
},
2525
"dependencies": {
26-
"@c15t/nextjs": "^1.8.6",
26+
"@c15t/nextjs": "^2.1.0",
2727
"@neondatabase/serverless": "^1.1.0",
2828
"@octokit/auth-app": "^8.2.0",
2929
"@octokit/rest": "^22.0.1",

0 commit comments

Comments
 (0)