E-Mail-Praeferenzen: Opt-out je Mail-Typ + One-Click-Unsubscribe ohne Login

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Kevin Adametz 2026-07-10 17:15:05 +00:00
parent 484e062264
commit d5b806443e
21 changed files with 746 additions and 9 deletions

View file

@ -1,5 +1,6 @@
<?php
use App\Enums\EmailNotificationType;
use App\Enums\VatIdCheckStatus;
use App\Models\User;
use App\Services\Billing\VatIdValidationService;
@ -60,6 +61,15 @@ new #[Layout('components.layouts.app'), Title('Mein Profil')] class extends Comp
public ?string $vatCheckMessage = null;
/**
* E-Mail-Benachrichtigungs-Präferenzen: Enum-Value => aktiviert?
* Spiegel der Opt-out-Tabelle (email_notification_opt_outs) — vor jedem
* Versand prüft das System, ob der Typ abbestellt wurde.
*
* @var array<string, bool>
*/
public array $emailNotifications = [];
public function mount(): void
{
$user = auth()->user();
@ -103,6 +113,12 @@ new #[Layout('components.layouts.app'), Title('Mein Profil')] class extends Comp
if (filled($this->taxIdNumber)) {
$this->refreshVatCheck();
}
$optedOutTypes = $user->emailNotificationOptOuts()->pluck('type')->all();
foreach (EmailNotificationType::cases() as $notificationType) {
$this->emailNotifications[$notificationType->value] = ! in_array($notificationType->value, $optedOutTypes, true);
}
}
/**
@ -254,6 +270,14 @@ new #[Layout('components.layouts.app'), Title('Mein Profil')] class extends Comp
);
}
foreach (EmailNotificationType::cases() as $notificationType) {
if ($this->emailNotifications[$notificationType->value] ?? true) {
$user->optIntoEmailNotification($notificationType);
} else {
$user->optOutOfEmailNotification($notificationType);
}
}
// Fokus-Fix: Nach dem Speichern soll kein Eingabefeld den Fokus
// erhalten (Browser/Autofill springen sonst auf das erste Feld).
$this->dispatch('profile-saved');
@ -522,7 +546,34 @@ new #[Layout('components.layouts.app'), Title('Mein Profil')] class extends Comp
</div>
</article>
{{-- ============== 4) EINSTELLUNGEN ============== --}}
{{-- ============== 4) E-MAIL-BENACHRICHTIGUNGEN ============== --}}
<article class="panel" id="e-mail-benachrichtigungen">
<div class="panel-head">
<span class="section-eyebrow">{{ __('E-Mail-Benachrichtigungen') }}</span>
</div>
<div class="p-5 space-y-4">
<p class="text-[12.5px] leading-[1.55] text-[color:var(--color-ink-3)] m-0 max-w-[680px]">
{{ __('Welche Benachrichtigungs-E-Mails möchten Sie erhalten? System-E-Mails wie Passwort-Zurücksetzen oder rechtlich erforderliche Bestätigungen sind hiervon ausgenommen und werden immer zugestellt.') }}
</p>
<div class="grid gap-4 md:grid-cols-2">
@foreach (\App\Enums\EmailNotificationType::cases() as $notificationType)
<flux:switch
wire:model="emailNotifications.{{ $notificationType->value }}"
align="right"
:label="$notificationType->label()"
:description="$notificationType->settingsDescription()"
/>
@endforeach
</div>
</div>
<div class="px-5 py-4 border-t border-[color:var(--color-bg-rule)] flex justify-end">
<flux:button type="submit" variant="primary">
{{ __('Speichern') }}
</flux:button>
</div>
</article>
{{-- ============== 5) EINSTELLUNGEN ============== --}}
<article class="panel">
<div class="panel-head">
<span class="section-eyebrow">{{ __('Einstellungen') }}</span>