---
description: 
globs: 
alwaysApply: true
---
# QR Code Implementation Guide

## Directory Structure

```
src/
├── components/
│   └── QRScanner/
│       ├── types.ts
│       ├── factory.ts
│       ├── CapacitorScanner.ts
│       ├── WebDialogScanner.ts
│       └── QRScannerDialog.vue
├── services/
│   └── QRScanner/
│       ├── types.ts
│       ├── QRScannerFactory.ts
│       ├── CapacitorQRScanner.ts
│       └── WebDialogQRScanner.ts
```

## Core Interfaces

```typescript
// types.ts
export interface ScanListener {
  onScan: (result: string) => void;
  onError?: (error: Error) => void;
}

export interface QRScannerService {
  checkPermissions(): Promise<boolean>;
  requestPermissions(): Promise<boolean>;
  isSupported(): Promise<boolean>;
  startScan(): Promise<void>;
  stopScan(): Promise<void>;
  addListener(listener: ScanListener): void;
  cleanup(): Promise<void>;
}
```

## Configuration Files

### Vite Configuration
```typescript
// vite.config.ts
export default defineConfig({
  define: {
    __USE_QR_READER__: JSON.stringify(!isMobile),
    __IS_MOBILE__: JSON.stringify(isMobile),
  },
  build: {
    rollupOptions: {
      external: isMobile ? ['vue-qrcode-reader'] : [],
    }
  }
});
```

### Capacitor Configuration
```typescript
// capacitor.config.ts
const config: CapacitorConfig = {
  plugins: {
    MLKitBarcodeScanner: {
      formats: ['QR_CODE'],
      detectorSize: 1.0,
      lensFacing: 'back',
      googleBarcodeScannerModuleInstallState: true
    }
  }
};
```

## Implementation Steps

1. **Install Dependencies**
```bash
npm install @capacitor-mlkit/barcode-scanning vue-qrcode-reader
```

2. **Create Core Types**
Create the interface files as shown above.

3. **Implement Factory**
```typescript
// QRScannerFactory.ts
export class QRScannerFactory {
  private static instance: QRScannerService | null = null;

  static getInstance(): QRScannerService {
    if (!this.instance) {
      if (__IS_MOBILE__ || Capacitor.isNativePlatform()) {
        this.instance = new CapacitorQRScanner();
      } else if (__USE_QR_READER__) {
        this.instance = new WebDialogQRScanner();
      } else {
        throw new Error('No QR scanner implementation available');
      }
    }
    return this.instance;
  }

  static async cleanup() {
    if (this.instance) {
      await this.instance.cleanup();
      this.instance = null;
    }
  }
}
```

4. **Implement Mobile Scanner**
```typescript
// CapacitorQRScanner.ts
export class CapacitorQRScanner implements QRScannerService {
  private scanListener: ScanListener | null = null;
  private isScanning = false;
  private listenerHandles: Array<() => Promise<void>> = [];

  async checkPermissions() {
    try {
      const { camera } = await BarcodeScanner.checkPermissions();
      return camera === 'granted';
    } catch (error) {
      logger.error('Error checking camera permissions:', error);
      return false;
    }
  }

  async requestPermissions() {
    try {
      const { camera } = await BarcodeScanner.requestPermissions();
      return camera === 'granted';
    } catch (error) {
      logger.error('Error requesting camera permissions:', error);
      return false;
    }
  }

  async isSupported() {
    return Capacitor.isNativePlatform();
  }

  async startScan() {
    if (this.isScanning) return;
    this.isScanning = true;

    try {
      await BarcodeScanner.startScan();
    } catch (error) {
      this.isScanning = false;
      throw error;
    }
  }

  async stopScan() {
    if (!this.isScanning) return;
    this.isScanning = false;

    try {
      await BarcodeScanner.stopScan();
    } catch (error) {
      logger.error('Error stopping scan:', error);
    }
  }

  addListener(listener: ScanListener) {
    this.scanListener = listener;
    const handle = BarcodeScanner.addListener('barcodeScanned', (result) => {
      if (this.scanListener) {
        this.scanListener.onScan(result.barcode);
      }
    });
    this.listenerHandles.push(handle.remove);
  }

  async cleanup() {
    await this.stopScan();
    for (const handle of this.listenerHandles) {
      await handle();
    }
    this.listenerHandles = [];
    this.scanListener = null;
  }
}
```

5. **Implement Web Scanner**
```typescript
// WebDialogQRScanner.ts
export class WebDialogQRScanner implements QRScannerService {
  private dialogInstance: App | null = null;
  private dialogComponent: InstanceType<typeof QRScannerDialog> | null = null;
  private scanListener: ScanListener | null = null;

  async checkPermissions(): Promise<boolean> {
    try {
      const permissions = await navigator.permissions.query({
        name: 'camera' as PermissionName
      });
      return permissions.state === 'granted';
    } catch (error) {
      logger.error('Error checking camera permissions:', error);
      return false;
    }
  }

  async requestPermissions(): Promise<boolean> {
    try {
      const stream = await navigator.mediaDevices.getUserMedia({ video: true });
      stream.getTracks().forEach(track => track.stop());
      return true;
    } catch (error) {
      logger.error('Error requesting camera permissions:', error);
      return false;
    }
  }

  async isSupported(): Promise<boolean> {
    return 'mediaDevices' in navigator && 'getUserMedia' in navigator.mediaDevices;
  }

  async startScan() {
    if (this.dialogInstance) return;

    const container = document.createElement('div');
    document.body.appendChild(container);

    this.dialogInstance = createApp(QRScannerDialog, {
      onScan: (result: string) => {
        if (this.scanListener) {
          this.scanListener.onScan(result);
        }
      },
      onError: (error: Error) => {
        if (this.scanListener?.onError) {
          this.scanListener.onError(error);
        }
      },
      onClose: () => {
        this.cleanup();
      }
    });

    this.dialogComponent = this.dialogInstance.mount(container) as InstanceType<typeof QRScannerDialog>;
  }

  async stopScan() {
    await this.cleanup();
  }

  addListener(listener: ScanListener) {
    this.scanListener = listener;
  }

  async cleanup() {
    if (this.dialogInstance) {
      this.dialogInstance.unmount();
      this.dialogInstance = null;
      this.dialogComponent = null;
    }
  }
}
```

