Skip to content

Build Course Browse Page — Real API Data, Search, Difficulty Filter, Tag Chips, and Pagination #221

@portableDD

Description

@portableDD

Labels

Frontend

Description

ByteChain Academy's course browse page at /courses currently renders a hardcoded static array of six fake courses. It has no search, no filtering, and no connection to the backend. As the course catalogue grows, a static list becomes useless. This issue replaces the hardcoded course list with a fully functional browse experience — real data from the backend, live search, difficulty and tag filters, sort options, and pagination — wired to the backend course search API from Issue 17.

Background & Context

  • frontend/app/courses/page.tsx defines a const courses = [...] array of 6 hardcoded fake courses and renders them — no API call is made
  • Backend (Issue 17) exposes: GET /api/v1/courses?search=&difficulty=&tags=&sortBy=&sortOrder=&page=&limit=
  • frontend/components/course-card.tsx exists and renders a course card — it should be reused here
  • The landing page links to /courses as the main entry point for new users — this page must work well

Requirements

  • Remove the hardcoded const courses array from /app/courses/page.tsx
  • Fetch courses from GET /api/v1/courses using TanStack Query
  • Add a search input (debounced 300ms) that passes ?search= to the backend query
  • Add a difficulty filter: pill buttons for "All", "Beginner", "Intermediate", "Advanced"
  • Add a tag filter: fetch available tags from GET /api/v1/courses/tags and render as selectable chips (multi-select)
  • Add a sort selector: "Newest", "Most Popular" (by enrollment count), "A-Z"
  • Add pagination: "Load More" button that appends the next page of results to the current list (infinite scroll pattern using TanStack Query useInfiniteQuery), or numbered pagination
  • Each course card should show enrollmentCount and isEnrolled badge when the user is authenticated
  • Show a loading skeleton grid while fetching, and an empty state when no courses match the filters

Suggested Execution

Branch: git checkout -b feat/course-search-browse-ui

Files to touch:

  • frontend/app/courses/page.tsx
  • frontend/components/course-card.tsx
  • frontend/components/courses/course-filters.tsx ← create this
  • frontend/components/courses/course-grid-skeleton.tsx ← create this
  • frontend/hooks/use-courses.ts ← create this

Implement:

  • useCourses hook — useInfiniteQuery calling GET /api/v1/courses with all filter params
  • useCourseTags hook — useQuery calling GET /api/v1/courses/tags
  • CourseFilters — search input, difficulty pills, tag chips, sort selector — all as controlled components that update a shared filter state object
  • Debounce search input with a 300ms delay using useDebounce (implement a small custom hook)
  • Update CourseCard to accept and render enrollmentCount and an isEnrolled green badge
  • "Load More" button that calls fetchNextPage() from useInfiniteQuery
  • Empty state: illustration or icon + "No courses match your filters" message with a "Clear filters" button

Test & Validate:

  • Page loads and shows real courses from the backend
  • Typing in search filters results after 300ms debounce
  • Difficulty pill filters correctly — selecting "Beginner" shows only beginner courses
  • Selecting multiple tags filters correctly
  • "Load More" appends next page without replacing the current list
  • Empty state appears when filters return no results
  • Authenticated users see enrollment status badges on cards

Acceptance Criteria

  • No hardcoded course data remains in courses/page.tsx
  • Courses fetched from GET /api/v1/courses via TanStack Query
  • Search input is debounced and passes ?search= to backend
  • Difficulty filter pills work and pass ?difficulty= to backend
  • Tag filter chips are populated from GET /api/v1/courses/tags
  • Sort selector works with ?sortBy= and ?sortOrder= params
  • "Load More" pagination works with useInfiniteQuery
  • Loading skeletons and empty state are implemented

Example Commit Message

feat: replace hardcoded courses with real API, add search, filter, tag, and pagination UI

Guidelines

  • Assignment required before starting
  • PR description must include Closes #[issue_id]
  • Join our Telegram: https://t.me/ByteChainAcademy
  • Complexity: High (200 pts)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions