The add/edit Offering form takes a Start date and a Sessions control: one-off (term ends the day it starts) or weekly for N sessions (term_end = term_start + (N−1) weeks, computed by Offering::weeklyTermEnd()).
Dates are validated by Offering::normalizeDate() (strict Y-m-d, rejects e.g. 2026-02-30); an invalid date leaves both term columns NULL.
Fills the existing term_start/term_end columns — no schema change; REST already round-trips them.
The offerings admin list gains ID and Term columns; the student class card shows "Sep 8, 2026" or "Sep 8, 2026 – Nov 10, 2026 (10 weekly sessions)".
Single-class embed mode
[us_group_classes offering="<id>"] renders only that class with the full enrolment flow, for embedding on a page describing the class; bare usage still shows the catalog.
The Gutenberg block gains an offeringId sidebar option — a dropdown of active group classes fetched from GET /us-scheduler/v1/offerings?kind=group_class (editor script now depends on wp-api-fetch). A stored class that is deleted/deactivated shows as "Unavailable class #N" instead of silently reverting to all classes.
A pinned class that is not open shows "This class is not open for enrolment right now."
Offering editing
Edit per row prefills the shared form via ?usc_edit=<id>; saving posts usc_action=update. The weekly-sessions control is reconstructed from the stored dates.
Add and update share one parser; updates always preserve the original owner and currency, so a forged submission cannot reassign an offering. Non-admin instructors can neither prefill nor update another instructor's offering.
The form gains the previously missing description textarea and an Active — open for registration toggle (admin-UI counterpart of the REST is_active flag), so an edit can't wipe fields the form never collected.
Testing
New OfferingControllerTest (term parsing incl. invalid dates, update ownership/currency preservation, deactivation, foreign-offering rejection, edit prefill) and GroupClassPageTest (offering attribute handling); OfferingTest covers the new date helpers; BlockRegistrarTest updated for the block attribute.
composer test (308 tests), composer lint (PHPStan level 10), composer cs all pass.
Note: no Node/JS tooling in this repo, so blocks.js/group-classes.js changes are review-verified only — worth a click-through in the block editor.
Closes #59
## Term dates for group classes
- The add/edit Offering form takes a **Start date** and a **Sessions** control: one-off (term ends the day it starts) or weekly for N sessions (`term_end = term_start + (N−1) weeks`, computed by `Offering::weeklyTermEnd()`).
- Dates are validated by `Offering::normalizeDate()` (strict `Y-m-d`, rejects e.g. `2026-02-30`); an invalid date leaves both term columns NULL.
- Fills the existing `term_start`/`term_end` columns — no schema change; REST already round-trips them.
- The offerings admin list gains **ID** and **Term** columns; the student class card shows "Sep 8, 2026" or "Sep 8, 2026 – Nov 10, 2026 (10 weekly sessions)".
## Single-class embed mode
- `[us_group_classes offering="<id>"]` renders only that class with the full enrolment flow, for embedding on a page describing the class; bare usage still shows the catalog.
- The Gutenberg block gains an `offeringId` sidebar option — a dropdown of active group classes fetched from `GET /us-scheduler/v1/offerings?kind=group_class` (editor script now depends on `wp-api-fetch`). A stored class that is deleted/deactivated shows as "Unavailable class #N" instead of silently reverting to all classes.
- A pinned class that is not open shows "This class is not open for enrolment right now."
## Offering editing
- **Edit** per row prefills the shared form via `?usc_edit=<id>`; saving posts `usc_action=update`. The weekly-sessions control is reconstructed from the stored dates.
- Add and update share one parser; updates **always preserve the original owner and currency**, so a forged submission cannot reassign an offering. Non-admin instructors can neither prefill nor update another instructor's offering.
- The form gains the previously missing **description** textarea and an **Active — open for registration** toggle (admin-UI counterpart of the REST `is_active` flag), so an edit can't wipe fields the form never collected.
## Testing
- New `OfferingControllerTest` (term parsing incl. invalid dates, update ownership/currency preservation, deactivation, foreign-offering rejection, edit prefill) and `GroupClassPageTest` (offering attribute handling); `OfferingTest` covers the new date helpers; `BlockRegistrarTest` updated for the block attribute.
- `composer test` (308 tests), `composer lint` (PHPStan level 10), `composer cs` all pass.
- Note: no Node/JS tooling in this repo, so `blocks.js`/`group-classes.js` changes are review-verified only — worth a click-through in the block editor.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Group class offerings now carry real dates: the add/edit form takes a
start date plus a sessions control (one-off, or weekly for N sessions;
the end date is computed as start + (N-1) weeks via
Offering::weeklyTermEnd). Dates are validated strictly (Y-m-d) and shown
in the offerings list and on the student-facing class card, including
the weekly session count.
[us_group_classes offering="<id>"] (block attribute offeringId, chosen
from a dropdown of active classes fetched from the public offerings
endpoint) restricts the page to a single class so the enrolment flow can
be embedded on a page dedicated to that class; a pinned class that is no
longer offered reports itself closed instead of falling back to the
catalog.
Offerings are now editable from the admin screen: an Edit button
prefills the shared add/edit form and saving posts usc_action=update.
Updates always preserve the original owner and currency, and non-admin
instructors can only load and update their own offerings. The form also
gains the previously missing description field and an Active toggle (the
admin-UI counterpart of the REST is_active flag) so an edit cannot wipe
data the form never collected.
Closes#59
Co-Authored-By: Claude Fable 5 <[email protected]>
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Closes #59
Term dates for group classes
term_end = term_start + (N−1) weeks, computed byOffering::weeklyTermEnd()).Offering::normalizeDate()(strictY-m-d, rejects e.g.2026-02-30); an invalid date leaves both term columns NULL.term_start/term_endcolumns — no schema change; REST already round-trips them.Single-class embed mode
[us_group_classes offering="<id>"]renders only that class with the full enrolment flow, for embedding on a page describing the class; bare usage still shows the catalog.offeringIdsidebar option — a dropdown of active group classes fetched fromGET /us-scheduler/v1/offerings?kind=group_class(editor script now depends onwp-api-fetch). A stored class that is deleted/deactivated shows as "Unavailable class #N" instead of silently reverting to all classes.Offering editing
?usc_edit=<id>; saving postsusc_action=update. The weekly-sessions control is reconstructed from the stored dates.is_activeflag), so an edit can't wipe fields the form never collected.Testing
OfferingControllerTest(term parsing incl. invalid dates, update ownership/currency preservation, deactivation, foreign-offering rejection, edit prefill) andGroupClassPageTest(offering attribute handling);OfferingTestcovers the new date helpers;BlockRegistrarTestupdated for the block attribute.composer test(308 tests),composer lint(PHPStan level 10),composer csall pass.blocks.js/group-classes.jschanges are review-verified only — worth a click-through in the block editor.🤖 Generated with Claude Code