291 lines
15 KiB
PHP
291 lines
15 KiB
PHP
<?php
|
|
|
|
use App\Models\RegistrationCode;
|
|
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('Registrierung')] class extends Component {
|
|
public string $roleSlug;
|
|
public string $roleKey;
|
|
public string $codePrefix = '';
|
|
public string $codePart1 = '';
|
|
public string $codePart2 = '';
|
|
public string $codePart3 = '';
|
|
public string $codePart4 = '';
|
|
public string $roleLabel = '';
|
|
public string $roleDescription = '';
|
|
|
|
protected array $roleMap = [];
|
|
|
|
|
|
public function mount(string $role): void
|
|
{
|
|
$this->loadRoleMap();
|
|
|
|
$slug = strtolower($role);
|
|
if (!isset($this->roleMap[$slug])) {
|
|
abort(404);
|
|
}
|
|
|
|
$this->roleSlug = $slug;
|
|
$this->roleKey = $this->roleMap[$slug]['key'];
|
|
$this->roleLabel = $this->roleMap[$slug]['label'];
|
|
$this->roleDescription = $this->roleMap[$slug]['description'];
|
|
$this->codePrefix = $this->roleMap[$slug]['prefix'] ?? '';
|
|
}
|
|
|
|
protected function loadRoleMap(): void
|
|
{
|
|
if (!empty($this->roleMap)) {
|
|
return;
|
|
}
|
|
$roles = Role::whereNotNull('reg_prefix')->where('can_be_invited', true)->get();
|
|
foreach ($roles as $role) {
|
|
$slug = strtolower(str_replace('-', '', $role->reg_prefix));
|
|
$key = strtolower(str_replace('-', '', $role->name));
|
|
$this->roleMap[$slug] = [
|
|
'key' => $key,
|
|
'label' => __('registration.roles.' . $key . '.label'),
|
|
'description' => __('registration.roles.' . $key . '.description'),
|
|
'prefix' => $role->reg_prefix,
|
|
];
|
|
}
|
|
}
|
|
|
|
public function submitCode(): void
|
|
{
|
|
$this->validate([
|
|
'codePrefix' => ['required', 'string', 'size:1'],
|
|
'codePart1' => ['required', 'digits:2'],
|
|
'codePart2' => ['required', 'digits:2'],
|
|
'codePart3' => ['required', 'digits:2'],
|
|
'codePart4' => ['required', 'digits:2'],
|
|
]);
|
|
|
|
|
|
$normalized = $this->normalizeCode(
|
|
$this->codePrefix,
|
|
$this->codePart1,
|
|
$this->codePart2,
|
|
$this->codePart3,
|
|
$this->codePart4
|
|
);
|
|
/** @var RegistrationCode|null $registrationCode */
|
|
$registrationCode = RegistrationCode::where('code', $normalized)
|
|
->where('role', $this->roleKey)
|
|
->first();
|
|
if (!$registrationCode || !$registrationCode->isAvailable()) {
|
|
$this->addError('code', __('registration.messages.code_invalid'));
|
|
return;
|
|
}
|
|
|
|
// Merke Code in Session; Verbrauch/Markierung erfolgt nach erfolgreicher Registrierung.
|
|
session([
|
|
'registration_code_id' => $registrationCode->id,
|
|
'registration_role' => $this->roleKey,
|
|
'registration_slug' => $this->roleSlug,
|
|
]);
|
|
|
|
session()->flash('message', __('registration.messages.code_accepted'));
|
|
$this->redirect(route('partner.create.account'), navigate: true);
|
|
}
|
|
|
|
protected function normalizeCode(string $prefix, string ...$parts): string
|
|
{
|
|
$prefix = strtoupper(trim($prefix));
|
|
$segments = array_map(fn ($p) => str_pad(preg_replace('/\D+/', '', $p), 2, '0', STR_PAD_LEFT), $parts);
|
|
return $prefix . implode('', $segments);
|
|
}
|
|
}; ?>
|
|
|
|
|
|
<div class="bg-background">
|
|
<livewire:web.components.ui.header />
|
|
|
|
<main class="variante-glass-flow">
|
|
<div class="max-w-xl mx-auto px-6 space-y-8 pt-20 pb-40" id="code-form">
|
|
{{-- Hero --}}
|
|
<div class="text-center space-y-3">
|
|
<p class="text-xs uppercase tracking-[0.2em] text-accent-500">{{ __('registration.titles.registration') }}</p>
|
|
<h1 class="text-3xl md:text-4xl font-bold text-foreground">
|
|
{{ __('registration.titles.access_for_role', ['role' => $roleLabel]) }}
|
|
</h1>
|
|
<p class="text-base text-muted-foreground">
|
|
{{ $roleDescription }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="bg-card/80 backdrop-blur shadow-2xl border border-border rounded-3xl p-6 md:p-8 space-y-6">
|
|
|
|
|
|
<form wire:submit="submitCode" class="space-y-4 mt-10">
|
|
<flux:field>
|
|
<div class="text-center text-lg font-medium text-foreground mb-4">{{ __('registration.titles.enter_code') }}</div>
|
|
<div class="flex justify-center items-center gap-3 flex-nowrap mx-auto w-fit">
|
|
<input
|
|
wire:model.defer="codePrefix"
|
|
placeholder="M"
|
|
maxlength="1"
|
|
autofocus
|
|
class="w-12 text-center uppercase text-xl px-3 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"
|
|
/>
|
|
<span class="text-muted-foreground">|</span>
|
|
<input
|
|
wire:model.defer="codePart1"
|
|
placeholder="00"
|
|
maxlength="2"
|
|
class="w-16 text-center text-xl px-3 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"
|
|
/>
|
|
<input
|
|
wire:model.defer="codePart2"
|
|
placeholder="00"
|
|
maxlength="2"
|
|
class="w-16 text-center text-xl px-3 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"
|
|
/>
|
|
<input
|
|
wire:model.defer="codePart3"
|
|
placeholder="00"
|
|
maxlength="2"
|
|
class="w-16 text-center text-xl px-3 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"
|
|
/>
|
|
<input
|
|
wire:model.defer="codePart4"
|
|
placeholder="00"
|
|
maxlength="2"
|
|
class="w-16 text-center text-xl px-3 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"
|
|
/>
|
|
</div>
|
|
<br />
|
|
<div class="flex flex-col gap-3 p-4 mt-10 rounded-2xl bg-accent-50 dark:bg-accent-900/15 border border-accent-100 dark:border-accent-900/30">
|
|
<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">
|
|
<flux:icon.key class="h-5 w-5 text-accent-600 dark:text-accent-300" />
|
|
</div>
|
|
<div>
|
|
<div class="font-base text-foreground">{{ __('registration.messages.code_unique') }}</div>
|
|
<div class="text-sm text-muted-foreground">{{ __('registration.messages.code_format') }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
<div class="text-xs text-center text-muted-foreground mt-2 mb-2">
|
|
{{ __('') }}
|
|
</div>
|
|
<br />
|
|
|
|
</flux:field>
|
|
|
|
<x-error-alert-static light />
|
|
|
|
<div class="flex justify-between">
|
|
<flux:button type="button" variant="primary" icon="arrow-down" href="#how-it-works" class="cursor-pointer">
|
|
{{ __('registration.actions.how_it_works') }}
|
|
</flux:button>
|
|
<button type="submit" class="btn-secondary-accent small cursor-pointer" wire:target="submitCode">
|
|
<span class="flex items-center gap-2"><flux:icon.arrow-right class="h-5 w-5" /> {{ __('registration.actions.check_code') }}</span>
|
|
</button>
|
|
</div>
|
|
</form>
|
|
|
|
|
|
@if (session()->has('message'))
|
|
<div class="p-3 rounded-lg border border-red-200 bg-red-50 text-red-800">
|
|
{{ session('message') }}
|
|
</div>
|
|
@endif
|
|
</div>
|
|
|
|
<div class="text-center text-sm text-muted-foreground">
|
|
{{ __('registration.messages.code_problems') }}
|
|
</div>
|
|
|
|
|
|
</div>
|
|
|
|
<section class="section-padding bg-accent" id="how-it-works">
|
|
<div class="container-padding">
|
|
<div class="text-center mb-16 slide-up delay-300">
|
|
<h2 class="text-section-title text-foreground mb-6">
|
|
{{ __('registration.titles.how_it_works') }}
|
|
</h2>
|
|
<p class="text-large text-muted-foreground max-w-3xl mx-auto">
|
|
{{ __('registration.titles.how_it_works_description', ['role' => $roleLabel]) }}
|
|
</p>
|
|
</div>
|
|
|
|
<div class="grid md:grid-cols-3 gap-8 mb-16">
|
|
<div class="card-elevated p-0 overflow-hidden group hover:shadow-elevated transition-all duration-300 slide-up delay-200">
|
|
<div class="relative pt-12 pb-8">
|
|
<div class="mx-auto w-20 h-20 icon-secondary-linear glow-soft group-hover:glow-medium rounded-2xl flex items-center justify-center transition-colors duration-300">
|
|
@svg('heroicon-o-key', 'w-10 h-10 text-secondary-foreground')
|
|
</div>
|
|
</div>
|
|
<div class="p-8">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-10 h-10 rounded-lg bg-secondary/10 flex items-center justify-center">
|
|
<span class="text-lg font-bold text-secondary">1</span>
|
|
</div>
|
|
<h3 class="text-2xl font-medium text-foreground">
|
|
{{ __('registration.steps.code_entry.title') }}
|
|
</h3>
|
|
</div>
|
|
<p class="text-muted-foreground leading-relaxed mb-0">
|
|
{{ __('registration.steps.code_entry.description') }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-elevated p-0 overflow-hidden group hover:shadow-elevated transition-all duration-300 slide-up delay-400">
|
|
<div class="relative pt-12 pb-8">
|
|
<div class="mx-auto w-20 h-20 icon-secondary-linear glow-soft group-hover:glow-medium rounded-2xl flex items-center justify-center transition-colors duration-300">
|
|
@svg('heroicon-o-user-plus', 'w-10 h-10 text-secondary-foreground')
|
|
</div>
|
|
</div>
|
|
<div class="p-8">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-10 h-10 rounded-lg bg-secondary/10 flex items-center justify-center">
|
|
<span class="text-lg font-bold text-secondary">2</span>
|
|
</div>
|
|
<h3 class="text-2xl font-medium text-foreground">
|
|
{{ __('registration.steps.create_account.title') }}
|
|
</h3>
|
|
</div>
|
|
<p class="text-muted-foreground leading-relaxed mb-0">
|
|
{{ __('registration.steps.create_account.description', ['role' => $roleLabel]) }}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card-elevated p-0 overflow-hidden group hover:shadow-elevated transition-all duration-300 slide-up delay-600">
|
|
<div class="relative pt-12 pb-8">
|
|
<div class="mx-auto w-20 h-20 icon-secondary-linear glow-soft group-hover:glow-medium rounded-2xl flex items-center justify-center transition-colors duration-300">
|
|
@svg('heroicon-o-sparkles', 'w-10 h-10 text-secondary-foreground')
|
|
</div>
|
|
</div>
|
|
<div class="p-8">
|
|
<div class="flex items-center gap-3 mb-4">
|
|
<div class="w-10 h-10 rounded-lg bg-secondary/10 flex items-center justify-center">
|
|
<span class="text-lg font-bold text-secondary">3</span>
|
|
</div>
|
|
<h3 class="text-2xl font-medium text-foreground">
|
|
{{ __('registration.steps.complete_onboarding.title') }}
|
|
</h3>
|
|
</div>
|
|
<p class="text-muted-foreground leading-relaxed mb-6">
|
|
{{ __('registration.steps.complete_onboarding.description') }}
|
|
</p>
|
|
<a href="#code-form" class="block">
|
|
<button class="btn-secondary-accent small w-full cursor-pointer">
|
|
{{ __('registration.actions.start_now') }}
|
|
</button>
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
</main>
|
|
<livewire:web.components.ui.footer />
|
|
</div>
|