Initial commit: TravelHub multi-tenant flight booking platform
Angular + Go + PostgreSQL sample with strict per-tenant isolation. - Backend: Go API (basic auth, tenant-scoped repository layer, pgx pool) - Frontend: Angular 22 SPA (search, checkout, bookings, dashboard) - Database: schema, seed data and demo bookings migrations - Tests: 311 across Go (integration), Angular (unit) and Playwright (e2e), plus a generated evidence report with PDF export - Security: whitebox VAPT report under security/ Secrets (.env, tokens, DB credentials) are git-ignored. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>Bookings</h1>
|
||||
<p class="subtitle">Every reservation sold by your agency.</p>
|
||||
</div>
|
||||
<a routerLink="/search" class="btn btn-primary btn-sm">+ New booking</a>
|
||||
</div>
|
||||
|
||||
<div class="filters" role="group" aria-label="Filter by status">
|
||||
<button
|
||||
type="button"
|
||||
class="filter"
|
||||
[class.active]="status() === ''"
|
||||
(click)="filterBy('')"
|
||||
>
|
||||
All
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="filter"
|
||||
[class.active]="status() === 'confirmed'"
|
||||
(click)="filterBy('confirmed')"
|
||||
>
|
||||
Confirmed
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="filter"
|
||||
[class.active]="status() === 'cancelled'"
|
||||
(click)="filterBy('cancelled')"
|
||||
>
|
||||
Cancelled
|
||||
</button>
|
||||
</div>
|
||||
|
||||
@if (error()) {
|
||||
<div class="alert alert-error" role="alert">{{ error() }}</div>
|
||||
}
|
||||
|
||||
@if (loading()) {
|
||||
<div class="card card-pad"><div class="skeleton" style="height: 260px"></div></div>
|
||||
} @else if (bookings().length === 0) {
|
||||
<div class="card empty">
|
||||
<h3>No bookings yet</h3>
|
||||
<p>Search for a flight to create the first reservation.</p>
|
||||
<p style="margin-top: 14px">
|
||||
<a routerLink="/search" class="btn btn-primary btn-sm">Search flights</a>
|
||||
</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="card table-wrap">
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Reference</th>
|
||||
<th>Route</th>
|
||||
<th>Departure</th>
|
||||
<th>Travellers</th>
|
||||
<th>Total</th>
|
||||
<th>Status</th>
|
||||
<th>Booked by</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@for (booking of bookings(); track booking.id) {
|
||||
<tr>
|
||||
<td><span class="pnr">{{ booking.bookingReference }}</span></td>
|
||||
<td>
|
||||
<span class="route">
|
||||
{{ booking.flight.origin.iataCode }} → {{ booking.flight.destination.iataCode }}
|
||||
</span>
|
||||
<span class="route-meta">
|
||||
{{ booking.flight.airline.iataCode }} {{ booking.flight.flightNumber }}
|
||||
</span>
|
||||
</td>
|
||||
<td class="tabular">{{ booking.flight.departureTime | date: 'd MMM y, HH:mm' }}</td>
|
||||
<td class="tabular">{{ booking.passengerCount }}</td>
|
||||
<td class="tabular">
|
||||
{{ booking.totalAmount | currency: booking.currency : 'symbol' : '1.2-2' }}
|
||||
</td>
|
||||
<td>
|
||||
<span class="badge" [class]="'badge-' + booking.status">{{ booking.status }}</span>
|
||||
</td>
|
||||
<td class="muted">{{ booking.bookedBy }}</td>
|
||||
<td class="right">
|
||||
<a [routerLink]="['/bookings', booking.id]" class="btn btn-ghost btn-sm">View</a>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@if (canPrev() || canNext()) {
|
||||
<nav class="pager" aria-label="Pagination">
|
||||
<button type="button" class="btn btn-ghost btn-sm" [disabled]="!canPrev()" (click)="prevPage()">
|
||||
← Previous
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost btn-sm" [disabled]="!canNext()" (click)="nextPage()">
|
||||
Next →
|
||||
</button>
|
||||
</nav>
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user