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>
This commit is contained in:
@@ -0,0 +1,595 @@
|
||||
/**
|
||||
* Cookie Banner Generator
|
||||
*
|
||||
* Generiert Cookie-Banner Konfigurationen und Embed-Code aus dem Datenpunktkatalog.
|
||||
* Die Cookie-Kategorien werden automatisch aus den Datenpunkten abgeleitet.
|
||||
*/
|
||||
|
||||
import {
|
||||
DataPoint,
|
||||
CookieCategory,
|
||||
CookieBannerCategory,
|
||||
CookieBannerConfig,
|
||||
CookieBannerStyling,
|
||||
CookieBannerTexts,
|
||||
CookieBannerEmbedCode,
|
||||
CookieInfo,
|
||||
LocalizedText,
|
||||
SupportedLanguage,
|
||||
} from '../types'
|
||||
import { DEFAULT_COOKIE_CATEGORIES } from '../catalog/loader'
|
||||
|
||||
// =============================================================================
|
||||
// HELPER FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Holt den lokalisierten Text
|
||||
*/
|
||||
function t(text: LocalizedText, language: SupportedLanguage): string {
|
||||
return text[language]
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// COOKIE BANNER CONFIGURATION
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Standard Cookie Banner Texte
|
||||
*/
|
||||
export const DEFAULT_COOKIE_BANNER_TEXTS: CookieBannerTexts = {
|
||||
title: {
|
||||
de: 'Cookie-Einstellungen',
|
||||
en: 'Cookie Settings',
|
||||
},
|
||||
description: {
|
||||
de: 'Wir verwenden Cookies, um Ihnen die bestmoegliche Nutzung unserer Website zu ermoeglichen. Einige Cookies sind technisch notwendig, waehrend andere uns helfen, Ihre Nutzererfahrung zu verbessern.',
|
||||
en: 'We use cookies to provide you with the best possible experience on our website. Some cookies are technically necessary, while others help us improve your user experience.',
|
||||
},
|
||||
acceptAll: {
|
||||
de: 'Alle akzeptieren',
|
||||
en: 'Accept All',
|
||||
},
|
||||
rejectAll: {
|
||||
de: 'Nur notwendige',
|
||||
en: 'Essential Only',
|
||||
},
|
||||
customize: {
|
||||
de: 'Einstellungen',
|
||||
en: 'Customize',
|
||||
},
|
||||
save: {
|
||||
de: 'Auswahl speichern',
|
||||
en: 'Save Selection',
|
||||
},
|
||||
privacyPolicyLink: {
|
||||
de: 'Mehr in unserer Datenschutzerklaerung',
|
||||
en: 'More in our Privacy Policy',
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard Styling fuer Cookie Banner
|
||||
*/
|
||||
export const DEFAULT_COOKIE_BANNER_STYLING: CookieBannerStyling = {
|
||||
position: 'BOTTOM',
|
||||
theme: 'LIGHT',
|
||||
primaryColor: '#6366f1', // Indigo
|
||||
secondaryColor: '#f1f5f9', // Slate-100
|
||||
textColor: '#1e293b', // Slate-800
|
||||
backgroundColor: '#ffffff',
|
||||
borderRadius: 12,
|
||||
maxWidth: 480,
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// GENERATOR FUNCTIONS
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Generiert Cookie-Banner Kategorien aus Datenpunkten
|
||||
*/
|
||||
export function generateCookieCategories(
|
||||
dataPoints: DataPoint[]
|
||||
): CookieBannerCategory[] {
|
||||
// Filtere nur Datenpunkte mit Cookie-Kategorie
|
||||
const cookieDataPoints = dataPoints.filter((dp) => dp.cookieCategory !== null)
|
||||
|
||||
// Erstelle die Kategorien basierend auf den Defaults
|
||||
return DEFAULT_COOKIE_CATEGORIES.map((defaultCat) => {
|
||||
// Filtere die Datenpunkte fuer diese Kategorie
|
||||
const categoryDataPoints = cookieDataPoints.filter(
|
||||
(dp) => dp.cookieCategory === defaultCat.id
|
||||
)
|
||||
|
||||
// Erstelle Cookie-Infos aus den Datenpunkten
|
||||
const cookies: CookieInfo[] = categoryDataPoints.map((dp) => ({
|
||||
name: dp.code,
|
||||
provider: 'First Party',
|
||||
purpose: dp.purpose,
|
||||
expiry: getExpiryFromRetention(dp.retentionPeriod),
|
||||
type: 'FIRST_PARTY',
|
||||
}))
|
||||
|
||||
return {
|
||||
...defaultCat,
|
||||
dataPointIds: categoryDataPoints.map((dp) => dp.id),
|
||||
cookies,
|
||||
}
|
||||
}).filter((cat) => cat.dataPointIds.length > 0 || cat.isRequired)
|
||||
}
|
||||
|
||||
/**
|
||||
* Konvertiert Retention Period zu Cookie-Expiry String
|
||||
*/
|
||||
function getExpiryFromRetention(retention: string): string {
|
||||
const mapping: Record<string, string> = {
|
||||
'24_HOURS': '24 Stunden / 24 hours',
|
||||
'30_DAYS': '30 Tage / 30 days',
|
||||
'90_DAYS': '90 Tage / 90 days',
|
||||
'12_MONTHS': '1 Jahr / 1 year',
|
||||
'24_MONTHS': '2 Jahre / 2 years',
|
||||
'36_MONTHS': '3 Jahre / 3 years',
|
||||
'UNTIL_REVOCATION': 'Bis Widerruf / Until revocation',
|
||||
'UNTIL_PURPOSE_FULFILLED': 'Session',
|
||||
'UNTIL_ACCOUNT_DELETION': 'Bis Kontoschliessung / Until account deletion',
|
||||
}
|
||||
return mapping[retention] || 'Session'
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert die vollstaendige Cookie Banner Konfiguration
|
||||
*/
|
||||
export function generateCookieBannerConfig(
|
||||
tenantId: string,
|
||||
dataPoints: DataPoint[],
|
||||
customTexts?: Partial<CookieBannerTexts>,
|
||||
customStyling?: Partial<CookieBannerStyling>
|
||||
): CookieBannerConfig {
|
||||
const categories = generateCookieCategories(dataPoints)
|
||||
|
||||
return {
|
||||
id: `cookie-banner-${tenantId}`,
|
||||
tenantId,
|
||||
categories,
|
||||
styling: {
|
||||
...DEFAULT_COOKIE_BANNER_STYLING,
|
||||
...customStyling,
|
||||
},
|
||||
texts: {
|
||||
...DEFAULT_COOKIE_BANNER_TEXTS,
|
||||
...customTexts,
|
||||
},
|
||||
updatedAt: new Date(),
|
||||
}
|
||||
}
|
||||
|
||||
// =============================================================================
|
||||
// EMBED CODE GENERATION
|
||||
// =============================================================================
|
||||
|
||||
/**
|
||||
* Generiert den Embed-Code fuer den Cookie Banner
|
||||
*/
|
||||
export function generateEmbedCode(
|
||||
config: CookieBannerConfig,
|
||||
privacyPolicyUrl: string = '/datenschutz'
|
||||
): CookieBannerEmbedCode {
|
||||
const css = generateCSS(config.styling)
|
||||
const html = generateHTML(config, privacyPolicyUrl)
|
||||
const js = generateJS(config)
|
||||
|
||||
const scriptTag = `<script src="/cookie-banner.js" data-tenant="${config.tenantId}"></script>`
|
||||
|
||||
return {
|
||||
html,
|
||||
css,
|
||||
js,
|
||||
scriptTag,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert das CSS fuer den Cookie Banner
|
||||
*/
|
||||
function generateCSS(styling: CookieBannerStyling): string {
|
||||
const positionStyles: Record<string, string> = {
|
||||
BOTTOM: 'bottom: 0; left: 0; right: 0;',
|
||||
TOP: 'top: 0; left: 0; right: 0;',
|
||||
CENTER: 'top: 50%; left: 50%; transform: translate(-50%, -50%);',
|
||||
}
|
||||
|
||||
const isDark = styling.theme === 'DARK'
|
||||
const bgColor = isDark ? '#1e293b' : styling.backgroundColor || '#ffffff'
|
||||
const textColor = isDark ? '#f1f5f9' : styling.textColor || '#1e293b'
|
||||
const borderColor = isDark ? 'rgba(255,255,255,0.1)' : 'rgba(0,0,0,0.1)'
|
||||
|
||||
return `
|
||||
/* Cookie Banner Styles */
|
||||
.cookie-banner-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 9998;
|
||||
opacity: 0;
|
||||
visibility: hidden;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cookie-banner-overlay.active {
|
||||
opacity: 1;
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.cookie-banner {
|
||||
position: fixed;
|
||||
${positionStyles[styling.position]}
|
||||
z-index: 9999;
|
||||
background: ${bgColor};
|
||||
color: ${textColor};
|
||||
border-radius: ${styling.borderRadius || 12}px;
|
||||
box-shadow: 0 -4px 20px rgba(0, 0, 0, 0.1);
|
||||
padding: 24px;
|
||||
max-width: ${styling.maxWidth}px;
|
||||
margin: ${styling.position === 'CENTER' ? '0' : '16px'};
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
transform: translateY(100%);
|
||||
opacity: 0;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.cookie-banner.active {
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.cookie-banner-title {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.cookie-banner-description {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
margin-bottom: 16px;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.cookie-banner-buttons {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.cookie-banner-btn {
|
||||
flex: 1;
|
||||
min-width: 120px;
|
||||
padding: 12px 20px;
|
||||
border-radius: ${(styling.borderRadius || 12) / 2}px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
border: none;
|
||||
}
|
||||
|
||||
.cookie-banner-btn-primary {
|
||||
background: ${styling.primaryColor};
|
||||
color: white;
|
||||
}
|
||||
|
||||
.cookie-banner-btn-primary:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.cookie-banner-btn-secondary {
|
||||
background: ${styling.secondaryColor || borderColor};
|
||||
color: ${textColor};
|
||||
}
|
||||
|
||||
.cookie-banner-btn-secondary:hover {
|
||||
filter: brightness(0.95);
|
||||
}
|
||||
|
||||
.cookie-banner-link {
|
||||
display: block;
|
||||
margin-top: 16px;
|
||||
font-size: 12px;
|
||||
color: ${styling.primaryColor};
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.cookie-banner-link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Category Details */
|
||||
.cookie-banner-details {
|
||||
margin-top: 16px;
|
||||
border-top: 1px solid ${borderColor};
|
||||
padding-top: 16px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.cookie-banner-details.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.cookie-banner-category {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 12px 0;
|
||||
border-bottom: 1px solid ${borderColor};
|
||||
}
|
||||
|
||||
.cookie-banner-category:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.cookie-banner-category-info {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.cookie-banner-category-name {
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.cookie-banner-category-desc {
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.cookie-banner-toggle {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 28px;
|
||||
background: ${borderColor};
|
||||
border-radius: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.cookie-banner-toggle.active {
|
||||
background: ${styling.primaryColor};
|
||||
}
|
||||
|
||||
.cookie-banner-toggle.disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.cookie-banner-toggle::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 4px;
|
||||
left: 4px;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.cookie-banner-toggle.active::after {
|
||||
left: 24px;
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.cookie-banner {
|
||||
margin: 0;
|
||||
border-radius: ${styling.position === 'CENTER' ? (styling.borderRadius || 12) : 0}px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.cookie-banner-buttons {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.cookie-banner-btn {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
`.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert das HTML fuer den Cookie Banner
|
||||
*/
|
||||
function generateHTML(config: CookieBannerConfig, privacyPolicyUrl: string): string {
|
||||
const categoriesHTML = config.categories
|
||||
.map((cat) => {
|
||||
const isRequired = cat.isRequired
|
||||
return `
|
||||
<div class="cookie-banner-category" data-category="${cat.id}">
|
||||
<div class="cookie-banner-category-info">
|
||||
<div class="cookie-banner-category-name">${cat.name.de}</div>
|
||||
<div class="cookie-banner-category-desc">${cat.description.de}</div>
|
||||
</div>
|
||||
<div class="cookie-banner-toggle ${cat.defaultEnabled ? 'active' : ''} ${isRequired ? 'disabled' : ''}"
|
||||
data-category="${cat.id}"
|
||||
data-required="${isRequired}"></div>
|
||||
</div>
|
||||
`
|
||||
})
|
||||
.join('')
|
||||
|
||||
return `
|
||||
<div class="cookie-banner-overlay" id="cookieBannerOverlay"></div>
|
||||
<div class="cookie-banner" id="cookieBanner" role="dialog" aria-labelledby="cookieBannerTitle" aria-modal="true">
|
||||
<div class="cookie-banner-title" id="cookieBannerTitle">${config.texts.title.de}</div>
|
||||
<div class="cookie-banner-description">${config.texts.description.de}</div>
|
||||
|
||||
<div class="cookie-banner-buttons">
|
||||
<button class="cookie-banner-btn cookie-banner-btn-secondary" id="cookieBannerReject">
|
||||
${config.texts.rejectAll.de}
|
||||
</button>
|
||||
<button class="cookie-banner-btn cookie-banner-btn-secondary" id="cookieBannerCustomize">
|
||||
${config.texts.customize.de}
|
||||
</button>
|
||||
<button class="cookie-banner-btn cookie-banner-btn-primary" id="cookieBannerAccept">
|
||||
${config.texts.acceptAll.de}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="cookie-banner-details" id="cookieBannerDetails">
|
||||
${categoriesHTML}
|
||||
<div class="cookie-banner-buttons" style="margin-top: 16px;">
|
||||
<button class="cookie-banner-btn cookie-banner-btn-primary" id="cookieBannerSave">
|
||||
${config.texts.save.de}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a href="${privacyPolicyUrl}" class="cookie-banner-link" target="_blank">
|
||||
${config.texts.privacyPolicyLink.de}
|
||||
</a>
|
||||
</div>
|
||||
`.trim()
|
||||
}
|
||||
|
||||
/**
|
||||
* Generiert das JavaScript fuer den Cookie Banner
|
||||
*/
|
||||
function generateJS(config: CookieBannerConfig): string {
|
||||
const categoryIds = config.categories.map((c) => c.id)
|
||||
const requiredCategories = config.categories.filter((c) => c.isRequired).map((c) => c.id)
|
||||
|
||||
return `
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
const COOKIE_NAME = 'cookie_consent';
|
||||
const COOKIE_EXPIRY_DAYS = 365;
|
||||
const CATEGORIES = ${JSON.stringify(categoryIds)};
|
||||
const REQUIRED_CATEGORIES = ${JSON.stringify(requiredCategories)};
|
||||
|
||||
// Get consent from cookie
|
||||
function getConsent() {
|
||||
const cookie = document.cookie.split('; ').find(row => row.startsWith(COOKIE_NAME + '='));
|
||||
if (!cookie) return null;
|
||||
try {
|
||||
return JSON.parse(decodeURIComponent(cookie.split('=')[1]));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Save consent to cookie
|
||||
function saveConsent(consent) {
|
||||
const date = new Date();
|
||||
date.setTime(date.getTime() + (COOKIE_EXPIRY_DAYS * 24 * 60 * 60 * 1000));
|
||||
document.cookie = COOKIE_NAME + '=' + encodeURIComponent(JSON.stringify(consent)) +
|
||||
';expires=' + date.toUTCString() +
|
||||
';path=/;SameSite=Lax';
|
||||
|
||||
// Dispatch event
|
||||
window.dispatchEvent(new CustomEvent('cookieConsentUpdated', { detail: consent }));
|
||||
}
|
||||
|
||||
// Check if category is consented
|
||||
function hasConsent(category) {
|
||||
const consent = getConsent();
|
||||
if (!consent) return REQUIRED_CATEGORIES.includes(category);
|
||||
return consent[category] === true;
|
||||
}
|
||||
|
||||
// Initialize banner
|
||||
function initBanner() {
|
||||
const banner = document.getElementById('cookieBanner');
|
||||
const overlay = document.getElementById('cookieBannerOverlay');
|
||||
const details = document.getElementById('cookieBannerDetails');
|
||||
|
||||
if (!banner) return;
|
||||
|
||||
const consent = getConsent();
|
||||
if (consent) {
|
||||
// User has already consented
|
||||
return;
|
||||
}
|
||||
|
||||
// Show banner
|
||||
setTimeout(() => {
|
||||
banner.classList.add('active');
|
||||
overlay.classList.add('active');
|
||||
}, 500);
|
||||
|
||||
// Accept all
|
||||
document.getElementById('cookieBannerAccept')?.addEventListener('click', () => {
|
||||
const consent = {};
|
||||
CATEGORIES.forEach(cat => consent[cat] = true);
|
||||
saveConsent(consent);
|
||||
closeBanner();
|
||||
});
|
||||
|
||||
// Reject all (only essential)
|
||||
document.getElementById('cookieBannerReject')?.addEventListener('click', () => {
|
||||
const consent = {};
|
||||
CATEGORIES.forEach(cat => consent[cat] = REQUIRED_CATEGORIES.includes(cat));
|
||||
saveConsent(consent);
|
||||
closeBanner();
|
||||
});
|
||||
|
||||
// Customize
|
||||
document.getElementById('cookieBannerCustomize')?.addEventListener('click', () => {
|
||||
details.classList.toggle('active');
|
||||
});
|
||||
|
||||
// Save selection
|
||||
document.getElementById('cookieBannerSave')?.addEventListener('click', () => {
|
||||
const consent = {};
|
||||
CATEGORIES.forEach(cat => {
|
||||
const toggle = document.querySelector('.cookie-banner-toggle[data-category="' + cat + '"]');
|
||||
consent[cat] = toggle?.classList.contains('active') || REQUIRED_CATEGORIES.includes(cat);
|
||||
});
|
||||
saveConsent(consent);
|
||||
closeBanner();
|
||||
});
|
||||
|
||||
// Toggle handlers
|
||||
document.querySelectorAll('.cookie-banner-toggle').forEach(toggle => {
|
||||
if (toggle.dataset.required === 'true') return;
|
||||
toggle.addEventListener('click', () => {
|
||||
toggle.classList.toggle('active');
|
||||
});
|
||||
});
|
||||
|
||||
// Close on overlay click
|
||||
overlay?.addEventListener('click', () => {
|
||||
// Don't close - user must make a choice
|
||||
});
|
||||
}
|
||||
|
||||
function closeBanner() {
|
||||
const banner = document.getElementById('cookieBanner');
|
||||
const overlay = document.getElementById('cookieBannerOverlay');
|
||||
banner?.classList.remove('active');
|
||||
overlay?.classList.remove('active');
|
||||
}
|
||||
|
||||
// Expose API
|
||||
window.CookieConsent = {
|
||||
getConsent,
|
||||
saveConsent,
|
||||
hasConsent,
|
||||
show: () => {
|
||||
document.getElementById('cookieBanner')?.classList.add('active');
|
||||
document.getElementById('cookieBannerOverlay')?.classList.add('active');
|
||||
},
|
||||
hide: closeBanner
|
||||
};
|
||||
|
||||
// Initialize on DOM ready
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initBanner);
|
||||
} else {
|
||||
initBanner();
|
||||
}
|
||||
})();
|
||||
`.trim()
|
||||
}
|
||||
|
||||
// Note: All exports are defined inline with 'export const' and 'export function'
|
||||
Reference in New Issue
Block a user