Deploy stack su Proxmox / deploy (push) Failing after 3s
Monorepo npm con apps/public (Angular SSR), apps/private (Angular SPA), apps/api (Fastify), packages/shared e packages/styles; Medusa 2.21 in medusa/; infrastruttura Docker (Postgres PostGIS+pgvector, Redis, Caddy, backup) e workflow Gitea Actions per il deploy via SSH a ogni push su master. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018jQyT6dR9czzGwqb7FkG6u
105 lines
3.8 KiB
TypeScript
105 lines
3.8 KiB
TypeScript
import { ChangeDetectionStrategy, Component, inject, input, signal } from '@angular/core'
|
|
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms'
|
|
import { Title } from '@angular/platform-browser'
|
|
import { RouterLink } from '@angular/router'
|
|
import { ApiError, toApiError } from '../../core/api.service'
|
|
import { AuthStore } from '../../core/auth.store'
|
|
import { authErrorMessage } from '../../shared/form-errors'
|
|
import { FormField } from '../../shared/form-field'
|
|
|
|
/**
|
|
* Login. `?redirect=` (validato in core/redirect.ts) indica dove tornare: una pagina dell'area riservata
|
|
* o del sito pubblico (es. `/checkout`). `?scaduta=1` = sessione persa durante l'uso;
|
|
* `?account=disattivato` = account appena disattivato dall'utente.
|
|
*/
|
|
@Component({
|
|
selector: 'app-login',
|
|
imports: [ReactiveFormsModule, RouterLink, FormField],
|
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
template: `
|
|
<h1>Accedi</h1>
|
|
@if (account() === 'disattivato') {
|
|
<p class="alert alert--success" role="status">
|
|
Il tuo account è stato disattivato e la richiesta di cancellazione è stata inviata. Grazie per averci scelto.
|
|
</p>
|
|
}
|
|
@if (scaduta()) {
|
|
<p class="alert" role="status">La sessione è scaduta: accedi di nuovo per continuare.</p>
|
|
}
|
|
@if (error(); as message) {
|
|
<p class="alert alert--error" role="alert">{{ message }}</p>
|
|
}
|
|
<form [formGroup]="form" (ngSubmit)="submit()" novalidate>
|
|
<div class="form-grid single">
|
|
<app-form-field [control]="form.controls.email" label="Email" type="email" autocomplete="email" [submitted]="submitted()" />
|
|
<app-form-field
|
|
[control]="form.controls.password"
|
|
label="Password"
|
|
type="password"
|
|
autocomplete="current-password"
|
|
[submitted]="submitted()"
|
|
/>
|
|
</div>
|
|
<p class="forgot"><a routerLink="/forgot-password">Password dimenticata?</a></p>
|
|
<button type="submit" class="btn btn--primary btn--block" [disabled]="busy()">
|
|
{{ busy() ? 'Accesso in corso…' : 'Accedi' }}
|
|
</button>
|
|
</form>
|
|
<p class="switch">
|
|
Non hai un account?
|
|
<a routerLink="/registrazione" [queryParams]="{ redirect: redirect() || null }">Registrati</a>
|
|
</p>
|
|
`,
|
|
styles: `
|
|
.single {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
.forgot {
|
|
margin: var(--space-2) 0 var(--space-5);
|
|
text-align: right;
|
|
}
|
|
.switch {
|
|
margin: var(--space-5) 0 0;
|
|
text-align: center;
|
|
}
|
|
`,
|
|
})
|
|
export default class Login {
|
|
private readonly auth = inject(AuthStore)
|
|
|
|
readonly redirect = input<string>()
|
|
readonly scaduta = input<string>()
|
|
/** `disattivato` dopo la richiesta di cancellazione dell'account. */
|
|
readonly account = input<string>()
|
|
|
|
protected readonly form = new FormGroup({
|
|
email: new FormControl('', { nonNullable: true, validators: [Validators.required, Validators.email] }),
|
|
password: new FormControl('', { nonNullable: true, validators: [Validators.required] }),
|
|
})
|
|
protected readonly submitted = signal(false)
|
|
protected readonly busy = signal(false)
|
|
protected readonly error = signal<string | null>(null)
|
|
|
|
constructor() {
|
|
inject(Title).setTitle('Accedi | Area riservata ITI TECHNOLOGY')
|
|
}
|
|
|
|
protected async submit(): Promise<void> {
|
|
this.submitted.set(true)
|
|
this.error.set(null)
|
|
if (this.form.invalid || this.busy()) return
|
|
this.busy.set(true)
|
|
try {
|
|
const { email, password } = this.form.getRawValue()
|
|
await this.auth.login(email.trim(), password)
|
|
await this.auth.navigateAfterLogin(this.redirect() ?? null)
|
|
} catch (err) {
|
|
const error = err instanceof ApiError ? err : toApiError(err)
|
|
this.error.set(error.code === 'INVALID_CREDENTIALS' ? 'Email o password non corretti.' : authErrorMessage(error))
|
|
this.form.controls.password.reset()
|
|
} finally {
|
|
this.busy.set(false)
|
|
}
|
|
}
|
|
}
|