Add Gutenberg dynamic-block wrappers for the front-end shortcodes
CI / No Debug Code (pull_request) Successful in 4s
CI / Tests (PHP 8.2) (pull_request) Successful in 52s
CI / Tests (PHP 8.1) (pull_request) Successful in 54s
CI / Tests (PHP 8.3) (pull_request) Successful in 1m29s
CI / Coding Standards (pull_request) Successful in 1m57s
CI / PHPStan (pull_request) Successful in 2m14s
CI / Build Plugin Zip (pull_request) Has been skipped

Wrap the four shortcodes (us_booking, us_student_login,
us_student_register, us_group_classes) in dynamic blocks so pages can be
previewed and styled in the block editor. Front-end rendering delegates
to the same page objects the shortcodes use; in the editor's
block-renderer REST preview a static, script-free BlockPreview is
rendered instead (no live REST calls, redirects, or Stripe.js). The
editor script (vanilla JS, no build step) registers each block with
wp.serverSideRender previews and shortcode transforms; frontend.css is
attached as the block style so previews pick up theme styling.

Resolves #44

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
2026-06-12 12:03:27 -03:00
co-authored by Claude Fable 5
parent 63e2fbcc5b
commit fc70cde9d5
10 changed files with 624 additions and 20 deletions
+126
View File
@@ -0,0 +1,126 @@
<?php
declare(strict_types=1);
namespace Unsupervised\Schedular;
use Unsupervised\Schedular\Auth\LoginPage;
use Unsupervised\Schedular\Auth\RegistrationPage;
use Unsupervised\Schedular\Booking\BookingPage;
use Unsupervised\Schedular\GroupClass\GroupClassPage;
/**
* Registers Gutenberg dynamic-block wrappers for the front-end shortcodes so
* the pages can be previewed and styled inside the block editor.
*
* On the front end each block delegates to the same page object its shortcode
* uses, so output is identical either way. Inside the editor (the
* block-renderer REST preview used by wp.serverSideRender) a static preview
* from BlockPreview is rendered instead — same markup and CSS classes, no
* live REST calls, redirects, or Stripe.js.
*/
class BlockRegistrar {
public const SCRIPT_HANDLE = 'us-scheduler-blocks';
public const STYLE_HANDLE = 'us-scheduler';
public function __construct(
private BookingPage $bookingPage,
private LoginPage $loginPage,
private RegistrationPage $registrationPage,
private GroupClassPage $groupClassPage,
) {}
public function register(): void {
add_action( 'init', [ $this, 'registerBlocks' ] );
}
public function registerBlocks(): void {
// The editor script registers the client side of each block (title,
// icon, shortcode transform) and previews it via wp.serverSideRender.
wp_register_script(
self::SCRIPT_HANDLE,
USC_PLUGIN_URL . 'assets/js/blocks.js',
[ 'wp-blocks', 'wp-element', 'wp-block-editor', 'wp-server-side-render', 'wp-i18n' ],
USC_VERSION,
true
);
// The front-end stylesheet doubles as the block style so editor
// previews look like the published page. ShortcodeRegistrar registers
// the same handle on the front end, hence the guard.
if ( ! wp_style_is( self::STYLE_HANDLE, 'registered' ) ) {
wp_register_style( self::STYLE_HANDLE, USC_PLUGIN_URL . 'assets/css/frontend.css', [], USC_VERSION );
}
foreach ( $this->blocks() as $name => $renderCallback ) {
register_block_type(
$name,
[
'api_version' => '3',
'editor_script' => self::SCRIPT_HANDLE,
'style' => self::STYLE_HANDLE,
'render_callback' => $renderCallback,
]
);
}
}
/**
* Block names mapped to their render callbacks.
*
* @return array<string, callable(array<string, mixed>): string>
*/
private function blocks(): array {
return [
'us-scheduler/booking' => [ $this, 'renderBooking' ],
'us-scheduler/student-login' => [ $this, 'renderLogin' ],
'us-scheduler/student-register' => [ $this, 'renderRegistration' ],
'us-scheduler/group-classes' => [ $this, 'renderGroupClasses' ],
];
}
/**
* Renders the booking block.
*
* @param array<string, mixed> $attributes Block attributes (unused — the blocks have none yet).
*/
public function renderBooking( array $attributes = [] ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
return $this->isEditorPreview() ? BlockPreview::booking() : $this->bookingPage->render( [] );
}
/**
* Renders the student-login block.
*
* @param array<string, mixed> $attributes Block attributes (unused — the blocks have none yet).
*/
public function renderLogin( array $attributes = [] ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
return $this->isEditorPreview() ? BlockPreview::login() : $this->loginPage->render( [] );
}
/**
* Renders the student-registration block.
*
* @param array<string, mixed> $attributes Block attributes (unused — the blocks have none yet).
*/
public function renderRegistration( array $attributes = [] ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
return $this->isEditorPreview() ? BlockPreview::registration() : $this->registrationPage->render( [] );
}
/**
* Renders the group-classes block.
*
* @param array<string, mixed> $attributes Block attributes (unused — the blocks have none yet).
*/
public function renderGroupClasses( array $attributes = [] ): string { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.Found
return $this->isEditorPreview() ? BlockPreview::groupClasses() : $this->groupClassPage->render( [] );
}
/**
* Whether this render is the editor's block-renderer REST preview rather
* than a real front-end page render. Front-end template rendering never
* happens inside a REST request, so REST_REQUEST is a reliable signal.
*/
protected function isEditorPreview(): bool {
return defined( 'REST_REQUEST' ) && (bool) constant( 'REST_REQUEST' );
}
}