From eedda5766b26e6f13a6222f75dc336361ad6206c Mon Sep 17 00:00:00 2001 From: ImMocha Date: Fri, 20 Jun 2025 18:05:25 +0800 Subject: [PATCH] add feature: reset password --- .../Auth/ForgotPasswordController.php | 53 ++++++++ .../Auth/ResetPasswordController.php | 81 ++++++++++++ app/Models/User.php | 12 ++ .../ResetPasswordNotification.php | 125 ++++++++++++++++++ .../views/auth/forgot_password.blade.php | 45 +++++++ resources/views/auth/login.blade.php | 4 + resources/views/auth/reset_password.blade.php | 65 +++++++++ .../views/emails/reset_password.blade.php | 105 +++++++++++++++ routes/web.php | 10 ++ 9 files changed, 500 insertions(+) create mode 100644 app/Http/Controllers/Auth/ForgotPasswordController.php create mode 100644 app/Http/Controllers/Auth/ResetPasswordController.php create mode 100644 app/Notifications/ResetPasswordNotification.php create mode 100644 resources/views/auth/forgot_password.blade.php create mode 100644 resources/views/auth/reset_password.blade.php create mode 100644 resources/views/emails/reset_password.blade.php diff --git a/app/Http/Controllers/Auth/ForgotPasswordController.php b/app/Http/Controllers/Auth/ForgotPasswordController.php new file mode 100644 index 0000000..acfe4e7 --- /dev/null +++ b/app/Http/Controllers/Auth/ForgotPasswordController.php @@ -0,0 +1,53 @@ +middleware('guest'); + } + + /** + * Menampilkan form lupa password + */ + public function showLinkRequestForm() + { + return view('auth.forgot_password'); + } + + /** + * Mengirim email reset password + */ + public function sendResetLinkEmail(Request $request) + { + $validator = Validator::make($request->all(), [ + 'email' => 'required|email|exists:users,email', + ], [ + 'email.required' => 'Email wajib diisi.', + 'email.email' => 'Format email tidak valid.', + 'email.exists' => 'Email tidak ditemukan dalam sistem kami.', + ]); + + if ($validator->fails()) { + return back()->withErrors($validator)->withInput(); + } + + // Mengirim email reset password + $status = Password::sendResetLink( + $request->only('email') + ); + + if ($status === Password::RESET_LINK_SENT) { + return back()->with('status', 'Link reset password telah dikirim ke email Anda!'); + } + + return back()->withErrors(['email' => 'Terjadi kesalahan saat mengirim email reset password.']); + } +} \ No newline at end of file diff --git a/app/Http/Controllers/Auth/ResetPasswordController.php b/app/Http/Controllers/Auth/ResetPasswordController.php new file mode 100644 index 0000000..9c3a8b3 --- /dev/null +++ b/app/Http/Controllers/Auth/ResetPasswordController.php @@ -0,0 +1,81 @@ +middleware('guest'); + } + + /** + * Menampilkan form reset password + */ + public function showResetForm(Request $request, $token = null) + { + return view('auth.reset_password')->with([ + 'token' => $token, + 'email' => $request->email + ]); + } + + /** + * Memproses reset password + */ + public function reset(Request $request) + { + $request->validate([ + 'token' => 'required', + 'email' => 'required|email', + 'password' => 'required|min:8|confirmed', + ], [ + 'token.required' => 'Token reset password diperlukan.', + 'email.required' => 'Email wajib diisi.', + 'email.email' => 'Format email tidak valid.', + 'password.required' => 'Password wajib diisi.', + 'password.min' => 'Password minimal 8 karakter.', + 'password.confirmed' => 'Konfirmasi password tidak sesuai.', + ]); + + $status = Password::reset( + $request->only('email', 'password', 'password_confirmation', 'token'), + function (User $user, string $password) { + $user->forceFill([ + 'password' => Hash::make($password) + ])->setRememberToken(Str::random(60)); + + $user->save(); + } + ); + + if ($status === Password::PASSWORD_RESET) { + return redirect()->route('login')->with('status', 'Password berhasil direset! Silakan login dengan password baru Anda.'); + } + + return back()->withErrors(['email' => $this->getResetErrorMessage($status)]); + } + + /** + * Mendapatkan pesan error untuk reset password + */ + private function getResetErrorMessage($status) + { + switch ($status) { + case Password::INVALID_TOKEN: + return 'Token reset password tidak valid.'; + case Password::INVALID_USER: + return 'Email tidak ditemukan.'; + default: + return 'Terjadi kesalahan saat mereset password.'; + } + } +} \ No newline at end of file diff --git a/app/Models/User.php b/app/Models/User.php index 9e8a0cb..a8f8b76 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -7,6 +7,7 @@ use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Sanctum\HasApiTokens; +use App\Notifications\ResetPasswordNotification; class User extends Authenticatable { @@ -65,4 +66,15 @@ public function satisfactions() { return $this->hasMany(Satisfaction::class); } + + /** + * Send the password reset notification. + * + * @param string $token + * @return void + */ + public function sendPasswordResetNotification($token) + { + $this->notify(new ResetPasswordNotification($token)); + } } diff --git a/app/Notifications/ResetPasswordNotification.php b/app/Notifications/ResetPasswordNotification.php new file mode 100644 index 0000000..49376aa --- /dev/null +++ b/app/Notifications/ResetPasswordNotification.php @@ -0,0 +1,125 @@ +token = $token; + } + + /** + * Get the notification's delivery channels. + * + * @param mixed $notifiable + * @return array + */ + public function via($notifiable) + { + return ['mail']; + } + + /** + * Get the mail representation of the notification. + * + * @param mixed $notifiable + * @return \Illuminate\Notifications\Messages\MailMessage + */ + public function toMail($notifiable) + { + if (static::$toMailCallback) { + return call_user_func(static::$toMailCallback, $notifiable, $this->token); + } + + return $this->buildMailMessage($this->resetUrl($notifiable)); + } + + /** + * Get the reset password notification mail message for the given URL. + * + * @param string $url + * @return \Illuminate\Notifications\Messages\MailMessage + */ + protected function buildMailMessage($url) + { + return (new MailMessage) + ->subject('Reset Password - Syeba Air') + ->view('emails.reset_password', ['actionUrl' => $url]); + } + + /** + * Get the reset URL for the given notifiable. + * + * @param mixed $notifiable + * @return string + */ + protected function resetUrl($notifiable) + { + if (static::$createUrlCallback) { + return call_user_func(static::$createUrlCallback, $notifiable, $this->token); + } + + return url(route('password.reset', [ + 'token' => $this->token, + 'email' => $notifiable->getEmailForPasswordReset(), + ], false)); + } + + /** + * Set a callback that should be used when creating the reset password button URL. + * + * @param \Closure $callback + * @return void + */ + public static function createUrlUsing($callback) + { + static::$createUrlCallback = $callback; + } + + /** + * Set a callback that should be used when building the notification mail message. + * + * @param \Closure $callback + * @return void + */ + public static function toMailUsing($callback) + { + static::$toMailCallback = $callback; + } +} \ No newline at end of file diff --git a/resources/views/auth/forgot_password.blade.php b/resources/views/auth/forgot_password.blade.php new file mode 100644 index 0000000..40d643c --- /dev/null +++ b/resources/views/auth/forgot_password.blade.php @@ -0,0 +1,45 @@ +@extends('layouts.auth') + +@section('title', 'Lupa Password') + +@section('content') +

LUPA PASSWORD

+ + @if (session('status')) +
+ {{ session('status') }} +
+ @endif + + @if ($errors->any()) +
+ @foreach ($errors->all() as $error) +
{{ $error }}
+ @endforeach +
+ @endif + +

+ Masukkan email Anda dan kami akan mengirimkan link untuk mereset password. +

+ +
+ @csrf + +
+ + @error('email') + + {{ $message }} + + @enderror +
+ + +
+ + +@endsection \ No newline at end of file diff --git a/resources/views/auth/login.blade.php b/resources/views/auth/login.blade.php index 007818f..1a57182 100644 --- a/resources/views/auth/login.blade.php +++ b/resources/views/auth/login.blade.php @@ -41,6 +41,10 @@ +
+ Lupa Password? +
+ diff --git a/resources/views/auth/reset_password.blade.php b/resources/views/auth/reset_password.blade.php new file mode 100644 index 0000000..af7a153 --- /dev/null +++ b/resources/views/auth/reset_password.blade.php @@ -0,0 +1,65 @@ +@extends('layouts.auth') + +@section('title', 'Reset Password') + +@section('content') +

RESET PASSWORD

+ + @if ($errors->any()) +
+ @foreach ($errors->all() as $error) +
{{ $error }}
+ @endforeach +
+ @endif + +
+ @csrf + + + +
+ + @error('email') + + {{ $message }} + + @enderror +
+ +
+ + + + + @error('password') + + {{ $message }} + + @enderror +
+ +
+ + + + + @error('password_confirmation') + + {{ $message }} + + @enderror +
+ + +
+ + +@endsection \ No newline at end of file diff --git a/resources/views/emails/reset_password.blade.php b/resources/views/emails/reset_password.blade.php new file mode 100644 index 0000000..841091d --- /dev/null +++ b/resources/views/emails/reset_password.blade.php @@ -0,0 +1,105 @@ + + + + + + Reset Password - Syeba Air + + + +
+
+ +

Reset Password

+
+ +
+

Halo,

+ +

Kami menerima permintaan untuk mereset password akun Anda. Jika Anda yang melakukan permintaan ini, silakan klik tombol di bawah untuk mereset password Anda:

+ +
+ RESET PASSWORD +
+ +
+ Penting: Link ini akan kadaluarsa dalam 60 menit untuk keamanan akun Anda. +
+ +

Jika tombol di atas tidak berfungsi, Anda dapat menyalin dan menempelkan URL berikut ke browser Anda:

+

{{ $actionUrl }}

+ +

Jika Anda tidak meminta reset password, silakan abaikan email ini. Password Anda tidak akan berubah.

+
+ + +
+ + \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index a8e035a..c3706e9 100644 --- a/routes/web.php +++ b/routes/web.php @@ -6,6 +6,8 @@ use App\Http\Controllers\SatisfactionController; use App\Http\Controllers\Auth\LoginController; use App\Http\Controllers\Auth\RegisterController; +use App\Http\Controllers\Auth\ForgotPasswordController; +use App\Http\Controllers\Auth\ResetPasswordController; use App\Http\Controllers\Admin\AdminController; use App\Http\Controllers\ProfileController; use App\Http\Controllers\CustomerController; @@ -43,6 +45,14 @@ // Register Customer Route::get('/register', [RegisterController::class, 'showRegistrationForm'])->name('register'); Route::post('/register', [RegisterController::class, 'register']); + + // Forgot Password + Route::get('/forgot-password', [ForgotPasswordController::class, 'showLinkRequestForm'])->name('password.request'); + Route::post('/forgot-password', [ForgotPasswordController::class, 'sendResetLinkEmail'])->name('password.email'); + + // Reset Password + Route::get('/reset-password/{token}', [ResetPasswordController::class, 'showResetForm'])->name('password.reset'); + Route::post('/reset-password', [ResetPasswordController::class, 'reset'])->name('password.update'); }); // Logout