feat: Guest Management & RSVP system (7ead7758)
Backend: - POST /api/events/:id/guests — add guest, auto-generate RSVP token (128-bit crypto.randomBytes), build wa.me WhatsApp deep-link, store in invitations - GET /api/events/:id/guests — list with pg_trgm fuzzy Hebrew name search, status filter, pagination; returns RSVP summary + capacity warning at 90% - GET /api/events/:id/guests/export — CSV export with UTF-8 BOM for Excel Hebrew support (json2csv) - PUT /api/guests/:id — PATCH-style update (COALESCE), organizer ownership check - DELETE /api/guests/:id — hard delete per Israeli Privacy Law 2023 - GET /api/rsvp/:token — public (no auth), marks opened_at on first visit - POST /api/rsvp/:token — idempotent RSVP submit, supports dietary update, handles cancelled events (410) - Israeli phone normalization: 05X-XXXXXXX → +972XXXXXXXXX E.164 - Capacity warning returned in add/list/update responses when confirmed ≥ 90% of venue_capacity (fire safety compliance) Frontend: - GuestListPage — sortable/filterable table, inline RSVP status override, WhatsApp send links, delete with confirmation, 30s polling for real-time updates - AddGuestForm — RTL Hebrew form, all guest fields, shows WhatsApp link on success - RsvpSummaryCard — 4-metric summary (total/confirmed/declined/pending) + capacity warning - RsvpPage — public page at /rsvp/:token, shows event details, confirm/decline, dietary preference update; no login required - New UI components: Badge, Select (shadcn/ui compatible) - App.tsx: added /events/:eventId/guests (protected) and /rsvp/:token (public) routes Build: 0 TS errors, all routes wired in server.js Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
67
client/src/components/RsvpSummaryCard.tsx
Normal file
67
client/src/components/RsvpSummaryCard.tsx
Normal file
@@ -0,0 +1,67 @@
|
||||
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
|
||||
|
||||
interface Summary {
|
||||
total: string | number;
|
||||
confirmed: string | number;
|
||||
declined: string | number;
|
||||
pending: string | number;
|
||||
}
|
||||
|
||||
interface CapacityWarning {
|
||||
message: string;
|
||||
percent: number;
|
||||
confirmed: number;
|
||||
capacity: number;
|
||||
}
|
||||
|
||||
interface RsvpSummaryCardProps {
|
||||
summary: Summary;
|
||||
warning?: CapacityWarning | null;
|
||||
}
|
||||
|
||||
export function RsvpSummaryCard({ summary, warning }: RsvpSummaryCardProps) {
|
||||
return (
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
|
||||
<Card>
|
||||
<CardHeader className="pb-1 pt-4 px-4">
|
||||
<CardTitle className="text-sm font-medium text-muted-foreground">סה״כ מוזמנים</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<p className="text-2xl font-bold">{summary.total}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-green-200">
|
||||
<CardHeader className="pb-1 pt-4 px-4">
|
||||
<CardTitle className="text-sm font-medium text-green-700">מאושרים</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<p className="text-2xl font-bold text-green-700">{summary.confirmed}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-red-200">
|
||||
<CardHeader className="pb-1 pt-4 px-4">
|
||||
<CardTitle className="text-sm font-medium text-red-700">לא מגיעים</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<p className="text-2xl font-bold text-red-700">{summary.declined}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
<Card className="border-yellow-200">
|
||||
<CardHeader className="pb-1 pt-4 px-4">
|
||||
<CardTitle className="text-sm font-medium text-yellow-700">ממתינים</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="px-4 pb-4">
|
||||
<p className="text-2xl font-bold text-yellow-700">{summary.pending}</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{warning && (
|
||||
<div className="rounded-md border border-orange-300 bg-orange-50 p-3 text-sm text-orange-800" dir="rtl">
|
||||
⚠️ {warning.message}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user