Skip to content

[FRONTEND] Build Course Create/Edit Form Page with Draft Persistence #251

Description

@smattymatty

Claiming This Task

Before you start working, check the Assignees section. If no one is assigned, leave a comment claiming the issue and assign it to yourself. This prevents duplicate work.

See the Community Wiki for contributing guidelines and git workflow.

Problem

There is no way to create or edit a course through the frontend UI. The backend's POST /api/courses/ and PATCH /api/courses/{id}/ endpoints are fully implemented, but there's no dedicated form page. Users need a proper create/edit experience with draft persistence, date pickers, and unsaved changes protection — matching the quality of SlideshowEditorPage.tsx.

Requirements

Routes

  • /courses/new — Create a new course (protected)
  • /courses/:id/edit — Edit an existing course (protected, teacher-only)

Form Fields

Field Input Type Validation
Title Text input Required, non-empty
Outline Textarea Optional, max 1000 chars with counter
Subject LazySelect Optional, choices from choicesService
Language LazySelect Optional, choices from choicesService
Country LazySelect Optional, choices from choicesService
Visibility Dropdown/radio Required: Public / Restricted / Private (with descriptions)
Start Date Date picker Required, must be before end date
End Date Date picker Required, must be after start date
Allow Join Requests Toggle/checkbox Only relevant when visibility is "Restricted"

Draft Auto-Save (like slideshow editor)

  • Save form state to localStorage on every change
  • Key: course-draft-{courseId} or course-draft-new
  • On edit mode: fetch server data, compare against draft, discard stale drafts
  • Show toast when recovering from a draft: "Draft recovered from {time ago}"
  • Clear draft after successful save

Unsaved Changes Protection (3-layer)

  1. Browser beforeunload warning (via useUnsavedChanges hook)
  2. React Router useBlocker with custom modal (via UnsavedChangesModal)
  3. Visual indicators: red borders on changed fields (via useFormDirtyState)

Create Flow

  1. User fills out form
  2. Clicks "Create Course"
  3. POST /api/courses/ — creator automatically becomes teacher
  4. On success: clear draft, show toast, redirect to /courses/{newId}
  5. On error: show toast with sanitized error message

Edit Flow

  1. Page loads: fetch course detail via getCourseDetail(id)
  2. Verify user is a teacher in this course (check user_role field) — redirect or show error if not
  3. Populate form with current data
  4. Check for localStorage draft, offer to restore if newer
  5. On save: PATCH /api/courses/{id}/ with changed fields only
  6. On success: update original data, clear dirty state, show toast
  7. On error: show toast, keep form state

Design

  • Follow the form layout patterns from ProfilePage.tsx (glass-morphism, grouped sections)
  • Header with back button (to course list or course detail)
  • Save button disabled when no changes or during save (with spinner)
  • Full dark mode support
  • Responsive: single column on mobile, two-column grid on desktop for date fields

Architecture Context

Component Hierarchy

App.jsx
├── /courses/new route (protected)
│   └── CourseFormPage.tsx (mode="create")
└── /courses/:id/edit route (protected)
    └── CourseFormPage.tsx (mode="edit")

A single CourseFormPage.tsx component handles both create and edit, switching behavior based on a mode prop or URL params.

Dependencies

  • Requires Issue [FRONTEND] Create Courses API Service, TypeScript Types, and Hooks #247 (API service, types, hooks)
  • Uses: coursesApi.createCourse(), coursesApi.getCourseDetail(), coursesApi.updateCourse()
  • Uses: CourseCreateRequest, CourseUpdateRequest, CourseDetail types
  • Uses: LazySelect from components/common/
  • Uses: useUnsavedChanges, useFormDirtyState hooks
  • Uses: UnsavedChangesModal from components/common/
  • Uses: choicesService for subject/language/country choices

API Dependencies

POST /api/courses/ — fully implemented

  • Required fields: title
  • Optional: outline, subject, language, country, visibility, start_date, end_date, allow_join_requests
  • Creator is automatically added as teacher with status='enrolled'
  • Requires CanCreateCourse permission (may require teacher occupation)

GET /api/courses/{id}/ — fully implemented

  • Returns CourseDetail with user_role field for permission checking

PATCH /api/courses/{id}/ — fully implemented

  • Partial update, only send changed fields
  • Requires IsCourseTeacher permission

Files to be Altered

New files:

  • Frontend/EduLiteFrontend/src/pages/CourseFormPage.tsx
  • Frontend/EduLiteFrontend/src/hooks/useCourseDraft.ts (optional — localStorage draft management)

Modified files:

  • Frontend/EduLiteFrontend/src/App.jsx — add /courses/new and /courses/:id/edit routes
  • Frontend/EduLiteFrontend/src/i18n/locales/en.json — add courses.form.* translations
  • Frontend/EduLiteFrontend/src/i18n/locales/ar.json — add courses.form.* translations

Testing Requirements

  • Create flow: fill form, submit, verify redirect to new course detail page
  • Edit flow: loads existing data, saves changes, shows success toast
  • Draft recovery: close tab mid-edit, reopen, verify draft is offered
  • Stale draft detection: edit on another device, return, verify stale draft is discarded
  • Validation: empty title blocked, start date after end date blocked
  • Unsaved changes: navigate away triggers modal, browser close triggers warning
  • Teacher permission: non-teachers redirected or shown error on edit page
  • Visibility toggle: "Allow Join Requests" only appears for restricted visibility
  • Dark mode: all elements styled correctly
  • Mobile: form is usable on small screens
  • Arabic: RTL layout, all text translated
  • No console errors

Metadata

Metadata

Assignees

Labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions