refactor(compliance-sdk): split client/provider/embed/state under 500 LOC

Phase 4 continuation. All touched files now under the file-size cap, and
drive-by fixes unblock the types/core/react/vanilla builds which were broken
at baseline.

Splits
- packages/types/src/state 505 -> 31 LOC barrel + state-flow/-assessment/-core
- packages/core/src/client 521 -> 395 LOC + client-http 187 LOC (HTTP transport)
- packages/react/src/provider 539 -> 460 LOC + provider-context 101 LOC
- packages/vanilla/src/embed 611 -> 290 LOC + embed-banner 321 + embed-translations 78

Drive-by fixes (pre-existing typecheck/build failures)
- types/rag.ts: rename colliding LegalDocument export to RagLegalDocument
  (the `export *` chain in index.ts was ambiguous; two consumers updated
  - core/modules/rag.ts drops unused import, vue/composables/useRAG.ts
  switches to the renamed symbol).
- core/modules/rag.ts: wrap client searchRAG response to add the missing
  `query` field so the declared SearchResponse return type is satisfied.
- react/provider.tsx: re-export useCompliance so ComplianceDashboard /
  ConsentBanner / DSRPortal legacy `from '../provider'` imports resolve.
- vanilla/embed.ts + web-components/base.ts: default tenantId to ''
  so ComplianceClient construction typechecks.
- vanilla/web-components/consent-banner.ts: tighten categories literal to
  `as const` so t.categories indexing narrows correctly.

Verification: packages/types + core + react + vanilla all `pnpm build`
clean with DTS emission. consent-sdk unaffected (still green).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Sharang Parnerkar
2026-04-11 22:39:47 +02:00
parent 4ed39d2616
commit 5cb91e88d2
16 changed files with 1380 additions and 1162 deletions

View File

