From dc3eb68861b027d845b64f749c24d551affa24f9 Mon Sep 17 00:00:00 2001
From: philipp lang <philipp@aweos.de>
Date: Sun, 8 Dec 2024 23:38:03 +0100
Subject: [PATCH] Add Password Reset email page

---
 modules/Auth/AuthServiceProvider.php          |  3 +
 modules/Auth/Components/LoginForm.php         |  2 +-
 modules/Auth/Components/LoginFormTest.php     |  3 +-
 modules/Auth/Components/PasswordReset.php     | 66 +++++++++++++++++++
 modules/Auth/Components/PasswordResetTest.php | 53 +++++++++++++++
 5 files changed, 125 insertions(+), 2 deletions(-)
 create mode 100644 modules/Auth/Components/PasswordReset.php
 create mode 100644 modules/Auth/Components/PasswordResetTest.php

diff --git a/modules/Auth/AuthServiceProvider.php b/modules/Auth/AuthServiceProvider.php
index 85e58e7d..0755bc9d 100644
--- a/modules/Auth/AuthServiceProvider.php
+++ b/modules/Auth/AuthServiceProvider.php
@@ -5,6 +5,7 @@ namespace Modules\Auth;
 use Illuminate\Routing\Router;
 use Illuminate\Support\ServiceProvider;
 use Modules\Auth\Components\LoginForm;
+use Modules\Auth\Components\PasswordReset;
 
 class AuthServiceProvider extends ServiceProvider
 {
@@ -26,6 +27,8 @@ class AuthServiceProvider extends ServiceProvider
     {
         app(Router::class)->middleware(['web', 'guest'])->group(function ($router) {
             $router->get('/login', LoginForm::class)->name('login');
+            $router->get('/password/reset', PasswordReset::class)->name('password.request');
+            $router->get('/password/reseta', PasswordReset::class)->name('password.reset');
         });
     }
 }
diff --git a/modules/Auth/Components/LoginForm.php b/modules/Auth/Components/LoginForm.php
index 3a6d2f8f..e95a90f0 100644
--- a/modules/Auth/Components/LoginForm.php
+++ b/modules/Auth/Components/LoginForm.php
@@ -52,7 +52,7 @@ class LoginForm extends Component
                         <x-form::text name="password" wire:model="password" type="password" label="Passwort"></x-form::text>
                         <x-ui::button type="submit">Login</x-ui::button>
                         <div class="flex justify-center">
-                            <button type="button" class="text-gray-500 text-sm hover:text-gray-300" @click.prevent="$inertia.visit('/password/reset')">Passwort vergessen?</button>
+                            <a href="{{route('password.request')}}" class="text-gray-500 text-sm hover:text-gray-300">Passwort vergessen?</a>
                         </div>
                     </div>
                 </form>
