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:
611
src/pages/calendar-enhanced.astro
Normal file
611
src/pages/calendar-enhanced.astro
Normal file
@@ -0,0 +1,611 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import PublicHeader from '../components/PublicHeader.astro';
|
||||
|
||||
// Get query parameters for filtering
|
||||
const url = new URL(Astro.request.url);
|
||||
const featured = url.searchParams.get('featured');
|
||||
const category = url.searchParams.get('category');
|
||||
const search = url.searchParams.get('search');
|
||||
---
|
||||
|
||||
<Layout title="Enhanced Event Calendar - Black Canyon Tickets">
|
||||
<div class="min-h-screen">
|
||||
<!-- Hero Section with Dynamic Background -->
|
||||
<section class="relative overflow-hidden bg-gradient-to-br from-indigo-900 via-purple-900 to-slate-900">
|
||||
<PublicHeader showCalendarNav={true} />
|
||||
|
||||
<!-- Animated Background Elements -->
|
||||
<div class="absolute inset-0 opacity-20">
|
||||
<div class="absolute top-20 left-20 w-64 h-64 bg-gradient-to-br from-blue-400 to-purple-500 rounded-full blur-3xl animate-pulse"></div>
|
||||
<div class="absolute bottom-20 right-20 w-96 h-96 bg-gradient-to-br from-purple-400 to-pink-500 rounded-full blur-3xl animate-pulse delay-1000"></div>
|
||||
<div class="absolute top-1/2 left-1/2 transform -translate-x-1/2 -translate-y-1/2 w-80 h-80 bg-gradient-to-br from-cyan-400 to-blue-500 rounded-full blur-3xl animate-pulse delay-500"></div>
|
||||
</div>
|
||||
|
||||
<!-- Geometric Patterns -->
|
||||
<div class="absolute inset-0 opacity-10">
|
||||
<svg class="w-full h-full" viewBox="0 0 100 100" preserveAspectRatio="none">
|
||||
<defs>
|
||||
<pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse">
|
||||
<path d="M 10 0 L 0 0 0 10" fill="none" stroke="white" stroke-width="0.5"/>
|
||||
</pattern>
|
||||
</defs>
|
||||
<rect width="100" height="100" fill="url(#grid)" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="relative max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-32 pb-24 lg:pt-40 lg:pb-32">
|
||||
<div class="text-center">
|
||||
<!-- Badge -->
|
||||
<div class="inline-flex items-center px-4 py-2 rounded-full bg-white/10 backdrop-blur-lg border border-white/20 mb-8">
|
||||
<span class="text-sm font-medium text-white/90">✨ Discover Events Near You</span>
|
||||
</div>
|
||||
|
||||
<!-- Main Heading -->
|
||||
<h1 class="text-5xl lg:text-7xl font-light text-white mb-6 tracking-tight">
|
||||
Smart Event
|
||||
<span class="font-bold bg-gradient-to-r from-blue-400 via-purple-400 to-pink-400 bg-clip-text text-transparent">
|
||||
Discovery
|
||||
</span>
|
||||
</h1>
|
||||
|
||||
<!-- Subheading -->
|
||||
<p class="text-xl lg:text-2xl text-white/80 mb-12 max-w-3xl mx-auto leading-relaxed">
|
||||
Find trending events near you with personalized recommendations and location-based discovery.
|
||||
</p>
|
||||
|
||||
<!-- Location Detection -->
|
||||
<div class="max-w-xl mx-auto mb-8">
|
||||
<div id="location-detector" class="bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-6">
|
||||
<div class="flex items-center justify-center space-x-3 mb-4">
|
||||
<svg class="w-6 h-6 text-white/80" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
<h3 class="text-lg font-semibold text-white">Find Events Near You</h3>
|
||||
</div>
|
||||
|
||||
<div id="location-status" class="text-center">
|
||||
<button id="enable-location" class="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white px-6 py-3 rounded-xl font-semibold transition-all duration-200 shadow-lg hover:shadow-xl">
|
||||
Enable Location
|
||||
</button>
|
||||
<p class="text-white/60 text-sm mt-2">Get personalized event recommendations</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Search Bar -->
|
||||
<div class="max-w-2xl mx-auto">
|
||||
<div class="relative group">
|
||||
<div class="absolute inset-0 bg-gradient-to-r from-blue-600 to-purple-600 rounded-2xl blur opacity-20 group-hover:opacity-30 transition-opacity"></div>
|
||||
<div class="relative bg-white/10 backdrop-blur-xl border border-white/20 rounded-2xl p-2 flex items-center space-x-2">
|
||||
<div class="flex-1 flex items-center space-x-3 px-4">
|
||||
<svg class="w-5 h-5 text-white/60" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
<input
|
||||
type="text"
|
||||
id="search-input"
|
||||
placeholder="Search events, venues, or organizers..."
|
||||
class="bg-transparent text-white placeholder-white/60 focus:outline-none flex-1 text-lg"
|
||||
value={search || ''}
|
||||
/>
|
||||
</div>
|
||||
<button
|
||||
id="search-btn"
|
||||
class="bg-gradient-to-r from-blue-600 to-purple-600 hover:from-blue-700 hover:to-purple-700 text-white px-8 py-3 rounded-xl font-semibold transition-all duration-200 shadow-lg hover:shadow-xl"
|
||||
>
|
||||
Search
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- What's Hot Section -->
|
||||
<section id="whats-hot-section" class="py-16 bg-gradient-to-br from-gray-50 to-gray-100">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div id="whats-hot-container">
|
||||
<!-- Will be populated by WhatsHotEvents component -->
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Filter Controls -->
|
||||
<section class="sticky top-0 z-50 bg-white/95 backdrop-blur-xl border-b border-gray-200/50 shadow-lg">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-4">
|
||||
<div class="flex flex-wrap items-center justify-between gap-4">
|
||||
<!-- Location Display -->
|
||||
<div id="location-display" class="hidden flex items-center space-x-2 bg-blue-50 px-3 py-2 rounded-lg">
|
||||
<svg class="w-4 h-4 text-blue-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"></path>
|
||||
</svg>
|
||||
<span id="location-text" class="text-sm text-blue-800 font-medium"></span>
|
||||
<button id="change-location" class="text-blue-600 hover:text-blue-800 text-xs font-medium">
|
||||
Change
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- View Toggle -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-sm font-medium text-gray-700">View:</span>
|
||||
<div class="bg-gray-100 rounded-lg p-1 flex border border-gray-200">
|
||||
<button
|
||||
id="calendar-view-btn"
|
||||
class="px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 bg-white shadow-sm text-gray-900"
|
||||
>
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
Calendar
|
||||
</button>
|
||||
<button
|
||||
id="list-view-btn"
|
||||
class="px-4 py-2 rounded-md text-sm font-medium transition-all duration-200 text-gray-600 hover:text-gray-900 hover:bg-gray-50"
|
||||
>
|
||||
<svg class="w-4 h-4 inline mr-2" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 10h16M4 14h16M4 18h16"></path>
|
||||
</svg>
|
||||
List
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Advanced Filters -->
|
||||
<div class="flex flex-wrap items-center space-x-4">
|
||||
<!-- Category Filter -->
|
||||
<div class="relative">
|
||||
<select
|
||||
id="category-filter"
|
||||
class="appearance-none bg-white border border-gray-300 rounded-lg px-4 py-2 pr-8 text-sm font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="">All Categories</option>
|
||||
<option value="music" {category === 'music' ? 'selected' : ''}>Music & Concerts</option>
|
||||
<option value="arts" {category === 'arts' ? 'selected' : ''}>Arts & Culture</option>
|
||||
<option value="community" {category === 'community' ? 'selected' : ''}>Community Events</option>
|
||||
<option value="business" {category === 'business' ? 'selected' : ''}>Business & Networking</option>
|
||||
<option value="food" {category === 'food' ? 'selected' : ''}>Food & Wine</option>
|
||||
<option value="sports" {category === 'sports' ? 'selected' : ''}>Sports & Recreation</option>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Distance Filter -->
|
||||
<div id="distance-filter" class="relative hidden">
|
||||
<select
|
||||
id="radius-filter"
|
||||
class="appearance-none bg-white border border-gray-300 rounded-lg px-4 py-2 pr-8 text-sm font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="10">Within 10 miles</option>
|
||||
<option value="25" selected>Within 25 miles</option>
|
||||
<option value="50">Within 50 miles</option>
|
||||
<option value="100">Within 100 miles</option>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Date Range Filter -->
|
||||
<div class="relative">
|
||||
<select
|
||||
id="date-filter"
|
||||
class="appearance-none bg-white border border-gray-300 rounded-lg px-4 py-2 pr-8 text-sm font-medium text-gray-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
|
||||
>
|
||||
<option value="">All Dates</option>
|
||||
<option value="today">Today</option>
|
||||
<option value="tomorrow">Tomorrow</option>
|
||||
<option value="this-week">This Week</option>
|
||||
<option value="this-weekend">This Weekend</option>
|
||||
<option value="next-week">Next Week</option>
|
||||
<option value="this-month">This Month</option>
|
||||
<option value="next-month">Next Month</option>
|
||||
</select>
|
||||
<div class="absolute inset-y-0 right-0 flex items-center px-2 pointer-events-none">
|
||||
<svg class="w-4 h-4 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"></path>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Featured Toggle -->
|
||||
<label class="flex items-center space-x-2 cursor-pointer">
|
||||
<input
|
||||
type="checkbox"
|
||||
id="featured-filter"
|
||||
class="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
||||
{featured ? 'checked' : ''}
|
||||
/>
|
||||
<span class="text-sm font-medium text-gray-700">Featured Only</span>
|
||||
</label>
|
||||
|
||||
<!-- Clear Filters -->
|
||||
<button
|
||||
id="clear-filters"
|
||||
class="text-sm font-medium text-gray-500 hover:text-gray-700 transition-colors"
|
||||
>
|
||||
Clear All
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Main Content -->
|
||||
<main class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- Loading State -->
|
||||
<div id="loading-state" class="text-center py-16">
|
||||
<div class="inline-flex items-center space-x-2">
|
||||
<div class="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
|
||||
<span class="text-lg font-medium text-gray-600">Loading events...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Enhanced Calendar Container -->
|
||||
<div id="enhanced-calendar-container">
|
||||
<!-- React Calendar component will be mounted here -->
|
||||
</div>
|
||||
|
||||
<!-- Empty State -->
|
||||
<div id="empty-state" class="hidden text-center py-16">
|
||||
<div class="max-w-md mx-auto">
|
||||
<div class="w-24 h-24 mx-auto mb-6 bg-gradient-to-br from-gray-100 to-gray-200 rounded-full flex items-center justify-center">
|
||||
<svg class="w-12 h-12 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" d="M8 7V3m8 4V3m-9 8h10M5 21h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v12a2 2 0 002 2z"></path>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-xl font-semibold text-gray-900 mb-2">No Events Found</h3>
|
||||
<p class="text-gray-600 mb-6">Try adjusting your filters or search terms to find events.</p>
|
||||
<button
|
||||
id="clear-filters-empty"
|
||||
class="inline-flex items-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors font-medium"
|
||||
>
|
||||
Clear All Filters
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
<!-- Location Input Modal -->
|
||||
<div id="location-modal" class="fixed inset-0 z-50 hidden">
|
||||
<div class="absolute inset-0 bg-black/60 backdrop-blur-sm"></div>
|
||||
<div class="relative min-h-screen flex items-center justify-center p-4">
|
||||
<div class="bg-white rounded-2xl shadow-2xl max-w-md w-full">
|
||||
<div class="p-6">
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h3 class="text-xl font-semibold text-gray-900">Set Your Location</h3>
|
||||
<button id="close-location-modal" class="text-gray-400 hover:text-gray-600">
|
||||
<svg class="w-6 h-6" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div id="location-input-container">
|
||||
<!-- LocationInput component will be mounted here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Quick Purchase Modal -->
|
||||
<div id="quick-purchase-modal" class="fixed inset-0 z-50 hidden">
|
||||
<div id="quick-purchase-container">
|
||||
<!-- QuickTicketPurchase component will be mounted here -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import Calendar from '../components/Calendar.tsx';
|
||||
import WhatsHotEvents from '../components/WhatsHotEvents.tsx';
|
||||
import LocationInput from '../components/LocationInput.tsx';
|
||||
import QuickTicketPurchase from '../components/QuickTicketPurchase.tsx';
|
||||
import { geolocationService } from '../lib/geolocation.ts';
|
||||
import { trendingAnalyticsService } from '../lib/analytics.ts';
|
||||
|
||||
// State
|
||||
let userLocation = null;
|
||||
let currentRadius = 25;
|
||||
let sessionId = sessionStorage.getItem('sessionId') || Date.now().toString();
|
||||
sessionStorage.setItem('sessionId', sessionId);
|
||||
|
||||
// DOM elements
|
||||
const enableLocationBtn = document.getElementById('enable-location');
|
||||
const locationStatus = document.getElementById('location-status');
|
||||
const locationDisplay = document.getElementById('location-display');
|
||||
const locationText = document.getElementById('location-text');
|
||||
const changeLocationBtn = document.getElementById('change-location');
|
||||
const distanceFilter = document.getElementById('distance-filter');
|
||||
const radiusFilter = document.getElementById('radius-filter');
|
||||
const locationModal = document.getElementById('location-modal');
|
||||
const closeLocationModalBtn = document.getElementById('close-location-modal');
|
||||
const quickPurchaseModal = document.getElementById('quick-purchase-modal');
|
||||
|
||||
// React component containers
|
||||
const whatsHotContainer = document.getElementById('whats-hot-container');
|
||||
const calendarContainer = document.getElementById('enhanced-calendar-container');
|
||||
const locationInputContainer = document.getElementById('location-input-container');
|
||||
const quickPurchaseContainer = document.getElementById('quick-purchase-container');
|
||||
|
||||
// Initialize React components
|
||||
let whatsHotRoot = null;
|
||||
let calendarRoot = null;
|
||||
let locationInputRoot = null;
|
||||
let quickPurchaseRoot = null;
|
||||
|
||||
// Initialize location detection
|
||||
async function initializeLocation() {
|
||||
try {
|
||||
// Try to get saved location preference first
|
||||
const savedLocation = await geolocationService.getUserLocationPreference(null, sessionId);
|
||||
if (savedLocation) {
|
||||
userLocation = {
|
||||
latitude: savedLocation.preferredLatitude,
|
||||
longitude: savedLocation.preferredLongitude,
|
||||
city: savedLocation.preferredCity,
|
||||
state: savedLocation.preferredState,
|
||||
source: savedLocation.locationSource
|
||||
};
|
||||
currentRadius = savedLocation.searchRadiusMiles;
|
||||
updateLocationDisplay();
|
||||
loadComponents();
|
||||
return;
|
||||
}
|
||||
|
||||
// If no saved location, try IP geolocation
|
||||
const ipLocation = await geolocationService.getLocationFromIP();
|
||||
if (ipLocation) {
|
||||
userLocation = ipLocation;
|
||||
updateLocationDisplay();
|
||||
loadComponents();
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error initializing location:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Update location display
|
||||
function updateLocationDisplay() {
|
||||
if (userLocation) {
|
||||
locationStatus.innerHTML = `
|
||||
<div class="flex items-center space-x-2 text-green-400">
|
||||
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||
</svg>
|
||||
<span class="font-medium">Location enabled</span>
|
||||
</div>
|
||||
<p class="text-white/60 text-sm mt-1">
|
||||
${userLocation.city ? `${userLocation.city}, ${userLocation.state}` : 'Location detected'}
|
||||
</p>
|
||||
`;
|
||||
|
||||
locationDisplay.classList.remove('hidden');
|
||||
locationText.textContent = userLocation.city ?
|
||||
`${userLocation.city}, ${userLocation.state}` :
|
||||
'Location detected';
|
||||
|
||||
distanceFilter.classList.remove('hidden');
|
||||
radiusFilter.value = currentRadius.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Load React components
|
||||
function loadComponents() {
|
||||
// Load What's Hot Events
|
||||
if (whatsHotRoot) {
|
||||
whatsHotRoot.unmount();
|
||||
}
|
||||
whatsHotRoot = createRoot(whatsHotContainer);
|
||||
whatsHotRoot.render(React.createElement(WhatsHotEvents, {
|
||||
userLocation: userLocation,
|
||||
radius: currentRadius,
|
||||
limit: 8,
|
||||
onEventClick: handleEventClick,
|
||||
className: 'w-full'
|
||||
}));
|
||||
|
||||
// Load Enhanced Calendar
|
||||
if (calendarRoot) {
|
||||
calendarRoot.unmount();
|
||||
}
|
||||
calendarRoot = createRoot(calendarContainer);
|
||||
calendarRoot.render(React.createElement(Calendar, {
|
||||
events: [], // Will be populated by the calendar component
|
||||
onEventClick: handleEventClick,
|
||||
showLocationFeatures: true,
|
||||
showTrending: true
|
||||
}));
|
||||
}
|
||||
|
||||
// Handle event click
|
||||
function handleEventClick(event) {
|
||||
// Track the click
|
||||
trendingAnalyticsService.trackEvent({
|
||||
eventId: event.id || event.eventId,
|
||||
metricType: 'page_view',
|
||||
sessionId: sessionId,
|
||||
locationData: userLocation ? {
|
||||
latitude: userLocation.latitude,
|
||||
longitude: userLocation.longitude,
|
||||
city: userLocation.city,
|
||||
state: userLocation.state
|
||||
} : undefined
|
||||
});
|
||||
|
||||
// Show quick purchase modal
|
||||
showQuickPurchaseModal(event);
|
||||
}
|
||||
|
||||
// Show quick purchase modal
|
||||
function showQuickPurchaseModal(event) {
|
||||
if (quickPurchaseRoot) {
|
||||
quickPurchaseRoot.unmount();
|
||||
}
|
||||
quickPurchaseRoot = createRoot(quickPurchaseContainer);
|
||||
quickPurchaseRoot.render(React.createElement(QuickTicketPurchase, {
|
||||
event: event,
|
||||
onClose: hideQuickPurchaseModal,
|
||||
onPurchaseStart: handlePurchaseStart
|
||||
}));
|
||||
|
||||
quickPurchaseModal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
// Hide quick purchase modal
|
||||
function hideQuickPurchaseModal() {
|
||||
quickPurchaseModal.classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
if (quickPurchaseRoot) {
|
||||
quickPurchaseRoot.unmount();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle purchase start
|
||||
function handlePurchaseStart(ticketTypeId, quantity) {
|
||||
// Track checkout start
|
||||
trendingAnalyticsService.trackEvent({
|
||||
eventId: event.id || event.eventId,
|
||||
metricType: 'checkout_start',
|
||||
sessionId: sessionId,
|
||||
locationData: userLocation ? {
|
||||
latitude: userLocation.latitude,
|
||||
longitude: userLocation.longitude,
|
||||
city: userLocation.city,
|
||||
state: userLocation.state
|
||||
} : undefined,
|
||||
metadata: {
|
||||
ticketTypeId: ticketTypeId,
|
||||
quantity: quantity
|
||||
}
|
||||
});
|
||||
|
||||
// Navigate to checkout
|
||||
window.location.href = `/checkout?ticketType=${ticketTypeId}&quantity=${quantity}`;
|
||||
}
|
||||
|
||||
// Show location modal
|
||||
function showLocationModal() {
|
||||
if (locationInputRoot) {
|
||||
locationInputRoot.unmount();
|
||||
}
|
||||
locationInputRoot = createRoot(locationInputContainer);
|
||||
locationInputRoot.render(React.createElement(LocationInput, {
|
||||
initialLocation: userLocation,
|
||||
defaultRadius: currentRadius,
|
||||
onLocationChange: handleLocationChange,
|
||||
onRadiusChange: handleRadiusChange,
|
||||
className: 'w-full'
|
||||
}));
|
||||
|
||||
locationModal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
}
|
||||
|
||||
// Hide location modal
|
||||
function hideLocationModal() {
|
||||
locationModal.classList.add('hidden');
|
||||
document.body.style.overflow = 'auto';
|
||||
if (locationInputRoot) {
|
||||
locationInputRoot.unmount();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle location change
|
||||
function handleLocationChange(location) {
|
||||
userLocation = location;
|
||||
if (location) {
|
||||
// Save location preference
|
||||
geolocationService.saveUserLocationPreference({
|
||||
sessionId: sessionId,
|
||||
preferredLatitude: location.latitude,
|
||||
preferredLongitude: location.longitude,
|
||||
preferredCity: location.city,
|
||||
preferredState: location.state,
|
||||
preferredCountry: location.country,
|
||||
preferredZipCode: location.zipCode,
|
||||
searchRadiusMiles: currentRadius,
|
||||
locationSource: location.source
|
||||
});
|
||||
|
||||
updateLocationDisplay();
|
||||
loadComponents();
|
||||
hideLocationModal();
|
||||
}
|
||||
}
|
||||
|
||||
// Handle radius change
|
||||
function handleRadiusChange(radius) {
|
||||
currentRadius = radius;
|
||||
if (userLocation) {
|
||||
// Update saved preference
|
||||
geolocationService.saveUserLocationPreference({
|
||||
sessionId: sessionId,
|
||||
preferredLatitude: userLocation.latitude,
|
||||
preferredLongitude: userLocation.longitude,
|
||||
preferredCity: userLocation.city,
|
||||
preferredState: userLocation.state,
|
||||
preferredCountry: userLocation.country,
|
||||
preferredZipCode: userLocation.zipCode,
|
||||
searchRadiusMiles: currentRadius,
|
||||
locationSource: userLocation.source
|
||||
});
|
||||
|
||||
loadComponents();
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
enableLocationBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
const location = await geolocationService.requestLocationPermission();
|
||||
if (location) {
|
||||
userLocation = location;
|
||||
updateLocationDisplay();
|
||||
loadComponents();
|
||||
|
||||
// Save location preference
|
||||
geolocationService.saveUserLocationPreference({
|
||||
sessionId: sessionId,
|
||||
preferredLatitude: location.latitude,
|
||||
preferredLongitude: location.longitude,
|
||||
preferredCity: location.city,
|
||||
preferredState: location.state,
|
||||
preferredCountry: location.country,
|
||||
preferredZipCode: location.zipCode,
|
||||
searchRadiusMiles: currentRadius,
|
||||
locationSource: location.source
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error enabling location:', error);
|
||||
}
|
||||
});
|
||||
|
||||
changeLocationBtn.addEventListener('click', showLocationModal);
|
||||
closeLocationModalBtn.addEventListener('click', hideLocationModal);
|
||||
|
||||
radiusFilter.addEventListener('change', (e) => {
|
||||
currentRadius = parseInt(e.target.value);
|
||||
handleRadiusChange(currentRadius);
|
||||
});
|
||||
|
||||
// Initialize on page load
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
initializeLocation();
|
||||
});
|
||||
</script>
|
||||
</Layout>
|
||||
Reference in New Issue
Block a user