EventCheckIn Component
EventCheckIn Component
Component: src/features/events/components/EventCheckIn.tsx Route: /app/fundraising/events/:id/check-in Access: Authenticated users with Fundraising Hub access Last Updated: February 14, 2026 Status: ✅ Production Ready
---
Recent Updates (February 2026)
Styling Guide Alignment (Feb 14, 2026)
- Removed: Stat card icons — tiles now use standard
text-centerlayout with no icons per STYLING-GUIDE.md - Changed:
text-3xl font-bold→text-2xl font-semiboldon all stat numbers - Added: 4th stat tile "Check-In Rate" (was separate progress bar card) — grid now
grid-cols-2 md:grid-cols-4 - Removed: Separate full-width progress bar card
- Replaced: Back button with
<Breadcrumb>component (Marketing > Events & Ticketing > Event Name > Check-In) - Centered: Title/subtitle block
- Replaced: Local
formatTime(rawtoLocaleTimeString) with shareduseDateFormathook - Removed: Unused imports (
Separator,useTranslation,ArrowLeft,Collapsiblecomponents) - Kept: Green financial color on Checked In number (
text-green-600 dark:text-green-400)
Recent Updates (January 2026)
Bug Fix: Supabase Client Auth (Jan 13, 2026)
- Fixed: Component was using raw REST API calls with anon key instead of authenticated Supabase client
- Solution: Replaced all
fetch()calls withsupabase.from()queries for proper JWT authentication and RLS enforcement - Affected functions:
fetchRegistrationsQuery(),handleCheckIn(),handleUndoCheckIn()
---
Overview
The EventCheckIn component provides a real-time check-in interface for event day operations. It supports individual and guest check-ins with live updates via Supabase Realtime.
Features
- Real-time updates - Supabase Realtime subscription
- Stats cards - Total, checked in, remaining with progress bar
- Search - Filter by name or email
- Status filter - All, checked in, not checked in
- Guest support - Expandable rows showing guests
- Undo capability - Reverse accidental check-ins
UI Layout
┌─────────────────────────────────────────────────────────┐
│ ← Back to Event │
├─────────────────────────────────────────────────────────┤
│ Event Name - Check-In │
├─────────────────────────────────────────────────────────┤
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
│ │ Total │ │ Checked In │ │ Remaining │ │
│ │ 45 │ │ 35 │ │ 10 │ │
│ └───────────────┘ └───────────────┘ └───────────────┘ │
│ │
│ ████████████████████████░░░░░░░░░░ 78% │
├─────────────────────────────────────────────────────────┤
│ [Search by name or email...] [Status: All ▼] │
├─────────────────────────────────────────────────────────┤
│ ┌─────────────────────────────────────────────────────┐ │
│ │ ▶ John Doe ✓ Checked in 2:30 PM │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ ▶ Jane Smith [ Check In ] │ │
│ ├─────────────────────────────────────────────────────┤ │
│ │ ▼ Bob Wilson (2 guests) ✓ Checked in 2:45 PM │ │
│ │ └ Guest 1: Alice Wilson ✓ Checked in 2:45 PM │ │
│ │ └ Guest 2: Tom Wilson [ Check In ] │ │
│ └─────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘State Management
const [registrations, setRegistrations] = useState<Registration[]>([]);
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState<'all' | 'checked_in' | 'not_checked_in'>('all');
const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set());Real-time Subscription
useEffect(() => {
const channel = supabase
.channel('event-checkin')
.on(
'postgres_changes',
{
event: '*',
schema: 'public',
table: 'event_registrations',
filter: `event_id=eq.${eventId}`
},
(payload) => {
// Update local state with new data
handleRealtimeUpdate(payload);
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [eventId]);Check-In Logic
const handleCheckIn = async (registrationId: string, isGuest: boolean = false) => {
const table = isGuest ? 'registration_guests' : 'event_registrations';
await supabase
.from(table)
.update({
checked_in_at: new Date().toISOString(),
checked_in_by: currentUserId
})
.eq('id', registrationId);
};
const handleUndoCheckIn = async (registrationId: string, isGuest: boolean = false) => {
const table = isGuest ? 'registration_guests' : 'event_registrations';
await supabase
.from(table)
.update({
checked_in_at: null,
checked_in_by: null
})
.eq('id', registrationId);
};Stats Calculation
const stats = useMemo(() => {
const total = registrations.length + registrations.reduce(
(sum, r) => sum + (r.guests?.length || 0), 0
);
const checkedIn = registrations.filter(r => r.checked_in_at).length +
registrations.reduce(
(sum, r) => sum + (r.guests?.filter(g => g.checked_in_at).length || 0), 0
);
return {
total,
checkedIn,
remaining: total - checkedIn,
percentage: total > 0 ? Math.round((checkedIn / total) * 100) : 0
};
}, [registrations]);Filtering
const filteredRegistrations = registrations.filter(reg => {
// Search filter
const searchLower = searchQuery.toLowerCase();
const matchesSearch = !searchQuery ||
reg.registrant_first_name.toLowerCase().includes(searchLower) ||
reg.registrant_last_name.toLowerCase().includes(searchLower) ||
reg.registrant_email.toLowerCase().includes(searchLower);
// Status filter
const matchesStatus = statusFilter === 'all' ||
(statusFilter === 'checked_in' && reg.checked_in_at) ||
(statusFilter === 'not_checked_in' && !reg.checked_in_at);
return matchesSearch && matchesStatus;
});Data Fetching
Uses React Query for data fetching with automatic cache invalidation:
const { data: registrations = [], isLoading: loading, refetch } = useQuery({
queryKey: ['eventRegistrations', event.id],
queryFn: fetchRegistrationsQuery,
staleTime: 10000,
});Bug Fixes
December 30, 2025
- Fixed
initialLoad is not definederror - changed to useloadingfrom React Query
Related Components
EventDashboard- Parent metrics viewEventsManager- Event listRegistrationRow- Individual registration displayGuestRow- Guest check-in row
Related Documentation
- Main Events Doc:
../06-EVENTS-TICKETING.md - Event Dashboard:
./03-EVENT-DASHBOARD.md
Synced from IFMmvp-Frontend documentation: pages/marketing/events/04-EVENT-CHECK-IN.md
Ready to Get Started?
See how Alignmint can simplify your nonprofit's operations. Schedule a free demo with our team and we'll walk you through everything.
Questions? Email us at steven@getalignmint.org