authorizeAbility($request, 'kiosk.use'); $organization = $this->organization($request); $settings = $organization->settings ?? []; $hosts = Host::owned($this->ownerRef($request)) ->where('organization_id', $organization->id) ->where('is_available', true); $this->scopeToBranch($request, $hosts); $hosts = $hosts->orderBy('name')->get(); return view('frontdesk.kiosk.index', [ 'organization' => $organization, 'hosts' => $hosts, 'visitorTypes' => config('frontdesk.visitor_types'), 'typeConfigs' => $visitorTypes->configsForFrontend(), 'resetSeconds' => (int) ($settings['kiosk_reset_seconds'] ?? config('frontdesk.kiosk.inactivity_reset_seconds', 120)), 'visitorPolicy' => $settings['visitor_policy'] ?? null, 'checkOutUrl' => route('frontdesk.kiosk.check-out'), // Staff sign-in requires a registered kiosk device (/kiosk/d/{token}). 'employeeKioskEnabled' => false, 'stepOutReasons' => collect(config('frontdesk.employee_step_out_reasons')) ->map(fn ($label, $key) => ['key' => $key, 'label' => $label]) ->values(), ]); } public function checkIn(Request $request, VisitCheckInService $checkIn, VisitorTypeService $visitorTypes): JsonResponse { $this->authorizeAbility($request, 'kiosk.use'); $organization = $this->organization($request); $visitorType = $request->input('visitor_type', 'visitor'); $validated = $request->validate(array_merge([ 'full_name' => ['required', 'string', 'max:255'], 'company' => ['nullable', 'string', 'max:255'], 'phone' => ['nullable', 'string', 'max:50'], 'email' => ['nullable', 'email', 'max:255'], 'host_id' => ['nullable', 'integer'], 'visitor_type' => ['required', 'string', 'in:'.implode(',', array_keys(config('frontdesk.visitor_types')))], 'purpose' => ['nullable', 'string', 'max:500'], 'expected_duration_minutes' => ['nullable', 'integer'], 'policies_accepted' => ['accepted'], 'photo_data' => ['nullable', 'string'], 'signature_path' => ['nullable', 'string'], ], $visitorTypes->validationRules($visitorType))); $payload = $visitorTypes->enrichCheckInData($visitorType, $validated, $organization); if (! empty($validated['photo_data'])) { $payload['photo_data'] = $validated['photo_data']; } $visit = $checkIn->checkIn($this->ownerRef($request), $organization, $payload); return response()->json([ 'visit' => [ 'id' => $visit->id, 'public_id' => $visit->public_id, 'badge_code' => $visit->badge_code, 'visitor_name' => $visit->visitor->full_name, 'host_name' => $visit->host?->name, 'status' => $visit->status, 'awaiting_approval' => $visit->awaitingApproval(), 'checked_in_at' => $visit->checked_in_at?->toIso8601String(), 'badge_url' => $visit->isInside() ? route('frontdesk.visits.badge', $visit) : null, 'host_notified' => (bool) ($visit->host_notified ?? false), ], ]); } public function checkOut(Request $request, VisitCheckOutService $checkOut): JsonResponse { $this->authorizeAbility($request, 'kiosk.use'); $organization = $this->organization($request); $validated = $request->validate([ 'badge_code' => ['nullable', 'string', 'max:32'], 'qr_token' => ['nullable', 'string', 'max:500'], ]); if (trim((string) ($validated['badge_code'] ?? '')) === '' && trim((string) ($validated['qr_token'] ?? '')) === '') { throw ValidationException::withMessages([ 'credentials' => 'Enter your badge code or scan your badge QR code.', ]); } $branchScope = app(\App\Services\Frontdesk\OrganizationResolver::class) ->branchScope($this->member($request)); try { $visit = $checkOut->checkOutFromKiosk( $this->ownerRef($request), $organization, $validated['badge_code'] ?? null, $validated['qr_token'] ?? null, $branchScope, ); } catch (ModelNotFoundException) { throw ValidationException::withMessages([ 'credentials' => 'No active visit found for that badge. Ask reception if you need help.', ]); } return response()->json([ 'visit' => [ 'visitor_name' => $visit->visitor->full_name, 'badge_code' => $visit->badge_code, 'checked_out_at' => $visit->checked_out_at?->toIso8601String(), ], ]); } }