refactor(sdk): split hooks, dsr-portal, provider, sync approaching 500 LOC
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>
This commit is contained in:
@@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Render helpers for <breakpilot-dsr-portal>
|
||||
*
|
||||
* Provides DSR_PORTAL_STYLES, buildFormHtml, and buildSuccessHtml.
|
||||
* Kept separate from the element class to stay under the 300-LOC target.
|
||||
*/
|
||||
|
||||
import type { DSRRequestType } from '@breakpilot/compliance-sdk-types'
|
||||
import { COMMON_STYLES } from './base'
|
||||
import type { DSRTranslations } from './dsr-portal-translations'
|
||||
|
||||
// =============================================================================
|
||||
// STYLES
|
||||
// =============================================================================
|
||||
|
||||
export const DSR_PORTAL_STYLES = `
|
||||
${COMMON_STYLES}
|
||||
|
||||
:host {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.portal {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0 0 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.type-options {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.type-option {
|
||||
display: flex;
|
||||
padding: 15px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.type-option:hover {
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
.type-option.selected {
|
||||
border-color: #1a1a1a;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.type-option input {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.type-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.type-description {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: #1a1a1a;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 12px;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: #1a1a1a;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.success {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
background: #f0fdf4;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.success-title {
|
||||
margin: 0 0 10px;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
margin: 0;
|
||||
color: #166534;
|
||||
}
|
||||
`
|
||||
|
||||
// =============================================================================
|
||||
// HTML BUILDERS
|
||||
// =============================================================================
|
||||
|
||||
export interface FormRenderOptions {
|
||||
t: DSRTranslations
|
||||
selectedType: DSRRequestType | null
|
||||
name: string
|
||||
email: string
|
||||
additionalInfo: string
|
||||
isSubmitting: boolean
|
||||
error: string | null
|
||||
}
|
||||
|
||||
export function buildFormHtml(styles: string, opts: FormRenderOptions): string {
|
||||
const { t, selectedType, name, email, additionalInfo, isSubmitting, error } = opts
|
||||
|
||||
const types: DSRRequestType[] = [
|
||||
'ACCESS',
|
||||
'RECTIFICATION',
|
||||
'ERASURE',
|
||||
'PORTABILITY',
|
||||
'RESTRICTION',
|
||||
'OBJECTION',
|
||||
]
|
||||
|
||||
const typesHtml = types
|
||||
.map(
|
||||
type => `
|
||||
<label class="type-option ${selectedType === type ? 'selected' : ''}">
|
||||
<input
|
||||
type="radio"
|
||||
name="dsrType"
|
||||
value="${type}"
|
||||
${selectedType === type ? 'checked' : ''}
|
||||
/>
|
||||
<div>
|
||||
<div class="type-name">${t.types[type].name}</div>
|
||||
<div class="type-description">${t.types[type].description}</div>
|
||||
</div>
|
||||
</label>
|
||||
`
|
||||
)
|
||||
.join('')
|
||||
|
||||
const isValid = selectedType && email && name
|
||||
const isDisabled = !isValid || isSubmitting
|
||||
|
||||
return `
|
||||
<style>${styles}</style>
|
||||
<div class="portal">
|
||||
<h2 class="title">${t.title}</h2>
|
||||
<p class="subtitle">${t.subtitle}</p>
|
||||
|
||||
<form id="dsr-form">
|
||||
<div class="form-group">
|
||||
<label class="label">${t.requestType} *</label>
|
||||
<div class="type-options">
|
||||
${typesHtml}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">${t.name} *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
id="name-input"
|
||||
value="${name}"
|
||||
placeholder="${t.namePlaceholder}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">${t.email} *</label>
|
||||
<input
|
||||
type="email"
|
||||
class="input"
|
||||
id="email-input"
|
||||
value="${email}"
|
||||
placeholder="${t.emailPlaceholder}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">${t.additionalInfo}</label>
|
||||
<textarea
|
||||
class="input textarea"
|
||||
id="info-input"
|
||||
placeholder="${t.additionalInfoPlaceholder}"
|
||||
rows="4"
|
||||
>${additionalInfo}</textarea>
|
||||
</div>
|
||||
|
||||
${error ? `<div class="error">${error}</div>` : ''}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-submit"
|
||||
${isDisabled ? 'disabled' : ''}
|
||||
>
|
||||
${isSubmitting ? t.submitting : t.submit}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="disclaimer">${t.disclaimer}</p>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
export function buildSuccessHtml(styles: string, t: DSRTranslations, email: string): string {
|
||||
return `
|
||||
<style>${styles}</style>
|
||||
<div class="portal">
|
||||
<div class="success">
|
||||
<div class="success-icon">✓</div>
|
||||
<h2 class="success-title">${t.successTitle}</h2>
|
||||
<p class="success-message">
|
||||
${t.successMessage} ${email}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
/**
|
||||
* Translations for <breakpilot-dsr-portal>
|
||||
*
|
||||
* Supported languages: 'de' | 'en'
|
||||
*/
|
||||
|
||||
export const DSR_TRANSLATIONS = {
|
||||
de: {
|
||||
title: 'Betroffenenrechte-Portal',
|
||||
subtitle:
|
||||
'Hier können Sie Ihre Rechte gemäß DSGVO wahrnehmen. Wählen Sie die gewünschte Anfrage und füllen Sie das Formular aus.',
|
||||
requestType: 'Art der Anfrage',
|
||||
name: 'Ihr Name',
|
||||
namePlaceholder: 'Max Mustermann',
|
||||
email: 'E-Mail-Adresse',
|
||||
emailPlaceholder: 'max@example.com',
|
||||
additionalInfo: 'Zusätzliche Informationen (optional)',
|
||||
additionalInfoPlaceholder: 'Weitere Details zu Ihrer Anfrage...',
|
||||
submit: 'Anfrage einreichen',
|
||||
submitting: 'Wird gesendet...',
|
||||
successTitle: 'Anfrage eingereicht',
|
||||
successMessage:
|
||||
'Wir werden Ihre Anfrage innerhalb von 30 Tagen bearbeiten. Sie erhalten eine Bestätigung per E-Mail an',
|
||||
disclaimer:
|
||||
'Ihre Anfrage wird gemäß Art. 12 DSGVO innerhalb von einem Monat bearbeitet. In komplexen Fällen kann diese Frist um weitere zwei Monate verlängert werden.',
|
||||
types: {
|
||||
ACCESS: {
|
||||
name: 'Auskunft (Art. 15)',
|
||||
description: 'Welche Daten haben Sie über mich gespeichert?',
|
||||
},
|
||||
RECTIFICATION: {
|
||||
name: 'Berichtigung (Art. 16)',
|
||||
description: 'Korrigieren Sie falsche Daten über mich.',
|
||||
},
|
||||
ERASURE: {
|
||||
name: 'Löschung (Art. 17)',
|
||||
description: 'Löschen Sie alle meine personenbezogenen Daten.',
|
||||
},
|
||||
PORTABILITY: {
|
||||
name: 'Datenübertragbarkeit (Art. 20)',
|
||||
description: 'Exportieren Sie meine Daten in einem maschinenlesbaren Format.',
|
||||
},
|
||||
RESTRICTION: {
|
||||
name: 'Einschränkung (Art. 18)',
|
||||
description: 'Schränken Sie die Verarbeitung meiner Daten ein.',
|
||||
},
|
||||
OBJECTION: {
|
||||
name: 'Widerspruch (Art. 21)',
|
||||
description: 'Ich widerspreche der Verarbeitung meiner Daten.',
|
||||
},
|
||||
},
|
||||
},
|
||||
en: {
|
||||
title: 'Data Subject Rights Portal',
|
||||
subtitle:
|
||||
'Here you can exercise your rights under GDPR. Select the type of request and fill out the form.',
|
||||
requestType: 'Request Type',
|
||||
name: 'Your Name',
|
||||
namePlaceholder: 'John Doe',
|
||||
email: 'Email Address',
|
||||
emailPlaceholder: 'john@example.com',
|
||||
additionalInfo: 'Additional Information (optional)',
|
||||
additionalInfoPlaceholder: 'Any additional details about your request...',
|
||||
submit: 'Submit Request',
|
||||
submitting: 'Submitting...',
|
||||
successTitle: 'Request Submitted',
|
||||
successMessage:
|
||||
'We will process your request within 30 days. You will receive a confirmation email at',
|
||||
disclaimer:
|
||||
'Your request will be processed in accordance with Article 12 GDPR within one month. In complex cases, this period may be extended by up to two additional months.',
|
||||
types: {
|
||||
ACCESS: {
|
||||
name: 'Access (Art. 15)',
|
||||
description: 'What data do you have about me?',
|
||||
},
|
||||
RECTIFICATION: {
|
||||
name: 'Rectification (Art. 16)',
|
||||
description: 'Correct inaccurate data about me.',
|
||||
},
|
||||
ERASURE: {
|
||||
name: 'Erasure (Art. 17)',
|
||||
description: 'Delete all my personal data.',
|
||||
},
|
||||
PORTABILITY: {
|
||||
name: 'Data Portability (Art. 20)',
|
||||
description: 'Export my data in a machine-readable format.',
|
||||
},
|
||||
RESTRICTION: {
|
||||
name: 'Restriction (Art. 18)',
|
||||
description: 'Restrict the processing of my data.',
|
||||
},
|
||||
OBJECTION: {
|
||||
name: 'Objection (Art. 21)',
|
||||
description: 'I object to the processing of my data.',
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
export type DSRLanguage = keyof typeof DSR_TRANSLATIONS
|
||||
export type DSRTranslations = (typeof DSR_TRANSLATIONS)[DSRLanguage]
|
||||
@@ -8,103 +8,15 @@
|
||||
* 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, COMMON_STYLES } from './base'
|
||||
|
||||
const TRANSLATIONS = {
|
||||
de: {
|
||||
title: 'Betroffenenrechte-Portal',
|
||||
subtitle:
|
||||
'Hier können Sie Ihre Rechte gemäß DSGVO wahrnehmen. Wählen Sie die gewünschte Anfrage und füllen Sie das Formular aus.',
|
||||
requestType: 'Art der Anfrage',
|
||||
name: 'Ihr Name',
|
||||
namePlaceholder: 'Max Mustermann',
|
||||
email: 'E-Mail-Adresse',
|
||||
emailPlaceholder: 'max@example.com',
|
||||
additionalInfo: 'Zusätzliche Informationen (optional)',
|
||||
additionalInfoPlaceholder: 'Weitere Details zu Ihrer Anfrage...',
|
||||
submit: 'Anfrage einreichen',
|
||||
submitting: 'Wird gesendet...',
|
||||
successTitle: 'Anfrage eingereicht',
|
||||
successMessage:
|
||||
'Wir werden Ihre Anfrage innerhalb von 30 Tagen bearbeiten. Sie erhalten eine Bestätigung per E-Mail an',
|
||||
disclaimer:
|
||||
'Ihre Anfrage wird gemäß Art. 12 DSGVO innerhalb von einem Monat bearbeitet. In komplexen Fällen kann diese Frist um weitere zwei Monate verlängert werden.',
|
||||
types: {
|
||||
ACCESS: {
|
||||
name: 'Auskunft (Art. 15)',
|
||||
description: 'Welche Daten haben Sie über mich gespeichert?',
|
||||
},
|
||||
RECTIFICATION: {
|
||||
name: 'Berichtigung (Art. 16)',
|
||||
description: 'Korrigieren Sie falsche Daten über mich.',
|
||||
},
|
||||
ERASURE: {
|
||||
name: 'Löschung (Art. 17)',
|
||||
description: 'Löschen Sie alle meine personenbezogenen Daten.',
|
||||
},
|
||||
PORTABILITY: {
|
||||
name: 'Datenübertragbarkeit (Art. 20)',
|
||||
description: 'Exportieren Sie meine Daten in einem maschinenlesbaren Format.',
|
||||
},
|
||||
RESTRICTION: {
|
||||
name: 'Einschränkung (Art. 18)',
|
||||
description: 'Schränken Sie die Verarbeitung meiner Daten ein.',
|
||||
},
|
||||
OBJECTION: {
|
||||
name: 'Widerspruch (Art. 21)',
|
||||
description: 'Ich widerspreche der Verarbeitung meiner Daten.',
|
||||
},
|
||||
},
|
||||
},
|
||||
en: {
|
||||
title: 'Data Subject Rights Portal',
|
||||
subtitle:
|
||||
'Here you can exercise your rights under GDPR. Select the type of request and fill out the form.',
|
||||
requestType: 'Request Type',
|
||||
name: 'Your Name',
|
||||
namePlaceholder: 'John Doe',
|
||||
email: 'Email Address',
|
||||
emailPlaceholder: 'john@example.com',
|
||||
additionalInfo: 'Additional Information (optional)',
|
||||
additionalInfoPlaceholder: 'Any additional details about your request...',
|
||||
submit: 'Submit Request',
|
||||
submitting: 'Submitting...',
|
||||
successTitle: 'Request Submitted',
|
||||
successMessage:
|
||||
'We will process your request within 30 days. You will receive a confirmation email at',
|
||||
disclaimer:
|
||||
'Your request will be processed in accordance with Article 12 GDPR within one month. In complex cases, this period may be extended by up to two additional months.',
|
||||
types: {
|
||||
ACCESS: {
|
||||
name: 'Access (Art. 15)',
|
||||
description: 'What data do you have about me?',
|
||||
},
|
||||
RECTIFICATION: {
|
||||
name: 'Rectification (Art. 16)',
|
||||
description: 'Correct inaccurate data about me.',
|
||||
},
|
||||
ERASURE: {
|
||||
name: 'Erasure (Art. 17)',
|
||||
description: 'Delete all my personal data.',
|
||||
},
|
||||
PORTABILITY: {
|
||||
name: 'Data Portability (Art. 20)',
|
||||
description: 'Export my data in a machine-readable format.',
|
||||
},
|
||||
RESTRICTION: {
|
||||
name: 'Restriction (Art. 18)',
|
||||
description: 'Restrict the processing of my data.',
|
||||
},
|
||||
OBJECTION: {
|
||||
name: 'Objection (Art. 21)',
|
||||
description: 'I object to the processing of my data.',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
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[] {
|
||||
@@ -119,12 +31,12 @@ export class DSRPortalElement extends BreakPilotElement {
|
||||
private isSubmitted = false
|
||||
private error: string | null = null
|
||||
|
||||
private get language(): 'de' | 'en' {
|
||||
return (this.getAttribute('language') as 'de' | 'en') || 'de'
|
||||
private get language(): DSRLanguage {
|
||||
return (this.getAttribute('language') as DSRLanguage) || 'de'
|
||||
}
|
||||
|
||||
private get t() {
|
||||
return TRANSLATIONS[this.language]
|
||||
return DSR_TRANSLATIONS[this.language]
|
||||
}
|
||||
|
||||
private handleTypeSelect = (type: DSRRequestType): void => {
|
||||
@@ -165,253 +77,23 @@ export class DSRPortalElement extends BreakPilotElement {
|
||||
}
|
||||
|
||||
protected render(): void {
|
||||
const styles = `
|
||||
${COMMON_STYLES}
|
||||
|
||||
:host {
|
||||
max-width: 600px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.portal {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
.title {
|
||||
margin: 0 0 10px;
|
||||
font-size: 24px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
margin: 0 0 20px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.label {
|
||||
display: block;
|
||||
font-weight: 500;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.type-options {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.type-option {
|
||||
display: flex;
|
||||
padding: 15px;
|
||||
border: 2px solid #ddd;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
background: #fff;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.type-option:hover {
|
||||
border-color: #999;
|
||||
}
|
||||
|
||||
.type-option.selected {
|
||||
border-color: #1a1a1a;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.type-option input {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.type-name {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.type-description {
|
||||
font-size: 13px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
font-size: 14px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.input:focus {
|
||||
outline: none;
|
||||
border-color: #1a1a1a;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
resize: vertical;
|
||||
min-height: 100px;
|
||||
}
|
||||
|
||||
.error {
|
||||
padding: 12px;
|
||||
background: #fef2f2;
|
||||
color: #dc2626;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.btn-submit {
|
||||
width: 100%;
|
||||
padding: 12px 24px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
color: #fff;
|
||||
background: #1a1a1a;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.btn-submit:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.disclaimer {
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.success {
|
||||
text-align: center;
|
||||
padding: 40px 20px;
|
||||
background: #f0fdf4;
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.success-icon {
|
||||
font-size: 48px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.success-title {
|
||||
margin: 0 0 10px;
|
||||
color: #166534;
|
||||
}
|
||||
|
||||
.success-message {
|
||||
margin: 0;
|
||||
color: #166534;
|
||||
}
|
||||
`
|
||||
|
||||
if (this.isSubmitted) {
|
||||
this.renderSuccess(styles)
|
||||
this.shadow.innerHTML = buildSuccessHtml(DSR_PORTAL_STYLES, this.t, this.email)
|
||||
} else {
|
||||
this.renderForm(styles)
|
||||
this.renderForm()
|
||||
}
|
||||
}
|
||||
|
||||
private renderForm(styles: string): void {
|
||||
const t = this.t
|
||||
const types: DSRRequestType[] = [
|
||||
'ACCESS',
|
||||
'RECTIFICATION',
|
||||
'ERASURE',
|
||||
'PORTABILITY',
|
||||
'RESTRICTION',
|
||||
'OBJECTION',
|
||||
]
|
||||
|
||||
const typesHtml = types
|
||||
.map(
|
||||
type => `
|
||||
<label class="type-option ${this.selectedType === type ? 'selected' : ''}">
|
||||
<input
|
||||
type="radio"
|
||||
name="dsrType"
|
||||
value="${type}"
|
||||
${this.selectedType === type ? 'checked' : ''}
|
||||
/>
|
||||
<div>
|
||||
<div class="type-name">${t.types[type].name}</div>
|
||||
<div class="type-description">${t.types[type].description}</div>
|
||||
</div>
|
||||
</label>
|
||||
`
|
||||
)
|
||||
.join('')
|
||||
|
||||
const isValid = this.selectedType && this.email && this.name
|
||||
const isDisabled = !isValid || this.isSubmitting
|
||||
|
||||
this.shadow.innerHTML = `
|
||||
<style>${styles}</style>
|
||||
<div class="portal">
|
||||
<h2 class="title">${t.title}</h2>
|
||||
<p class="subtitle">${t.subtitle}</p>
|
||||
|
||||
<form id="dsr-form">
|
||||
<div class="form-group">
|
||||
<label class="label">${t.requestType} *</label>
|
||||
<div class="type-options">
|
||||
${typesHtml}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">${t.name} *</label>
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
id="name-input"
|
||||
value="${this.name}"
|
||||
placeholder="${t.namePlaceholder}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">${t.email} *</label>
|
||||
<input
|
||||
type="email"
|
||||
class="input"
|
||||
id="email-input"
|
||||
value="${this.email}"
|
||||
placeholder="${t.emailPlaceholder}"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label class="label">${t.additionalInfo}</label>
|
||||
<textarea
|
||||
class="input textarea"
|
||||
id="info-input"
|
||||
placeholder="${t.additionalInfoPlaceholder}"
|
||||
rows="4"
|
||||
>${this.additionalInfo}</textarea>
|
||||
</div>
|
||||
|
||||
${this.error ? `<div class="error">${this.error}</div>` : ''}
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
class="btn-submit"
|
||||
${isDisabled ? 'disabled' : ''}
|
||||
>
|
||||
${this.isSubmitting ? t.submitting : t.submit}
|
||||
</button>
|
||||
</form>
|
||||
|
||||
<p class="disclaimer">${t.disclaimer}</p>
|
||||
</div>
|
||||
`
|
||||
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
|
||||
@@ -439,23 +121,6 @@ export class DSRPortalElement extends BreakPilotElement {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private renderSuccess(styles: string): void {
|
||||
const t = this.t
|
||||
|
||||
this.shadow.innerHTML = `
|
||||
<style>${styles}</style>
|
||||
<div class="portal">
|
||||
<div class="success">
|
||||
<div class="success-icon">✓</div>
|
||||
<h2 class="success-title">${t.successTitle}</h2>
|
||||
<p class="success-message">
|
||||
${t.successMessage} ${this.email}.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
// Register the custom element
|
||||
|
||||
Reference in New Issue
Block a user