- 83 Komponenten: Livewire\Volt\Component → Livewire\Component
- 73 Routen (admin, auth, customer): Volt::route() → Route::livewire()
- 88 Testdateien: Volt::test/actingAs → Livewire::test/actingAs
- VoltServiceProvider gelöscht; composer remove livewire/volt
- config/livewire.php auf v4-Schlüssel (component_layout, component_placeholder,
component_locations, make_command: SFC ohne ⚡). Der alte Schlüssel layout war
wirkungslos; alle gerouteten Komponenten setzen ihr Layout selbst.
- CLAUDE.md, AGENTS.md, boost.json, .cursorrules, Skill volt-development
- Roadmap: erledigt; Larastan erfasst SFCs weiterhin nicht (Blade-Dateien)
Suite 2.325 grün, einer übersprungen; Larastan und Pint ohne Meldung;
route:cache und view:cache laufen.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
179 lines
7.6 KiB
PHP
179 lines
7.6 KiB
PHP
<?php
|
|
|
|
use App\Enums\RegistrationType;
|
|
use App\Models\User;
|
|
use Illuminate\Auth\Events\Registered;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Hash;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Illuminate\Validation\Rules;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
|
|
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Konto erstellen', 'eyebrow' => 'Registrierung im Publisher-Hub', 'topRightLabel' => 'Bereits Konto?', 'topRightLinkText' => 'Anmelden', 'topRightLinkHref' => '/login'])] class extends Component {
|
|
public string $name = '';
|
|
|
|
public string $email = '';
|
|
|
|
public string $password = '';
|
|
|
|
public string $password_confirmation = '';
|
|
|
|
public bool $terms_accepted = false;
|
|
|
|
/**
|
|
* Handle an incoming registration request.
|
|
*/
|
|
public function register(): void
|
|
{
|
|
$validated = $this->validate([
|
|
'name' => ['required', 'string', 'max:255'],
|
|
'email' => ['required', 'string', 'lowercase', 'email', 'max:255', 'unique:'.User::class],
|
|
'password' => ['required', 'string', 'confirmed', Rules\Password::defaults()],
|
|
'terms_accepted' => ['accepted'],
|
|
], [
|
|
'terms_accepted.accepted' => 'Bitte bestätigen Sie unsere AGB und die Datenschutzerklärung.',
|
|
]);
|
|
|
|
// Bot-/Massenanmeldungen drosseln (R6.2): pro IP, großzügig genug
|
|
// für legitime Mehrfachversuche.
|
|
if (RateLimiter::tooManyAttempts('register-ip|'.request()->ip(), 10)) {
|
|
throw ValidationException::withMessages([
|
|
'email' => __('Zu viele Registrierungen. Bitte versuchen Sie es später erneut.'),
|
|
]);
|
|
}
|
|
|
|
RateLimiter::hit('register-ip|'.request()->ip(), 3600);
|
|
|
|
// Die bestätigte AGB-/Datenschutz-Checkbox wird als DSGVO-Einwilligung
|
|
// mit Zeitstempel festgehalten (users.gdpr_consent_at).
|
|
unset($validated['terms_accepted']);
|
|
$validated['gdpr_consent_at'] = now();
|
|
|
|
$validated['password'] = Hash::make($validated['password']);
|
|
// Ohne expliziten Typ greift der DB-Default `existing_legacy` (für den
|
|
// Legacy-Import gedacht). Selbst-Registrierungen sind Neukunden —
|
|
// gleicher Default wie im CustomerAccountProvisioner.
|
|
$validated['registration_type'] = RegistrationType::Company->value;
|
|
// Konto bleibt bis zur E-Mail-Verifizierung inaktiv und rollenlos
|
|
// (Entscheidung 15.06.). Rolle + Aktivierung erfolgen erst nach dem
|
|
// bestätigten Verifizierungslink (ActivateUserAfterVerification).
|
|
$validated['is_active'] = false;
|
|
|
|
// Das Registered-Event versendet über den verdrahteten
|
|
// SendEmailVerificationNotification-Listener die Bestätigungsmail.
|
|
event(new Registered($user = User::create($validated)));
|
|
|
|
Auth::login($user);
|
|
|
|
// Direkt zur Bestätigungs-/Notice-Seite. Ohne navigate:true, weil das
|
|
// Panel ein anderes Vite-Bundle nutzt als das Hub-Auth-Layout.
|
|
$this->redirect(route('verification.notice', absolute: false));
|
|
}
|
|
}; ?>
|
|
|
|
<div>
|
|
<x-session-status class="mb-4" />
|
|
|
|
<form wire:submit="register" class="space-y-[18px]" novalidate>
|
|
|
|
<div>
|
|
<label class="field-label" for="auth-name">{{ __('Name') }}</label>
|
|
<input
|
|
id="auth-name"
|
|
type="text"
|
|
wire:model="name"
|
|
required
|
|
autofocus
|
|
autocomplete="name"
|
|
class="field-input"
|
|
placeholder="{{ __('Vor- und Nachname') }}"
|
|
@error('name') aria-invalid="true" @enderror
|
|
/>
|
|
@error('name')
|
|
<p class="field-error">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
|
|
<div>
|
|
<label class="field-label" for="auth-email">{{ __('E-Mail-Adresse') }}</label>
|
|
<input
|
|
id="auth-email"
|
|
type="email"
|
|
wire:model="email"
|
|
required
|
|
autocomplete="email"
|
|
class="field-input"
|
|
placeholder="redaktion@ihr-unternehmen.de"
|
|
@error('email') aria-invalid="true" @enderror
|
|
/>
|
|
@error('email')
|
|
<p class="field-error">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
|
|
<x-password-input
|
|
name="password"
|
|
:label="__('Passwort')"
|
|
autocomplete="new-password"
|
|
:placeholder="__('Mindestens 8 Zeichen')"
|
|
/>
|
|
|
|
<x-password-input
|
|
name="password_confirmation"
|
|
:label="__('Passwort bestätigen')"
|
|
autocomplete="new-password"
|
|
:placeholder="__('Passwort wiederholen')"
|
|
:show-error="false"
|
|
/>
|
|
|
|
<div class="!mt-5">
|
|
<label for="auth-terms" class="flex items-start gap-3 cursor-pointer select-none">
|
|
<input
|
|
id="auth-terms"
|
|
type="checkbox"
|
|
wire:model="terms_accepted"
|
|
required
|
|
class="auth-check !mt-[3px]"
|
|
@error('terms_accepted') aria-invalid="true" @enderror
|
|
/>
|
|
<span class="text-[12.5px] text-ink-2 leading-[1.55]">
|
|
{{ __('Ich habe die') }}
|
|
<a href="{{ route('agb') }}" target="_blank" rel="noopener" class="link-hub">{{ __('AGB') }}</a>
|
|
{{ __('und die') }}
|
|
<a href="{{ route('datenschutz') }}" target="_blank" rel="noopener" class="link-hub">{{ __('Datenschutzerklärung') }}</a>
|
|
{{ __('gelesen und stimme der Verarbeitung meiner Daten zur Konto-Erstellung ausdrücklich zu.') }}
|
|
</span>
|
|
</label>
|
|
@error('terms_accepted')
|
|
<p class="field-error !ml-7">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
|
|
<button type="submit" class="auth-btn-primary !mt-[18px]" wire:loading.attr="disabled" wire:target="register">
|
|
<span wire:loading.remove wire:target="register">{{ __('Konto erstellen') }}</span>
|
|
<span wire:loading wire:target="register">{{ __('Konto wird angelegt …') }}</span>
|
|
</button>
|
|
|
|
<div class="flex items-center gap-3 !mt-[22px] !mb-[14px]">
|
|
<span class="flex-1 h-px bg-bg-rule"></span>
|
|
<span class="text-[11px] font-semibold tracking-[0.18em] uppercase text-ink-3">{{ __('Bereits Konto?') }}</span>
|
|
<span class="flex-1 h-px bg-bg-rule"></span>
|
|
</div>
|
|
|
|
<a href="{{ route('login') }}" class="auth-btn-outline !mt-0" wire:navigate>
|
|
{{ __('Stattdessen anmelden') }}
|
|
</a>
|
|
|
|
<a href="{{ route('oauth.google.redirect') }}" class="auth-btn-outline !mt-3">
|
|
<svg width="15" height="15" viewBox="0 0 18 18" aria-hidden="true">
|
|
<path fill="#4285F4" d="M17.64 9.2c0-.64-.06-1.25-.16-1.84H9v3.48h4.84a4.14 4.14 0 0 1-1.8 2.72v2.26h2.92c1.71-1.57 2.68-3.89 2.68-6.62z"/>
|
|
<path fill="#34A853" d="M9 18c2.43 0 4.47-.8 5.96-2.18l-2.92-2.26c-.81.54-1.84.86-3.04.86-2.34 0-4.32-1.58-5.03-3.7H.96v2.33A9 9 0 0 0 9 18z"/>
|
|
<path fill="#FBBC05" d="M3.97 10.72A5.4 5.4 0 0 1 3.68 9c0-.6.1-1.18.29-1.72V4.95H.96A9 9 0 0 0 0 9c0 1.45.35 2.82.96 4.05l3.01-2.33z"/>
|
|
<path fill="#EA4335" d="M9 3.58c1.32 0 2.5.45 3.44 1.35l2.58-2.59C13.46.89 11.43 0 9 0A9 9 0 0 0 .96 4.95l3.01 2.33C4.68 5.16 6.66 3.58 9 3.58z"/>
|
|
</svg>
|
|
<span>{{ __('Mit Google registrieren') }}</span>
|
|
</a>
|
|
</form>
|
|
</div>
|