feat: apply azionelab branding to frontend

This commit is contained in:
2026-05-05 10:46:10 +02:00
parent cf4e9ec239
commit fbbb1ae5fe
9 changed files with 213 additions and 179 deletions
@@ -63,20 +63,20 @@ type BarcodeDetectorConstructor = new (options?: { formats?: string[] }) => Barc
template: `
<section class="page">
<header class="page-header">
<p class="eyebrow">Staff check-in</p>
<h1>Token validation</h1>
<p class="supporting">Enter a token manually or scan a QR code to preview admission data and confirm entrance.</p>
<p class="eyebrow">Accoglienza staff</p>
<h1>Verifica ingresso</h1>
<p class="supporting">Inserisci il token manualmente oppure inquadra il QR code per controllare i dati di accesso e confermare l'ingresso.</p>
</header>
<div class="checkin-grid">
<mat-card class="side-card">
<mat-card-content>
<p class="side-label">Front of house</p>
<h2>Designed for quick, low-friction arrivals.</h2>
<p class="side-label">Ingresso sala</p>
<h2>Uno strumento pensato per un'accoglienza rapida e chiara.</h2>
<ul class="side-list">
<li>Scan a QR code when a device camera is available.</li>
<li>Enter the token manually if scanning is not possible.</li>
<li>Confirm admission only after the preview data matches the guest.</li>
<li>Inquadra il QR code quando la fotocamera del dispositivo e' disponibile.</li>
<li>Inserisci il token manualmente se la scansione non e' possibile.</li>
<li>Conferma l'ingresso solo dopo aver verificato che i dati corrispondano alla prenotazione.</li>
</ul>
</mat-card-content>
</mat-card>
@@ -85,13 +85,13 @@ type BarcodeDetectorConstructor = new (options?: { formats?: string[] }) => Barc
<mat-card-content>
<section class="scanner-panel">
<div class="scanner-copy">
<h2>Camera scan</h2>
<p>Optional on supported browsers. If the QR contains a full check-in URL, the token is extracted automatically.</p>
<h2>Scansione con fotocamera</h2>
<p>Disponibile nei browser compatibili. Se il QR contiene l'intero link di check-in, il token viene estratto automaticamente.</p>
</div>
<div class="actions scanner-actions">
@if (cameraState() === 'active') {
<button mat-stroked-button type="button" (click)="stopScanner()">Stop camera</button>
<button mat-stroked-button type="button" (click)="stopScanner()">Ferma fotocamera</button>
} @else {
<button
mat-stroked-button
@@ -101,9 +101,9 @@ type BarcodeDetectorConstructor = new (options?: { formats?: string[] }) => Barc
>
@if (cameraState() === 'starting') {
<mat-progress-spinner mode="indeterminate" diameter="18"></mat-progress-spinner>
<span>Starting camera...</span>
<span>Avvio fotocamera...</span>
} @else {
<span>Use camera</span>
<span>Usa fotocamera</span>
}
</button>
}
@@ -123,10 +123,10 @@ type BarcodeDetectorConstructor = new (options?: { formats?: string[] }) => Barc
<form [formGroup]="tokenForm" (ngSubmit)="preview()" novalidate>
<mat-form-field appearance="outline" class="full-width">
<mat-label>Opaque token</mat-label>
<mat-label>Token opaco</mat-label>
<input matInput formControlName="token" autocomplete="off" />
@if (tokenForm.controls.token.touched && tokenForm.controls.token.hasError('required')) {
<mat-error>Token is required.</mat-error>
<mat-error>Il token e' obbligatorio.</mat-error>
}
</mat-form-field>
@@ -134,34 +134,34 @@ type BarcodeDetectorConstructor = new (options?: { formats?: string[] }) => Barc
<button mat-flat-button type="submit" [disabled]="isBusy()">
@if (state() === 'preview_loading') {
<mat-progress-spinner mode="indeterminate" diameter="18"></mat-progress-spinner>
<span>Validating...</span>
<span>Verifica in corso...</span>
} @else {
<span>Preview check-in</span>
<span>Anteprima check-in</span>
}
</button>
<a mat-button routerLink="/">Home</a>
<a mat-button routerLink="/shows">Shows</a>
<a mat-button routerLink="/">Inizio</a>
<a mat-button routerLink="/shows">Spettacoli</a>
</div>
</form>
@if (previewData() && shouldShowPreview()) {
<section class="preview-panel" aria-live="polite">
<h2>Admission preview</h2>
<h2>Anteprima accesso</h2>
<dl>
<div><dt>Show</dt><dd>{{ previewData()!.show_title }}</dd></div>
<div><dt>Venue</dt><dd>{{ previewData()!.venue_name }}</dd></div>
<div><dt>Starts at</dt><dd>{{ previewData()!.starts_at | date: 'EEEE d MMMM, HH:mm' }}</dd></div>
<div><dt>Party size</dt><dd>{{ previewData()!.party_size }}</dd></div>
<div><dt>Reservation</dt><dd>#{{ previewData()!.reservation_id }}</dd></div>
<div><dt>Spettacolo</dt><dd>{{ previewData()!.show_title }}</dd></div>
<div><dt>Spazio</dt><dd>{{ previewData()!.venue_name }}</dd></div>
<div><dt>Inizio</dt><dd>{{ previewData()!.starts_at | date: 'EEEE d MMMM, HH:mm' }}</dd></div>
<div><dt>Posti</dt><dd>{{ previewData()!.party_size }}</dd></div>
<div><dt>Prenotazione</dt><dd>#{{ previewData()!.reservation_id }}</dd></div>
</dl>
<button mat-flat-button type="button" (click)="confirm()" [disabled]="isBusy() || state() === 'confirm_success'">
@if (state() === 'confirm_loading') {
<mat-progress-spinner mode="indeterminate" diameter="18"></mat-progress-spinner>
<span>Confirming...</span>
<span>Conferma in corso...</span>
} @else if (state() === 'confirm_success') {
<span>Checked in</span>
<span>Ingresso registrato</span>
} @else {
<span>Confirm check-in</span>
<span>Conferma ingresso</span>
}
</button>
</section>
@@ -169,24 +169,24 @@ type BarcodeDetectorConstructor = new (options?: { formats?: string[] }) => Barc
@if (state() === 'confirm_success' && confirmData()) {
<p class="success-message" aria-live="polite">
Check-in confirmed at {{ confirmData()!.checked_in_at | date: 'HH:mm' }}.
Ingresso confermato alle {{ confirmData()!.checked_in_at | date: 'HH:mm' }}.
</p>
}
@if (state() === 'invalid_token') {
<p class="error-message" aria-live="assertive">Invalid token.</p>
<p class="error-message" aria-live="assertive">Token non valido.</p>
}
@if (state() === 'pending_reservation') {
<p class="error-message" aria-live="assertive">Reservation is still pending confirmation.</p>
<p class="error-message" aria-live="assertive">La prenotazione non e' ancora confermata.</p>
}
@if (state() === 'already_checked_in') {
<p class="error-message" aria-live="assertive">This reservation is already checked in.</p>
<p class="error-message" aria-live="assertive">Questa prenotazione risulta gia' registrata all'ingresso.</p>
}
@if (state() === 'unauthorized') {
<p class="error-message" aria-live="assertive">You are not authorized. Log into <code>/admin</code> with a staff account, let the page reload with that session, then retry this check-in.</p>
<p class="error-message" aria-live="assertive">Non sei autorizzato. Accedi a <code>/admin</code> con un account staff, lascia ricaricare la pagina con quella sessione e poi riprova.</p>
}
@if (state() === 'error') {
<p class="error-message" aria-live="assertive">Something went wrong. Please try again.</p>
<p class="error-message" aria-live="assertive">Qualcosa non ha funzionato. Riprova.</p>
}
</mat-card-content>
</mat-card>
@@ -388,8 +388,8 @@ export class CheckInPlaceholderPageComponent {
protected readonly cameraState = signal<CameraState>(this.scannerSupported ? 'ready' : 'unsupported');
protected readonly cameraMessage = signal(
this.scannerSupported
? 'Open the camera to scan a QR code, or keep using manual token entry.'
: 'Camera scanning is not available in this browser. Manual token entry still works.',
? 'Apri la fotocamera per scansionare un QR code, oppure continua con l\'inserimento manuale del token.'
: 'La scansione con fotocamera non e\' disponibile in questo browser. Puoi comunque inserire il token manualmente.',
);
constructor() {
@@ -452,13 +452,13 @@ export class CheckInPlaceholderPageComponent {
protected async startScanner(): Promise<void> {
if (!this.scannerSupported || !this.barcodeDetectorCtor) {
this.cameraState.set('unsupported');
this.cameraMessage.set('Camera scanning is not available in this browser. Manual token entry still works.');
this.cameraMessage.set('La scansione con fotocamera non e\' disponibile in questo browser. Puoi comunque inserire il token manualmente.');
return;
}
this.stopScanner();
this.cameraState.set('starting');
this.cameraMessage.set('Starting camera...');
this.cameraMessage.set('Avvio della fotocamera in corso...');
try {
this.scannerStream = await navigator.mediaDevices.getUserMedia({
@@ -468,19 +468,19 @@ export class CheckInPlaceholderPageComponent {
this.detector = new this.barcodeDetectorCtor({ formats: ['qr_code'] });
this.cameraState.set('active');
this.cameraMessage.set('Point the camera at the visitor QR code.');
this.cameraMessage.set('Inquadra il QR code del visitatore.');
this.scheduleScan();
} catch (error) {
this.stopScanner();
if (error instanceof DOMException && (error.name === 'NotAllowedError' || error.name === 'SecurityError')) {
this.cameraState.set('denied');
this.cameraMessage.set('Camera access was denied. You can continue with manual token entry.');
this.cameraMessage.set('L\'accesso alla fotocamera e\' stato negato. Puoi continuare con l\'inserimento manuale del token.');
return;
}
this.cameraState.set('error');
this.cameraMessage.set('Could not start the camera. You can continue with manual token entry.');
this.cameraMessage.set('Non siamo riusciti ad avviare la fotocamera. Puoi continuare con l\'inserimento manuale del token.');
}
}
@@ -507,7 +507,7 @@ export class CheckInPlaceholderPageComponent {
if (this.scannerSupported && this.cameraState() === 'active') {
this.cameraState.set('ready');
this.cameraMessage.set('Camera stopped. You can scan again or continue with manual token entry.');
this.cameraMessage.set('Fotocamera fermata. Puoi riavviare la scansione oppure continuare con l\'inserimento manuale del token.');
}
}
@@ -559,7 +559,7 @@ export class CheckInPlaceholderPageComponent {
const context = canvas.getContext('2d');
if (!context) {
this.cameraState.set('error');
this.cameraMessage.set('Camera scan is not available right now. Please enter the token manually.');
this.cameraMessage.set('La scansione non e\' disponibile in questo momento. Inserisci il token manualmente.');
this.stopScanner();
return;
}
@@ -578,14 +578,14 @@ export class CheckInPlaceholderPageComponent {
if (token) {
this.tokenForm.controls.token.setValue(token);
this.tokenForm.controls.token.markAsTouched();
this.cameraMessage.set('QR captured. Validating token...');
this.cameraMessage.set('QR acquisito. Verifica del token in corso...');
this.stopScanner();
this.preview();
return;
}
} catch {
this.cameraState.set('error');
this.cameraMessage.set('Camera scan failed. Please enter the token manually.');
this.cameraMessage.set('La scansione non e\' andata a buon fine. Inserisci il token manualmente.');
this.stopScanner();
return;
} finally {