Files
blackcanyontickets/src/lib/social-media-generator.ts
dzinesco e8b95231b7 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>
2025-07-08 18:30:26 -06:00

333 lines
9.3 KiB
TypeScript

import { qrGenerator } from './qr-generator';
import { canvasImageGenerator } from './canvas-image-generator';
interface SocialPost {
text: string;
imageUrl: string;
hashtags: string[];
dimensions: { width: number; height: number };
platformSpecific: any;
}
interface EventData {
id: string;
title: string;
description: string;
venue: string;
start_time: string;
end_time: string;
slug: string;
image_url?: string;
social_links?: any;
website_url?: string;
organizations: {
name: string;
logo?: string;
social_links?: any;
website_url?: string;
};
}
class SocialMediaGenerator {
private platformDimensions = {
facebook: { width: 1200, height: 630 },
instagram: { width: 1080, height: 1080 },
twitter: { width: 1200, height: 675 },
linkedin: { width: 1200, height: 627 }
};
private platformLimits = {
facebook: { textLimit: 2000 },
instagram: { textLimit: 2200 },
twitter: { textLimit: 280 },
linkedin: { textLimit: 3000 }
};
/**
* Generate social media post for specific platform
*/
async generatePost(event: EventData, platform: string): Promise<SocialPost> {
const dimensions = this.platformDimensions[platform] || this.platformDimensions.facebook;
// Generate post text
const text = this.generatePostText(event, platform);
// Generate hashtags
const hashtags = this.generateHashtags(event, platform);
// Generate image
const imageUrl = await this.generateSocialImage(event, platform, dimensions);
// Platform-specific configuration
const platformSpecific = this.getPlatformSpecificConfig(event, platform);
return {
text,
imageUrl,
hashtags,
dimensions,
platformSpecific
};
}
/**
* Generate platform-appropriate post text
*/
private generatePostText(event: EventData, platform: string): string {
const eventDate = new Date(event.start_time);
const formattedDate = eventDate.toLocaleDateString('en-US', {
weekday: 'long',
year: 'numeric',
month: 'long',
day: 'numeric'
});
const formattedTime = eventDate.toLocaleTimeString('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
});
// Get social handles from event or organization
const socialLinks = event.social_links || event.organizations.social_links || {};
const orgHandle = this.getSocialHandle(socialLinks, platform);
// Get ticket URL
const ticketUrl = `${import.meta.env.PUBLIC_SITE_URL || 'https://portal.blackcanyontickets.com'}/e/${event.slug}`;
const templates = {
facebook: `🎉 You're Invited: ${event.title}
📅 ${formattedDate} at ${formattedTime}
📍 ${event.venue}
${event.description ? event.description.substring(0, 300) + (event.description.length > 300 ? '...' : '') : 'Join us for an unforgettable experience!'}
🎫 Get your tickets now: ${ticketUrl}
${orgHandle ? `Follow us: ${orgHandle}` : ''}
#Events #Tickets #${event.venue.replace(/\s+/g, '')}`,
instagram: `${event.title}
📅 ${formattedDate}
${formattedTime}
📍 ${event.venue}
${event.description ? event.description.substring(0, 200) + '...' : 'An experience you won\'t want to miss! 🎭'}
Link in bio for tickets 🎫
👆 or scan the QR code in this post
${orgHandle ? `Follow ${orgHandle} for more events` : ''}`,
twitter: `🎉 ${event.title}
📅 ${formattedDate}${formattedTime}
📍 ${event.venue}
🎫 Tickets: ${ticketUrl}
${orgHandle || ''}`,
linkedin: `Professional Event Announcement: ${event.title}
Date: ${formattedDate}
Time: ${formattedTime}
Venue: ${event.venue}
${event.description ? event.description.substring(0, 400) : 'We invite you to join us for this professional gathering.'}
Secure your tickets: ${ticketUrl}
${orgHandle ? `Connect with us: ${orgHandle}` : ''}
#ProfessionalEvents #Networking #${event.organizations.name.replace(/\s+/g, '')}`
};
const text = templates[platform] || templates.facebook;
const limit = this.platformLimits[platform]?.textLimit || 2000;
return text.length > limit ? text.substring(0, limit - 3) + '...' : text;
}
/**
* Generate relevant hashtags for the event
*/
private generateHashtags(event: EventData, platform: string): string[] {
const baseHashtags = [
'Events',
'Tickets',
event.organizations.name.replace(/\s+/g, ''),
event.venue.replace(/\s+/g, ''),
'EventTickets'
];
// Add date-based hashtags
const eventDate = new Date(event.start_time);
const month = eventDate.toLocaleDateString('en-US', { month: 'long' });
const year = eventDate.getFullYear();
baseHashtags.push(`${month}${year}`);
// Platform-specific hashtag strategies
const platformHashtags = {
facebook: [...baseHashtags, 'LocalEvents', 'Community'],
instagram: [...baseHashtags, 'InstaEvent', 'EventPlanning', 'Memories', 'Experience'],
twitter: [...baseHashtags.slice(0, 3)], // Twitter users prefer fewer hashtags
linkedin: [...baseHashtags, 'ProfessionalEvents', 'Networking', 'Business']
};
return platformHashtags[platform] || baseHashtags;
}
/**
* Generate social media image with event details
*/
private async generateSocialImage(
event: EventData,
platform: string,
dimensions: { width: number; height: number }
): Promise<string> {
// Generate QR code for the event
const qrCode = await qrGenerator.generateEventQR(event.slug, {
size: platform === 'instagram' ? 200 : 150,
color: { dark: '#000000', light: '#FFFFFF' }
});
// Generate branded image with canvas
const imageConfig = {
width: dimensions.width,
height: dimensions.height,
platform,
event,
qrCode: qrCode.dataUrl,
backgroundColor: this.getPlatformTheme(platform).backgroundColor,
textColor: this.getPlatformTheme(platform).textColor,
accentColor: this.getPlatformTheme(platform).accentColor
};
const imageUrl = await canvasImageGenerator.generateSocialImage(imageConfig);
return imageUrl;
}
/**
* Get platform-specific theme colors
*/
private getPlatformTheme(platform: string) {
const themes = {
facebook: {
backgroundColor: ['#1877F2', '#4267B2'],
textColor: '#FFFFFF',
accentColor: '#FF6B35'
},
instagram: {
backgroundColor: ['#E4405F', '#F77737', '#FCAF45'],
textColor: '#FFFFFF',
accentColor: '#C13584'
},
twitter: {
backgroundColor: ['#1DA1F2', '#0084b4'],
textColor: '#FFFFFF',
accentColor: '#FF6B6B'
},
linkedin: {
backgroundColor: ['#0077B5', '#004182'],
textColor: '#FFFFFF',
accentColor: '#2867B2'
}
};
return themes[platform] || themes.facebook;
}
/**
* Get social handle for platform
*/
private getSocialHandle(socialLinks: any, platform: string): string {
if (!socialLinks || !socialLinks[platform]) {
return '';
}
const url = socialLinks[platform];
// Extract handle from URL
if (platform === 'twitter') {
const match = url.match(/twitter\.com\/([^\/]+)/);
return match ? `@${match[1]}` : '';
} else if (platform === 'instagram') {
const match = url.match(/instagram\.com\/([^\/]+)/);
return match ? `@${match[1]}` : '';
} else if (platform === 'facebook') {
const match = url.match(/facebook\.com\/([^\/]+)/);
return match ? `facebook.com/${match[1]}` : '';
} else if (platform === 'linkedin') {
return url;
}
return url;
}
/**
* Get platform-specific configuration
*/
private getPlatformSpecificConfig(event: EventData, platform: string) {
const ticketUrl = `${import.meta.env.PUBLIC_SITE_URL || 'https://portal.blackcanyontickets.com'}/e/${event.slug}`;
return {
facebook: {
linkUrl: ticketUrl,
callToAction: 'Get Tickets',
eventType: 'ticket_sales'
},
instagram: {
linkInBio: true,
storyLink: ticketUrl,
callToAction: 'Link in Bio 👆'
},
twitter: {
linkUrl: ticketUrl,
tweetIntent: `Check out ${event.title} - ${ticketUrl}`,
callToAction: 'Get Tickets'
},
linkedin: {
linkUrl: ticketUrl,
eventType: 'professional',
callToAction: 'Secure Your Spot'
}
}[platform];
}
/**
* Generate multiple variations of a post
*/
async generateVariations(event: EventData, platform: string, count: number = 3): Promise<SocialPost[]> {
const variations: SocialPost[] = [];
for (let i = 0; i < count; i++) {
// Modify the approach slightly for each variation
const variation = await this.generatePost(event, platform);
// TODO: Implement different text styles, image layouts, etc.
variations.push(variation);
}
return variations;
}
/**
* Get optimal posting times for platform
*/
getOptimalPostingTimes(platform: string): string[] {
const times = {
facebook: ['9:00 AM', '1:00 PM', '7:00 PM'],
instagram: ['11:00 AM', '2:00 PM', '8:00 PM'],
twitter: ['8:00 AM', '12:00 PM', '6:00 PM'],
linkedin: ['8:00 AM', '10:00 AM', '5:00 PM']
};
return times[platform] || times.facebook;
}
}
export const socialMediaGenerator = new SocialMediaGenerator();