@ -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 ;
/ / T h e Q R s c a n n e r c o m p o n e n t w i l l a u t o m a t i c a l l y r e i n i t i a l i z e
}
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 2 s cubic - bezier ( 0.4 , 0 , 0.6 , 1 ) infinite ;
}
< / style >