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 && (
+
+ Maximum number of users allowed on the waitlist.
+
+ )}
+
+