Files
breakpilot-compliance/breakpilot-compliance-sdk/packages/react/src/components/ConsentBanner.tsx
Benjamin Boenisch 4435e7ea0a Initial commit: breakpilot-compliance - Compliance SDK Platform
Services: Admin-Compliance, Backend-Compliance,
AI-Compliance-SDK, Consent-SDK, Developer-Portal,
PCA-Platform, DSMS

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-11 23:47:28 +01:00

214 lines
7.0 KiB
TypeScript

'use client'
import React, { useState, useCallback } from 'react'
import { useCompliance } from '../provider'
import type { ConsentPurpose, CookieBannerPosition, CookieBannerTheme } from '@breakpilot/compliance-sdk-types'
export interface ConsentBannerProps {
position?: CookieBannerPosition
theme?: CookieBannerTheme
onConsentChange?: (consents: Record<ConsentPurpose, boolean>) => void
privacyPolicyUrl?: string
imprintUrl?: string
className?: string
style?: React.CSSProperties
}
export function ConsentBanner({
position = 'BOTTOM',
theme = 'LIGHT',
onConsentChange,
privacyPolicyUrl = '/privacy',
imprintUrl = '/imprint',
className,
style,
}: ConsentBannerProps) {
const { state, dispatch } = useCompliance()
const [showSettings, setShowSettings] = useState(false)
const [consents, setConsents] = useState<Record<ConsentPurpose, boolean>>({
ESSENTIAL: true,
FUNCTIONAL: false,
ANALYTICS: false,
MARKETING: false,
PERSONALIZATION: false,
THIRD_PARTY: false,
})
const config = state.cookieBanner
const handleAcceptAll = useCallback(() => {
const allConsents: Record<ConsentPurpose, boolean> = {
ESSENTIAL: true,
FUNCTIONAL: true,
ANALYTICS: true,
MARKETING: true,
PERSONALIZATION: true,
THIRD_PARTY: true,
}
setConsents(allConsents)
onConsentChange?.(allConsents)
// Would save to backend here
}, [onConsentChange])
const handleRejectAll = useCallback(() => {
const minimalConsents: Record<ConsentPurpose, boolean> = {
ESSENTIAL: true,
FUNCTIONAL: false,
ANALYTICS: false,
MARKETING: false,
PERSONALIZATION: false,
THIRD_PARTY: false,
}
setConsents(minimalConsents)
onConsentChange?.(minimalConsents)
}, [onConsentChange])
const handleSaveSettings = useCallback(() => {
onConsentChange?.(consents)
setShowSettings(false)
}, [consents, onConsentChange])
const handleConsentToggle = useCallback((purpose: ConsentPurpose) => {
if (purpose === 'ESSENTIAL') return // Cannot disable essential
setConsents(prev => ({ ...prev, [purpose]: !prev[purpose] }))
}, [])
const positionStyles: Record<CookieBannerPosition, React.CSSProperties> = {
TOP: { top: 0, left: 0, right: 0 },
BOTTOM: { bottom: 0, left: 0, right: 0 },
CENTER: { top: '50%', left: '50%', transform: 'translate(-50%, -50%)' },
}
const themeStyles: Record<CookieBannerTheme, React.CSSProperties> = {
LIGHT: { backgroundColor: '#ffffff', color: '#1a1a1a' },
DARK: { backgroundColor: '#1a1a1a', color: '#ffffff' },
CUSTOM: config?.customColors
? {
backgroundColor: config.customColors.background,
color: config.customColors.text,
}
: {},
}
const bannerStyle: React.CSSProperties = {
position: 'fixed',
zIndex: 9999,
padding: '20px',
boxShadow: '0 -2px 10px rgba(0, 0, 0, 0.1)',
fontFamily: 'system-ui, -apple-system, sans-serif',
...positionStyles[position],
...themeStyles[theme],
...style,
}
const buttonBaseStyle: React.CSSProperties = {
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
fontWeight: 500,
marginRight: '10px',
}
const primaryButtonStyle: React.CSSProperties = {
...buttonBaseStyle,
backgroundColor: theme === 'DARK' ? '#ffffff' : '#1a1a1a',
color: theme === 'DARK' ? '#1a1a1a' : '#ffffff',
}
const secondaryButtonStyle: React.CSSProperties = {
...buttonBaseStyle,
backgroundColor: 'transparent',
border: `1px solid ${theme === 'DARK' ? '#ffffff' : '#1a1a1a'}`,
color: theme === 'DARK' ? '#ffffff' : '#1a1a1a',
}
if (showSettings) {
return (
<div style={bannerStyle} className={className}>
<h3 style={{ margin: '0 0 15px', fontSize: '18px' }}>
{config?.texts?.settings || 'Cookie-Einstellungen'}
</h3>
<div style={{ marginBottom: '20px' }}>
{Object.entries({
ESSENTIAL: { name: 'Notwendig', description: 'Erforderlich für die Grundfunktionen' },
FUNCTIONAL: { name: 'Funktional', description: 'Verbesserte Funktionen' },
ANALYTICS: { name: 'Analyse', description: 'Nutzungsstatistiken' },
MARKETING: { name: 'Marketing', description: 'Personalisierte Werbung' },
}).map(([key, { name, description }]) => (
<div
key={key}
style={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '10px 0',
borderBottom: `1px solid ${theme === 'DARK' ? '#333' : '#eee'}`,
}}
>
<div>
<div style={{ fontWeight: 500 }}>{name}</div>
<div style={{ fontSize: '12px', opacity: 0.7 }}>{description}</div>
</div>
<label style={{ cursor: key === 'ESSENTIAL' ? 'not-allowed' : 'pointer' }}>
<input
type="checkbox"
checked={consents[key as ConsentPurpose]}
onChange={() => handleConsentToggle(key as ConsentPurpose)}
disabled={key === 'ESSENTIAL'}
style={{ width: '20px', height: '20px' }}
/>
</label>
</div>
))}
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px' }}>
<button style={primaryButtonStyle} onClick={handleSaveSettings}>
{config?.texts?.save || 'Einstellungen speichern'}
</button>
<button style={secondaryButtonStyle} onClick={() => setShowSettings(false)}>
Zurück
</button>
</div>
</div>
)
}
return (
<div style={bannerStyle} className={className}>
<div style={{ marginBottom: '15px' }}>
<h3 style={{ margin: '0 0 10px', fontSize: '18px' }}>
{config?.texts?.title || 'Cookie-Einwilligung'}
</h3>
<p style={{ margin: 0, fontSize: '14px', opacity: 0.8 }}>
{config?.texts?.description ||
'Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Weitere Informationen finden Sie in unserer Datenschutzerklärung.'}
</p>
</div>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '10px', alignItems: 'center' }}>
<button style={primaryButtonStyle} onClick={handleAcceptAll}>
{config?.texts?.acceptAll || 'Alle akzeptieren'}
</button>
<button style={secondaryButtonStyle} onClick={handleRejectAll}>
{config?.texts?.rejectAll || 'Nur notwendige'}
</button>
<button style={secondaryButtonStyle} onClick={() => setShowSettings(true)}>
{config?.texts?.settings || 'Einstellungen'}
</button>
<div style={{ marginLeft: 'auto', fontSize: '12px' }}>
<a href={privacyPolicyUrl} style={{ marginRight: '15px', color: 'inherit' }}>
Datenschutz
</a>
<a href={imprintUrl} style={{ color: 'inherit' }}>
Impressum
</a>
</div>
</div>
</div>
)
}