Conversation
…grades
feat(scores): add Scores page with exam/assessment viewer
New protected route /scores showing all exams and assessments grouped by
course.
- ScoresClient: stats strip (total / scored / pending / avg%), filter tabs
(all / assessments / assignments), course-grouped cards, animated detail
drawer, resolved score logic (resolvedScore > pivot.score), maxMark
fallback, ungraded "Pending" state, keyboard-accessible close (Esc /
autoFocus)
- useExams / useAllExamAnswers / useAllExamQuestions hooks with TanStack
Query (staleTime 5 min)
- exam.d.ts: Exam, ExamAnswer, ExamQuestion, ExamParticipant types
- error.tsx boundary scoped to /scores
- robots.ts + sitemap.ts updated to include /scores
- 48 unit tests covering all states and edge cases
- OpenAPI spec updated with /scores entry
fix(auth): harden session expiry and logout flow
- lib/security/auth.ts: handleLogout() clears both EzyGo
(ezygo_access_token) and Supabase session; eliminates bare
router.replace("/") calls that left HttpOnly cookies intact
- protected layout: missing session/user → await handleLogout() instead of
bare redirect; auth session missing errors similarly routed through
handleLogout()
- login-form.tsx: improved post-login redirect handling
perf: reduce unnecessary network requests across protected pages
- user-settings provider: staleTime 30 s → 5 min, gcTime → 30 min,
refetchOnWindowFocus/Interval disabled; cache invalidated only on
SIGNED_IN (not TOKEN_REFRESHED)
- useNotifications: add countOnly param — when true, only the HEAD-only
unread-count query runs; action-conflict and infinite-feed queries (both
with 30 s polling) are skipped
- private-navbar: useNotifications(true, true) — eliminates two wasted
Supabase connections + 30 s polling on every protected page
- protected layout: institution loading/error no longer gates page render;
useInstitutions() still called for cache pre-warming
- DashboardClient ChartSkeleton: <Loading minimal /> instead of full-
viewport <Loading /> inside chart card containers
- GET /api/profile: fast path — DB row exists → return immediately (avatar
renders at once); EzyGo sync deferred to after() background task
feat(ui): Loading minimal prop for compact inline spinner
- loading.tsx: minimal mode uses py-8 compact container, hides ghost
message and timeout buttons; full mode unchanged
- sr-only label updated to "Loading, please wait..."
feat(http): axios interceptors and circuit-breaker integration
- lib/axios.ts: request-signing interceptor, response error normalisation,
circuit-breaker wrapping for EzyGo API calls
refactor(accept-terms): extract AcceptTermsClient component
- Accept-terms page split into server page.tsx + client
AcceptTermsClient.tsx for better RSC boundary separation
- Metadata and OG tags improved
chore(infra): bump npm pin in Dockerfile to 11.10.1
- Updated tarball URL and SHA-256 in base layer
test: fix stale assertions, lint errors, add 48 new tests
- ScoresClient.test.tsx: 48 tests — loading/error/empty states, stats
strip, course grouping, filter tabs, score display, drawer, a11y
- exams.test.tsx: unit tests for useExams and related query hooks
- profile route.test.ts: after() mocked via next/server mock
- robots/sitemap tests updated for new /scores route
- Fixed: unused waitFor/DeepPartial/sectionHeaders imports, stale
jsx-a11y/no-autofocus disable comment, missing activity_name_id: null in
makeExam fixture, unused mockPush in vi.hoisted()
|
✅ Version already bumped to No automatic version bump needed - the PR already includes a version update. This PR is ready for review! 🚀 |
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
There was a problem hiding this comment.
Pull request overview
This PR introduces a comprehensive scores viewer feature alongside significant auth hardening, performance optimizations, and infrastructure upgrades. The changes include a new protected /scores route with exam/assignment viewing capabilities, performance improvements through reduced network requests, enhanced auth session handling, automatic CSRF token refresh, and various infrastructure updates.
Changes:
- New scores viewer with course-grouped cards, stats strip, filter tabs, per-question breakdown drawer, and accessibility features
- Performance optimizations: eliminated redundant auth.getUser() calls in user-settings provider, added countOnly param to useNotifications, implemented fast-path response in profile API with background sync
- Auth hardening: handleLogout now clears both EzyGo and Supabase sessions, protected layout uses handleLogout instead of bare redirects, added isSupabaseLockTimeoutError helper
- CSRF token auto-refresh in axios interceptors with singleton deduplication
- Infrastructure: npm 11.10.1 bump in Dockerfile, dependency updates in package-lock.json
Reviewed changes
Copilot reviewed 34 out of 36 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
src/types/exam.d.ts |
New TypeScript types for Exam, ExamAnswer, ExamQuestion, ExamParticipant, ExamSettings, ExamCourse |
src/hooks/courses/exams.ts |
New hooks: useExams, useExamAnswers, useExamQuestions, useAllExamAnswers, useAllExamQuestions with proper staleTime/gcTime configs |
src/hooks/courses/__tests__/exams.test.tsx |
Comprehensive unit tests for all exam hooks (48 tests) |
src/app/(protected)/scores/ScoresClient.tsx |
Main scores component with ScoreCard, ExamDetailDrawer sub-components, stats strip, filter tabs, course grouping |
src/app/(protected)/scores/__tests__/ScoresClient.test.tsx |
48 tests covering loading/error/empty states, stats, filters, drawer, accessibility |
src/app/(protected)/scores/page.tsx |
Server page with metadata and dynamic rendering config |
src/app/(protected)/scores/error.tsx |
Error boundary scoped to scores route |
src/providers/user-settings.ts |
Eliminated redundant getUser() calls, increased staleTime to 5 min, disabled refetchOnWindowFocus/Interval, invalidate only on SIGNED_IN |
src/hooks/notifications/useNotifications.ts |
Added countOnly param to skip action-conflict and feed queries when only badge count needed |
src/lib/security/auth.ts |
Added isSupabaseLockTimeoutError helper for browser lock manager timeouts |
src/lib/axios.ts |
Added CSRF token auto-refresh interceptor with singleton promise deduplication and single-retry logic |
src/components/user/login-form.tsx |
Use getSession instead of getUser, handle lock timeout errors |
src/app/api/profile/route.ts |
Fast-path response for existing users with background EzyGo sync via after() |
src/app/(protected)/layout.tsx |
Use handleLogout instead of bare router.replace, removed institution loading gate |
src/app/(protected)/dashboard/page.tsx |
Updated robots metadata to noindex/nofollow |
src/app/(protected)/dashboard/DashboardClient.tsx |
Use minimal loading spinner in charts, updated welcome message |
src/components/loading.tsx |
Added minimal prop for compact inline spinner |
src/components/layout/private-navbar.tsx |
Use useNotifications(true, true) for count-only, added Scores nav button |
src/proxy.ts |
Added scores route to protected routes list |
src/app/sitemap.ts |
Added /build-info entry |
src/app/robots.ts |
Added /scores to disallow list |
src/app/accept-terms/page.tsx |
Refactored to RSC with metadata |
src/app/accept-terms/AcceptTermsClient.tsx |
Extracted client component |
src/app/(auth)/page.tsx |
Added metadata with robots noindex/nofollow |
src/app/(public)/build-info/page.tsx |
Added metadata with robots index/follow |
Dockerfile |
Bumped npm to 11.10.1 with SHA-256 verification |
package.json / package-lock.json |
Version bump to 1.9.3, dependency updates |
public/api-docs/openapi.yaml |
Version bump to 1.9.3 |
.example.env |
Updated NEXT_PUBLIC_APP_VERSION to 1.9.3 |
README.md |
Added scores viewer documentation |
Pull Request
Description
feat: scores viewer, auth hardening, perf optimisations, and infra upgrades
feat(scores): add Scores page with exam/assessment viewer
New protected route /scores showing all exams and assessments grouped by course.
fix(auth): harden session expiry and logout flow
perf: reduce unnecessary network requests across protected pages
feat(ui): Loading minimal prop for compact inline spinner
feat(http): axios interceptors and circuit-breaker integration
refactor(accept-terms): extract AcceptTermsClient component
chore(infra): bump npm pin in Dockerfile to 11.10.1
test: fix stale assertions, lint errors, add 48 new tests
Type of Change
Version Bump
node scripts/bump-version.js(fork PRs)