Skip to content

Courses & Library: Faculty Panel

The faculty panel groups everything a trainer teaches under a single Courses sidebar entry with two sub-routes: My Courses (/courses) and Library (/courses/library). It replaces the old parallel "Courses" and "Lessons" nav entries and fixes the terminology so "Module" means a syllabus chapter everywhere, not a single lecture.

How it works

Mental model

Course                ← the thing a learner enrolls in
  └── Module          ← syllabus chapter (stored as lesson.unit_name, a string)
        └── Lesson    ← single atom: slide deck, reading, video, quiz, TipTap doc

Orthogonal to it, the Library is a pool of reusable Lessons not bound to any course.

Rules:

  • A Lesson belongs to exactly one (course, module) pair, or lives in the Library unattached.
  • A Module is a soft grouping with no DB entity: just lesson.unit_name values grouped client-side. Renaming a module updates unit_name on all its lessons.
  • Moving a Lesson between Library and a Course is a first-class UI action backed by the linkToCourse / unlinkFromCourse endpoints.
  • "Lesson" is the same word for every org type. "Module" resolves via the tp('module') terminology key: "Chapter" for K-12, "Module" for college and corporate.

Creating a course

A trainer clicks Courses, lands on /courses (empty-state CTA "Create your first course"), and opens the create wizard: title, branch, semester, then add modules by name. They land on /courses/:id/edit with one group per module. Inside a module, Add lesson reveals an inline TipTap editor so content is authored without leaving the course view. Publish is one modal with visibility and targeting.

Reusing a Library lesson

From Library, a row menu's Add to course… opens a course + module picker and calls linkToCourse. From the course editor, Add from Library opens a lesson picker that calls the same endpoint. A Library row shows attached-to chips for each course it is linked into.

Edge cases

  • First visit to /courses shows two CTAs: Create your first course (primary) and Browse Library (secondary).
  • An orphan standalone lesson from before this change shows up in Library automatically; no data migration needed.
  • A bookmarked /lessons URL redirects to /courses/library for trainers; learners keep /lessons unchanged.
  • Deleting a module with lessons prompts "Delete 10 lessons or move them to Library?", defaulting to move.

Routes

  • /courses: CoursesPage (shared). Trainers see tabs My Courses | Curated | External | Library; learners see the first three (no Library). Source tabs switch content inline; Library navigates to /courses/library. ?tab=<id> is honored for deep links.
  • /courses/library: LessonsPageTeacherLessonsView with Library active.
  • /courses/new: CreateCoursePage.
  • /courses/:courseId/edit: CourseEditPage. Unit → Module terminology applied.
  • /courses/browse: CourseBrowserPage, a secondary trainer browse route.
  • /lessons: learners get StudentLessonsView; trainers auto-redirect to /courses/library.
  • /lessons/new, /lessons/:id/edit, /lessons/:id/analytics: trainer authoring sub-pages, reachable from Library cards.

Endpoints

Method Endpoint Purpose
GET/POST /courses List/Create courses
GET/PATCH/DELETE /courses/{id} Course CRUD
POST /courses/{id}/publish Publish with targeting
POST /courses/{id}/lessons Add lesson to course
GET/PATCH /courses/lessons/{id} Nested lesson CRUD
POST/PATCH/DELETE /courses/lessons/{id}/blocks TipTap block CRUD
GET/POST /lessons Standalone (Library) lesson CRUD
POST /lessons/{id}/publish Publish standalone
POST /lessons/{id}/link-to-course Attach lesson to a course
POST /lessons/{id}/unlink-from-course Detach lesson to Library
GET /lessons/feed Learner lesson feed

Where it lives

Routes: /courses, /courses/library, /courses/:id/edit.

  • apps/web/src/pages/teacher/TeacherCoursesPage/: tab shell + MyCoursesTab / LibraryTab / AddToCourseModal
  • apps/web/src/pages/teacher/CourseEditPage/: inline editor, Add from Library, module rename
  • apps/web/src/hooks/useTerminology.ts: module terminology key