Skip to content

File Structure

Mariana Almeida edited this page Mar 13, 2026 · 5 revisions

This page explains how the TTS frontend codebase is organised and what each folder is for. If you are new to React, read the React primer section first — it will make everything else click faster.


React primer (read this first if you're new)

TTS is a React app written in TypeScript. React lets you build UIs out of reusable components — functions that return HTML-like syntax called JSX (or TSX in TypeScript).

Components

A component is just a function that returns what should appear on screen:

function Hello({ name }: { name: string }) {
  return <p>Hello, {name}!</p>
}

You can nest components like HTML tags:

<Hello name="Ana" />

State — useState

State is data that can change over time. When it changes, React re-renders the component.

const [count, setCount] = useState(0)
// count is the current value
// setCount is the function you call to change it

Side effects — useEffect

Use this to run code after the component renders — for example, to react when a value changes:

useEffect(() => {
  document.title = `Count: ${count}`
}, [count])  // runs every time `count` changes

If the dependency array is empty [], the effect runs only once when the component first mounts.

Sharing data — useContext

Passing props through many layers of components gets messy. A Context lets you share data across the whole component tree without prop-drilling.

// 1. Create the context
const ThemeContext = createContext('light')

// 2. Wrap your tree with a Provider
<ThemeContext.Provider value="dark">
  <App />
</ThemeContext.Provider>

// 3. Read it anywhere inside the tree
const theme = useContext(ThemeContext)

Data fetching — useSWR

TTS uses the SWR library to fetch data from the backend. It handles loading state, caching, and re-fetching automatically:

const { data, isLoading } = useSWR('/api/courses', fetcher)

Custom hooks

A custom hook is a function whose name starts with use. It wraps useState, useEffect, etc. into a reusable unit:

function useCourseUnits(majorId: number | null) {
  const { data, isLoading } = useSWR(
    majorId ? `/api/courses/${majorId}` : null,
    fetcher
  )
  return { courseUnits: data ?? [], loading: isLoading }
}

Top-level layout

tts-fe/
├── src/
│   ├── @types/          # TypeScript type definitions
│   ├── api/             # Functions that call the backend
│   ├── components/      # Reusable UI pieces
│   ├── contexts/        # Global state shared across the app
│   ├── hooks/           # Custom React hooks
│   ├── pages/           # One file per route/page
│   ├── styles/          # Global CSS (Tailwind config lives here)
│   └── utils/           # Pure utility functions (no React)
├── public/              # Static assets (images, icons)
├── .env                 # Environment variables (not committed)
└── vite.config.ts       # Build configuration

src/@types/ — TypeScript types

Contains the shared type definitions used across the whole app. If you see a type like CourseUnit or CourseOption and want to know its shape, look here.

Example: src/@types/index.d.ts exports all the main types.

When to touch this: When you add a new data structure that more than one file will use.


src/api/ — Backend calls

Each file in here groups related API calls. These are plain async functions — no React, no hooks. They use fetch (or a thin wrapper) to call the Django backend.

// src/api/courses.ts
export async function getCoursesByMajorId(majorId: number) {
  const res = await fetch(`/api/courses/${majorId}`)
  return res.json()
}

When to touch this: When you need to call a new or changed backend endpoint.


src/hooks/ — Custom hooks

Hooks wrap API calls and state logic so components stay clean. A hook fetches data, tracks loading state, and returns the result — the component just calls the hook.

// src/hooks/useCourseUnits.tsx
export default function useCourseUnits(majorId: number | null) {
  const { data, isLoading } = useSWR(
    majorId ? getCoursesByMajorId(majorId) : null
  )
  return { courseUnits: data ?? [], loading: isLoading }
}

When to touch this: When you need to fetch data and want to reuse that logic in more than one component.


src/contexts/ — Global state

Contexts hold state that many components need at the same time — things like the currently selected timetable option, the list of picked courses, or the theme.

Each context has two files:

  • FooContext.ts — creates the context object and its TypeScript type
  • FooProvider.tsx — the Provider component that holds the actual useState and passes data down

CombinedProvider.tsx wraps all providers together so main.tsx stays clean.

When to touch this: When you add state that needs to be shared across pages or deeply nested components.


src/components/ — UI building blocks

This is the biggest folder. Components are reusable pieces of UI. They receive data via props (or contexts) and render HTML.

The folder is organised by feature area:

components/
├── planner/          # The main timetable planner UI
│   ├── sidebar/      # Left panel (course picker, option switcher)
│   └── schedule/     # The timetable grid
├── ui/               # Generic reusable UI (Button, Dialog, Skeleton…)
├── admin/            # Admin dashboard components
└── svgs/             # SVG illustrations used in the UI

When to touch this: Almost always. Every visible UI change lives here.


src/pages/ — Routes

Each file here corresponds to a URL route. Pages are thin — they compose components together and pass top-level data down. Business logic belongs in hooks or contexts, not in pages.

pages/
├── PlannerPage.tsx       # /planner
├── AdminPage.tsx         # /admin
└── LoginPage.tsx         # /login

When to touch this: When you add a new route, or need to change what a page renders at the top level.


src/utils/ — Pure helpers

Utility functions with no React dependency — date formatting, local storage helpers, analytics tracking, etc.

When to touch this: When you have logic that doesn't depend on React state and could be tested in isolation.


How it all connects

A typical data flow looks like this:

pages/PlannerPage.tsx
  └── components/planner/sidebar/CoursePicker.tsx
        └── hooks/useCourseUnits.tsx      ← fetches data
              └── api/courses.ts          ← calls the backend

And shared state flows like this:

contexts/CombinedProvider.tsx
  └── contexts/MultipleOptionsContext.tsx
        └── (any component) useContext(MultipleOptionsContext)

Diagrams

High-level component map:

diagram-export-10-28-2024-12_08_15-PM

image