From b0952ca06dd98a238ab41f4dd2982803755d05f1 Mon Sep 17 00:00:00 2001 From: James Griffin Date: Tue, 28 Jul 2026 10:37:36 -0300 Subject: [PATCH] Omit the class description when the group block shows one class The Group Classes block can be pinned to a single class via its Class option so it can be embedded on a page dedicated to that class. On such a page the surrounding copy already describes the class, so the card repeated it. In single-class mode the description is now left out and the card shows only the schedule, instructor, schedule note, price, enrolment deadline and the enrol/withdraw controls. The editor preview follows the same rule: BlockPreview::groupClasses() takes the mode from the block's offeringId attribute, drops the sample description when a class is pinned, and notes what the published page shows. Its sample card also gained the .us-class-when and .us-enrol-deadline elements the live markup has always rendered. Co-Authored-By: Claude Opus 5 --- CHANGELOG.md | 3 +++ assets/js/blocks.js | 2 +- assets/js/group-classes.js | 5 ++++- docs/features/editor-blocks.md | 6 ++++-- docs/features/group-classes.md | 2 +- src/BlockPreview.php | 24 ++++++++++++++++++++---- src/BlockRegistrar.php | 6 +++++- tests/Unit/BlockPreviewTest.php | 11 +++++++++++ tests/Unit/BlockRegistrarTest.php | 13 +++++++++++++ 9 files changed, 62 insertions(+), 10 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 384d298..9935427 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ each change under the current top section as you work. ## [1.2.2] +### Added +- The **Group Classes** block can now be pinned to a single class, under **Classes shown → Class** in the block sidebar (shortcode: `[us_group_classes offering="…"]`). Pick a class and the block shows only that one, so it can be embedded on a page that describes the class. In this mode the class's own description is left out to avoid repeating the page copy — the card shows the schedule, instructor, price, enrolment deadline and the enrol/withdraw controls. Leaving it on **All classes** keeps the full browsable catalog with descriptions. + ## [1.2.1] ### Fixed diff --git a/assets/js/blocks.js b/assets/js/blocks.js index d7d0f60..96cdd1f 100644 --- a/assets/js/blocks.js +++ b/assets/js/blocks.js @@ -192,7 +192,7 @@ { title: __('Classes shown', 'unsupervised-schedular') }, el(GroupClassSelect, { label: __('Class', 'unsupervised-schedular'), - help: __('Show only one group class, for embedding on a page dedicated to it.', 'unsupervised-schedular'), + help: __('Show only one group class, for embedding on a page dedicated to it. That class’s description is left out — the card shows just the schedule, price and enrolment controls.', 'unsupervised-schedular'), value: attributes.offeringId, onChange: (offeringId) => setAttributes({ offeringId }), }) diff --git a/assets/js/group-classes.js b/assets/js/group-classes.js index 5b9f82d..e770c97 100644 --- a/assets/js/group-classes.js +++ b/assets/js/group-classes.js @@ -12,6 +12,9 @@ // When the shortcode/block pins a single offering, only that class is // shown, so the page can be embedded alongside a full class description. + // The class's own description is then omitted from the card — the page it + // sits on already describes the class — leaving the schedule, price and + // enrolment controls. const singleOfferingId = Number(app.dataset.offering || 0); function apiFetch(path, options = {}) { @@ -148,7 +151,7 @@ ${whenLabel(o) ? `

${escHtml(whenLabel(o))}

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

With ${escHtml(o.instructor_name)}

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

${escHtml(o.schedule_note)}

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

${escHtml(o.description)}

` : ''} + ${!singleOfferingId && o.description ? `

${escHtml(o.description)}

` : ''}

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

${!enrolledMap.has(Number(o.id)) && isEnrollmentOpen(o) && enrolmentDeadline(o) ? `

Enrol by ${escHtml(formatDate(enrolmentDeadline(o)))}

