Skip to content

fix(catalog): use the paginated courses API instead of the first page only - #80

Merged
udaycodespace merged 1 commit into
udaycodespace:mainfrom
MOHITKOURAV01:fix/75-catalog-pagination
Aug 18, 2026
Merged

fix(catalog): use the paginated courses API instead of the first page only#80
udaycodespace merged 1 commit into
udaycodespace:mainfrom
MOHITKOURAV01:fix/75-catalog-pagination

Conversation

@MOHITKOURAV01

Copy link
Copy Markdown
Contributor

Summary

#43 made GET /api/user/getallcourses paginated, searchable, filterable and
sortable. The catalogue never picked any of it up:

const res = await axiosInstance.get("/api/user/getallcourses");
setAllCourses(res.data.data || []);

normalizePagination defaults to DEFAULT_LIMIT = 12, so that response holds
twelve courses. AllCourses.jsx then filtered those twelve in a useMemo
and dropped res.data.pagination on the floor.

Course thirteen could not be seen or enrolled in, there was no page control
anywhere in the UI, the search box searched one page, and the "courses found"
counter maxed out at twelve.

The API already does the work, so this is all on the client.

Related Issue

Closes #75

What changed

  • lib/catalogQuery.js — the pure half, no React, so it can be tested without
    a DOM: the query builder, the pagination reader, page clamping, the
    page-number window, and the range label.
  • hooks/useCourseCatalog.js owns the request. Two things it has to get right:
    • Debounce, 350 ms after the last keystroke. Without it the search box
      fires a request per character.
    • Stale responses. A slow request for "re" landing after a fast one for
      "react" would put the wrong results on screen. Every request takes a
      ticket and only the newest one may write state.
      It also resets to page one whenever the query changes, since staying on page
      four while switching to a filter with two pages shows an empty grid.
  • CatalogPager renders Previous / numbered pages / Next, always keeping the
    first, last and current pages present so nothing is more than two clicks
    away, with aria-current="page" and per-button labels.
  • The free/paid test moves off /\d/.test(course.C_price) — which calls a
    course priced "Free for the first 100" paid while the server's
    FREE_PRICE_PATTERN calls it free — onto the server's rule. The two halves
    of the same filter used to disagree.
  • A page that no longer exists (a filter narrowed, a course deleted) clamps to
    one that does instead of rendering an empty grid.
  • A sort control, since the API already accepts sort and nothing exposed it.
  • The empty state distinguishes "nothing matched your filters" from "there are
    no courses yet", and only offers "Clear filters" in the first case.

Type

  • Bug fix
  • New feature
  • Refactor
  • Docs only
  • Tests
  • Config / workflow
  • Security
  • Breaking change

Areas touched

  • Frontend
  • Backend
  • Database
  • Docs
  • Workflow / GitHub Actions
  • Config / environment

Testing

  • Tested locally
  • Build passes
  • Lint passes
  • Tests added or updated
  • Docs only, no runtime testing needed

npm test in frontend/: 22 passing. npm run build passes.

Test steps

  1. Seed more than 12 courses.
  2. Open the catalogue. A pager appears under the grid; the counter shows the
    real total, not 12.
  3. Search for a course that used to sit on page two — it is found.
  4. DevTools → Network: one request per ~350 ms of typing, carrying
    ?page=1&limit=12&search=….
  5. Go to the last page, then narrow the filter. The grid does not go blank; the
    page clamps to one that exists.
  6. cd frontend && npm test

Screenshots

  • Not needed
  • Added below

Happy to add before/after shots of the grid and the new pager if that helps
review.

Edge cases checked

  • Empty or missing data
  • Loading / slow response
  • API failure / server error
  • Rate limit / throttling
  • Invalid or unexpected input
  • Permission / access denied
  • Partial or inconsistent data
  • Mobile / small screen behavior
  • Other

Other edge case details

