Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/asubap/website/llms.txt

Use this file to discover all available pages before exploring further.

Event management for e-board users lives on the Events page (EventsPage.tsx, route /events) — not inside the Admin Dashboard at /admin. When the authenticated user’s role is "e-board", the Events page renders additional controls: a + New Event button above the Upcoming Events list, and per-card Edit, Delete, and Announce icon buttons. Creating events opens CreateEventModal; editing opens EditEventModal. Both modals are mounted via React.createPortal onto document.body. A toggle pill bar (Standard / Hidden) lets e-board users switch between the public-facing event feed and the hidden administrative bucket. All event data is typed as AdminEvent for e-board sessions and MemberEvent for general-member sessions, both extending the shared BaseEvent base type from src/types/index.ts.

The AdminEvent Type

The AdminEvent type is defined in src/types/index.ts and is the authoritative shape for events viewed by e-board users. It extends BaseEvent with fields that are only exposed to admins.
// src/types/index.ts

export type BaseEvent = {
  id: string;
  event_name: string;
  event_description: string;
  event_location?: string;
  event_date: string;
  event_time?: string;
  dress_code?: string;
  event_hours?: number;
  event_hours_type?: string;
  sponsors_attending?: string[];
  event_limit?: number;
  rsvp_count: number;
  attending_count: number;
};

export type AdminEvent = BaseEvent & {
  event_lat: number;
  event_long: number;
  event_rsvped: string[];      // User IDs who RSVPed
  event_attending: string[];   // User IDs who checked in
  is_hidden: boolean;          // Hidden from member feed
  check_in_window: number;     // Minutes before/after event time when check-in is open
  check_in_radius: number;     // Meters radius for geo-fenced check-in
  user_rsvped: boolean;
  user_attended: boolean;
};
The isAdminEvent type guard checks for the is_hidden property:
const isAdminEvent = (event: Event): event is AdminEvent => {
  return 'is_hidden' in event;
};
This guard is used in EventsPage and EditEventModal to safely branch logic that only applies to the admin-facing event shape.

View Modes: Standard vs. Hidden Buckets

EventsPage maintains a local showHidden boolean. A pill-style toggle at the top of the page switches between the two modes when the user is e-board.
const filteredEvents = allEvents.filter((event) => {
  const isHiddenEvent = 'is_hidden' in event && event.is_hidden;
  if (showHidden) {
    if (!isHiddenEvent) return false;  // Show only hidden events
  } else {
    if (isHiddenEvent) return false;   // Show only standard events
  }
  // ... also filter by searchQuery
});
Shows three sub-sections:
  • Events In-Session — events currently active based on event_date, event_time, and event_hours
  • Upcoming Events — events with a future event_date not yet in-session
  • Past Events — events with a past event_date, paginated with a “Load More” button (3 per page)
Members see this same feed on the public events page.
Hidden events participate in the same hours-granting system as standard events. The event_hours and event_hours_type fields are still required when creating a bucket so that the backend correctly credits the right hours category when members are manually added.

Creating an Event

CreateEventModal is mounted via React.createPortal onto document.body (z-index 9999). It owns all form state locally and uses useScrollLock(true) to prevent background scroll while open. The + New Event button is only visible to e-board users in the Upcoming Events header.

Form Fields

FieldState VariableTypeAPI KeyNotes
Event TitleeventTitlestringevent_namePlain text, trimmed on submit
Descriptiondescriptionstringevent_descriptionPlain text <textarea>, 3 rows
Locationlocation.namestringevent_locationSet by LocationPicker component
Datedatestringevent_dateHTML date input (YYYY-MM-DD)
Timetimestringevent_timeHTML time input (HH:MM)
Event Hourshoursstringevent_hoursNumeric, parsed with parseFloat, step 0.5
Hours TypehoursTypestringevent_hours_typeSelect: professional, social, service, development, n/a
Check-in WindowcheckInWindownumbercheck_in_windowMinutes, default 15
Check-in RadiuscheckInRadiusnumbercheck_in_radiusMeters, default 50
Event LimiteventLimitnumberevent_limitMax attendees, default 100
FieldState VariableTypeAPI KeyNotes
Dress CodedressCodestringdress_codeFree-text, e.g., "Business Professional"
Sponsors Attendingsponsorsstring[]sponsors_attendingMulti-select via SponsorMultiSelect component
Latitudelocation.latitudenumberevent_latSet by LocationPicker, default 33.4242 (ASU)
Longitudelocation.longitudenumberevent_longSet by LocationPicker, default -111.9281 (ASU)
HiddenisHiddenbooleanis_hiddenCheckbox; makes event invisible in member feed

Request Payload

const eventData = {
  event_name: eventTitle.trim(),
  event_description: description.trim(),
  event_location: location.name.trim() || "Unnamed Location",
  event_lat: location.latitude,
  event_long: location.longitude,
  event_date: date,
  event_time: time,
  event_hours: parseFloat(hours),
  event_hours_type: hoursType,
  dress_code: dressCode.trim(),
  sponsors_attending: sponsors,
  check_in_window: checkInWindow,
  check_in_radius: checkInRadius,
  event_limit: eventLimit,
  is_hidden: isHidden,
};

