import { DeesElement, customElement, html, css, cssManager, property, type TemplateResult, } from '@design.estate/dees-element'; import type { ISgAuthProvider } from '../interfaces.js'; declare global { interface HTMLElementTagNameMap { 'sg-login-view': SgLoginView; } } @customElement('sg-login-view') export class SgLoginView extends DeesElement { public static demo = () => html`
`; public static demoGroups = ['Auth']; @property({ type: Array }) public accessor providers: ISgAuthProvider[] = []; @property({ type: Boolean }) public accessor localAuthEnabled: boolean = true; @property({ type: Boolean }) public accessor loading: boolean = false; @property({ type: String }) public accessor error: string = ''; public static styles = [ cssManager.defaultStyles, css` :host { display: flex; align-items: center; justify-content: center; min-height: 100vh; width: 100%; background: ${cssManager.bdTheme('#f4f4f5', '#09090b')}; } .login-container { width: 100%; max-width: 420px; padding: 24px; } .login-card { background: ${cssManager.bdTheme('#ffffff', '#111')}; border: 1px solid ${cssManager.bdTheme('#e5e5e5', '#333')}; padding: 40px 32px; } .logo-section { text-align: center; margin-bottom: 32px; } .logo { width: 56px; height: 56px; background: ${cssManager.bdTheme('#111', '#fff')}; display: inline-flex; align-items: center; justify-content: center; margin-bottom: 16px; } .logo svg { width: 32px; height: 32px; color: ${cssManager.bdTheme('#fff', '#111')}; } .brand-title { font-size: 22px; font-weight: 700; color: ${cssManager.bdTheme('#111', '#fff')}; margin-bottom: 4px; letter-spacing: -0.02em; } .brand-subtitle { font-size: 14px; color: ${cssManager.bdTheme('#666', '#999')}; } .form { display: flex; flex-direction: column; gap: 16px; } .form-group { display: flex; flex-direction: column; gap: 6px; } .form-label { font-size: 13px; font-weight: 600; color: ${cssManager.bdTheme('#111', '#ddd')}; text-transform: uppercase; letter-spacing: 0.04em; } .form-input { width: 100%; padding: 10px 12px; background: ${cssManager.bdTheme('#fff', '#0a0a0a')}; border: 1px solid ${cssManager.bdTheme('#ddd', '#333')}; font-size: 14px; color: ${cssManager.bdTheme('#111', '#fff')}; outline: none; transition: border-color 150ms ease; box-sizing: border-box; font-family: inherit; } .form-input:focus { border-color: ${cssManager.bdTheme('#111', '#fff')}; } .form-input::placeholder { color: ${cssManager.bdTheme('#aaa', '#555')}; } .form-input.has-error { border-color: #ef4444; } .error-banner { display: flex; align-items: center; gap: 8px; padding: 10px 12px; background: rgba(239, 68, 68, 0.1); border: 1px solid rgba(239, 68, 68, 0.3); font-size: 13px; color: #f87171; } .submit-btn { width: 100%; padding: 10px 20px; background: ${cssManager.bdTheme('#111', '#fff')}; border: none; font-size: 14px; font-weight: 600; color: ${cssManager.bdTheme('#fff', '#111')}; cursor: pointer; transition: opacity 150ms ease; display: flex; align-items: center; justify-content: center; gap: 8px; } .submit-btn:hover:not(:disabled) { opacity: 0.85; } .submit-btn:disabled { opacity: 0.5; cursor: not-allowed; } .spinner { width: 16px; height: 16px; border: 2px solid transparent; border-top-color: currentColor; border-radius: 50%; animation: spin 0.7s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } .divider { display: flex; align-items: center; gap: 12px; margin: 8px 0; } .divider-line { flex: 1; height: 1px; background: ${cssManager.bdTheme('#e5e5e5', '#333')}; } .divider-text { font-size: 12px; color: ${cssManager.bdTheme('#999', '#666')}; text-transform: uppercase; letter-spacing: 0.05em; } .oauth-buttons { display: flex; flex-direction: column; gap: 8px; } .oauth-btn { width: 100%; padding: 10px 16px; background: transparent; border: 1px solid ${cssManager.bdTheme('#ddd', '#333')}; font-size: 14px; font-weight: 500; color: ${cssManager.bdTheme('#333', '#ddd')}; cursor: pointer; transition: all 150ms ease; display: flex; align-items: center; justify-content: center; gap: 8px; } .oauth-btn:hover { background: ${cssManager.bdTheme('#f5f5f5', '#1a1a1a')}; border-color: ${cssManager.bdTheme('#ccc', '#555')}; } .ldap-section { margin-top: 8px; } .ldap-toggle { display: flex; align-items: center; gap: 8px; padding: 8px 0; font-size: 13px; color: ${cssManager.bdTheme('#666', '#999')}; cursor: pointer; border: none; background: transparent; width: 100%; text-align: left; } .ldap-toggle:hover { color: ${cssManager.bdTheme('#333', '#ddd')}; } .ldap-form { display: flex; flex-direction: column; gap: 12px; padding-top: 8px; } .ldap-provider-select { display: flex; gap: 8px; flex-wrap: wrap; } .ldap-provider-btn { padding: 6px 12px; background: transparent; border: 1px solid ${cssManager.bdTheme('#ddd', '#333')}; font-size: 12px; color: ${cssManager.bdTheme('#666', '#999')}; cursor: pointer; transition: all 150ms ease; } .ldap-provider-btn:hover, .ldap-provider-btn.active { background: ${cssManager.bdTheme('#111', '#fff')}; color: ${cssManager.bdTheme('#fff', '#111')}; border-color: ${cssManager.bdTheme('#111', '#fff')}; } .footer { margin-top: 24px; text-align: center; font-size: 12px; color: ${cssManager.bdTheme('#999', '#555')}; } `, ]; private ldapExpanded = false; private selectedLdapProviderId = ''; private get oauthProviders(): ISgAuthProvider[] { return this.providers.filter((p) => p.type === 'oidc'); } private get ldapProviders(): ISgAuthProvider[] { return this.providers.filter((p) => p.type === 'ldap'); } public render(): TemplateResult { const hasOauth = this.oauthProviders.length > 0; const hasLdap = this.ldapProviders.length > 0; return html`
Stack.Gallery
Sign in to your registry
${this.error ? html`
${this.error}
` : ''} ${this.localAuthEnabled ? html`
` : ''} ${hasOauth ? html` ${this.localAuthEnabled ? html`
or continue with
` : ''}
${this.oauthProviders.map( (p) => html` ` )}
` : ''} ${hasLdap ? html`
${this.localAuthEnabled || hasOauth ? html`
enterprise
` : ''} ${this.ldapExpanded ? html`
${this.ldapProviders.length > 1 ? html`
${this.ldapProviders.map( (p) => html` ` )}
` : ''}
` : ''}
` : ''}
`; } private handleLocalLogin(e: Event) { e.preventDefault(); const emailInput = this.shadowRoot?.getElementById('login-email') as HTMLInputElement; const passwordInput = this.shadowRoot?.getElementById('login-password') as HTMLInputElement; if (!emailInput || !passwordInput) return; const email = emailInput.value.trim(); const password = passwordInput.value; if (!email || !password) return; this.dispatchEvent(new CustomEvent('login', { detail: { email, password }, bubbles: true, composed: true, })); } private handleOAuthLogin(providerId: string) { this.dispatchEvent(new CustomEvent('oauth-login', { detail: { providerId }, bubbles: true, composed: true, })); } private handleLdapLogin() { const usernameInput = this.shadowRoot?.getElementById('ldap-username') as HTMLInputElement; const passwordInput = this.shadowRoot?.getElementById('ldap-password') as HTMLInputElement; if (!usernameInput || !passwordInput) return; const username = usernameInput.value.trim(); const password = passwordInput.value; if (!username || !password) return; const providerId = this.selectedLdapProviderId || this.ldapProviders[0]?.id; if (!providerId) return; this.dispatchEvent(new CustomEvent('ldap-login', { detail: { providerId, username, password }, bubbles: true, composed: true, })); } }