import { ChangeDetectionStrategy, Component, DestroyRef, inject, signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { FormBuilder, ReactiveFormsModule, Validators } from '@angular/forms'; import { ActivatedRoute, RouterLink } from '@angular/router'; import { HttpErrorResponse } from '@angular/common/http'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatFormFieldModule } from '@angular/material/form-field'; import { MatIconModule } from '@angular/material/icon'; import { MatInputModule } from '@angular/material/input'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { ReservationCreatePayload, ShowsApiService } from '../services/shows-api.service'; type ApiValidationErrors = Record; @Component({ standalone: true, imports: [ ReactiveFormsModule, RouterLink, MatButtonModule, MatCardModule, MatFormFieldModule, MatIconModule, MatInputModule, MatProgressSpinnerModule, ], template: `

Come funziona

Ti chiediamo pochi dati e ti accompagniamo fino alla conferma.

  • Riceverai un link di conferma all'indirizzo email che inserisci.
  • La disponibilita' viene controllata prima della conferma finale.
  • Dopo la conferma avrai il tuo QR code per l'ingresso.
@if (isSuccess()) {
mark_email_read

La tua richiesta e' partita

Controlla la tua email: con un ultimo passaggio potrai confermare la prenotazione e ricevere il QR code per l'ingresso.

mail Apri l'email che ti abbiamo inviato verified Conferma i posti con un tocco
} @else {
info

Ti chiediamo solo il necessario. La conferma via email ci aiuta a tenere la disponibilita' chiara per tutti.

person Nome @if (bookingForm.controls.name.touched && bookingForm.controls.name.hasError('required')) { Il nome e' obbligatorio. } mail Email Qui arrivera' il link per confermare la tua richiesta. @if (bookingForm.controls.email.touched && bookingForm.controls.email.hasError('required')) { L'email e' obbligatoria. } @if (bookingForm.controls.email.touched && bookingForm.controls.email.hasError('email')) { Inserisci un indirizzo email valido. } group Numero di posti Indica quante persone desideri includere nella prenotazione. @if (bookingForm.controls.partySize.touched && bookingForm.controls.partySize.hasError('required')) { Il numero di posti e' obbligatorio. } @if (bookingForm.controls.partySize.touched && bookingForm.controls.partySize.hasError('min')) { Devi richiedere almeno 1 posto. }
@if (submitError()) {
error

{{ submitError() }}

} @if (fieldErrors().length > 0) {
warning

Controlla i dati evidenziati:

@for (message of fieldErrors(); track message) {

{{ message }}

}
}
Torna agli spettacoli
}
`, styles: [` .page-header { margin-bottom: 26px; } .supporting { max-width: 56ch; margin: 14px 0 0; } .booking-grid { display: grid; grid-template-columns: minmax(0, 320px) minmax(0, 1fr); gap: 20px; align-items: start; } .summary-card, .content-card { border-radius: var(--azionelab-radius-lg); border: 1px solid var(--azionelab-border); background: var(--azionelab-surface-strong); box-shadow: var(--azionelab-shadow); overflow: hidden; } .summary-card { background: linear-gradient(180deg, rgba(255, 252, 248, 0.98), rgba(247, 238, 227, 0.94)); } mat-card-content { padding: 28px !important; } .summary-label { margin: 0 0 10px; font-size: 0.78rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: var(--azionelab-accent); } .summary-card h2 { margin: 0; max-width: 14ch; } .summary-list { display: grid; gap: 12px; margin: 18px 0 0; padding-left: 18px; color: var(--azionelab-ink-soft); line-height: 1.6; } .intro-note { display: flex; align-items: flex-start; gap: 12px; margin-bottom: 20px; padding: 14px 16px; border-radius: 16px; background: rgba(159, 47, 40, 0.06); color: var(--azionelab-muted); } .intro-note p { margin: 0; line-height: 1.55; } .intro-note mat-icon { color: var(--azionelab-accent); } .form-grid { display: grid; gap: 14px; } mat-form-field { width: 100%; } .message-panel { display: flex; align-items: flex-start; gap: 12px; margin-top: 14px; padding: 14px 16px; border-radius: 16px; border: 1px solid transparent; } .message-panel.error { background: var(--azionelab-error-bg); border-color: var(--azionelab-error-border); color: var(--azionelab-error-ink); } .message-panel p, .field-errors p { margin: 0; line-height: 1.4; font-size: 0.92rem; } .message-title { font-weight: 700; margin-bottom: 6px !important; } .field-errors > div { display: grid; gap: 6px; } .actions { display: flex; align-items: center; gap: 12px; margin-top: 18px; flex-wrap: wrap; } .actions button[mat-flat-button] { min-width: 130px; display: inline-flex; align-items: center; justify-content: center; gap: 8px; } .status-panel { display: flex; align-items: flex-start; gap: 16px; padding: 4px 0; } .status-panel h2 { margin: 0 0 6px; } .status-panel p { margin: 0; color: var(--azionelab-muted); line-height: 1.55; } .status-panel.success { padding: 22px; border-radius: 18px; background: var(--azionelab-success-bg); border: 1px solid var(--azionelab-success-border); } .status-icon { display: grid; place-items: center; width: 52px; height: 52px; border-radius: 16px; background: rgba(46, 125, 50, 0.12); } .status-icon mat-icon { color: var(--azionelab-success-ink); } .status-steps { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 14px; } .status-steps span { display: inline-flex; align-items: center; gap: 6px; padding: 8px 12px; border-radius: 999px; background: rgba(255, 255, 255, 0.72); color: var(--azionelab-success-ink); font-size: 0.92rem; } .status-steps mat-icon { font-size: 18px; width: 18px; height: 18px; } @media (max-width: 640px) { .booking-grid { grid-template-columns: 1fr; } mat-card-content { padding: 22px !important; } .status-panel, .message-panel, .intro-note { border-radius: 14px; } } `], changeDetection: ChangeDetectionStrategy.OnPush, }) export class BookingPlaceholderPageComponent { private readonly destroyRef = inject(DestroyRef); private readonly formBuilder = inject(FormBuilder); private readonly route = inject(ActivatedRoute); private readonly showsApi = inject(ShowsApiService); protected readonly performanceId = this.route.snapshot.paramMap.get('id') ?? ''; protected readonly isSubmitting = signal(false); protected readonly isSuccess = signal(false); protected readonly submitError = signal(''); protected readonly fieldErrors = signal([]); protected readonly bookingForm = this.formBuilder.nonNullable.group({ name: ['', [Validators.required, Validators.maxLength(200)]], email: ['', [Validators.required, Validators.email]], partySize: [1, [Validators.required, Validators.min(1)]], }); protected submit(): void { this.submitError.set(''); this.fieldErrors.set([]); if (this.bookingForm.invalid) { this.bookingForm.markAllAsTouched(); return; } const payload: ReservationCreatePayload = { name: this.bookingForm.controls.name.value.trim(), email: this.bookingForm.controls.email.value.trim(), party_size: this.bookingForm.controls.partySize.value, }; this.isSubmitting.set(true); this.showsApi.createReservation(this.performanceId, payload) .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: () => { this.isSubmitting.set(false); this.isSuccess.set(true); this.bookingForm.disable(); }, error: (error: HttpErrorResponse) => { this.isSubmitting.set(false); if (error.status === 400 && error.error && typeof error.error === 'object') { this.fieldErrors.set(this.flattenValidationErrors(error.error as ApiValidationErrors)); return; } this.submitError.set('Non siamo riusciti a inviare la richiesta in questo momento. Riprova tra poco.'); }, }); } private flattenValidationErrors(errors: ApiValidationErrors): string[] { return Object.entries(errors).flatMap(([field, messages]) => { const labelMap: Record = { name: 'nome', email: 'email', party_size: 'numero di posti', }; const label = labelMap[field] ?? field; return messages.map((message) => `${label}: ${this.translateValidationMessage(message)}`); }); } private translateValidationMessage(message: string): string { const translations: Record = { 'This field is required.': 'questo campo e\' obbligatorio.', 'Enter a valid email address.': 'inserisci un indirizzo email valido.', 'Ensure this value is greater than or equal to 1.': 'inserisci un valore maggiore o uguale a 1.', }; return translations[message] ?? message; } }