Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 0 additions & 73 deletions .eslintrc.js

This file was deleted.

6 changes: 0 additions & 6 deletions .flowconfig

This file was deleted.

10 changes: 6 additions & 4 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,13 @@ jobs:
- uses: actions/checkout@v7
- uses: actions/setup-node@v7
with:
node-version: "20"
node-version: "24"
cache: yarn
- name: Install dependencies
run: yarn install --frozen-lockfile
- name: Type check
run: yarn typecheck
- name: Lint
run: yarn lint
- name: Build
# react-scripts fails the build on lint warnings when CI=true; the
# existing warnings are reported but don't block, matching the Docker build.
run: CI=false yarn react-scripts build
run: yarn build
10 changes: 5 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,19 @@
# ---------------------------------------------------------------------------
# Frontend build stage
#
# Builds the React app; Flask serves the output as its static and template
# folder (server/static).
# Type-checks and builds the React app with Vite; Flask serves the output as
# its static and template folder (server/static).
# ---------------------------------------------------------------------------
FROM node:20-slim AS frontend
FROM node:24-slim AS frontend

WORKDIR /build
COPY package.json yarn.lock ./
RUN yarn install --frozen-lockfile

COPY .eslintrc.js .flowconfig .prettierrc.json ./
COPY index.html tsconfig.json vite.config.ts ./
COPY public ./public
COPY src ./src
RUN yarn react-scripts build
RUN yarn typecheck && yarn build

# ---------------------------------------------------------------------------
# Python build stage
Expand Down
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

## Local Development Set Up

Requires Python 3.14 and Node 20 (`mise install` picks both up from `mise.toml`).
Requires Python 3.14 and Node 24 (`mise install` picks both up from `mise.toml`). The frontend is React 19 and TypeScript, built with Vite.

1. Copy `.env.example` to `.env` and fill in the Canvas sandbox `CANVAS_CLIENT_ID` and `CANVAS_CLIENT_SECRET` (ask a maintainer). Never commit `.env`.
2. Create a virtual environment and install the server dependencies:
Expand All @@ -13,7 +13,7 @@ Requires Python 3.14 and Node 20 (`mise install` picks both up from `mise.toml`)
pip install -r server/requirements.txt
```
3. Install the frontend dependencies with `yarn install`.
4. Run both with `yarn run dev`, or separately: the backend with `python3 main.py` from `server/` (port 8000) and the frontend with `yarn start` (port 3000, proxies API calls to 8000).
4. Run both with `yarn dev`, or separately: the backend with `python3 main.py` from `server/` (port 8000) and the frontend with `yarn start` (the Vite dev server on port 3000, which forwards API calls, the login routes and `/offerings` to port 8000).
5. Run the backend tests from the repository root with `python -m unittest discover -s server -p "test_*.py"`.

## Authentication
Expand All @@ -22,7 +22,7 @@ One deployment serves every bCourses course, with the same routes as seating. Us

Course staff set up Sections for a course with "Import courses from canvas" (`/offerings/new`). Offerings moved from the monorepo store their data under an old key (`cs61a`, `data8`, ...); link each one to its Canvas course with `flask --app main link-offering <canvas id> <key> "<name>"`, run from `server/` against that database.

To sign in locally, click `Sign in`. You will be sent to `localhost:3000/login/`; change the port to 8000 (`localhost:8000/login/`) and finish signing in with Canvas, then go back to `localhost:3000`. The course list (`/offerings`) is only served by the backend, at `localhost:8000/offerings`. A "template not found" error on port 8000 after signing in is expected, because the built frontend isn't served in development.
To sign in locally, open `localhost:3000/offerings` and log in. This works on port 3000 if the sandbox developer key allows `http://localhost:3000/authorized/` as a redirect URI. Otherwise, log in at `localhost:8000/login/` instead, then go back to `localhost:3000`. A "template not found" error for course pages on port 8000 is expected, because the built frontend isn't served in development.

### About the Sandbox

Expand All @@ -39,7 +39,7 @@ bCourses API keys have strict scope and permissions. bCourses API keys should on
## Import Test Sections Locally

1. Start the server and front end.
2. Set up your sandbox course, either by signing in and using "Import courses from canvas" on `localhost:8000/offerings`, or with `python3 seed.py 157` from `server/` (157 is Mango 101 on the sandbox; this also adds demo sections).
2. Set up your sandbox course, either by signing in and using "Import courses from canvas" on `localhost:3000/offerings`, or with `python3 seed.py 157` from `server/` (157 is Mango 101 on the sandbox; this also adds demo sections).
3. From the `server` directory, import example sections and enrollment into that course:

```
Expand Down Expand Up @@ -72,7 +72,7 @@ curl -X POST \
```

## Notes
1. Use `yarn run flow` for typechecking.
1. Use `yarn typecheck` (TypeScript) and `yarn lint` (ESLint); CI runs both.
2. If you'd to see debugging logs while the server is up and running, the usual `print` statements will not work. Instead, a method that currently works is described in this [commit](https://github.com/Cal-CS-61A-Staff/berkeley-cs61a/commit/0e75b8798543c0edd68cc02d1d9c1a9389105087).
3. If you run the app locally and sign in, you may need to update the local database to give yourself appropriate access levels to test the admin, staff, and student views. To assign yourself staff and admin access run the following (substituting your own email) *after* having already logged in:

