docs: client-approved public-site redesign — PRD 1.4, UX spec v2.0, architecture §6.2, tasks 24-x Ready for Dev

Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
This commit is contained in:
Naser Mansour
2026-10-31 18:23:02 +02:00
parent caec396fa4
commit 3d1fe40be6
17 changed files with 1761 additions and 5 deletions
+145
View File
@@ -0,0 +1,145 @@
---
id: 24-5
title: 24-5 Home Booking Card (Compact Booking and Day Strip)
status: Ready for Dev
date: 2026-10-31
parent: 24
---
## Description
Add section 8 of the home page: a forest booking card that embeds the existing guest-booking
component in a one-screen "compact" mode, with a 14-day strip of bookable days. The booking rules
must not change: name, email and phone required; case summary required, minimum 20 characters;
the math captcha (`App\Services\CaptchaService`); one booking per email per day; slots from
`App\Services\AvailabilityService`; the existing confirm step (`showConfirmation`); and the
existing `submit()` that redirects to `route('booking.success')`. Logged-in visitors see the
existing logged-in message instead of the form.
### 1. Availability calendar — `resources/views/livewire/availability-calendar.blade.php`
- Add `public string $variant = 'month';` (settable from the tag: `variant="strip"`). The default
`month` must render exactly as today, because
`resources/views/livewire/client/consultations/book.blade.php` also uses this component.
- When `$variant === 'strip'`:
- Compute `$stripDays`: starting today, step one day at a time up to 60 days ahead and collect
the first 14 dates for which `AvailabilityService::getDateStatus($date)` returns
`'available'` or `'partial'` (the same two values `selectDate()` accepts today), each with
its `getAvailableSlots($date)`.
- On mount, select the first strip day (`$selectedDate`) and load its slots with the existing
`loadAvailableSlots()`.
- Render a day strip: `<div class="days" role="group" aria-label="{{ __('site.booking_day') }}">`
of `<button type="button" class="day" aria-pressed="true|false" wire:click="selectDate('Y-m-d')">`
with the weekday name (`translatedFormat('l')`) and `<b>` the day number. Then a slot row:
`<div class="slots" role="group" aria-label="{{ __('site.booking_time') }}">` of
`<button type="button" class="slot" dir="ltr" aria-pressed=…>` per slot, clicking calls the
existing `$parent.selectSlot('<date>', '<slot>')`, and the button for the parent's current
`selectedTime` on the selected date is pressed. Slot labels use the same time format as the
month view.
- No strip day available within 60 days → show `site.booking_none` (ar «لا توجد مواعيد
متاحة حالياً»; en "No times are available at the moment") in place of the strip and slots.
- CSS (in `resources/css/public.css`, scoped `body.site`; mockup `docs/ux-mockup.html` lines
256–265): `.days` flex, gap 8px, `overflow-x:auto`, `scrollbar-width:none`; `.day` 66×72px min,
radius 14px, `1.5px solid rgba(45,54,36,.15)`, white, .78rem muted, `<b>` display font 1.35rem
ink; hover gold border and lift 2px; `[aria-pressed=true]` forest background, `--gold-lt` text,
cream number, shadow `0 10px 22px -10px rgba(45,54,36,.8)`. `.slots` flex-wrap gap 8px;
`.slot` min-height 44px, padding 8px 16px, pill, same border, white; hover gold border;
`[aria-pressed=true]` gold fill, gold border, ink text.
### 2. Booking component — `resources/views/livewire/pages/booking.blade.php`
- Add `public bool $compact = false;` and accept it in `mount(bool $compact = false)` (keep the
existing mount logic). The `#[Layout]` attribute stays; it is ignored when the component is
embedded.
- When `$compact` is true, render (instead of the page markup) a
`<form class="book-r" wire:submit="showConfirm">` (mockup CSS lines 247–267: cream, padding
44px 44px 40px, margin 12px, radius 20px) containing, in order:
1. `.f2` two-column row: full name (`guestName`, `autocomplete="name"`) and phone
(`guestPhone`, `type="tel" dir="ltr" autocomplete="tel"`), labels from the existing
`booking.*` keys with ` *`;
2. email (`guestEmail`, `type="email" dir="ltr" autocomplete="email"`);
3. `<livewire:availability-calendar variant="strip" />` — the strip variant itself renders a
visible label `site.booking_day` (.85rem, weight 600, margin-bottom 6px) above `.days` and
`site.booking_time` above `.slots`;
4. case summary textarea (`problemSummary`, rows 3, label `booking.problem_summary` + ` *`,
help text `booking.problem_summary_help`);
5. the captcha question and input (`captchaAnswer`, `inputmode="numeric" dir="ltr"`,
max-width 140px) exactly as the page version renders it;
6. `<button type="submit" class="btn btn-gold">` `site.booking_submit`, full width;
7. `<p class="note">` `booking.limit_message`.
Validation errors render under each field as in the page version. Submitting without a
selected slot shows the existing `selectedTime` error.
- When `$compact` and `$showConfirmation` are true, the same `.book-r` panel shows the existing
confirmation summary (date, time, name, email, phone, summary) with the existing Confirm
(`submit`) and Back (`$set('showConfirmation', false)`) actions, styled `btn-gold` and
`btn-ghost`.
- When `$compact` and the visitor is logged in, the panel shows the existing
`booking.logged_in_title`, `booking.logged_in_message` and the `booking.go_to_dashboard` link
(`route('client.consultations.book')`) styled as `btn-gold`.
- Inputs and textarea (scoped `body.site .book-r`): min-height 48px, padding 12px 16px, radius
12px, `1.5px solid rgba(45,54,36,.18)`, white, `font:400 1rem var(--body)`; focus border
`--gold` and `box-shadow:0 0 0 4px rgba(166,137,102,.2)`; labels .85rem weight 600. Flux inputs
may be replaced by plain inputs in compact mode, keeping the same `wire:model` names.
- The non-compact (page) rendering is unchanged by this task.
### 3. Home page — `resources/views/livewire/pages/home.blade.php`
Replace `{{-- booking card: task 24-5 --}}` with
`<section id="book"><div class="wrap"><div class="book" data-reveal>` (mockup CSS lines 236–246:
forest, radius 28px, two columns `1fr 1.1fr`, one column ≤1100px, shadow
`0 50px 90px -40px rgba(30,34,25,.8)`), containing:
- `<div class="book-l on-dark">` (padding 56px 52px; 40px 24px ≤760px): `.eyebrow`
`site.art_booking` (colour `--gold-lt`), `<h2>` `site.booking_title` (cream), `<p>`
`site.booking_lede` (`--gold-lt`), `.contact` list — phone link `tel:+970599353502` with
`<span dir="ltr">{{ __('footer.phone') }}</span>`, email link `mailto:` `footer.email`, and the
address `footer.address`, each with the 20px gold line icon from mockup lines 517–519 — and
`<x-site.balance mode="finale" style="width:190px;height:190px;margin-top:22px" />`.
- `<livewire:pages.booking :compact="true" />`.
### Language keys — add to `lang/ar/site.php` and `lang/en/site.php`
| Key | ar | en |
|---|---|---|
| `art_booking` | المادة ٠٦ — الاستشارة | Article 06 — Consultation |
| `booking_title` | احجز استشارة | Book a consultation |
| `booking_lede` | اختر يوماً ووقتاً متاحَين، وأخبرنا باختصار عن قضيتك. نراجع كل طلب ونؤكّد الموعد بالبريد الإلكتروني — دون الحاجة إلى حساب. | Choose an available day and time and tell us briefly about your case. We review every request and confirm by email — no account needed. |
| `booking_day` | اليوم | Day |
| `booking_time` | الوقت | Time |
| `booking_submit` | اطلب هذا الموعد | Request this appointment |
| `booking_none` | لا توجد مواعيد متاحة حالياً | No times are available at the moment |
### Files
- Modify: `resources/views/livewire/availability-calendar.blade.php`,
`resources/views/livewire/pages/booking.blade.php`,
`resources/views/livewire/pages/home.blade.php`, `resources/css/public.css`,
`lang/ar/site.php`, `lang/en/site.php`.
- Do not modify `app/Services/AvailabilityService.php`, `app/Services/CaptchaService.php`,
`resources/views/livewire/client/consultations/book.blade.php`, or any migration.
## Acceptance Criteria
- [ ] `/` shows the booking card with id `book`: the forest panel (title, lede, phone, email,
address, finale balance) and the cream form with fields in the order name, phone, email,
day strip, slots, summary, captcha, submit, one-per-day note.
- [ ] The day strip lists up to 14 dates, all of which have at least one slot in
`AvailabilityService::getAvailableSlots()`; the first is pressed on load and its slots show;
clicking another day loads that day's slots.
- [ ] A guest who fills valid data, picks a slot, answers the captcha and confirms creates exactly
one guest consultation (same attributes as a booking made on `/booking`) and lands on
`/booking/success`.
- [ ] A summary of fewer than 20 characters, a wrong captcha answer, a missing slot, or a second
booking with the same email on the same day each produce the same error as on `/booking`,
and no consultation is created.
- [ ] Logged in as a client, `/` shows the logged-in message with a link to
`/client/consultations/book` and no form.
- [ ] `/booking` and `/client/consultations/book` render the month calendar as before this task
(`variant` defaults to `month`).
- [ ] The finale balance is tilted at the top of `/` and level when scrolled to the booking card at
the end of the page.
- [ ] At 390px the card is one column, the day strip scrolls horizontally inside the card, and the
page has no horizontal scroll.
## Sub-tasks
None.