Major additions: - Territory manager system with application workflow - Custom pricing and page builder with Craft.js - Enhanced Stripe Connect onboarding - CodeReadr QR scanning integration - Kiosk mode for venue sales - Super admin dashboard and analytics - MCP integration for AI-powered operations Infrastructure improvements: - Centralized API client and routing system - Enhanced authentication with organization context - Comprehensive theme management system - Advanced event management with custom tabs - Performance monitoring and accessibility features Database schema updates: - Territory management tables - Custom pages and pricing structures - Kiosk PIN system - Enhanced organization profiles - CodeReadr integration tables 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
147 lines
3.7 KiB
TypeScript
147 lines
3.7 KiB
TypeScript
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<QRCodeResult> {
|
|
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<QRCodeResult> {
|
|
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<QRCodeResult> {
|
|
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(); |