pendingRegistrationData($request); if ($pendingRegistration === null) { return redirect() ->route('register') ->withErrors(['email' => 'Silakan isi formulir registrasi terlebih dahulu.']); } return view('auth.verify-register-otp', [ 'pendingEmail' => $pendingRegistration['email'], 'expiresAt' => $pendingRegistration['otp_expires_at'], ]); } public function login(Request $request): RedirectResponse { $request->validate([ 'login' => ['required', 'string'], 'password' => ['required', 'string'], ]); $loginInput = $request->input('login'); $field = filter_var($loginInput, FILTER_VALIDATE_EMAIL) ? 'email' : 'username'; if (! Auth::attempt([$field => $loginInput, 'password' => $request->input('password')], $request->boolean('remember'))) { throw ValidationException::withMessages([ 'login' => 'Email/username atau password tidak valid.', ]); } $request->session()->regenerate(); /** @var User $user */ $user = $request->user(); $this->recordLoginHistory($request, $user, $loginInput); return $this->redirectAfterLogin($request, $user) ->with('status', 'Login berhasil.'); } public function register(Request $request): RedirectResponse { $validated = $request->validate([ 'name' => ['required', 'string', 'max:100'], 'username' => ['required', 'string', 'max:50', 'alpha_dash', 'unique:users,username'], 'email' => ['required', 'email', 'max:100', 'unique:users,email'], 'password' => ['required', 'string', 'min:8', 'confirmed'], ]); try { $this->issueRegistrationOtp($request, [ 'name' => $validated['name'], 'username' => $validated['username'], 'email' => $validated['email'], 'password' => Hash::make($validated['password']), ]); } catch (Throwable) { return back() ->withInput($request->except(['password', 'password_confirmation'])) ->withErrors(['email' => 'Kode OTP gagal dikirim ke email. Pastikan konfigurasi Gmail SMTP sudah aktif lalu coba lagi.']); } return redirect() ->route('register.verify') ->with('status', 'Kode OTP sudah dikirim ke email Anda. Masukkan 6 digit OTP untuk menyelesaikan registrasi.'); } public function verifyRegisterOtp(Request $request): RedirectResponse { $validated = $request->validate([ 'otp' => ['required', 'digits:6'], ]); $pendingRegistration = $this->pendingRegistrationData($request); if ($pendingRegistration === null) { return redirect() ->route('register') ->withErrors(['email' => 'Sesi OTP registrasi sudah habis. Silakan daftar ulang.']); } if (now()->timestamp > $pendingRegistration['otp_expires_at']) { return back()->withErrors([ 'otp' => 'Kode OTP sudah kedaluwarsa. Silakan kirim ulang OTP.', ]); } if (! Hash::check($validated['otp'], $pendingRegistration['otp_hash'])) { throw ValidationException::withMessages([ 'otp' => 'Kode OTP tidak valid.', ]); } if ( User::query()->where('email', '=', $pendingRegistration['email'])->exists() || User::query()->where('username', '=', $pendingRegistration['username'])->exists() ) { $this->clearPendingRegistration($request); return redirect() ->route('register') ->withErrors(['email' => 'Email atau username sudah digunakan. Silakan registrasi ulang dengan data yang berbeda.']); } $user = User::create([ 'name' => $pendingRegistration['name'], 'username' => $pendingRegistration['username'], 'email' => $pendingRegistration['email'], 'password' => $pendingRegistration['password'], 'role' => 'user', 'member_level' => 'tanpa_member', 'email_verified_at' => now(), ]); $this->clearPendingRegistration($request); Auth::login($user); $request->session()->regenerate(); $this->recordLoginHistory($request, $user, $user->email); return redirect() ->route('profile') ->with('status', 'Registrasi berhasil. Email sudah terverifikasi dan Anda sudah masuk ke akun.'); } public function resendRegisterOtp(Request $request): RedirectResponse { $pendingRegistration = $this->pendingRegistrationData($request); if ($pendingRegistration === null) { return redirect() ->route('register') ->withErrors(['email' => 'Sesi OTP registrasi sudah habis. Silakan daftar ulang.']); } if ( isset($pendingRegistration['otp_sent_at']) && (now()->timestamp - (int) $pendingRegistration['otp_sent_at']) < 60 ) { return back()->withErrors([ 'otp' => 'Tunggu sebentar sebelum meminta OTP baru.', ]); } try { $this->issueRegistrationOtp($request, [ 'name' => $pendingRegistration['name'], 'username' => $pendingRegistration['username'], 'email' => $pendingRegistration['email'], 'password' => $pendingRegistration['password'], ]); } catch (Throwable) { return back()->withErrors([ 'otp' => 'OTP gagal dikirim ulang. Periksa konfigurasi email lalu coba lagi.', ]); } return back()->with('status', 'OTP baru sudah dikirim ke email Anda.'); } public function logout(Request $request): RedirectResponse { Auth::logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('landing')->with('status', 'Berhasil logout.'); } private function redirectAfterLogin(Request $request, User $user): RedirectResponse { if ($user->isAdmin()) { $request->session()->forget('url.intended'); return redirect()->route('dashboard'); } $hasUnreadAdminNotification = $user->penyewaans() ->whereNotNull('admin_catatan') ->whereNull('admin_catatan_read_at') ->exists(); if ($hasUnreadAdminNotification) { $request->session()->forget('url.intended'); return redirect() ->route('profile') ->with('status', 'Ada notifikasi baru dari admin pada pengajuan sewa Anda.'); } $intendedUrl = $request->session()->pull('url.intended'); if ($intendedUrl) { return redirect()->to($intendedUrl); } return redirect()->route('profile'); } private function recordLoginHistory(Request $request, User $user, string $loginIdentifier): void { LoginHistory::create([ 'user_id' => $user->id, 'login_identifier' => $loginIdentifier, 'ip_address' => $request->ip(), 'user_agent' => (string) $request->userAgent(), 'login_at' => now(), ]); } private function issueRegistrationOtp(Request $request, array $registrationData): void { $otp = (string) random_int(100000, 999999); Mail::to($registrationData['email'])->send(new RegisterOtpMail( otp: $otp, recipientName: $registrationData['name'] )); $request->session()->put(self::PENDING_REGISTRATION_SESSION_KEY, [ 'name' => $registrationData['name'], 'username' => $registrationData['username'], 'email' => $registrationData['email'], 'password' => $registrationData['password'], 'otp_hash' => Hash::make($otp), 'otp_expires_at' => now()->addMinutes(10)->timestamp, 'otp_sent_at' => now()->timestamp, ]); } private function pendingRegistrationData(Request $request): ?array { $pendingRegistration = $request->session()->get(self::PENDING_REGISTRATION_SESSION_KEY); return is_array($pendingRegistration) ? $pendingRegistration : null; } private function clearPendingRegistration(Request $request): void { $request->session()->forget(self::PENDING_REGISTRATION_SESSION_KEY); } }