import { DeesElement, html, customElement, type TemplateResult, css, state, property, cssManager, } from '@design.estate/dees-element'; import * as interfaces from '../../../dist_ts_interfaces/index.js'; declare global { interface HTMLElementTagNameMap { 'dns-provider-form': DnsProviderForm; } } /** * Reactive credential form for a DNS provider. Renders the type picker * and the credential fields for the currently-selected type. * * Provider-agnostic — driven entirely by `dnsProviderTypeDescriptors` from * `ts_interfaces/data/dns-provider.ts`. Adding a new provider type means * appending one entry to the descriptors array; this form picks it up * automatically. * * Usage: * * const formEl = document.createElement('dns-provider-form'); * formEl.providerName = 'My provider'; * // ... pass element into a DeesModal as content ... * // on submit: * const data = formEl.collectData(); * // → { name, type, credentials } * * In edit mode, set `lockType = true` so the user cannot change provider * type after creation (credentials shapes don't transfer between types). */ @customElement('dns-provider-form') export class DnsProviderForm extends DeesElement { /** Pre-populated provider name. */ @property({ type: String }) accessor providerName: string = ''; /** * Currently selected provider type. Initialized to the first descriptor; * caller can override before mounting (e.g. for edit dialogs). */ @state() accessor selectedType: interfaces.data.TDnsProviderType = interfaces.data.dnsProviderTypeDescriptors[0]?.type ?? 'cloudflare'; /** When true, hide the type picker — used in edit dialogs. */ @property({ type: Boolean }) accessor lockType: boolean = false; /** * Help text shown above credentials. Useful for edit dialogs to indicate * that fields can be left blank to keep current values. */ @property({ type: String }) accessor credentialsHint: string = ''; /** Internal map of credential field values, keyed by the descriptor's `key`. */ @state() accessor credentialValues: Record = {}; public static styles = [ cssManager.defaultStyles, css` :host { display: block; } .field { margin-bottom: 12px; } .helpText { font-size: 12px; opacity: 0.7; margin-top: -6px; margin-bottom: 8px; } .typeDescription { font-size: 12px; opacity: 0.8; margin: 4px 0 16px; padding: 8px 12px; background: ${cssManager.bdTheme('#f3f4f6', '#1f2937')}; border-radius: 6px; } .credentialsHint { font-size: 12px; opacity: 0.7; margin-bottom: 12px; } `, ]; public render(): TemplateResult { const descriptors = interfaces.data.dnsProviderTypeDescriptors; const descriptor = interfaces.data.getDnsProviderTypeDescriptor(this.selectedType); return html`
${this.lockType ? html`
` : html`
({ option: d.displayName, key: d.type }))} .selectedOption=${descriptor ? { option: descriptor.displayName, key: descriptor.type } : undefined} @selectedOption=${(e: CustomEvent) => { const newType = (e.detail as any)?.key as | interfaces.data.TDnsProviderType | undefined; if (newType && newType !== this.selectedType) { this.selectedType = newType; this.credentialValues = {}; } }} >
`} ${descriptor ? html`
${descriptor.description}
${this.credentialsHint ? html`
${this.credentialsHint}
` : ''} ${descriptor.credentialFields.map( (f) => html`
${f.helpText ? html`
${f.helpText}
` : ''}
`, )} ` : html`

No provider types registered.

`}
`; } /** * Read the form values and assemble the create/update payload. * Returns the typed credentials object built from the descriptor's keys. */ public async collectData(): Promise<{ name: string; type: interfaces.data.TDnsProviderType; credentials: interfaces.data.TDnsProviderCredentials; credentialsTouched: boolean; } | null> { const form = this.shadowRoot?.querySelector('dees-form') as any; if (!form) return null; const data = await form.collectFormData(); const descriptor = interfaces.data.getDnsProviderTypeDescriptor(this.selectedType); if (!descriptor) return null; // Build the credentials object from the descriptor's field keys. const credsBody: Record = {}; let credentialsTouched = false; for (const f of descriptor.credentialFields) { const value = data[f.key]; if (value !== undefined && value !== null && String(value).length > 0) { credsBody[f.key] = String(value); credentialsTouched = true; } } // The discriminator goes on the credentials object so the backend // factory and the discriminated union both stay happy. const credentials = { type: this.selectedType, ...credsBody, } as unknown as interfaces.data.TDnsProviderCredentials; return { name: String(data.name ?? ''), type: this.selectedType, credentials, credentialsTouched, }; } }