import * as plugins from '../../plugins.js'; import { customElement, DeesElement, property, html, cssManager, css, 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'; declare global { interface HTMLElementTagNameMap { 'idp-create-org-modal': CreateOrgModal; } } @customElement('idp-create-org-modal') export class CreateOrgModal extends DeesElement { @state() accessor visible: boolean = false; @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 static styles = [ cssManager.defaultStyles, accountDesignTokens, css` :host { display: none; } :host([visible]) { display: block; } .overlay { position: fixed; inset: 0; background: rgba(0, 0, 0, 0.8); display: flex; align-items: center; justify-content: center; z-index: 1000; animation: fadeIn 0.15s ease; } @keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } } .modal { background: #18181b; border: 1px solid #27272a; border-radius: 16px; width: 100%; max-width: 480px; max-height: 90vh; overflow-y: auto; animation: slideIn 0.2s ease; } @keyframes slideIn { from { opacity: 0; transform: translateY(-20px); } to { opacity: 1; transform: translateY(0); } } .modal-header { padding: 20px 24px; border-bottom: 1px solid #27272a; } .modal-title { font-size: 18px; font-weight: 600; margin: 0 0 4px 0; color: #fafafa; } .modal-description { font-size: 14px; color: #71717a; margin: 0; } .modal-body { padding: 24px; } .form-group { margin-bottom: 20px; } .form-group:last-child { margin-bottom: 0; } .form-label { display: block; font-size: 13px; font-weight: 500; margin-bottom: 8px; color: #a1a1aa; } .form-input { width: 100%; padding: 10px 14px; border-radius: 8px; border: 1px solid #27272a; background: #0a0a0a; color: #fafafa; font-size: 14px; box-sizing: border-box; transition: border-color 0.15s ease; } .form-input:focus { outline: none; border-color: #3b82f6; } .form-input:disabled { opacity: 0.5; cursor: not-allowed; } .slug-preview { margin-top: 12px; padding: 12px 16px; background: #0a0a0a; border: 1px solid #27272a; border-radius: 8px; } .slug-label { font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.05em; color: #71717a; margin-bottom: 4px; } .slug-value { font-family: 'Geist Mono', monospace; font-size: 14px; color: #fafafa; } .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; } .modal-footer { display: flex; justify-content: flex-end; gap: 12px; padding: 16px 24px; border-top: 1px solid #27272a; } `, ]; public render(): TemplateResult { if (!this.visible) { return html``; } const canCreate = this.orgName.length > 0 && this.validationResult?.available && !this.validating && !this.creating; return html`
`; } 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 show() { this.orgName = ''; this.orgSlug = ''; this.validating = false; this.validationResult = null; this.creating = false; this.error = ''; this.visible = true; this.setAttribute('visible', ''); } public hide() { this.visible = false; this.removeAttribute('visible'); if (this.validationDebounceTimer) { clearTimeout(this.validationDebounceTimer); } } private handleOverlayClick(e: Event) { if ((e.target as HTMLElement).classList.contains('overlay') && !this.creating) { this.hide(); } } private handleCancel() { if (!this.creating) { this.hide(); } } private handleNameInput(e: Event) { const input = e.target as HTMLInputElement; this.orgName = input.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; } } private async handleCreate() { if (!this.validationResult?.available || this.creating) { 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.dispatchEvent(new CustomEvent('org-created', { bubbles: true, composed: true, detail: { org: result.resultingOrganization }, })); this.hide(); } catch (error) { console.error('Error creating organization:', error); this.error = error instanceof Error ? error.message : 'Failed to create organization. Please try again.'; } finally { this.creating = false; } } }