- 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>
95 lines
3.5 KiB
PHP
95 lines
3.5 KiB
PHP
<?php
|
|
|
|
use Illuminate\Support\Facades\Password;
|
|
use Illuminate\Support\Facades\RateLimiter;
|
|
use Illuminate\Support\Str;
|
|
use Illuminate\Validation\ValidationException;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
|
|
new #[Layout('components.layouts.auth.pressekonto', ['heading' => 'Passwort vergessen', 'eyebrow' => 'Passwort zurücksetzen', 'topRightLabel' => 'Doch erinnert?', 'topRightLinkText' => 'Zur Anmeldung', 'topRightLinkHref' => '/login'])] class extends Component {
|
|
public string $email = '';
|
|
|
|
/**
|
|
* Send a password reset link to the provided email address.
|
|
*/
|
|
public function sendPasswordResetLink(): void
|
|
{
|
|
$this->validate([
|
|
'email' => ['required', 'string', 'email'],
|
|
]);
|
|
|
|
// Drosseln wie beim Anmeldelink: pro E-Mail (Mail-Fluten eines
|
|
// Postfachs) und pro IP (Durchprobieren vieler Adressen). Der
|
|
// Password-Broker drosselt zusätzlich nur pro Nutzer (60s).
|
|
$this->ensureIsNotRateLimited();
|
|
RateLimiter::hit($this->throttleKey(), 3600);
|
|
RateLimiter::hit($this->ipThrottleKey(), 3600);
|
|
|
|
Password::sendResetLink($this->only('email'));
|
|
|
|
session()->flash('status', __('A reset link will be sent if the account exists.'));
|
|
}
|
|
|
|
protected function ensureIsNotRateLimited(): void
|
|
{
|
|
$withinEmail = ! RateLimiter::tooManyAttempts($this->throttleKey(), 3);
|
|
$withinIp = ! RateLimiter::tooManyAttempts($this->ipThrottleKey(), 15);
|
|
|
|
if ($withinEmail && $withinIp) {
|
|
return;
|
|
}
|
|
|
|
throw ValidationException::withMessages([
|
|
'email' => __('Zu viele Anfragen. Bitte versuchen Sie es später erneut.'),
|
|
]);
|
|
}
|
|
|
|
protected function throttleKey(): string
|
|
{
|
|
return 'password-reset|'.Str::transliterate(Str::lower($this->email));
|
|
}
|
|
|
|
protected function ipThrottleKey(): string
|
|
{
|
|
return 'password-reset-ip|'.request()->ip();
|
|
}
|
|
}; ?>
|
|
|
|
<div>
|
|
<p class="text-[13.5px] text-ink-2 leading-[1.6] !-mt-4 mb-6">
|
|
{{ __('Geben Sie die E-Mail-Adresse Ihres Kontos ein. Sie erhalten innerhalb weniger Minuten einen Link, mit dem Sie ein neues Passwort vergeben können.') }}
|
|
</p>
|
|
|
|
<x-session-status class="mb-4" />
|
|
|
|
<form wire:submit="sendPasswordResetLink" class="space-y-[18px]" novalidate>
|
|
|
|
<div>
|
|
<label class="field-label" for="auth-email">{{ __('E-Mail-Adresse') }}</label>
|
|
<input
|
|
id="auth-email"
|
|
type="email"
|
|
wire:model="email"
|
|
required
|
|
autofocus
|
|
autocomplete="username"
|
|
class="field-input"
|
|
placeholder="redaktion@ihr-unternehmen.de"
|
|
@error('email') aria-invalid="true" @enderror
|
|
/>
|
|
@error('email')
|
|
<p class="field-error">{{ $message }}</p>
|
|
@enderror
|
|
</div>
|
|
|
|
<button type="submit" class="auth-btn-primary !mt-[22px]" wire:loading.attr="disabled" wire:target="sendPasswordResetLink">
|
|
<span wire:loading.remove wire:target="sendPasswordResetLink">{{ __('Reset-Link senden') }}</span>
|
|
<span wire:loading wire:target="sendPasswordResetLink">{{ __('Link wird gesendet …') }}</span>
|
|
</button>
|
|
|
|
<div class="flex items-center justify-center pt-2 text-[12.5px] text-ink-3">
|
|
<a href="{{ route('login') }}" class="link-hub" wire:navigate>← {{ __('Zurück zur Anmeldung') }}</a>
|
|
</div>
|
|
</form>
|
|
</div>
|