Files
ladill-frontdesk/resources/views/frontdesk/kiosk/index.blade.php
T
isaaccladandCursor 793e39964e
Deploy Ladill Frontdesk / deploy (push) Successful in 34s
Replace kiosk visitor card icon with custom people SVG.
Uses the provided two-person outline icon with currentColor so it inherits the indigo accent on the choose screen.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-28 21:48:04 +00:00

565 lines
41 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@php
$visitorTypeCards = collect($visitorTypes)
->map(fn ($label, $key) => ['key' => $key, 'label' => $label])
->values();
@endphp
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>Reception · {{ $organization->name }}</title>
@include('partials.favicon')
<link rel="preconnect" href="https://fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=figtree:400,500,600,700&display=swap" rel="stylesheet" />
<script>
window.__KIOSK__ = {
visitorTypes: @json($visitorTypeCards),
typeConfigs: @json($typeConfigs),
resetSeconds: {{ $resetSeconds }},
checkInUrl: @json($checkInUrl ?? route('frontdesk.kiosk.check-in')),
employeeKioskEnabled: @json($employeeKioskEnabled ?? false),
staffActionUrl: @json($staffActionUrl ?? ''),
stepOutReasons: @json($stepOutReasons ?? []),
};
</script>
@vite(['resources/css/app.css', 'resources/js/app.js'])
<style>
@keyframes kiosk-tap-pulse {
0%, 100% { transform: scale(1); box-shadow: 0 20px 40px -12px rgb(79 70 229 / 0.45); }
50% { transform: scale(1.02); box-shadow: 0 24px 48px -10px rgb(79 70 229 / 0.55); }
}
.kiosk-tap-btn { animation: kiosk-tap-pulse 2.4s ease-in-out infinite; }
#staff-qr-reader { min-height: 280px; }
#staff-qr-reader video { border-radius: 0.75rem; width: 100% !important; }
</style>
</head>
<body class="min-h-screen bg-slate-100 font-sans text-slate-900 antialiased" x-data="kioskFlow" x-init="startTimer()">
<div class="pointer-events-none fixed inset-0 overflow-hidden" aria-hidden="true">
<div class="absolute -left-24 top-0 h-72 w-72 rounded-full bg-indigo-200/40 blur-3xl"></div>
<div class="absolute -right-16 bottom-0 h-80 w-80 rounded-full bg-violet-200/40 blur-3xl"></div>
</div>
<div class="fixed inset-x-0 top-0 z-10 h-1.5 bg-gradient-to-r from-indigo-600 to-violet-600"></div>
@include('frontdesk.partials.kiosk-brand')
<div class="relative mx-auto flex min-h-screen max-w-3xl flex-col pt-20">
{{-- Welcome: tap to begin --}}
<template x-if="step === 'welcome'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-12 text-center">
<div class="max-w-xl">
<p class="text-sm font-semibold uppercase tracking-wider text-indigo-600">Reception</p>
<h1 class="mt-3 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
Welcome to {{ $organization->name }}
</h1>
<p class="mt-4 text-lg text-slate-500">Visitor check-in &amp; staff sign-in</p>
</div>
<button type="button"
@click="beginKiosk()"
class="kiosk-tap-btn btn-primary btn-primary-lg mt-16 w-full max-w-lg py-8 text-xl font-bold">
Tap to begin
</button>
</div>
</template>
{{-- Choose: visitor or staff --}}
<template x-if="step === 'choose'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-12">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="max-w-xl text-center">
<h2 class="text-2xl font-bold text-slate-900 sm:text-3xl">Who are you?</h2>
<p class="mt-2 text-slate-500">Select an option to continue</p>
</div>
<div class="mt-12 grid w-full max-w-2xl gap-4 sm:grid-cols-2">
<button type="button"
@click="startVisitorFlow()"
class="group flex flex-col items-center rounded-3xl border-2 border-slate-200 bg-white px-6 py-10 text-center shadow-sm transition hover:border-indigo-300 hover:shadow-md active:scale-[0.98]">
<span class="flex h-16 w-16 items-center justify-center rounded-2xl bg-indigo-50 text-indigo-600 transition group-hover:bg-indigo-100">
<svg class="h-9 w-9" viewBox="0 0 14 14" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<path d="M5 6C6.24264 6 7.25 4.99264 7.25 3.75C7.25 2.50736 6.24264 1.5 5 1.5C3.75736 1.5 2.75 2.50736 2.75 3.75C2.75 4.99264 3.75736 6 5 6Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9.5 13.5H0.5V12.5C0.5 11.3065 0.974106 10.1619 1.81802 9.31802C2.66193 8.47411 3.80653 8 5 8C6.19347 8 7.33807 8.47411 8.18198 9.31802C9.02589 10.1619 9.5 11.3065 9.5 12.5V13.5Z" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M9 1.5C9.59674 1.5 10.169 1.73705 10.591 2.15901C11.0129 2.58097 11.25 3.15326 11.25 3.75C11.25 4.34674 11.0129 4.91903 10.591 5.34099C10.169 5.76295 9.59674 6 9 6" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.6 8.18994C11.4518 8.51399 12.1852 9.08905 12.703 9.83903C13.2209 10.589 13.4988 11.4786 13.5 12.3899V13.4999H12" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</span>
<span class="mt-5 text-xl font-bold text-slate-900">Visitor</span>
<span class="mt-1 text-sm text-slate-500">Check in as a guest</span>
</button>
<template x-if="employeeKioskEnabled">
<button type="button"
@click="startStaffFlow()"
class="group flex flex-col items-center rounded-3xl border-2 border-slate-200 bg-white px-6 py-10 text-center shadow-sm transition hover:border-indigo-300 hover:shadow-md active:scale-[0.98]">
<span class="flex h-16 w-16 items-center justify-center rounded-2xl bg-violet-50 text-violet-600 transition group-hover:bg-violet-100">
<svg class="h-9 w-9" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M15 9h3.75M15 12h3.75M15 15h3.75M4.5 19.5h15a2.25 2.25 0 0 0 2.25-2.25V6.75A2.25 2.25 0 0 0 19.5 4.5h-15a2.25 2.25 0 0 0-2.25 2.25v10.5A2.25 2.25 0 0 0 4.5 19.5Zm6-10.125a1.875 1.875 0 1 1-3.75 0 1.875 1.875 0 0 1 3.75 0Zm1.294 6.336a6.721 6.721 0 0 1-3.17.789 6.721 6.721 0 0 1-3.168-.789 3.376 3.376 0 0 1 6.338 0Z" />
</svg>
</span>
<span class="mt-5 text-xl font-bold text-slate-900">Staff</span>
<span class="mt-1 text-sm text-slate-500">Sign in or step out</span>
</button>
</template>
</div>
</div>
</template>
{{-- Visitor type carousel --}}
<template x-if="step === 'type'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8"
@touchstart="onTypeTouchStart($event)"
@touchend="onTypeTouchEnd($event)">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<h2 class="mb-10 text-center text-2xl font-semibold text-slate-900 sm:text-3xl">What brings you here?</h2>
<div class="flex w-full max-w-lg items-center gap-3">
<button type="button"
@click="prevType()"
:disabled="typeIndex === 0"
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-indigo-200 disabled:opacity-30"
aria-label="Previous option">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M15.75 19.5 8.25 12l7.5-7.5" /></svg>
</button>
<div class="relative min-w-0 flex-1 overflow-hidden">
<div class="flex transition-transform duration-300 ease-out"
:style="`transform: translateX(-${typeIndex * 100}%)`">
<template x-for="type in visitorTypes" :key="type.key">
<div class="w-full shrink-0 px-1">
<button type="button"
@click="selectType(type.key)"
class="w-full rounded-3xl border-2 border-slate-200 bg-white px-6 py-14 text-center shadow-sm transition hover:border-indigo-300 hover:shadow-md active:scale-[0.98]">
<span class="block text-2xl font-bold text-slate-900" x-text="type.label"></span>
<span class="mt-3 block text-sm font-medium text-indigo-600">Tap to select</span>
</button>
</div>
</template>
</div>
</div>
<button type="button"
@click="nextType()"
:disabled="typeIndex === visitorTypes.length - 1"
class="flex h-12 w-12 shrink-0 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-600 shadow-sm transition hover:border-indigo-200 disabled:opacity-30"
aria-label="Next option">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" /></svg>
</button>
</div>
<div class="mt-8 flex justify-center gap-2">
<template x-for="(type, i) in visitorTypes" :key="'dot-' + type.key">
<button type="button"
@click="typeIndex = i; resetTimer()"
class="h-2.5 rounded-full transition-all duration-200"
:class="typeIndex === i ? 'w-8 bg-indigo-600' : 'w-2.5 bg-slate-300'"
:aria-label="'Go to ' + type.label"></button>
</template>
</div>
</div>
</template>
{{-- Multi-step check-in form --}}
<template x-if="step === 'details'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="mb-6">
<div class="flex items-center justify-between text-xs font-medium text-slate-500">
<span x-text="typeConfigs[form.visitor_type]?.label"></span>
<span x-text="`Step ${formStep} of ${totalFormSteps()}`"></span>
</div>
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full bg-gradient-to-r from-indigo-600 to-violet-600 transition-all duration-300"
:style="`width: ${(formStep / totalFormSteps()) * 100}%`"></div>
</div>
<p class="mt-2 text-sm font-semibold text-slate-900" x-text="currentFormStepLabel()"></p>
</div>
<p x-show="errorMessage" x-cloak class="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<div>
{{-- Step 1: Identity --}}
<template x-if="currentStepKind() === 'identity'">
<div class="space-y-4">
<input type="text" x-model="form.full_name" placeholder="Full name *" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="form.company" placeholder="Company" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="form.phone" placeholder="Phone" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<input type="text" x-model="form.email" placeholder="Email" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
</div>
</template>
{{-- Step 2: Visit --}}
<template x-if="currentStepKind() === 'visit'">
<div class="space-y-4">
<select x-model="form.host_id" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<option value="">Who are you visiting?</option>
@foreach ($hosts as $host)
<option value="{{ $host->id }}">{{ $host->name }}</option>
@endforeach
</select>
<select x-model="form.purpose" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<option value="">Purpose of visit</option>
@foreach (config('frontdesk.visit_purposes') as $label)
<option value="{{ $label }}">{{ $label }}</option>
@endforeach
</select>
</div>
</template>
{{-- Step 3: Type-specific fields (when present) --}}
<template x-if="currentStepKind() === 'typeFields'">
<div class="space-y-4">
<template x-for="field in typeConfigs[form.visitor_type]?.fields || []" :key="field.name">
<div>
<label class="mb-1 block text-sm font-medium text-slate-600" x-text="field.label"></label>
<template x-if="field.type === 'textarea'">
<textarea x-model="form.type_fields[field.name]" rows="2" class="w-full rounded-xl border-slate-200 px-4 py-3 text-lg"></textarea>
</template>
<template x-if="field.type === 'checkbox'">
<label class="flex items-center gap-2 text-sm">
<input type="checkbox" :checked="form.type_fields[field.name]" @change="form.type_fields[field.name] = $event.target.checked" class="h-5 w-5 rounded">
Yes
</label>
</template>
<template x-if="field.type === 'date' || field.type === 'datetime-local' || field.type === 'text'">
<input :type="field.type" x-model="form.type_fields[field.name]" class="w-full rounded-xl border-slate-200 px-4 py-3 text-lg">
</template>
<template x-if="field.type === 'signature'">
<canvas :x-ref="'sig_' + field.name" width="400" height="120" class="w-full rounded-xl border border-slate-200 bg-slate-50"
@mousedown="startSign($event, field.name)" @mousemove="drawSign($event, field.name)" @mouseup="endSign(field.name)"
@touchstart.prevent="startSign($event, field.name)" @touchmove.prevent="drawSign($event, field.name)" @touchend.prevent="endSign(field.name)"></canvas>
<button type="button" @click="clearSign(field.name)" class="mt-1 text-xs text-slate-500">Clear signature</button>
</template>
</div>
</template>
</div>
</template>
{{-- Final step: Photo & finish --}}
<template x-if="currentStepKind() === 'finish'">
<div class="space-y-4">
<div class="rounded-xl border border-slate-200 p-4">
<p class="mb-2 text-sm font-medium text-slate-600">Visitor photo (optional)</p>
<video x-ref="camera" x-show="cameraActive" autoplay playsinline class="mb-2 w-full max-h-48 rounded-lg bg-slate-900 object-cover"></video>
<img x-show="form.photo_data" :src="form.photo_data" alt="Captured photo preview" class="mb-2 max-h-48 rounded-lg">
<div class="flex flex-wrap gap-2">
<button type="button" @click="startCamera()" class="rounded-lg bg-slate-100 px-3 py-2 text-sm">Open camera</button>
<button type="button" @click="capturePhoto()" class="rounded-lg bg-slate-100 px-3 py-2 text-sm">Capture</button>
<button type="button" @click="clearPhoto()" class="rounded-lg bg-slate-100 px-3 py-2 text-sm">Clear</button>
</div>
</div>
<template x-if="typeConfigs[form.visitor_type]?.requires_approval">
<p class="rounded-xl bg-amber-50 px-4 py-3 text-sm text-amber-800">Reception will review your visit before issuing a badge.</p>
</template>
<label class="flex items-center gap-3 text-sm">
<input type="checkbox" x-model="form.policies_accepted" class="h-5 w-5 rounded">
I have read and accept the visitor policies
</label>
</div>
</template>
<div class="mt-6">
<button type="button"
@click="handleContinue()"
:disabled="loading"
class="btn-primary w-full py-3 disabled:opacity-50">
<span x-text="loading ? 'Submitting…' : (isLastFormStep() ? 'Complete check-in' : 'Continue')"></span>
</button>
</div>
</div>
</div>
</div>
</template>
{{-- Employee: identify --}}
<template x-if="step === 'staff_identify'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="mb-6">
<div class="flex items-center justify-between text-xs font-medium text-slate-500">
<span>Staff sign-in</span>
<span x-text="`Step ${staffFormStep} of 2`"></span>
</div>
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-100">
<div class="h-full rounded-full bg-gradient-to-r from-violet-600 to-indigo-600 transition-all duration-300"
:style="`width: ${(staffFormStep / 2) * 100}%`"></div>
</div>
<p class="mt-2 text-sm font-semibold text-slate-900" x-text="staffIdentifyStepLabel()"></p>
</div>
<p x-show="errorMessage" x-cloak class="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
{{-- Step 1: Identity --}}
<template x-if="staffFormStep === 1">
<div>
<div class="grid grid-cols-2 gap-3">
<button type="button"
@click="setStaffIdentifyMode('code')"
class="group flex flex-col items-center rounded-2xl border-2 px-4 py-5 text-center transition active:scale-[0.98]"
:class="staffIdentifyMode === 'code' ? 'border-violet-300 bg-violet-50 shadow-sm' : 'border-slate-200 bg-white hover:border-violet-200'">
<span class="flex h-11 w-11 items-center justify-center rounded-xl transition"
:class="staffIdentifyMode === 'code' ? 'bg-violet-100 text-violet-600' : 'bg-slate-100 text-slate-500 group-hover:bg-violet-50 group-hover:text-violet-600'">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M15.75 5.25a3 3 0 0 1 3 3m3 0a6 6 0 0 1-7.029 5.912c-.563-.097-1.159.026-1.563.43L10.5 17.25H8.25v2.25H6v2.25H2.25v-2.818c0-.597.237-1.17.659-1.591l6.499-6.499a1.875 1.875 0 0 1 3.182 1.32l-1.92 1.92" />
</svg>
</span>
<span class="mt-3 text-sm font-semibold text-slate-900">Employee code</span>
<span class="mt-0.5 text-xs text-slate-500">Type your ID</span>
</button>
<button type="button"
@click="setStaffIdentifyMode('badge')"
class="group flex flex-col items-center rounded-2xl border-2 px-4 py-5 text-center transition active:scale-[0.98]"
:class="staffIdentifyMode === 'badge' ? 'border-violet-300 bg-violet-50 shadow-sm' : 'border-slate-200 bg-white hover:border-violet-200'">
<span class="flex h-11 w-11 items-center justify-center rounded-xl transition"
:class="staffIdentifyMode === 'badge' ? 'bg-violet-100 text-violet-600' : 'bg-slate-100 text-slate-500 group-hover:bg-violet-50 group-hover:text-violet-600'">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 3.75 9.375v-4.5ZM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 0 1-1.125-1.125v-4.5ZM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0 1 13.5 9.375v-4.5Z" />
<path stroke-linecap="round" stroke-linejoin="round" d="M6.75 6.75h.75v.75h-.75v-.75ZM6.75 16.5h.75v.75h-.75v-.75ZM16.5 6.75h.75v.75h-.75v-.75ZM13.5 13.5h.75v.75h-.75v-.75ZM13.5 19.5h.75v.75h-.75v-.75ZM19.5 13.5h.75v.75h-.75v-.75ZM19.5 19.5h.75v.75h-.75v-.75ZM16.5 16.5h.75v.75h-.75v-.75Z" />
</svg>
</span>
<span class="mt-3 text-sm font-semibold text-slate-900">Scan badge</span>
<span class="mt-0.5 text-xs text-slate-500">Use camera or scanner</span>
</button>
</div>
<div class="mt-6">
<template x-if="staffIdentifyMode === 'code'">
<div>
<label for="staff-employee-code" class="mb-1.5 block text-sm font-medium text-slate-600">Employee code</label>
<input id="staff-employee-code"
type="text"
x-model="staffForm.employee_code"
placeholder="e.g. EMP001"
autocomplete="off"
class="w-full rounded-xl border-slate-200 px-4 py-4 font-mono text-lg">
</div>
</template>
<template x-if="staffIdentifyMode === 'badge'">
<div class="space-y-3">
<div x-show="!staffQrScanned">
<label class="mb-1.5 block text-sm font-medium text-slate-600">Badge QR code</label>
<div id="staff-qr-reader" class="overflow-hidden rounded-xl border border-slate-200 bg-slate-900/5"></div>
<p class="mt-2 text-center text-xs text-slate-500">Hold your badge in front of the camera</p>
</div>
<div x-show="staffQrScanned" x-cloak class="rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-5 text-center">
<span class="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-emerald-100 text-emerald-600">
<svg class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
</span>
<p class="mt-3 text-sm font-semibold text-emerald-800">Badge recognized</p>
<p class="mt-1 text-sm text-emerald-700">Continue to enter your PIN.</p>
<button type="button" @click="rescanStaffBadge()" class="mt-3 text-sm font-medium text-violet-600 hover:text-violet-800">Scan a different badge</button>
</div>
<p x-show="staffQrError" x-cloak class="rounded-xl bg-amber-50 px-4 py-3 text-sm text-amber-800" x-text="staffQrError"></p>
<details class="rounded-xl border border-slate-200 bg-slate-50 px-4 py-3 text-sm text-slate-600">
<summary class="cursor-pointer font-medium text-slate-700">Using an external scanner?</summary>
<label class="mb-1.5 mt-3 block text-xs font-medium text-slate-500">Badge code</label>
<input type="text"
x-model="staffForm.qr_token"
placeholder="Scan or paste badge code"
autocomplete="off"
class="w-full rounded-lg border-slate-200 px-3 py-3 font-mono text-base">
</details>
</div>
</template>
</div>
</div>
</template>
{{-- Step 2: PIN --}}
<template x-if="staffFormStep === 2">
<div class="space-y-4">
<div x-show="staffIdentifyMode === 'badge' && staffQrScanned" x-cloak class="flex items-center gap-3 rounded-2xl border border-emerald-200 bg-emerald-50 px-4 py-3">
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-emerald-100 text-emerald-600">
<svg class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="m4.5 12.75 6 6 9-13.5" />
</svg>
</span>
<div class="min-w-0 text-left">
<p class="text-sm font-semibold text-emerald-800">Badge verified</p>
<p class="text-xs text-emerald-700">Enter your PIN to continue.</p>
</div>
</div>
<div x-show="staffIdentifyMode === 'code'" x-cloak class="flex items-center gap-3 rounded-2xl border border-slate-200 bg-slate-50 px-4 py-3">
<span class="flex h-9 w-9 shrink-0 items-center justify-center rounded-full bg-violet-100 font-mono text-sm font-bold text-violet-700" x-text="(staffForm.employee_code || '').slice(0, 2).toUpperCase() || '••'"></span>
<div class="min-w-0 text-left">
<p class="text-sm font-semibold text-slate-900">Employee code</p>
<p class="truncate font-mono text-xs text-slate-600" x-text="staffForm.employee_code"></p>
</div>
</div>
<div>
<label for="staff-pin" class="mb-1.5 block text-sm font-medium text-slate-600">PIN</label>
<input id="staff-pin"
type="password"
inputmode="numeric"
x-model="staffForm.pin"
placeholder="46 digits"
maxlength="6"
autocomplete="off"
class="w-full rounded-xl border-slate-200 px-4 py-4 text-center text-2xl tracking-[0.35em]">
<p class="mt-2 text-center text-xs text-slate-500">Your personal PIN not shared with anyone</p>
</div>
</div>
</template>
<div class="mt-6">
<button type="button"
@click="staffIdentify()"
:disabled="loading"
class="btn-primary w-full py-3 disabled:opacity-50">
<span x-text="loading ? 'Checking…' : (staffFormStep === 1 ? 'Continue' : 'Verify & continue')"></span>
</button>
</div>
</div>
</div>
</template>
{{-- Employee: actions --}}
<template x-if="step === 'staff_actions'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="text-center">
<span class="mx-auto flex h-16 w-16 items-center justify-center rounded-2xl bg-violet-100 text-xl font-bold text-violet-700"
x-text="(staffEmployee?.full_name || '?').split(' ').map(n => n[0]).join('').slice(0, 2).toUpperCase()"></span>
<p class="mt-4 text-sm font-medium text-violet-600" x-text="staffEmployee?.status_label"></p>
<h2 class="mt-1 text-2xl font-bold text-slate-900" x-text="staffEmployee?.full_name"></h2>
<p class="mt-1 text-sm text-slate-500" x-show="staffEmployee?.destination" x-text="'Currently: ' + staffEmployee?.destination"></p>
</div>
<p x-show="errorMessage" x-cloak class="mt-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<p class="mt-6 mb-3 text-sm font-semibold text-slate-700">What would you like to do?</p>
<div class="space-y-3">
<template x-for="action in staffEmployee?.available_actions || []" :key="action">
<button type="button"
@click="action === 'step_out' ? openStaffStepOut() : staffPerform(action)"
:disabled="loading"
class="group flex w-full items-center justify-between rounded-2xl border-2 border-slate-200 px-5 py-4 text-left transition hover:border-violet-300 hover:bg-violet-50 active:scale-[0.99] disabled:opacity-50">
<span class="text-lg font-semibold text-slate-800" x-text="staffActionLabel(action)"></span>
<svg class="h-5 w-5 text-slate-400 transition group-hover:text-violet-600" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" aria-hidden="true">
<path stroke-linecap="round" stroke-linejoin="round" d="m8.25 4.5 7.5 7.5-7.5 7.5" />
</svg>
</button>
</template>
</div>
</div>
</div>
</template>
{{-- Employee: step out --}}
<template x-if="step === 'staff_step_out'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="absolute left-6 top-8 z-10">
@include('frontdesk.partials.kiosk-back')
</div>
<div class="mx-auto w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-6 shadow-sm">
<div class="mb-6">
<div class="flex items-center justify-between text-xs font-medium text-slate-500">
<span x-text="staffEmployee?.full_name"></span>
<span>Step out</span>
</div>
<div class="mt-2 h-1.5 overflow-hidden rounded-full bg-slate-100">
<div class="h-full w-full rounded-full bg-gradient-to-r from-violet-600 to-indigo-600"></div>
</div>
<p class="mt-2 text-sm font-semibold text-slate-900">Where are you going?</p>
<p class="mt-0.5 text-xs text-slate-500">Reception will know you're away from your desk.</p>
</div>
<p x-show="errorMessage" x-cloak class="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="errorMessage"></p>
<div class="space-y-4">
<div>
<label for="staff-step-out-reason" class="mb-1.5 block text-sm font-medium text-slate-600">Reason</label>
<select id="staff-step-out-reason" x-model="staffStepOut.step_out_reason" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
<template x-for="reason in stepOutReasons" :key="reason.key">
<option :value="reason.key" x-text="reason.label"></option>
</template>
</select>
</div>
<div>
<label for="staff-step-out-destination" class="mb-1.5 block text-sm font-medium text-slate-600">Destination</label>
<input id="staff-step-out-destination" type="text" x-model="staffStepOut.destination" placeholder="Required for Other" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
</div>
<div>
<label for="staff-step-out-return" class="mb-1.5 block text-sm font-medium text-slate-600">Expected return (optional)</label>
<input id="staff-step-out-return" type="datetime-local" x-model="staffStepOut.expected_return_at" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
</div>
<div>
<label for="staff-step-out-notes" class="mb-1.5 block text-sm font-medium text-slate-600">Notes (optional)</label>
<input id="staff-step-out-notes" type="text" x-model="staffStepOut.notes" placeholder="Any extra details" class="w-full rounded-xl border-slate-200 px-4 py-4 text-lg">
</div>
</div>
<button type="button" @click="submitStaffStepOut()" :disabled="loading" class="btn-primary mt-6 w-full py-3 disabled:opacity-50">
<span x-text="loading ? 'Saving…' : 'Confirm step out'"></span>
</button>
</div>
</div>
</template>
{{-- Employee: branch mismatch confirm --}}
<template x-if="step === 'staff_branch_confirm'">
<div class="relative flex min-h-[calc(100vh-5rem)] flex-col justify-center px-6 py-8">
<div class="mx-auto w-full max-w-lg rounded-3xl border border-amber-200 bg-white p-6 shadow-sm">
<h2 class="text-xl font-bold text-slate-900">Different branch</h2>
<p class="mt-3 text-sm text-slate-600" x-text="staffBranchWarning"></p>
<div class="mt-6 flex gap-3">
<button type="button" @click="step = 'staff_actions'" class="flex-1 rounded-xl border border-slate-200 py-3 font-medium">Cancel</button>
<button type="button" @click="staffConfirmSignIn()" :disabled="loading" class="btn-primary flex-1 py-3 disabled:opacity-50">Sign in here</button>
</div>
</div>
</div>
</template>
{{-- Employee: done --}}
<template x-if="step === 'staff_done'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8">
<div class="w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<div class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-indigo-100 text-4xl">✓</div>
<h2 class="text-2xl font-bold" x-text="staffMessage"></h2>
<p class="mt-2 text-slate-600" x-text="staffEmployee?.full_name"></p>
<p class="mt-1 text-sm text-slate-500" x-text="staffEmployee?.status_label"></p>
<button type="button" @click="reset()" class="btn-primary btn-primary-lg mt-8 w-full">Done</button>
</div>
</div>
</template>
{{-- Done --}}
<template x-if="step === 'done'">
<div class="flex min-h-[calc(100vh-5rem)] flex-col items-center justify-center px-6 py-8">
<div class="w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-8 text-center shadow-sm">
<div class="mx-auto mb-4 flex h-20 w-20 items-center justify-center rounded-full bg-indigo-100 text-4xl">✓</div>
<h2 class="text-2xl font-bold" x-text="result?.awaiting_approval ? 'Submitted for approval' : 'You\'re checked in!'"></h2>
<p class="mt-2 text-slate-600" x-text="result?.visitor_name"></p>
<template x-if="!result?.awaiting_approval">
<p class="mt-1 text-sm text-slate-500">Badge code: <span class="font-mono font-bold" x-text="result?.badge_code"></span></p>
</template>
<p class="mt-4 text-sm text-slate-500"
x-text="result?.awaiting_approval ? 'Please wait at reception.' : (result?.host_notified ? 'Your host has been notified.' : (result?.host_name ? 'Check in complete.' : 'Thank you for checking in.'))"></p>
<button type="button" @click="reset()" class="btn-primary btn-primary-lg mt-8 w-full">Done</button>
</div>
</div>
</template>
</div>
</body>
</html>