Add QR code error handling, mobile control enhancements, and POS flow updates:

- Added SweetAlert error notifications for untrusted QR codes in `LoginQR.vue`.
- Updated `LoginForm.vue` to hide the QR code login button on desktop.
- Enhanced POS mobile flow in `PosDepartmentStepMobileFlow.vue` with sound feedback for successful scans.
- Implemented camera delay logic for improved capture management in `PosDepartmentStepMobile1.vue`.
- Removed unused element in `PWADownload.vue` and adjusted geolocation visibility in `PosDepartmentStepMobile1Location.vue`.
This commit is contained in:
Jeppe Bundgaard
2025-08-27 16:09:56 +02:00
parent 36b97b67d5
commit cf0e5e8a01
6 changed files with 62 additions and 13 deletions
@@ -13,7 +13,7 @@ import PosDepartmentStep1MobileManualInput
from "@/components/displays/department/pos/steps/mobile/views/PosDepartmentStep1MobileManualInput.vue";
import PosDepartmentStepMobileButtonNextStep
from "@/components/displays/department/pos/steps/mobile/elements/PosDepartmentStepMobileButtonNextStep.vue";
import { manualInput, vehicles, camera } from "./objects/PosDepartmentStepMobileFlow.vue";
import { manualInput, vehicles, camera, sounds } from "./objects/PosDepartmentStepMobileFlow.vue";
import { PosSearchResult } from "./objects/PosSearchResult.vue";
import RegistrationNumberSearchResult from "@/components/models/pos/step1/RegistrationNumberSearchResult.vue";
import UnknownCustomer from "@/components/viewport/elements/icons/UnknownCustomer.vue";
@@ -54,6 +54,10 @@ const handleLPRResult = () => {
customerStatus: 'unknown'
});
if (searchResultIndex === vehicles.activeVehicleIndex.value) {
// Only play the sound if the vehicle registration number is being set / changed.
if (vehicles.getActiveVehicle()?.reg !== latestLPRResponse.value?.license_plate_number) {
sounds.play(sounds.list.value.onAfterSuccessfulScan)
}
vehicles.select(searchResultIndex, {
reg: latestLPRResponse.value?.license_plate_number || "",
status: 'unknown'
@@ -63,6 +67,11 @@ const handleLPRResult = () => {
const parseImage = (image: string) => {
// Check if the time since the last successful parse is enough
if (!camera.hasDelayAfterSuccessPassed()) {
console.log("Skipping parsing due to delay after success.");
return;
}
if (lastParsedImage.value === image) {
// If the image is the same as the last parsed one, skip parsing
console.log("Skipping parsing for the same image.");
@@ -79,6 +88,8 @@ const parseImage = (image: string) => {
).then((response) => {
latestLPRResponse.value = response.data.data as LPRResponse;
console.log("LPR Response:", latestLPRResponse.value);
// Set the last successful capture time
camera.setLastSuccess();
// Handle parsed result.
handleLPRResult()
@@ -26,17 +26,19 @@ watch(coords, (newCoords) => {
</script>
<template>
<div v-show="false">
<div v-show="true">
<p>Latitude: {{ coords.latitude }}</p>
<p>Longitude: {{ coords.longitude }}</p>
<p>Located At: {{ new Date(locatedAt).toLocaleString() }}</p>
<p v-if="error">Error: {{ error.message }}</p>
<button @click="resume">Resume</button>
<button @click="pause">Pause</button>
<button @click="onUpdate({
latitude: 55.613448,
longitude: 12.495521
})">Update Now</button>
<template v-if="false">
<button @click="resume">Resume</button>
<button @click="pause">Pause</button>
<button @click="onUpdate({
latitude: 55.613448,
longitude: 12.495521
})">Update Now</button>
</template>
</div>
</template>
@@ -459,8 +459,33 @@ const productList = {
// Define the reactive properties
const latestImage = ref<string | null>(null);
const isCameraMounted = ref<boolean>(false);
const cameraImageCaptureDelayInitial = ref<number>(3000); // Initial delay for camera image capture in milliseconds (First capture)
const cameraImageCaptureDelaySubsequent = ref<number>(10000); // Further captures delay in milliseconds (Every capture after the first one)
const cameraImageCaptureDelayInitial = ref<number>(2000); // Initial delay for camera image capture in milliseconds (First capture)
const cameraImageCaptureDelaySubsequent = ref<number>(1000); // Further captures delay in milliseconds (Every capture after the first one)
const cameraImageCaptureDelayAfterSuccess = ref<number>(5000); // Delay after a successful capture in milliseconds (After a successful capture, before the next one)
const cameraImageCaptureLastSuccess = ref<number | null>(null); // Timestamp of the last successful capture
// Function to set the timestamp of the last successful capture
const setCameraImageCaptureLastSuccess = (timestamp?: number | null) => {
cameraImageCaptureLastSuccess.value = ( timestamp !== undefined ? timestamp : Date.now() );
}
// Function to get the timestamp of the last successful capture
const getCameraImageCaptureLastSuccess = () => {
return cameraImageCaptureLastSuccess.value;
}
// Function to get the delay after a successful capture
const getCameraImageCaptureDelayAfterSuccess = () => {
return cameraImageCaptureDelayAfterSuccess.value;
}
// Function to set the delay after a successful capture
const setCameraImageCaptureDelayAfterSuccess = (delay: number) => {
cameraImageCaptureDelayAfterSuccess.value = delay;
}
// Function to check if the delay after a successful capture has passed
const hasCameraImageCaptureDelayAfterSuccessPassed = (): boolean => {
if (cameraImageCaptureLastSuccess.value === null) return true; // No successful capture yet
const currentTime = Date.now();
return (currentTime - cameraImageCaptureLastSuccess.value) > cameraImageCaptureDelayAfterSuccess.value;
}
// Function to retrieve the latest image frame.
const getLatestImage = async () => {
return latestImage.value;
@@ -503,7 +528,13 @@ const camera = {
clearLatestImage,
clearMounted: clearCameraMounted,
getImageCaptureDelay: getCameraImageCaptureDelay,
setImageCaptureDelay: setCameraImageCaptureDelay
setImageCaptureDelay: setCameraImageCaptureDelay,
// Capture delay after success functions
getImageCaptureDelayAfterSuccess: getCameraImageCaptureDelayAfterSuccess,
setImageCaptureDelayAfterSuccess: setCameraImageCaptureDelayAfterSuccess,
setLastSuccess: setCameraImageCaptureLastSuccess,
getLastSuccess: getCameraImageCaptureLastSuccess,
hasDelayAfterSuccessPassed: hasCameraImageCaptureDelayAfterSuccessPassed,
}
/** Metadata */
+1 -1
View File
@@ -94,7 +94,7 @@ SessionUser.auth.reCAPTCHA.preCheck.get().then((response) => {
<div class="control">
<div class="buttons">
<LoadButtonWhileAwait :loadFunction="login" class="button is-dark is-fullwidth" id="login-button">Login</LoadButtonWhileAwait>
<LoadButtonWhileAwait :loadFunction="() => router.push('/login/qr')" class="button is-dark is-outlined is-fullwidth" id="qr-login-button">
<LoadButtonWhileAwait :loadFunction="() => router.push('/login/qr')" class="button is-dark is-outlined is-fullwidth is-hidden-desktop" id="qr-login-button">
<!-- QR code icon -->
<span class="icon">
<i class="fas fa-qrcode"></i>
+6
View File
@@ -9,6 +9,7 @@ import PosDepartmentStepMobileFixedBottomControl
import { QrcodeStream, QrcodeDropZone, QrcodeCapture } from 'vue-qrcode-reader'
import { sounds } from "@/components/displays/department/pos/steps/mobile/objects/PosDepartmentStepMobileFlow.vue";
import PageLoader from "@/components/global/PageLoader.vue";
import Swal from "sweetalert2";
const isProcessingQRCode = ref(false); // State to indicate if QR code is being processed
const playCaptureSound = () => {
@@ -52,6 +53,11 @@ const isValidUrl = (string: string) => {
// Check if the url is from the same origin
if (!string.startsWith(window.location.origin)) {
console.log("URL is not from the same origin:", string, window.location.origin);
Swal.fire({
icon: 'error',
title: 'Untrusted QR Code',
text: 'The scanned QR code is not from a trusted source.',
});
return false;
}
// Check if the url contains the token parameter
-1
View File
@@ -113,7 +113,6 @@ onUnmounted(() => {
<span v-if="isInstalled">Pleno is installed</span>
<span v-else-if="isInstallable">Install Pleno</span>
<span v-else>Install Pleno through your browser menu</span>
<span> Oh well </span>
</a>
</template>
</WhiteBoxCard>