diff --git a/app/Mail/MagicLoginLink.php b/app/Mail/MagicLoginLink.php index e9a38987..2565d736 100644 --- a/app/Mail/MagicLoginLink.php +++ b/app/Mail/MagicLoginLink.php @@ -22,7 +22,7 @@ class MagicLoginLink extends Mailable public function envelope(): Envelope { return new Envelope( - subject: 'Ihr Login-Link fuer pressekonto' + subject: 'Ihr Anmeldelink für Ihr pressekonto' ); } diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index 9dd28b1e..8589eb87 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -27,7 +27,10 @@ use Illuminate\Auth\Events\Login; use Illuminate\Auth\Events\Registered; use Illuminate\Auth\Events\Verified; use Illuminate\Auth\Listeners\SendEmailVerificationNotification; +use Illuminate\Auth\Notifications\ResetPassword; +use Illuminate\Auth\Notifications\VerifyEmail; use Illuminate\Database\Events\QueryExecuted; +use Illuminate\Notifications\Messages\MailMessage; use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Event; use Illuminate\Support\Facades\Gate; @@ -92,6 +95,33 @@ class AppServiceProvider extends ServiceProvider Event::listen(Registered::class, SendEmailVerificationNotification::class); Event::listen(Verified::class, ActivateUserAfterVerification::class); + // Framework-Auth-Mails (Passwort-Reset, E-Mail-Verifizierung) im + // pressekonto-CI rendern statt im Laravel-Standard-Markdown-Theme — + // gleiches Layout wie alle übrigen System-Mails (emails/layout). + ResetPassword::toMailUsing(function (User $user, string $token): MailMessage { + $resetUrl = url(route('password.reset', [ + 'token' => $token, + 'email' => $user->getEmailForPasswordReset(), + ], false)); + + return (new MailMessage) + ->subject('Passwort zurücksetzen für Ihr pressekonto') + ->view('emails.auth.reset-password', [ + 'user' => $user, + 'resetUrl' => $resetUrl, + 'expiresInMinutes' => (int) config('auth.passwords.'.config('auth.defaults.passwords').'.expire'), + ]); + }); + + VerifyEmail::toMailUsing(function (User $user, string $verificationUrl): MailMessage { + return (new MailMessage) + ->subject('E-Mail-Adresse bestätigen für Ihr pressekonto') + ->view('emails.auth.verify-email', [ + 'user' => $user, + 'verificationUrl' => $verificationUrl, + ]); + }); + // Bei jedem Login Firmen-/Kontakt-Zuordnungen (gleiche E-Mail) auffrischen. Event::listen(Login::class, SyncCompanyMembershipsOnLogin::class); diff --git a/resources/views/components/emails/button.blade.php b/resources/views/components/emails/button.blade.php new file mode 100644 index 00000000..aa67f25c --- /dev/null +++ b/resources/views/components/emails/button.blade.php @@ -0,0 +1,12 @@ +@props([ + 'url', +]) + +{{-- E-Mail-tauglicher Button im Hub-CI (Tabellen-Konstrukt für Outlook). --}} + + + + +
+ {{ $slot }} +
diff --git a/resources/views/components/emails/layout.blade.php b/resources/views/components/emails/layout.blade.php new file mode 100644 index 00000000..be9ee94e --- /dev/null +++ b/resources/views/components/emails/layout.blade.php @@ -0,0 +1,106 @@ +@props([ + 'title' => null, + 'preheader' => null, +]) + +@php + /** + * Zentrales CI-Layout für alle System-E-Mails (Auth, Pressemitteilungen, + * Kündigung/Widerruf, Kontakt). Absender-Marke ist immer pressekonto — + * der gemeinsame Publisher · Hub der Portale presseecho und + * businessportal24. Header und Footer erklären diese Zugehörigkeit, + * damit Empfänger die Mail sofort einordnen können. + * + * E-Mail-tauglich gebaut: Tabellen-Layout, Inline-Styles für alles + * Layout-Kritische, ergänzende Klassen im + + + + @if ($preheader) + {{-- Vorschautext im Posteingang; im Mail-Body unsichtbar. --}} +
{{ $preheader }}
+ @endif + + + + + +
+ + + {{-- Bernstein-Akzentstreifen (CI: 3px-Streifen des Hubs) --}} + + + + + {{-- Header: Wortmarke + Publisher · Hub in Hub-Blau --}} + + + + + {{-- Inhalt --}} + + + + + {{-- Brand-Familie: Zugehörigkeit von presseecho + businessportal24 --}} + + + +
 
+ {{ $brand['name'] ?? 'presse' }}{{ $brand['accent'] ?? 'konto' }} +
+ {{ $taglineShort }} +
+
+ pressekonto + ist der gemeinsame Publisher · Hub für + presseecho + und + businessportal24 + — ein Konto, redaktionell geprüft, auf beiden Portalen veröffentlichen. +
+ + {{-- Rechtliches unterhalb der Karte --}} + + + + +
+ {{ $footerLegal }} +
+
+ + diff --git a/resources/views/emails/auth/go-live-password-reset.blade.php b/resources/views/emails/auth/go-live-password-reset.blade.php index ec81ac61..0c0912c4 100644 --- a/resources/views/emails/auth/go-live-password-reset.blade.php +++ b/resources/views/emails/auth/go-live-password-reset.blade.php @@ -1,15 +1,4 @@ - - - - - Bitte Passwort setzen – {{ config('app.name') }} - - - +

Hallo {{ $user->name }},

@@ -18,9 +7,9 @@ um sich künftig einzuloggen.

-

Klicken Sie auf den folgenden Link, um Ihr Passwort festzulegen:

+

Klicken Sie auf den folgenden Button, um Ihr Passwort festzulegen:

-

Passwort jetzt setzen

+ Passwort jetzt setzen

Alternativ können Sie diesen Link in Ihren Browser kopieren:
@@ -35,5 +24,4 @@

Bei Fragen erreichen Sie uns unter {{ config('mail.from.address') }}.

- - +
diff --git a/resources/views/emails/auth/magic-login-link.blade.php b/resources/views/emails/auth/magic-login-link.blade.php index 453ad3a9..2fe41713 100644 --- a/resources/views/emails/auth/magic-login-link.blade.php +++ b/resources/views/emails/auth/magic-login-link.blade.php @@ -1,20 +1,13 @@ - - - - - Ihr Login-Link - - +

Hallo {{ $user->name }},

-

hier ist Ihr Einmal-Link fuer den Login in Ihr Kundenkonto.

+

hier ist Ihr Einmal-Link für den Login in Ihr Kundenkonto.

-

- Jetzt einloggen -

+ Jetzt einloggen -

Der Link ist gueltig bis {{ $expiresAt }} Uhr und kann nur einmal verwendet werden.

+

Der Link ist gültig bis {{ $expiresAt }} Uhr und kann nur einmal verwendet werden.

-

Falls Sie den Login nicht angefordert haben, ignorieren Sie diese E-Mail bitte.

- - +
+

Falls Sie den Login nicht angefordert haben, ignorieren Sie diese E-Mail bitte.

+
+
diff --git a/resources/views/emails/auth/reset-password.blade.php b/resources/views/emails/auth/reset-password.blade.php new file mode 100644 index 00000000..6ca33cfa --- /dev/null +++ b/resources/views/emails/auth/reset-password.blade.php @@ -0,0 +1,23 @@ + +

Hallo {{ $user->name }},

+ +

+ Sie erhalten diese E-Mail, weil für Ihr Konto ein neues Passwort angefordert wurde. + Klicken Sie auf den folgenden Button, um ein neues Passwort zu vergeben: +

+ + Neues Passwort vergeben + +

+ Alternativ können Sie diesen Link in Ihren Browser kopieren:
+ {{ $resetUrl }} +

+ +
+

Der Link ist {{ $expiresInMinutes }} Minuten gültig.

+

+ Falls Sie kein neues Passwort angefordert haben, ignorieren Sie diese E-Mail bitte — + Ihr Passwort bleibt unverändert. +

+
+
diff --git a/resources/views/emails/auth/verify-email.blade.php b/resources/views/emails/auth/verify-email.blade.php new file mode 100644 index 00000000..361b221b --- /dev/null +++ b/resources/views/emails/auth/verify-email.blade.php @@ -0,0 +1,19 @@ + +

Hallo {{ $user->name }},

+ +

+ vielen Dank für Ihre Registrierung. Bitte bestätigen Sie Ihre E-Mail-Adresse, + um Ihr Konto zu aktivieren: +

+ + E-Mail-Adresse bestätigen + +

+ Alternativ können Sie diesen Link in Ihren Browser kopieren:
+ {{ $verificationUrl }} +

+ +
+

Falls Sie kein Konto erstellt haben, ignorieren Sie diese E-Mail bitte.

+
+
diff --git a/resources/views/emails/contact/submitted.blade.php b/resources/views/emails/contact/submitted.blade.php index bdd5aee9..aa456496 100644 --- a/resources/views/emails/contact/submitted.blade.php +++ b/resources/views/emails/contact/submitted.blade.php @@ -1,18 +1,4 @@ - - - - - Neue Kontaktanfrage - - - +

Über das Kontaktformular auf {{ $host }} ist eine neue Anfrage eingegangen:

@@ -24,6 +10,7 @@
{{ $messageText }}
-

Antworten gehen direkt an die Absenderadresse (Reply-To).

- - +
+

Antworten gehen direkt an die Absenderadresse (Reply-To).

+
+ diff --git a/resources/views/emails/press-release/published.blade.php b/resources/views/emails/press-release/published.blade.php index 8de0fff8..5457dc81 100644 --- a/resources/views/emails/press-release/published.blade.php +++ b/resources/views/emails/press-release/published.blade.php @@ -1,23 +1,11 @@ - - - - - Ihre Pressemitteilung wurde veröffentlicht - - - +

Hallo {{ $user->name }},

Ihre Pressemitteilung wurde geprüft und erfolgreich veröffentlicht.

{{ $pressRelease->title }}

-

Pressemitteilung ansehen

+ Pressemitteilung ansehen @if($pressRelease->published_at)

Veröffentlicht am: {{ $pressRelease->published_at->format('d.m.Y H:i') }} Uhr

@@ -26,5 +14,4 @@

Bei Fragen erreichen Sie uns unter {{ config('mail.from.address') }}.

- - +
diff --git a/resources/views/emails/press-release/rejected.blade.php b/resources/views/emails/press-release/rejected.blade.php index fa3e9d8a..696b61a7 100644 --- a/resources/views/emails/press-release/rejected.blade.php +++ b/resources/views/emails/press-release/rejected.blade.php @@ -1,17 +1,4 @@ - - - - - Ihre Pressemitteilung wurde abgelehnt - - - +

Hallo {{ $user->name }},

Ihre eingereichte Pressemitteilung konnte leider nicht veröffentlicht werden.

@@ -19,7 +6,7 @@

{{ $pressRelease->title }}

@if($reason) -
+
Ablehnungsgrund:
{{ $reason }}
@@ -27,10 +14,9 @@

Bitte überarbeiten Sie den Inhalt und reichen Sie die Pressemitteilung erneut ein.

@endif -

Pressemitteilung bearbeiten

+ Pressemitteilung bearbeiten

Bei Fragen erreichen Sie uns unter {{ config('mail.from.address') }}.

- - + diff --git a/resources/views/emails/termination/received.blade.php b/resources/views/emails/termination/received.blade.php index 913ef68f..b05fc3c1 100644 --- a/resources/views/emails/termination/received.blade.php +++ b/resources/views/emails/termination/received.blade.php @@ -1,17 +1,4 @@ - - - - - Eingangsbestätigung Ihrer Kündigung - - - +

Guten Tag {{ $terminationRequest->name }},

@@ -46,10 +33,11 @@ schnellstmöglich den Zeitpunkt, zu dem das Vertragsverhältnis endet.

-

- Diese Bestätigung wurde automatisch erstellt und dokumentiert Inhalt sowie Datum - und Uhrzeit des Zugangs Ihrer Kündigungserklärung (§ 312k BGB). - Bitte bewahren Sie sie auf. -

- - +
+

+ Diese Bestätigung wurde automatisch erstellt und dokumentiert Inhalt sowie Datum + und Uhrzeit des Zugangs Ihrer Kündigungserklärung (§ 312k BGB). + Bitte bewahren Sie sie auf. +

+
+
diff --git a/resources/views/emails/termination/submitted.blade.php b/resources/views/emails/termination/submitted.blade.php index f92d0638..7a69169e 100644 --- a/resources/views/emails/termination/submitted.blade.php +++ b/resources/views/emails/termination/submitted.blade.php @@ -1,20 +1,7 @@ - - - - - Neue Online-Kündigung - - - +

Über den Kündigungsbutton auf {{ $terminationRequest->host }} ist eine neue Kündigung eingegangen:

-
+
Vorgangsnummer
K-{{ $terminationRequest->id }}
Art der Kündigung
@@ -35,9 +22,10 @@
{{ $terminationRequest->created_at->format('d.m.Y H:i') }} Uhr
-

- Die Zugangsbestätigung an den Verbraucher wurde automatisch versendet (§ 312k Abs. 4 BGB). - Bitte den Vorgang dem Vertrag zuordnen und dem Kunden den Beendigungszeitpunkt bestätigen. -

- - +
+

+ Die Zugangsbestätigung an den Verbraucher wurde automatisch versendet (§ 312k Abs. 4 BGB). + Bitte den Vorgang dem Vertrag zuordnen und dem Kunden den Beendigungszeitpunkt bestätigen. +

+
+ diff --git a/resources/views/emails/withdrawal/received.blade.php b/resources/views/emails/withdrawal/received.blade.php index 22f97519..8d1d060e 100644 --- a/resources/views/emails/withdrawal/received.blade.php +++ b/resources/views/emails/withdrawal/received.blade.php @@ -1,17 +1,4 @@ - - - - - Eingangsbestätigung Ihres Widerrufs - - - +

Guten Tag {{ $withdrawalRequest->name }},

@@ -38,9 +25,10 @@ schnellstmöglich mit den weiteren Schritten (z. B. zur Rückabwicklung) bei Ihnen.

-

- Diese Bestätigung wurde automatisch erstellt und dokumentiert den Zeitpunkt - des Eingangs Ihrer Widerrufserklärung. Bitte bewahren Sie sie auf. -

- - +
+

+ Diese Bestätigung wurde automatisch erstellt und dokumentiert den Zeitpunkt + des Eingangs Ihrer Widerrufserklärung. Bitte bewahren Sie sie auf. +

+
+
diff --git a/resources/views/emails/withdrawal/submitted.blade.php b/resources/views/emails/withdrawal/submitted.blade.php index b9ade2f8..2907ce04 100644 --- a/resources/views/emails/withdrawal/submitted.blade.php +++ b/resources/views/emails/withdrawal/submitted.blade.php @@ -1,20 +1,7 @@ - - - - - Neuer Online-Widerruf - - - +

Über den Widerrufsbutton auf {{ $withdrawalRequest->host }} ist ein neuer Widerruf eingegangen:

-
+
Vorgangsnummer
W-{{ $withdrawalRequest->id }}
Name
@@ -27,10 +14,11 @@
{{ $withdrawalRequest->created_at->format('d.m.Y H:i') }} Uhr
-

- Die Eingangsbestätigung an den Verbraucher wurde automatisch versendet. - Bitte den Vorgang dem Vertrag zuordnen und die Rückabwicklung anstoßen - (gesetzliche Frist beachten). -

- - +
+

+ Die Eingangsbestätigung an den Verbraucher wurde automatisch versendet. + Bitte den Vorgang dem Vertrag zuordnen und die Rückabwicklung anstoßen + (gesetzliche Frist beachten). +

+
+ diff --git a/tests/Feature/Mail/EmailBrandingTest.php b/tests/Feature/Mail/EmailBrandingTest.php new file mode 100644 index 00000000..9b99d8b8 --- /dev/null +++ b/tests/Feature/Mail/EmailBrandingTest.php @@ -0,0 +1,100 @@ +toContain('Publisher · Hub') + ->toContain('presseecho') + ->toContain('businessportal24') + ->toContain('ist der gemeinsame Publisher') + ->toContain('#1A2540') + ->toContain('#B07A3A'); +} + +test('the magic login link mail uses the hub ci layout', function () { + $user = User::factory()->make(); + + assertHubEmailBranding((new MagicLoginLink($user, 'https://pressekonto.test/magic-login/abc', '12.07.2026 10:00'))->render()); +}); + +test('the go-live password reset mail uses the hub ci layout', function () { + $user = User::factory()->make(); + + assertHubEmailBranding((new GoLivePasswordReset($user, 'https://pressekonto.test/reset-password/abc'))->render()); +}); + +test('the press release published and rejected mails use the hub ci layout', function () { + $pressRelease = PressRelease::factory()->published()->create(); + $user = $pressRelease->user; + + assertHubEmailBranding((new PressReleasePublished($user, $pressRelease))->render()); + assertHubEmailBranding((new PressReleaseRejected($user, $pressRelease, 'Bitte Quellen ergänzen.'))->render()); +}); + +test('the termination mails use the hub ci layout', function () { + $terminationRequest = TerminationRequest::factory()->create(); + + assertHubEmailBranding((new TerminationRequestReceived($terminationRequest))->render()); + assertHubEmailBranding((new TerminationRequestSubmitted($terminationRequest))->render()); +}); + +test('the withdrawal mails use the hub ci layout', function () { + $withdrawalRequest = WithdrawalRequest::factory()->create(); + + assertHubEmailBranding((new WithdrawalRequestReceived($withdrawalRequest))->render()); + assertHubEmailBranding((new WithdrawalRequestSubmitted($withdrawalRequest))->render()); +}); + +test('the contact request mail uses the hub ci layout', function () { + assertHubEmailBranding( + (new ContactRequestSubmitted('Paula Presse', 'paula@example.test', 'Eine Frage zur Plattform.', 'pressekonto.test'))->render() + ); +}); + +test('the password reset notification renders in the hub ci layout', function () { + $user = User::factory()->create(); + + $mailMessage = (new ResetPassword('test-token'))->toMail($user); + + expect($mailMessage->subject)->toBe('Passwort zurücksetzen für Ihr pressekonto'); + + $html = (string) $mailMessage->render(); + + assertHubEmailBranding($html); + expect($html)->toContain('/reset-password/test-token'); +}); + +test('the email verification notification renders in the hub ci layout', function () { + $user = User::factory()->unverified()->create(); + + $mailMessage = (new VerifyEmail)->toMail($user); + + expect($mailMessage->subject)->toBe('E-Mail-Adresse bestätigen für Ihr pressekonto'); + + $html = (string) $mailMessage->render(); + + assertHubEmailBranding($html); + expect($html)->toContain('/verify-email/'); +});