readPagination fills in totalPages and the has-page flags when a response
does not carry them, and falls back to a single page if the whole block is
missing, so an older or proxied response cannot leave the grid without a pager.
An empty catalogue reports zero pages rather than one. The pager's buttons
shrink on small screens so "‹ Previous 1 … 9 Next ›" still fits.

Checklist

  • Read CONTRIBUTING.md
  • Linked the issue
  • Assigned before starting or approved by maintainer
  • Changes are focused on one issue
  • No debug logs or unused code
  • Documentation updated if needed
  • No new warnings or console errors
  • Changes are meaningful, not trivial

Notes

frontend/package.json gains an npm test script running node --test over
src/**/*.test.js. No new dependency — Node's own runner reads the ESM
modules directly. That is why buildPageWindow lives in lib/catalogQuery.js
rather than next to the component: Node cannot import .jsx, and I would
rather the arithmetic be testable than have it sit in a file the runner cannot
open. The React components still need a DOM and are not covered; the logic that
decides which courses are reachable is.

npm run lint is failing on main already (68 errors, mostly missing
prop-types across existing components). I matched the surrounding style
rather than adding prop-types to the two new components only; happy to add them
if you would prefer to start narrowing that down.

docs/issue-75-catalog-pagination.md has the details.

… only

udaycodespace#43 made GET /api/user/getallcourses paginated, searchable and filterable.
AllCourses.jsx never picked it up: it called the endpoint bare, got the
default twelve rows, filtered those twelve in a useMemo and dropped the
pagination block. Course thirteen was unreachable, the search box searched
one page, and the "courses found" counter maxed out at twelve.

The API already does all of this, so the change is on the client.

- lib/catalogQuery.js is the pure half: the query builder, the pagination
  reader, page clamping, the page-number window, and the range label. Kept
  free of React so it can be tested without a DOM.
- hooks/useCourseCatalog.js owns the request. It debounces typing at 350ms so
  the search box is one request rather than one per character, and gives every
  request a ticket so a slow response for "re" cannot land on top of a fast
  one for "react".
- CatalogPager renders Previous/pages/Next, always keeping the first, last and
  current pages present so nothing is more than two clicks away.
- The free/paid test moves off /\d/.test(C_price), which called a course
  priced "Free for the first 100" paid while the server called it free, onto
  the same pattern the server uses.
- A page that no longer exists — a filter narrowed, a course deleted — clamps
  to one that does instead of rendering an empty grid.

frontend gains npm test, running node --test over src/**/*.test.js. No new
dependency; Node's runner reads the ESM modules directly.

Closes udaycodespace#75
@udaycodespace udaycodespace added ECSoC26 Required label for a PR to be eligible for Sentinel scoring in review PR is up and waiting on maintainer review labels Aug 17, 2026
@udaycodespace
udaycodespace self-requested a review August 17, 2026 09:48
@udaycodespace udaycodespace added good-pr PA-awarded bonus for an exceptionally executed PR — +15 XP and removed in review PR is up and waiting on maintainer review frontend documentation configuration fullstack tests labels Aug 18, 2026
@udaycodespace

Copy link
Copy Markdown
Owner

@MOHITKOURAV01 Looks good to me. The pagination, search, sorting, and edge-case handling are well covered. Approved and ready to merge.

@udaycodespace
udaycodespace merged commit 0179b0f into udaycodespace:main Aug 18, 2026
2 of 14 checks passed
@ecsoc-sentinel ecsoc-sentinel Bot added the ECSoC26-L3 Difficult, auto-assigned by Sentinel — 15 points label Aug 18, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ECSoC26-L3 Difficult, auto-assigned by Sentinel — 15 points ECSoC26 Required label for a PR to be eligible for Sentinel scoring good-pr PA-awarded bonus for an exceptionally executed PR — +15 XP

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: The course catalogue only ever shows the first 12 courses, and the search box only searches those

2 participants