diff --git a/src/APIFunctions/SCEvents.js b/src/APIFunctions/SCEvents.js index e29aa3426..3e2cd82bd 100644 --- a/src/APIFunctions/SCEvents.js +++ b/src/APIFunctions/SCEvents.js @@ -141,3 +141,28 @@ export async function registerForSCEvent(eventId, token, payload) { return status; } + +export async function joinWaitlistForSCEvent(eventId, token) { + const status = new ApiResponse(); + + try { + const res = await fetch(`${SCEVENTS_API_URL}/events/${eventId}/waitlist`, { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + const body = await res.json(); + status.responseData = body; + + if (!res.ok) { + status.error = true; + } + } catch (err) { + status.error = true; + status.responseData = err; + } + + return status; +} diff --git a/src/Pages/Events/CalendarView.js b/src/Pages/Events/CalendarView.js index 5378ead08..69dc9fc21 100644 --- a/src/Pages/Events/CalendarView.js +++ b/src/Pages/Events/CalendarView.js @@ -1,6 +1,6 @@ import React, { useState, useMemo, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; -import { getEventAttendanceSummary } from '../../APIFunctions/SCEvents'; +import { getEventAttendanceSummary, joinWaitlistForSCEvent } from '../../APIFunctions/SCEvents'; // ─── tiny helpers ──────────────────────────────────────────────────────────── @@ -281,6 +281,9 @@ function EventPopup({ event, onClose, isAdminView, user }) { const [attendeeCount, setAttendeeCount] = useState(null); const [attendanceLoading, setAttendanceLoading] = useState(false); const [attendanceLoaded, setAttendanceLoaded] = useState(false); + const [waitlistSubmitting, setWaitlistSubmitting] = useState(false); + const [waitlistMessage, setWaitlistMessage] = useState(''); + const [waitlistError, setWaitlistError] = useState(''); const canEditEvent = Array.isArray(event.admins) && userId && @@ -292,6 +295,13 @@ function EventPopup({ event, onClose, isAdminView, user }) { ? Math.max(maxAttendees - attendeeCount, 0) : null; const registrationCta = getRegistrationCta(event); + const isFull = hasCapacityLimit && typeof remainingSpots === 'number' && remainingSpots <= 0; + const shouldShowWaitlistJoin = + !canEditEvent && + event.status === 'published' && + !registrationCta.disabled && + isFull && + !!event.waitlist_enabled; useEffect(() => { function onKey(e) { @@ -342,6 +352,41 @@ function EventPopup({ event, onClose, isAdminView, user }) { }; }, [eventId, authToken]); + async function handleJoinWaitlist() { + setWaitlistError(''); + setWaitlistMessage(''); + + if (!authToken) { + setWaitlistError('You must be logged in to join the waitlist.'); + return; + } + + if (!eventId) { + setWaitlistError('Missing event id.'); + return; + } + + setWaitlistSubmitting(true); + const response = await joinWaitlistForSCEvent(eventId, authToken); + setWaitlistSubmitting(false); + + if (response.error) { + let msg = ''; + const data = response.responseData; + + if (data && typeof data === 'object' && data.error) { + msg = String(data.error); + } else if (typeof data === 'string' && data.trim()) { + msg = data.trim(); + } + + setWaitlistError(msg || 'Failed to join waitlist.'); + return; + } + + setWaitlistMessage('Joined waitlist successfully.'); + } + return (
{attendeeCount} {attendeeCount === 1 ? 'person' : 'people'} registered {event.waitlist_enabled && ( - · waitlist enabled + · waitlist available )}

)} @@ -433,7 +478,7 @@ function EventPopup({ event, onClose, isAdminView, user }) {

{remainingSpots} spot{remainingSpots !== 1 ? 's' : ''} left {event.waitlist_enabled && ( - · waitlist enabled + · waitlist available )}

)} @@ -483,7 +528,18 @@ function EventPopup({ event, onClose, isAdminView, user }) {
)} - {!canEditEvent && event.status === 'published' && !registrationCta.disabled && ( + {shouldShowWaitlistJoin && ( + + )} + + {!shouldShowWaitlistJoin && !canEditEvent && event.status === 'published' && !registrationCta.disabled && ( )} + + {waitlistError && ( +

{waitlistError}

+ )} + + {waitlistMessage && ( +

{waitlistMessage}

+ )}
diff --git a/src/Pages/Events/CreateEventPage.js b/src/Pages/Events/CreateEventPage.js index fe23a89bc..a9edd305c 100644 --- a/src/Pages/Events/CreateEventPage.js +++ b/src/Pages/Events/CreateEventPage.js @@ -89,6 +89,8 @@ export default function CreateEventPage() { const [visibility, setVisibility] = useState('public'); const [minimumVisibleRole, setMinimumVisibleRole] = useState(''); const [maxAttendees, setMaxAttendees] = useState(UNLIMITED_ATTENDEES); + const [waitlistEnabled, setWaitlistEnabled] = useState(false); + const [waitlistSize, setWaitlistSize] = useState(10); const [questions, setQuestions] = useState(defaultQuestions); const [submitError, setSubmitError] = useState(''); const [submitting, setSubmitting] = useState(false); @@ -184,6 +186,10 @@ export default function CreateEventPage() { if (maxAttendees !== UNLIMITED_ATTENDEES && (maxAttendees === '' || maxAttendees <= 0)) { setSubmitError('Please enter a valid max attendees, or check "No limit".'); } + if (waitlistEnabled && (!waitlistSize || Number(waitlistSize) <= 0)) { + setSubmitError('Please enter a valid waitlist size.'); + return; + } const payload = { id: eventId, @@ -200,6 +206,8 @@ export default function CreateEventPage() { status, visibility, minimum_visible_role: visibility === 'private' ? minimumVisibleRole : '', + waitlist_enabled: waitlistEnabled, + waitlist_size: waitlistEnabled ? Number(waitlistSize) : 0, }; setSubmitting(true); @@ -364,6 +372,51 @@ export default function CreateEventPage() { +
+
+ Waitlist +
+ +
+
+ {waitlistEnabled ? ( + setWaitlistSize(e.target.value ? parseInt(e.target.value, 10) : '')} + placeholder="e.g. 20" + /> + ) : ( + + )} +
+ + +
+ + {waitlistEnabled && ( +

+ Maximum number of users allowed on the waitlist. +

+ )} +
+