Compare commits
1
Commits
v1.2.2
...
b0952ca06d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b0952ca06d
|
@@ -13,6 +13,9 @@ each change under the current top section as you work.
|
|||||||
|
|
||||||
## [1.2.2]
|
## [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]
|
## [1.2.1]
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|||||||
+1
-1
@@ -192,7 +192,7 @@
|
|||||||
{ title: __('Classes shown', 'unsupervised-schedular') },
|
{ title: __('Classes shown', 'unsupervised-schedular') },
|
||||||
el(GroupClassSelect, {
|
el(GroupClassSelect, {
|
||||||
label: __('Class', 'unsupervised-schedular'),
|
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,
|
value: attributes.offeringId,
|
||||||
onChange: (offeringId) => setAttributes({ offeringId }),
|
onChange: (offeringId) => setAttributes({ offeringId }),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -12,6 +12,9 @@
|
|||||||
|
|
||||||
// When the shortcode/block pins a single offering, only that class is
|
// When the shortcode/block pins a single offering, only that class is
|
||||||
// shown, so the page can be embedded alongside a full class description.
|
// 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);
|
const singleOfferingId = Number(app.dataset.offering || 0);
|
||||||
|
|
||||||
function apiFetch(path, options = {}) {
|
function apiFetch(path, options = {}) {
|
||||||
@@ -148,7 +151,7 @@
|
|||||||
${whenLabel(o) ? `<p class="us-class-when">${escHtml(whenLabel(o))}</p>` : ''}
|
${whenLabel(o) ? `<p class="us-class-when">${escHtml(whenLabel(o))}</p>` : ''}
|
||||||
${o.instructor_name ? `<p class="us-class-instructor">With ${escHtml(o.instructor_name)}</p>` : ''}
|
${o.instructor_name ? `<p class="us-class-instructor">With ${escHtml(o.instructor_name)}</p>` : ''}
|
||||||
${o.schedule_note ? `<p>${escHtml(o.schedule_note)}</p>` : ''}
|
${o.schedule_note ? `<p>${escHtml(o.schedule_note)}</p>` : ''}
|
||||||
${o.description ? `<p>${escHtml(o.description)}</p>` : ''}
|
${!singleOfferingId && o.description ? `<p>${escHtml(o.description)}</p>` : ''}
|
||||||
<p>${escHtml(Number(o.price).toFixed(2))} ${escHtml(o.currency)}</p>
|
<p>${escHtml(Number(o.price).toFixed(2))} ${escHtml(o.currency)}</p>
|
||||||
${!enrolledMap.has(Number(o.id)) && isEnrollmentOpen(o) && enrolmentDeadline(o)
|
${!enrolledMap.has(Number(o.id)) && isEnrollmentOpen(o) && enrolmentDeadline(o)
|
||||||
? `<p class="us-enrol-deadline">Enrol by ${escHtml(formatDate(enrolmentDeadline(o)))}</p>`
|
? `<p class="us-enrol-deadline">Enrol by ${escHtml(formatDate(enrolmentDeadline(o)))}</p>`
|
||||||
|
|||||||
@@ -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` | `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-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/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 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
|
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
|
- **Booking** — `#us-booking-app` with sample `.us-day` / `.us-slot` rows and
|
||||||
disabled Book buttons.
|
disabled Book buttons.
|
||||||
- **Group classes** — `#us-group-app` with a sample `.us-class` card and a
|
- **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
|
- **Login** — the real `templates/frontend/login-page.php` template (it has
|
||||||
no request-state dependencies).
|
no request-state dependencies).
|
||||||
- **Registration** — a disabled sample of the `.us-register-form` fields.
|
- **Registration** — a disabled sample of the `.us-register-form` fields.
|
||||||
|
|||||||
@@ -176,7 +176,7 @@ class becomes enrollable for them — they choose whether to enrol.
|
|||||||
- Model: `Unsupervised\Schedular\GroupClass\Enrollment`
|
- 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`)
|
- 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`
|
- 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`)
|
- Reuses `Registration\RegistrationGate` (intake answers + booking-scoped policy acceptance, type `enrollment`)
|
||||||
|
|
||||||
> **Payment:** a priced enrolment creates a payment via `Payment\PaymentService`
|
> **Payment:** a priced enrolment creates a payment via `Payment\PaymentService`
|
||||||
|
|||||||
+20
-4
@@ -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
|
||||||
|
? ''
|
||||||
|
: '<p>' . esc_html__( 'A sample class shown so the page can be styled.', 'unsupervised-schedular' ) . '</p>';
|
||||||
|
|
||||||
return sprintf(
|
return sprintf(
|
||||||
'<div id="us-group-app">%s<div id="us-group-list"><div class="us-class"><h3>%s</h3><p>%s</p><p>%s</p><p>25.00 CAD</p><button type="button" class="us-enrol-btn" disabled>%s</button></div></div></div>',
|
'<div id="us-group-app">%s<div id="us-group-list"><div class="us-class"><h3>%s</h3><p class="us-class-when">%s</p>%s<p>25.00 CAD</p><p class="us-enrol-deadline">%s</p><button type="button" class="us-enrol-btn" disabled>%s</button></div></div></div>',
|
||||||
self::note( __( 'Editor preview — students see live group classes on the published page.', 'unsupervised-schedular' ) ),
|
self::note( $note ),
|
||||||
esc_html__( 'Beginner Group Class', 'unsupervised-schedular' ),
|
esc_html__( 'Beginner Group Class', 'unsupervised-schedular' ),
|
||||||
esc_html__( 'Saturdays 10:00 AM–11:00 AM', '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' )
|
esc_html__( 'Enrol', 'unsupervised-schedular' )
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,7 +160,11 @@ class BlockRegistrar {
|
|||||||
* @param array<string, mixed> $attributes Block attributes.
|
* @param array<string, mixed> $attributes Block attributes.
|
||||||
*/
|
*/
|
||||||
public function renderGroupClasses( array $attributes = [] ): string {
|
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 );
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -29,6 +29,17 @@ class BlockPreviewTest extends TestCase
|
|||||||
self::assertStringContainsString('class="us-class"', $html);
|
self::assertStringContainsString('class="us-class"', $html);
|
||||||
self::assertStringContainsString('class="us-enrol-btn" disabled', $html);
|
self::assertStringContainsString('class="us-enrol-btn" disabled', $html);
|
||||||
self::assertStringContainsString('us-editor-note', $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
|
public function testLoginPreviewIncludesTheRealLoginTemplate(): void
|
||||||
|
|||||||
@@ -179,6 +179,19 @@ class BlockRegistrarTest extends TestCase
|
|||||||
self::assertStringContainsString('us-group-app', $this->registrar->renderGroupClasses());
|
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
|
public function testIsEditorPreviewIsFalseOutsideRestRequests(): void
|
||||||
{
|
{
|
||||||
// REST_REQUEST is undefined in the test process, so the real
|
// REST_REQUEST is undefined in the test process, so the real
|
||||||
|
|||||||
Reference in New Issue
Block a user