ChecklistPortal Component
ChecklistPortal Component
Component: src/features/events/components/ChecklistPortal.tsx Route: /checklist/:token Access: Public (magic link token required)
---
Overview
The ChecklistPortal allows event registrants to complete pre-event requirements via a magic link. It supports waivers, file uploads, confirmations, and external links.
> Note (Mar 2026): Required event waivers are now signed during registration on PublicEventPage. Checklist portal waiver signing remains supported for backward compatibility and legacy registrations.
Recent Updates (Apr 2026)
- External link hardening: Checklist
linkitems open withwindow.open(url, '_blank', 'noopener,noreferrer')to prevent reverse-tabnabbing.
Features
- Magic link access - No login required, token-based authentication
- Progress tracking - Visual progress bar showing completion
- Multiple item types - Waivers, uploads, confirmations, links
- Real-time updates - Status updates via Edge Functions
- Mobile friendly - Responsive design for on-the-go completion
UI Layout
┌─────────────────────────────────────────────────────────┐
│ Organization Logo │
├─────────────────────────────────────────────────────────┤
│ │
│ Pre-Event Checklist │
│ Event Name │
│ December 25, 2025 │
│ │
│ Hello, John! Complete the items below before the event.│
│ │
├─────────────────────────────────────────────────────────┤
│ │
│ Progress: 2 of 4 complete │
│ ████████████░░░░░░░░░░░░ 50% │
│ │
├─────────────────────────────────────────────────────────┤
│ │
│ ✓ Liability Waiver [Completed] │
│ Signed on Dec 15, 2025 │
│ │
│ ✓ Medical Information Form [Completed] │
│ Uploaded: medical-form.pdf │
│ │
│ ○ Packing List Confirmation [Confirm] │
│ Please confirm you've reviewed the packing list │
│ │
│ ○ Travel Insurance [View Link] │
│ Purchase travel insurance (optional) │
│ │
└─────────────────────────────────────────────────────────┘URL Parameters
// URL: /checklist/abc123xyz789
const { token } = useParams<{ token: string }>();State Management
const [registration, setRegistration] = useState<Registration | null>(null);
const [event, setEvent] = useState<Event | null>(null);
const [checklistItems, setChecklistItems] = useState<ChecklistItem[]>([]);
const [checklistStatus, setChecklistStatus] = useState<ChecklistStatus[]>([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);Data Fetching
useEffect(() => {
const fetchChecklist = async () => {
const response = await fetch(
`${SUPABASE_URL}/functions/v1/get-checklist-portal?token=${token}`
);
if (!response.ok) {
setError('Invalid or expired link');
return;
}
const data = await response.json();
setRegistration(data.registration);
setEvent(data.event);
setChecklistItems(data.checklistItems);
setChecklistStatus(data.checklistStatus);
setLoading(false);
};
fetchChecklist();
}, [token]);Checklist Item Types
Waiver
const handleSignWaiver = (itemId: string, waiverId: string) => {
// Open public waiver signing route with registration/checklist context
window.open(`/waivers/sign/${waiverId}?registration=${registration.id}&checklist=${itemId}&token=${token}`, '_blank');
};Note: token is required for attendee waiver signing links; links without token are rejected.
File Upload
const handleFileUpload = async (itemId: string, file: File) => {
const formData = new FormData();
formData.append('file', file);
formData.append('registrationId', registration.id);
formData.append('checklistItemId', itemId);
formData.append('token', token);
const response = await fetch(
`${SUPABASE_URL}/functions/v1/upload-checklist-file`,
{ method: 'POST', body: formData }
);
if (response.ok) {
// Refresh checklist status
refetchStatus();
}
};Confirmation
const handleConfirm = async (itemId: string) => {
await fetch(`${SUPABASE_URL}/functions/v1/update-checklist-status`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
registrationId: registration.id,
checklistItemId: itemId,
status: 'completed',
token
})
});
refetchStatus();
};External Link
const handleOpenLink = (url: string) => {
window.open(url, '_blank', 'noopener,noreferrer');
};Progress Calculation
const progress = useMemo(() => {
const requiredItems = checklistItems.filter(item => item.required);
const completedRequired = requiredItems.filter(item => {
const status = checklistStatus.find(s => s.checklist_item_id === item.id);
return status?.status === 'completed' || status?.status === 'approved';
});
return {
total: requiredItems.length,
completed: completedRequired.length,
percentage: requiredItems.length > 0
? Math.round((completedRequired.length / requiredItems.length) * 100)
: 100
};
}, [checklistItems, checklistStatus]);Token Validation
The Edge Function validates:
- Token exists in
event_registrations.access_token - Token has not expired (
access_token_expires_at) - Registration is not canceled
Related Components
EventRegistrationSuccess- Links to checklist portalWaiverSigning- Waiver completion flow
Related Documentation
- Main Events Doc:
../06-EVENTS-TICKETING.md - Registration Success:
./06-EVENT-REGISTRATION-SUCCESS.md
Synced from IFMmvp-Frontend documentation: pages/fundraising/events/07-CHECKLIST-PORTAL.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