PublicEventPage Component
PublicEventPage Component
Component: src/features/events/components/PublicEventPage.tsx Route: /events/:orgIdPrefix/:orgSlug/:eventSlug Access: Public (no authentication required)
---
Overview
The PublicEventPage is the public-facing event registration page. It displays event details, ticket options, and a registration form that integrates with Stripe Checkout for paid events.
Recent Updates (January 2026)
Sales Tax Support (Jan 22, 2026)
- Stripe Tax Integration: When
collect_sales_taxis enabled on the event, the checkout flow passes this to the Edge Function - Automatic Tax Calculation: Stripe Tax calculates applicable sales tax based on attendee location
- Tax Code: Event tickets use Stripe Tax code
txcd_90010001(Admission to events)
Features
- Hero section - Event image or gradient background
- Event details - Date, time, location/virtual link
- Ticket selection - Multiple ticket types with quantity controls
- Promo codes - Apply discounts
- Registration form - Name, email, phone, custom fields
- Order summary - Real-time total calculation
- Stripe integration - Secure payment processing
- Sold out/waitlist states - Graceful handling of capacity
UI Layout
┌─────────────────────────────────────────────────────────┐
│ ┌─────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ [Event Hero Image] │ │
│ │ │ │
│ │ Event Name │ │
│ │ Organization Name │ │
│ └─────────────────────────────────────────────────────┘ │
├─────────────────────────────────────────────────────────┤
│ │
│ 📅 December 25, 2025 at 6:00 PM │
│ 📍 Venue Name, City, State │
│ │
│ Event description text goes here... │
│ │
├─────────────────────────────────────────────────────────┤
│ Select Tickets │
│ ┌─────────────────────────────────────────────────────┐│
│ │ General Admission $50 [-] 2 [+] ││
│ │ VIP Package $100 [-] 0 [+] ││
│ └─────────────────────────────────────────────────────┘│
│ │
│ Promo Code: [________] [Apply] │
│ │
├─────────────────────────────────────────────────────────┤
│ Your Information │
│ ┌─────────────────────────────────────────────────────┐│
│ │ First Name: [________] Last Name: [________] ││
│ │ Email: [________________] ││
│ │ Phone: [________________] ││
│ │ ││
│ │ [Custom Fields...] ││
│ └─────────────────────────────────────────────────────┘│
│ │
├─────────────────────────────────────────────────────────┤
│ Order Summary │
│ 2x General Admission $100.00 │
│ Promo: SAVE20 -$20.00 │
│ ───────────────────────────────────────────────────── │
│ Total $80.00 │
│ │
│ [Complete Registration] │
└─────────────────────────────────────────────────────────┘Props
interface PublicEventPageProps {
config: {
id: string;
slug: string;
organizationId: string;
organizationSlug: string;
organizationName: string;
name: string;
description?: string;
event_type: string;
start_date: string;
end_date?: string;
timezone: string;
location_name?: string;
location_address?: string;
is_virtual: boolean;
virtual_url?: string;
capacity?: number;
total_registrations: number;
waitlist_enabled: boolean;
image_url?: string;
status: string;
ticket_types: TicketType[];
custom_fields: CustomField[];
has_checklist: boolean;
// Tax compliance (added Jan 2026)
collect_sales_tax?: boolean;
};
}State Management
const [ticketSelections, setTicketSelections] = useState<Record<string, number>>({});
const [promoCode, setPromoCode] = useState('');
const [appliedPromo, setAppliedPromo] = useState<PromoCode | null>(null);
const [registrantInfo, setRegistrantInfo] = useState({
firstName: '',
lastName: '',
email: '',
phone: ''
});
const [customFieldValues, setCustomFieldValues] = useState<Record<string, string>>({});
const [isSubmitting, setIsSubmitting] = useState(false);Checkout Flow
const handleSubmit = async () => {
setIsSubmitting(true);
try {
const response = await fetch(`${SUPABASE_URL}/functions/v1/create-event-checkout`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
eventId: config.id,
eventSlug: config.slug,
organizationId: config.organizationId,
tickets: Object.entries(ticketSelections).map(([id, qty]) => ({
ticketTypeId: id,
quantity: qty
})),
registrantEmail: registrantInfo.email,
registrantFirstName: registrantInfo.firstName,
registrantLastName: registrantInfo.lastName,
registrantPhone: registrantInfo.phone,
customFields: Object.entries(customFieldValues).map(([id, value]) => ({
fieldId: id,
value
})),
promoCode: appliedPromo?.code,
successUrl: `${window.location.origin}/events/${orgIdPrefix}/${orgSlug}/${eventSlug}/success`,
cancelUrl: window.location.href,
// Tax compliance
collectSalesTax: config.collect_sales_tax || false
})
});
const data = await response.json();
if (data.free) {
// Free registration - redirect to success
window.location.href = data.url;
} else {
// Paid registration - redirect to Stripe
window.location.href = data.url;
}
} catch (error) {
toast.error('Registration failed. Please try again.');
} finally {
setIsSubmitting(false);
}
};Promo Code Validation
const handleApplyPromo = async () => {
const response = await fetch(`${SUPABASE_URL}/functions/v1/validate-promo`, {
method: 'POST',
body: JSON.stringify({
eventId: config.id,
code: promoCode
})
});
const data = await response.json();
if (data.valid) {
setAppliedPromo(data.promo);
toast.success(`Promo code applied: ${data.discount}`);
} else {
toast.error(data.error || 'Invalid promo code');
}
};Capacity States
| State | Display |
|-------|---------|
| Available | Normal registration form |
| Sold Out (no waitlist) | "Sold Out" message, no form |
| Sold Out (waitlist enabled) | "Join Waitlist" button |
| Waitlist Full | "Event Full" message |
Related Components
PublicEventLoader- URL parsing and data fetchingEventRegistrationSuccess- Post-registration confirmation
Related Documentation
- Main Events Doc:
../06-EVENTS-TICKETING.md - Registration Success:
./06-EVENT-REGISTRATION-SUCCESS.md
Synced from IFMmvp-Frontend documentation: pages/marketing/events/05-PUBLIC-EVENT-PAGE.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