Skip to content

[Enhancement] Course progress tracking: resume playback, completion state, and progress bars on course cards #108

Description

@zeemscript

Summary

Build course progress tracking UI: persist playback position and completion per course, offer "Resume where you left off" on the course detail page, and show progress bars on course cards. This issue defines the frontend data contract and ships the UI with an optimistic local layer; the matching persistence endpoints are a small paired backend task.

Current state

  • There is no progress tracking anywhere. A purchased course plays through components/atoms/dashboard/vid-player-box.jsx — a Vidstack MediaPlayer fed a single data?.video URL with no position persistence, no onTimeUpdate/onEnded handlers, and hardcoded demo subtitle/thumbnail tracks pointing at files.vidstack.io.
  • Courses are currently single-video (one course.video field), not multi-lesson — the contract below must handle that today while being forward-compatible with lessons.
  • app/dashboard/courses/[courseId]/CourseDetailPageClient.jsx gates playback on canAccess (useHasCourse from hooks/usePurchase.js, which checks user.purchasedCourses), but renders the player from time zero every visit.
  • components/molecules/dashboard/cards/courseCard.jsx shows thumbnail/category/price/bookmark — no progress indicator. @radix-ui/react-progress (^1.1.4) is already installed and wrapped by the shadcn components/ui/progress primitive.
  • Out of scope (owned by an existing open issue): the dashboard-home widgets with placeholder values, e.g. the hardcoded chartData in components/organisms/dashboard/LearningProgress.jsx. Do not touch those here; expose your data via the hook so that issue can consume it later.

What to build

  1. Frontend contract (document it in the PR; align with the paired backend issue before merging):
    • GET /api/progress/courses{ success, progress: [{ courseId, percent, positionSeconds, durationSeconds, completed, lessonId?, updatedAt }] }
    • PUT /api/progress/course/:courseId with { positionSeconds, durationSeconds, lessonId?, completed? } → upserted record.
    • lessonId is optional/null for today's single-video courses; the shape supports arrays of lessons later without a breaking change.
  2. hooks/useCourseProgress.js:
    • Reads progress for one course (and a useAllCourseProgress variant for card grids — one batched fetch, not N requests).
    • reportProgress(positionSeconds, durationSeconds) — throttled/debounced writes (every ~15s of playback, plus on pause and on unmount via visibilitychange/cleanup), optimistic local state, and a localStorage write-behind (dnb:progress:<userId>:<courseId>) so progress survives offline/failed PUTs and is flushed on next load.
    • Completion rule: mark completed: true when position/duration ≥ 0.9 (90% watched) or on the player's ended event; completion is sticky (never un-completes by scrubbing back).
  3. Player integration: extend VidPlayerBox with optional startTime and onProgress/onEnded props using Vidstack's media events (onTimeUpdate, onEnded, or a MediaPlayer ref + subscription). Course detail wires these to the hook. While in there, remove or make conditional the hardcoded files.vidstack.io subtitle/thumbnail demo URLs (they 404-spam for real courses).
  4. Resume UX on CourseDetailPageClient.jsx: if saved progress exists and is >30s and <90%, show "Resume from 12:34" and "Start over" affordances before/over the player; picking resume seeds startTime.
  5. Progress bars on cards: in courseCard.jsx, for courses the user owns (user.purchasedCourses), render a slim progress bar (shadcn/Radix Progress) with percent, and a "Completed" check/badge state at 100%. Unowned courses keep the current price badge — no layout jank for the common case.
  6. Graceful degradation: if the backend endpoints 404 (not yet deployed), the hook must operate on localStorage only, silently — the UI works either way. Feature-detect once per session rather than erroring on every write.

Acceptance criteria

  • Watching a purchased course, leaving, and returning offers "Resume from mm:ss" and actually resumes there; "Start over" plays from 0.
  • Progress writes are throttled (network tab shows ~1 PUT per 15s of playback, plus pause/unload), not one per timeupdate.
  • Reaching ≥90% or the end marks the course completed; the state persists across reloads and never regresses when rewatching.
  • Owned-course cards on /dashboard/courses show accurate progress bars from one batched request; unowned cards are visually unchanged.
  • With progress endpoints unavailable, the full feature still works via localStorage and no error toasts/console spam appear.
  • Contract documented (endpoint shapes above) in the PR body for the backend pairing issue.
  • The hardcoded files.vidstack.io demo tracks no longer load for real course videos.
  • npm run lint and npm run build pass; CI green.

Pointers

  • components/atoms/dashboard/vid-player-box.jsx, app/dashboard/courses/[courseId]/CourseDetailPageClient.jsx, components/molecules/dashboard/cards/courseCard.jsx, hooks/usePurchase.js, hooks/useAuth.js (user identity for storage keys), lib/config/axios.config.js.
  • Vidstack media events/state: https://vidstack.io/docs/player/core-concepts/state-management
  • Gotcha: courseCard.jsx is rendered in grids — keep useAllCourseProgress at the page level and pass percent down as a prop to avoid N fetches. Respect the existing open issue boundary: no changes to components/organisms/dashboard/LearningProgress.jsx or other dashboard-home widgets.
  • Lockfile must stay npm-compatible; PRs target dev.

Difficulty

Medium — clean scope with an existing player and card to extend, but requires a well-designed sync contract, throttling/offline write-behind, and forward compatibility with a future lessons model.


🏆 GrantFox OSS — Official Campaign | FWC26. Apply for this issue through the GrantFox campaign page. The maintainer assigns one contributor before work starts; unassigned PRs may not be reviewed. PRs target the dev branch. Quality bar: CI must stay green.

💬 Questions or need help? Reach the maintainers and other contributors on the DeenBridge Telegram: https://t.me/+nst9lXNj1wc4ZDE0

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions