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 $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'); } }