forked from jsnbuchanan/crowd-funder-for-time-pwa
fix: update Vue template syntax and improve Vite config
- Fix Vue template syntax in App.vue by using proper event handler format - Update Vite config to properly handle ESM imports and crypto modules - Add manual chunks for better code splitting - Improve environment variable handling in vite-env.d.ts - Fix TypeScript linting errors in App.vue
This commit is contained in:
319
src/components/QRScanner/QRScannerDialog.vue
Normal file
319
src/components/QRScanner/QRScannerDialog.vue
Normal file
@@ -0,0 +1,319 @@
|
||||
<template>
|
||||
<div v-if="visible" class="dialog-overlay z-[60]">
|
||||
<div class="dialog relative">
|
||||
<div class="text-lg text-center font-light relative z-50">
|
||||
<div
|
||||
id="ViewHeading"
|
||||
class="text-center font-bold absolute top-0 left-0 right-0 px-4 py-0.5 bg-black/50 text-white leading-none"
|
||||
>
|
||||
<span v-if="state.isProcessing">{{ state.processingStatus }}</span>
|
||||
<span v-else>Scan QR Code</span>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="text-lg text-center px-2 py-0.5 leading-none absolute right-0 top-0 text-white cursor-pointer"
|
||||
@click="close()"
|
||||
>
|
||||
<font-awesome icon="xmark" class="w-[1em]" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8">
|
||||
<!-- Web QR Code Scanner -->
|
||||
<qrcode-stream
|
||||
v-if="useQRReader"
|
||||
class="w-full max-w-lg mx-auto"
|
||||
@detect="onScanDetect"
|
||||
@error="onScanError"
|
||||
/>
|
||||
|
||||
<!-- Mobile Camera Button -->
|
||||
<div v-else class="text-center mt-4">
|
||||
<button
|
||||
v-if="!state.isProcessing"
|
||||
class="bg-gradient-to-b from-blue-400 to-blue-700 shadow-[inset_0_-1px_0_0_rgba(0,0,0,0.5)] text-white px-4 py-2 rounded-md"
|
||||
@click="openMobileCamera"
|
||||
>
|
||||
Open Camera
|
||||
</button>
|
||||
<div v-else class="text-center">
|
||||
<font-awesome icon="spinner" class="fa-spin fa-3x" />
|
||||
<p class="mt-2">
|
||||
{{ state.processingDetails }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-if="state.error" class="mt-4 text-red-500 text-center">
|
||||
{{ state.error }}
|
||||
</p>
|
||||
|
||||
<p class="mt-4 text-sm text-gray-600 text-center">
|
||||
Position the QR code within the camera view to scan
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { Component, Vue } from 'vue-facing-decorator'
|
||||
import { QrcodeStream } from 'vue-qrcode-reader'
|
||||
import { reactive } from 'vue'
|
||||
import {
|
||||
BarcodeScanner,
|
||||
type ScanResult
|
||||
} from '@capacitor-mlkit/barcode-scanning'
|
||||
import type { PluginListenerHandle } from '@capacitor/core'
|
||||
import { logger } from '../../utils/logger'
|
||||
import { NotificationIface } from '../../constants/app'
|
||||
|
||||
// Declare global constants
|
||||
declare const __USE_QR_READER__: boolean
|
||||
declare const __IS_MOBILE__: boolean
|
||||
|
||||
interface AppState {
|
||||
isProcessing: boolean
|
||||
processingStatus: string
|
||||
processingDetails: string
|
||||
error: string
|
||||
}
|
||||
|
||||
@Component({
|
||||
components: {
|
||||
QrcodeStream
|
||||
}
|
||||
})
|
||||
export default class QRScannerDialog extends Vue {
|
||||
$notify!: (notification: NotificationIface, timeout?: number) => void
|
||||
|
||||
visible = false
|
||||
private scanListener: PluginListenerHandle | null = null
|
||||
private onScanCallback: ((result: string) => void) | null = null
|
||||
|
||||
state = reactive<AppState>({
|
||||
isProcessing: false,
|
||||
processingStatus: '',
|
||||
processingDetails: '',
|
||||
error: ''
|
||||
})
|
||||
|
||||
async open(onScan: (result: string) => void) {
|
||||
this.onScanCallback = onScan
|
||||
this.visible = true
|
||||
this.state.error = ''
|
||||
|
||||
if (!this.useQRReader) {
|
||||
// Check if barcode scanning is supported on mobile
|
||||
try {
|
||||
const { supported } = await BarcodeScanner.isSupported()
|
||||
if (!supported) {
|
||||
this.showError('Barcode scanning is not supported on this device')
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
this.showError('Failed to check barcode scanner support')
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.visible = false
|
||||
this.stopScanning().catch((error) => {
|
||||
logger.error('Error stopping scanner during close:', error)
|
||||
})
|
||||
this.onScanCallback = null
|
||||
}
|
||||
|
||||
async openMobileCamera() {
|
||||
try {
|
||||
this.state.isProcessing = true
|
||||
this.state.processingStatus = 'Starting camera...'
|
||||
logger.log('Opening mobile camera - starting initialization')
|
||||
|
||||
// Check current permission status
|
||||
const status = await BarcodeScanner.checkPermissions()
|
||||
logger.log('Camera permission status:', JSON.stringify(status, null, 2))
|
||||
|
||||
if (status.camera !== 'granted') {
|
||||
// Request permission if not granted
|
||||
logger.log('Requesting camera permissions...')
|
||||
const permissionStatus = await BarcodeScanner.requestPermissions()
|
||||
if (permissionStatus.camera !== 'granted') {
|
||||
throw new Error('Camera permission not granted')
|
||||
}
|
||||
logger.log(
|
||||
'Camera permission granted:',
|
||||
JSON.stringify(permissionStatus, null, 2)
|
||||
)
|
||||
}
|
||||
|
||||
// Remove any existing listener first
|
||||
await this.cleanupScanListener()
|
||||
|
||||
// Set up the listener before starting the scan
|
||||
logger.log('Setting up new barcode listener')
|
||||
this.scanListener = await BarcodeScanner.addListener(
|
||||
'barcodesScanned',
|
||||
async (result: ScanResult) => {
|
||||
logger.log(
|
||||
'Barcode scan result received:',
|
||||
JSON.stringify(result, null, 2)
|
||||
)
|
||||
if (result.barcodes && result.barcodes.length > 0) {
|
||||
this.state.processingDetails = `Processing QR code: ${result.barcodes[0].rawValue}`
|
||||
await this.handleScanResult(result.barcodes[0].rawValue)
|
||||
}
|
||||
}
|
||||
)
|
||||
logger.log('Barcode listener setup complete')
|
||||
|
||||
// Start the scanner
|
||||
logger.log('Starting barcode scanner')
|
||||
await BarcodeScanner.startScan()
|
||||
logger.log('Barcode scanner started successfully')
|
||||
|
||||
this.state.isProcessing = false
|
||||
this.state.processingStatus = ''
|
||||
} catch (error) {
|
||||
logger.error('Failed to open camera:', error)
|
||||
this.state.isProcessing = false
|
||||
this.state.processingStatus = ''
|
||||
this.showError(
|
||||
error instanceof Error ? error.message : 'Failed to open camera'
|
||||
)
|
||||
|
||||
// Cleanup on error
|
||||
await this.cleanupScanListener()
|
||||
}
|
||||
}
|
||||
|
||||
private async handleScanResult(rawValue: string) {
|
||||
try {
|
||||
this.state.isProcessing = true
|
||||
this.state.processingStatus = 'Processing QR code...'
|
||||
this.state.processingDetails = `Scanned value: ${rawValue}`
|
||||
|
||||
// Stop scanning before processing
|
||||
await this.stopScanning()
|
||||
|
||||
if (this.onScanCallback) {
|
||||
await this.onScanCallback(rawValue)
|
||||
// Only close after the callback is complete
|
||||
this.close()
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error handling scan result:', error)
|
||||
this.showError('Failed to process scan result')
|
||||
} finally {
|
||||
this.state.isProcessing = false
|
||||
this.state.processingStatus = ''
|
||||
this.state.processingDetails = ''
|
||||
}
|
||||
}
|
||||
|
||||
private async cleanupScanListener() {
|
||||
try {
|
||||
if (this.scanListener) {
|
||||
await this.scanListener.remove()
|
||||
this.scanListener = null
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error removing scan listener:', error)
|
||||
}
|
||||
}
|
||||
|
||||
async stopScanning() {
|
||||
try {
|
||||
await this.cleanupScanListener()
|
||||
|
||||
if (!this.useQRReader) {
|
||||
// Stop the native scanner
|
||||
await BarcodeScanner.stopScan()
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Error stopping scanner:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Web QR reader handlers
|
||||
async onScanDetect(result: { rawValue: string }) {
|
||||
await this.handleScanResult(result.rawValue)
|
||||
}
|
||||
|
||||
onScanError(error: Error) {
|
||||
logger.error('Scan error:', error)
|
||||
this.showError('Failed to scan QR code')
|
||||
}
|
||||
|
||||
private showError(message: string) {
|
||||
this.state.error = message
|
||||
this.$notify(
|
||||
{
|
||||
group: 'alert',
|
||||
type: 'danger',
|
||||
title: 'Error',
|
||||
text: message
|
||||
},
|
||||
5000
|
||||
)
|
||||
}
|
||||
|
||||
get useQRReader(): boolean {
|
||||
return __USE_QR_READER__
|
||||
}
|
||||
|
||||
get isMobile(): boolean {
|
||||
return __IS_MOBILE__
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.dialog-overlay {
|
||||
z-index: 60;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.dialog {
|
||||
background-color: white;
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
width: 100%;
|
||||
max-width: 700px;
|
||||
position: relative;
|
||||
z-index: 61;
|
||||
}
|
||||
|
||||
/* Add styles for the camera preview */
|
||||
.qrcode-stream {
|
||||
position: relative;
|
||||
z-index: 62;
|
||||
}
|
||||
|
||||
.qrcode-stream video {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
max-height: 70vh;
|
||||
object-fit: contain;
|
||||
}
|
||||
|
||||
/* Ensure mobile camera elements are also properly layered */
|
||||
.barcode-scanner-container {
|
||||
position: relative;
|
||||
z-index: 62;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user