From 4e449bc1eb80f892e1ef616022767a673484db37 Mon Sep 17 00:00:00 2001 From: isaacclad Date: Mon, 29 Jun 2026 09:30:34 +0000 Subject: [PATCH] Align kiosk visitor check-out UI with staff sign-in. Use the same two-step card, mode cards, and badge scan flow before confirming check-out. Co-authored-by: Cursor --- resources/js/kiosk-flow.js | 102 ++++++++-- .../views/frontdesk/kiosk/index.blade.php | 190 +++++++++++++----- 2 files changed, 223 insertions(+), 69 deletions(-) diff --git a/resources/js/kiosk-flow.js b/resources/js/kiosk-flow.js index 8799be4..fb3579d 100644 --- a/resources/js/kiosk-flow.js +++ b/resources/js/kiosk-flow.js @@ -31,6 +31,7 @@ export function registerKioskFlow(Alpine) { checkInUrl: config.checkInUrl || '', checkOutUrl: config.checkOutUrl || '', checkoutIdentifyMode: 'code', + checkoutFormStep: 1, checkoutForm: { badge_code: '', qr_token: '' }, checkoutQrScanner: null, checkoutQrScanning: false, @@ -191,6 +192,17 @@ export function registerKioskFlow(Alpine) { async goBack() { if (this.step === 'checkout_identify') { await this.stopCheckoutQrScanner(true); + if (this.checkoutFormStep === 2) { + this.checkoutFormStep = 1; + this.errorMessage = ''; + if (this.checkoutIdentifyMode === 'badge' && ! this.checkoutQrScanned) { + await this.$nextTick(); + await this.startCheckoutQrScanner(); + } + this.resetTimer(); + + return; + } } if (this.step === 'staff_identify') { if (this.staffFormStep === 2) { @@ -247,6 +259,7 @@ export function registerKioskFlow(Alpine) { this.result = null; this.checkoutResult = null; this.checkoutIdentifyMode = 'code'; + this.checkoutFormStep = 1; this.checkoutForm = { badge_code: '', qr_token: '' }; this.checkoutQrScanned = false; this.checkoutQrError = ''; @@ -286,18 +299,79 @@ export function registerKioskFlow(Alpine) { this.step = 'checkout_identify'; this.errorMessage = ''; this.checkoutIdentifyMode = 'code'; + this.checkoutFormStep = 1; this.checkoutForm = { badge_code: '', qr_token: '' }; this.checkoutQrScanned = false; this.checkoutQrError = ''; this.resetTimer(); }, + checkoutIdentifyStepLabel() { + return this.checkoutFormStep === 1 ? 'Identify your visit' : 'Confirm check-out'; + }, + + validateCheckoutIdentifyStep1() { + if (this.checkoutIdentifyMode === 'code') { + if (! String(this.checkoutForm.badge_code || '').trim()) { + this.showError('Please enter your badge code.'); + + return false; + } + } else if (! String(this.checkoutForm.qr_token || '').trim()) { + this.showError('Scan your badge to continue.'); + + return false; + } + + this.errorMessage = ''; + + return true; + }, + + async nextCheckoutIdentifyStep() { + if (! this.validateCheckoutIdentifyStep1()) { + return; + } + + await this.stopCheckoutQrScanner(false); + this.checkoutFormStep = 2; + this.errorMessage = ''; + this.resetTimer(); + }, + + async prevCheckoutIdentifyStep() { + if (this.checkoutFormStep === 2) { + this.checkoutFormStep = 1; + this.errorMessage = ''; + if (this.checkoutIdentifyMode === 'badge' && ! this.checkoutQrScanned) { + await this.$nextTick(); + await this.startCheckoutQrScanner(); + } + this.resetTimer(); + + return; + } + + await this.goBack(); + }, + + async checkoutIdentify() { + if (this.checkoutFormStep === 1) { + await this.nextCheckoutIdentifyStep(); + + return; + } + + await this.submitCheckOut(); + }, + async setCheckoutIdentifyMode(mode) { if (this.checkoutIdentifyMode === mode) { return; } await this.stopCheckoutQrScanner(true); this.checkoutIdentifyMode = mode; + this.checkoutFormStep = 1; this.checkoutForm = { badge_code: '', qr_token: '' }; this.checkoutQrError = ''; if (mode === 'badge') { @@ -323,7 +397,7 @@ export function registerKioskFlow(Alpine) { }, async startCheckoutQrScanner() { - if (this.checkoutQrScanning || this.checkoutQrScanned || this.checkoutIdentifyMode !== 'badge' || this.step !== 'checkout_identify') { + if (this.checkoutQrScanning || this.checkoutQrScanned || this.checkoutIdentifyMode !== 'badge' || this.step !== 'checkout_identify' || this.checkoutFormStep !== 1) { return; } @@ -349,7 +423,7 @@ export function registerKioskFlow(Alpine) { ); this.checkoutQrScanning = true; } catch (e) { - this.checkoutQrError = 'Camera unavailable. Enter your badge code instead.'; + this.checkoutQrError = 'Camera unavailable. Use an external scanner or switch to badge code.'; console.error('Checkout QR scanner failed', e); } }, @@ -363,12 +437,14 @@ export function registerKioskFlow(Alpine) { this.checkoutQrScanned = true; this.errorMessage = ''; await this.stopCheckoutQrScanner(false); - await this.submitCheckOut(); + this.checkoutFormStep = 2; + this.resetTimer(); }, async rescanCheckoutBadge() { this.checkoutQrScanned = false; this.checkoutForm.qr_token = ''; + this.checkoutFormStep = 1; this.checkoutQrError = ''; await this.$nextTick(); await this.startCheckoutQrScanner(); @@ -395,24 +471,6 @@ export function registerKioskFlow(Alpine) { } }, - validateCheckoutIdentify() { - if (this.checkoutIdentifyMode === 'code') { - if (! String(this.checkoutForm.badge_code || '').trim()) { - this.showError('Please enter your badge code.'); - - return false; - } - } else if (! String(this.checkoutForm.qr_token || '').trim()) { - this.showError('Scan your badge QR code to continue.'); - - return false; - } - - this.errorMessage = ''; - - return true; - }, - checkoutCredentials() { const body = {}; if (this.checkoutIdentifyMode === 'badge' && this.checkoutForm.qr_token.trim()) { @@ -425,7 +483,7 @@ export function registerKioskFlow(Alpine) { }, async submitCheckOut() { - if (! this.validateCheckoutIdentify()) { + if (! this.validateCheckoutIdentifyStep1()) { return; } diff --git a/resources/views/frontdesk/kiosk/index.blade.php b/resources/views/frontdesk/kiosk/index.blade.php index fd59dcd..c002b27 100644 --- a/resources/views/frontdesk/kiosk/index.blade.php +++ b/resources/views/frontdesk/kiosk/index.blade.php @@ -589,66 +589,161 @@ {{-- Visitor checkout --}}