@@ -0,0 +1,321 @@
/**
* Cookie banner DOM rendering for the vanilla embed SDK.
*
* Phase 4: extracted from embed.ts. The banner module is pure rendering —
* caller passes in a BannerContext describing current consent state plus
* callbacks for user actions.
*/
import type {
ConsentPurpose,
CookieBannerPosition,
CookieBannerTheme,
} from '@breakpilot/compliance-sdk-types'
import {
TRANSLATIONS,
createElement,
type BannerLanguage,
} from './embed-translations'
export interface BannerConfig {
position?: CookieBannerPosition
theme?: CookieBannerTheme
language?: BannerLanguage
privacyPolicyUrl?: string
imprintUrl?: string
texts?: {
title?: string
description?: string
acceptAll?: string
rejectAll?: string
settings?: string
save?: string
}
customColors?: {
background?: string
text?: string
primary?: string
secondary?: string
}
}
export interface BannerCallbacks {
onAcceptAll: () => void
onRejectAll: () => void
onShowSettings: () => void
onToggleCategory: (category: ConsentPurpose, granted: boolean) => void
onSaveSettings: () => void
onBack: () => void
}
export interface BannerContext {
config: BannerConfig
consents: Record<ConsentPurpose, boolean>
callbacks: BannerCallbacks
}
interface ResolvedColors {
bgColor: string
textColor: string
primaryColor: string
secondaryColor: string
isDark: boolean
}
function resolveColors(config: BannerConfig): ResolvedColors {
const theme = config.theme || 'LIGHT'
const isDark = theme === 'DARK'
return {
isDark,
bgColor: config.customColors?.background || (isDark ? '#1a1a1a' : '#ffffff'),
textColor: config.customColors?.text || (isDark ? '#ffffff' : '#1a1a1a'),
primaryColor: config.customColors?.primary || (isDark ? '#ffffff' : '#1a1a1a'),
secondaryColor: config.customColors?.secondary || (isDark ? '#ffffff' : '#1a1a1a'),
}
}
export function createBannerElement(ctx: BannerContext): HTMLElement {
const { config, callbacks } = ctx
const lang: BannerLanguage = config.language || 'de'
const t = TRANSLATIONS[lang]
const position = config.position || 'BOTTOM'
const colors = resolveColors(config)
const { bgColor, textColor, primaryColor, secondaryColor, isDark } = colors
const container = createElement(
'div',
{
position: 'fixed',
zIndex: '99999',
left: position === 'CENTER' ? '50%' : '0',
right: position === 'CENTER' ? 'auto' : '0',
top: position === 'TOP' ? '0' : position === 'CENTER' ? '50%' : 'auto',
bottom: position === 'BOTTOM' ? '0' : 'auto',
transform: position === 'CENTER' ? 'translate(-50%, -50%)' : 'none',
maxWidth: position === 'CENTER' ? '500px' : 'none',
backgroundColor: bgColor,
color: textColor,
padding: '20px',
boxShadow: '0 -2px 10px rgba(0, 0, 0, 0.1)',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSize: '14px',
lineHeight: '1.5',
},
{ id: 'breakpilot-consent-banner', role: 'dialog', 'aria-label': t.title }
)
const title = createElement('h3', {
margin: '0 0 10px',
fontSize: '18px',
fontWeight: '600',
})
title.textContent = config.texts?.title || t.title
const description = createElement('p', {
margin: '0 0 15px',
opacity: '0.8',
})
description.textContent = config.texts?.description || t.description
const buttonsContainer = createElement('div', {
display: 'flex',
flexWrap: 'wrap',
gap: '10px',
alignItems: 'center',
})
const acceptBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
fontWeight: '500',
backgroundColor: primaryColor,
color: isDark ? '#1a1a1a' : '#ffffff',
},
{ type: 'button' }
)
acceptBtn.textContent = config.texts?.acceptAll || t.acceptAll
acceptBtn.onclick = () => callbacks.onAcceptAll()
const rejectBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: 'transparent',
border: `1px solid ${secondaryColor}`,
color: textColor,
cursor: 'pointer',
fontWeight: '500',
},
{ type: 'button' }
)
rejectBtn.textContent = config.texts?.rejectAll || t.rejectAll
rejectBtn.onclick = () => callbacks.onRejectAll()
const settingsBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: 'transparent',
border: `1px solid ${secondaryColor}`,
color: textColor,
cursor: 'pointer',
fontWeight: '500',
},
{ type: 'button' }
)
settingsBtn.textContent = config.texts?.settings || t.settings
settingsBtn.onclick = () => callbacks.onShowSettings()
const linksContainer = createElement('div', {
marginLeft: 'auto',
fontSize: '12px',
})
const privacyLink = createElement('a', {
marginRight: '15px',
color: textColor,
textDecoration: 'none',
})
privacyLink.href = config.privacyPolicyUrl || '/privacy'
privacyLink.textContent = t.privacy
const imprintLink = createElement('a', {
color: textColor,
textDecoration: 'none',
})
imprintLink.href = config.imprintUrl || '/imprint'
imprintLink.textContent = t.imprint
linksContainer.appendChild(privacyLink)
linksContainer.appendChild(imprintLink)
buttonsContainer.appendChild(acceptBtn)
buttonsContainer.appendChild(rejectBtn)
buttonsContainer.appendChild(settingsBtn)
buttonsContainer.appendChild(linksContainer)
container.appendChild(title)
container.appendChild(description)
container.appendChild(buttonsContainer)
return container
}
export function renderSettingsPanel(element: HTMLElement, ctx: BannerContext): void {
const { config, consents, callbacks } = ctx
const lang: BannerLanguage = config.language || 'de'
const t = TRANSLATIONS[lang]
const colors = resolveColors(config)
const { textColor, primaryColor, secondaryColor, isDark } = colors
element.innerHTML = ''
const title = createElement('h3', {
margin: '0 0 15px',
fontSize: '18px',
fontWeight: '600',
})
title.textContent = t.settings
element.appendChild(title)
const categories: ConsentPurpose[] = [
'ESSENTIAL',
'FUNCTIONAL',
'ANALYTICS',
'MARKETING',
]
categories.forEach(category => {
const catInfo = t.categories[category]
const row = createElement('div', {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '10px 0',
borderBottom: `1px solid ${isDark ? '#333' : '#eee'}`,
})
const labelContainer = createElement('div', {})
const labelName = createElement('div', { fontWeight: '500' })
labelName.textContent = catInfo.name
const labelDesc = createElement('div', { fontSize: '12px', opacity: '0.7' })
labelDesc.textContent = catInfo.description
labelContainer.appendChild(labelName)
labelContainer.appendChild(labelDesc)
const checkbox = createElement(
'input',
{
width: '20px',
height: '20px',
cursor: category === 'ESSENTIAL' ? 'not-allowed' : 'pointer',
},
{
type: 'checkbox',
'data-category': category,
}
)
checkbox.checked = consents[category]
checkbox.disabled = category === 'ESSENTIAL'
checkbox.onchange = () => {
if (category !== 'ESSENTIAL') {
callbacks.onToggleCategory(category, checkbox.checked)
}
}
row.appendChild(labelContainer)
row.appendChild(checkbox)
element.appendChild(row)
})
const buttonsContainer = createElement('div', {
display: 'flex',
flexWrap: 'wrap',
gap: '10px',
marginTop: '15px',
})
const saveBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
fontWeight: '500',
backgroundColor: primaryColor,
color: isDark ? '#1a1a1a' : '#ffffff',
},
{ type: 'button' }
)
saveBtn.textContent = config.texts?.save || t.save
saveBtn.onclick = () => callbacks.onSaveSettings()
const backBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: 'transparent',
border: `1px solid ${secondaryColor}`,
color: textColor,
cursor: 'pointer',
fontWeight: '500',
},
{ type: 'button' }
)
backBtn.textContent = 'Zurück'
backBtn.onclick = () => callbacks.onBack()
buttonsContainer.appendChild(saveBtn)
buttonsContainer.appendChild(backBtn)
element.appendChild(buttonsContainer)
}

