From 598c6b112cef537c29bb3dffbb0046eee84ea889 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89mile=20R=C3=A9?= Date: Fri, 24 Apr 2026 18:04:05 +0400 Subject: [PATCH] Add cookie_policy_url field to cookie banners MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Introduce a required cookie_policy_url alongside the existing privacy_policy_url (now optional) so banners can link directly to a dedicated cookie policy — a compliance best practice recommended by CNIL, ICO, and the EDPB. Existing rows are seeded from their current privacy_policy_url value. Both {{cookie_policy_link}} and {{privacy_policy_link}} placeholders are supported independently in banner description translations. Signed-off-by: Émile Ré --- .../cookie-banners/NewCookieBannerPage.tsx | 14 +++++- .../_components/BannerSettingsForm.tsx | 14 ++++-- .../_components/BannerPreview.tsx | 50 ++++++++----------- .../_components/BannerTranslationSection.tsx | 22 ++++++-- .../_components/translationDefaults.ts | 2 + packages/cookie-banner/src/client.ts | 3 +- .../src/themed-banner/themed-banner.ts | 16 ++++-- pkg/cookiebanner/defaults.go | 12 +++-- pkg/cookiebanner/service.go | 32 +++++++++--- pkg/coredata/cookie_banner.go | 12 ++++- pkg/coredata/cookie_banner_version.go | 3 +- pkg/coredata/migrations/20260424T140000Z.sql | 8 +++ .../api/console/v1/cookie_banner_resolvers.go | 2 + .../console/v1/graphql/cookie_banner.graphql | 7 ++- .../api/console/v1/types/cookie_banner.go | 1 + 15 files changed, 142 insertions(+), 56 deletions(-) create mode 100644 pkg/coredata/migrations/20260424T140000Z.sql diff --git a/apps/console/src/pages/organizations/cookie-banners/NewCookieBannerPage.tsx b/apps/console/src/pages/organizations/cookie-banners/NewCookieBannerPage.tsx index b53f7cc3d..811259409 100644 --- a/apps/console/src/pages/organizations/cookie-banners/NewCookieBannerPage.tsx +++ b/apps/console/src/pages/organizations/cookie-banners/NewCookieBannerPage.tsx @@ -60,6 +60,7 @@ export default function NewCookieBannerPage() { const [name, setName] = useState(""); const [origin, setOrigin] = useState(""); + const [cookiePolicyUrl, setCookiePolicyUrl] = useState(""); const [privacyPolicyUrl, setPrivacyPolicyUrl] = useState(""); const [consentExpiryDays, setConsentExpiryDays] = useState("365"); const [consentMode, setConsentMode] = useState("OPT_IN"); @@ -73,7 +74,8 @@ export default function NewCookieBannerPage() { organizationId, name, origin, - privacyPolicyUrl, + cookiePolicyUrl, + privacyPolicyUrl: privacyPolicyUrl || undefined, consentExpiryDays: parseInt(consentExpiryDays, 10), consentMode: consentMode as "OPT_IN" | "OPT_OUT", }, @@ -136,12 +138,20 @@ export default function NewCookieBannerPage() { /> + + setCookiePolicyUrl(e.target.value)} + placeholder="https://example.com/cookies" + required + /> + + setPrivacyPolicyUrl(e.target.value)} placeholder="https://example.com/privacy" - required /> diff --git a/apps/console/src/pages/organizations/cookie-banners/configuration/_components/BannerSettingsForm.tsx b/apps/console/src/pages/organizations/cookie-banners/configuration/_components/BannerSettingsForm.tsx index 36cb07b04..d725f3ea4 100644 --- a/apps/console/src/pages/organizations/cookie-banners/configuration/_components/BannerSettingsForm.tsx +++ b/apps/console/src/pages/organizations/cookie-banners/configuration/_components/BannerSettingsForm.tsx @@ -27,6 +27,7 @@ const bannerSettingsFormFragment = graphql` id name origin + cookiePolicyUrl privacyPolicyUrl consentExpiryDays consentMode @@ -40,6 +41,7 @@ const updateBannerMutation = graphql` cookieBanner { id name + cookiePolicyUrl privacyPolicyUrl consentExpiryDays consentMode @@ -67,7 +69,8 @@ export function BannerSettingsForm({ cookieBannerKey }: BannerSettingsFormProps) const [updateBanner, isUpdating] = useMutation(updateBannerMutation); const [name, setName] = useState(banner.name); - const [privacyPolicyUrl, setPrivacyPolicyUrl] = useState(banner.privacyPolicyUrl); + const [cookiePolicyUrl, setCookiePolicyUrl] = useState(banner.cookiePolicyUrl); + const [privacyPolicyUrl, setPrivacyPolicyUrl] = useState(banner.privacyPolicyUrl ?? ""); const [consentExpiryDays, setConsentExpiryDays] = useState(String(banner.consentExpiryDays)); const [consentMode, setConsentMode] = useState(banner.consentMode); const [defaultLanguage, setDefaultLanguage] = useState(banner.defaultLanguage); @@ -80,7 +83,8 @@ export function BannerSettingsForm({ cookieBannerKey }: BannerSettingsFormProps) input: { cookieBannerId: banner.id, name, - privacyPolicyUrl, + cookiePolicyUrl, + privacyPolicyUrl: privacyPolicyUrl || undefined, consentExpiryDays: parseInt(consentExpiryDays, 10), consentMode: consentMode, defaultLanguage, @@ -108,8 +112,12 @@ export function BannerSettingsForm({ cookieBannerKey }: BannerSettingsFormProps) + + setCookiePolicyUrl(e.target.value)} required /> + + - setPrivacyPolicyUrl(e.target.value)} required /> + setPrivacyPolicyUrl(e.target.value)} />
diff --git a/apps/console/src/pages/organizations/cookie-banners/configuration/translations/_components/BannerPreview.tsx b/apps/console/src/pages/organizations/cookie-banners/configuration/translations/_components/BannerPreview.tsx index 9d596a742..753899aee 100644 --- a/apps/console/src/pages/organizations/cookie-banners/configuration/translations/_components/BannerPreview.tsx +++ b/apps/console/src/pages/organizations/cookie-banners/configuration/translations/_components/BannerPreview.tsx @@ -21,19 +21,10 @@ interface BannerPreviewProps { buttonRejectAll: string; buttonCustomize: string; privacyPolicyLinkText: string; + cookiePolicyLinkText: string; showBranding: boolean; } -function interpolateDescription( - description: string, - linkText: string, -): string { - return description.replaceAll( - "{{privacy_policy_link}}", - linkText, - ); -} - export function BannerPreview({ bannerTitle, bannerDescription, @@ -41,10 +32,11 @@ export function BannerPreview({ buttonRejectAll, buttonCustomize, privacyPolicyLinkText, + cookiePolicyLinkText, showBranding, }: BannerPreviewProps) { - const descriptionParts = bannerDescription.split("{{privacy_policy_link}}"); - const hasPlaceholder = descriptionParts.length > 1; + const cookieParts = bannerDescription.split("{{cookie_policy_link}}"); + const hasAnyPlaceholder = cookieParts.length > 1 || bannerDescription.includes("{{privacy_policy_link}}"); return (
- {hasPlaceholder - ? descriptionParts.map((part, i) => ( - - {part} - {i < descriptionParts.length - 1 && ( - e.preventDefault()} - style={{ - color: "var(--probo-accent, #1a1a1a)", - textDecoration: "underline", - }} - > - {privacyPolicyLinkText} + {hasAnyPlaceholder + ? cookieParts.map((cookiePart, ci) => ( + + {cookiePart.split("{{privacy_policy_link}}").map((privPart, pi, arr) => ( + + {privPart} + {pi < arr.length - 1 && ( + e.preventDefault()} style={{ color: "var(--probo-accent, #1a1a1a)", textDecoration: "underline" }}> + {privacyPolicyLinkText} + + )} + + ))} + {ci < cookieParts.length - 1 && ( + e.preventDefault()} style={{ color: "var(--probo-accent, #1a1a1a)", textDecoration: "underline" }}> + {cookiePolicyLinkText} )} )) - : ( - interpolateDescription(bannerDescription, privacyPolicyLinkText) - )} + : bannerDescription}

-

{"Use {{privacy_policy_link}} to insert the privacy policy link."}

+

{"Use {{cookie_policy_link}} and {{privacy_policy_link}} to insert policy links."}