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:
Michilis
2026-06-25 07:12:59 +00:00
co-authored by Cursor
parent f0e2de2834
commit 613bd7be1d
75 changed files with 7702 additions and 5580 deletions
@@ -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>
);
}