299 lines
15 KiB
PHP
299 lines
15 KiB
PHP
<?php
|
|
|
|
use App\Models\RegistrationCode;
|
|
use App\Models\Partner;
|
|
use App\Models\User;
|
|
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('web.layouts.web-master-slot'), Title('Willkommen bei B2In')] class extends Component {
|
|
public string $firstName = '';
|
|
public string $lastName = '';
|
|
public string $email = '';
|
|
public string $password = '';
|
|
public string $password_confirmation = '';
|
|
public bool $acceptTerms = false;
|
|
|
|
protected ?RegistrationCode $registrationCode = null;
|
|
protected ?string $roleKey = null;
|
|
protected ?string $roleSlug = null;
|
|
|
|
|
|
public function mount(): void
|
|
{
|
|
$this->loadRegistrationCode();
|
|
$this->roleSlug = session('registration_slug') ?? null;
|
|
if (!$this->registrationCode || !$this->roleKey) {
|
|
session()->flash('message', __('Registrierungscode fehlt oder ist ungültig. Bitte starten Sie erneut über den QR-Link.'));
|
|
$this->redirect(route('registration.landing', ['role' => $this->roleSlug]), navigate: true);
|
|
return;
|
|
}
|
|
|
|
// Optional: E-Mail vorbelegen, wenn im Code gespeichert (meta)
|
|
if ($this->registrationCode->metadata['email'] ?? false) {
|
|
$this->email = $this->registrationCode->metadata['email'];
|
|
}
|
|
}
|
|
|
|
public function createAccount(): void
|
|
{
|
|
$this->loadRegistrationCode();
|
|
|
|
if (!$this->registrationCode || !$this->roleKey || !$this->registrationCode->isAvailable()) {
|
|
$this->addError('registration_code', __('Registrierungscode fehlt oder ist ungültig. Bitte starten Sie erneut über den QR-Link.'));
|
|
return;
|
|
}
|
|
|
|
$this->validate([
|
|
'firstName' => 'required|string|max:255',
|
|
'lastName' => 'required|string|max:255',
|
|
'email' => 'required|email|max:255|unique:users,email',
|
|
'password' => 'required|string|min:8|confirmed',
|
|
'acceptTerms' => 'accepted',
|
|
], [
|
|
'firstName.required' => __('Bitte geben Sie Ihren Vornamen ein.'),
|
|
'lastName.required' => __('Bitte geben Sie Ihren Nachnamen ein.'),
|
|
'email.required' => __('Bitte geben Sie Ihre E-Mail-Adresse ein.'),
|
|
'email.email' => __('Bitte geben Sie eine gültige E-Mail-Adresse ein.'),
|
|
'email.unique' => __('Diese E-Mail-Adresse ist bereits registriert.'),
|
|
'password.required' => __('Bitte vergeben Sie ein Passwort.'),
|
|
'password.min' => __('Das Passwort muss mindestens 8 Zeichen haben.'),
|
|
'password.confirmed' => __('Die Passwörter stimmen nicht überein.'),
|
|
'acceptTerms.accepted' => __('Bitte akzeptieren Sie die AGB und Datenschutzbestimmungen.'),
|
|
]);
|
|
|
|
try {
|
|
\DB::beginTransaction();
|
|
|
|
// Parent-Partner-ID ermitteln (Makler oder Händler, für Kunden)
|
|
$parentPartnerId = null;
|
|
if ($this->registrationCode->assigned_to_code_id) {
|
|
$assignedCode = RegistrationCode::with('partner')->find($this->registrationCode->assigned_to_code_id);
|
|
if ($assignedCode && $assignedCode->partner_id) {
|
|
$parentPartnerId = $assignedCode->partner_id;
|
|
}
|
|
}
|
|
|
|
// Aktuelle Marke/Theme ermitteln (aus Domain)
|
|
$brand = config('app.theme', 'b2in');
|
|
|
|
// Partner anlegen (minimal, wird im Wizard vervollständigt)
|
|
$partnerName = __('roles.' . $this->registrationCode->role . ' :code', ['code' => $this->registrationCode->code]);
|
|
$partner = Partner::create([
|
|
'company_name' => $partnerName,
|
|
'slug' => Str::slug($partnerName . '-' . $this->registrationCode->id),
|
|
'type' => $this->roleKey,
|
|
'brand' => $brand,
|
|
'parent_partner_id' => $parentPartnerId,
|
|
'is_active' => false,
|
|
]);
|
|
|
|
// User anlegen
|
|
$user = User::create([
|
|
'partner_id' => $partner->id,
|
|
'name' => trim($this->firstName . ' ' . $this->lastName),
|
|
'display_name' => $this->registrationCode->name ?? null, // Name aus Registrierungscode übernehmen
|
|
'email' => $this->email,
|
|
'password' => Hash::make($this->password),
|
|
'email_verified_at' => null, // E-Mail muss verifiziert werden
|
|
]);
|
|
|
|
// Rolle zuweisen
|
|
if ($role = Role::whereRaw('LOWER(REPLACE(name, "-", "")) = ?', [strtolower($this->roleKey)])->first()) {
|
|
$user->assignRole($role);
|
|
}
|
|
|
|
// Registrierungscode verbrauchen
|
|
$this->registrationCode->markUsed($user);
|
|
|
|
// E-Mail-Verifizierung senden
|
|
$user->sendEmailVerificationNotification();
|
|
|
|
session()->forget(['registration_code_id', 'registration_role', 'registration_slug']);
|
|
|
|
\DB::commit();
|
|
|
|
// Weiterleitung zur Danke-Seite (User ist NICHT eingeloggt)
|
|
$this->redirect(route('registration.thank-you', ['email' => $user->email]), navigate: true);
|
|
} catch (\Exception $e) {
|
|
\DB::rollBack();
|
|
$this->addError('email', __('Fehler beim Erstellen des Kontos: ') . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
protected function loadRegistrationCode(): void
|
|
{
|
|
if ($this->registrationCode) {
|
|
return;
|
|
}
|
|
|
|
$codeId = session('registration_code_id');
|
|
$role = session('registration_role');
|
|
|
|
if (!$codeId || !$role) {
|
|
return;
|
|
}
|
|
$this->registrationCode = RegistrationCode::where('id', $codeId)
|
|
->where('role', $role)
|
|
->first();
|
|
|
|
$this->roleKey = $role;
|
|
|
|
// Falls Rolle (z.B. customer) nicht unterstützt, Session leeren
|
|
if (!$this->roleKey || !$this->registrationCode?->isAvailable()) {
|
|
session()->forget(['registration_code_id', 'registration_role', 'registration_slug']);
|
|
$this->registrationCode = null;
|
|
$this->roleKey = null;
|
|
}
|
|
}
|
|
}; ?>
|
|
|
|
<div class="bg-background">
|
|
<livewire:web.components.ui.header />
|
|
|
|
<main class="variante-glass-flow">
|
|
<div class="max-w-3xl mx-auto px-6 space-y-8 pt-20 pb-40">
|
|
{{-- Header --}}
|
|
<div class="text-center space-y-2">
|
|
<p class="text-xs uppercase tracking-[0.2em] text-accent-500">{{ __('Konto anlegen') }}</p>
|
|
<h1 class="text-3xl md:text-4xl font-bold text-foreground">
|
|
{{ __('Zugang erstellen & Setup starten') }}
|
|
</h1>
|
|
<p class="text-base text-muted-foreground">
|
|
{{ __('Mit Ihrem Registrierungscode haben Sie den Zugang freigeschaltet. Legen Sie jetzt Ihr persönliches Konto an.') }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bg-card/80 backdrop-blur shadow-2xl border border-border rounded-3xl p-6 md:p-8 space-y-6">
|
|
<div class="flex flex-col gap-3 p-4 rounded-2xl bg-blue-50 border border-blue-100">
|
|
<div class="flex items-center gap-3">
|
|
<div class="h-10 w-10 rounded-xl bg-accent-100 dark:bg-accent-900/40 flex items-center justify-center">
|
|
@svg('heroicon-o-key', 'h-5 w-5 text-accent-600')
|
|
</div>
|
|
<div>
|
|
<div class="font-medium text-foreground">{{ __('Code bestätigt') }}</div>
|
|
<div class="text-sm text-muted-foreground">
|
|
{{ __('Fortschritt: Konto anlegen, danach Setup-Wizard abschließen.') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
<form wire:submit="createAccount" class="space-y-6">
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-foreground mb-2">{{ __('Vorname') }}</label>
|
|
<input
|
|
wire:model="firstName"
|
|
type="text"
|
|
placeholder="{{ __('z.B. Max') }}"
|
|
class="w-full px-4 py-3 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-sky-600 text-foreground"
|
|
/>
|
|
@error('firstName') <div class="text-sm text-red-600 mt-1">{{ $message }}</div> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-foreground mb-2">{{ __('Nachname') }}</label>
|
|
<input
|
|
wire:model="lastName"
|
|
type="text"
|
|
placeholder="{{ __('z.B. Mustermann') }}"
|
|
class="w-full px-4 py-3 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-sky-600 text-foreground"
|
|
/>
|
|
@error('lastName') <div class="text-sm text-red-600 mt-1">{{ $message }}</div> @enderror
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-foreground mb-2">{{ __('E-Mail-Adresse') }}</label>
|
|
<input
|
|
wire:model="email"
|
|
type="email"
|
|
placeholder="email@example.com"
|
|
class="w-full px-4 py-3 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-sky-600 text-foreground"
|
|
/>
|
|
@error('email') <div class="text-sm text-red-600 mt-1">{{ $message }}</div> @enderror
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
<div>
|
|
<label class="block text-sm font-medium text-foreground mb-2">{{ __('Passwort') }}</label>
|
|
<input
|
|
wire:model="password"
|
|
type="password"
|
|
placeholder="{{ __('Mindestens 8 Zeichen') }}"
|
|
class="w-full px-4 py-3 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-sky-600 text-foreground"
|
|
/>
|
|
@error('password') <div class="text-sm text-red-600 mt-1">{{ $message }}</div> @enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="block text-sm font-medium text-foreground mb-2">{{ __('Passwort bestätigen') }}</label>
|
|
<input
|
|
wire:model="password_confirmation"
|
|
type="password"
|
|
placeholder="{{ __('Passwort wiederholen') }}"
|
|
class="w-full px-4 py-3 bg-gray-50 border border-border rounded-lg outline-1 -outline-offset-1 outline-gray-300 focus-visible:outline-2 focus-visible:-outline-offset-2 focus-visible:outline-sky-600 text-foreground"
|
|
/>
|
|
@error('password_confirmation') <div class="text-sm text-red-600 mt-1">{{ $message }}</div> @enderror
|
|
</div>
|
|
</div>
|
|
|
|
<div>
|
|
<div class="flex items-start gap-3">
|
|
<div class="group relative inline-flex w-8 shrink-0 rounded-full bg-gray-200 p-px inset-ring inset-ring-gray-900/5 outline-offset-2 outline-lime-600 transition-colors duration-200 ease-in-out has-checked:bg-lime-600 has-focus-visible:outline-2">
|
|
<span class="size-4 rounded-full bg-white shadow-xs ring-1 ring-gray-900/5 transition-transform duration-200 ease-in-out group-has-checked:translate-x-3.5"></span>
|
|
<input id="acceptTerms" type="checkbox" name="acceptTerms" aria-label="Agree to policies" class="absolute inset-0 appearance-none focus:outline-hidden" wire:model="acceptTerms" />
|
|
</div>
|
|
|
|
<div class="text-sm text-muted-foreground">
|
|
{{ __('Ich akzeptiere die') }}
|
|
<a href="#" class="text-accent-600 hover:text-accent-900">{{ __('AGB') }}</a>
|
|
{{ __('und') }}
|
|
<a href="#" class="text-accent-600 hover:text-accent-900">{{ __('Datenschutzbestimmungen') }}</a>.
|
|
</div>
|
|
</div>
|
|
@error('acceptTerms') <div class="text-sm text-red-600 mt-1">{{ $message }}</div> @enderror
|
|
</div>
|
|
|
|
<x-error-alert-static light />
|
|
|
|
<div class="flex justify-end">
|
|
<button type="submit" class="btn-secondary-accent small cursor-pointer" wire:target="createAccount">
|
|
<span class="flex items-center gap-2">
|
|
@svg('heroicon-o-arrow-right', 'h-5 w-5')
|
|
{{ __('Konto erstellen & Setup starten') }}
|
|
</span>
|
|
</button>
|
|
</div>
|
|
<div class="flex flex-col gap-3 p-4 rounded-2xl bg-lime-50 border border-lime-200">
|
|
<div class="flex items-start gap-3">
|
|
<div class="h-10 w-10 rounded-xl bg-lime-100 flex items-center justify-center flex-shrink-0">
|
|
@svg('heroicon-o-arrow-right-circle', 'h-5 w-5 text-lime-600')
|
|
</div>
|
|
<div>
|
|
<div class="font-medium text-foreground">{{ __('Verifizieren Sie Ihre E-Mail-Adresse') }}</div>
|
|
<div class="text-sm text-muted-foreground">
|
|
{{ __('Nach erfolgreicher Registrierung erhalten Sie einen Verifizierungslink per E-Mail. Bitte klicken Sie auf den Link, um Ihre E-Mail-Adresse zu verifizieren.') }}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<div class="text-center text-sm text-muted-foreground">
|
|
{{ __('Probleme? Bitte wenden Sie sich an Ihren Ansprechpartner oder den Support.') }}
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<livewire:web.components.ui.footer />
|
|
</div>
|