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.

The E-Board & Faculty page (/eboard-faculty) presents every executive board role and faculty advisor associated with the Beta Tau Chapter in a responsive card grid. The page is fully public — no authentication is required to view it — and is accessible from the primary navigation bar. To minimize redundant API calls while keeping data reasonably fresh, the component implements a short-lived localStorage cache: entries fetched from GET /eboard are stored in localStorage under the key "eboard_faculty_cache" alongside a timestamp, and the cached payload is reused for up to 6 seconds (CACHE_DURATION = 6000 ms) before the API is re-queried.

Data Fetching & Caching

const CACHE_KEY = "eboard_faculty_cache";
const CACHE_DURATION = 6000; // milliseconds

type CacheData = {
  data:      EboardFacultyEntry[];
  timestamp: number;
};
On mount the component:
1

Read localStorage

Retrieves the JSON string stored at "eboard_faculty_cache". If the entry exists and its timestamp is less than 6 seconds old, the cached data array is applied directly to state and the API call is skipped.
2

Fetch from API (if cache is stale or absent)

const response = await fetch(
  `${import.meta.env.VITE_BACKEND_URL}/eboard`,
  { method: "GET", headers: { "Content-Type": "application/json" } }
);
if (response.ok) {
  const data = await response.json();
  setEboardEntries(data);
  localStorage.setItem(
    CACHE_KEY,
    JSON.stringify({ data, timestamp: Date.now() })
  );
}
No Authorization header is sent — the /eboard endpoint is public.
3

Handle errors gracefully

If the fetch throws or returns a non-OK status, eboardEntries is set to an empty array [] and no error message is shown in the UI, resulting in an empty grid rather than a broken page.

EboardFacultyEntry Type

export type EboardFacultyEntry = {
  role:              string;           // e.g. "President", "Faculty Advisor"
  role_email:        string;           // role-based email address
  email:             string;           // personal email
  display_email?:    string;           // preferred email to show publicly
  profile_photo_url?: string;          // optional headshot URL
  name:              string | null;    // full name (null if not yet assigned)
  major:             string | null;    // declared major
  rank?:             number;           // sort order (lower = displayed first)
};
display_email takes precedence over role_email which takes precedence over email when rendering the clickable email button. Clicking the email copies it to the clipboard via navigator.clipboard.writeText() and triggers a success toast.

Card Grid & Sort Order

Entries are sorted ascending by rank before rendering, with unranked entries (rank === undefined) pushed to the end using 9999 as a fallback sort key:
[...eboardEntries]
  .sort((a, b) => (a.rank ?? 9999) - (b.rank ?? 9999))
  .map((entry, index) => (
    <div
      key={index}
      className="bg-white shadow-xl rounded-lg p-6 flex flex-col items-center text-center"
    >
      {/* card content — see Card Content section below */}
    </div>
  ))
The grid uses a three-breakpoint column layout:
BreakpointColumns
Mobile (default)1
Tablet (sm)2
Desktop (lg)3

Card Content

Each entry card is a white shadow-xl rounded tile (p-6) with centered content:

Profile Photo

Rendered as a w-32 h-32 circular image (rounded-full object-cover) when profile_photo_url is present. If the field is absent the photo section is omitted entirely.

Name

Displays entry.name or a hyphen ("-") if name is null. This handles in-progress board transitions where a seat is defined but not yet filled.

Role

Displayed in maroon (text-bapred) below the name. This is the primary identifier for the card.

Major

Displayed in muted gray (text-bapgray text-sm) below the role. Empty string if major is null.

Email Copy Button

If any of display_email, role_email, or email is present, a clickable <button> renders the address in maroon with an underline on hover. Clicking it:
  1. Calls navigator.clipboard.writeText(...) with the resolved email.
  2. Fires showToast("Role email copied to clipboard!", "success") from the ToastContext.
<button
  type="button"
  title="Copy role email to clipboard"
  onClick={() => {
    navigator.clipboard.writeText(
      entry.display_email || entry.role_email || entry.email
    );
    showToast("Role email copied to clipboard!", "success");
  }}
>
  {entry.display_email || entry.role_email || entry.email}
</button>

Page Layout Summary

EboardFacultyPage
├── Navbar (public nav, white bg, maroon outline, role prop forwarded)
├── main
│   └── section (py-16 pt-32)
│       └── max-w-6xl container
│           ├── <h1> Executive Board & Faculty
│           └── grid (1 → 2 → 3 cols)
│               └── EboardFacultyEntry cards, sorted by rank asc
└── Footer (backgroundColor="#AF272F")
The role prop is passed to Navbar so the navbar can conditionally expose the admin dashboard link without needing its own context call. Even on the public e-board page, an authenticated admin will still see the full authenticated navigation.

Build docs developers (and LLMs) love