diff --git a/frontend/src/app/pages/booking-placeholder-page.component.ts b/frontend/src/app/pages/booking-placeholder-page.component.ts index 6755bda..763bbfa 100644 --- a/frontend/src/app/pages/booking-placeholder-page.component.ts +++ b/frontend/src/app/pages/booking-placeholder-page.component.ts @@ -31,23 +31,37 @@ type ApiValidationErrors = Record; @if (isSuccess()) { -
- check_circle +
+
+ mark_email_read +

Reservation created

-

check your email

+

Check your email to confirm the booking and unlock the QR code for admission.

+
+ mail Open the confirmation email + verified Confirm your reservation +
} @else {
+
+ info +

We only ask for the essentials. Your seats are held only after email confirmation.

+
+
+ person Name @if (bookingForm.controls.name.touched && bookingForm.controls.name.hasError('required')) { @@ -56,8 +70,10 @@ type ApiValidationErrors = Record; + mail Email + We will send the confirmation link here. @if (bookingForm.controls.email.touched && bookingForm.controls.email.hasError('required')) { Email is required. } @@ -67,8 +83,10 @@ type ApiValidationErrors = Record; + group Number of seats + Enter the total number of guests in your party. @if (bookingForm.controls.partySize.touched && bookingForm.controls.partySize.hasError('required')) { Number of seats is required. } @@ -79,14 +97,21 @@ type ApiValidationErrors = Record;
@if (submitError()) { -

{{ submitError() }}

+
+ error +

{{ submitError() }}

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

Please check the highlighted details:

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

{{ message }}

} +
} @@ -96,7 +121,10 @@ type ApiValidationErrors = Record; Submitting... } @else { - Reserve + + confirmation_number + Reserve + } Back to shows @@ -138,10 +166,35 @@ type ApiValidationErrors = Record; } .content-card { - border-radius: 8px; + border-radius: 20px; border: 1px solid var(--azionelab-border); - background: var(--azionelab-surface); + background: var(--azionelab-surface-strong); box-shadow: var(--azionelab-shadow); + overflow: hidden; + } + + mat-card-content { + padding: 28px !important; + } + + .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 { @@ -153,18 +206,37 @@ type ApiValidationErrors = Record; width: 100%; } - .error-message, + .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; - color: #b3261e; line-height: 1.4; font-size: 0.92rem; } - .field-errors { + .message-title { + font-weight: 700; + margin-bottom: 6px !important; + } + + .field-errors > div { display: grid; gap: 6px; - margin-top: 10px; } .actions { @@ -183,23 +255,77 @@ type ApiValidationErrors = Record; gap: 8px; } - .status-copy { + .status-panel { display: flex; align-items: flex-start; - gap: 14px; + gap: 16px; + padding: 4px 0; } - .status-copy h2 { + .status-panel h2 { margin: 0 0 6px; } - .status-copy p { + .status-panel p { margin: 0; color: var(--azionelab-muted); + line-height: 1.55; } - .status-copy.success mat-icon { - color: #2e7d32; + .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) { + mat-card-content { + padding: 22px !important; + } + + .status-panel, + .message-panel, + .intro-note { + border-radius: 14px; + } } `], changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/frontend/src/app/pages/reservation-confirm-page.component.ts b/frontend/src/app/pages/reservation-confirm-page.component.ts index 013e329..cdd26c0 100644 --- a/frontend/src/app/pages/reservation-confirm-page.component.ts +++ b/frontend/src/app/pages/reservation-confirm-page.component.ts @@ -25,12 +25,13 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error' @if (state() === 'loading') { -
+

Confirming reservation...

@@ -40,28 +41,40 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error' } @if (state() === 'success' && confirmation()) { -
- check_circle +
+
+ verified +

Reservation confirmed

-

Your seats are confirmed. Present this QR code at check-in.

+

Your seats are confirmed. Present this QR code at check-in and keep the link handy if staff needs manual access.