` diff --git a/docs/features/editor-blocks.md b/docs/features/editor-blocks.md index 3d5af19..67e520e 100644 --- a/docs/features/editor-blocks.md +++ b/docs/features/editor-blocks.md @@ -30,7 +30,7 @@ Four blocks have sidebar (inspector) options: | `us-scheduler/student-login` | `bookingPageId` (number) | `0` | Page the "View available lessons" link points to for logged-in visitors, and the post-login redirect target. `0` = the current page. | | `us-scheduler/student-login` | `autoRedirect` (boolean) | `false` | Send logged-in visitors straight to the booking page instead of showing the link. Does nothing until a booking page is chosen. | | `us-scheduler/student-register` | `loginPageId` (number) | `0` | Page the "Sign in to your account" link points to after a student confirms their email. `0` = the WordPress login screen. Shortcode equivalent: `[us_student_register login_page_id="…"]`. | -| `us-scheduler/group-classes` | `offeringId` (number) | `0` | Restrict the page to a single group class, for embedding on a page dedicated to that class. `0` = browse all classes. Shortcode equivalent: `[us_group_classes offering="…"]`. | +| `us-scheduler/group-classes` | `offeringId` (number) | `0` | Restrict the page to a single group class, for embedding on a page dedicated to that class. The class description is then omitted — only the schedule, instructor, price and enrolment controls are shown, so the surrounding page's own copy is not repeated. `0` = browse all classes, descriptions included. Shortcode equivalent: `[us_group_classes offering="…"]`. | The page selects list all published pages; if a chosen page is later deleted, the blocks fall back to their defaults. The group-classes block's class @@ -78,7 +78,9 @@ placeholder content: - **Booking** — `#us-booking-app` with sample `.us-day` / `.us-slot` rows and disabled Book buttons. - **Group classes** — `#us-group-app` with a sample `.us-class` card and a - disabled Enrol button. + disabled Enrol button. When `offeringId` pins a single class the preview + drops the sample description, matching what the live page renders in that + mode. - **Login** — the real `templates/frontend/login-page.php` template (it has no request-state dependencies). - **Registration** — a disabled sample of the `.us-register-form` fields. diff --git a/docs/features/group-classes.md b/docs/features/group-classes.md index fb31d29..8f81309 100644 --- a/docs/features/group-classes.md +++ b/docs/features/group-classes.md @@ -176,7 +176,7 @@ class becomes enrollable for them — they choose whether to enrol. - Model: `Unsupervised\Schedular\GroupClass\Enrollment` - Admin controller: `Unsupervised\Schedular\GroupClass\GroupClassController` — `renderPage` (studio admin per-class summary, `view_all_lessons`) and `renderInstructorPage` (instructor summary + `?class_id` roster detail, `view_own_lessons`) - REST endpoint: `Unsupervised\Schedular\GroupClass\EnrollmentEndpoint` -- Frontend: `Unsupervised\Schedular\GroupClass\GroupClassPage` (`[us_group_classes]` shortcode; `offering="…"` restricts it to a single class for embedding on a dedicated page — the block equivalent is the `offeringId` attribute) +- Frontend: `Unsupervised\Schedular\GroupClass\GroupClassPage` (`[us_group_classes]` shortcode; `offering="…"` restricts it to a single class for embedding on a dedicated page — the block equivalent is the `offeringId` attribute). In single-class mode `assets/js/group-classes.js` leaves the class description out of the card, since the page it is embedded on already describes the class; the schedule, instructor, schedule note, price and enrolment controls are still shown. - Reuses `Registration\RegistrationGate` (intake answers + booking-scoped policy acceptance, type `enrollment`) > **Payment:** a priced enrolment creates a payment via `Payment\PaymentService` diff --git a/src/BlockPreview.php b/src/BlockPreview.php index cc8173a..88b4bd0 100644 --- a/src/BlockPreview.php +++ b/src/BlockPreview.php @@ -58,13 +58,29 @@ class BlockPreview { ); } - public static function groupClasses(): string { + /** + * Sample group-class card. + * + * @param bool $singleClass Whether the block is pinned to one class, in + * which case the live page omits the class + * description and the preview does too. + */ + public static function groupClasses( bool $singleClass = false ): string { + $note = $singleClass + ? __( 'Editor preview — the published page shows the chosen class with its live schedule and enrolment status.', 'unsupervised-schedular' ) + : __( 'Editor preview — students see live group classes on the published page.', 'unsupervised-schedular' ); + + $description = $singleClass + ? '' + : '

