diff --git a/app/Http/Controllers/Qms/KioskDeviceController.php b/app/Http/Controllers/Qms/KioskDeviceController.php index 0449cbd..066377b 100644 --- a/app/Http/Controllers/Qms/KioskDeviceController.php +++ b/app/Http/Controllers/Qms/KioskDeviceController.php @@ -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), ]); } diff --git a/resources/css/app.css b/resources/css/app.css index 98cbbee..76807c0 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -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; diff --git a/resources/js/kiosk-flow.js b/resources/js/kiosk-flow.js index 8e6c89e..59e7cc0 100644 --- a/resources/js/kiosk-flow.js +++ b/resources/js/kiosk-flow.js @@ -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) { diff --git a/resources/views/qms/kiosk/device.blade.php b/resources/views/qms/kiosk/device.blade.php index 453c084..05ff8bd 100644 --- a/resources/views/qms/kiosk/device.blade.php +++ b/resources/views/qms/kiosk/device.blade.php @@ -1,17 +1,24 @@ - + - + - + {{ $device->name }} · Queue Kiosk @vite(['resources/css/app.css', 'resources/js/app.js']) + -
-
-
- +
+
+
+ +
+ + @include('qms.partials.kiosk-brand', ['logoUrl' => $logoUrl, 'logoAlt' => $logoAlt]) + +
+ {{-- Welcome --}} + -
-
- {{-- Queue selection --}} -
-
-

Welcome

-

-
- - - - - - - -

+ {{-- Service selection --}} + + + {{-- Optional details --}} + -