import { DatePipe } from '@angular/common'; import { ChangeDetectionStrategy, Component, DestroyRef, inject, signal } from '@angular/core'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { ActivatedRoute, RouterLink } from '@angular/router'; import { MatButtonModule } from '@angular/material/button'; import { MatCardModule } from '@angular/material/card'; import { MatIconModule } from '@angular/material/icon'; import { MatProgressSpinnerModule } from '@angular/material/progress-spinner'; import { switchMap, map, of } from 'rxjs'; import { ShowDetail, ShowPerformance, ShowsApiService } from '../services/shows-api.service'; @Component({ standalone: true, imports: [ DatePipe, RouterLink, MatButtonModule, MatCardModule, MatIconModule, MatProgressSpinnerModule, ], template: ` @if (isLoading()) { Loading show details... } @else if (errorMessage()) { error Could not load this show {{ errorMessage() }} Try again Back to shows } @else if (show()) { Show detail {{ show()!.title }} {{ show()!.description || show()!.summary }} @if (show()!.image_url) { } Upcoming performances Choose a performance to continue to the booking placeholder. Back to show list @if (performances().length === 0) { theaters No performances published yet This show is online, but there are no upcoming performances available right now. } @else { @for (performance of performances(); track performance.id) { {{ performance.starts_at | date: 'EEEE d MMMM, HH:mm' }} {{ performance.venue.name }}, {{ performance.venue.city }} Venue {{ performance.venue.name }} City {{ performance.venue.city }} Available seats {{ performance.available_seats }} @if (performance.booking_enabled) { Book this performance } @else { Booking unavailable } } } } `, styles: [` .page { max-width: 1080px; margin: 0 auto; } .page-header { display: grid; grid-template-columns: minmax(0, 1.2fr) minmax(280px, 420px); gap: 24px; align-items: start; margin-bottom: 28px; } .hero-copy { min-width: 0; } .eyebrow { margin: 0 0 10px; color: var(--azionelab-accent); text-transform: uppercase; font-size: 0.78rem; font-weight: 700; } h1 { margin: 0; font-size: clamp(2rem, 4vw, 3.2rem); } .supporting { margin: 14px 0 0; color: var(--azionelab-muted); line-height: 1.6; max-width: 64ch; } .hero-image-wrap { overflow: hidden; border-radius: 12px; border: 1px solid var(--azionelab-border); background: linear-gradient(135deg, rgba(207, 71, 51, 0.16), rgba(15, 22, 36, 0.08)), #f8f1ea; box-shadow: var(--azionelab-shadow); aspect-ratio: 4 / 5; } .hero-image { width: 100%; height: 100%; display: block; object-fit: cover; } .section { display: grid; gap: 20px; } .section-heading { display: flex; justify-content: space-between; gap: 20px; align-items: end; } .section-heading h2 { margin: 0 0 6px; font-size: 1.4rem; } .section-heading p { margin: 0; color: var(--azionelab-muted); } .performance-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 20px; } .performance-card, .status-card { border-radius: 8px; border: 1px solid var(--azionelab-border); background: var(--azionelab-surface); box-shadow: var(--azionelab-shadow); } .performance-card { min-height: 260px; } .performance-meta { display: grid; gap: 14px; margin: 0; } .performance-meta div { display: grid; gap: 2px; } .performance-meta dt { font-size: 0.78rem; font-weight: 700; text-transform: uppercase; color: var(--azionelab-muted); } .performance-meta dd { margin: 0; font-size: 0.98rem; } .status-panel, .status-copy { display: flex; align-items: center; gap: 16px; } .status-panel { min-height: 240px; justify-content: center; color: var(--azionelab-muted); } .status-copy { align-items: flex-start; } .status-copy h1, .status-copy h2 { margin: 0 0 8px; font-size: 1.2rem; } .status-copy p { margin: 0; color: var(--azionelab-muted); line-height: 1.6; } @media (max-width: 860px) { .page-header { grid-template-columns: 1fr; } .section-heading { align-items: flex-start; flex-direction: column; } } `], changeDetection: ChangeDetectionStrategy.OnPush, }) export class ShowDetailPlaceholderPageComponent { private readonly destroyRef = inject(DestroyRef); private readonly route = inject(ActivatedRoute); private readonly showsApi = inject(ShowsApiService); protected readonly show = signal(null); protected readonly performances = signal([]); protected readonly isLoading = signal(true); protected readonly errorMessage = signal(''); constructor() { this.loadShow(); } protected reload(): void { this.loadShow(); } private loadShow(): void { const slug = this.route.snapshot.paramMap.get('slug'); if (!slug) { this.errorMessage.set('The requested show is missing a valid identifier.'); this.show.set(null); this.performances.set([]); this.isLoading.set(false); return; } this.isLoading.set(true); this.errorMessage.set(''); this.showsApi.getShow(slug) .pipe( switchMap((show) => { if (show.performances) { return of({ show, performances: show.performances }); } return this.showsApi.listPerformancesForShow(slug).pipe( map(({ results }) => ({ show, performances: results })), ); }), takeUntilDestroyed(this.destroyRef), ) .subscribe({ next: ({ show, performances }) => { this.show.set(show); this.performances.set(performances); this.isLoading.set(false); }, error: () => { this.show.set(null); this.performances.set([]); this.errorMessage.set('Please try again in a moment.'); this.isLoading.set(false); }, }); } }
Loading show details...
{{ errorMessage() }}
Show detail
{{ show()!.description || show()!.summary }}
Choose a performance to continue to the booking placeholder.
This show is online, but there are no upcoming performances available right now.