Restyle Queue kiosk to match Frontdesk kiosk design.
Deploy Ladill Queue / deploy (push) Successful in 35s
Deploy Ladill Queue / deploy (push) Successful in 35s
Welcome screen, choice cards, form progress, and success state now follow the same lobby-tablet visual language with idle reset. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,6 +33,7 @@ class KioskDeviceController extends Controller
|
||||
|
||||
return view('qms.kiosk.device', [
|
||||
'device' => $device,
|
||||
'organization' => $organization,
|
||||
'queues' => $queues,
|
||||
'settings' => $settings,
|
||||
'logoUrl' => $organization
|
||||
@@ -41,7 +42,6 @@ class KioskDeviceController extends Controller
|
||||
'logoAlt' => $organization
|
||||
? OrganizationBranding::logoAlt($organization)
|
||||
: 'Ladill Queue',
|
||||
'poweredByLogoUrl' => OrganizationBranding::assetUrl(OrganizationBranding::POWERED_BY_LOGO),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
+4
-94
@@ -428,104 +428,14 @@ html:has(.qms-display) body {
|
||||
rgb(255 255 255 / 0.97);
|
||||
}
|
||||
|
||||
/* Self-service kiosk */
|
||||
html:has(.qms-kiosk),
|
||||
html:has(.qms-kiosk) body,
|
||||
html:has(.qms-kiosk) .qms-kiosk {
|
||||
/* Self-service kiosk — ticket number sizing (Frontdesk-style shell uses Tailwind) */
|
||||
html:has(.qms-kiosk__ticket-number),
|
||||
html:has(.qms-kiosk__ticket-number) body {
|
||||
font-family: 'Figtree', ui-sans-serif, system-ui, sans-serif;
|
||||
}
|
||||
|
||||
html:has(.qms-kiosk),
|
||||
html:has(.qms-kiosk) body {
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.qms-kiosk {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100dvh;
|
||||
max-height: 100dvh;
|
||||
overflow: hidden;
|
||||
background:
|
||||
radial-gradient(ellipse 90% 60% at 50% -20%, rgb(238 242 255 / 0.9), transparent),
|
||||
linear-gradient(180deg, rgb(248 250 252) 0%, rgb(255 255 255) 100%);
|
||||
color: rgb(15 23 42);
|
||||
}
|
||||
|
||||
.qms-kiosk__shell {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.qms-kiosk__main {
|
||||
display: flex;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.qms-kiosk__queue-grid {
|
||||
display: grid;
|
||||
width: 100%;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.qms-kiosk__queue-btn {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
border-radius: 1rem;
|
||||
border: 1px solid rgb(226 232 240);
|
||||
background: #fff;
|
||||
padding: 1rem 1.25rem;
|
||||
text-align: left;
|
||||
box-shadow: 0 1px 2px rgb(15 23 42 / 0.04);
|
||||
transition: border-color 0.15s ease, box-shadow 0.15s ease;
|
||||
}
|
||||
|
||||
.qms-kiosk__queue-btn:hover {
|
||||
border-color: rgb(165 180 252);
|
||||
box-shadow: 0 8px 24px -12px rgb(79 70 229 / 0.25);
|
||||
}
|
||||
|
||||
.qms-kiosk__queue-btn:disabled {
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.qms-kiosk__queue-mark {
|
||||
display: flex;
|
||||
height: 2.75rem;
|
||||
width: 2.75rem;
|
||||
flex-shrink: 0;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 0.75rem;
|
||||
font-size: 0.875rem;
|
||||
font-weight: 700;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.qms-kiosk__ticket-card {
|
||||
width: 100%;
|
||||
border-radius: 1.5rem;
|
||||
border: 1px solid rgb(226 232 240);
|
||||
background: #fff;
|
||||
padding: clamp(1.5rem, 5vh, 3rem);
|
||||
text-align: center;
|
||||
box-shadow:
|
||||
0 1px 2px rgb(15 23 42 / 0.04),
|
||||
0 24px 48px -20px rgb(79 70 229 / 0.18);
|
||||
}
|
||||
|
||||
.qms-kiosk__ticket-number {
|
||||
font-size: clamp(3.5rem, 16vh, 7rem);
|
||||
font-size: clamp(3.5rem, 16vh, 5.5rem);
|
||||
font-weight: 700;
|
||||
line-height: 1;
|
||||
letter-spacing: -0.04em;
|
||||
|
||||
+73
-35
@@ -39,36 +39,98 @@ function pickVoice(voices, locale, preference) {
|
||||
|
||||
export function registerKioskFlow(Alpine) {
|
||||
Alpine.data('kioskFlow', (config = {}) => ({
|
||||
step: 'select',
|
||||
step: 'welcome',
|
||||
queueId: null,
|
||||
customerName: '',
|
||||
customerPhone: '',
|
||||
ticket: null,
|
||||
error: null,
|
||||
loading: false,
|
||||
idleTimer: null,
|
||||
countdownInterval: null,
|
||||
resetCountdown: null,
|
||||
resetTimer: null,
|
||||
countdownTimer: null,
|
||||
issueUrl: config.issueUrl,
|
||||
csrf: config.csrf,
|
||||
queues: config.queues ?? [],
|
||||
collectName: config.collectName ?? false,
|
||||
collectPhone: config.collectPhone ?? false,
|
||||
resetSeconds: config.resetSeconds ?? 15,
|
||||
welcomeMessage: config.welcomeMessage ?? 'Tap a service below to get your ticket',
|
||||
welcomeMessage: config.welcomeMessage ?? null,
|
||||
|
||||
startTimer() {
|
||||
this.resetIdleTimer();
|
||||
},
|
||||
|
||||
resetIdleTimer() {
|
||||
clearTimeout(this.idleTimer);
|
||||
clearInterval(this.countdownInterval);
|
||||
this.resetCountdown = this.resetSeconds;
|
||||
|
||||
this.countdownInterval = setInterval(() => {
|
||||
this.resetCountdown -= 1;
|
||||
if (this.resetCountdown <= 0) {
|
||||
clearInterval(this.countdownInterval);
|
||||
this.countdownInterval = null;
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.idleTimer = setTimeout(() => this.reset(), this.resetSeconds * 1000);
|
||||
},
|
||||
|
||||
clearIdleTimer() {
|
||||
clearTimeout(this.idleTimer);
|
||||
clearInterval(this.countdownInterval);
|
||||
this.idleTimer = null;
|
||||
this.countdownInterval = null;
|
||||
this.resetCountdown = null;
|
||||
},
|
||||
|
||||
beginKiosk() {
|
||||
this.resetIdleTimer();
|
||||
|
||||
if (this.queues.length === 0) {
|
||||
this.step = 'select';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
init() {
|
||||
if (this.queues.length === 1) {
|
||||
this.queueId = this.queues[0].id;
|
||||
|
||||
if (! this.collectName && ! this.collectPhone) {
|
||||
this.issue();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
this.step = 'select';
|
||||
},
|
||||
|
||||
goBack() {
|
||||
this.resetIdleTimer();
|
||||
this.error = null;
|
||||
|
||||
if (this.step === 'details') {
|
||||
this.step = 'select';
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.step === 'select') {
|
||||
this.step = 'welcome';
|
||||
this.queueId = null;
|
||||
}
|
||||
},
|
||||
|
||||
selectQueue(id) {
|
||||
this.queueId = id;
|
||||
this.error = null;
|
||||
this.resetIdleTimer();
|
||||
|
||||
if (! this.collectName && ! this.collectPhone) {
|
||||
this.issue();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -82,6 +144,7 @@ export function registerKioskFlow(Alpine) {
|
||||
|
||||
this.loading = true;
|
||||
this.error = null;
|
||||
this.resetIdleTimer();
|
||||
|
||||
try {
|
||||
const res = await fetch(this.issueUrl, {
|
||||
@@ -105,7 +168,7 @@ export function registerKioskFlow(Alpine) {
|
||||
|
||||
this.ticket = data.data;
|
||||
this.step = 'ticket';
|
||||
this.scheduleReset();
|
||||
this.resetIdleTimer();
|
||||
} catch (e) {
|
||||
this.error = e.message || 'Something went wrong';
|
||||
} finally {
|
||||
@@ -113,41 +176,16 @@ export function registerKioskFlow(Alpine) {
|
||||
}
|
||||
},
|
||||
|
||||
scheduleReset() {
|
||||
this.clearResetTimers();
|
||||
this.resetCountdown = this.resetSeconds;
|
||||
|
||||
this.countdownTimer = setInterval(() => {
|
||||
this.resetCountdown -= 1;
|
||||
if (this.resetCountdown <= 0) {
|
||||
this.clearResetTimers();
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
this.resetTimer = setTimeout(() => this.reset(), this.resetSeconds * 1000);
|
||||
},
|
||||
|
||||
clearResetTimers() {
|
||||
if (this.resetTimer) {
|
||||
clearTimeout(this.resetTimer);
|
||||
this.resetTimer = null;
|
||||
}
|
||||
if (this.countdownTimer) {
|
||||
clearInterval(this.countdownTimer);
|
||||
this.countdownTimer = null;
|
||||
}
|
||||
this.resetCountdown = null;
|
||||
},
|
||||
|
||||
reset() {
|
||||
this.clearResetTimers();
|
||||
this.step = 'select';
|
||||
this.queueId = this.queues.length === 1 ? this.queues[0].id : null;
|
||||
this.clearIdleTimer();
|
||||
this.step = 'welcome';
|
||||
this.queueId = null;
|
||||
this.customerName = '';
|
||||
this.customerPhone = '';
|
||||
this.ticket = null;
|
||||
this.error = null;
|
||||
this.loading = false;
|
||||
this.startTimer();
|
||||
},
|
||||
|
||||
formatWait(seconds) {
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en" class="h-full overflow-hidden bg-slate-50 font-sans">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
|
||||
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||
<meta name="theme-color" content="#f8fafc">
|
||||
<meta name="theme-color" content="#f1f5f9">
|
||||
<title>{{ $device->name }} · Queue Kiosk</title>
|
||||
<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">
|
||||
@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; }
|
||||
</style>
|
||||
</head>
|
||||
<body
|
||||
class="qms-kiosk font-sans text-slate-900"
|
||||
class="min-h-screen bg-slate-100 font-sans text-slate-900 antialiased"
|
||||
x-data="kioskFlow(@js([
|
||||
'issueUrl' => route('qms.kiosk.device.issue', $device->device_token),
|
||||
'csrf' => csrf_token(),
|
||||
@@ -24,152 +31,192 @@
|
||||
'collectName' => $settings['collect_name'],
|
||||
'collectPhone' => $settings['collect_phone'],
|
||||
'resetSeconds' => $settings['reset_seconds'],
|
||||
'welcomeMessage' => $settings['welcome_message'] ?? 'Tap a service below to get your ticket',
|
||||
'welcomeMessage' => $settings['welcome_message'] ?? null,
|
||||
]))"
|
||||
x-init="init()"
|
||||
x-init="startTimer()"
|
||||
>
|
||||
<div class="qms-kiosk__shell">
|
||||
<header class="shrink-0 border-b border-slate-200/80 bg-white/90 px-5 py-4 backdrop-blur-md lg:px-8">
|
||||
<div class="mx-auto flex max-w-3xl items-center justify-between gap-4">
|
||||
<img
|
||||
src="{{ $logoUrl }}"
|
||||
alt="{{ $logoAlt }}"
|
||||
class="h-8 w-auto max-w-[min(100%,220px)] object-contain object-left lg:h-10"
|
||||
>
|
||||
<p class="text-right text-sm font-medium text-slate-500">{{ $device->name }}</p>
|
||||
<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>
|
||||
</header>
|
||||
|
||||
<main class="qms-kiosk__main px-5 py-6 lg:px-8">
|
||||
<div class="w-full max-w-2xl">
|
||||
{{-- Queue selection --}}
|
||||
<div x-show="step === 'select'" x-cloak>
|
||||
<div class="text-center">
|
||||
<h1 class="text-2xl font-semibold tracking-tight text-slate-900 lg:text-3xl">Welcome</h1>
|
||||
<p class="mt-2 text-base text-slate-500" x-text="welcomeMessage"></p>
|
||||
<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('qms.partials.kiosk-brand', ['logoUrl' => $logoUrl, 'logoAlt' => $logoAlt])
|
||||
|
||||
<div class="relative mx-auto flex min-h-screen max-w-3xl flex-col pt-20">
|
||||
{{-- Welcome --}}
|
||||
<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">Queue</p>
|
||||
<h1 class="mt-3 text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
|
||||
Welcome to {{ $organization?->name ?? 'Queue' }}
|
||||
</h1>
|
||||
<p
|
||||
class="mt-4 text-lg text-slate-500"
|
||||
x-text="welcomeMessage || 'Tap below to get your ticket'"
|
||||
></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"
|
||||
:disabled="loading"
|
||||
>
|
||||
<span x-show="!loading">Tap to begin</span>
|
||||
<span x-show="loading">Please wait…</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
{{-- Service selection --}}
|
||||
<template x-if="step === 'select'">
|
||||
<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('qms.partials.kiosk-back')
|
||||
</div>
|
||||
|
||||
<div class="max-w-xl text-center">
|
||||
<h2 class="text-2xl font-bold text-slate-900 sm:text-3xl">Choose a service</h2>
|
||||
<p class="mt-2 text-slate-500">Select an option to get your ticket</p>
|
||||
</div>
|
||||
|
||||
<template x-if="queues.length === 1">
|
||||
<div class="mt-8">
|
||||
<div class="mt-12 w-full max-w-lg">
|
||||
<button
|
||||
type="button"
|
||||
class="qms-kiosk__ticket-card w-full transition hover:ring-2 hover:ring-indigo-200"
|
||||
class="group flex w-full 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]"
|
||||
:disabled="loading"
|
||||
@click="selectQueue(queues[0].id)"
|
||||
>
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-slate-500">Get ticket for</p>
|
||||
<p class="mt-3 text-2xl font-semibold text-slate-900" x-text="queues[0].name"></p>
|
||||
<p class="mt-6 text-sm font-semibold text-indigo-600" x-show="!loading">Tap here to continue</p>
|
||||
<p class="mt-6 text-sm font-semibold text-indigo-600" x-show="loading">Issuing your ticket…</p>
|
||||
<span
|
||||
class="flex h-16 w-16 items-center justify-center rounded-2xl text-xl font-bold text-white transition group-hover:brightness-110"
|
||||
:style="'background-color:' + (queues[0].color || '#4f46e5')"
|
||||
x-text="queues[0].prefix"
|
||||
></span>
|
||||
<span class="mt-5 text-xl font-bold text-slate-900" x-text="queues[0].name"></span>
|
||||
<span class="mt-1 text-sm text-slate-500">Tap to get ticket</span>
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="queues.length > 1">
|
||||
<div class="qms-kiosk__queue-grid mt-8">
|
||||
<div class="mt-12 grid w-full max-w-2xl gap-4 sm:grid-cols-2">
|
||||
<template x-for="queue in queues" :key="queue.id">
|
||||
<button
|
||||
type="button"
|
||||
class="qms-kiosk__queue-btn"
|
||||
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]"
|
||||
:disabled="loading"
|
||||
@click="selectQueue(queue.id)"
|
||||
>
|
||||
<span
|
||||
class="qms-kiosk__queue-mark"
|
||||
class="flex h-16 w-16 items-center justify-center rounded-2xl text-xl font-bold text-white transition group-hover:brightness-110"
|
||||
:style="'background-color:' + (queue.color || '#4f46e5')"
|
||||
x-text="queue.prefix"
|
||||
></span>
|
||||
<span class="min-w-0">
|
||||
<span class="block truncate text-lg font-semibold text-slate-900" x-text="queue.name"></span>
|
||||
<span class="mt-0.5 block text-sm text-slate-500">Tap to get ticket</span>
|
||||
</span>
|
||||
<span class="mt-5 text-xl font-bold text-slate-900" x-text="queue.name"></span>
|
||||
<span class="mt-1 text-sm text-slate-500">Tap to get ticket</span>
|
||||
</button>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template x-if="queues.length === 0">
|
||||
<div class="qms-kiosk__ticket-card mt-8">
|
||||
<div class="mt-12 w-full max-w-lg rounded-3xl border border-slate-200 bg-white p-8 text-center shadow-sm">
|
||||
<p class="text-lg font-semibold text-slate-900">No services available</p>
|
||||
<p class="mt-2 text-sm text-slate-500">Ask a staff member for assistance.</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<p x-show="error" class="mt-4 text-center text-sm text-rose-600" x-text="error"></p>
|
||||
<p x-show="error" x-cloak class="mt-6 max-w-lg rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="error"></p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
{{-- Optional details --}}
|
||||
<div x-show="step === 'details'" x-cloak>
|
||||
<div class="qms-kiosk__ticket-card">
|
||||
<h2 class="text-xl font-semibold text-slate-900">Your details</h2>
|
||||
<p class="mt-1 text-sm text-slate-500">Optional information to help staff assist you.</p>
|
||||
<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('qms.partials.kiosk-back')
|
||||
</div>
|
||||
|
||||
<div class="mt-6 space-y-4 text-left">
|
||||
<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>Your details</span>
|
||||
<span>Step 1 of 1</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-indigo-600 to-violet-600"></div>
|
||||
</div>
|
||||
<p class="mt-2 text-sm font-semibold text-slate-900">Tell us a little about yourself</p>
|
||||
</div>
|
||||
|
||||
<p x-show="error" x-cloak class="mb-4 rounded-xl bg-red-50 px-4 py-3 text-sm text-red-700" x-text="error"></p>
|
||||
|
||||
<div class="space-y-4">
|
||||
<div x-show="collectName">
|
||||
<label class="block text-sm font-medium text-slate-700">Name</label>
|
||||
<label class="block text-sm font-medium text-slate-600">Name</label>
|
||||
<input
|
||||
type="text"
|
||||
x-model="customerName"
|
||||
class="mt-1 w-full rounded-xl border-slate-300 text-base shadow-sm"
|
||||
placeholder="Your name"
|
||||
autocomplete="name"
|
||||
class="mt-1 w-full rounded-xl border-slate-200 px-4 py-4 text-lg"
|
||||
>
|
||||
</div>
|
||||
<div x-show="collectPhone">
|
||||
<label class="block text-sm font-medium text-slate-700">Phone</label>
|
||||
<label class="block text-sm font-medium text-slate-600">Phone</label>
|
||||
<input
|
||||
type="tel"
|
||||
x-model="customerPhone"
|
||||
class="mt-1 w-full rounded-xl border-slate-300 text-base shadow-sm"
|
||||
placeholder="Phone number"
|
||||
autocomplete="tel"
|
||||
class="mt-1 w-full rounded-xl border-slate-200 px-4 py-4 text-lg"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p x-show="error" class="mt-4 text-sm text-rose-600" x-text="error"></p>
|
||||
|
||||
<div class="mt-6 flex gap-3">
|
||||
<button type="button" class="btn-secondary flex-1" @click="reset()" :disabled="loading">Back</button>
|
||||
<button type="button" class="btn-primary flex-1" @click="issue()" :disabled="loading">
|
||||
<button
|
||||
type="button"
|
||||
class="btn-primary mt-6 w-full py-3"
|
||||
@click="issue()"
|
||||
:disabled="loading"
|
||||
>
|
||||
<span x-show="!loading">Get ticket</span>
|
||||
<span x-show="loading">Please wait…</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
{{-- Ticket issued --}}
|
||||
<div x-show="step === 'ticket' && ticket" x-cloak>
|
||||
<div class="qms-kiosk__ticket-card">
|
||||
<p class="text-xs font-semibold uppercase tracking-[0.2em] text-slate-500">Your ticket number</p>
|
||||
<p class="qms-kiosk__ticket-number mt-4" x-text="ticket?.ticket_number"></p>
|
||||
<template x-if="step === 'ticket' && ticket">
|
||||
<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 text-indigo-600">✓</div>
|
||||
<h2 class="text-2xl font-bold text-slate-900">You're in the queue!</h2>
|
||||
<p class="mt-4 text-sm font-semibold uppercase tracking-wider text-slate-500">Your ticket number</p>
|
||||
<p class="qms-kiosk__ticket-number mt-2" x-text="ticket?.ticket_number"></p>
|
||||
|
||||
<div class="mt-6 space-y-2 text-sm text-slate-600">
|
||||
<div class="mt-4 space-y-1 text-sm text-slate-600">
|
||||
<p x-show="ticket?.position">
|
||||
<span class="font-medium text-slate-900" x-text="ticket?.position"></span>
|
||||
<span class="font-semibold text-slate-900" x-text="ticket?.position"></span>
|
||||
<span> ahead of you</span>
|
||||
</p>
|
||||
<p x-show="formatWait(ticket?.estimated_wait_seconds)" x-text="formatWait(ticket?.estimated_wait_seconds)"></p>
|
||||
</div>
|
||||
|
||||
<p class="mt-8 text-base text-slate-600">Please take a seat. Your number will be called on the display.</p>
|
||||
<p class="mt-6 text-sm text-slate-500">Please take a seat. Your number will be called on the display.</p>
|
||||
|
||||
<button type="button" class="btn-primary mt-8 w-full" @click="reset()">
|
||||
<button type="button" class="btn-primary btn-primary-lg mt-8 w-full" @click="reset()">
|
||||
Done
|
||||
<span x-show="resetCountdown" x-text="'(' + resetCountdown + 's)'"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<footer class="shrink-0 border-t border-slate-200 bg-white/80 px-5 py-3 lg:px-8">
|
||||
<div class="mx-auto flex max-w-3xl items-center justify-center gap-2">
|
||||
<span class="text-[10px] font-medium uppercase tracking-wider text-slate-400">Powered by</span>
|
||||
<img src="{{ $poweredByLogoUrl }}" alt="Ladill Queue" class="h-3.5 w-auto opacity-80">
|
||||
</div>
|
||||
</footer>
|
||||
</template>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<button
|
||||
type="button"
|
||||
@click="goBack()"
|
||||
class="flex h-11 w-11 items-center justify-center rounded-full border border-slate-200 bg-white text-slate-700 shadow-sm transition hover:border-indigo-200 hover:bg-indigo-50 active:scale-95"
|
||||
aria-label="Go back"
|
||||
>
|
||||
<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="M15.75 19.5 8.25 12l7.5-7.5" />
|
||||
</svg>
|
||||
</button>
|
||||
@@ -0,0 +1,7 @@
|
||||
<div class="fixed left-0 top-0 z-20 p-6">
|
||||
<img
|
||||
src="{{ $logoUrl }}"
|
||||
alt="{{ $logoAlt }}"
|
||||
class="h-8 w-auto max-w-[220px] object-contain object-left"
|
||||
>
|
||||
</div>
|
||||
@@ -73,9 +73,9 @@ class KioskDeviceTest extends TestCase
|
||||
$response = $this->get(route('qms.kiosk.device', $token));
|
||||
|
||||
$response->assertOk();
|
||||
$response->assertSee('Welcome', false);
|
||||
$response->assertSee($queue->name, false);
|
||||
$response->assertSee('Powered by', false);
|
||||
$response->assertSee('Welcome to Test Org', false);
|
||||
$response->assertSee('Tap to begin', false);
|
||||
$response->assertSee('Queue', false);
|
||||
}
|
||||
|
||||
public function test_kiosk_issues_ticket_for_allowed_queue(): void
|
||||
|
||||
Reference in New Issue
Block a user