+
+ qr_code_2 Ready for entry + theater_comedy See you at the performance +
@if (confirmation()!.qr_code_image) {
+

Your check-in QR code

Reservation QR code
} @if (confirmation()!.qr_code_url) { -

Check-in URL: {{ confirmation()!.qr_code_url }}

+
+ link +

Check-in URL: {{ confirmation()!.qr_code_url }}

+
} } @if (state() === 'invalid') { -
- error +
+
+ error +

Invalid confirmation link

This token is not valid. Please use the latest email confirmation link.

@@ -70,8 +83,10 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error' } @if (state() === 'expired') { -
- schedule +
+
+ schedule +

Confirmation link expired

This link has expired. Please create a new reservation.

@@ -80,8 +95,10 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error' } @if (state() === 'error') { -
- warning +
+
+ warning +

Could not confirm reservation

Please try again in a moment.

@@ -120,54 +137,195 @@ type ConfirmationState = 'loading' | 'success' | 'invalid' | 'expired' | 'error' font-size: clamp(2rem, 4vw, 3rem); } - .status-card { - border-radius: 8px; - border: 1px solid var(--azionelab-border); - background: var(--azionelab-surface); - box-shadow: var(--azionelab-shadow); + .supporting { + color: var(--azionelab-muted); + line-height: 1.6; + max-width: 58ch; + margin: 14px 0 0; } - .status-copy { + .status-card { + border-radius: 20px; + border: 1px solid var(--azionelab-border); + background: var(--azionelab-surface-strong); + box-shadow: var(--azionelab-shadow); + overflow: hidden; + } + + mat-card-content { + padding: 28px !important; + } + + mat-card-actions { + padding: 0 28px 24px !important; + gap: 8px; + } + + .status-panel { display: flex; align-items: flex-start; - gap: 14px; + gap: 16px; + padding: 20px; + border-radius: 18px; + border: 1px solid transparent; } - .status-copy h2 { + .status-panel h2 { margin: 0 0 6px; font-size: 1.2rem; } - .status-copy p { + .status-panel p { margin: 0; color: var(--azionelab-muted); line-height: 1.5; } - .status-copy.success mat-icon { - color: #2e7d32; + .status-panel.loading { + background: rgba(159, 47, 40, 0.04); + border-color: rgba(159, 47, 40, 0.1); + } + + .status-panel.success { + background: var(--azionelab-success-bg); + border-color: var(--azionelab-success-border); + } + + .status-panel.warning { + background: #fff7ea; + border-color: rgba(181, 126, 0, 0.15); + } + + .status-panel.error { + background: var(--azionelab-error-bg); + border-color: var(--azionelab-error-border); + } + + .status-icon { + display: grid; + place-items: center; + width: 52px; + height: 52px; + border-radius: 16px; + flex: 0 0 auto; + background: rgba(30, 27, 24, 0.06); + } + + .status-panel.success .status-icon { + background: rgba(46, 125, 50, 0.12); + } + + .status-panel.warning .status-icon { + background: rgba(181, 126, 0, 0.14); + } + + .status-panel.error .status-icon { + background: rgba(179, 38, 30, 0.12); + } + + .status-panel.success .status-icon mat-icon { + color: var(--azionelab-success-ink); + } + + .status-panel.warning .status-icon mat-icon { + color: #9b6c00; + } + + .status-panel.error .status-icon mat-icon { + color: var(--azionelab-error-ink); + } + + .success-points { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 14px; + } + + .success-points 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; + } + + .success-points mat-icon { + font-size: 18px; + width: 18px; + height: 18px; } .qr-panel { margin-top: 18px; - padding: 14px; - border-radius: 8px; + padding: 16px; + border-radius: 18px; border: 1px solid var(--azionelab-border); display: inline-block; background: white; } + .panel-label { + margin: 0 0 12px; + font-size: 0.88rem; + font-weight: 700; + color: var(--azionelab-muted); + text-transform: uppercase; + letter-spacing: 0.08em; + } + .qr-panel img { width: min(280px, 100%); height: auto; display: block; } - .meta { - margin: 14px 0 0; - word-break: break-word; + .meta-card { + display: flex; + align-items: flex-start; + gap: 10px; + margin-top: 16px; + padding: 14px 16px; + border-radius: 16px; + background: rgba(159, 47, 40, 0.05); color: var(--azionelab-muted); } + + .meta-card p { + margin: 0; + word-break: break-word; + } + + .meta-card a { + color: var(--azionelab-accent-strong); + } + + @media (max-width: 640px) { + mat-card-content { + padding: 22px !important; + } + + mat-card-actions { + padding: 0 22px 20px !important; + } + + .status-panel { + padding: 18px; + border-radius: 16px; + } + + .qr-panel { + width: 100%; + } + + .qr-panel img { + width: min(100%, 280px); + margin: 0 auto; + } + } `], changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/frontend/src/index.html b/frontend/src/index.html index 46bc0db..b2dfe80 100644 --- a/frontend/src/index.html +++ b/frontend/src/index.html @@ -5,6 +5,12 @@ AzioneLab + + + diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 87eff7e..6e568db 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -1,12 +1,19 @@ :root { --azionelab-bg: #f3eee6; --azionelab-surface: rgba(255, 255, 255, 0.78); + --azionelab-surface-strong: rgba(255, 255, 255, 0.92); --azionelab-ink: #1e1b18; --azionelab-muted: #645b53; --azionelab-accent: #9f2f28; --azionelab-accent-strong: #7f211c; --azionelab-border: rgba(30, 27, 24, 0.12); --azionelab-shadow: 0 18px 48px rgba(46, 28, 18, 0.12); + --azionelab-success-bg: #edf7ef; + --azionelab-success-ink: #1f5f2b; + --azionelab-success-border: rgba(46, 125, 50, 0.18); + --azionelab-error-bg: #fff3f1; + --azionelab-error-ink: #8b2a20; + --azionelab-error-border: rgba(179, 38, 30, 0.16); } * { @@ -16,7 +23,7 @@ html, body { margin: 0; min-height: 100%; - font-family: "Segoe UI", "Helvetica Neue", sans-serif; + font-family: "Manrope", "Segoe UI", sans-serif; color: var(--azionelab-ink); background: radial-gradient(circle at top right, rgba(159, 47, 40, 0.12), transparent 28%), @@ -28,6 +35,19 @@ body { min-height: 100vh; } +h1, h2, h3 { + font-family: "Fraunces", "Times New Roman", serif; + letter-spacing: -0.02em; +} + button, input, textarea { font: inherit; } + +.material-symbols-outlined { + font-variation-settings: + 'FILL' 0, + 'wght' 500, + 'GRAD' 0, + 'opsz' 24; +}