// POST /events/add-event

Validation

All required fields are validated client-side before the request is sent. Errors are stored per-field in a FormErrors object and displayed inline below the relevant input. The form does not submit if any required field is empty or if hours is not a non-negative number.

Unsaved Changes Guard

CreateEventModal computes hasChanges() by JSON-serializing current state vs. the initialStateRef. If the user attempts to close with unsaved changes, a ConfirmationModal prompts:
"You have unsaved changes. Are you sure you want to close this form?"

Editing an Event

EditEventModal mirrors CreateEventModal but pre-populates its form from the eventToEdit prop. It additionally persists draft edits to localStorage under the key modal-event-edit-{eventId} whenever formData differs from initialStateRef. On successful save or deliberate discard, the key is cleared.

Edit Endpoint

// POST /events/edit-event
const eventDataToUpdate = {
  event_id: eventToEdit.id,
  name: formData.eventTitle.trim(),
  date: formData.date,
  location: {
    name: formData.location.name.trim(),
    latitude: formData.location.latitude,
    longitude: formData.location.longitude,
  },
  description: formData.description.trim(),
  time: formData.time,
  dress_code: formData.dressCode.trim(),
  sponsors: formData.sponsors,
  check_in_window: formData.checkInWindow,
  check_in_radius: formData.checkInRadius,
  event_limit: parseInt(formData.eventLimit),
  is_hidden: formData.isHidden,
  // event_hours and event_hours_type only included if set:
  ...(formData.hours.trim() && { event_hours: parseFloat(formData.hours) }),
  ...(formData.hoursType && { event_hours_type: formData.hoursType }),
};
After a successful save, EventsPage re-fetches the full event list from GET /events to refresh the UI.

Deleting an Event

Each EventCard rendered for e-board users has a Delete icon button. Clicking it sets eventToDelete and opens a ConfirmationModal:
"Are you sure you want to delete the event '{event_name}'? This action cannot be undone."
On confirm:
// POST /events/delete-event
body: JSON.stringify({ event_id: eventToDelete.id })
The deleted event is filtered from allEvents[] local state immediately.

Announcing an Event

E-board users can send an email announcement for an event via the Announce button on each EventCard. Clicking it opens a ConfirmationModal:
"Are you sure you want to announce the event '{event_name}'?"
On confirm:
// POST /events/send-event
body: JSON.stringify({
  event_id: eventToAnnounce.id,
  recipient_filter: "rsvped",  // Backend fetches emails from database
})

Location Picker

Both create and edit modals use the LocationPicker component (src/components/common/LocationPicker.tsx), which wraps Leaflet + leaflet-geosearch. It exposes a LocationObject:
interface LocationObject {
  name: string;
  latitude: number;
  longitude: number;
}
The default coordinates (33.4242, -111.9281) correspond to Arizona State University’s main campus.
The SponsorMultiSelect component (src/components/common/SponsorMultiSelect.tsx) lets e-board users tag which sponsors are attending an event. Selected values are stored as an array of company name strings that maps to the sponsors_attending API field.

Check-In Configuration

The check_in_window and check_in_radius fields control the geo-fenced mobile check-in feature:
FieldUnitDefaultPurpose
check_in_windowMinutes15How many minutes before/after event_time the check-in button is active
check_in_radiusMeters50Maximum GPS distance from event_lat/event_long for a valid check-in
These values are only surfaced in the AdminEvent type — MemberEvent only receives check_in_window and check_in_radius if the member has RSVP’d, and only exposes a boolean can_check_in rather than the raw values.

RSVP and Attendance Data

AdminEvent carries the full user ID arrays:
event_rsvped: string[];     // IDs of users who RSVPed
event_attending: string[];  // IDs of users who successfully checked in
rsvp_count: number;         // Aggregate count (from BaseEvent)
attending_count: number;    // Aggregate count (from BaseEvent)
The detailed participant objects (name + email per user) are fetched from a separate endpoint:
GET /events/:eventId/participants
Which returns EventParticipants:
export type EventParticipants = {
  event_id: string;
  event_name: string;
  rsvped_users: { user_id: string; name: string; user_email: string }[];
  attending_users: {
    user_id: string;
    name: string;
    user_email: string;
    checked_in_at?: string;
  }[];
  rsvp_count: number;
  attending_count: number;
};
Use rsvp_count and attending_count from BaseEvent for display in list rows (these are aggregates, always present). Only call /events/:eventId/participants when opening a detail/attendee modal to avoid fetching large arrays unnecessarily.

API Reference

OperationMethodEndpointKey Body Fields
List events (authed)GET/events
List events (public)GET/events/public
Create eventPOST/events/add-eventevent_name, event_date, event_time, event_hours, event_hours_type, is_hidden, check_in_window, check_in_radius, event_limit
Edit eventPOST/events/edit-eventevent_id, name, date, location, is_hidden, check_in_window, check_in_radius
Delete eventPOST/events/delete-eventevent_id
Announce eventPOST/events/send-eventevent_id, recipient_filter
Get participantsGET/events/:eventId/participants

Build docs developers (and LLMs) love