require(); $categories = $business->serviceCategories()->where('isActive', true)->with(['services' => fn ($query) => $query->where('isActive', true)->where('allowOnlineBooking', true)->orderBy('displayOrder')])->orderBy('displayOrder')->get(); return view('booking.services', compact('business', 'categories')); } public function selectServices(Request $request): RedirectResponse { $ids = array_values(array_filter((array) $request->input('serviceIds'))); if ($ids === []) return back()->withErrors(['serviceIds' => 'Choose at least one service.']); $request->session()->put('booking.serviceIds', $ids); return redirect()->route('booking.time'); } public function time(Request $request, TenantContext $tenant, AvailabilityService $availability): View|RedirectResponse { $business = $tenant->require(); $serviceIds = $request->session()->get('booking.serviceIds', []); if ($serviceIds === []) return redirect()->route('booking.services'); $date = CarbonImmutable::parse($request->query('date', now($business->timezone)->toDateString()), $business->timezone); $slots = $availability->slots($business, $date, $serviceIds); $services = $business->services()->whereIn('id', $serviceIds)->get(); return view('booking.time', compact('business', 'date', 'slots', 'services')); } public function selectTime(Request $request): RedirectResponse { $validated = $request->validate(['staffId' => ['required', 'string'], 'startAt' => ['required', 'date']]); $request->session()->put('booking.time', $validated); return redirect()->route('booking.details'); } public function details(Request $request, TenantContext $tenant): View|RedirectResponse { $business = $tenant->require(); if (! $request->session()->has('booking.time')) return redirect()->route('booking.time'); return view('booking.details', compact('business')); } public function saveDetails(Request $request): RedirectResponse { $validated = $request->validate(['firstName' => ['required', 'string', 'max:80'], 'lastName' => ['required', 'string', 'max:80'], 'email' => ['required', 'email', 'max:190'], 'phone' => ['nullable', 'string', 'max:40'], 'notes' => ['nullable', 'string', 'max:2000']]); $request->session()->put('booking.customer', $validated); return redirect()->route('booking.review'); } public function review(Request $request, TenantContext $tenant): View|RedirectResponse { $business = $tenant->require(); $draft = $request->session()->get('booking'); if (! isset($draft['serviceIds'], $draft['time'], $draft['customer'])) return redirect()->route('booking.services'); $services = $business->services()->whereIn('id', $draft['serviceIds'])->get(); $staff = $business->staff()->with('user')->find($draft['time']['staffId']); if (! $staff) return redirect()->route('booking.time')->withErrors(['time' => 'That barber is no longer available.']); return view('booking.review', ['business' => $business, 'services' => $services, 'staff' => $staff, 'draft' => $draft, 'total' => $services->sum('price')]); } public function confirm(Request $request, TenantContext $tenant, BookingService $booking): RedirectResponse { $business = $tenant->require(); $draft = $request->session()->get('booking'); if (! isset($draft['serviceIds'], $draft['time'], $draft['customer'])) return redirect()->route('booking.services'); try { $result = $booking->create($business, array_merge($draft['customer'], $draft['time'], ['serviceIds' => $draft['serviceIds']])); } catch (Throwable $exception) { return redirect()->route('booking.time')->withErrors(['booking' => $exception->getMessage()]); } $request->session()->forget('booking'); $request->session()->put('booking.result', [ 'appointmentId' => $result['appointment']->id, 'accessToken' => $result['accessToken'], ]); return redirect()->route('booking.confirmation'); } public function confirmation(Request $request, TenantContext $tenant): View|RedirectResponse { $business = $tenant->require(); $result = $request->session()->get('booking.result'); if (! $result) return redirect()->route('public.home'); $request->session()->forget('booking.result'); $appointment = Appointment::query() ->where('businessId', $business->id) ->with(['customer', 'staff.user', 'services.service']) ->find($result['appointmentId'] ?? null); if (! $appointment) return redirect()->route('public.home'); return view('booking.confirmation', compact('business', 'appointment')); } }