import * as plugins from '../../plugins.js'; import { customElement, DeesElement, html, css, cssManager, state, type TemplateResult, } from '@design.estate/dees-element'; import { accountDesignTokens } from './sharedstyles.js'; import * as accountStateModule from '../../states/accountstate.js'; import { IdpState } from '../../states/idp.state.js'; // Internal form element for reactive state management @customElement('idp-create-org-form') class CreateOrgForm extends DeesElement { @state() accessor orgName: string = ''; @state() accessor orgSlug: string = ''; @state() accessor validating: boolean = false; @state() accessor validationResult: { available: boolean; message: string } | null = null; @state() accessor creating: boolean = false; @state() accessor error: string = ''; private validationDebounceTimer: any = null; public resolveWith: ((org: plugins.idpInterfaces.data.IOrganization | null) => void) | null = null; public modal: plugins.deesCatalog.DeesModal | null = null; public static styles = [ cssManager.defaultStyles, css` :host { display: block; } .slug-preview { margin-top: 12px; padding: 12px 16px; background: var(--dees-color-background); border: 1px solid var(--dees-color-line); border-radius: 8px; } .slug-label { font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: var(--dees-color-muted); margin-bottom: 4px; } .slug-value { font-family: 'Geist Mono', monospace; font-size: 14px; color: var(--dees-color-text); } .validation-status { display: flex; align-items: center; gap: 8px; margin-top: 12px; padding: 10px 14px; border-radius: 8px; font-size: 13px; } .validation-status.validating { background: rgba(59, 130, 246, 0.1); color: #3b82f6; } .validation-status.available { background: rgba(34, 197, 94, 0.1); color: #22c55e; } .validation-status.unavailable { background: rgba(239, 68, 68, 0.1); color: #ef4444; } .validation-status dees-icon { font-size: 16px; } .error-message { margin-top: 16px; padding: 12px 16px; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); border-radius: 8px; color: #ef4444; font-size: 13px; } .description { color: var(--dees-color-muted); font-size: 14px; margin-bottom: 20px; } `, ]; public render(): TemplateResult { return html`
Create a new organization to manage apps, users, and billing.
${this.orgSlug ? html`
Organization URL Slug
${this.orgSlug}
` : ''} ${this.renderValidationStatus()} ${this.error ? html`
${this.error}
` : ''} `; } private renderValidationStatus(): TemplateResult | null { if (!this.orgSlug) { return null; } if (this.validating) { return html`
Checking availability...
`; } if (this.validationResult) { if (this.validationResult.available) { return html`
${this.validationResult.message}
`; } else { return html`
${this.validationResult.message}
`; } } return null; } public async firstUpdated() { const inputElement = this.shadowRoot.querySelector('dees-input-text') as any; if (inputElement) { inputElement.changeSubject.subscribe((element: any) => { this.handleNameInput(element.value); }); } } private handleNameInput(value: string) { this.orgName = value; this.orgSlug = this.generateSlug(this.orgName); this.error = ''; // Debounce validation if (this.validationDebounceTimer) { clearTimeout(this.validationDebounceTimer); } if (this.orgSlug) { this.validating = true; this.validationResult = null; this.validationDebounceTimer = setTimeout(() => { this.validateSlug(); }, 500); } else { this.validating = false; this.validationResult = null; } } private generateSlug(name: string): string { return name .replace(/[^a-zA-Z0-9\s-]/g, '') .replace(/\s+/g, '-') .toLowerCase(); } private async validateSlug() { if (!this.orgSlug) { this.validating = false; return; } try { const idpState = await IdpState.getSingletonInstance(); const result = await idpState.idpClient.createOrganization( this.orgName, this.orgSlug, 'checkAvailability' ); this.validationResult = { available: result.nameAvailable, message: result.nameAvailable ? 'This name is available!' : 'This name is already taken. Please choose another.', }; } catch (error) { console.error('Validation error:', error); this.validationResult = { available: false, message: 'Unable to validate. Please try again.', }; } finally { this.validating = false; } } public canCreate(): boolean { return this.orgName.length > 0 && this.validationResult?.available === true && !this.validating && !this.creating; } public async handleCreate(): Promise { if (!this.canCreate()) { return; } this.creating = true; this.error = ''; try { const idpState = await IdpState.getSingletonInstance(); const result = await idpState.idpClient.createOrganization( this.orgName, this.orgSlug, 'manifest' ); // Update state with new organization const currentState = accountStateModule.accountState.getState(); currentState.organizations.push(result.resultingOrganization); accountStateModule.accountState.dispatchAction( accountStateModule.setSelectedOrg, result.resultingOrganization ); this.modal?.destroy(); this.resolveWith?.(result.resultingOrganization); } catch (error) { console.error('Error creating organization:', error); this.error = error instanceof Error ? error.message : 'Failed to create organization. Please try again.'; this.creating = false; } } public handleCancel(): void { if (this.validationDebounceTimer) { clearTimeout(this.validationDebounceTimer); } this.modal?.destroy(); this.resolveWith?.(null); } } // Export the modal utility class export class CreateOrgModal { public static async show(): Promise { return new Promise((resolve) => { const formElement = new CreateOrgForm(); formElement.resolveWith = resolve; plugins.deesCatalog.DeesModal.createAndShow({ heading: 'Create Organization', content: html`${formElement}`, menuOptions: [ { name: 'Cancel', action: async () => { formElement.handleCancel(); }, }, { name: 'Create Organization', action: async () => { await formElement.handleCreate(); }, }, ], width: 480, }).then((modal) => { formElement.modal = modal; }); }); } }