[us_account], or the Account block: the signed-in visitor's name, their email, a Sign out link, and — only when the account books for someone besides itself — the students it books for. A parent's first question on seeing "signed in as Grace" is whether this is the account their children's lessons are on. Two decisions worth naming. Signed out with no login page chosen, the block renders nothing. Its whole subject is the person signed in, which a stranger is not, and a bare "you are not signed in" in a site header is noise with no way to act on it. With a login page chosen it offers a Sign in link instead. The editor preview is populated regardless, so the block is never an invisible box to the person placing it. Signing out returns to the chosen login page, or to the current page when there is none. A block meant for a header should not also navigate someone somewhere when they use it; the login page wins when configured, because the page they were on may well be members-only. The name comes from UserName::format(), so the block never exposes a username the way display_name can. Also brings docs/features/editor-blocks.md back in step: it still described "four shortcodes" and had never listed the family block. Closes #142 Co-Authored-By: Claude Opus 5 <[email protected]>
149 lines
10 KiB
Markdown
149 lines
10 KiB
Markdown
# Editor Blocks
|
|
|
|
Gutenberg dynamic-block wrappers for the plugin's front-end shortcodes, so the
|
|
pages can be previewed and styled inside the block editor instead of appearing
|
|
as grey shortcode text.
|
|
|
|
## Blocks
|
|
|
|
| Block | Wraps shortcode | Front-end renderer |
|
|
|---|---|---|
|
|
| `us-scheduler/booking` | `[us_booking]` | `Booking\BookingPage::render()` |
|
|
| `us-scheduler/student-login` | `[us_student_login]` | `Auth\LoginPage::render()` |
|
|
| `us-scheduler/student-register` | `[us_student_register]` | `Auth\RegistrationPage::render()` |
|
|
| `us-scheduler/group-classes` | `[us_group_classes]` | `GroupClass\GroupClassPage::render()` |
|
|
| `us-scheduler/family` | `[us_family]` | `Guardian\FamilyPage::render()` |
|
|
| `us-scheduler/account` | `[us_account]` | `Auth\AccountPage::render()` |
|
|
|
|
The shortcodes remain registered for back-compat; blocks and shortcodes share
|
|
the same page objects (constructed once in `Plugin::boot()`), so front-end
|
|
output is identical either way. Pasting a shortcode into the block editor
|
|
auto-converts it to the matching block via a `transforms.from` shortcode
|
|
transform.
|
|
|
|
## Block options
|
|
|
|
Most blocks have sidebar (inspector) options:
|
|
|
|
| Block | Attribute | Default | Effect |
|
|
|---|---|---|---|
|
|
| `us-scheduler/booking` | `loginPageId` (number) | `0` | Page the "log in to book a lesson" link points to for logged-out visitors. `0` = the WordPress login screen (with a redirect back to the current page). |
|
|
| `us-scheduler/booking` | `autoRedirect` (boolean) | `false` | Send logged-out visitors straight to the login page instead of showing the link. |
|
|
| `us-scheduler/booking` | `lessonTypeId` (number) | `0` | Pin the calendar to a single private-lesson type: only the times bookable as that type are listed, and it is the only type students can book here (auto-selected on the registration form). `0` = every type. Shortcode equivalent: `[us_booking lesson_type="…"]`. |
|
|
| `us-scheduler/booking` | `showTypeFilter` (boolean) | `true` | Whether students get the **Show Only** button that narrows the calendar to chosen lesson types. Unused when a single type is pinned (there is nothing to choose). Shortcode equivalent: `[us_booking show_filter="no"]`. |
|
|
| `us-scheduler/booking` | `displayMode` (string) | `both` | Which halves of the page to embed: `both`, `booking` (calendar only, no upcoming-lessons panel) or `upcoming` (the student's lessons only, nothing bookable) — so the two halves can live on different pages. Anything unrecognised falls back to `both`. Shortcode equivalent: `[us_booking show="booking"]`. |
|
|
| `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 students continue to once registration finishes — the "Sign in to your account" link after they confirm their email, and the "Continue to your account" link an invited student gets on the spot. `0` = the WordPress login screen for the confirmation link, and no link at all for the (already signed-in) invited student. Shortcode equivalent: `[us_student_register login_page_id="…"]`. |
|
|
| `us-scheduler/student-register` | `autoRedirect` (boolean) | `false` | Send students straight to that page instead of showing the link. Does nothing until a page is chosen — there is no login-screen fallback here. |
|
|
| `us-scheduler/family` | `loginPageId` (number) | `0` | Where visitors who are not signed in are sent to log in. Shortcode equivalent: `[us_family login_page_id="…"]`. |
|
|
| `us-scheduler/account` | `loginPageId` (number) | `0` | Where signing out returns to, and where a signed-out visitor is offered a **Sign in** link. `0` = signing out returns to the current page, and a signed-out visitor sees **nothing at all** — see below. Shortcode equivalent: `[us_account 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. 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
|
|
select is a dropdown of active group classes fetched from
|
|
`GET /us-scheduler/v1/offerings?kind=group_class`; a stored class that is no
|
|
longer offered shows as "Unavailable class #N" rather than silently falling
|
|
back to all classes. The booking block's lesson-type select works the same way
|
|
against `?kind=private_lesson` ("Unavailable lesson type #N"), and the live
|
|
page says so plainly when the pinned type has been withdrawn.
|
|
|
|
The booking block's options reach the front end as data attributes on
|
|
`#us-booking-app` (`data-lesson-type`, `data-type-filter`) or as omitted
|
|
containers (`displayMode`), which `assets/js/booking.js` reads on load — see
|
|
`lesson-booking.md`. Its editor preview follows `displayMode`, showing the
|
|
calendar, the upcoming-lessons panel, or both. The link targets are also available
|
|
to the shortcodes as `[us_booking login_page_id="…"]` and
|
|
`[us_student_login booking_page_id="…"]`; auto-redirect is block-only.
|
|
|
|
Auto-redirect cannot happen during block rendering (output has already
|
|
started, so a `Location` header cannot be sent). Instead
|
|
`BlockRegistrar::maybeAutoRedirect()` runs on `template_redirect`, parses the
|
|
queried singular post's content for the block (including inside nested
|
|
blocks), and redirects when the block opts in. A block whose target is its
|
|
own page is ignored to avoid a redirect loop.
|
|
|
|
The registration block's auto-redirect additionally only fires on a
|
|
**finished** registration — `RegistrationPage::isRegistrationComplete()`: an
|
|
invited student who is now logged in (`?us_registered=invite`), or a
|
|
self-signup back from the emailed confirmation link (`?us_confirmed=ready|1`).
|
|
The intermediate "check your email" step and every failure (a validation
|
|
error, `?us_confirmed=expired`) stay on the page so the student reads the
|
|
message. That check runs before the content is parsed, so an ordinary page
|
|
view does not pay for the extra block scan.
|
|
|
|
## How it works
|
|
|
|
- **`BlockRegistrar`** (`src/BlockRegistrar.php`) hooks `init` and registers
|
|
each block with `register_block_type()`: a `render_callback` per block, the
|
|
shared editor script (`assets/js/blocks.js`, handle
|
|
`us-scheduler-blocks`), and the front-end stylesheet
|
|
(`assets/css/frontend.css`, handle `us-scheduler`) as the block `style` so
|
|
it also loads inside the editor and previews pick up theme styling.
|
|
- **`assets/js/blocks.js`** (vanilla JS, no build step) registers the client
|
|
side of each block — title, icon, keywords, shortcode transform — and
|
|
renders the editor preview with `wp.serverSideRender`, which fetches the
|
|
server-rendered markup via the `/wp/v2/block-renderer` REST route.
|
|
- **`BlockPreview`** (`src/BlockPreview.php`) supplies static, script-free
|
|
markup for editor previews. `BlockRegistrar::isEditorPreview()` detects the
|
|
block-renderer context via the `REST_REQUEST` constant (front-end template
|
|
rendering never happens inside a REST request) and renders the preview
|
|
instead of the live page.
|
|
|
|
## Editor preview behaviour
|
|
|
|
Live pages cannot run in the editor: booking and group classes are populated
|
|
by JavaScript making authenticated REST calls (and may load Stripe.js),
|
|
registration requires a valid invite token, and login short-circuits for
|
|
logged-in users (the editing admin always is). Each preview therefore
|
|
reproduces the live wrapper elements and CSS classes with representative
|
|
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. 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.
|
|
- **Account** — a populated sample panel. Deliberately populated whatever the
|
|
editor user's own state: on the published page a signed-out visitor may see
|
|
nothing at all, and an empty box tells the person placing the block nothing
|
|
about where it will sit.
|
|
|
|
Each preview starts with a `.us-editor-note` paragraph explaining what the
|
|
published page shows instead. The note class only appears in editor previews.
|
|
|
|
## Tests
|
|
|
|
- `tests/Unit/BlockRegistrarTest.php` — hook registration, block/asset
|
|
registration, attribute schemas, front-end delegation to the page objects,
|
|
preview-mode routing, auto-redirect behaviour.
|
|
- `tests/Unit/Booking/BookingPageTest.php` — logged-out login-link targets
|
|
and fallbacks.
|
|
- `tests/Unit/Auth/LoginPageTest.php` — logged-in booking-link targets and
|
|
fallbacks.
|
|
- `tests/Unit/Auth/AccountPageTest.php` — what each visitor sees, the
|
|
sign-out redirect target, and the signed-out empty render.
|
|
- `tests/Unit/BlockPreviewTest.php` — preview markup mirrors the live CSS
|
|
classes/ids and includes the editor note.
|
|
|
|
## The account block's signed-out behaviour
|
|
|
|
`us-scheduler/account` is the one block that can render **nothing**. It is meant
|
|
for a header, sidebar or account page, and its whole subject is the person
|
|
signed in — which a stranger is not. A bare "you are not signed in" in a site
|
|
header is noise that cannot be acted on, so:
|
|
|
|
- **No login page chosen** → empty string for signed-out visitors.
|
|
- **Login page chosen** → a single **Sign in** link.
|
|
|
|
Signed in, it shows the display name (`Auth\UserName::format()`, so a username
|
|
is never exposed), the account email, a **Sign out** link, and — only on an
|
|
account that books for someone other than itself — the students it books for.
|
|
Signing out returns to the chosen login page, or to the current page when there
|
|
is none, so a header sign-out does not also navigate the visitor somewhere.
|