diff --git a/modules/Auth/Components/LoginFormTest.php b/modules/Auth/Components/LoginFormTest.php
index 318598cd..923284a8 100644
--- a/modules/Auth/Components/LoginFormTest.php
+++ b/modules/Auth/Components/LoginFormTest.php
@@ -23,7 +23,8 @@ it('displays component', function () {
 it('displays form', function () {
     Livewire::test(LoginForm::class)
         ->assertSee('Login')
-        ->assertSee('Passwort vergessen');
+        ->assertSee('Passwort vergessen')
+        ->assertSee(route('password.request'));
 });
 
 it('loggs in', function () {
diff --git a/modules/Auth/Components/PasswordReset.php b/modules/Auth/Components/PasswordReset.php
new file mode 100644
index 00000000..d69c8237
--- /dev/null
+++ b/modules/Auth/Components/PasswordReset.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Modules\Auth\Components;
+
+use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
+use Illuminate\Http\Request;
+use Illuminate\Validation\ValidationException;
+use Livewire\Attributes\Layout;
+use Livewire\Component;
+
+class PasswordReset extends Component
+{
+
+    use SendsPasswordResetEmails;
+
+    public string $email = '';
+
+    protected function validateEmail(Request $request)
+    {
+        $this->validate([
+            'email' => 'required|max:255|string|email',
+        ]);
+    }
+
+    protected function sendResetLinkFailedResponse(Request $request, $response)
+    {
+        throw ValidationException::withMessages([
+            'email' => [trans($response)],
+        ]);
+    }
+
+    protected function credentials(Request $request)
+    {
+        return ['email' => $this->email];
+    }
+
+    public function submit()
+    {
+        $this->sendResetLinkEmail(request());
+        $this->dispatch('success', 'Du hast weitere Instruktionen per E-Mail erhalten.');
+    }
+
+    #[Layout('components.layouts.full')]
+    public function render(): string
+    {
+        return <<<'HTML'
+            <x-page::full heading="Passwort vergessen" title="Passwort vergessen">
+                <form wire:submit.prevent="submit">
+                    <div class="grid gap-5">
+                        <span class="text-gray-500 text-sm"
+                            >Hier kannst du dein Passwort zurücksetzen.<br />
+                            Gebe dafür die E-Mail-Adresse deines Benutzerkontos ein.<br />
+                            Anschließend bekommst du eine E-Mail<br />
+                            mit weiteren Anweisungen.</span
+                        >
+                        <x-form::text name="email" wire:model="email" label="E-Mail-Adresse"></x-form::text>
+                        <x-ui::button type="submit">Passwort zurücksetzen</x-ui::button>
+                        <div class="flex justify-center">
+                            <a href="/login" class="text-gray-500 text-sm hover:text-gray-300">Zurück zum Login</a>
+                        </div>
+                    </div>
+                </form>
+            </x-page::full>
+        HTML;
+    }
+}
diff --git a/modules/Auth/Components/PasswordResetTest.php b/modules/Auth/Components/PasswordResetTest.php
new file mode 100644
index 00000000..8982260e
--- /dev/null
+++ b/modules/Auth/Components/PasswordResetTest.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace Modules\Auth\Components;
+
+use App\Auth\ResetPassword;
+use App\User;
+use Illuminate\Auth\Events\Lockout;
+use Tests\TestCase;
+use Illuminate\Foundation\Testing\DatabaseTransactions;
+use Illuminate\Support\Facades\Event;
+use Illuminate\Support\Facades\Mail;
+use Illuminate\Support\Facades\Notification;
+use Livewire\Livewire;
+
+uses(TestCase::class);
+uses(DatabaseTransactions::class);
+
+it('displays component', function () {
+    test()->get(route('password.request'))->assertSeeLivewire(PasswordReset::class)->assertDontSee('Dashboard');
+});
+
+it('shows intro', function () {
+    Livewire::test(PasswordReset::class)->assertSee('Hier kannst du dein Passwort zurücksetzen.');
+});
+
+it('it needs email address', function (string $email, string $error) {
+    Livewire::test(PasswordReset::class)
+        ->set('email', $email)
+        ->call('submit')
+        ->assertHasErrors(['email' => $error]);
+})
+    ->with([
+        ['', 'E-Mail Adresse ist erforderlich.'],
+        ['aaa', 'E-Mail Adresse muss eine gültige E-Mail-Adresse sein.'],
+    ]);
+
+it('displays error when user doesnt exsst', function () {
+    Livewire::test(PasswordReset::class)
+        ->set('email', 'nowhere@example.com')
+        ->call('submit')
+        ->assertHasErrors(['email' => 'Es konnte leider kein Nutzer mit dieser E-Mail-Adresse gefunden werden.']);
+});
+
+it('requests link', function () {
+    Notification::fake();
+    $user = User::factory()->loginData('admin@example.com', 'secret')->create();
+    Livewire::test(PasswordReset::class)
+        ->set('email', 'admin@example.com')
+        ->call('submit')
+        ->assertDispatched('success', 'Du hast weitere Instruktionen per E-Mail erhalten.');
+
+    Notification::assertSentTo($user, ResetPassword::class);
+});