All four files split into focused sibling modules so every file lands
comfortably under the 300-LOC soft target (hard cap 500):
hooks.ts (474→43) → hooks-core / hooks-dsgvo / hooks-compliance
hooks-rag-security / hooks-ui
dsr-portal.ts (464→129) → dsr-portal-translations / dsr-portal-render
provider.tsx (462→247) → provider-effects / provider-callbacks
sync.ts (435→299) → sync-storage / sync-conflict
Zero behaviour changes. All public APIs remain importable from the
original paths (hooks.ts re-exports every hook, provider.tsx keeps all
named exports, sync.ts preserves StateSyncManager + factory).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
130 lines
3.6 KiB
TypeScript
130 lines
3.6 KiB
TypeScript
/**
|
|
* <breakpilot-dsr-portal> Web Component
|
|
*
|
|
* Data Subject Request Portal for GDPR rights
|
|
*
|
|
* Usage:
|
|
* <breakpilot-dsr-portal
|
|
* api-key="pk_live_xxx"
|
|
* language="de">
|
|
* </breakpilot-dsr-portal>
|
|
*
|
|
* Split: translations → dsr-portal-translations.ts
|
|
* styles + HTML builders → dsr-portal-render.ts
|
|
*/
|
|
|
|
import type { DSRRequestType } from '@breakpilot/compliance-sdk-types'
|
|
import { BreakPilotElement } from './base'
|
|
import { DSR_TRANSLATIONS, type DSRLanguage } from './dsr-portal-translations'
|
|
import { DSR_PORTAL_STYLES, buildFormHtml, buildSuccessHtml } from './dsr-portal-render'
|
|
|
|
export class DSRPortalElement extends BreakPilotElement {
|
|
static get observedAttributes(): string[] {
|
|
return ['api-key', 'api-endpoint', 'tenant-id', 'language']
|
|
}
|
|
|
|
private selectedType: DSRRequestType | null = null
|
|
private name = ''
|
|
private email = ''
|
|
private additionalInfo = ''
|
|
private isSubmitting = false
|
|
private isSubmitted = false
|
|
private error: string | null = null
|
|
|
|
private get language(): DSRLanguage {
|
|
return (this.getAttribute('language') as DSRLanguage) || 'de'
|
|
}
|
|
|
|
private get t() {
|
|
return DSR_TRANSLATIONS[this.language]
|
|
}
|
|
|
|
private handleTypeSelect = (type: DSRRequestType): void => {
|
|
this.selectedType = type
|
|
this.render()
|
|
}
|
|
|
|
private handleSubmit = async (e: Event): Promise<void> => {
|
|
e.preventDefault()
|
|
|
|
if (!this.selectedType || !this.email || !this.name) {
|
|
return
|
|
}
|
|
|
|
this.isSubmitting = true
|
|
this.error = null
|
|
this.render()
|
|
|
|
try {
|
|
// In a real implementation, this would use the client
|
|
// For now, we simulate a successful submission
|
|
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
|
|
this.emit('dsr-submitted', {
|
|
type: this.selectedType,
|
|
email: this.email,
|
|
name: this.name,
|
|
additionalInfo: this.additionalInfo,
|
|
})
|
|
|
|
this.isSubmitted = true
|
|
} catch (err) {
|
|
this.error = err instanceof Error ? err.message : 'Ein Fehler ist aufgetreten'
|
|
} finally {
|
|
this.isSubmitting = false
|
|
this.render()
|
|
}
|
|
}
|
|
|
|
protected render(): void {
|
|
if (this.isSubmitted) {
|
|
this.shadow.innerHTML = buildSuccessHtml(DSR_PORTAL_STYLES, this.t, this.email)
|
|
} else {
|
|
this.renderForm()
|
|
}
|
|
}
|
|
|
|
private renderForm(): void {
|
|
this.shadow.innerHTML = buildFormHtml(DSR_PORTAL_STYLES, {
|
|
t: this.t,
|
|
selectedType: this.selectedType,
|
|
name: this.name,
|
|
email: this.email,
|
|
additionalInfo: this.additionalInfo,
|
|
isSubmitting: this.isSubmitting,
|
|
error: this.error,
|
|
})
|
|
|
|
// Bind events
|
|
const form = this.shadow.getElementById('dsr-form') as HTMLFormElement
|
|
form.onsubmit = this.handleSubmit
|
|
|
|
const nameInput = this.shadow.getElementById('name-input') as HTMLInputElement
|
|
nameInput.oninput = e => {
|
|
this.name = (e.target as HTMLInputElement).value
|
|
}
|
|
|
|
const emailInput = this.shadow.getElementById('email-input') as HTMLInputElement
|
|
emailInput.oninput = e => {
|
|
this.email = (e.target as HTMLInputElement).value
|
|
}
|
|
|
|
const infoInput = this.shadow.getElementById('info-input') as HTMLTextAreaElement
|
|
infoInput.oninput = e => {
|
|
this.additionalInfo = (e.target as HTMLTextAreaElement).value
|
|
}
|
|
|
|
// Bind radio buttons
|
|
this.shadow.querySelectorAll<HTMLInputElement>('input[name="dsrType"]').forEach(radio => {
|
|
radio.onchange = () => {
|
|
this.handleTypeSelect(radio.value as DSRRequestType)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// Register the custom element
|
|
if (typeof customElements !== 'undefined') {
|
|
customElements.define('breakpilot-dsr-portal', DSRPortalElement)
|
|
}
|