View File

@@ -0,0 +1,78 @@
/**
* i18n string catalog for the vanilla embed SDK cookie banner.
*
* Phase 4: extracted from embed.ts.
*/
import type { ConsentPurpose } from '@breakpilot/compliance-sdk-types'
export type BannerLanguage = 'de' | 'en'
export interface CategoryCopy {
name: string
description: string
}
export interface BannerCopy {
title: string
description: string
acceptAll: string
rejectAll: string
settings: string
save: string
privacy: string
imprint: string
categories: Record<ConsentPurpose, CategoryCopy>
}
export const TRANSLATIONS: Record<BannerLanguage, BannerCopy> = {
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: 'Speichern',
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' },
PERSONALIZATION: { name: 'Personalisierung', description: 'Angepasste Inhalte' },
THIRD_PARTY: { name: 'Drittanbieter', description: 'Externe Dienste' },
},
},
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',
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' },
PERSONALIZATION: { name: 'Personalization', description: 'Customized content' },
THIRD_PARTY: { name: 'Third Party', description: 'External services' },
},
},
}
export function createElement<K extends keyof HTMLElementTagNameMap>(
tag: K,
styles: Partial<CSSStyleDeclaration> = {},
attributes: Record<string, string> = {}
): HTMLElementTagNameMap[K] {
const el = document.createElement(tag)
Object.assign(el.style, styles)
Object.entries(attributes).forEach(([key, value]) => el.setAttribute(key, value))
return el
}

View File

