From 30b01124314e28c5478419a6eb537ce8863ee000 Mon Sep 17 00:00:00 2001 From: James Griffin Date: Sun, 5 Jul 2026 23:41:01 -0300 Subject: [PATCH] Show enrolment status on the student group classes page The class list now loads the student's own enrolments alongside the catalog; a class they already have an active enrolment in shows "You are enrolled in this class." instead of the Enrol button, in both the browse-all catalog and the single-class embed mode. Previously the button always rendered and a duplicate attempt walked the student through the whole questions/policies flow before failing with 409 already_enrolled. A cancelled enrolment does not block re-enrolling, matching the server-side duplicate rule. Closes #61 Co-Authored-By: Claude Fable 5 --- assets/js/group-classes.js | 22 ++++++++++++++++++---- docs/features/group-classes.md | 6 ++++++ 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/assets/js/group-classes.js b/assets/js/group-classes.js index b28661d..7e5de90 100644 --- a/assets/js/group-classes.js +++ b/assets/js/group-classes.js @@ -89,7 +89,7 @@ return `${formatDate(o.term_start)} – ${formatDate(o.term_end)} (${sessions} weekly sessions)`; } - function renderClasses(offerings) { + function renderClasses(offerings, enrolledOfferingIds) { let groups = offerings.filter((o) => o.kind === 'group_class'); if (singleOfferingId) { groups = groups.filter((o) => Number(o.id) === singleOfferingId); @@ -108,7 +108,9 @@ ${o.schedule_note ? `

${escHtml(o.schedule_note)}

` : ''} ${o.description ? `

${escHtml(o.description)}

` : ''}

${escHtml(Number(o.price).toFixed(2))} ${escHtml(o.currency)}

- + ${enrolledOfferingIds.has(Number(o.id)) + ? '

You are enrolled in this class.

' + : ``} `).join(''); @@ -188,8 +190,20 @@ clearError(); list.style.display = 'block'; confirm.style.display = 'none'; - apiFetch('offerings?kind=group_class') - .then(renderClasses) + // The student's own enrolments are fetched alongside the catalog so a + // class they already have an active enrolment in shows its status + // instead of offering to enrol them again (the API would reject the + // duplicate anyway). A cancelled enrolment does not block re-enrolling. + Promise.all([ + apiFetch('offerings?kind=group_class'), + apiFetch('enrollments'), + ]) + .then(([offerings, enrollments]) => renderClasses( + offerings, + new Set(enrollments + .filter((e) => e.status === 'active') + .map((e) => Number(e.offering_id))) + )) .catch((err) => showError(err.message)); } diff --git a/docs/features/group-classes.md b/docs/features/group-classes.md index def60c4..5665396 100644 --- a/docs/features/group-classes.md +++ b/docs/features/group-classes.md @@ -22,6 +22,12 @@ sessions. The class card on the enrolment page shows the date or date range with the session count. ## Enrolment Flow +The class list is loaded together with the student's own enrolments +(`GET /enrollments`); a class the student already has an `active` enrolment in +shows "You are enrolled in this class." instead of the Enrol button (the +server would reject the duplicate with `409 already_enrolled` regardless — a +cancelled enrolment does not block re-enrolling). + 1. Student opens a group class from the offering catalog. 2. Student answers the offering's questions (`GET /offerings/{id}/questions`). 3. Student accepts the current published policy versions (`GET /policies`) — required to continue. -- 2.54.0