24-6: booking page and booking success — implemented, QA Review
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -239,13 +239,13 @@ new class extends Component
|
||||
@else
|
||||
<!-- Calendar Header -->
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<flux:button size="sm" wire:click="previousMonth" variant="outline">
|
||||
<flux:button size="sm" wire:click="previousMonth" variant="outline" class="site-cal-nav">
|
||||
<flux:icon name="chevron-{{ app()->getLocale() === 'ar' ? 'right' : 'left' }}" class="size-5" />
|
||||
</flux:button>
|
||||
|
||||
<flux:heading size="lg" class="text-body">{{ $monthName }}</flux:heading>
|
||||
|
||||
<flux:button size="sm" wire:click="nextMonth" variant="outline">
|
||||
<flux:button size="sm" wire:click="nextMonth" variant="outline" class="site-cal-nav">
|
||||
<flux:icon name="chevron-{{ app()->getLocale() === 'ar' ? 'left' : 'right' }}" class="size-5" />
|
||||
</flux:button>
|
||||
</div>
|
||||
@@ -268,7 +268,10 @@ new class extends Component
|
||||
<button
|
||||
wire:click="selectDate('{{ $dayData['date'] }}')"
|
||||
@class([
|
||||
'calendar-cell rounded-lg text-center transition-colors font-medium min-h-[44px] text-white',
|
||||
'calendar-cell site-cal-day rounded-lg text-center transition-colors font-medium min-h-[44px] text-white',
|
||||
'is-available' => in_array($dayData['status'], ['available', 'partial']),
|
||||
'is-selected' => $selectedDate === $dayData['date'],
|
||||
'is-unavailable' => in_array($dayData['status'], ['past', 'closed', 'blocked', 'full', 'user_booked']),
|
||||
'bg-[#4A5D23]/70 hover:bg-[#4A5D23]/85' => $dayData['status'] === 'available',
|
||||
'bg-[#D4A84B]/70 hover:bg-[#D4A84B]/85' => $dayData['status'] === 'partial',
|
||||
'bg-sky-400/50 cursor-not-allowed' => $dayData['status'] === 'user_booked',
|
||||
|
||||
@@ -8,20 +8,13 @@ new #[Layout('components.layouts.public')] class extends Component
|
||||
//
|
||||
}; ?>
|
||||
|
||||
<div class="wrap py-8">
|
||||
<div class="max-w-2xl mx-auto py-16 px-4 text-center">
|
||||
<flux:icon name="check-circle" class="w-16 h-16 mx-auto text-green-500 mb-6" />
|
||||
|
||||
<flux:heading size="xl" class="mb-4 text-body">
|
||||
{{ __('booking.success_title') }}
|
||||
</flux:heading>
|
||||
|
||||
<p class="text-zinc-600 mb-6">
|
||||
{{ __('booking.success_message') }}
|
||||
</p>
|
||||
|
||||
<flux:button href="{{ route('home') }}">
|
||||
{{ __('navigation.home') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
</div>
|
||||
<section class="sec">
|
||||
<div class="wrap">
|
||||
<div class="success-card">
|
||||
<x-site.balance mode="static" style="width:120px;height:120px;margin:0 auto 18px" />
|
||||
<h1>{{ __('booking.success_title') }}</h1>
|
||||
<p>{{ __('booking.success_message') }}</p>
|
||||
<a class="btn btn-ghost" href="{{ route('home') }}" wire:navigate>{{ __('site.nav_home') }}</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -276,181 +276,107 @@ new #[Layout('components.layouts.public')] class extends Component
|
||||
</form>
|
||||
@endif
|
||||
@else
|
||||
<div class="wrap py-8">
|
||||
<div class="max-w-4xl mx-auto py-8 px-4">
|
||||
<flux:heading size="xl" class="mb-6 text-body">
|
||||
{{ __('booking.request_consultation') }}
|
||||
</flux:heading>
|
||||
<section class="sec" style="padding-top:56px">
|
||||
<div class="wrap">
|
||||
<x-site.section-head :label="__('site.art_booking')" :title="__('booking.request_consultation')" :lede="__('site.booking_lede')" />
|
||||
|
||||
@if($isLoggedIn)
|
||||
{{-- Logged-in user message --}}
|
||||
<div class="bg-white rounded-lg shadow-sm border border-amber-200 p-8 text-center">
|
||||
<div class="mb-6">
|
||||
<div class="mx-auto w-16 h-16 bg-amber-100 rounded-full flex items-center justify-center mb-4">
|
||||
<flux:icon name="user-circle" class="w-8 h-8 text-amber-600" />
|
||||
</div>
|
||||
<flux:heading size="lg" class="text-body mb-2">
|
||||
{{ __('booking.logged_in_title') }}
|
||||
</flux:heading>
|
||||
<p class="text-zinc-600 max-w-md mx-auto">
|
||||
{{ __('booking.logged_in_message') }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-3 justify-center">
|
||||
<flux:button
|
||||
href="{{ route('client.consultations.book') }}"
|
||||
variant="primary"
|
||||
class="min-h-[44px]"
|
||||
>
|
||||
<flux:icon name="calendar" class="w-4 h-4 me-2" />
|
||||
{{ __('booking.go_to_dashboard') }}
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
<p class="mt-6 text-sm text-zinc-500">
|
||||
{{ __('booking.or_logout') }}
|
||||
</p>
|
||||
</div>
|
||||
@else
|
||||
@if(session('success'))
|
||||
<flux:callout variant="success" class="mb-6">
|
||||
{{ session('success') }}
|
||||
</flux:callout>
|
||||
@endif
|
||||
|
||||
@if(!$selectedDate || !$selectedTime)
|
||||
{{-- Step 1: Calendar Selection --}}
|
||||
{{-- Booking Expectations --}}
|
||||
<x-booking-expectations class="mb-6" />
|
||||
|
||||
<p class="mb-4 text-zinc-600">
|
||||
{{ __('booking.select_date_time') }}
|
||||
</p>
|
||||
|
||||
<livewire:availability-calendar />
|
||||
@else
|
||||
{{-- Step 2+: Contact Form & Confirmation --}}
|
||||
<div class="bg-amber-50 p-4 rounded-lg mb-6 border border-amber-200">
|
||||
<div class="flex justify-between items-center">
|
||||
<div>
|
||||
<p class="font-semibold text-zinc-900">
|
||||
{{ __('booking.selected_time') }}
|
||||
</p>
|
||||
<p class="text-zinc-600">
|
||||
{{ \Carbon\Carbon::parse($selectedDate)->translatedFormat('l, d M Y') }}
|
||||
</p>
|
||||
<p class="text-zinc-600">
|
||||
{{ \Carbon\Carbon::parse($selectedTime)->format('g:i A') }}
|
||||
</p>
|
||||
</div>
|
||||
<flux:button size="sm" wire:click="clearSelection">
|
||||
{{ __('common.change') }}
|
||||
</flux:button>
|
||||
<div class="book">
|
||||
<div class="book-l on-dark">
|
||||
<div class="contact">
|
||||
<a href="tel:+970599353502"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M5 4h4l2 5-2.5 1.5a11 11 0 0 0 5 5L15 13l5 2v4a2 2 0 0 1-2 2A16 16 0 0 1 3 6a2 2 0 0 1 2-2"/></svg><span dir="ltr">{{ __('footer.phone') }}</span></a>
|
||||
<a href="mailto:{{ __('footer.email') }}"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><rect x="3" y="5" width="18" height="14" rx="2"/><path d="m3 7 9 6 9-6"/></svg><span>{{ __('footer.email') }}</span></a>
|
||||
<span><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" aria-hidden="true"><path d="M12 21s7-6.2 7-12a7 7 0 0 0-14 0c0 5.8 7 12 7 12z"/><circle cx="12" cy="9" r="2.5"/></svg><span>{{ __('footer.address') }}</span></span>
|
||||
</div>
|
||||
<x-site.balance mode="static" style="width:190px;height:190px;margin-top:22px" />
|
||||
</div>
|
||||
|
||||
@if(!$showConfirmation)
|
||||
{{-- Contact Form --}}
|
||||
<div class="space-y-4">
|
||||
<flux:field>
|
||||
<flux:label class="required">{{ __('booking.guest_name') }}</flux:label>
|
||||
<flux:input wire:model="guestName" type="text" />
|
||||
<flux:error name="guestName" />
|
||||
</flux:field>
|
||||
<div class="book-r">
|
||||
@if($isLoggedIn)
|
||||
<div class="book-r-note">
|
||||
<h3>{{ __('booking.logged_in_title') }}</h3>
|
||||
<p>{{ __('booking.logged_in_message') }}</p>
|
||||
<a class="btn btn-gold" href="{{ route('client.consultations.book') }}">{{ __('booking.go_to_dashboard') }}</a>
|
||||
<p class="note">{{ __('booking.or_logout') }}</p>
|
||||
</div>
|
||||
@else
|
||||
@if(session('success'))
|
||||
<p class="ok" role="status">{{ session('success') }}</p>
|
||||
@endif
|
||||
|
||||
<flux:field>
|
||||
<flux:label class="required">{{ __('booking.guest_email') }}</flux:label>
|
||||
<flux:input wire:model="guestEmail" type="email" />
|
||||
<flux:error name="guestEmail" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label class="required">{{ __('booking.guest_phone') }}</flux:label>
|
||||
<flux:input wire:model="guestPhone" type="tel" />
|
||||
<flux:error name="guestPhone" />
|
||||
</flux:field>
|
||||
|
||||
<flux:field>
|
||||
<flux:label class="required">{{ __('booking.problem_summary') }}</flux:label>
|
||||
<flux:textarea
|
||||
wire:model="problemSummary"
|
||||
rows="6"
|
||||
placeholder="{{ __('booking.problem_summary_placeholder') }}"
|
||||
/>
|
||||
<flux:description>{{ __('booking.problem_summary_help') }}</flux:description>
|
||||
<flux:error name="problemSummary" />
|
||||
</flux:field>
|
||||
|
||||
{{-- Custom Captcha --}}
|
||||
<flux:field>
|
||||
<flux:label class="required">
|
||||
{{ app()->getLocale() === 'ar' ? $captchaQuestion['question_ar'] : $captchaQuestion['question'] }}
|
||||
</flux:label>
|
||||
<div class="flex gap-2">
|
||||
<flux:input wire:model="captchaAnswer" type="text" class="w-32" />
|
||||
<flux:button size="sm" wire:click="refreshCaptcha" type="button">
|
||||
<flux:icon name="arrow-path" class="w-4 h-4" />
|
||||
</flux:button>
|
||||
@if(!$selectedDate || !$selectedTime)
|
||||
<p class="note" style="text-align:start;margin:0 0 14px">{{ __('booking.select_date_time') }}</p>
|
||||
<livewire:availability-calendar />
|
||||
@else
|
||||
<div class="picked">
|
||||
<div>
|
||||
<b>{{ __('booking.selected_time') }}</b>
|
||||
<span>{{ \Carbon\Carbon::parse($selectedDate)->translatedFormat('l, d M Y') }}</span>
|
||||
<span dir="ltr">{{ \Carbon\Carbon::parse($selectedTime)->format('g:i A') }}</span>
|
||||
</div>
|
||||
<button type="button" class="btn btn-ghost" wire:click="clearSelection">{{ __('common.change') }}</button>
|
||||
</div>
|
||||
<flux:error name="captchaAnswer" />
|
||||
</flux:field>
|
||||
|
||||
<flux:button
|
||||
wire:click="showConfirm"
|
||||
class="w-full sm:w-auto min-h-[44px]"
|
||||
wire:loading.attr="disabled"
|
||||
>
|
||||
<span wire:loading.remove wire:target="showConfirm">{{ __('booking.continue') }}</span>
|
||||
<span wire:loading wire:target="showConfirm">{{ __('common.loading') }}</span>
|
||||
</flux:button>
|
||||
</div>
|
||||
@else
|
||||
{{-- Confirmation Step --}}
|
||||
<flux:callout>
|
||||
<flux:heading size="sm" class="text-body">{{ __('booking.confirm_booking') }}</flux:heading>
|
||||
<p class="text-zinc-600">{{ __('booking.confirm_message') }}</p>
|
||||
@if(!$showConfirmation)
|
||||
<div class="fld">
|
||||
<label for="bp-name">{{ __('booking.guest_name') }} *</label>
|
||||
<input id="bp-name" type="text" wire:model="guestName" autocomplete="name">
|
||||
@error('guestName') <p class="err">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div class="fld">
|
||||
<label for="bp-email">{{ __('booking.guest_email') }} *</label>
|
||||
<input id="bp-email" type="email" dir="ltr" wire:model="guestEmail" autocomplete="email">
|
||||
@error('guestEmail') <p class="err">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div class="fld">
|
||||
<label for="bp-phone">{{ __('booking.guest_phone') }} *</label>
|
||||
<input id="bp-phone" type="tel" dir="ltr" wire:model="guestPhone" autocomplete="tel">
|
||||
@error('guestPhone') <p class="err">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div class="fld">
|
||||
<label for="bp-summary">{{ __('booking.problem_summary') }} *</label>
|
||||
<textarea id="bp-summary" rows="5" wire:model="problemSummary" placeholder="{{ __('booking.problem_summary_placeholder') }}"></textarea>
|
||||
<p class="help">{{ __('booking.problem_summary_help') }}</p>
|
||||
@error('problemSummary') <p class="err">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<div class="fld cap">
|
||||
<label for="bp-captcha">{{ app()->getLocale() === 'ar' ? $captchaQuestion['question_ar'] : $captchaQuestion['question'] }} *</label>
|
||||
<div class="cap-row">
|
||||
<input id="bp-captcha" inputmode="numeric" dir="ltr" wire:model="captchaAnswer" style="max-width:140px">
|
||||
<button type="button" class="btn btn-ghost refresh" wire:click="refreshCaptcha" aria-label="{{ __('site.captcha_refresh') }}">↻</button>
|
||||
</div>
|
||||
@error('captchaAnswer') <p class="err">{{ $message }}</p> @enderror
|
||||
</div>
|
||||
<button type="button" class="btn btn-gold" wire:click="showConfirm" wire:loading.attr="disabled">
|
||||
<span wire:loading.remove wire:target="showConfirm">{{ __('booking.continue') }}</span>
|
||||
<span wire:loading wire:target="showConfirm">{{ __('common.loading') }}</span>
|
||||
</button>
|
||||
@else
|
||||
<h3>{{ __('booking.confirm_booking') }}</h3>
|
||||
<p class="note" style="text-align:start">{{ __('booking.confirm_message') }}</p>
|
||||
<dl class="summary">
|
||||
<dt>{{ __('booking.guest_name') }}</dt><dd>{{ $guestName }}</dd>
|
||||
<dt>{{ __('booking.guest_email') }}</dt><dd dir="ltr">{{ $guestEmail }}</dd>
|
||||
<dt>{{ __('booking.guest_phone') }}</dt><dd dir="ltr">{{ $guestPhone }}</dd>
|
||||
<dt>{{ __('booking.date') }}</dt><dd>{{ \Carbon\Carbon::parse($selectedDate)->translatedFormat('l, d M Y') }}</dd>
|
||||
<dt>{{ __('booking.time') }}</dt><dd dir="ltr">{{ \Carbon\Carbon::parse($selectedTime)->format('g:i A') }}</dd>
|
||||
<dt>{{ __('booking.duration') }}</dt><dd>45 {{ __('common.minutes') }}</dd>
|
||||
<dt>{{ __('booking.problem_summary') }}</dt><dd>{{ $problemSummary }}</dd>
|
||||
</dl>
|
||||
@error('selectedTime')
|
||||
<p class="err" role="alert">{{ $message }}</p>
|
||||
@enderror
|
||||
<button type="button" class="btn btn-gold" wire:click="submit" wire:loading.attr="disabled">
|
||||
<span wire:loading.remove wire:target="submit">{{ __('booking.submit_request') }}</span>
|
||||
<span wire:loading wire:target="submit">{{ __('common.submitting') }}</span>
|
||||
</button>
|
||||
<button type="button" class="btn btn-ghost" wire:click="$set('showConfirmation', false)">{{ __('common.back') }}</button>
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 space-y-2">
|
||||
<p><strong>{{ __('booking.guest_name') }}:</strong> {{ $guestName }}</p>
|
||||
<p><strong>{{ __('booking.guest_email') }}:</strong> {{ $guestEmail }}</p>
|
||||
<p><strong>{{ __('booking.guest_phone') }}:</strong> {{ $guestPhone }}</p>
|
||||
<p><strong>{{ __('booking.date') }}:</strong>
|
||||
{{ \Carbon\Carbon::parse($selectedDate)->translatedFormat('l, d M Y') }}</p>
|
||||
<p><strong>{{ __('booking.time') }}:</strong>
|
||||
{{ \Carbon\Carbon::parse($selectedTime)->format('g:i A') }}</p>
|
||||
<p><strong>{{ __('booking.duration') }}:</strong> 45 {{ __('common.minutes') }}</p>
|
||||
</div>
|
||||
|
||||
<div class="mt-4">
|
||||
<p><strong>{{ __('booking.problem_summary') }}:</strong></p>
|
||||
<p class="mt-1 text-sm text-zinc-600">{{ $problemSummary }}</p>
|
||||
</div>
|
||||
</flux:callout>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-3 mt-4">
|
||||
<flux:button wire:click="$set('showConfirmation', false)" class="w-full sm:w-auto min-h-[44px]">
|
||||
{{ __('common.back') }}
|
||||
</flux:button>
|
||||
<flux:button
|
||||
wire:click="submit"
|
||||
variant="primary"
|
||||
wire:loading.attr="disabled"
|
||||
class="w-full sm:w-auto min-h-[44px]"
|
||||
>
|
||||
<span wire:loading.remove wire:target="submit">{{ __('booking.submit_request') }}</span>
|
||||
<span wire:loading wire:target="submit">{{ __('common.submitting') }}</span>
|
||||
</flux:button>
|
||||
</div>
|
||||
|
||||
@error('selectedTime')
|
||||
<flux:callout variant="danger" class="mt-4">
|
||||
{{ $message }}
|
||||
</flux:callout>
|
||||
@enderror
|
||||
@endif
|
||||
@endif
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
<x-booking-expectations class="mt-10" />
|
||||
</div>
|
||||
</section>
|
||||
@endif
|
||||
|
||||
Reference in New Issue
Block a user