Frontend

Routing & Data Fetching

How pages are routed, how data flows between the frontend and backend, and how state is managed across the app.


Routing Map

Public Pages (no auth required)

RouteDescription
/loginEmail/password login + Google OAuth
/signupAccount registration
/verifyEmail verification code entry
/forgot-passwordPassword reset flow
/membershipMembership signup → Stripe payment
/landingMarketing landing page
/profile/[profileID]Public profile view (e.g., /profile/SillyPandasDeny)

Member Pages (require membership)

RouteDescription
/Home dashboard with greeting, highlighted event, recent activity
/eventsEvent browsing with search and category filters
/events/[eventId]/[year]Event registration form
/profileYour own profile page
/profile/editProfile editor
/connectionsYour connection history
/companionCompanion app entry (redirects to your event)
/companion/[eventId]/[year]Event companion home page
/companion/[eventId]/[year]/[page]Companion sub-pages (quests, connections, etc.)
/btxBizTech Exchange stock trading simulation

Admin Pages (require @ubcbiztech.com email)

RouteDescription
/adminEvent management portal
/admin/event/newCreate a new event
/admin/event/[eventId]/[year]Event dashboard (registrations, teams, analytics)
/admin/event/[eventId]/[year]/editEdit event details
/admin/event/[eventId]/[year]/statisticsPer-event statistics
/admin/membersMember management table
/admin/statisticsGlobal statistics dashboard
/admin/emailsEmail template management
/admin/companionCompanion configuration
/admin/livewall2D connection wall (force graph)
/admin/livewall/3d3D connection wall (Three.js)
/admin/btxBTX admin panel

Data Fetching Patterns

We use three data fetching approaches depending on the situation:

1. Server-Side Rendering (SSR) with getServerSideProps

Used for pages that need fresh data on every request and benefit from SEO or fast initial load.

// Example: src/pages/index.tsx
export const getServerSideProps: GetServerSideProps = async (context) => {
  const userData = await fetchBackendFromServer({
    endpoint: "/users",
    method: "GET",
    nextServerContext: { request: context.req, response: context.res },
  });
  return { props: { userData } };
};

The fetchBackendFromServer function extracts the auth token from cookies (server-side) and calls our REST API.

2. Client-Side with React Query

Used for interactive data that updates frequently or depends on user actions.

// Example: src/queries/useEvents.ts
export function useEvents() {
  return useQuery({
    queryKey: ["events"],
    queryFn: () => fetchBackend({ endpoint: "/events", method: "GET" }),
  });
}

React Query handles caching, background refetching, and loading/error states automatically.

3. Static Generation (SSG) with getStaticProps

Used rarely, only for pages where the content doesn't change per-user (like the event browsing page).


API Client (src/lib/db.ts)

All API calls go through two functions:

FunctionWhen to UseHow It Gets the Auth Token
fetchBackend()Client-side (React components, hooks)fetchAuthSession() from Amplify
fetchBackendFromServer()Server-side (getServerSideProps, middleware)Extracts from request cookies

Both functions call the same REST backend. The URL is determined by the NEXT_PUBLIC_STAGE environment variable:

local   → http://localhost:4000
dev     → https://api-dev.ubcbiztech.com
prod    → https://api.ubcbiztech.com

Middleware (src/middleware.ts)

The middleware runs on every request and enforces auth rules:

  1. Allow-listed paths skip auth entirely (login, signup, public assets, API routes, etc.)
  2. For all other paths, it fetches the current user from the backend
  3. If the user is not a member → redirect to /membership
  4. If the path starts with /admin and the user is not an admin → redirect to /
  5. On errors → redirect to /login

Admin Detection

A user is considered an admin if their email ends with @ubcbiztech.com. This check happens both in the middleware (frontend) and in backend service handlers.


State Management

React Query (Server State)

All backend data is managed through React Query hooks in src/queries/:

HookWhat It Fetches
useUser()Current authenticated user + admin status
useEvents() / useAllEvents()Published events / all events (admin)
useRegistrations()User's event registrations
useProfile()A profile by ID
useUserProfile()Authenticated user's own profile
useMembers()All members (admin)
useConnections()User's connections
useQuestProgress()Quest completion status
useQuizReport()Personality quiz results

React Context (Client State)

We have one context, RegistrationContext, used in companion pages to share the current user's event registration data without prop drilling.

Local Component State

Everything else (form state, UI toggles, filters, sorting) lives in local useState/useReducer within components.


Type System (src/types.ts)

Core types that appear everywhere:

TypeWhat It Represents
BiztechEventAn event with id, year, capacity, dates, registration questions, etc.
MemberA club member with profile data (faculty, major, year, etc.)
UserProfileA public-facing profile with visibility controls
UserAuth user with membership status, admin flag
RegistrationAn event registration with status, responses, QR scans
RegistrationQuestionA custom question on an event registration form
Previous
Architecture Overview