/** * Web Component * * Usage: * * */ import type { ConsentPurpose, CookieBannerPosition, CookieBannerTheme, } from '@breakpilot/compliance-sdk-types' import { BreakPilotElement, COMMON_STYLES } from './base' const TRANSLATIONS = { de: { title: 'Cookie-Einwilligung', description: 'Wir verwenden Cookies, um Ihre Erfahrung zu verbessern. Weitere Informationen finden Sie in unserer Datenschutzerklärung.', acceptAll: 'Alle akzeptieren', rejectAll: 'Nur notwendige', settings: 'Einstellungen', save: 'Einstellungen speichern', back: 'Zurück', privacy: 'Datenschutz', imprint: 'Impressum', categories: { 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' }, }, }, en: { title: 'Cookie Consent', description: 'We use cookies to improve your experience. For more information, please see our privacy policy.', acceptAll: 'Accept All', rejectAll: 'Reject Non-Essential', settings: 'Settings', save: 'Save Settings', back: 'Back', privacy: 'Privacy Policy', imprint: 'Imprint', categories: { ESSENTIAL: { name: 'Essential', description: 'Required for basic functionality' }, FUNCTIONAL: { name: 'Functional', description: 'Enhanced features' }, ANALYTICS: { name: 'Analytics', description: 'Usage statistics' }, MARKETING: { name: 'Marketing', description: 'Personalized advertising' }, }, }, } export class ConsentBannerElement extends BreakPilotElement { static get observedAttributes(): string[] { return [ 'api-key', 'api-endpoint', 'position', 'theme', 'language', 'privacy-url', 'imprint-url', ] } private consents: Record = { ESSENTIAL: true, FUNCTIONAL: false, ANALYTICS: false, MARKETING: false, PERSONALIZATION: false, THIRD_PARTY: false, } private showSettings = false connectedCallback(): void { super.connectedCallback() this.loadStoredConsents() } private get position(): CookieBannerPosition { return (this.getAttribute('position')?.toUpperCase() as CookieBannerPosition) || 'BOTTOM' } private get theme(): CookieBannerTheme { return (this.getAttribute('theme')?.toUpperCase() as CookieBannerTheme) || 'LIGHT' } private get language(): 'de' | 'en' { return (this.getAttribute('language') as 'de' | 'en') || 'de' } private get privacyUrl(): string { return this.getAttribute('privacy-url') || '/privacy' } private get imprintUrl(): string { return this.getAttribute('imprint-url') || '/imprint' } private get t() { return TRANSLATIONS[this.language] } private loadStoredConsents(): void { try { const stored = localStorage.getItem('breakpilot_consents') if (stored) { this.consents = JSON.parse(stored) this.hide() } } catch { // Ignore errors } } private storeConsents(): void { try { localStorage.setItem('breakpilot_consents', JSON.stringify(this.consents)) localStorage.setItem('breakpilot_consents_timestamp', Date.now().toString()) } catch { // Ignore errors } } private handleAcceptAll = (): void => { this.consents = { ESSENTIAL: true, FUNCTIONAL: true, ANALYTICS: true, MARKETING: true, PERSONALIZATION: true, THIRD_PARTY: true, } this.applyConsents() } private handleRejectAll = (): void => { this.consents = { ESSENTIAL: true, FUNCTIONAL: false, ANALYTICS: false, MARKETING: false, PERSONALIZATION: false, THIRD_PARTY: false, } this.applyConsents() } private handleSave = (): void => { this.applyConsents() } private applyConsents(): void { this.storeConsents() this.emit('consent-change', { consents: this.consents }) this.hide() } private toggleSettings = (): void => { this.showSettings = !this.showSettings this.render() } public show(): void { this.style.display = 'block' this.render() } public hide(): void { this.style.display = 'none' } public getConsents(): Record { return { ...this.consents } } protected render(): void { const isDark = this.theme === 'DARK' const bgColor = isDark ? '#1a1a1a' : '#ffffff' const textColor = isDark ? '#ffffff' : '#1a1a1a' const borderColor = isDark ? '#333' : '#eee' const positionStyles = { TOP: 'top: 0; left: 0; right: 0;', BOTTOM: 'bottom: 0; left: 0; right: 0;', CENTER: 'top: 50%; left: 50%; transform: translate(-50%, -50%); max-width: 500px;', } const styles = ` ${COMMON_STYLES} :host { position: fixed; z-index: 99999; ${positionStyles[this.position]} } .banner { background-color: ${bgColor}; color: ${textColor}; padding: 20px; box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1); } .title { margin: 0 0 10px; font-size: 18px; font-weight: 600; } .description { margin: 0 0 15px; opacity: 0.8; } .buttons { display: flex; flex-wrap: wrap; gap: 10px; align-items: center; } .btn { padding: 10px 20px; border-radius: 4px; border: none; font-weight: 500; } .btn-primary { background-color: ${isDark ? '#ffffff' : '#1a1a1a'}; color: ${isDark ? '#1a1a1a' : '#ffffff'}; } .btn-secondary { background-color: transparent; border: 1px solid ${textColor}; color: ${textColor}; } .links { margin-left: auto; font-size: 12px; } .links a { color: ${textColor}; text-decoration: none; margin-right: 15px; } .links a:last-child { margin-right: 0; } .category { display: flex; align-items: center; justify-content: space-between; padding: 10px 0; border-bottom: 1px solid ${borderColor}; } .category-name { font-weight: 500; } .category-description { font-size: 12px; opacity: 0.7; } .checkbox { width: 20px; height: 20px; } .checkbox:disabled { cursor: not-allowed; } ` if (this.showSettings) { this.renderSettings(styles) } else { this.renderMain(styles) } } private renderMain(styles: string): void { const t = this.t this.shadow.innerHTML = ` ` this.shadow.getElementById('accept-all')!.onclick = this.handleAcceptAll this.shadow.getElementById('reject-all')!.onclick = this.handleRejectAll this.shadow.getElementById('settings')!.onclick = this.toggleSettings } private renderSettings(styles: string): void { const t = this.t const categories = ['ESSENTIAL', 'FUNCTIONAL', 'ANALYTICS', 'MARKETING'] as const const categoriesHtml = categories .map( cat => `
${t.categories[cat].name}
${t.categories[cat].description}
` ) .join('') this.shadow.innerHTML = ` ` this.shadow.getElementById('save')!.onclick = this.handleSave this.shadow.getElementById('back')!.onclick = this.toggleSettings // Handle checkbox changes this.shadow.querySelectorAll('.checkbox').forEach(checkbox => { checkbox.onchange = () => { const category = checkbox.dataset.category as ConsentPurpose if (category !== 'ESSENTIAL') { this.consents[category] = checkbox.checked } } }) } } // Register the custom element if (typeof customElements !== 'undefined') { customElements.define('breakpilot-consent-banner', ConsentBannerElement) }