3d1fe40be6
Co-Authored-By: Claude Haiku 5.5 <noreply@anthropic.com>
146 lines
9.2 KiB
Markdown
146 lines
9.2 KiB
Markdown
---
|
||
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.
|