--- name: qr-code-generator description: Use when implementing QR code generation - provides complete patterns for generating QR codes from text/URLs, displaying QR codes in modals, and customizing QR code appearance when_to_use: When adding QR code generation functionality, displaying QR codes for addresses/invoices, or generating QR codes for sharing content --- # QR Code Generation ## Overview Complete implementation guide for QR code generation. Generate QR codes from any text or URL and display them in user-friendly modals with copy functionality. **Core Capabilities:** - Generate QR codes from text/URLs - Customize QR code appearance (size, colors, error correction) - Display QR codes in modals - Copy content to clipboard - Error handling and fallbacks ## Prerequisites **IMPORTANT:** Before adding dependencies, review your project's `package.json` to check if any of these packages already exist. If they do, verify the versions are compatible with the requirements below. Only add packages that are missing or need version updates. **Required packages:** ```json { "qrcode": "^1.5.4" } ``` ## Implementation Checklist - [ ] Install QR code package (`qrcode`) - [ ] Implement QR code generation hook - [ ] Add error handling and fallbacks - [ ] Create QR code display component - [ ] Add copy to clipboard functionality ## Part 1: QR Code Generation Hook ### Basic Generation Hook ```typescript // hooks/useQRCodeGenerator.ts import { useState, useCallback } from 'react'; import QRCode from 'qrcode'; // optional import { useToast } from '@/hooks/useToast'; export function useQRCodeGenerator() { const [isGenerating, setIsGenerating] = useState(false); const [error, setError] = useState(null); // Optional: User feedback notifications // Option 1: Console logging // const logMessage = (message: string) => console.log(message); // Option 2: Toast notifications (if useToast hook is available) // const { toast } = useToast(); // Option 3: No notification handler const generateQRCode = useCallback(async (text: string): Promise => { setIsGenerating(true); setError(null); try { const qrCodeUrl = await QRCode.toDataURL(text, { width: 256, margin: 2, color: { dark: '#000000', light: '#FFFFFF', }, }); setIsGenerating(false); return qrCodeUrl; } catch (err) { const errorMessage = 'Failed to generate QR code'; console.error(errorMessage, err); setError(errorMessage); setIsGenerating(false); // Optional: User feedback - choose one: // Option 1: Console logging // console.error('QR Code Failed:', errorMessage); // Option 2: Toast notification (if toast is available) // toast({ variant: "destructive", title: "QR Code Failed", description: errorMessage }); // Option 3: No notification (silent failure) return ''; } }, []); return { generateQRCode, isGenerating, error, }; } ``` **Usage:** ```typescript const { generateQRCode, isGenerating } = useQRCodeGenerator(); const qrUrl = await generateQRCode('bc1p...'); // Use qrUrl as image src: ``` ### Generation Options **Customize appearance:** ```typescript await QRCode.toDataURL(text, { width: 512, // Larger size for printing margin: 4, // White border color: { dark: '#000000', // QR code color light: '#FFFFFF', // Background color }, errorCorrectionLevel: 'M', // L, M, Q, H }); ``` **Error Correction Levels:** - `L` - Low (~7% recovery) - `M` - Medium (~15% recovery) - **Recommended default** - `Q` - Quartile (~25% recovery) - `H` - High (~30% recovery) - Use for damaged QR codes **Color Customization:** ```typescript // Dark mode friendly await QRCode.toDataURL(text, { color: { dark: '#FFFFFF', // White QR code light: '#000000', // Black background }, }); // Brand colors await QRCode.toDataURL(text, { color: { dark: '#1F2937', // Dark gray light: '#F9FAFB', // Light gray }, }); ``` ## Part 2: QR Code Display Component ### QR Modal Component ```typescript // components/QRModal.tsx import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from '@/components/ui/dialog'; import { Button } from '@/components/ui/button'; import { Copy, Check } from 'lucide-react'; import { useState } from 'react'; import * as VisuallyHidden from '@radix-ui/react-visually-hidden'; interface QRModalProps { isOpen: boolean; onClose: () => void; qrCodeUrl: string; content: string; // Text content shown below QR code title?: string; description?: string; } export function QRModal({ isOpen, onClose, qrCodeUrl, content, title, description }: QRModalProps) { const [copied, setCopied] = useState(false); const handleCopy = async () => { try { await navigator.clipboard.writeText(content); setCopied(true); setTimeout(() => setCopied(false), 2000); } catch (err) { console.error('Failed to copy to clipboard:', err); } }; return ( {title && {title}} {description && ( {description} )}
QR Code
{content}
); } ``` **Usage:** ```typescript const [showQR, setShowQR] = useState(false); const [qrUrl, setQrUrl] = useState(''); const { generateQRCode } = useQRCodeGenerator(); const handleShowQR = async () => { const url = await generateQRCode('bc1p...'); setQrUrl(url); setShowQR(true); }; setShowQR(false)} qrCodeUrl={qrUrl} content="bc1p..." title="Bitcoin Address" description="Scan this QR code to send Bitcoin" /> ``` ### Inline QR Code Display ```typescript // components/QRCodeDisplay.tsx import { useEffect, useState } from 'react'; import { useQRCodeGenerator } from '@/hooks/useQRCodeGenerator'; import { Skeleton } from '@/components/ui/skeleton'; interface QRCodeDisplayProps { content: string; size?: number; className?: string; } export function QRCodeDisplay({ content, size = 256, className }: QRCodeDisplayProps) { const { generateQRCode, isGenerating } = useQRCodeGenerator(); const [qrUrl, setQrUrl] = useState(''); useEffect(() => { if (content) { generateQRCode(content).then(setQrUrl); } }, [content, generateQRCode]); if (isGenerating) { return ; } if (!qrUrl) { return null; } return ( QR Code ); } ``` ## Common Pitfalls ### 1. ❌ Not handling empty or invalid input **Problem:** Generating QR codes from empty strings or invalid data causes errors. **Solution:** Validate input before generation: ```typescript const generateQRCode = useCallback(async (text: string): Promise => { if (!text || !text.trim()) { setError('Cannot generate QR code from empty content'); return ''; } // ... rest of generation logic }, []); ``` ### 2. ❌ Not providing loading states **Problem:** Users don't know QR code is being generated, causing confusion. **Solution:** Always show loading state: ```typescript {isGenerating ? ( ) : ( QR Code )} ``` ### 3. ❌ Not handling generation errors **Problem:** Errors during generation cause UI to break or show nothing. **Solution:** Always handle errors gracefully: ```typescript try { const qrCodeUrl = await QRCode.toDataURL(text, options); return qrCodeUrl; } catch (err) { console.error('Failed to generate QR code:', err); setError('Failed to generate QR code'); return ''; } ``` ### 4. ❌ Using wrong error correction level **Problem:** Using too low error correction causes QR codes to be unreadable when damaged. **Solution:** Use appropriate error correction level: - **Default:** `M` (Medium) - Good balance - **High damage risk:** `H` (High) - For printed materials - **Small QR codes:** `L` (Low) - Only if space is limited ### 5. ❌ Not optimizing QR code size **Problem:** QR codes too small are hard to scan, too large waste space. **Solution:** Choose appropriate size: - **Mobile display:** 256px - **Desktop display:** 512px - **Print:** 1024px or larger - **Minimum scannable:** 128px ## Testing Strategy ### Unit Tests ```typescript import { describe, it, expect, vi } from 'vitest'; import { renderHook, waitFor } from '@testing-library/react'; import { useQRCodeGenerator } from '@/hooks/useQRCodeGenerator'; import QRCode from 'qrcode'; vi.mock('qrcode'); describe('useQRCodeGenerator', () => { it('generates QR code successfully', async () => { const mockDataUrl = 'data:image/png;base64,...'; vi.mocked(QRCode.toDataURL).mockResolvedValue(mockDataUrl); const { result } = renderHook(() => useQRCodeGenerator()); const qrUrl = await result.current.generateQRCode('test content'); expect(qrUrl).toBe(mockDataUrl); expect(result.current.isGenerating).toBe(false); }); it('handles generation errors', async () => { vi.mocked(QRCode.toDataURL).mockRejectedValue(new Error('Generation failed')); const { result } = renderHook(() => useQRCodeGenerator()); const qrUrl = await result.current.generateQRCode('test'); expect(qrUrl).toBe(''); expect(result.current.error).toBeTruthy(); }); it('sets isGenerating state correctly', async () => { const mockDataUrl = 'data:image/png;base64,...'; vi.mocked(QRCode.toDataURL).mockImplementation( () => new Promise(resolve => setTimeout(() => resolve(mockDataUrl), 100)) ); const { result } = renderHook(() => useQRCodeGenerator()); const promise = result.current.generateQRCode('test'); expect(result.current.isGenerating).toBe(true); await promise; await waitFor(() => { expect(result.current.isGenerating).toBe(false); }); }); }); ``` ### Component Tests ```typescript import { describe, it, expect, vi } from 'vitest'; import { render, screen, fireEvent } from '@testing-library/react'; import { QRModal } from '@/components/QRModal'; describe('QRModal', () => { it('displays QR code and content', () => { render( ); expect(screen.getByText('Test QR')).toBeInTheDocument(); expect(screen.getByText('bc1p...')).toBeInTheDocument(); expect(screen.getByAltText('QR Code')).toHaveAttribute('src', 'data:image/png;base64,...'); }); it('copies content to clipboard', async () => { const writeText = vi.fn(); Object.assign(navigator, { clipboard: { writeText } }); render( ); const copyButton = screen.getByLabelText('Copy to clipboard'); fireEvent.click(copyButton); expect(writeText).toHaveBeenCalledWith('bc1p...'); }); }); ``` ## Security Considerations 1. **Validate input** - Don't generate QR codes from untrusted user input without validation 2. **Sanitize content** - Escape HTML/JavaScript in displayed content 3. **Rate limiting** - Prevent abuse by limiting QR code generation frequency 4. **Content size limits** - Very large QR codes can be slow to generate and scan ## Verification Checklist - [ ] QR code generation works for all content types - [ ] Loading states display correctly - [ ] Error handling works gracefully - [ ] QR codes are scannable at intended size - [ ] Copy to clipboard functionality works - [ ] QR codes display correctly in modals - [ ] Works on mobile and desktop - [ ] Error correction level appropriate for use case - [ ] Colors provide sufficient contrast - [ ] QR codes are accessible (alt text, ARIA labels) ## Summary To implement QR code generation: 1. **Install package** - `qrcode` 2. **Create hook** - Use `QRCode.toDataURL()` for generation 3. **Handle errors** - Provide graceful error handling 4. **Display QR codes** - Use modal or inline display components 5. **Add copy functionality** - Allow users to copy content easily 6. **Customize appearance** - Adjust size, colors, and error correction as needed 7. **Test thoroughly** - Test generation, display, and error cases **Key principle:** Always validate input and handle errors gracefully to ensure reliable QR code generation.