Files
libra/docs/tasks/24-5.md
T

146 lines
9.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
id: 24-5
title: 24-5 Home Booking Card (Compact Booking and Day Strip)
status: QA Review
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.