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>
214 lines
7.0 KiB
TypeScript
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>
|
|
)
|
|
}
|