Refactor monolithic modules and harden booking, email, and auth infrastructure.
Split oversized frontend API client, email service, and admin/booking pages into focused modules while preserving import surfaces, and add Redis-backed queues, stale booking cleanup, stronger auth, and scale deployment configs. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
import { Event } from '@/lib/api';
|
||||
import Card from '@/components/ui/Card';
|
||||
import { CalendarIcon, MapPinIcon, CurrencyDollarIcon, UsersIcon } from '@heroicons/react/24/outline';
|
||||
|
||||
interface OverviewTabProps {
|
||||
event: Event;
|
||||
formatDate: (dateStr: string) => string;
|
||||
fmtTime: (dateStr: string) => string;
|
||||
formatCurrency: (amount: number, currency: string) => string;
|
||||
confirmedCount: number;
|
||||
checkedInCount: number;
|
||||
}
|
||||
|
||||
export function OverviewTab({ event, formatDate, fmtTime, formatCurrency, confirmedCount, checkedInCount }: OverviewTabProps) {
|
||||
return (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-4">
|
||||
<Card className="p-5">
|
||||
<h3 className="font-semibold text-base mb-3">Event Information</h3>
|
||||
<div className="space-y-3">
|
||||
<div className="flex items-start gap-3">
|
||||
<CalendarIcon className="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-sm">Date & Time</p>
|
||||
<p className="text-sm text-gray-600">{formatDate(event.startDatetime)}</p>
|
||||
<p className="text-sm text-gray-600">{fmtTime(event.startDatetime)}{event.endDatetime && ` - ${fmtTime(event.endDatetime)}`}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<MapPinIcon className="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-sm">Location</p>
|
||||
<p className="text-sm text-gray-600">{event.location}</p>
|
||||
{event.locationUrl && (
|
||||
<a href={event.locationUrl} target="_blank" rel="noopener" className="text-blue-600 text-xs hover:underline">
|
||||
View on Map
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<CurrencyDollarIcon className="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-sm">Price</p>
|
||||
<p className="text-sm text-gray-600">{event.price === 0 ? 'Free' : formatCurrency(event.price, event.currency)}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-start gap-3">
|
||||
<UsersIcon className="w-5 h-5 text-gray-400 mt-0.5 flex-shrink-0" />
|
||||
<div>
|
||||
<p className="font-medium text-sm">Capacity</p>
|
||||
<p className="text-sm text-gray-600">{confirmedCount + checkedInCount} / {event.capacity} spots filled</p>
|
||||
<p className="text-xs text-gray-500">{Math.max(0, event.capacity - confirmedCount - checkedInCount)} spots remaining</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
<Card className="p-5">
|
||||
<h3 className="font-semibold text-base mb-3">Description</h3>
|
||||
<div className="prose prose-sm max-w-none">
|
||||
<p className="text-sm text-gray-600 whitespace-pre-wrap">{event.description}</p>
|
||||
{event.descriptionEs && (
|
||||
<>
|
||||
<p className="font-medium text-sm mt-3">Spanish:</p>
|
||||
<p className="text-sm text-gray-600 whitespace-pre-wrap">{event.descriptionEs}</p>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
{event.bannerUrl && (
|
||||
<Card className="p-5 lg:col-span-2">
|
||||
<h3 className="font-semibold text-base mb-3">Event Banner</h3>
|
||||
<img
|
||||
src={event.bannerUrl}
|
||||
alt={event.title}
|
||||
className="w-full max-h-64 object-cover rounded-lg"
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user