RSS Git Download  Clone
Raw Blame History 6kB 151 lines
import { Component, Inject, ChangeDetectionStrategy } from '@angular/core';
import { CommonModule } from '@angular/common';
import { FormsModule } from '@angular/forms';
import { MatFormFieldModule } from '@angular/material/form-field';
import { MatInputModule } from '@angular/material/input';
import { MatButtonModule } from '@angular/material/button';
import { MatIconModule } from '@angular/material/icon';
import { MatToolbarModule } from '@angular/material/toolbar';
import { MatMenuModule } from '@angular/material/menu';

import { I18nService } from '../services/i18n.service';
import { AuthService } from '../services/auth.service';

@Component({
    selector: 'p3xr-login',
    standalone: true,
    imports: [
        CommonModule,
        FormsModule,
        MatFormFieldModule,
        MatInputModule,
        MatButtonModule,
        MatIconModule,
        MatToolbarModule,
        MatMenuModule,
    ],
    changeDetection: ChangeDetectionStrategy.OnPush,
    template: `
    <div class="p3xr-login-dialog-wrapper">
        <div class="p3xr-login-dialog">
            <form (ngSubmit)="onLogin()" novalidate>
                <mat-toolbar class="p3xr-dialog-toolbar p3xr-mat-layout-strong">
                    <span class="p3xr-layout-spacer"></span>
                    <button mat-button type="button" [matMenuTriggerFor]="guiMenu"
                            style="color: rgba(255,255,255,0.87);">
                        GUI
                    </button>
                    <mat-menu #guiMenu>
                        <button mat-menu-item (click)="switchGui('ng')"
                                [class.p3xr-mat-menu-item-selected]="currentGui === 'ng'">
                            Angular
                        </button>
                        <button mat-menu-item (click)="switchGui('react')"
                                [class.p3xr-mat-menu-item-selected]="currentGui === 'react'">
                            React
                        </button>
                    </mat-menu>
                </mat-toolbar>

                <div class="p3xr-dialog-content">
                    <mat-form-field class="full-width">
                        <mat-label>{{ i18n.strings().form?.connection?.label?.username || 'Username' }}</mat-label>
                        <input matInput name="username" type="text"
                               [(ngModel)]="username" autocomplete="username" />
                    </mat-form-field>

                    <mat-form-field class="full-width">
                        <mat-label>{{ i18n.strings().form?.connection?.label?.password || 'Password' }}</mat-label>
                        <input matInput name="password"
                               [type]="hidePassword ? 'password' : 'text'"
                               [(ngModel)]="password" autocomplete="current-password"
                               (keydown.enter)="onLogin()" />
                        <button mat-icon-button matSuffix type="button"
                                (click)="hidePassword = !hidePassword">
                            <mat-icon>{{ hidePassword ? 'visibility_off' : 'visibility' }}</mat-icon>
                        </button>
                    </mat-form-field>

                    @if (auth.loginError()) {
                        <div class="p3xr-login-error">
                            {{ getErrorMessage(auth.loginError()) }}
                        </div>
                    }
                </div>

                <div class="p3xr-dialog-actions" style="display: flex; justify-content: flex-end; padding: 8px; gap: 8px;">
                    <button mat-raised-button class="btn-primary" type="submit"
                            [disabled]="loading || !username || !password">
                        <mat-icon>done</mat-icon>
                        {{ i18n.strings().intention?.ok || 'Login' }}
                    </button>
                </div>
            </form>
        </div>
    </div>
    `,
    styles: [`
    .p3xr-login-dialog-wrapper {
        display: flex;
        align-items: center;
        justify-content: center;
        min-height: calc(100vh - 96px);
    }
    .p3xr-login-dialog {
        width: 400px;
        max-width: calc(100vw - 32px);
        border-radius: 4px;
        overflow: hidden;
        box-shadow: 0 11px 15px -7px rgba(0,0,0,.2),
                    0 24px 38px 3px rgba(0,0,0,.14),
                    0 9px 46px 8px rgba(0,0,0,.12);
    }
    .full-width { width: 100%; }
    .p3xr-login-error {
        color: #f44336;
        font-size: 13px;
        margin: 4px 0 8px;
    }
    .p3xr-layout-spacer { flex: 1 1 auto; }
    `],
})
export class LoginComponent {
    username = '';
    password = '';
    loading = false;
    hidePassword = true;
    currentGui = 'ng';

    constructor(
        @Inject(I18nService) readonly i18n: I18nService,
        @Inject(AuthService) readonly auth: AuthService,
    ) {
        try {
            this.currentGui = localStorage.getItem('p3xr-frontend') || 'ng';
        } catch {}
    }

    async onLogin(): Promise<void> {
        if (this.loading || !this.username || !this.password) return;
        this.loading = true;
        const success = await this.auth.login(this.username, this.password);
        if (success) {
            location.reload();
        }
        this.loading = false;
    }

    switchGui(gui: string): void {
        this.currentGui = gui;
        try {
            localStorage.setItem('p3xr-frontend', gui);
        } catch {}
        location.href = gui === 'react' ? '/react/' : '/ng/';
    }

    getErrorMessage(error: string): string {
        const strings = this.i18n.strings();
        return strings?.confirm?.invalidCredentials || 'Invalid username or password.';
    }
}