36d9f929c6
F9: Verarbeiter-Tabelle - VendorTable.tsx: 82+ vendors grouped by category with expandable cookie details - EmbeddableVendorHTML.tsx: Copy-pasteable HTML table for privacy policy - Tab system: Konfiguration | Verarbeiter | Einbettung F3: Multi-Site UI - SiteSelector.tsx: Domain dropdown with "Neue Seite anlegen" dialog - useCookieBanner hook extended with sites management - Config/vendors reload per selected site Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
224 lines
6.8 KiB
TypeScript
224 lines
6.8 KiB
TypeScript
'use client'
|
|
|
|
import React, { useState } from 'react'
|
|
|
|
export interface Cookie {
|
|
name: string
|
|
provider: string
|
|
purpose: string
|
|
expiry: string
|
|
type: 'first-party' | 'third-party'
|
|
}
|
|
|
|
export interface CookieCategory {
|
|
id: string
|
|
name: string
|
|
description: string
|
|
required: boolean
|
|
enabled: boolean
|
|
cookies: Cookie[]
|
|
}
|
|
|
|
export interface BannerConfig {
|
|
position: 'bottom' | 'top' | 'center'
|
|
style: 'bar' | 'popup' | 'modal'
|
|
primaryColor: string
|
|
showDeclineAll: boolean
|
|
showSettings: boolean
|
|
blockScripts: boolean
|
|
}
|
|
|
|
export interface BannerTexts {
|
|
title: string
|
|
description: string
|
|
privacyLink: string
|
|
}
|
|
|
|
const DEFAULT_COOKIE_CATEGORIES: CookieCategory[] = [
|
|
{
|
|
id: 'necessary',
|
|
name: 'Notwendig',
|
|
description: 'Diese Cookies sind fuer die Grundfunktionen der Website erforderlich.',
|
|
required: true,
|
|
enabled: true,
|
|
cookies: [
|
|
{ name: 'session_id', provider: 'Eigene', purpose: 'Session-Verwaltung', expiry: 'Session', type: 'first-party' },
|
|
{ name: 'csrf_token', provider: 'Eigene', purpose: 'Sicherheit', expiry: 'Session', type: 'first-party' },
|
|
],
|
|
},
|
|
{
|
|
id: 'analytics',
|
|
name: 'Analyse',
|
|
description: 'Diese Cookies helfen uns, die Nutzung der Website zu verstehen.',
|
|
required: false,
|
|
enabled: true,
|
|
cookies: [
|
|
{ name: '_ga', provider: 'Google Analytics', purpose: 'Nutzeranalyse', expiry: '2 Jahre', type: 'third-party' },
|
|
{ name: '_gid', provider: 'Google Analytics', purpose: 'Nutzeranalyse', expiry: '24 Stunden', type: 'third-party' },
|
|
],
|
|
},
|
|
{
|
|
id: 'marketing',
|
|
name: 'Marketing',
|
|
description: 'Diese Cookies werden fuer personalisierte Werbung verwendet.',
|
|
required: false,
|
|
enabled: false,
|
|
cookies: [
|
|
{ name: '_fbp', provider: 'Meta (Facebook)', purpose: 'Werbung', expiry: '3 Monate', type: 'third-party' },
|
|
{ name: 'IDE', provider: 'Google Ads', purpose: 'Werbung', expiry: '1 Jahr', type: 'third-party' },
|
|
],
|
|
},
|
|
{
|
|
id: 'preferences',
|
|
name: 'Praeferenzen',
|
|
description: 'Diese Cookies speichern Ihre Einstellungen und Praeferenzen.',
|
|
required: false,
|
|
enabled: true,
|
|
cookies: [
|
|
{ name: 'language', provider: 'Eigene', purpose: 'Spracheinstellung', expiry: '1 Jahr', type: 'first-party' },
|
|
{ name: 'theme', provider: 'Eigene', purpose: 'Design-Einstellung', expiry: '1 Jahr', type: 'first-party' },
|
|
],
|
|
},
|
|
]
|
|
|
|
const defaultConfig: BannerConfig = {
|
|
position: 'bottom',
|
|
style: 'bar',
|
|
primaryColor: '#6366f1',
|
|
showDeclineAll: true,
|
|
showSettings: true,
|
|
blockScripts: true,
|
|
}
|
|
|
|
const defaultBannerTexts: BannerTexts = {
|
|
title: 'Wir verwenden Cookies',
|
|
description: 'Wir nutzen Cookies, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen.',
|
|
privacyLink: '/datenschutz',
|
|
}
|
|
|
|
export interface BannerSite {
|
|
id: string
|
|
site_id: string
|
|
site_name: string
|
|
site_url: string
|
|
is_active: boolean
|
|
}
|
|
|
|
export function useCookieBanner() {
|
|
const [categories, setCategories] = useState<CookieCategory[]>([])
|
|
const [config, setConfig] = useState<BannerConfig>(defaultConfig)
|
|
const [bannerTexts, setBannerTexts] = useState<BannerTexts>(defaultBannerTexts)
|
|
const [isSaving, setIsSaving] = useState(false)
|
|
const [exportToast, setExportToast] = useState<string | null>(null)
|
|
const [sites, setSites] = useState<BannerSite[]>([])
|
|
const [activeSiteId, setActiveSiteId] = useState<string | null>(null)
|
|
|
|
// Load sites list
|
|
React.useEffect(() => {
|
|
fetch('/api/sdk/v1/banner/admin/sites')
|
|
.then(r => r.ok ? r.json() : [])
|
|
.then(data => {
|
|
const siteList = Array.isArray(data) ? data : []
|
|
setSites(siteList)
|
|
if (siteList.length > 0 && !activeSiteId) {
|
|
setActiveSiteId(siteList[0].site_id)
|
|
}
|
|
})
|
|
.catch(() => {})
|
|
}, [])
|
|
|
|
// Load config for active site
|
|
React.useEffect(() => {
|
|
const loadConfig = async () => {
|
|
try {
|
|
const response = await fetch('/api/sdk/v1/einwilligungen/cookie-banner/config')
|
|
if (response.ok) {
|
|
const data = await response.json()
|
|
setCategories(data.categories?.length > 0 ? data.categories : DEFAULT_COOKIE_CATEGORIES)
|
|
if (data.config && Object.keys(data.config).length > 0) {
|
|
setConfig(prev => ({ ...prev, ...data.config }))
|
|
const savedTexts = data.config.banner_texts || data.config.texts
|
|
if (savedTexts) {
|
|
setBannerTexts(prev => ({ ...prev, ...savedTexts }))
|
|
}
|
|
}
|
|
} else {
|
|
setCategories(DEFAULT_COOKIE_CATEGORIES)
|
|
}
|
|
} catch {
|
|
setCategories(DEFAULT_COOKIE_CATEGORIES)
|
|
}
|
|
}
|
|
loadConfig()
|
|
}, [activeSiteId])
|
|
|
|
const createSite = async (data: { site_id: string; site_name: string; site_url: string }) => {
|
|
const res = await fetch('/api/sdk/v1/banner/admin/sites', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify(data),
|
|
})
|
|
if (res.ok) {
|
|
const newSite = await res.json()
|
|
setSites(prev => [...prev, newSite])
|
|
setActiveSiteId(newSite.site_id || data.site_id)
|
|
}
|
|
}
|
|
|
|
const handleCategoryToggle = async (categoryId: string, enabled: boolean) => {
|
|
setCategories(prev =>
|
|
prev.map(cat => cat.id === categoryId ? { ...cat, enabled } : cat)
|
|
)
|
|
try {
|
|
await fetch('/api/sdk/v1/einwilligungen/cookie-banner/config', {
|
|
method: 'PUT',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ categoryId, enabled }),
|
|
})
|
|
} catch {
|
|
// Silently ignore — local state already updated
|
|
}
|
|
}
|
|
|
|
const handleExportCode = async () => {
|
|
try {
|
|
const res = await fetch('/api/sdk/v1/einwilligungen/cookie-banner/embed-code')
|
|
if (res.ok) {
|
|
const data = await res.json()
|
|
const code = data.embed_code || data.script || ''
|
|
await navigator.clipboard.writeText(code)
|
|
setExportToast('Embed-Code in Zwischenablage kopiert!')
|
|
setTimeout(() => setExportToast(null), 3000)
|
|
} else {
|
|
setExportToast('Fehler beim Laden des Embed-Codes')
|
|
setTimeout(() => setExportToast(null), 3000)
|
|
}
|
|
} catch {
|
|
setExportToast('Fehler beim Kopieren in die Zwischenablage')
|
|
setTimeout(() => setExportToast(null), 3000)
|
|
}
|
|
}
|
|
|
|
const handleSaveConfig = async () => {
|
|
setIsSaving(true)
|
|
try {
|
|
await fetch('/api/sdk/v1/einwilligungen/cookie-banner/config', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ categories, config: { ...config, banner_texts: bannerTexts } }),
|
|
})
|
|
} catch {
|
|
// Silently ignore
|
|
} finally {
|
|
setIsSaving(false)
|
|
}
|
|
}
|
|
|
|
return {
|
|
categories, config, bannerTexts, isSaving, exportToast,
|
|
setConfig, setBannerTexts,
|
|
handleCategoryToggle, handleExportCode, handleSaveConfig,
|
|
sites, activeSiteId, setActiveSiteId, createSite,
|
|
}
|
|
}
|