b2in/resources/views/livewire/partner/invitation-accept.blade.php
2026-04-10 17:18:17 +02:00

254 lines
11 KiB
PHP

<?php
use App\Models\User;
use App\Models\Partner;
use App\Models\PartnerInvitation;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use Livewire\Attributes\Layout;
use Livewire\Attributes\Title;
use Livewire\Volt\Component;
use Spatie\Permission\Models\Role;
new #[Layout('components.layouts.guest'), Title('Willkommen bei B2in')] class extends Component {
public PartnerInvitation $invitation;
public string $firstName = '';
public string $lastName = '';
public string $email = '';
public string $password = '';
public string $password_confirmation = '';
public bool $acceptTerms = false;
public function mount(string $token): void
{
$this->invitation = PartnerInvitation::with('role')->where('token', $token)->firstOrFail();
// Prüfe ob Einladung abgelaufen ist
if ($this->invitation->isExpired()) {
$this->invitation->markAsExpired();
$this->redirect('/partner/invitation/expired/' . $token);
}
// Prüfe ob Einladung bereits verwendet wurde
if ($this->invitation->status !== 'pending') {
$this->redirect('/partner/invitation/used/' . $token);
}
$this->email = $this->invitation->email;
$this->firstName = $this->invitation->contact_first_name ?? '';
$this->lastName = $this->invitation->contact_last_name ?? '';
}
public function createAccount(): void
{
$this->validate([
'firstName' => 'required|string|max:255',
'lastName' => 'required|string|max:255',
'email' => 'required|email',
'password' => 'required|string|min:8|confirmed',
'acceptTerms' => 'accepted',
], [
'firstName.required' => __('Bitte geben Sie Ihren Vornamen ein.'),
'firstName.max' => __('Der Vorname darf maximal 255 Zeichen lang sein.'),
'lastName.required' => __('Bitte geben Sie Ihren Nachnamen ein.'),
'lastName.max' => __('Der Nachname darf maximal 255 Zeichen lang sein.'),
'email.required' => __('Bitte geben Sie Ihre E-Mail-Adresse ein.'),
'email.email' => __('Bitte geben Sie eine gültige E-Mail-Adresse ein.'),
'password.required' => __('Bitte geben Sie ein Passwort ein.'),
'password.min' => __('Das Passwort muss mindestens 8 Zeichen lang sein.'),
'password.confirmed' => __('Die Passwörter stimmen nicht überein.'),
'acceptTerms.accepted' => __('Sie müssen die AGB und Datenschutzbestimmungen akzeptieren.'),
]);
try {
\DB::beginTransaction();
// 1. Erstelle Partner-Firma
$partner = Partner::create([
'company_name' => $this->invitation->company_name,
'slug' => Str::slug($this->invitation->company_name),
'type' => $this->invitation->role->name,
'is_active' => false, // Wird später im Setup-Wizard aktiviert
]);
// 2. Erstelle User
$user = User::create([
'partner_id' => $partner->id,
'name' => $this->firstName . ' ' . $this->lastName,
'email' => $this->email,
'password' => Hash::make($this->password),
'email_verified_at' => now(), // Auto-verifiziert durch Einladung
]);
// 3. Weise Rolle zu
$user->assignRole($this->invitation->role);
// 4. Markiere Einladung als akzeptiert
$this->invitation->markAsAccepted($partner);
// 5. Logge User ein
Auth::login($user);
\DB::commit();
// 6. Weiterleitung zum Setup-Wizard
session()->flash('message', __('Willkommen bei B2in! Vervollständigen Sie nun Ihr Profil.'));
$this->redirect(route('partner.setup.wizard'), navigate: true);
} catch (\Exception $e) {
\DB::rollBack();
$this->addError('email', __('Fehler beim Erstellen des Kontos: ') . $e->getMessage());
}
}
}; ?>
<div class="w-full max-w-2xl">
{{-- Header --}}
<div class="text-center mb-8">
@include('partials.logo-head')
<h1 class="text-4xl font-bold text-zinc-900 dark:text-white mb-2">
{{ __('Willkommen, :company!', ['company' => $invitation->company_name]) }}
</h1>
<p class="text-lg text-zinc-600 dark:text-zinc-400">
{{ __('Erstellen Sie Ihr persönliches Konto, um Ihr Partner-Profil einzurichten.') }}
</p>
</div>
{{-- Card mit Formular --}}
<flux:card class="shadow-2xl">
<form wire:submit="createAccount" class="space-y-6">
{{-- Partner Info Badge --}}
<div class="flex items-center justify-center gap-3 p-4 bg-accent-50 dark:bg-accent-900/20 rounded-lg">
<flux:icon.briefcase class="h-6 w-6 text-accent-600 dark:text-accent-400" />
<div>
<div class="text-sm text-zinc-600 dark:text-zinc-400">{{ __('Partner-Typ') }}</div>
<div class="font-semibold text-zinc-900 dark:text-white">
{{ $invitation->role?->display_name ?? $invitation->role?->name }}
</div>
</div>
</div>
<flux:separator />
{{-- Name Felder --}}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<flux:field>
<flux:label>{{ __('Ihr Vorname') }} <span class="text-red-500">*</span></flux:label>
<flux:input
wire:model="firstName"
placeholder="{{ __('z.B. Max') }}"
icon="user"
autofocus
/>
@error('firstName') <flux:error>{{ $message }}</flux:error> @enderror
</flux:field>
<flux:field>
<flux:label>{{ __('Ihr Nachname') }} <span class="text-red-500">*</span></flux:label>
<flux:input
wire:model="lastName"
placeholder="{{ __('z.B. Mustermann') }}"
icon="user"
/>
@error('lastName') <flux:error>{{ $message }}</flux:error> @enderror
</flux:field>
</div>
{{-- E-Mail (gesperrt) --}}
<flux:field>
<flux:label>{{ __('E-Mail-Adresse') }}</flux:label>
<flux:input
wire:model="email"
type="email"
icon="envelope"
disabled
/>
<flux:description>{{ __('Diese E-Mail-Adresse wurde in der Einladung festgelegt und kann nicht geändert werden.') }}</flux:description>
@error('email') <flux:error>{{ $message }}</flux:error> @enderror
</flux:field>
<flux:separator />
{{-- Passwort --}}
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
<flux:field>
<flux:label>{{ __('Passwort festlegen') }} <span class="text-red-500">*</span></flux:label>
<flux:input
wire:model="password"
type="password"
placeholder="{{ __('Mindestens 8 Zeichen') }}"
icon="lock-closed"
/>
@error('password') <flux:error>{{ $message }}</flux:error> @enderror
</flux:field>
<flux:field>
<flux:label>{{ __('Passwort bestätigen') }} <span class="text-red-500">*</span></flux:label>
<flux:input
wire:model="password_confirmation"
type="password"
placeholder="{{ __('Passwort wiederholen') }}"
icon="lock-closed"
/>
@error('password_confirmation') <flux:error>{{ $message }}</flux:error> @enderror
</flux:field>
</div>
{{-- AGB Checkbox --}}
<flux:field>
<div class="flex items-start gap-3">
<flux:checkbox wire:model="acceptTerms" />
<div class="text-sm text-zinc-600 dark:text-zinc-400">
{{ __('Ich akzeptiere die') }}
<a href="#" class="text-accent-600 hover:text-accent-700 dark:text-accent-400">{{ __('AGB') }}</a>
{{ __('und') }}
<a href="#" class="text-accent-600 hover:text-accent-700 dark:text-accent-400">{{ __('Datenschutzbestimmungen') }}</a>.
</div>
</div>
@error('acceptTerms') <flux:error>{{ $message }}</flux:error> @enderror
</flux:field>
<flux:separator />
{{-- Error Alert --}}
<x-error-alert />
{{-- Submit Button --}}
<div class="flex justify-end">
<flux:button
type="submit"
variant="primary"
icon="arrow-right"
class="w-full md:w-auto"
wire:loading.attr="disabled"
wire:target="createAccount"
>
<span wire:loading.remove wire:target="createAccount">
{{ __('Konto erstellen & Setup starten') }}
</span>
<span wire:loading wire:target="createAccount">
<flux:icon.arrow-path class="animate-spin inline-block mr-2 h-4 w-4" />
{{ __('Wird erstellt...') }}
</span>
</flux:button>
</div>
</form>
</flux:card>
{{-- Footer Hinweis --}}
<div class="mt-6 text-center">
<p class="text-sm text-zinc-500 dark:text-zinc-400">
{{ __('Diese Einladung ist gültig bis zum') }}
@if($invitation->expires_at)
<strong>{{ $invitation->expires_at->format('d.m.Y H:i') }}</strong> {{ __('Uhr') }}.
@else
<strong>{{ __('unbegrenzt') }}</strong> {{ __('Uhr') }}.
@endif
</p>
</div>
</div>