' . esc_html__( 'A sample class shown so the page can be styled.', 'unsupervised-schedular' ) . '

'; + return sprintf( - '
%s

%s

%s

%s

25.00 CAD

', - self::note( __( 'Editor preview — students see live group classes on the published page.', 'unsupervised-schedular' ) ), + '
%s

%s

%s

%s

25.00 CAD

%s

', + self::note( $note ), esc_html__( 'Beginner Group Class', 'unsupervised-schedular' ), esc_html__( 'Saturdays 10:00 AM–11:00 AM', 'unsupervised-schedular' ), - esc_html__( 'A sample class shown so the page can be styled.', 'unsupervised-schedular' ), + $description, + esc_html__( 'Enrol by Sep 6, 2026', 'unsupervised-schedular' ), esc_html__( 'Enrol', 'unsupervised-schedular' ) ); } diff --git a/src/BlockRegistrar.php b/src/BlockRegistrar.php index 4da5240..dc364b2 100644 --- a/src/BlockRegistrar.php +++ b/src/BlockRegistrar.php @@ -160,7 +160,11 @@ class BlockRegistrar { * @param array $attributes Block attributes. */ public function renderGroupClasses( array $attributes = [] ): string { - return $this->isEditorPreview() ? BlockPreview::groupClasses() : $this->groupClassPage->render( $attributes ); + if ( ! $this->isEditorPreview() ) { + return $this->groupClassPage->render( $attributes ); + } + + return BlockPreview::groupClasses( Val::int( $attributes['offeringId'] ?? 0 ) > 0 ); } /** diff --git a/tests/Unit/BlockPreviewTest.php b/tests/Unit/BlockPreviewTest.php index 165d17f..b0e597b 100644 --- a/tests/Unit/BlockPreviewTest.php +++ b/tests/Unit/BlockPreviewTest.php @@ -29,6 +29,17 @@ class BlockPreviewTest extends TestCase self::assertStringContainsString('class="us-class"', $html); self::assertStringContainsString('class="us-enrol-btn" disabled', $html); self::assertStringContainsString('us-editor-note', $html); + self::assertStringContainsString('A sample class shown so the page can be styled.', $html); + } + + public function testSingleClassGroupPreviewDropsTheDescriptionButKeepsScheduleAndEnrolment(): void + { + $html = BlockPreview::groupClasses(true); + + self::assertStringNotContainsString('A sample class shown so the page can be styled.', $html); + self::assertStringContainsString('class="us-class-when"', $html); + self::assertStringContainsString('class="us-enrol-deadline"', $html); + self::assertStringContainsString('class="us-enrol-btn" disabled', $html); } public function testLoginPreviewIncludesTheRealLoginTemplate(): void diff --git a/tests/Unit/BlockRegistrarTest.php b/tests/Unit/BlockRegistrarTest.php index 2c402ab..2013110 100644 --- a/tests/Unit/BlockRegistrarTest.php +++ b/tests/Unit/BlockRegistrarTest.php @@ -179,6 +179,19 @@ class BlockRegistrarTest extends TestCase self::assertStringContainsString('us-group-app', $this->registrar->renderGroupClasses()); } + public function testEditorPreviewOfAPinnedGroupClassOmitsTheDescription(): void + { + $this->registrar->preview = true; + $this->groupClassPage->shouldNotReceive('render'); + + $all = $this->registrar->renderGroupClasses(); + $single = $this->registrar->renderGroupClasses(['offeringId' => 12]); + + self::assertStringContainsString('A sample class shown so the page can be styled.', $all); + self::assertStringNotContainsString('A sample class shown so the page can be styled.', $single); + self::assertStringContainsString('us-enrol-deadline', $single); + } + public function testIsEditorPreviewIsFalseOutsideRestRequests(): void { // REST_REQUEST is undefined in the test process, so the real