Files
fotospiel-app/app/Filament/Tenant/Pages/TenantOnboarding.php

312 lines
9.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace App\Filament\Tenant\Pages;
use App\Filament\Tenant\Resources\EventResource;
use App\Models\Event;
use App\Models\EventType;
use App\Models\TaskCollection;
use App\Services\EventJoinTokenService;
use App\Services\Tenant\TaskCollectionImportService;
use App\Support\JoinTokenLayoutRegistry;
use App\Support\TenantOnboardingState;
use BackedEnum;
use Filament\Notifications\Notification;
use Filament\Pages\Page;
use Illuminate\Database\Eloquent\Collection as EloquentCollection;
use Illuminate\Support\Arr;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;
use Throwable;
use UnitEnum;
class TenantOnboarding extends Page
{
protected static BackedEnum|string|null $navigationIcon = 'heroicon-o-sparkles';
protected string $view = 'filament.tenant.pages.onboarding';
protected static ?string $navigationLabel = 'Willkommen';
protected static ?string $slug = 'willkommen';
protected static ?string $title = 'Euer Start mit Fotospiel';
protected static UnitEnum|string|null $navigationGroup = null;
public string $step = 'intro';
public array $status = [];
public array $inviteDownloads = [];
public array $selectedPackages = [];
public string $eventName = '';
public ?string $eventDate = null;
public ?int $eventTypeId = null;
public ?string $palette = null;
public ?string $inviteLayout = null;
public bool $isProcessing = false;
protected static bool $shouldRegisterNavigation = true;
public function mount(): void
{
$tenant = TenantOnboardingState::tenant();
abort_if(! $tenant, 403);
$this->status = TenantOnboardingState::status($tenant);
if (TenantOnboardingState::completed($tenant)) {
$this->redirect(EventResource::getUrl());
return;
}
$this->eventDate = Carbon::now()->addWeeks(2)->format('Y-m-d');
$this->eventTypeId = $this->getDefaultEventTypeId();
}
public static function shouldRegisterNavigation(): bool
{
$tenant = TenantOnboardingState::tenant();
return ! TenantOnboardingState::completed($tenant);
}
public function start(): void
{
$this->step = 'packages';
}
public function savePackages(): void
{
$this->validate([
'selectedPackages' => ['required', 'array', 'min:1'],
'selectedPackages.*' => ['integer', 'exists:task_collections,id'],
], [
'selectedPackages.required' => 'Bitte wählt mindestens ein Aufgabenpaket aus.',
]);
$this->step = 'event';
}
public function saveEvent(): void
{
$this->validate([
'eventName' => ['required', 'string', 'max:255'],
'eventDate' => ['required', 'date'],
'eventTypeId' => ['required', 'exists:event_types,id'],
]);
$this->step = 'palette';
}
public function savePalette(): void
{
$this->validate([
'palette' => ['required', 'string'],
]);
$this->step = 'invite';
}
public function finish(
TaskCollectionImportService $importService,
EventJoinTokenService $joinTokenService
): void {
$this->validate([
'inviteLayout' => ['required', 'string'],
], [
'inviteLayout.required' => 'Bitte wählt ein Layout aus.',
]);
$tenant = TenantOnboardingState::tenant();
abort_if(! $tenant, 403);
$this->isProcessing = true;
try {
DB::transaction(function () use ($tenant, $importService, $joinTokenService) {
$event = $this->createEvent($tenant);
$this->importPackages($importService, $this->selectedPackages, $event);
$token = $joinTokenService->createToken($event, [
'label' => 'Fotospiel Einladung',
'metadata' => [
'preferred_layout' => $this->inviteLayout,
],
]);
$settings = $tenant->settings ?? [];
Arr::set($settings, 'branding.palette', $this->palette);
Arr::set($settings, 'branding.primary_event_id', $event->id);
Arr::set($settings, 'branding.preferred_invite_layout', $this->inviteLayout);
$tenant->forceFill(['settings' => $settings])->save();
TenantOnboardingState::markCompleted($tenant, [
'primary_event_id' => $event->id,
'selected_packages' => $this->selectedPackages,
'qr_layout' => $this->inviteLayout,
]);
$this->inviteDownloads = $this->buildInviteDownloads($event, $token);
$this->status = TenantOnboardingState::status($tenant);
Notification::make()
->title('Euer Setup ist bereit!')
->body('Wir haben euer Event erstellt, Aufgaben importiert und euren Einladungslink vorbereitet.')
->success()
->send();
$this->redirect(EventResource::getUrl('view', ['record' => $event]));
});
} catch (Throwable $exception) {
report($exception);
Notification::make()
->title('Setup konnte nicht abgeschlossen werden')
->body('Bitte prüft eure Eingaben oder versucht es später erneut.')
->danger()
->send();
} finally {
$this->isProcessing = false;
}
}
protected function createEvent($tenant): Event
{
$slugBase = Str::slug($this->eventName) ?: 'event';
do {
$slug = Str::of($slugBase)->append('-', Str::random(6))->lower();
} while (Event::where('slug', $slug)->exists());
return Event::create([
'tenant_id' => $tenant->id,
'name' => [
app()->getLocale() => $this->eventName,
'de' => $this->eventName,
],
'description' => null,
'date' => $this->eventDate,
'slug' => (string) $slug,
'event_type_id' => $this->eventTypeId,
'is_active' => true,
'default_locale' => app()->getLocale(),
'status' => 'draft',
'settings' => [
'appearance' => [
'palette' => $this->palette,
],
],
]);
}
protected function importPackages(
TaskCollectionImportService $importService,
array $packageIds,
Event $event
): void {
if (empty($packageIds)) {
return;
}
/** @var EloquentCollection<TaskCollection> $collections */
$collections = TaskCollection::query()
->whereIn('id', $packageIds)
->get();
$collections->each(function (TaskCollection $collection) use ($importService, $event) {
$importService->import($collection, $event);
});
}
protected function buildInviteDownloads(Event $event, $token): array
{
return JoinTokenLayoutRegistry::toResponse(function (string $layoutId, string $format) use ($event, $token) {
return route('api.v1.tenant.events.join-tokens.layouts.download', [
'event' => $event->slug,
'joinToken' => $token->getKey(),
'layout' => $layoutId,
'format' => $format,
]);
});
}
public function getPackageListProperty(): array
{
return TaskCollection::query()
->whereNull('tenant_id')
->orderBy('position')
->get()
->map(fn (TaskCollection $collection) => [
'id' => $collection->getKey(),
'name' => $collection->name,
'description' => $collection->description,
])
->toArray();
}
public function getEventTypeOptionsProperty(): array
{
return EventType::query()
->orderBy('name->'.app()->getLocale())
->get()
->mapWithKeys(function (EventType $type) {
$name = $type->name[app()->getLocale()] ?? $type->name['de'] ?? Arr::first($type->name);
return [$type->getKey() => $name];
})
->toArray();
}
public function getPaletteOptionsProperty(): array
{
return [
'romance' => [
'label' => 'Rosé & Gold',
'description' => 'Warme Rosé-Töne mit goldenen Akzenten romantisch und elegant.',
],
'sunset' => [
'label' => 'Sonnenuntergang',
'description' => 'Leuchtende Orange- und Pink-Verläufe für lebhafte Partys.',
],
'evergreen' => [
'label' => 'Evergreen',
'description' => 'Sanfte Grüntöne und Naturakzente für Boho- & Outdoor-Events.',
],
'midnight' => [
'label' => 'Midnight',
'description' => 'Tiefes Navy und Flieder perfekt für elegante Abendveranstaltungen.',
],
];
}
public function getLayoutOptionsProperty(): array
{
return collect(JoinTokenLayoutRegistry::all())
->map(fn ($layout) => [
'id' => $layout['id'],
'name' => $layout['name'],
'subtitle' => $layout['subtitle'] ?? '',
'description' => $layout['description'] ?? '',
])
->toArray();
}
protected function getDefaultEventTypeId(): ?int
{
return EventType::query()->orderBy('name->'.app()->getLocale())->value('id');
}
}