Extract components and hooks from 4 oversized pages (518–508 LOC each) to bring each page.tsx under 300 LOC (hard cap 500). Zero behavior changes. - dsr/new: TypeSelector, SourceSelector → _components/; useNewDSRForm → _hooks/ - compliance-hub: QuickActions, StatsRow, DomainChart, MappingsAndFindings, RegulationsTable → _components/; useComplianceHub → _hooks/ - iace/[projectId]/monitoring: Badges, EventForm, ResolveModal, TimelineEvent → _components/; useMonitoring → _hooks/ - cookie-banner: BannerPreview, CategoryCard → _components/; useCookieBanner → _hooks/ Result: page.tsx LOC: dsr/new=259, compliance-hub=95, monitoring=157, cookie-banner=212 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
185 lines
5.7 KiB
TypeScript
185 lines
5.7 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 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)
|
|
|
|
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()
|
|
}, [])
|
|
|
|
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,
|
|
}
|
|
}
|