Announcements surface to members through a bell icon on the Member Dashboard (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.
/member). The feature is owned entirely by MemberDescription — announcements are fetched, tracked, and displayed from within that component, not from a standalone page. The system is intentionally lightweight: there is no dedicated /announcements route for members; the modal is the sole interface.
Alumni members do not see announcements at all. The bell icon and its associated logic are gated by isAlumni(rank).
Architecture
Fetching Announcements
On mount,MemberDescription fetches all announcements the member is entitled to see:
Announcement[]. The full array is stored in allAnnouncementsData and each announcement’s id is extracted into allFetchedAnnouncementIds for badge calculation.
Announcement Type
The
description field is an HTML string. AnnouncementListShort uses DOMParser to strip HTML tags for the list preview and search matching. The full HTML is rendered inside ViewAnnouncementModal.Unread Badge Tracking
The unread count is tracked client-side usinglocalStorage. There is no server-side read state.
Storage key
Helpers
Badge calculation
calculateUnreadCount runs whenever allFetchedAnnouncementIds changes (i.e., after the fetch completes). It also runs after the modal is opened to immediately reflect the newly-read state.
Badge display
The badge renders as a red circle overlaid on the bell icon:"9+".
Opening the Modal
localStorage as read the moment the user opens the modal — not when they scroll to or click an individual item. This is a deliberate design choice: opening the modal is treated as acknowledgement.
MemberAnnouncementsListModal
The modal is rendered viacreatePortal into document.body, preventing z-index clipping issues with the page’s own stacking contexts. It applies useScrollLock to disable background scroll while open.
Props
Sorting
On open, announcements are sorted with pinned items first, then bycreated_at descending:
Loading state
The modal shows<LoadingSpinner> when it is open but announcementsData is still an empty array (i.e., the fetch from MemberDescription has not yet completed):
State reset
On close (!isOpen), sortedAnnouncements is reset to [] so the next open starts with a fresh sort.
AnnouncementListShort
AnnouncementListShort renders the scrollable list of announcement rows inside the modal. It has its own internal search state for filtering within the already-fetched list.
Props
announcements are optional. MemberAnnouncementsListModal passes only announcements and onView; the onEdit, onDelete, and onCreateNew props are used exclusively by admin views.
Search logic
The search in
AnnouncementListShort is a client-side substring filter on the already-loaded list. It does not re-query the backend.- Highlights pinned items with a red border (
border-bapred) and a red tinted background (bg-red-50) - Shows a pin SVG icon next to the title for
is_pinned === true - Truncates the description to 2 lines via
line-clamp-2 - Shows
created_atformatted as"MMM d, yyyy 'at' h:mm a"(e.g.,Jan 15, 2025 at 2:30 PM) usingdate-fns - Clicking anywhere on the row calls
onView(announcement)to openViewAnnouncementModal
ViewAnnouncementModal
A separate admin-shared modal (components/admin/ViewAnnouncementModal) renders the full announcement including the HTML description. It is opened from MemberAnnouncementsListModal.handleViewAnnouncement and stacks on top of the list modal (z-index is managed by the portal system).
Target Audience Field
Thetarget_audience field on Announcement ("all" | "members" | "pledges") is filtered server-side — the GET /announcements endpoint already returns only announcements relevant to the calling user. The client does not re-filter by this field.
Alumni Restrictions
canAccessFeature("alumni", "announcements") returns false per utils/permissions.ts. If you add a standalone announcements route, gate it with this helper.
Extending Announcements
Adding server-side read tracking
Adding server-side read tracking
Replace the localStorage helpers with API calls. On open, POST the announcement IDs to a
/announcements/mark-read endpoint. On mount, fetch unread count from /announcements/unread-count instead of computing locally.Adding per-announcement read state
Adding per-announcement read state
Currently all IDs are marked read on modal open. To mark items read individually, call
addAnnouncementsToReadStorage([announcement.id]) inside handleViewAnnouncement and then calculateUnreadCount() after.Showing announcements to alumni
Showing announcements to alumni
Remove the
!isAlumniUser guard around the bell button in MemberDescription. Alumni-specific announcements would need a new target_audience value (e.g., "alumni") supported by the backend filter.