@@ -16,10 +16,14 @@
import { ComplianceClient } from '@breakpilot/compliance-sdk-core'
import type {
ConsentPurpose,
CookieBannerPosition,
CookieBannerTheme,
DSRRequestType,
} from '@breakpilot/compliance-sdk-types'
import {
createBannerElement,
renderSettingsPanel,
type BannerConfig,
type BannerContext,
} from './embed-banner'
// ============================================================================
// Types
@@ -37,27 +41,7 @@ export interface BreakPilotSDKConfig {
debug?: boolean
}
export interface BannerConfig {
position?: CookieBannerPosition
theme?: CookieBannerTheme
language?: 'de' | 'en'
privacyPolicyUrl?: string
imprintUrl?: string
texts?: {
title?: string
description?: string
acceptAll?: string
rejectAll?: string
settings?: string
save?: string
}
customColors?: {
background?: string
text?: string
primary?: string
secondary?: string
}
}
export type { BannerConfig } from './embed-banner'
// ============================================================================
// Internal State
@@ -76,51 +60,6 @@ let _consents: Record<ConsentPurpose, boolean> = {
let _bannerElement: HTMLElement | null = null
let _isInitialized = false
// ============================================================================
// Translations
// ============================================================================
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: 'Speichern',
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' },
PERSONALIZATION: { name: 'Personalisierung', description: 'Angepasste Inhalte' },
THIRD_PARTY: { name: 'Drittanbieter', description: 'Externe Dienste' },
},
},
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',
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' },
PERSONALIZATION: { name: 'Personalization', description: 'Customized content' },
THIRD_PARTY: { name: 'Third Party', description: 'External services' },
},
},
}
// ============================================================================
// Utility Functions
// ============================================================================
@@ -149,301 +88,40 @@ function storeConsents(consents: Record<ConsentPurpose, boolean>): void {
}
}
function createElement<K extends keyof HTMLElementTagNameMap>(
tag: K,
styles: Partial<CSSStyleDeclaration> = {},
attributes: Record<string, string> = {}
): HTMLElementTagNameMap[K] {
const el = document.createElement(tag)
Object.assign(el.style, styles)
Object.entries(attributes).forEach(([key, value]) => el.setAttribute(key, value))
return el
}
// ============================================================================
// Banner Implementation
// Banner Orchestration
// ============================================================================
function createBanner(): HTMLElement {
const config = _config!
const bannerConfig = config.bannerConfig || {}
const lang = bannerConfig.language || 'de'
const t = TRANSLATIONS[lang]
const position = bannerConfig.position || 'BOTTOM'
const theme = bannerConfig.theme || 'LIGHT'
const isDark = theme === 'DARK'
const bgColor = bannerConfig.customColors?.background || (isDark ? '#1a1a1a' : '#ffffff')
const textColor = bannerConfig.customColors?.text || (isDark ? '#ffffff' : '#1a1a1a')
const primaryColor = bannerConfig.customColors?.primary || (isDark ? '#ffffff' : '#1a1a1a')
const secondaryColor = bannerConfig.customColors?.secondary || (isDark ? '#ffffff' : '#1a1a1a')
// Container
const container = createElement(
'div',
{
position: 'fixed',
zIndex: '99999',
left: position === 'CENTER' ? '50%' : '0',
right: position === 'CENTER' ? 'auto' : '0',
top: position === 'TOP' ? '0' : position === 'CENTER' ? '50%' : 'auto',
bottom: position === 'BOTTOM' ? '0' : 'auto',
transform: position === 'CENTER' ? 'translate(-50%, -50%)' : 'none',
maxWidth: position === 'CENTER' ? '500px' : 'none',
backgroundColor: bgColor,
color: textColor,
padding: '20px',
boxShadow: '0 -2px 10px rgba(0, 0, 0, 0.1)',
fontFamily: 'system-ui, -apple-system, sans-serif',
fontSize: '14px',
lineHeight: '1.5',
},
{ id: 'breakpilot-consent-banner', role: 'dialog', 'aria-label': t.title }
)
// Title
const title = createElement('h3', {
margin: '0 0 10px',
fontSize: '18px',
fontWeight: '600',
})
title.textContent = bannerConfig.texts?.title || t.title
// Description
const description = createElement('p', {
margin: '0 0 15px',
opacity: '0.8',
})
description.textContent = bannerConfig.texts?.description || t.description
// Buttons container
const buttonsContainer = createElement('div', {
display: 'flex',
flexWrap: 'wrap',
gap: '10px',
alignItems: 'center',
})
// Accept All button
const acceptBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
fontWeight: '500',
backgroundColor: primaryColor,
color: isDark ? '#1a1a1a' : '#ffffff',
},
{ type: 'button' }
)
acceptBtn.textContent = bannerConfig.texts?.acceptAll || t.acceptAll
acceptBtn.onclick = () => handleAcceptAll()
// Reject button
const rejectBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: 'transparent',
border: `1px solid ${secondaryColor}`,
color: textColor,
cursor: 'pointer',
fontWeight: '500',
},
{ type: 'button' }
)
rejectBtn.textContent = bannerConfig.texts?.rejectAll || t.rejectAll
rejectBtn.onclick = () => handleRejectAll()
// Settings button
const settingsBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: 'transparent',
border: `1px solid ${secondaryColor}`,
color: textColor,
cursor: 'pointer',
fontWeight: '500',
},
{ type: 'button' }
)
settingsBtn.textContent = bannerConfig.texts?.settings || t.settings
settingsBtn.onclick = () => showSettingsPanel()
// Links container
const linksContainer = createElement('div', {
marginLeft: 'auto',
fontSize: '12px',
})
const privacyLink = createElement('a', {
marginRight: '15px',
color: textColor,
textDecoration: 'none',
})
privacyLink.href = bannerConfig.privacyPolicyUrl || '/privacy'
privacyLink.textContent = t.privacy
const imprintLink = createElement('a', {
color: textColor,
textDecoration: 'none',
})
imprintLink.href = bannerConfig.imprintUrl || '/imprint'
imprintLink.textContent = t.imprint
// Assemble
linksContainer.appendChild(privacyLink)
linksContainer.appendChild(imprintLink)
buttonsContainer.appendChild(acceptBtn)
buttonsContainer.appendChild(rejectBtn)
buttonsContainer.appendChild(settingsBtn)
buttonsContainer.appendChild(linksContainer)
container.appendChild(title)
container.appendChild(description)
container.appendChild(buttonsContainer)
return container
}
function showSettingsPanel(): void {
if (!_bannerElement) return
const config = _config!
const bannerConfig = config.bannerConfig || {}
const lang = bannerConfig.language || 'de'
const t = TRANSLATIONS[lang]
const theme = bannerConfig.theme || 'LIGHT'
const isDark = theme === 'DARK'
const textColor = bannerConfig.customColors?.text || (isDark ? '#ffffff' : '#1a1a1a')
// Clear banner content
_bannerElement.innerHTML = ''
// Title
const title = createElement('h3', {
margin: '0 0 15px',
fontSize: '18px',
fontWeight: '600',
})
title.textContent = t.settings
_bannerElement.appendChild(title)
// Categories
const categories: ConsentPurpose[] = [
'ESSENTIAL',
'FUNCTIONAL',
'ANALYTICS',
'MARKETING',
]
categories.forEach(category => {
const catInfo = t.categories[category]
const row = createElement('div', {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
padding: '10px 0',
borderBottom: `1px solid ${isDark ? '#333' : '#eee'}`,
})
const labelContainer = createElement('div', {})
const labelName = createElement('div', { fontWeight: '500' })
labelName.textContent = catInfo.name
const labelDesc = createElement('div', { fontSize: '12px', opacity: '0.7' })
labelDesc.textContent = catInfo.description
labelContainer.appendChild(labelName)
labelContainer.appendChild(labelDesc)
const checkbox = createElement(
'input',
{
width: '20px',
height: '20px',
cursor: category === 'ESSENTIAL' ? 'not-allowed' : 'pointer',
function buildBannerContext(): BannerContext {
return {
config: _config?.bannerConfig || {},
consents: _consents,
callbacks: {
onAcceptAll: () => handleAcceptAll(),
onRejectAll: () => handleRejectAll(),
onShowSettings: () => showSettingsPanel(),
onToggleCategory: (category, granted) => {
_consents[category] = granted
},
{
type: 'checkbox',
'data-category': category,
}
)
checkbox.checked = _consents[category]
checkbox.disabled = category === 'ESSENTIAL'
checkbox.onchange = () => {
if (category !== 'ESSENTIAL') {
_consents[category] = checkbox.checked
}
}
row.appendChild(labelContainer)
row.appendChild(checkbox)
_bannerElement!.appendChild(row)
})
// Buttons
const buttonsContainer = createElement('div', {
display: 'flex',
flexWrap: 'wrap',
gap: '10px',
marginTop: '15px',
})
const primaryColor = bannerConfig.customColors?.primary || (isDark ? '#ffffff' : '#1a1a1a')
const secondaryColor = bannerConfig.customColors?.secondary || (isDark ? '#ffffff' : '#1a1a1a')
const saveBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
border: 'none',
cursor: 'pointer',
fontWeight: '500',
backgroundColor: primaryColor,
color: isDark ? '#1a1a1a' : '#ffffff',
onSaveSettings: () => handleSaveSettings(),
onBack: () => showMainBanner(),
},
{ type: 'button' }
)
saveBtn.textContent = bannerConfig.texts?.save || t.save
saveBtn.onclick = () => handleSaveSettings()
const backBtn = createElement(
'button',
{
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: 'transparent',
border: `1px solid ${secondaryColor}`,
color: textColor,
cursor: 'pointer',
fontWeight: '500',
},
{ type: 'button' }
)
backBtn.textContent = 'Zurück'
backBtn.onclick = () => showMainBanner()
buttonsContainer.appendChild(saveBtn)
buttonsContainer.appendChild(backBtn)
_bannerElement.appendChild(buttonsContainer)
}
}
function showMainBanner(): void {
if (_bannerElement) {
_bannerElement.remove()
}
_bannerElement = createBanner()
_bannerElement = createBannerElement(buildBannerContext())
document.body.appendChild(_bannerElement)
}
function showSettingsPanel(): void {
if (!_bannerElement) return
renderSettingsPanel(_bannerElement, buildBannerContext())
}
function hideBanner(): void {
if (_bannerElement) {
_bannerElement.remove()
@@ -507,7 +185,7 @@ function init(config: BreakPilotSDKConfig): void {
_client = new ComplianceClient({
apiEndpoint: config.apiEndpoint,
apiKey: config.apiKey,
tenantId: config.tenantId,
tenantId: config.tenantId ?? '',
})
// Check for stored consents

View File

@@ -37,7 +37,7 @@ export abstract class BreakPilotElement extends HTMLElement {
const apiKey = this.getAttribute('api-key')
const apiEndpoint =
this.getAttribute('api-endpoint') || 'https://compliance.breakpilot.app/api/v1'
const tenantId = this.getAttribute('tenant-id') || undefined
const tenantId = this.getAttribute('tenant-id') || ''
if (apiKey) {
this.client = new ComplianceClient({

View File

@@ -324,7 +324,7 @@ export class ConsentBannerElement extends BreakPilotElement {
private renderSettings(styles: string): void {
const t = this.t
const categories: ConsentPurpose[] = ['ESSENTIAL', 'FUNCTIONAL', 'ANALYTICS', 'MARKETING']
const categories = ['ESSENTIAL', 'FUNCTIONAL', 'ANALYTICS', 'MARKETING'] as const
const categoriesHtml = categories
.map(