Implemented a block editor for changing the layout of the page

This commit is contained in:
2026-04-04 01:17:05 +02:00
parent 0800f7664f
commit ff58e82497
41 changed files with 2706 additions and 298 deletions

View File

@@ -0,0 +1,251 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Models\PageBlock;
use App\Models\PreregistrationPage;
use Illuminate\Http\Request;
use Illuminate\Http\UploadedFile;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Storage;
class PreregistrationPageBlockWriter
{
/**
* @param array<int, array<string, mixed>> $blocks
*/
public function replaceBlocks(PreregistrationPage $page, array $blocks, Request $request): void
{
DB::transaction(function () use ($page, $blocks, $request): void {
$page->load('blocks');
$oldPaths = $this->collectBlockImagePaths($page);
$page->blocks()->delete();
$orderedKeys = array_keys($blocks);
usort($orderedKeys, function (string|int $a, string|int $b) use ($blocks): int {
/** @var array<string, mixed> $rowA */
$rowA = $blocks[$a];
/** @var array<string, mixed> $rowB */
$rowB = $blocks[$b];
return ((int) ($rowA['sort_order'] ?? 0)) <=> ((int) ($rowB['sort_order'] ?? 0));
});
$allNewPaths = [];
foreach ($orderedKeys as $blockKey) {
/** @var array<string, mixed> $blockRow */
$blockRow = $blocks[$blockKey];
$type = (string) $blockRow['type'];
/** @var array<string, mixed> $content */
$content = is_array($blockRow['content'] ?? null) ? $blockRow['content'] : [];
if ($type === 'text' && array_key_exists('body', $content) && is_string($content['body'])) {
$content['body'] = ltrim($content['body']);
}
if ($type === 'image') {
$content = $this->mergeImageBlockFiles($page, (string) $blockKey, $content, $request);
}
$allNewPaths = array_merge($allNewPaths, $this->pathsFromImageTypeContent($type, $content));
PageBlock::query()->create([
'preregistration_page_id' => $page->id,
'type' => $type,
'content' => $content,
'sort_order' => (int) ($blockRow['sort_order'] ?? 0),
'is_visible' => (bool) ($blockRow['is_visible'] ?? true),
]);
}
$allNewPaths = array_values(array_unique($allNewPaths));
foreach (array_diff($oldPaths, $allNewPaths) as $orphan) {
if (is_string($orphan) && $orphan !== '') {
Storage::disk('public')->delete($orphan);
}
}
});
}
public function seedDefaultBlocks(PreregistrationPage $page): void
{
$rows = [
[
'type' => 'hero',
'sort_order' => 0,
'is_visible' => true,
'content' => [
'headline' => $page->title,
'subheadline' => '',
'eyebrow_text' => '',
'eyebrow_style' => 'badge',
'text_alignment' => 'center',
],
],
];
$next = 1;
if ($page->start_date->isFuture()) {
$rows[] = [
'type' => 'countdown',
'sort_order' => $next,
'is_visible' => true,
'content' => [
'target_datetime' => $page->start_date->toIso8601String(),
'title' => 'De pre-registratie opent over:',
'expired_action' => 'reload',
'style' => 'large',
'show_labels' => true,
'labels' => [
'days' => 'dagen',
'hours' => 'uren',
'minutes' => 'minuten',
'seconds' => 'seconden',
],
],
];
$next++;
}
$rows[] = [
'type' => 'benefits',
'sort_order' => $next,
'is_visible' => true,
'content' => [
'title' => 'Waarom voorregistreren?',
'items' => [
['icon' => 'ticket', 'text' => 'Exclusieve korting op tickets'],
['icon' => 'clock', 'text' => 'Eerder toegang tot de ticketshop'],
],
'layout' => 'list',
'max_columns' => 2,
],
];
$next++;
$rows[] = [
'type' => 'social_proof',
'sort_order' => $next,
'is_visible' => true,
'content' => [
'template' => 'Al {count} bezoekers aangemeld!',
'min_count' => 10,
'show_animation' => true,
'style' => 'pill',
],
];
$next++;
$rows[] = [
'type' => 'form',
'sort_order' => $next,
'is_visible' => true,
'content' => [
'title' => 'Registreer nu',
'description' => '',
'button_label' => 'Registreer nu!',
'button_color' => '#F47B20',
'button_text_color' => '#FFFFFF',
'fields' => [
'first_name' => [
'enabled' => true,
'required' => true,
'label' => 'Voornaam',
'placeholder' => 'Je voornaam',
],
'last_name' => [
'enabled' => true,
'required' => true,
'label' => 'Achternaam',
'placeholder' => 'Je achternaam',
],
'email' => [
'enabled' => true,
'required' => true,
'label' => 'E-mailadres',
'placeholder' => 'je@email.nl',
],
'phone' => [
'enabled' => false,
'required' => false,
'label' => 'Mobiel',
'placeholder' => '+31 6 12345678',
],
],
'show_field_icons' => true,
'privacy_text' => 'Door je te registreren ga je akkoord met onze privacyverklaring.',
'privacy_url' => '',
],
];
$page->blocks()->createMany($rows);
}
/**
* @return list<string>
*/
private function collectBlockImagePaths(PreregistrationPage $page): array
{
$paths = [];
foreach ($page->blocks as $b) {
if ($b->type !== 'image') {
continue;
}
$paths = array_merge($paths, $this->pathsFromImageTypeContent('image', is_array($b->content) ? $b->content : []));
}
return array_values(array_unique($paths));
}
/**
* @param array<string, mixed> $content
* @return list<string>
*/
private function pathsFromImageTypeContent(string $type, array $content): array
{
if ($type !== 'image') {
return [];
}
$p = data_get($content, 'image');
if (is_string($p) && $p !== '') {
return [$p];
}
return [];
}
/**
* @param array<string, mixed> $content
* @return array<string, mixed>
*/
private function mergeImageBlockFiles(PreregistrationPage $page, string $blockKey, array $content, Request $request): array
{
$disk = Storage::disk('public');
$dir = "preregister/pages/{$page->id}";
$link = $content['link_url'] ?? null;
$content['link_url'] = is_string($link) && trim($link) !== '' ? trim($link) : null;
if ($request->boolean("blocks.$blockKey.remove_block_image")) {
$prev = $content['image'] ?? null;
if (is_string($prev) && $prev !== '') {
$disk->delete($prev);
}
$content['image'] = null;
}
$file = $request->file("blocks.$blockKey.block_image");
if ($this->isValidUpload($file)) {
if (isset($content['image']) && is_string($content['image']) && $content['image'] !== '') {
$disk->delete($content['image']);
}
$content['image'] = $file->store($dir, 'public');
}
return $content;
}
private function isValidUpload(?UploadedFile $file): bool
{
return $file !== null && $file->isValid();
}
}