From 165b9ad9d391d527ad7c52edd8d36f4a11f9d6a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=C3=89mile=20R=C3=A9?=
Date: Thu, 23 Apr 2026 19:07:11 +0400
Subject: [PATCH] Add slug to cookie categories for stable consent identifiers
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
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é
---
.../_components/CategoryDialog.tsx | 27 +++++++++++-
.../cookies/_components/CategorySection.tsx | 8 +++-
.../cookies/_components/EditCategoryForm.tsx | 13 +++++-
packages/cookie-banner/src/client.ts | 11 ++---
.../src/components/category-list.ts | 1 +
.../src/components/category-toggle.ts | 7 +--
.../cookie-banner/src/components/category.ts | 4 ++
.../src/components/cookie-banner-root.ts | 8 ++--
pkg/cookiebanner/defaults.go | 11 ++---
pkg/cookiebanner/errors.go | 1 +
pkg/cookiebanner/service.go | 16 +++++++
pkg/coredata/cookie_banner_version.go | 1 +
pkg/coredata/cookie_category.go | 21 +++++++++
pkg/coredata/migrations/20260423T144457Z.sql | 24 +++++++++++
.../api/console/v1/cookie_banner_resolvers.go | 8 ++++
.../console/v1/graphql/cookie_banner.graphql | 3 ++
.../api/console/v1/types/cookie_category.go | 1 +
pkg/validator/validator_format.go | 32 ++++++++++++++
pkg/validator/validator_format_test.go | 43 +++++++++++++++++++
19 files changed, 218 insertions(+), 22 deletions(-)
create mode 100644 pkg/coredata/migrations/20260423T144457Z.sql
diff --git a/apps/console/src/pages/organizations/cookie-banners/configuration/_components/CategoryDialog.tsx b/apps/console/src/pages/organizations/cookie-banners/configuration/_components/CategoryDialog.tsx
index c5cd3bc5a..b9d73c68c 100644
--- a/apps/console/src/pages/organizations/cookie-banners/configuration/_components/CategoryDialog.tsx
+++ b/apps/console/src/pages/organizations/cookie-banners/configuration/_components/CategoryDialog.tsx
@@ -40,6 +40,7 @@ const createMutation = graphql`
node {
id
name
+ slug
description
kind
rank
@@ -78,8 +79,27 @@ export function CategoryDialog({
const [create, isCreating] = useMutation(createMutation);
const [name, setName] = useState("");
+ const [slug, setSlug] = useState("");
+ const [slugTouched, setSlugTouched] = useState(false);
const [description, setDescription] = useState("");
+ const handleNameChange = (value: string) => {
+ setName(value);
+ if (!slugTouched) {
+ setSlug(
+ value
+ .toLowerCase()
+ .replace(/[^a-z0-9]+/g, "-")
+ .replace(/^-|-$/g, ""),
+ );
+ }
+ };
+
+ const handleSlugChange = (value: string) => {
+ setSlugTouched(true);
+ setSlug(value);
+ };
+
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
@@ -88,6 +108,7 @@ export function CategoryDialog({
input: {
cookieBannerId,
name,
+ slug,
description,
rank: nextRank,
},
@@ -113,7 +134,11 @@ export function CategoryDialog({
diff --git a/apps/console/src/pages/organizations/cookie-banners/configuration/cookies/_components/EditCategoryForm.tsx b/apps/console/src/pages/organizations/cookie-banners/configuration/cookies/_components/EditCategoryForm.tsx
index bc8f35c84..0b35de7f9 100644
--- a/apps/console/src/pages/organizations/cookie-banners/configuration/cookies/_components/EditCategoryForm.tsx
+++ b/apps/console/src/pages/organizations/cookie-banners/configuration/cookies/_components/EditCategoryForm.tsx
@@ -18,14 +18,16 @@ import { useState } from "react";
interface EditCategoryFormProps {
name: string;
+ slug: string;
description: string;
isUpdating: boolean;
- onSave: (name: string, description: string) => void;
+ onSave: (name: string, slug: string, description: string) => void;
onCancel: () => void;
}
export function EditCategoryForm({
name,
+ slug,
description,
isUpdating,
onSave,
@@ -33,6 +35,7 @@ export function EditCategoryForm({
}: EditCategoryFormProps) {
const { __ } = useTranslate();
const [editName, setEditName] = useState(name);
+ const [editSlug, setEditSlug] = useState(slug);
const [editDescription, setEditDescription] = useState(description);
return (
@@ -42,6 +45,12 @@ export function EditCategoryForm({
onChange={e => setEditName(e.target.value)}
placeholder={__("Category name")}
/>
+