Manifest UI
blockevent-detailsource: ok · hit
Event Detail
Full event detail view with organizer info, interactive map, policies, and ticket purchase. Fullwidth mode only.
npx shadcn add "https://ui.manifest.build/r/event-detail.json"free
No selection tags yet — empty means no evidence, not lack of support.
Dependencies
Bundle weight · lighticonslucide-react
Registry dependencies
buttonhttps://ui.manifest.build/r/manifest-types.jsonhttps://ui.manifest.build/r/event-shared.json
Files2 · source cached 10/11/2026, 6:46:21 AM
registry/events/event-detail.tsx
'use client'
import { Button } from '@/components/ui/button'
import { cn } from '@/lib/utils'
import {
MapPin,
Star,
Share2,
Heart,
ChevronLeft,
ChevronRight,
CheckCircle,
Car,
Train,
Bike,
Footprints,
Flag,
BadgeCheck,
Timer
} from 'lucide-react'
import { Suspense, useState } from 'react'
import type { EventDetails } from './types'
import {
LazyLeafletMap,
formatNumber,
MapPlaceholder,
EventSignalBadge
} from './shared'
import { demoEventDetails } from './demo/events'
// Format date for display
function formatEventDateTime(startDateTime: string, endDateTime?: string): string {
const start = new Date(startDateTime)
const now = new Date()
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate())
const tomorrow = new Date(today.getTime() + 24 * 60 * 60 * 1000)
const startDay = new Date(start.getFullYear(), start.getMonth(), start.getDate())
const timeOptions: Intl.DateTimeFormatOptions = { hour: 'numeric', minute: '2-digit', hour12: true }
const startTime = start.toLocaleTimeString('en-US', timeOptions)
let datePrefix: string
if (startDay.getTime() === today.getTime()) {
datePrefix = 'Today'
} else if (startDay.getTime() === tomorrow.getTime()) {
datePrefix = 'Tomorrow'
} else {
datePrefix = start.toLocaleDateString('en-US', { month: 'short', day: 'numeric' })
}
if (endDateTime) {
const end = new Date(endDateTime)
const endTime = end.toLocaleTimeString('en-US', timeOptions)
return `${datePrefix} · ${startTime} - ${endTime}`
}
return `${datePrefix} · ${startTime}`
}
/**
* ═══════════════════════════════════════════════════════════════════════════
* EventDetailProps
* ═══════════════════════════════════════════════════════════════════════════
*
* Props for the EventDetail component. Displays comprehensive event information
* including image carousel, location map, organizer details, and ticket actions.
*/
export interface EventDetailProps {
data?: {
/** The full event details object to display. */
event?: EventDetails
}
actions?: {
/** Called when "Get tickets" button is clicked. */
onGetTickets?: (event: EventDetails) => void
/** Called when share button is clicked. */
onShare?: (event: EventDetails) => void
/** Called when save/heart button is clicked. */
onSave?: (event: EventDetails) => void
/** Called when back navigation button is clicked. */
onBack?: () => void
/** Called when "Follow" organizer button is clicked. */
onFollow?: (organizer: EventDetails['organizer']) => void
/** Called when "Contact" organizer button is clicked. */
onContact?: (organizer: EventDetails['organizer']) => void
}
appearance?: {
/**
* Whether to show the AI match explanation section.
* @default true
*/
showAiMatch?: boolean
/**
* Whether to show the interactive map.
* @default true
*/
showMap?: boolean
}
}
export function EventDetail({ data, actions, appearance }: EventDetailProps) {
const resolved: NonNullable<EventDetailProps['data']> = data ?? { event: demoEventDetails }
const event = resolved.event
const onGetTickets = actions?.onGetTickets
const onShare = actions?.onShare
const onSave = actions?.onSave
const onBack = actions?.onBack
const onFollow = actions?.onFollow
const onContact = actions?.onContact
const showAiMatch = appearance?.showAiMatch ?? true
const showMap = appearance?.showMap ?? true
const [currentImageIndex, setCurrentImageIndex] = useState(0)
const [isSaved, setIsSaved] = useState(false)
if (!event) {
return null
}
const images = event.images?.length ? event.images : (event.image ? [event.image] : [])
const handlePrevImage = () => {
setCurrentImageIndex((prev) => (prev === 0 ? images.length - 1 : prev - 1))
}
const handleNextImage = () => {
setCurrentImageIndex((prev) => (prev === images.length - 1 ? 0 : prev + 1))
}
const handleSave = () => {
setIsSaved(!isSaved)
onSave?.(event)
}
return (
<div className="mx-auto max-w-lg bg-background">
{/* Image Carousel */}
{images.length > 0 && (
<div className="relative aspect-[4/3] overflow-hidden bg-muted">
<img
src={images[currentImageIndex]}
alt={event.title || 'Event image'}
className="h-full w-full object-cover"
/>
{/* Navigation overlay */}
<div className="absolute inset-0 flex items-center justify-between p-2">
{images.length > 1 && (
<>
<button
onClick={handlePrevImage}
aria-label="Previous image"
className="rounded-full bg-black/50 p-2 text-white hover:bg-black/70"
>
<ChevronLeft className="h-5 w-5" />
</button>
<button
onClick={handleNextImage}
aria-label="Next image"
className="rounded-full bg-black/50 p-2 text-white hover:bg-black/70"
>
<ChevronRight className="h-5 w-5" />
</button>
</>
)}
</div>
{/* Top actions */}
<div className="absolute top-3 right-3 flex gap-2">
<button
onClick={() => onShare?.(event)}
aria-label="Share event"
className="rounded-full bg-white/90 p-2 shadow-sm hover:bg-white"
>
<Share2 className="h-5 w-5" />
</button>
<button
onClick={handleSave}
aria-label={isSaved ? 'Remove from saved' : 'Save event'}
className="rounded-full bg-white/90 p-2 shadow-sm hover:bg-white"
>
<Heart className={cn('h-5 w-5', isSaved && 'fill-red-500 text-red-500')} />
</button>
</div>
{/* Back button */}
{onBack && (
<button
onClick={onBack}
aria-label="Go back"
className="absolute top-3 left-3 rounded-full bg-black/50 p-2 text-white hover:bg-black/70"
>
<ChevronLeft className="h-5 w-5" />
</button>
)}
{/* Image counter */}
{images.length > 1 && (
<div className="absolute bottom-3 right-3 rounded-full bg-black/60 px-2.5 py-1 text-xs text-white">
{currentImageIndex + 1} / {images.length}
</div>
)}
</div>
)}
<div className="space-y-6 p-4">
{/* Signal Badge */}
{event.eventSignal && (
<div>
<EventSignalBadge signal={event.eventSignal} />
</div>
)}
{/* Category */}
{event.category && (
<span className="inline-block rounded-full bg-muted px-3 py-1 text-sm font-medium">
{event.category}
</span>
)}
{/* Title */}
{event.title && (
<h1 className="text-2xl font-bold leading-tight">{event.title}</h1>
)}
{/* Organizer + Rating */}
{event.organizer && (
<div className="flex items-center gap-2 text-sm">
{event.organizer.verified && (
<BadgeCheck className="h-4 w-4 text-blue-500" />
)}
{event.organizer.name && (
<span className="font-medium">{event.organizer.name}</span>
)}
{(event.organizer.rating !== undefined || event.organizer.reviewCount !== undefined) && (
<>
<span className="text-muted-foreground">·</span>
<span className="flex items-center gap-1">
<Star className="h-4 w-4 fill-current text-yellow-500" />
{event.organizer.rating !== undefined && event.organizer.rating}
{event.organizer.reviewCount !== undefined && ` (${formatNumber(event.organizer.reviewCount)})`}
</span>
</>
)}
</div>
)}
{/* Venue + Location */}
{(event.venue_details?.name || event.venue || event.city) && (
<div className="flex items-start gap-2 text-sm text-muted-foreground">
<MapPin className="mt-0.5 h-4 w-4 shrink-0" />
<span>
{[event.venue_details?.name || event.venue, event.city].filter(Boolean).join(' · ')}
{event.neighborhood && ` (${event.neighborhood})`}
</span>
</div>
)}
{/* Price + Attendees */}
<div className="flex items-center gap-4">
{event.priceRange && (
<div>
<div className="text-lg font-semibold">{event.priceRange}</div>
</div>
)}
{event.attendeesCount !== undefined && (
<div className="flex items-center gap-2">
{event.friendsGoing && event.friendsGoing.length > 0 && (
<div className="flex -space-x-2">
{event.friendsGoing.slice(0, 3).map((friend) => (
friend.avatar && (
<img
key={friend.name || friend.avatar}
src={friend.avatar}
alt={friend.name || 'Friend'}
className="h-6 w-6 rounded-full border-2 border-background"
/>
)
))}
</div>
)}
<span className="text-sm text-muted-foreground">
{event.friendsGoing && event.friendsGoing.length > 0 && `+ ${event.friendsGoing.length} friends · `}
{event.attendeesCount} going
</span>
</div>
)}
</div>
{/* Vibe Tags */}
{event.vibeTags && event.vibeTags.length > 0 && (
<div className="flex flex-wrap gap-2">
{event.vibeTags.map((tag) => (
<span
key={tag}
className="rounded-full border px-3 py-1 text-sm"
>
{tag}
</span>
))}
</div>
)}
{/* CTA Buttons */}
<div className="flex gap-3">
<Button
className="flex-1 bg-primary hover:bg-primary/90"
onClick={() => onGetTickets?.(event)}
>
Get tickets
</Button>
<Button variant="outline" className="flex-1">
Invite friends
</Button>
</div>
{/* AI Match */}
{showAiMatch && event.aiSummary && (
<div className="rounded-lg bg-muted/50 p-4">
<h3 className="font-semibold">Why this matches your vibe</h3>
<p className="mt-1 text-sm text-muted-foreground">{event.aiSummary}</p>
</div>
)}
{/* About */}
{event.description && (
<div>
<h2 className="text-lg font-semibold">About</h2>
<p className="mt-2 text-sm text-muted-foreground">{event.description}</p>
</div>
)}
{/* Lineup */}
{event.lineup && event.lineup.length > 0 && (
<div>
<h3 className="text-sm font-medium text-muted-foreground">Lineup</h3>
<div className="mt-2 flex flex-wrap gap-2">
{event.lineup.map((artist) => (
<span key={artist} className="rounded-full border px-3 py-1 text-sm">
{artist}
</span>
))}
</div>
</div>
)}
{/* Good to Know */}
{event.goodToKnow && (
<div>
<h2 className="text-lg font-semibold">Good to know</h2>
<div className="mt-3 grid grid-cols-2 gap-4">
<div className="rounded-lg bg-muted/50 p-3">
<h4 className="text-sm font-medium">Highlights</h4>
<div className="mt-2 space-y-1.5 text-sm text-muted-foreground">
{event.goodToKnow.duration && (
<div className="flex items-center gap-2">
<Timer className="h-4 w-4" />
{event.goodToKnow.duration}
</div>
)}
{event.locationType !== 'online' && (
<div className="flex items-center gap-2">
<MapPin className="h-4 w-4" />
In person
</div>
)}
</div>
</div>
{event.policies?.refund && (
<div className="rounded-lg bg-muted/50 p-3">
<h4 className="text-sm font-medium">Refund Policy</h4>
<p className="mt-2 text-sm text-muted-foreground">
{event.policies.refund}
</p>
</div>
)}
</div>
</div>
)}
{/* Location */}
{event.venue_details && (
<div>
<h2 className="text-lg font-semibold">Location</h2>
<div className="mt-3">
{event.venue_details.name && (
<p className="font-medium">{event.venue_details.name}</p>
)}
{event.venue_details.address && (
<p className="text-sm text-muted-foreground">{event.venue_details.address}</p>
)}
{event.venue_details.city && (
<p className="text-sm text-muted-foreground">{event.venue_details.city}</p>
)}
</div>
{showMap && event.venue_details.coordinates && (
<div className="mt-4 aspect-video overflow-hidden rounded-lg bg-muted">
<Suspense fallback={<MapPlaceholder />}>
<LazyLeafletMap
center={[event.venue_details.coordinates.lat, event.venue_details.coordinates.lng]}
zoom={15}
scrollWheelZoom={false}
renderMarkers={({ Marker, L }) => {
const icon = L.divIcon({
className: '',
html: `<div style="
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -100%);
display: flex;
flex-direction: column;
align-items: center;
">
<div style="
background-color: #18181b;
color: white;
padding: 6px 10px;
border-radius: 8px;
font-size: 12px;
font-weight: 600;
font-family: system-ui, -apple-system, sans-serif;
white-space: nowrap;
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
">${event.venue_details!.name}</div>
<div style="
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 8px solid #18181b;
margin-top: -1px;
"></div>
</div>`,
iconSize: [100, 40],
iconAnchor: [50, 40]
})
return (
<Marker
position={[event.venue_details!.coordinates!.lat, event.venue_details!.coordinates!.lng]}
icon={icon}
/>
)
}}
/>
</Suspense>
</div>
)}
<div className="mt-4">
<p className="text-sm font-medium">How do you want to get there?</p>
<div className="mt-2 space-y-2">
{[
{ icon: Car, label: 'Driving' },
{ icon: Train, label: 'Public transport' },
{ icon: Bike, label: 'Biking' },
{ icon: Footprints, label: 'Walking' }
].map(({ icon: Icon, label }) => (
<button key={label} className="flex w-full items-center gap-3 rounded-lg p-2 hover:bg-muted">
<Icon className="h-5 w-5 text-muted-foreground" />
<span className="text-sm">{label}</span>
</button>
))}
</div>
</div>
</div>
)}
{/* Organizer */}
{event.organizer && (
<div>
<h2 className="text-lg font-semibold">Organized by</h2>
<div className="mt-3 rounded-lg border p-4">
<div className="flex items-center gap-3">
{event.organizer.image ? (
<img
src={event.organizer.image}
alt={event.organizer.name || 'Organizer'}
className="h-12 w-12 rounded-full object-cover"
/>
) : event.organizer.name ? (
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-muted text-lg font-medium">
{event.organizer.name.charAt(0)}
</div>
) : null}
<div className="flex-1">
{event.organizer.name && (
<p className="font-medium">{event.organizer.name}</p>
)}
<div className="flex gap-4 text-xs text-muted-foreground">
{event.organizer.followers !== undefined && (
<span>Followers<br /><strong>{formatNumber(event.organizer.followers)}</strong></span>
)}
{event.organizer.eventsCount !== undefined && (
<span>Events<br /><strong>{event.organizer.eventsCount}</strong></span>
)}
{event.organizer.hostingYears !== undefined && (
<span>Hosting<br /><strong>{event.organizer.hostingYears} yrs</strong></span>
)}
</div>
</div>
</div>
<div className="mt-3 flex gap-2">
<Button variant="outline" size="sm" className="flex-1" onClick={() => onContact?.(event.organizer)}>
Contact
</Button>
<Button size="sm" className="flex-1 bg-primary hover:bg-primary/90" onClick={() => onFollow?.(event.organizer)}>
Follow
</Button>
</div>
{(event.organizer.trackRecord || event.organizer.responseRate) && (
<div className="mt-3 flex flex-wrap gap-2 text-xs">
{event.organizer.trackRecord === 'great' && (
<span className="flex items-center gap-1 text-green-600">
<CheckCircle className="h-3 w-3" /> Great track record
</span>
)}
{event.organizer.responseRate === 'very responsive' && (
<span className="flex items-center gap-1 text-orange-600">
<CheckCircle className="h-3 w-3" /> Very responsive
</span>
)}
</div>
)}
</div>
</div>
)}
{/* Policies & Info */}
{event.policies && (
<div>
<h2 className="text-lg font-semibold">Policies & Info</h2>
<div className="mt-3 flex flex-wrap gap-2">
{event.goodToKnow?.ageRestriction && (
<span className="rounded-full border px-3 py-1 text-sm">{event.goodToKnow.ageRestriction}</span>
)}
{event.policies.idRequired && (
<span className="rounded-full border px-3 py-1 text-sm">ID checks</span>
)}
{event.policies.securityOnSite && (
<span className="rounded-full border px-3 py-1 text-sm">Security on site</span>
)}
</div>
</div>
)}
{/* FAQs */}
{event.faq && event.faq.length > 0 && (
<div>
<h2 className="text-lg font-semibold">FAQs</h2>
<div className="mt-3 space-y-2">
{event.faq.map((item) => (
<div key={item.question} className="text-sm">
<p><strong>{item.question.replace('?', '')}:</strong> {item.answer}</p>
</div>
))}
</div>
</div>
)}
{/* Report */}
<button className="flex items-center gap-2 text-sm text-blue-600 hover:underline">
<Flag className="h-4 w-4" />
Report this event
</button>
{/* Related Tags */}
{event.relatedTags && event.relatedTags.length > 0 && (
<div>
<h2 className="text-lg font-semibold">Related to this event</h2>
<div className="mt-3 flex flex-wrap gap-2">
{event.relatedTags.map((tag) => (
<span key={tag} className="rounded-full border px-3 py-1 text-sm hover:bg-muted cursor-pointer">
{tag}
</span>
))}
</div>
</div>
)}
{/* Bottom CTA */}
<div className="mt-6 rounded-lg border bg-muted/30 p-4">
<div className="flex items-center justify-between gap-4">
<div>
{event.priceRange && <p className="font-semibold">{event.priceRange}</p>}
{event.startDateTime && <p className="text-sm text-muted-foreground">{formatEventDateTime(event.startDateTime, event.endDateTime)}</p>}
</div>
<Button
className="bg-primary hover:bg-primary/90"
onClick={() => onGetTickets?.(event)}
>
Get tickets
</Button>
</div>
</div>
</div>
</div>
)
}
registry/events/demo/events.ts
// Demo data for Events category components
// This file contains sample data used for component previews and documentation
import type { Event, EventDetails } from '../types'
// Helper to generate dates relative to today
function getDateAt(daysFromNow: number, hour: number): string {
const date = new Date()
date.setDate(date.getDate() + daysFromNow)
date.setHours(hour, 0, 0, 0)
return date.toISOString()
}
// Single event for EventCard default
export const demoEvent: Event = {
title: 'NEON Vol. 9',
category: 'Music',
venue: 'Echoplex',
neighborhood: 'Echo Park',
city: 'Los Angeles',
dateTime: 'Tonight 9:00 PM - 3:00 AM',
priceRange: '$45 - $150',
image: 'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=800',
vibeTags: ['High energy', 'Late night', 'Dressy'],
vibeDescription:
'Immersive electronic experience with world-class DJs and stunning visuals.',
aiSummary:
"Immersive electronic night with world-class DJs and stunning visuals at LA's top-rated venue.",
lineup: ['DJ Shadow', 'Bonobo', 'Four Tet', 'Caribou'],
ticketTiers: [
'General Admission $45',
'VIP Access $120',
'Backstage Pass $150'
],
eventSignal: 'going-fast',
organizerRating: 4.8,
reviewCount: 12453,
venueRating: 4.8,
ageRestriction: '21+',
hasMultipleDates: true
}
// 15 events for EventList default
export const demoEvents: Event[] = [
{
title: 'NEON Vol. 9',
category: 'Music',
venue: 'Echoplex',
neighborhood: 'Echo Park',
city: 'Los Angeles',
dateTime: 'Tonight 9:00 PM - 3:00 AM',
priceRange: '$45 - $150',
image: 'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=800',
coordinates: { lat: 34.0781, lng: -118.2606 },
vibeTags: ['High energy', 'Late night'],
eventSignal: 'going-fast',
organizerRating: 4.8,
reviewCount: 12453,
ageRestriction: '21+'
},
{
title: 'The Midnight Show',
category: 'Comedy',
venue: 'The Comedy Underground',
neighborhood: 'Santa Monica',
city: 'Los Angeles',
dateTime: 'Tonight 10:00 PM - 12:00 AM',
priceRange: '$15 - $35',
image: 'https://images.unsplash.com/photo-1585699324551-f6c309eedeca?w=800',
coordinates: { lat: 34.0195, lng: -118.4912 },
vibeTags: ['Social', 'Late night'],
eventSignal: 'popular',
organizerRating: 4.7,
reviewCount: 3241,
discount: 'TONIGHT ONLY - 40% OFF'
},
{
title: 'Salsa Sundays @ Echo Park',
category: 'Classes',
venue: 'Echo Park Lake',
neighborhood: 'Echo Park',
city: 'Los Angeles',
dateTime: 'Saturday 6:00 PM - 10:00 PM',
priceRange: 'Free',
image: 'https://images.unsplash.com/photo-1504609813442-a8924e83f76e?w=800',
coordinates: { lat: 34.0731, lng: -118.2608 },
vibeTags: ['High energy', 'Social'],
eventSignal: 'just-added',
organizerRating: 4.9,
reviewCount: 8764
},
{
title: 'Dawn Flow: Griffith Park',
category: 'Classes',
venue: 'Griffith Park',
neighborhood: 'Los Feliz',
city: 'Los Angeles',
dateTime: 'Tomorrow 6:00 AM - 8:00 AM',
priceRange: 'Free',
image: 'https://images.unsplash.com/photo-1545205597-3d9d02c29597?w=800',
coordinates: { lat: 34.1365, lng: -118.2943 },
vibeTags: ['Chill', 'Wellness', 'Outdoor'],
organizerRating: 4.9,
reviewCount: 8764,
discount: 'FREE - First 50 Only'
},
{
title: 'Lakers vs Celtics',
category: 'Sports',
venue: 'Crypto.com Arena',
neighborhood: 'Downtown',
city: 'Los Angeles',
dateTime: 'Friday 7:30 PM - 10:30 PM',
priceRange: '$125 - $850',
image: 'https://images.unsplash.com/photo-1546519638-68e109498ffc?w=800',
coordinates: { lat: 34.0430, lng: -118.2673 },
vibeTags: ['High energy', 'Social', 'Premium'],
eventSignal: 'sales-end-soon',
organizerRating: 4.5,
reviewCount: 2341
},
{
title: 'Smorgasburg LA: Sunday Market',
category: 'Food & Drink',
venue: 'ROW DTLA',
neighborhood: 'Arts District',
city: 'Los Angeles',
dateTime: 'Sunday 10:00 AM - 4:00 PM',
priceRange: 'Free',
image: 'https://images.unsplash.com/photo-1555939594-58d7cb561ad1?w=800',
coordinates: { lat: 34.0341, lng: -118.2324 },
vibeTags: ['Family-friendly', 'Outdoor', 'Social'],
organizerRating: 4.8,
reviewCount: 5632
},
{
title: 'LACMA After Hours',
category: 'Arts',
venue: 'LACMA',
neighborhood: 'Miracle Mile',
city: 'Los Angeles',
dateTime: 'Friday 7:00 PM - 11:00 PM',
priceRange: '$35 - $75',
image: 'https://images.unsplash.com/photo-1531243269054-5ebf6f34081e?w=800',
coordinates: { lat: 34.0639, lng: -118.3592 },
vibeTags: ['Chill', 'Date night', 'Sophisticated'],
organizerRating: 4.7,
reviewCount: 1234,
ageRestriction: '21+',
discount: 'MEMBER PRICE'
},
{
title: 'Blue Note Under Stars',
category: 'Music',
venue: 'Hollywood Bowl',
neighborhood: 'Hollywood Hills',
city: 'Los Angeles',
dateTime: 'Saturday 8:00 PM - 11:00 PM',
priceRange: '$45 - $200',
image: 'https://images.unsplash.com/photo-1514320291840-2e0a9bf2a9ae?w=800',
coordinates: { lat: 34.1122, lng: -118.3391 },
vibeTags: ['Chill', 'Date night', 'Outdoor'],
lineup: ['Kamasi Washington', 'Thundercat', 'Terrace Martin'],
organizerRating: 4.8,
reviewCount: 12453
},
{
title: 'Meraki: Seth Troxler',
category: 'Nightlife',
venue: 'Sound Nightclub',
neighborhood: 'Hollywood',
city: 'Los Angeles',
dateTime: 'Saturday 10:00 PM - 4:00 AM',
priceRange: '$35 - $65',
image: 'https://images.unsplash.com/photo-1571266028243-e4733b0f0bb0?w=800',
coordinates: { lat: 34.0928, lng: -118.3287 },
vibeTags: ['High energy', 'Late night', 'Underground'],
lineup: ['Amelie Lens', 'I Hate Models', 'FJAAK'],
organizerRating: 4.6,
reviewCount: 1876,
ageRestriction: '21+'
},
{
title: 'Whitney Cummings + Friends',
category: 'Comedy',
venue: 'The Laugh Factory',
neighborhood: 'Hollywood',
city: 'Los Angeles',
dateTime: 'In 2 days 8:00 PM - 11:00 PM',
priceRange: '$25 - $55',
image: 'https://images.unsplash.com/photo-1527224538127-2104bb71c51b?w=800',
coordinates: { lat: 34.0901, lng: -118.3615 },
vibeTags: ['Chill', 'Social', 'Date night'],
organizerRating: 4.7,
reviewCount: 3241,
ageRestriction: '18+'
},
{
title: 'Venice Beach Drum Circle',
category: 'Music',
venue: 'Venice Beach Boardwalk',
neighborhood: 'Venice',
city: 'Los Angeles',
dateTime: 'Sunday 4:00 PM - 8:00 PM',
priceRange: 'Free',
image: 'https://images.unsplash.com/photo-1506157786151-b8491531f063?w=800',
coordinates: { lat: 33.9850, lng: -118.4695 },
vibeTags: ['Outdoor', 'Social', 'Chill'],
eventSignal: 'popular',
organizerRating: 4.6,
reviewCount: 2145
},
{
title: 'Rooftop Cinema: Blade Runner',
category: 'Film',
venue: 'Rooftop Cinema Club',
neighborhood: 'DTLA',
city: 'Los Angeles',
dateTime: 'Friday 8:30 PM - 11:00 PM',
priceRange: '$25 - $45',
image: 'https://images.unsplash.com/photo-1489599849927-2ee91cede3ba?w=800',
coordinates: { lat: 34.0407, lng: -118.2468 },
vibeTags: ['Date night', 'Views', 'Chill'],
organizerRating: 4.8,
reviewCount: 892
},
{
title: 'Dodgers vs Giants',
category: 'Sports',
venue: 'Dodger Stadium',
neighborhood: 'Elysian Park',
city: 'Los Angeles',
dateTime: 'Saturday 1:10 PM - 4:30 PM',
priceRange: '$35 - $350',
image: 'https://images.unsplash.com/photo-1566577739112-5180d4bf9390?w=800',
coordinates: { lat: 34.0739, lng: -118.2400 },
vibeTags: ['Family-friendly', 'Social', 'High energy'],
eventSignal: 'few-tickets-left',
organizerRating: 4.7,
reviewCount: 15678
},
{
title: 'Natural Wine Fair',
category: 'Food & Drink',
venue: 'Grand Central Market',
neighborhood: 'Downtown',
city: 'Los Angeles',
dateTime: 'Sunday 12:00 PM - 6:00 PM',
priceRange: '$45 - $85',
image: 'https://images.unsplash.com/photo-1510812431401-41d2bd2722f3?w=800',
coordinates: { lat: 34.0508, lng: -118.2490 },
vibeTags: ['Tasting', 'Social', 'Sophisticated'],
eventSignal: 'just-added',
organizerRating: 4.5,
reviewCount: 567,
ageRestriction: '21+'
},
{
title: 'Meditation in the Gardens',
category: 'Wellness',
venue: 'The Getty Center',
neighborhood: 'Brentwood',
city: 'Los Angeles',
dateTime: 'Sunday 7:00 AM - 9:00 AM',
priceRange: 'Free',
image: 'https://images.unsplash.com/photo-1506126613408-eca07ce68773?w=800',
coordinates: { lat: 34.0780, lng: -118.4741 },
vibeTags: ['Wellness', 'Outdoor', 'Chill'],
organizerRating: 4.9,
reviewCount: 1234
}
]
// Detailed event for EventDetail default
export const demoEventDetails: EventDetails = {
title: 'Sunglasses at Night: Underground Techno',
category: 'Nightlife',
venue: 'The White Rabbit',
neighborhood: 'The Woodlands',
city: 'Houston, TX',
startDateTime: getDateAt(2, 22),
endDateTime: getDateAt(3, 4),
priceRange: '$15 - $30',
images: [
'https://images.unsplash.com/photo-1470225620780-dba8ba36b745?w=800',
'https://images.unsplash.com/photo-1514525253161-7a46d19cd819?w=800',
'https://images.unsplash.com/photo-1533174072545-7a4b6ad7a6c3?w=800'
],
vibeTags: ['High energy', 'Late night', 'Underground'],
eventSignal: 'going-fast',
aiSummary: 'Raw, unfiltered techno in an authentic warehouse setting.',
description:
'Experience the raw energy of underground techno. Industrial beats, immersive visuals, and a crowd that lives for the music.',
lineup: ['Amelie Lens', 'I Hate Models', 'FJAAK'],
attendeesCount: 537,
friendsGoing: [
{ name: 'Alex', avatar: 'https://i.pravatar.cc/40?u=alex' },
{ name: 'Sam', avatar: 'https://i.pravatar.cc/40?u=sam' }
],
organizer: {
name: 'Midnight Lovers',
image: 'https://i.pravatar.cc/80?u=midnight',
rating: 4.5,
reviewCount: 1067,
verified: true,
followers: 1200,
eventsCount: 154,
hostingYears: 8,
trackRecord: 'great',
responseRate: 'very responsive'
},
venue_details: {
name: 'The White Rabbit',
address: '8827 Nasher Ave',
city: 'Houston TX',
coordinates: { lat: 29.7604, lng: -95.3698 }
},
tiers: [
{ name: 'General Admission', price: 15, available: 50 },
{
name: 'VIP Access',
price: 30,
available: 20,
benefits: ['Skip the line', 'Exclusive lounge']
}
],
goodToKnow: {
duration: '2 hours',
doorsOpen: '7:00 PM',
showtime: '7:30 PM',
ageRestriction: '21+',
dressCode: 'Casual',
parking: 'Limited, leave early to avoid long queues'
},
policies: {
refund: 'No refunds. Tickets are transferable.',
entry: 'Open 2 hours before event',
idRequired: true,
securityOnSite: true
},
faq: [
{
question: 'What is the refund policy?',
answer: 'No refunds. Tickets are transferable.'
},
{
question: 'When do doors open?',
answer: 'Open 2 hours before event.'
},
{
question: 'Is there parking?',
answer: 'Limited, leave early to avoid long queues.'
}
],
relatedTags: ['Houston Events', 'Texas Nightlife', 'Techno Parties']
}
// Ticket tiers for TicketTierSelect
export const demoTicketTiers = [
{
id: '1',
name: 'General Admission',
price: 45,
fee: 5,
available: 100,
maxPerOrder: 10,
},
{
id: '2',
name: 'VIP',
price: 150,
fee: 15,
available: 20,
maxPerOrder: 4,
description: 'Includes backstage access',
},
]
// Event confirmation data
export const demoEventConfirmation = {
orderNumber: 'EVT-12345',
eventTitle: 'Summer Music Festival',
ticketCount: 2,
recipientEmail: 'customer@example.com',
eventDate: 'Jan 20, 2024',
eventLocation: 'Central Park, New York',
organizer: {
name: 'Live Nation',
},
}