Add slug to cookie categories for stable consent identifiers
The category slug provides a stable, URL-safe key used as the data-cookie-consent attribute value and consent data key, replacing the fragile category name. This prevents breakage when categories are renamed. - Add slug column with unique-per-banner constraint and backfill migration - Add Slug validator (lowercase alphanumeric + hyphens) - Propagate slug through GraphQL schema, service layer, and snapshot - Update console UI with slug field in create/edit forms - Switch cookie-banner widget to use slug as consent data keys Signed-off-by: Émile Ré <emile@getprobo.com>
This commit is contained in:
@@ -35,6 +35,7 @@ export interface CookieItem {
|
||||
|
||||
export interface Category {
|
||||
name: string;
|
||||
slug: string;
|
||||
description: string;
|
||||
kind: string;
|
||||
cookies: CookieItem[];
|
||||
@@ -174,7 +175,7 @@ export class CookieBannerClient {
|
||||
|
||||
const consentData: Record<string, boolean> = {};
|
||||
for (const cat of cfg.categories) {
|
||||
consentData[cat.name] = true;
|
||||
consentData[cat.slug] = true;
|
||||
}
|
||||
|
||||
this.recordConsent("ACCEPT_ALL", consentData);
|
||||
@@ -185,7 +186,7 @@ export class CookieBannerClient {
|
||||
|
||||
const consentData: Record<string, boolean> = {};
|
||||
for (const cat of cfg.categories) {
|
||||
consentData[cat.name] = cat.kind === "NECESSARY";
|
||||
consentData[cat.slug] = cat.kind === "NECESSARY";
|
||||
}
|
||||
|
||||
this.recordConsent("REJECT_ALL", consentData);
|
||||
@@ -196,7 +197,7 @@ export class CookieBannerClient {
|
||||
|
||||
const consentData: Record<string, boolean> = {};
|
||||
for (const cat of cfg.categories) {
|
||||
consentData[cat.name] = cat.kind === "NECESSARY" || !!categories[cat.name];
|
||||
consentData[cat.slug] = cat.kind === "NECESSARY" || !!categories[cat.slug];
|
||||
}
|
||||
|
||||
this.recordConsent("CUSTOMIZE", consentData);
|
||||
@@ -244,8 +245,8 @@ export class CookieBannerClient {
|
||||
const categoryCookies: Record<string, string[]> = {};
|
||||
const categoryLabels: Record<string, string> = {};
|
||||
for (const cat of this.config.categories) {
|
||||
categoryCookies[cat.name] = cat.cookies.map((c) => c.name);
|
||||
categoryLabels[cat.name] = cat.name;
|
||||
categoryCookies[cat.slug] = cat.cookies.map((c) => c.name);
|
||||
categoryLabels[cat.slug] = cat.name;
|
||||
}
|
||||
|
||||
const texts = this.config.texts;
|
||||
|
||||
@@ -59,6 +59,7 @@ export class ProboCategoryList extends ProboElement {
|
||||
|
||||
const wrapper = document.createElement("probo-category");
|
||||
wrapper.setAttribute("name", cat.name);
|
||||
wrapper.setAttribute("slug", cat.slug);
|
||||
wrapper.setAttribute("kind", cat.kind);
|
||||
wrapper.setAttribute("description", cat.description);
|
||||
wrapper.setAttribute("cookies", JSON.stringify(cat.cookies));
|
||||
|
||||
@@ -49,6 +49,7 @@ export class ProboCategoryToggle extends ProboElement {
|
||||
if (!this.category || !this.root) return;
|
||||
|
||||
const name = this.category.categoryName;
|
||||
const slug = this.category.categorySlug;
|
||||
this.checkbox.setAttribute("aria-label", name);
|
||||
const isRequired = this.category.kind === "NECESSARY";
|
||||
|
||||
@@ -59,14 +60,14 @@ export class ProboCategoryToggle extends ProboElement {
|
||||
}
|
||||
|
||||
const draft = this.root.consentDraft;
|
||||
this.checkbox.checked = !!draft[name];
|
||||
this.checkbox.checked = !!draft[slug];
|
||||
this.checkbox.addEventListener("change", this.handleChange);
|
||||
|
||||
if (this.root) {
|
||||
this.root.addEventListener("probo-state", (e: Event) => {
|
||||
const { state } = (e as CustomEvent).detail;
|
||||
if (state === "panel" && this.checkbox && this.category && this.root) {
|
||||
this.checkbox.checked = !!this.root.consentDraft[this.category.categoryName];
|
||||
this.checkbox.checked = !!this.root.consentDraft[this.category.categorySlug];
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -74,6 +75,6 @@ export class ProboCategoryToggle extends ProboElement {
|
||||
|
||||
private handleChange = (): void => {
|
||||
if (!this.checkbox || !this.category || !this.root) return;
|
||||
this.root.updateDraft(this.category.categoryName, this.checkbox.checked);
|
||||
this.root.updateDraft(this.category.categorySlug, this.checkbox.checked);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,10 @@ export class ProboCategory extends ProboElement {
|
||||
return this.getAttribute("name") ?? "Other";
|
||||
}
|
||||
|
||||
get categorySlug(): string {
|
||||
return this.getAttribute("slug") ?? this.categoryName.toLowerCase();
|
||||
}
|
||||
|
||||
get kind(): string {
|
||||
return this.getAttribute("kind") ?? "NORMAL";
|
||||
}
|
||||
|
||||
@@ -109,11 +109,11 @@ export class ProboCookieBannerRoot extends ProboElement implements ProboRootElem
|
||||
|
||||
for (const cat of config.categories) {
|
||||
if (cat.kind === "NECESSARY") {
|
||||
draft[cat.name] = true;
|
||||
} else if (existing && cat.name in existing) {
|
||||
draft[cat.name] = existing[cat.name];
|
||||
draft[cat.slug] = true;
|
||||
} else if (existing && cat.slug in existing) {
|
||||
draft[cat.slug] = existing[cat.slug];
|
||||
} else {
|
||||
draft[cat.name] = config.consent_mode === "OPT_OUT";
|
||||
draft[cat.slug] = config.consent_mode === "OPT_OUT";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user