forked from jsnbuchanan/crowd-funder-for-time-pwa
- Enhance JWT extraction with unified path handling and validation - Add debouncing to prevent duplicate scans - Improve error handling and logging throughout QR flow - Add proper TypeScript interfaces for QR scan results - Implement mobile app lifecycle handlers (pause/resume) - Enhance logging with structured data and consistent levels - Clean up scanner resources properly on component destroy - Split contact handling into separate method for better organization - Add proper type for UserNameDialog ref This commit improves the reliability and maintainability of the QR code scanning functionality while adding better error handling and logging.
180 lines
4.8 KiB
Vue
180 lines
4.8 KiB
Vue
<!-- QRScannerDialog.vue -->
|
|
<template>
|
|
<div
|
|
v-if="visible && !isNativePlatform"
|
|
class="dialog-overlay z-[60] fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center"
|
|
>
|
|
<div
|
|
class="dialog relative bg-white rounded-lg shadow-xl max-w-lg w-full mx-4"
|
|
>
|
|
<!-- Header -->
|
|
<div class="p-4 border-b border-gray-200">
|
|
<h3 class="text-lg font-medium text-gray-900">Scan QR Code</h3>
|
|
<button
|
|
class="absolute top-4 right-4 text-gray-400 hover:text-gray-500"
|
|
aria-label="Close dialog"
|
|
@click="close"
|
|
>
|
|
<svg
|
|
class="h-6 w-6"
|
|
fill="none"
|
|
viewBox="0 0 24 24"
|
|
stroke="currentColor"
|
|
>
|
|
<path
|
|
stroke-linecap="round"
|
|
stroke-linejoin="round"
|
|
stroke-width="2"
|
|
d="M6 18L18 6M6 6l12 12"
|
|
/>
|
|
</svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Scanner -->
|
|
<div class="p-4">
|
|
<div
|
|
v-if="useQRReader && !isNativePlatform"
|
|
class="relative aspect-square"
|
|
>
|
|
<qrcode-stream
|
|
:camera="options?.camera === 'front' ? 'user' : 'environment'"
|
|
@decode="onDecode"
|
|
@init="onInit"
|
|
/>
|
|
<div
|
|
class="absolute inset-0 border-2 border-blue-500 opacity-50 pointer-events-none"
|
|
></div>
|
|
</div>
|
|
<div v-else class="text-center py-8">
|
|
<p class="text-gray-500">
|
|
{{
|
|
isNativePlatform
|
|
? "Using native camera scanner..."
|
|
: "QR code scanning is not supported in this browser."
|
|
}}
|
|
</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>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<script lang="ts">
|
|
import { Component, Prop, Vue } from "vue-facing-decorator";
|
|
import { QrcodeStream } from "vue-qrcode-reader";
|
|
import { QRScannerOptions } from "@/services/QRScanner/types";
|
|
import { logger } from "@/utils/logger";
|
|
import { Capacitor } from "@capacitor/core";
|
|
|
|
interface ScanProps {
|
|
onScan: (result: string) => void;
|
|
onError?: (error: Error) => void;
|
|
options?: QRScannerOptions;
|
|
}
|
|
|
|
@Component({
|
|
components: {
|
|
QrcodeStream,
|
|
},
|
|
})
|
|
export default class QRScannerDialog extends Vue {
|
|
@Prop({ type: Function, required: true }) onScan!: ScanProps["onScan"];
|
|
@Prop({ type: Function }) onError?: ScanProps["onError"];
|
|
@Prop({ type: Object }) options?: ScanProps["options"];
|
|
|
|
visible = true;
|
|
error: string | null = null;
|
|
useQRReader = __USE_QR_READER__;
|
|
isNativePlatform =
|
|
Capacitor.isNativePlatform() ||
|
|
__IS_MOBILE__ ||
|
|
Capacitor.getPlatform() === "android" ||
|
|
Capacitor.getPlatform() === "ios";
|
|
|
|
created() {
|
|
logger.log("QRScannerDialog platform detection:", {
|
|
capacitorNative: Capacitor.isNativePlatform(),
|
|
isMobile: __IS_MOBILE__,
|
|
platform: Capacitor.getPlatform(),
|
|
useQRReader: this.useQRReader,
|
|
isNativePlatform: this.isNativePlatform,
|
|
});
|
|
|
|
// If on native platform, close immediately and don't initialize web scanner
|
|
if (this.isNativePlatform) {
|
|
logger.log("Closing QR dialog on native platform");
|
|
this.$nextTick(() => this.close());
|
|
}
|
|
}
|
|
|
|
async onInit(promise: Promise<void>): Promise<void> {
|
|
// Don't initialize on mobile platforms
|
|
if (this.isNativePlatform) {
|
|
logger.log("Skipping web scanner initialization on native platform");
|
|
return;
|
|
}
|
|
|
|
try {
|
|
await promise;
|
|
this.error = null;
|
|
} catch (error) {
|
|
const wrappedError =
|
|
error instanceof Error ? error : new Error(String(error));
|
|
this.error = wrappedError.message;
|
|
if (this.onError) {
|
|
this.onError(wrappedError);
|
|
}
|
|
logger.error("Error initializing QR scanner:", wrappedError);
|
|
}
|
|
}
|
|
|
|
onDecode(result: string): void {
|
|
try {
|
|
this.onScan(result);
|
|
this.close();
|
|
} catch (error) {
|
|
const wrappedError =
|
|
error instanceof Error ? error : new Error(String(error));
|
|
this.error = wrappedError.message;
|
|
if (this.onError) {
|
|
this.onError(wrappedError);
|
|
}
|
|
logger.error("Error handling QR scan result:", wrappedError);
|
|
}
|
|
}
|
|
|
|
async close(): Promise<void> {
|
|
this.visible = false;
|
|
await this.$nextTick();
|
|
if (this.$el && this.$el.parentNode) {
|
|
this.$el.parentNode.removeChild(this.$el);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style scoped>
|
|
.dialog-overlay {
|
|
backdrop-filter: blur(4px);
|
|
}
|
|
|
|
.qrcode-stream {
|
|
width: 100%;
|
|
height: 100%;
|
|
}
|
|
</style>
|