import QRCode from 'qrcode'; interface QRCodeOptions { size?: number; margin?: number; color?: { dark?: string; light?: string; }; errorCorrectionLevel?: 'L' | 'M' | 'Q' | 'H'; } interface QRCodeResult { dataUrl: string; svg: string; size: number; } export class QRCodeGenerator { private defaultOptions: QRCodeOptions = { size: 256, margin: 2, color: { dark: '#000000', light: '#FFFFFF' }, errorCorrectionLevel: 'M' }; /** * Generate QR code for event ticket URL */ async generateEventQR(eventSlug: string, options: QRCodeOptions = {}): Promise { const ticketUrl = `${import.meta.env.PUBLIC_SITE_URL || 'https://portal.blackcanyontickets.com'}/e/${eventSlug}`; return this.generateQRCode(ticketUrl, options); } /** * Generate QR code for any URL */ async generateQRCode(url: string, options: QRCodeOptions = {}): Promise { const mergedOptions = { ...this.defaultOptions, ...options }; try { // Generate data URL (base64 PNG) const dataUrl = await QRCode.toDataURL(url, { width: mergedOptions.size, margin: mergedOptions.margin, color: mergedOptions.color, errorCorrectionLevel: mergedOptions.errorCorrectionLevel }); // Generate SVG const svg = await QRCode.toString(url, { type: 'svg', width: mergedOptions.size, margin: mergedOptions.margin, color: mergedOptions.color, errorCorrectionLevel: mergedOptions.errorCorrectionLevel }); return { dataUrl, svg, size: mergedOptions.size || this.defaultOptions.size! }; } catch (error) { throw new Error('Failed to generate QR code'); } } /** * Generate QR code with custom branding/logo overlay */ async generateBrandedQR( url: string, logoDataUrl?: string, options: QRCodeOptions = {} ): Promise { const qrResult = await this.generateQRCode(url, { ...options, errorCorrectionLevel: 'H' // Higher error correction for logo overlay }); if (!logoDataUrl) { return qrResult; } // If logo is provided, we'll need to composite it onto the QR code // This would typically be done server-side with canvas or image processing // For now, we'll return the base QR code and handle logo overlay in the image generation return qrResult; } /** * Validate URL before QR generation */ private validateUrl(url: string): boolean { try { new URL(url); return true; } catch { return false; } } /** * Get optimal QR code size for different use cases */ getRecommendedSize(useCase: 'social' | 'flyer' | 'email' | 'print'): number { switch (useCase) { case 'social': return 200; case 'flyer': return 300; case 'email': return 150; case 'print': return 600; default: return 256; } } /** * Generate multiple QR code formats for different use cases */ async generateMultiFormat(url: string): Promise<{ social: QRCodeResult; flyer: QRCodeResult; email: QRCodeResult; print: QRCodeResult; }> { const [social, flyer, email, print] = await Promise.all([ this.generateQRCode(url, { size: this.getRecommendedSize('social') }), this.generateQRCode(url, { size: this.getRecommendedSize('flyer') }), this.generateQRCode(url, { size: this.getRecommendedSize('email') }), this.generateQRCode(url, { size: this.getRecommendedSize('print') }) ]); return { social, flyer, email, print }; } } // Export singleton instance export const qrGenerator = new QRCodeGenerator();