6. **Create Dialog Component**
```vue
<!-- QRScannerDialog.vue -->
<template>
  <div v-if="visible" class="dialog-overlay z-[60]">
    <div class="dialog relative">
      <div class="dialog-header">
        <h2>Scan QR Code</h2>
        <button @click="onClose" class="close-button">×</button>
      </div>
      <div class="dialog-content">
        <div v-if="useQRReader">
          <qrcode-stream
            class="w-full max-w-lg mx-auto"
            @detect="onScanDetect"
            @error="onScanError"
          />
        </div>
        <div v-else>
          <button @click="startMobileScan" class="scan-button">
            Start Camera
          </button>
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue';
import { QrcodeStream } from 'vue-qrcode-reader';

export default defineComponent({
  name: 'QRScannerDialog',
  components: { QrcodeStream },
  props: {
    onScan: {
      type: Function,
      required: true
    },
    onError: {
      type: Function,
      required: true
    },
    onClose: {
      type: Function,
      required: true
    }
  },
  data() {
    return {
      visible: true,
      useQRReader: __USE_QR_READER__
    };
  },
  methods: {
    onScanDetect(promisedResult: Promise<string>) {
      promisedResult
        .then(result => this.onScan(result))
        .catch(error => this.onError(error));
    },
    onScanError(error: Error) {
      this.onError(error);
    },
    async startMobileScan() {
      try {
        const scanner = QRScannerFactory.getInstance();
        await scanner.startScan();
      } catch (error) {
        this.onError(error as Error);
      }
    }
  }
});
</script>

<style scoped>
.dialog-overlay {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.5);
  display: flex;
  align-items: center;
  justify-content: center;
}

.dialog {
  background: white;
  border-radius: 8px;
  padding: 20px;
  max-width: 90vw;
  max-height: 90vh;
  overflow: auto;
}

.dialog-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 20px;
}

.close-button {
  background: none;
  border: none;
  font-size: 24px;
  cursor: pointer;
}

.scan-button {
  background: #4CAF50;
  color: white;
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.scan-button:hover {
  background: #45a049;
}
</style>
```

## Usage Example

```typescript
// In your component
async function scanQRCode() {
  const scanner = QRScannerFactory.getInstance();
  
  if (!(await scanner.checkPermissions())) {
    const granted = await scanner.requestPermissions();
    if (!granted) {
      throw new Error('Camera permission denied');
    }
  }

  scanner.addListener({
    onScan: (result) => {
      console.log('Scanned:', result);
    },
    onError: (error) => {
      console.error('Scan error:', error);
    }
  });

  await scanner.startScan();
}

// Cleanup when done
onUnmounted(() => {
  QRScannerFactory.cleanup();
});
```

## Platform-Specific Notes

### Mobile (Capacitor)
- Uses MLKit for optimal performance
- Handles native permissions
- Supports both iOS and Android
- Uses back camera by default
- Handles device rotation
- Provides native UI for scanning

### Web
- Uses MediaDevices API
- Requires HTTPS for camera access
- Handles browser compatibility
- Manages memory and resources
- Provides fallback UI
- Uses vue-qrcode-reader for web scanning

## Testing

1. **Unit Tests**
- Test factory pattern
- Test platform detection
- Test error handling
- Test cleanup procedures
- Test permission flows

2. **Integration Tests**
- Test camera access
- Test QR code detection
- Test cross-platform behavior
- Test UI components
- Test error scenarios

3. **E2E Tests**
- Test complete scanning flow
- Test permission handling
- Test cross-platform compatibility
- Test error recovery
- Test cleanup procedures

## Best Practices

1. **Error Handling**
- Always handle permission errors gracefully
- Provide clear error messages to users
- Implement proper cleanup on errors
- Log errors for debugging

2. **Performance**
- Clean up resources when not in use
- Handle device rotation properly
- Optimize camera usage
- Manage memory efficiently

3. **Security**
- Request minimum required permissions
- Handle sensitive data securely
- Validate scanned data
- Implement proper cleanup

4. **User Experience**
- Provide clear feedback
- Handle edge cases gracefully
- Support both platforms seamlessly
- Implement proper loading states

## Troubleshooting

1. **Common Issues**
- Camera permissions denied
- Device not supported
- Scanner not working
- Memory leaks
- UI glitches

2. **Solutions**
- Check permissions
- Verify device support
- Debug scanner implementation
- Monitor memory usage
- Test UI components

## Maintenance

1. **Regular Updates**
- Keep dependencies updated
- Monitor platform changes
- Update documentation
- Review security patches

2. **Performance Monitoring**
- Track memory usage
- Monitor camera performance
- Check error rates
- Analyze user feedback