feat: Modularize event management system - 98.7% reduction in main file size

BREAKING CHANGES:
- Refactored monolithic manage.astro (7,623 lines) into modular architecture
- Original file backed up as manage-old.astro

NEW ARCHITECTURE:
 5 Utility Libraries:
  - event-management.ts: Event data operations & formatting
  - ticket-management.ts: Ticket CRUD operations & sales data
  - seating-management.ts: Seating map management & layout generation
  - sales-analytics.ts: Sales metrics, reporting & data export
  - marketing-kit.ts: Marketing asset generation & social media

 5 Shared Components:
  - TicketTypeModal.tsx: Reusable ticket type creation/editing
  - SeatingMapModal.tsx: Advanced seating map editor with drag-and-drop
  - EmbedCodeModal.tsx: Widget embedding with customization
  - OrdersTable.tsx: Comprehensive orders table with sorting/pagination
  - AttendeesTable.tsx: Attendee management with export capabilities

 11 Tab Components:
  - TicketsTab.tsx: Ticket management with card/list views
  - VenueTab.tsx: Seating map management & venue configuration
  - OrdersTab.tsx: Sales data & order management
  - AttendeesTab.tsx: Attendee check-in & management
  - PresaleTab.tsx: Presale code generation & tracking
  - DiscountTab.tsx: Discount code management
  - AddonsTab.tsx: Add-on product management
  - PrintedTab.tsx: Printed ticket barcode management
  - SettingsTab.tsx: Event configuration & custom fields
  - MarketingTab.tsx: Marketing kit with social media templates
  - PromotionsTab.tsx: Campaign & promotion management

 4 Infrastructure Components:
  - TabNavigation.tsx: Responsive tab navigation system
  - EventManagement.tsx: Main orchestration component
  - EventHeader.astro: Event information header
  - QuickStats.astro: Statistics dashboard

BENEFITS:
- 98.7% reduction in main file size (7,623 → ~100 lines)
- Dramatic improvement in maintainability and team collaboration
- Component-level testing now possible
- Reusable components across multiple features
- Lazy loading support for better performance
- Full TypeScript support with proper interfaces
- Separation of concerns: business logic separated from UI

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-07-08 18:30:26 -06:00
parent 23f190c7a7
commit e8b95231b7
76 changed files with 26728 additions and 7101 deletions

147
src/lib/qr-generator.ts Normal file
View File

@@ -0,0 +1,147 @@
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) {
console.error('Error generating QR code:', 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();