From 68e513af416f85b3a2940a05c79febff1a4b597c Mon Sep 17 00:00:00 2001 From: trista-chen-29 Date: Fri, 17 Apr 2026 21:43:05 -0700 Subject: [PATCH] Add SCEvents visibility filtering and event visibility controls --- src/Pages/Events/CreateEventPage.js | 68 +++++++++++++++++++++++++- src/Pages/Events/EditEventPage.js | 74 ++++++++++++++++++++++++++++- src/Pages/Events/Events.js | 65 +++++++++++++++++++++++-- 3 files changed, 199 insertions(+), 8 deletions(-) diff --git a/src/Pages/Events/CreateEventPage.js b/src/Pages/Events/CreateEventPage.js index dd87d4a92..c130510c8 100644 --- a/src/Pages/Events/CreateEventPage.js +++ b/src/Pages/Events/CreateEventPage.js @@ -87,6 +87,9 @@ export default function CreateEventPage() { }); const [location, setLocation] = useState(''); const [description, setDescription] = useState(''); + const [status, setStatus] = useState('draft'); + const [visibility, setVisibility] = useState('public'); + const [minimumVisibleRole, setMinimumVisibleRole] = useState(''); const [maxAttendees, setMaxAttendees] = useState(UNLIMITED_ATTENDEES); const [questions, setQuestions] = useState(defaultQuestions); const [submitError, setSubmitError] = useState(''); @@ -176,6 +179,10 @@ export default function CreateEventPage() { setSubmitError('Could not resolve your user id.'); return; } + if (visibility === 'private' && !minimumVisibleRole) { + setSubmitError('Please select a minimum visible role for private events.'); + return; + } const payload = { id: eventId, @@ -184,12 +191,14 @@ export default function CreateEventPage() { time, location: location.trim(), description: description.trim(), - admins: [adminId], + admins: [adminId], // The event creator becomes the initial event admin in SCEvents registration_form: toApiRegistrationForm(questions), max_attendees: maxAttendees === UNLIMITED_ATTENDEES ? UNLIMITED_ATTENDEES : Number(maxAttendees), created_at: new Date().toISOString(), - status: 'draft', + status, + visibility, + minimum_visible_role: visibility === 'private' ? minimumVisibleRole : '', }; setSubmitting(true); @@ -336,6 +345,61 @@ export default function CreateEventPage() { placeholder="No limit" /> + +
+ + + +
+ + {visibility === 'private' && ( + + )}

diff --git a/src/Pages/Events/EditEventPage.js b/src/Pages/Events/EditEventPage.js index 08613db5f..e758a5e08 100644 --- a/src/Pages/Events/EditEventPage.js +++ b/src/Pages/Events/EditEventPage.js @@ -56,6 +56,9 @@ export default function EditEventPage() { const [time, setTime] = useState(''); const [location, setLocation] = useState(''); const [description, setDescription] = useState(''); + const [status, setStatus] = useState('draft'); + const [visibility, setVisibility] = useState('public'); + const [minimumVisibleRole, setMinimumVisibleRole] = useState(''); const [maxAttendees, setMaxAttendees] = useState(UNLIMITED_ATTENDEES); const [questions, setQuestions] = useState([]); const [eventAdmins, setEventAdmins] = useState([]); @@ -83,7 +86,12 @@ export default function EditEventPage() { setTime(evt.time || ''); setLocation(evt.location || ''); setDescription(evt.description || ''); - setMaxAttendees(evt.max_attendees || UNLIMITED_ATTENDEES); + setStatus(evt.status || 'draft'); + setVisibility(evt.visibility || 'public'); + setMinimumVisibleRole(evt.minimum_visible_role || ''); + setMaxAttendees( + typeof evt.max_attendees === 'number' ? evt.max_attendees : UNLIMITED_ATTENDEES, + ); setQuestions(evt.registration_form || []); setEventAdmins(evt.admins || []); } @@ -172,6 +180,11 @@ export default function EditEventPage() { return; } + if (visibility === 'private' && !minimumVisibleRole) { + setSubmitError('Please select a minimum visible role for private events.'); + return; + } + const payload = { name: eventName.trim(), date, @@ -181,6 +194,9 @@ export default function EditEventPage() { registration_form: toApiRegistrationForm(questions), max_attendees: maxAttendees === UNLIMITED_ATTENDEES ? UNLIMITED_ATTENDEES : Number(maxAttendees), + status, + visibility, + minimum_visible_role: visibility === 'private' ? minimumVisibleRole : '', }; setSubmitting(true); @@ -249,6 +265,7 @@ export default function EditEventPage() { ); } + // Edit access: only users listed in event.admins can update an event const isEventAdmin = eventAdmins.includes(userId); if (!isEventAdmin) { return ( @@ -360,6 +377,61 @@ export default function EditEventPage() { placeholder="No limit" /> + +
+ + + +
+ + {visibility === 'private' && ( + + )}

diff --git a/src/Pages/Events/Events.js b/src/Pages/Events/Events.js index cfa3be327..26461c395 100644 --- a/src/Pages/Events/Events.js +++ b/src/Pages/Events/Events.js @@ -74,8 +74,49 @@ function EditIcon() { ); } +function getUserAccessLevel(user) { + return user?.accessLevel ?? membershipState.NON_MEMBER; +} + +function canUserSeeEvent(event, user) { + const userId = user?._id != null ? String(user._id) : ''; + const userAccess = getUserAccessLevel(user); + + const isGlobalAdmin = userAccess >= membershipState.ADMIN; + const isEventAdmin = Array.isArray(event.admins) && userId + ? event.admins.includes(userId) + : false; + + const status = event.status || 'draft'; + const visibility = event.visibility || 'public'; + const minimumVisibleRole = event.minimum_visible_role || ''; + + // Draft events are only visible to global admins or users listed in event.admins + if (status === 'draft') { + return isGlobalAdmin || isEventAdmin; + } + + // Public → everyone + if (visibility === 'public') { + return true; + } + + // Private → compare access levels directly + if (visibility === 'private') { + const requiredLevel = membershipState[minimumVisibleRole?.toUpperCase()]; + if (requiredLevel === undefined) return false; + + return userAccess >= requiredLevel; + } + + return false; +} + function EventCard({ event, user }) { - const isAdmin = event.admins && user?._id && event.admins.includes(String(user._id)); + const isEventAdmin = + Array.isArray(event.admins) && user?._id + ? event.admins.includes(String(user._id)) + : false; return (
@@ -83,7 +124,7 @@ function EventCard({ event, user }) {

{event.name || 'Untitled Event'}

- {isAdmin && ( + {isEventAdmin && ( +
+ {event.status === 'draft' && ( + + Draft + + )} + {event.visibility === 'private' && ( + + Private + + )} +
+
{(event.date || event.time) && (
@@ -139,6 +193,7 @@ export default function EventsPage() { const [hasError, setHasError] = useState(false); const isSCEventsEnabled = config.SCEvents?.ENABLED; const canCreateEvent = user?.accessLevel >= membershipState.OFFICER; + const visibleEvents = events.filter((event) => canUserSeeEvent(event, user)); useEffect(() => { if (!isSCEventsEnabled) { @@ -210,15 +265,15 @@ export default function EventsPage() {
)} - {!isLoading && !hasError && events.length === 0 && ( + {!isLoading && !hasError && visibleEvents.length === 0 && (
No events available right now.
)} - {!isLoading && !hasError && events.length > 0 && ( + {!isLoading && !hasError && visibleEvents.length > 0 && (
- {events.map((event) => ( + {visibleEvents.map((event) => ( ))}