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
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)
Labels
FrontendDescription
ByteChain Academy's course browse page at
/coursescurrently 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.tsxdefines aconst courses = [...]array of 6 hardcoded fake courses and renders them — no API call is madeGET /api/v1/courses?search=&difficulty=&tags=&sortBy=&sortOrder=&page=&limit=frontend/components/course-card.tsxexists and renders a course card — it should be reused here/coursesas the main entry point for new users — this page must work wellRequirements
const coursesarray from/app/courses/page.tsxGET /api/v1/coursesusing TanStack Query?search=to the backend queryGET /api/v1/courses/tagsand render as selectable chips (multi-select)useInfiniteQuery), or numbered paginationenrollmentCountandisEnrolledbadge when the user is authenticatedSuggested Execution
Branch:
git checkout -b feat/course-search-browse-uiFiles to touch:
frontend/app/courses/page.tsxfrontend/components/course-card.tsxfrontend/components/courses/course-filters.tsx← create thisfrontend/components/courses/course-grid-skeleton.tsx← create thisfrontend/hooks/use-courses.ts← create thisImplement:
useCourseshook —useInfiniteQuerycallingGET /api/v1/courseswith all filter paramsuseCourseTagshook —useQuerycallingGET /api/v1/courses/tagsCourseFilters— search input, difficulty pills, tag chips, sort selector — all as controlled components that update a shared filter state objectuseDebounce(implement a small custom hook)CourseCardto accept and renderenrollmentCountand anisEnrolledgreen badgefetchNextPage()fromuseInfiniteQueryTest & Validate:
Acceptance Criteria
courses/page.tsxGET /api/v1/coursesvia TanStack Query?search=to backend?difficulty=to backendGET /api/v1/courses/tags?sortBy=and?sortOrder=paramsuseInfiniteQueryExample Commit Message
feat: replace hardcoded courses with real API, add search, filter, tag, and pagination UIGuidelines
Closes #[issue_id]