check()) { $this->redirect(route('client.consultations.book')); return; } $this->refreshCaptcha(); } public function refreshCaptcha(): void { $this->captchaQuestion = app(CaptchaService::class)->generate(); $this->captchaAnswer = ''; } public function selectSlot(string $date, string $time): void { $this->selectedDate = $date; $this->selectedTime = $time; } public function clearSelection(): void { $this->selectedDate = null; $this->selectedTime = null; $this->showConfirmation = false; } public function showConfirm(): void { $this->validate([ 'selectedDate' => ['required', 'date', 'after_or_equal:today'], 'selectedTime' => ['required'], 'guestName' => ['required', 'string', 'min:3', 'max:255'], 'guestEmail' => ['required', 'email', 'max:255'], 'guestPhone' => ['required', 'string', 'max:50'], 'problemSummary' => ['required', 'string', 'min:20', 'max:2000'], 'captchaAnswer' => ['required'], ]); // Validate captcha if (! app(CaptchaService::class)->validate($this->captchaAnswer)) { $this->addError('captchaAnswer', __('booking.invalid_captcha')); $this->refreshCaptcha(); return; } // Check 1-per-day limit for this email $existingBooking = Consultation::query() ->where('guest_email', $this->guestEmail) ->whereDate('booking_date', $this->selectedDate) ->whereIn('status', [ConsultationStatus::Pending, ConsultationStatus::Approved]) ->exists(); if ($existingBooking) { $this->addError('guestEmail', __('booking.guest_already_booked_this_day')); return; } // Verify slot still available $service = app(AvailabilityService::class); $availableSlots = $service->getAvailableSlots(Carbon::parse($this->selectedDate)); if (! in_array($this->selectedTime, $availableSlots)) { $this->addError('selectedTime', __('booking.slot_no_longer_available')); return; } $this->showConfirmation = true; } public function submit(): void { // Rate limiting by IP $ipKey = 'guest-booking:'.request()->ip(); if (RateLimiter::tooManyAttempts($ipKey, 5)) { $this->addError('guestEmail', __('booking.too_many_attempts')); return; } RateLimiter::hit($ipKey, 60 * 60 * 24); // 24 hours try { DB::transaction(function () { // Double-check slot availability with lock $slotTaken = Consultation::query() ->whereDate('booking_date', $this->selectedDate) ->where('booking_time', $this->selectedTime) ->whereIn('status', [ConsultationStatus::Pending, ConsultationStatus::Approved]) ->lockForUpdate() ->exists(); if ($slotTaken) { throw new \Exception(__('booking.slot_taken')); } // Double-check 1-per-day with lock $emailHasBooking = Consultation::query() ->where('guest_email', $this->guestEmail) ->whereDate('booking_date', $this->selectedDate) ->whereIn('status', [ConsultationStatus::Pending, ConsultationStatus::Approved]) ->lockForUpdate() ->exists(); if ($emailHasBooking) { throw new \Exception(__('booking.guest_already_booked_this_day')); } // Create guest consultation $consultation = Consultation::create([ 'user_id' => null, 'guest_name' => $this->guestName, 'guest_email' => $this->guestEmail, 'guest_phone' => $this->guestPhone, 'booking_date' => $this->selectedDate, 'booking_time' => $this->selectedTime, 'problem_summary' => $this->problemSummary, 'status' => ConsultationStatus::Pending, 'payment_status' => PaymentStatus::NotApplicable, ]); // Send confirmation to guest Mail::to($this->guestEmail)->queue( new GuestBookingSubmittedMail($consultation) ); // Notify admin $admin = User::query()->where('user_type', 'admin')->first(); if ($admin) { Mail::to($admin)->queue( new NewBookingAdminEmail($consultation) ); } }); // Clear captcha app(CaptchaService::class)->clear(); session()->flash('success', __('booking.guest_submitted_successfully')); $this->redirect(route('booking.success')); } catch (\Exception $e) { $this->addError('selectedTime', $e->getMessage()); $this->showConfirmation = false; $this->refreshCaptcha(); } } }; ?>
{{ __('booking.guest_intro') }}
{{ __('booking.select_date_time') }}
{{ __('booking.selected_time') }}
{{ \Carbon\Carbon::parse($selectedDate)->translatedFormat('l, d M Y') }}
{{ \Carbon\Carbon::parse($selectedTime)->format('g:i A') }}
{{ __('booking.confirm_message') }}
{{ __('booking.guest_name') }}: {{ $guestName }}
{{ __('booking.guest_email') }}: {{ $guestEmail }}
{{ __('booking.guest_phone') }}: {{ $guestPhone }}
{{ __('booking.date') }}: {{ \Carbon\Carbon::parse($selectedDate)->translatedFormat('l, d M Y') }}
{{ __('booking.time') }}: {{ \Carbon\Carbon::parse($selectedTime)->format('g:i A') }}
{{ __('booking.duration') }}: 45 {{ __('common.minutes') }}
{{ __('booking.problem_summary') }}:
{{ $problemSummary }}