From ae937a35d7ebaff328abdd41698a46223b880750 Mon Sep 17 00:00:00 2001 From: Benjamin Admin Date: Tue, 12 May 2026 17:55:29 +0200 Subject: [PATCH] =?UTF-8?q?feat(cmp):=20Phase=203=20=E2=80=94=20backend=20?= =?UTF-8?q?consent=20withdrawal=20+=20consent=5Fid=20tracking?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ConsentBanner: save consent_id to localStorage after successful POST - Footer: DELETE /api/consent/{id} on consent re-open (Art. 17 DSGVO) - New proxy route: DELETE /api/consent/[id] → backend withdrawal endpoint Co-Authored-By: Claude Opus 4.6 (1M context) --- .../app/api/consent/[id]/route.ts | 21 +++++++++++++++++++ .../components/layout/ConsentBanner.tsx | 12 ++++++++--- .../components/layout/Footer.tsx | 7 ++++++- 3 files changed, 36 insertions(+), 4 deletions(-) create mode 100644 marketing-website/app/api/consent/[id]/route.ts diff --git a/marketing-website/app/api/consent/[id]/route.ts b/marketing-website/app/api/consent/[id]/route.ts new file mode 100644 index 0000000..ec5eccb --- /dev/null +++ b/marketing-website/app/api/consent/[id]/route.ts @@ -0,0 +1,21 @@ +import { NextRequest, NextResponse } from 'next/server' + +const BACKEND_URL = process.env.CONSENT_BACKEND_URL || 'https://macmini:3007/api/sdk/v1/banner' +const TENANT_ID = process.env.CONSENT_TENANT_ID || '9282a473-5c95-4b3a-bf78-0ecc0ec71d3e' + +export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) { + try { + const { id } = await params + const res = await fetch(`${BACKEND_URL}/consent/${id}`, { + method: 'DELETE', + headers: { 'X-Tenant-ID': TENANT_ID }, + }) + const data = await res.text() + return new NextResponse(data, { + status: res.status, + headers: { 'Content-Type': 'application/json' }, + }) + } catch { + return NextResponse.json({ error: 'Consent service not reachable' }, { status: 503 }) + } +} diff --git a/marketing-website/components/layout/ConsentBanner.tsx b/marketing-website/components/layout/ConsentBanner.tsx index de665b5..8d06f87 100644 --- a/marketing-website/components/layout/ConsentBanner.tsx +++ b/marketing-website/components/layout/ConsentBanner.tsx @@ -127,12 +127,12 @@ function detectCookies(): CookieEntry[] { return cookies } -async function sendConsent(consent: ConsentState, method: ConsentMethod, vendorConsents?: Record) { +async function sendConsent(consent: ConsentState, method: ConsentMethod, vendorConsents?: Record): Promise { try { const { device_type, browser, os } = detectDevice() const { blocked, released } = detectScripts() const cookies_set = detectCookies() - await fetch('/api/consent', { + const res = await fetch('/api/consent', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ @@ -161,8 +161,14 @@ async function sendConsent(consent: ConsentState, method: ConsentMethod, vendorC cookies_set, }), }) + const result = await res.json().catch(() => null) + if (result?.id) { + localStorage.setItem('bp_consent_id', result.id) + return result.id + } + return null } catch { - // Consent API not reachable — store locally anyway + return null } } diff --git a/marketing-website/components/layout/Footer.tsx b/marketing-website/components/layout/Footer.tsx index 8b91d39..9f10829 100644 --- a/marketing-website/components/layout/Footer.tsx +++ b/marketing-website/components/layout/Footer.tsx @@ -4,7 +4,12 @@ import { Cookie } from 'lucide-react' import { t } from '@/lib/content' import { useApp } from '@/lib/context' -function reopenConsentBanner() { +async function reopenConsentBanner() { + const consentId = localStorage.getItem('bp_consent_id') + if (consentId) { + fetch(`/api/consent/${consentId}`, { method: 'DELETE' }).catch(() => {}) + localStorage.removeItem('bp_consent_id') + } localStorage.removeItem('bp_consent') window.location.reload() }