Expand Down
26 changes: 26 additions & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import js from "@eslint/js";
import prettier from "eslint-config-prettier";
import reactHooks from "eslint-plugin-react-hooks";
import reactRefresh from "eslint-plugin-react-refresh";
import globals from "globals";
import tseslint from "typescript-eslint";

export default tseslint.config(
{ ignores: ["build", "server", "venv", "node_modules"] },
{
files: ["**/*.{ts,tsx}"],
extends: [js.configs.recommended, ...tseslint.configs.recommended, prettier],
languageOptions: {
ecmaVersion: 2022,
globals: globals.browser,
},
plugins: {
"react-hooks": reactHooks,
"react-refresh": reactRefresh,
},
rules: {
...reactHooks.configs.recommended.rules,
"react-refresh/only-export-components": ["warn", { allowConstantExport: true }],
},
}
);
19 changes: 19 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<!-- {{ course }} is filled in by Flask, which serves the built page as a Jinja template. -->
<meta name="description" content="Sign up for {{ course }} Sections!" />
<link rel="apple-touch-icon" href="/logo192.png" />
<link rel="manifest" href="/manifest.json" />
<title>{{ course }} Sections</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>
2 changes: 1 addition & 1 deletion mise.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[tools]
python = "3.14"
node = "20"
node = "24"
78 changes: 36 additions & 42 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,51 +1,45 @@
{
"name": "sections",
"license": "MIT",
"proxy": "http://localhost:8000",
"dependencies": {
"bootstrap": "^4.5.2",
"bootstrap4-toggle": "^3.6.1",
"concurrently": "^5.1.0",
"eslint-config-airbnb": "^17.1.1",
"eslint-config-prettier": "^6.11.0",
"eslint-plugin-flowtype": "^8.0.3",
"flow-bin": "^0.131.0",
"jquery": "^3.5.1",
"levenary": "^1.1.1",
"moment": "^2.27.0",
"moment-timezone": "^0.5.31",
"nodemon": "^2.0.4",
"prettier": "2.0.5",
"react": "^16.13.1",
"react-bootstrap": "^1.3.0",
"react-bootstrap-icons": "^1.11.5",
"react-dom": "^16.13.1",
"react-markdown": "^5.0.3",
"react-router-dom": "^5.2.0",
"react-scripts": "^5.0.0",
"styled-components": "^5.1.1"
"private": true,
"type": "module",
"engines": {
"node": ">=24"
},
"scripts": {
"start": "react-scripts start",
"build": "yarn run react-scripts build && rm -rf deploy && mkdir deploy && cp -a server/. deploy/ && mv build deploy/static",
"test": "react-scripts test",
"eject": "react-scripts eject",
"dev": "concurrently -n 'server, client' \"nodemon --exec python3 server/main.py\" \"yarn start\"",
"flow": "flow"
"start": "vite",
"dev": "concurrently -n 'server,client' \"python3 server/main.py\" \"vite\"",
"build": "vite build",
"typecheck": "tsc --noEmit",
"lint": "eslint .",
"format": "prettier --write src"
},
"eslintConfig": {
"extends": "react-app"
"dependencies": {
"bootstrap": "^5.3.8",
"moment": "^2.31.0",
"moment-timezone": "^0.6.5",
"react": "^19.3.0",
"react-bootstrap": "^2.10.10",
"react-bootstrap-icons": "^1.11.6",
"react-dom": "^19.3.0",
"react-markdown": "^10.1.0",
"react-router-dom": "^7.18.4",
"styled-components": "^6.5.3"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.3.0",
"@vitejs/plugin-react": "^6.1.2",
"concurrently": "^10.0.5",
"eslint": "^10.12.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.7",
"globals": "^17.13.0",
"prettier": "^3.9.9",
"typescript": "~6.0.3",
"typescript-eslint": "^8.71.1",
"vite": "^8.3.3"
}
}
46 changes: 0 additions & 46 deletions public/index.html

This file was deleted.

13 changes: 5 additions & 8 deletions src/AddSessionModal.js → src/AddSessionModal.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
// @flow strict

import * as React from "react";
import Button from "react-bootstrap/Button";
import ListGroup from "react-bootstrap/ListGroup";
import Modal from "react-bootstrap/Modal";
Expand All @@ -10,16 +7,16 @@ import type { Section } from "./models";
import useSectionAPI from "./useSectionAPI";

type Props = {
section: Section,
show: boolean,
onClose: () => void,
section: Section;
show: boolean;
onClose: () => void;
};

const FloatRightDiv = styled.div`
float: right;
`;

export default function AddSessionModel({ section, show, onClose }: Props) {
export default function AddSessionModal({ section, show, onClose }: Props) {
const startSession = useSectionAPI("start_session");
const startTimes = sessionStartTimes(section);

Expand All @@ -31,7 +28,7 @@ export default function AddSessionModel({ section, show, onClose }: Props) {
<Modal.Body>
<ListGroup variant="flush">
{startTimes.map((startTime) => (
<ListGroup.Item>
<ListGroup.Item key={startTime.unix()}>
{startTime.format("MMMM D")}{" "}
<FloatRightDiv>
<Button
Expand Down
Loading
Loading