forked from jsnbuchanan/crowd-funder-for-time-pwa
feat(qr-scanner): Enhance QR scanner dialog with user feedback
- Add status messages for different scanning states (initializing, scanning, error) - Add visual feedback with color-coded scanning frame and animations - Add camera switch button for toggling between front/back cameras - Add scanning instructions and tips in the footer - Add retry button for error recovery - Improve error handling and state management - Add browser compatibility message for unsupported browsers This change improves the user experience by providing clear visual feedback and guidance during the QR code scanning process.
This commit is contained in:
@@ -37,6 +37,16 @@
|
||||
v-if="useQRReader && !isNativePlatform"
|
||||
class="relative aspect-square"
|
||||
>
|
||||
<!-- Status Message -->
|
||||
<div
|
||||
class="absolute top-0 left-0 right-0 bg-black bg-opacity-50 text-white text-center py-2 z-10"
|
||||
>
|
||||
<p v-if="isInitializing">Initializing camera...</p>
|
||||
<p v-else-if="isScanning">Position QR code in the frame</p>
|
||||
<p v-else-if="error" class="text-red-300">{{ error }}</p>
|
||||
<p v-else>Ready to scan</p>
|
||||
</div>
|
||||
|
||||
<qrcode-stream
|
||||
:camera="options?.camera === 'front' ? 'user' : 'environment'"
|
||||
@decode="onDecode"
|
||||
@@ -44,9 +54,44 @@
|
||||
@detect="onDetect"
|
||||
@error="onError"
|
||||
/>
|
||||
|
||||
<!-- Scanning Frame -->
|
||||
<div
|
||||
class="absolute inset-0 border-2 border-blue-500 opacity-50 pointer-events-none"
|
||||
class="absolute inset-0 border-2"
|
||||
:class="{
|
||||
'border-blue-500': !error && !isScanning,
|
||||
'border-green-500 animate-pulse': isScanning,
|
||||
'border-red-500': error
|
||||
}"
|
||||
style="opacity: 0.5; pointer-events: none;"
|
||||
></div>
|
||||
|
||||
<!-- Camera Switch Button -->
|
||||
<button
|
||||
@click="toggleCamera"
|
||||
class="absolute bottom-4 right-4 bg-white rounded-full p-2 shadow-lg"
|
||||
title="Switch camera"
|
||||
>
|
||||
<svg
|
||||
class="h-6 w-6 text-gray-600"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 9a2 2 0 012-2h.93a2 2 0 001.664-.89l.812-1.22A2 2 0 0110.07 4h3.86a2 2 0 011.664.89l.812 1.22A2 2 0 0018.07 7H19a2 2 0 012 2v9a2 2 0 01-2 2H5a2 2 0 01-2-2V9z"
|
||||
/>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M15 13a3 3 0 11-6 0 3 3 0 016 0z"
|
||||
/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div v-else class="text-center py-8">
|
||||
<p class="text-gray-500">
|
||||
@@ -56,19 +101,43 @@
|
||||
: "QR code scanning is not supported in this browser."
|
||||
}}
|
||||
</p>
|
||||
<p v-if="!isNativePlatform" class="text-sm text-gray-400 mt-2">
|
||||
Please ensure you're using a modern browser with camera access.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Footer -->
|
||||
<div class="p-4 border-t border-gray-200">
|
||||
<p v-if="error" class="text-red-500 text-sm mb-4">{{ error }}</p>
|
||||
<div class="flex justify-end">
|
||||
<button
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200"
|
||||
@click="close"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<div class="flex flex-col space-y-4">
|
||||
<!-- Instructions -->
|
||||
<div class="text-sm text-gray-600">
|
||||
<ul class="list-disc list-inside space-y-1">
|
||||
<li>Ensure the QR code is well-lit and in focus</li>
|
||||
<li>Hold your device steady</li>
|
||||
<li>The QR code should fit within the scanning frame</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Error Message -->
|
||||
<p v-if="error" class="text-red-500 text-sm">{{ error }}</p>
|
||||
|
||||
<!-- Actions -->
|
||||
<div class="flex justify-end space-x-2">
|
||||
<button
|
||||
v-if="error"
|
||||
class="px-4 py-2 bg-blue-500 text-white rounded-md hover:bg-blue-600"
|
||||
@click="retryScanning"
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
<button
|
||||
class="px-4 py-2 bg-gray-100 text-gray-700 rounded-md hover:bg-gray-200"
|
||||
@click="close"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -106,6 +175,10 @@ export default class QRScannerDialog extends Vue {
|
||||
__IS_MOBILE__ ||
|
||||
Capacitor.getPlatform() === "android" ||
|
||||
Capacitor.getPlatform() === "ios";
|
||||
|
||||
isInitializing = true;
|
||||
isScanning = false;
|
||||
preferredCamera: 'user' | 'environment' = 'environment';
|
||||
|
||||
created() {
|
||||
logger.log("QRScannerDialog platform detection:", {
|
||||
@@ -133,10 +206,13 @@ export default class QRScannerDialog extends Vue {
|
||||
return;
|
||||
}
|
||||
|
||||
this.isInitializing = true;
|
||||
this.error = null;
|
||||
logger.log("Initializing QR scanner...");
|
||||
|
||||
try {
|
||||
await promise;
|
||||
this.error = null;
|
||||
this.isInitializing = false;
|
||||
logger.log("QR scanner initialized successfully");
|
||||
} catch (error) {
|
||||
const wrappedError =
|
||||
@@ -150,10 +226,13 @@ export default class QRScannerDialog extends Vue {
|
||||
stack: wrappedError.stack,
|
||||
name: wrappedError.name,
|
||||
});
|
||||
} finally {
|
||||
this.isInitializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
onDetect(promise: Promise<any>): void {
|
||||
this.isScanning = true;
|
||||
logger.log("QR code detected, processing...");
|
||||
promise
|
||||
.then((result) => {
|
||||
@@ -164,6 +243,9 @@ export default class QRScannerDialog extends Vue {
|
||||
error: error instanceof Error ? error.message : String(error),
|
||||
stack: error instanceof Error ? error.stack : undefined,
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
this.isScanning = false;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -188,6 +270,7 @@ export default class QRScannerDialog extends Vue {
|
||||
}
|
||||
|
||||
onError(error: Error): void {
|
||||
this.isScanning = false;
|
||||
logger.error("QR scanner error:", {
|
||||
error: error.message,
|
||||
stack: error.stack,
|
||||
@@ -199,6 +282,16 @@ export default class QRScannerDialog extends Vue {
|
||||
}
|
||||
}
|
||||
|
||||
toggleCamera(): void {
|
||||
this.preferredCamera = this.preferredCamera === 'user' ? 'environment' : 'user';
|
||||
}
|
||||
|
||||
retryScanning(): void {
|
||||
this.error = null;
|
||||
this.isInitializing = true;
|
||||
// The QR scanner component will automatically reinitialize
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
logger.log("Closing QR scanner dialog");
|
||||
this.visible = false;
|
||||
@@ -219,4 +312,20 @@ export default class QRScannerDialog extends Vue {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.75;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.animate-pulse {
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user