import { Component, computed, effect, inject, OnInit, signal } from '@angular/core'; import { CommonModule } from '@angular/common'; import { FormsModule } from '@angular/forms'; import { ActivatedRoute, Router } from '@angular/router'; import { Actions } from '@ngrx/effects'; import { Store } from '@ngrx/store'; import { NgIcon, provideIcons } from '@ng-icons/core'; import { lucideLogIn } from '@ng-icons/lucide'; import { firstValueFrom } from 'rxjs'; import { AuthenticationService } from '../../application/services/authentication.service'; import { ServerDirectoryFacade } from '../../../server-directory'; import { AUTH_MODE_AUTHORIZE, buildLoginReturnQueryParams, isAuthorizeAuthMode, resolveSafeReturnUrl, waitForAuthenticationOutcome } from '../../domain/logic/auth-navigation.rules'; import { UsersActions } from '../../../../store/users/users.actions'; import { User } from '../../../../shared-kernel'; import { AppI18nService, APP_TRANSLATE_IMPORTS } from '../../../../core/i18n'; import { AutoFocusDirective, SelectOnFocusDirective } from '../../../../shared/directives'; import { selectCurrentUser } from '../../../../store/users/users.selectors'; @Component({ selector: 'app-login', standalone: true, imports: [ CommonModule, FormsModule, NgIcon, AutoFocusDirective, SelectOnFocusDirective, ...APP_TRANSLATE_IMPORTS ], viewProviders: [provideIcons({ lucideLogIn })], templateUrl: './login.component.html' }) /** * Login form allowing existing users to authenticate against a selected server. */ export class LoginComponent implements OnInit { serversSvc = inject(ServerDirectoryFacade); servers = this.serversSvc.servers; username = ''; password = ''; serverId: string | undefined = this.serversSvc.activeServer()?.id; error = signal(null); readonly isAuthorizeMode = signal(false); readonly authorizeServerName = computed(() => { const sid = this.serverId || this.serversSvc.activeServer()?.id; const endpoint = this.servers().find((server) => server.id === sid); return endpoint?.name ?? this.appI18n.instant('auth.authorize.defaultServerName'); }); private readonly appI18n = inject(AppI18nService); private auth = inject(AuthenticationService); private actions$ = inject(Actions); private store = inject(Store); private readonly route = inject(ActivatedRoute); private readonly router = inject(Router); private readonly currentUser = this.store.selectSignal(selectCurrentUser); constructor() { effect(() => { if (this.isAuthorizeMode()) { return; } const user = this.currentUser(); if (!user) { return; } const returnUrl = resolveSafeReturnUrl(this.route.snapshot.queryParamMap.get('returnUrl')); void this.router.navigateByUrl(returnUrl); }); } /** TrackBy function for server list rendering. */ trackById(_index: number, item: { id: string }) { return item.id; } ngOnInit(): void { const mode = this.route.snapshot.queryParamMap.get('mode'); const requestedServerId = this.route.snapshot.queryParamMap.get('serverId')?.trim(); this.isAuthorizeMode.set(isAuthorizeAuthMode(mode)); if (requestedServerId) { this.serverId = requestedServerId; } } /** Validate and submit the login form, then navigate to search on success. */ submit() { this.error.set(null); const sid = this.serverId || this.serversSvc.activeServer()?.id; this.auth.login({ username: this.username.trim(), password: this.password, serverId: sid }).subscribe({ next: async (resp) => { const serverUrl = this.auth.resolveServerUrlFor(sid); if (this.isAuthorizeMode()) { this.store.dispatch(UsersActions.authorizeSignalServer({ serverUrl, response: resp, provisioned: false })); const returnUrl = resolveSafeReturnUrl(this.route.snapshot.queryParamMap.get('returnUrl')); await this.router.navigateByUrl(returnUrl); return; } if (sid) { this.serversSvc.setActiveServer(sid); } const homeSignalServerUrl = this.serversSvc.servers().find((server) => server.id === sid)?.url ?? this.serversSvc.activeServer()?.url; const user: User = { id: resp.id, oderId: resp.id, username: resp.username, displayName: resp.displayName, status: 'online', role: 'member', joinedAt: Date.now(), homeSignalServerUrl }; this.store.dispatch(UsersActions.authenticateUser({ user, loginResponse: resp })); const outcome = await firstValueFrom(waitForAuthenticationOutcome(this.actions$)); if (outcome.kind === 'failure') { this.error.set(outcome.error); return; } const returnUrl = resolveSafeReturnUrl(this.route.snapshot.queryParamMap.get('returnUrl')); await this.router.navigateByUrl(returnUrl); }, error: (err) => { this.error.set(err?.error?.error || this.appI18n.instant('auth.login.failed')); } }); } /** Navigate to the registration page. */ goRegister() { this.router.navigate(['/register'], { queryParams: buildLoginReturnQueryParams(this.router.url, undefined, { mode: this.isAuthorizeMode() ? AUTH_MODE_AUTHORIZE : undefined, serverId: this.serverId }) }); } }