CI / Tests (PHP 8.1) (pull_request) Successful in 45s
CI / No Debug Code (pull_request) Successful in 2s
CI / Tests (PHP 8.2) (pull_request) Successful in 55s
CI / PHPStan (pull_request) Successful in 2m57s
CI / Coding Standards (pull_request) Successful in 3m3s
CI / Tests (PHP 8.3) (pull_request) Successful in 2m42s
CI / Build Plugin Zip (pull_request) Skipped
The Profile block is headed "Your profile", but the one person on it you could not change was yourself: your name, your birth year, and whether you take lessons yourself were fixed at whatever signup recorded, and correcting any of them meant asking a studio admin. A "Your details" section now opens the page, saved through the same nonce-checked template_redirect post/redirect/get path the child rows use: - Your name, written to display_name and nickname together, for the reason updateChild() does — UserName reads the nickname first, and leaving it behind would put the account's email address back on every screen that names a person. - "I take lessons myself", the positive of us_guardian_only. This makes good on the claim already in bookableStudents() and the feature doc that a guardian-only account can put itself right from the profile page. - Your birth year, held to the same normaliseBirthYear() rule as every other student. The email is shown but not editable: it is the account's user_login as well as its address, so changing it stays a studio-side job. The birth-year field deliberately carries no `required` attribute. It is asked of a student only, and this page loads no JavaScript, so a browser-enforced `required` would leave a guardian who books solely for other people unable to submit the form at all; handleSelf() enforces it against the checkbox instead. Unticking the box does not clear a stored birth year — it says who books, not "forget what is on file". Closes #165 Co-Authored-By: Claude Opus 5 <[email protected]>
254 lines
10 KiB
PHP
254 lines
10 KiB
PHP
<?php
|
||
declare(strict_types=1);
|
||
|
||
namespace Unsupervised\Schedular;
|
||
|
||
/**
|
||
* Static, script-free markup for the editor previews of the front-end blocks.
|
||
*
|
||
* The booking and group-class pages are populated by JavaScript on the live
|
||
* site, and the registration page requires a valid invite token — none of
|
||
* which exist inside the block editor. These previews reproduce the same
|
||
* wrapper elements and CSS classes the live pages use, filled with
|
||
* representative placeholder content, so themes can be styled against
|
||
* realistic markup without firing REST calls, redirects, or Stripe.js.
|
||
*/
|
||
class BlockPreview {
|
||
|
||
/**
|
||
* The marker a required field's label carries, matching the one
|
||
* {@see Registration\QuestionField::render()} puts on a required question.
|
||
*/
|
||
private const REQUIRED_MARK = ' <span class="us-required" aria-hidden="true">*</span>';
|
||
|
||
/**
|
||
* Sample booking page.
|
||
*
|
||
* @param string $mode Which halves the block embeds — one of
|
||
* {@see Booking\BookingPage::MODE_BOTH},
|
||
* `MODE_BOOKING` or `MODE_UPCOMING`. The preview shows
|
||
* the same sections the published page would.
|
||
*/
|
||
public static function booking( string $mode = Booking\BookingPage::MODE_BOTH ): string {
|
||
if ( Booking\BookingPage::MODE_UPCOMING === $mode ) {
|
||
return sprintf(
|
||
'<div id="us-booking-app">%s<div id="us-my-lessons">%s</div></div>',
|
||
self::note( __( 'Editor preview — students see their own lessons on the published page.', 'unsupervised-schedular' ) ),
|
||
self::upcomingLessons()
|
||
);
|
||
}
|
||
|
||
$days = [
|
||
[
|
||
'label' => __( 'Monday', 'unsupervised-schedular' ),
|
||
'slots' => [
|
||
[ '4:00 PM–4:30 PM', 30 ],
|
||
[ '4:30 PM–5:00 PM', 30 ],
|
||
],
|
||
],
|
||
[
|
||
'label' => __( 'Wednesday', 'unsupervised-schedular' ),
|
||
'slots' => [
|
||
[ '5:00 PM–5:45 PM', 45 ],
|
||
],
|
||
],
|
||
];
|
||
|
||
$dayHtml = '';
|
||
foreach ( $days as $day ) {
|
||
$slotHtml = '';
|
||
foreach ( $day['slots'] as $slot ) {
|
||
$slotHtml .= sprintf(
|
||
'<div class="us-slot"><span>%s (%d min)</span><button type="button" class="us-book-btn" disabled>%s</button></div>',
|
||
esc_html( $slot[0] ),
|
||
(int) $slot[1],
|
||
esc_html__( 'Book', 'unsupervised-schedular' )
|
||
);
|
||
}
|
||
|
||
$dayHtml .= sprintf(
|
||
'<div class="us-day"><h3 class="us-day-heading">%s</h3>%s</div>',
|
||
esc_html( $day['label'] ),
|
||
$slotHtml
|
||
);
|
||
}
|
||
|
||
$lessons = Booking\BookingPage::MODE_BOOKING === $mode
|
||
? ''
|
||
: sprintf( '<div id="us-my-lessons">%s</div>', self::upcomingLessons() );
|
||
|
||
return sprintf(
|
||
'<div id="us-booking-app">%s%s<div id="us-slot-list">%s</div></div>',
|
||
self::note( __( 'Editor preview — students see live availability on the published page.', 'unsupervised-schedular' ) ),
|
||
$lessons,
|
||
$dayHtml
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Sample "your upcoming lessons" panel, shared by the booking preview's
|
||
* full and upcoming-only modes.
|
||
*/
|
||
private static function upcomingLessons(): string {
|
||
return sprintf(
|
||
'<div class="us-my-lessons"><h3>%s</h3>'
|
||
. '<div class="us-my-lesson"><div class="us-my-lesson-info">'
|
||
. '<strong class="us-my-lesson-title">%s <span class="us-my-lesson-duration">(30 min)</span></strong>'
|
||
. '<span class="us-my-lesson-when">%s</span></div>'
|
||
. '<div class="us-my-lesson-actions">'
|
||
. '<span class="us-lesson-status us-lesson-status-confirmed">%s</span>'
|
||
. '<button type="button" class="us-cancel-lesson" disabled>%s</button>'
|
||
. '</div></div></div>',
|
||
esc_html__( 'Your upcoming lessons', 'unsupervised-schedular' ),
|
||
esc_html__( 'Piano Lesson', 'unsupervised-schedular' ),
|
||
esc_html__( 'Monday · 4:00 PM–4:30 PM', 'unsupervised-schedular' ),
|
||
esc_html__( 'Confirmed', 'unsupervised-schedular' ),
|
||
esc_html__( 'Cancel', 'unsupervised-schedular' )
|
||
);
|
||
}
|
||
|
||
/**
|
||
* 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(
|
||
'<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 class="us-class-price">%s</p><p class="us-enrol-deadline">%s</p><button type="button" class="us-enrol-btn" disabled>%s</button></div></div></div>',
|
||
self::note( $note ),
|
||
esc_html__( 'Beginner Group Class', 'unsupervised-schedular' ),
|
||
esc_html__( 'Saturdays 10:00 AM–11:00 AM', 'unsupervised-schedular' ),
|
||
$description,
|
||
// Prices on the live page always carry their cadence, so the sample does too.
|
||
esc_html__( '25.00 CAD up front', 'unsupervised-schedular' ),
|
||
esc_html__( 'Enrol by Sep 6, 2026', 'unsupervised-schedular' ),
|
||
esc_html__( 'Enrol', 'unsupervised-schedular' )
|
||
);
|
||
}
|
||
|
||
/**
|
||
* The live login form renders fine without any request state, so the
|
||
* preview includes the real template (the editing user is logged in, which
|
||
* would otherwise short-circuit to an "already logged in" message).
|
||
*/
|
||
public static function login(): string {
|
||
$error = '';
|
||
|
||
ob_start();
|
||
include USC_PLUGIN_DIR . 'templates/frontend/login-page.php';
|
||
|
||
return self::note( __( 'Editor preview — logged-in visitors are offered a link to the booking page instead.', 'unsupervised-schedular' ) ) . (string) ob_get_clean();
|
||
}
|
||
|
||
public static function registration(): string {
|
||
$fields = sprintf(
|
||
'<p><label for="us-reg-email">%s</label><input type="email" id="us-reg-email" value="[email protected]" readonly></p>',
|
||
esc_html__( 'Email', 'unsupervised-schedular' )
|
||
);
|
||
$fields .= sprintf(
|
||
'<p><label for="us-reg-name">%s</label><input type="text" id="us-reg-name"></p>',
|
||
esc_html__( 'Your name', 'unsupervised-schedular' )
|
||
);
|
||
$fields .= sprintf(
|
||
'<p><label for="us-reg-pass">%s</label><input type="password" id="us-reg-pass"></p>',
|
||
esc_html__( 'Password', 'unsupervised-schedular' )
|
||
);
|
||
$fields .= sprintf(
|
||
'<p><input type="submit" value="%s" disabled></p>',
|
||
esc_attr__( 'Create Account', 'unsupervised-schedular' )
|
||
);
|
||
|
||
return sprintf(
|
||
'<div class="us-register-form">%s<form>%s</form></div>',
|
||
self::note( __( 'Editor preview — the live form requires a valid invite link and lists signup policies.', 'unsupervised-schedular' ) ),
|
||
$fields
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Sample family page: the account holder's own details, two representative
|
||
* children and the add form, with the controls inert so the editor preview
|
||
* cannot post.
|
||
*/
|
||
public static function family(): string {
|
||
$self = sprintf(
|
||
'<h4>%s</h4><p class="us-family-self-email">%s <span>[email protected]</span></p>'
|
||
. '<p><label for="us-own-name">%s' . self::REQUIRED_MARK . '</label><input type="text" id="us-own-name" value="%s"></p>'
|
||
. '<p><label><input type="checkbox" checked disabled> %s</label></p>'
|
||
. '<p><label for="us-own-birth-year">%s</label><input type="number" id="us-own-birth-year" placeholder="YYYY"></p>'
|
||
. '<p><button type="button" disabled>%s</button></p>',
|
||
esc_html__( 'Your details', 'unsupervised-schedular' ),
|
||
esc_html__( 'Email', 'unsupervised-schedular' ),
|
||
esc_html__( 'Your name', 'unsupervised-schedular' ),
|
||
esc_attr__( 'Grace Hopper', 'unsupervised-schedular' ),
|
||
esc_html__( 'I take lessons myself', 'unsupervised-schedular' ),
|
||
esc_html__( 'Your birth year', 'unsupervised-schedular' ),
|
||
esc_html__( 'Save my details', 'unsupervised-schedular' )
|
||
);
|
||
|
||
$children = '';
|
||
foreach ( [ 'Ada Lovelace', 'Alan Turing' ] as $name ) {
|
||
$children .= sprintf(
|
||
'<li class="us-family-child"><span class="us-family-child-name">%s</span>'
|
||
. '<span class="us-family-child-actions"><a href="#">%s</a> <button type="button" disabled>%s</button></span></li>',
|
||
esc_html( $name ),
|
||
esc_html__( 'Edit', 'unsupervised-schedular' ),
|
||
esc_html__( 'Remove', 'unsupervised-schedular' )
|
||
);
|
||
}
|
||
|
||
$add = sprintf(
|
||
'<h4>%s</h4><p><label for="us-child-name">%s' . self::REQUIRED_MARK . '</label><input type="text" id="us-child-name"></p>'
|
||
. '<p><label for="us-child-birth-year">%s' . self::REQUIRED_MARK . '</label><input type="number" id="us-child-birth-year" placeholder="YYYY"></p>'
|
||
. '<p><button type="button" disabled>%s</button></p>',
|
||
esc_html__( 'Add a student', 'unsupervised-schedular' ),
|
||
esc_html__( 'Name', 'unsupervised-schedular' ),
|
||
esc_html__( 'Birth year', 'unsupervised-schedular' ),
|
||
esc_html__( 'Add student', 'unsupervised-schedular' )
|
||
);
|
||
|
||
return sprintf(
|
||
'<div class="us-family">%s<h3>%s</h3><form class="us-family-self">%s</form>'
|
||
. '<h4>%s</h4><ul class="us-family-list">%s</ul><form class="us-family-add">%s</form></div>',
|
||
self::note( __( 'Editor preview — signed-in visitors see and manage their own details and students here.', 'unsupervised-schedular' ) ),
|
||
esc_html__( 'Your profile', 'unsupervised-schedular' ),
|
||
$self,
|
||
esc_html__( 'Your students', 'unsupervised-schedular' ),
|
||
$children,
|
||
$add
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Sample account panel. Shown populated whatever the editor's own login
|
||
* state, since on the published page a signed-out visitor may see nothing at
|
||
* all and an empty box tells the person placing the block nothing.
|
||
*/
|
||
public static function account(): string {
|
||
return sprintf(
|
||
'<div class="us-account">%s'
|
||
. '<p class="us-account-who"><span class="us-account-name">%s</span>'
|
||
. '<span class="us-account-email">%s</span></p>'
|
||
. '<p class="us-account-actions"><a class="us-account-signout" href="#">%s</a></p></div>',
|
||
self::note( __( 'Editor preview — each visitor sees their own account here.', 'unsupervised-schedular' ) ),
|
||
esc_html__( 'Grace Hopper', 'unsupervised-schedular' ),
|
||
esc_html__( '[email protected]', 'unsupervised-schedular' ),
|
||
esc_html__( 'Sign out', 'unsupervised-schedular' )
|
||
);
|
||
}
|
||
|
||
private static function note( string $text ): string {
|
||
return '<p class="us-editor-note">' . esc_html( $text ) . '</p>';
|
||
}
|
||
}
|