Verifikasi Email dengan Mailtrap

This commit is contained in:
Stephen Gesityan 2025-05-10 03:30:42 +07:00
parent 3d4a23ddcd
commit 6000103c21
8 changed files with 267 additions and 144 deletions

View File

@ -6,6 +6,7 @@
use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Foundation\Auth\AuthenticatesUsers;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
class LoginController extends Controller class LoginController extends Controller
{ {
@ -40,6 +41,40 @@ public function __construct()
$this->middleware('auth')->only('logout'); $this->middleware('auth')->only('logout');
} }
/**
* The user has been authenticated.
*
* @param \Illuminate\Http\Request $request
* @param mixed $user
* @return mixed
*/
protected function authenticated(Request $request, $user)
{
// Cek jika email belum terverifikasi
if (!$user->hasVerifiedEmail()) {
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
throw ValidationException::withMessages([
'email' => [__('Akun Anda belum diverifikasi. Silakan periksa email Anda untuk link verifikasi.')],
])->redirectTo(route('verification.notice'));
}
session()->flash('success', 'Login berhasil!');
if ($user->role === 'admin') {
return redirect('/admin');
}
return redirect()->intended($this->redirectTo);
}
/**
* Log the user out of the application.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function logout(Request $request) public function logout(Request $request)
{ {
Auth::logout(); Auth::logout();
@ -50,14 +85,4 @@ public function logout(Request $request)
session()->flash('error', 'Berhasil logout!'); session()->flash('error', 'Berhasil logout!');
return redirect('/'); return redirect('/');
} }
protected function authenticated(Request $request, $user)
{
session()->flash('success', 'Login berhasil!');
if ($user->role === 'admin') {
return redirect('/admin');
}
return redirect()->intended($this->redirectTo);
}
} }

View File

@ -7,6 +7,8 @@
use Illuminate\Foundation\Auth\RegistersUsers; use Illuminate\Foundation\Auth\RegistersUsers;
use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Validator; use Illuminate\Support\Facades\Validator;
use Illuminate\Http\Request;
use Illuminate\Auth\Events\Registered;
class RegisterController extends Controller class RegisterController extends Controller
{ {
@ -69,4 +71,25 @@ protected function create(array $data)
'password' => Hash::make($data['password']), 'password' => Hash::make($data['password']),
]); ]);
} }
/**
* Override the registration method to prevent auto-login after registration
* and show a verification email message.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function register(Request $request)
{
$this->validator($request->all())->validate();
event(new Registered($user = $this->create($request->all())));
// Don't automatically login the user
// $this->guard()->login($user);
return $this->registered($request, $user)
?: redirect($this->redirectPath())
->with('success', 'Pendaftaran berhasil! Silahkan periksa email Anda untuk verifikasi akun.');
}
} }

View File

@ -6,6 +6,7 @@
use App\Providers\RouteServiceProvider; use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\VerifiesEmails; use Illuminate\Foundation\Auth\VerifiesEmails;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Auth\Events\Verified;
class VerificationController extends Controller class VerificationController extends Controller
{ {
@ -36,59 +37,52 @@ class VerificationController extends Controller
*/ */
public function __construct() public function __construct()
{ {
$this->middleware('auth'); $this->middleware('auth')->except(['verify']);
$this->middleware('signed')->only('verify'); $this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend'); $this->middleware('throttle:6,1')->only('verify', 'resend');
} }
/** /**
* Show the email verification notice. * Mark the authenticated user's email address as verified.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\View\View
*/
public function show(Request $request)
{
// Jika user sudah terverifikasi, redirect ke halaman utama
if ($request->user()->hasVerifiedEmail()) {
return redirect($this->redirectPath())
->with('success', 'Email anda sudah terverifikasi.');
}
// Jika user belum terverifikasi dan baru register (email_verified_at adalah null),
// tampilkan halaman verifikasi
return view('auth.verify');
}
/**
* The user has been verified.
*
* @param \Illuminate\Http\Request $request
* @return mixed
*/
protected function verified(Request $request)
{
session()->flash('success', 'Email berhasil diverifikasi! Selamat datang di Ayo Venue.');
return redirect($this->redirectPath());
}
/**
* Resend the email verification notification.
* *
* @param \Illuminate\Http\Request $request * @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse * @return \Illuminate\Http\RedirectResponse
*/ */
public function resend(Request $request) public function verify(Request $request)
{ {
// Jika user sudah terverifikasi, tidak perlu kirim ulang $user = \App\Models\User::find($request->route('id'));
if ($request->user()->hasVerifiedEmail()) {
return redirect($this->redirectPath()) if (!hash_equals((string) $request->route('hash'), sha1($user->getEmailForVerification()))) {
->with('success', 'Email anda sudah terverifikasi.'); return redirect()->route('verification.notice')
->with('error', 'Link verifikasi tidak valid.');
} }
// Kirim email verifikasi baru if ($user->hasVerifiedEmail()) {
$request->user()->sendEmailVerificationNotification(); return redirect()->route('login')
->with('verified', true)
->with('success', 'Email sudah terverifikasi sebelumnya. Silakan login.');
}
return back()->with('success', 'Link verifikasi baru telah dikirim ke email anda.'); if ($user->markEmailAsVerified()) {
event(new Verified($user));
}
if ($request->user()) {
auth()->logout();
}
return redirect()->route('login')
->with('verified', true)
->with('success', 'Email berhasil diverifikasi. Silakan login.');
}
/**
* Show the verification success page.
*
* @return \Illuminate\Contracts\View\View
*/
public function verified()
{
return view('auth.verified');
} }
} }

View File

@ -0,0 +1,37 @@
@extends('layouts.app')
@section('content')
<div class="container mx-auto px-4 py-8">
<div class="max-w-md mx-auto bg-white shadow-lg rounded-lg overflow-hidden">
<div class="bg-green-600 px-6 py-4">
<h2 class="text-xl font-bold text-white">{{ __('Email Berhasil Diverifikasi!') }}</h2>
</div>
<div class="p-6">
<div class="text-center mb-6">
<div class="inline-flex items-center justify-center w-16 h-16 rounded-full bg-green-100 mb-4">
<i class="fas fa-check text-green-500 text-3xl"></i>
</div>
<h3 class="text-xl font-semibold text-gray-800">{{ __('Verifikasi Berhasil') }}</h3>
<p class="text-gray-600 mt-2">
{{ __('Selamat! Email Anda telah berhasil diverifikasi.') }}
</p>
</div>
<div class="bg-gray-50 p-4 rounded-lg mb-6">
<p class="text-gray-600 text-sm">
<i class="fas fa-info-circle text-green-500 mr-2"></i>
{{ __('Anda sekarang memiliki akses penuh ke semua fitur Ayo Venue.') }}
</p>
</div>
<div class="text-center">
<a href="{{ route('index') }}"
class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition duration-200 inline-block">
{{ __('Login Sekarang') }}
</a>
</div>
</div>
</div>
</div>
@endsection

View File

@ -14,21 +14,29 @@
</div> </div>
@endif @endif
@if (session('verified'))
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-4" role="alert">
<p>{{ __('Akun Anda telah berhasil diverifikasi. Sekarang Anda dapat login.') }}</p>
</div>
@endif
<div class="mb-6"> <div class="mb-6">
<div class="flex items-center mb-4"> <div class="flex items-center mb-4">
<i class="fas fa-envelope-open-text text-blue-500 text-3xl mr-4"></i> <i class="fas fa-envelope-open-text text-blue-500 text-3xl mr-4"></i>
<div> <div>
<p class="text-gray-700"> <p class="text-gray-700">
{{ __('Sebelum melanjutkan, silakan periksa email Anda untuk link verifikasi.') }}</p> {{ __('Sebelum melanjutkan, silakan periksa email Anda untuk link verifikasi.') }}
</p>
<p class="text-sm text-gray-500 mt-1"> <p class="text-sm text-gray-500 mt-1">
{{ __('Email verifikasi biasanya dikirim dalam beberapa menit.') }}</p> {{ __('Email verifikasi biasanya dikirim dalam beberapa menit.') }}
</p>
</div> </div>
</div> </div>
<div class="bg-gray-50 p-4 rounded-lg mb-4"> <div class="bg-gray-50 p-4 rounded-lg mb-4">
<p class="text-gray-600 text-sm"> <p class="text-gray-600 text-sm">
<i class="fas fa-info-circle text-blue-500 mr-2"></i> <i class="fas fa-info-circle text-blue-500 mr-2"></i>
{{ __('Tidak perlu khawatir. Anda sudah dapat login dan menggunakan fitur dasar Ayo Venue. Verifikasi email ini hanya diperlukan untuk fitur tertentu dan keamanan akun Anda.') }} {{ __('Verifikasi email diperlukan untuk menggunakan fitur aplikasi Ayo Venue. Pastikan Anda memverifikasi email Anda untuk akses penuh ke platform kami.') }}
</p> </p>
</div> </div>
</div> </div>

View File

@ -122,96 +122,120 @@ class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100
<!-- Modal --> <!-- Modal -->
<div x-show="showModal" x-cloak x-transition:enter="transition ease-out duration-300" <div x-show="showModal" x-cloak x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100" x-transition:enter-start="opacity-0" x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200" x-transition:leave-start="opacity-100" x-transition:leave="transition ease-in duration-200" x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0" x-transition:leave-end="opacity-0"
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50"> class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
<!-- Modal Box --> <!-- Modal Box -->
<div @click.away="showModal = false" class="bg-white rounded-xl shadow-lg w-full max-w-md p-6 relative"> <div @click.away="showModal = false" class="bg-white rounded-xl shadow-lg w-full max-w-md p-6 relative">
<button @click="showModal = false" <button @click="showModal = false"
class="absolute top-3 right-4 text-gray-500 hover:text-gray-700 text-xl"> class="absolute top-3 right-4 text-gray-500 hover:text-gray-700 text-xl">
&times; &times;
</button> </button>
<template x-if="modalType === 'login'"> <template x-if="modalType === 'login'">
<div> <div>
<h2 class="text-xl font-semibold mb-4">Masuk</h2> <h2 class="text-xl font-semibold mb-4">Masuk</h2>
<form method="POST" action="{{ route('login') }}" class="space-y-4">
@csrf
<input type="email" name="email" placeholder="Email" class="w-full border px-4 py-2 rounded"
required>
<input type="password" name="password" placeholder="Password"
class="w-full border px-4 py-2 rounded" required>
<div class="flex items-center justify-between"> <!-- Error message for validation errors -->
<div class="flex items-center"> @if($errors->any())
<input class="mr-2" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}> <div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4" role="alert">
<label class="text-sm text-gray-600" for="remember"> <ul>
Ingat saya @foreach($errors->all() as $error)
</label> <li>{{ $error }}</li>
</div> @endforeach
<a href="{{ route('password.request') }}" class="text-sm text-primary hover:underline"> </ul>
Lupa Password? </div>
</a> @endif
</div>
<button type="submit" <form method="POST" action="{{ route('login') }}" class="space-y-4">
class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded">Masuk</button> @csrf
</form> <input type="email" name="email" placeholder="Email" class="w-full border px-4 py-2 rounded"
<p class="text-sm mt-4 text-center"> required>
Belum punya akun? <input type="password" name="password" placeholder="Password"
<button @click="modalType = 'register'" class="text-primary hover:underline">Daftar</button> class="w-full border px-4 py-2 rounded" required>
</p>
<div class="flex items-center justify-between">
<div class="flex items-center">
<input class="mr-2" type="checkbox" name="remember" id="remember" {{ old('remember') ? 'checked' : '' }}>
<label class="text-sm text-gray-600" for="remember">
Ingat saya
</label>
</div>
<a href="{{ route('password.request') }}" class="text-sm text-primary hover:underline">
Lupa Password?
</a>
</div> </div>
</template>
<template x-if="modalType === 'register'"> <button type="submit"
<div> class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded">Masuk</button>
<h2 class="text-xl font-semibold mb-4">Daftar</h2> </form>
<form method="POST" action="{{ route('register') }}" class="space-y-4"> <p class="text-sm mt-4 text-center">
@csrf Belum punya akun?
<input type="text" name="name" placeholder="Nama Lengkap" <button @click="modalType = 'register'" class="text-primary hover:underline">Daftar</button>
class="w-full border px-4 py-2 rounded" required> </p>
<input type="email" name="email" placeholder="Email" class="w-full border px-4 py-2 rounded"
required>
<input type="password" name="password" placeholder="Password"
class="w-full border px-4 py-2 rounded" required>
<input type="password" name="password_confirmation" placeholder="Konfirmasi Password"
class="w-full border px-4 py-2 rounded" required>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded">Daftar</button>
</form>
<p class="text-sm mt-4 text-center">
Sudah punya akun?
<button @click="modalType = 'login'" class="text-primary hover:underline">Masuk</button>
</p>
</div>
</template>
</div> </div>
</div> </template>
<template x-if="modalType === 'register'">
<div>
<h2 class="text-xl font-semibold mb-4">Daftar</h2>
<!-- Error message for validation errors -->
@if($errors->any())
<div class="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 mb-4" role="alert">
<ul>
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
<form method="POST" action="{{ route('register') }}" class="space-y-4">
@csrf
<input type="text" name="name" placeholder="Nama Lengkap"
class="w-full border px-4 py-2 rounded" required>
<input type="email" name="email" placeholder="Email" class="w-full border px-4 py-2 rounded"
required>
<input type="password" name="password" placeholder="Password"
class="w-full border px-4 py-2 rounded" required>
<input type="password" name="password_confirmation" placeholder="Konfirmasi Password"
class="w-full border px-4 py-2 rounded" required>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded">Daftar</button>
</form>
<p class="text-sm mt-4 text-center">
Sudah punya akun?
<button @click="modalType = 'login'" class="text-primary hover:underline">Masuk</button>
</p>
</div>
</template>
</div>
</div>
</header> </header>
<main class="pt-20"> <main class="pt-20">
@if (session('success') || session('error')) @if (session('success') || session('error'))
<div id="floating-alert" style=" <div id="floating-alert" style="
position: fixed; position: fixed;
top: 30px; top: 30px;
left: 50%; left: 50%;
transform: translateX(-50%); transform: translateX(-50%);
background-color: {{ session('success') ? '#d1e7dd' : '#f8d7da' }}; background-color: {{ session('success') ? '#d1e7dd' : '#f8d7da' }};
color: {{ session('success') ? '#0f5132' : '#842029' }}; color: {{ session('success') ? '#0f5132' : '#842029' }};
padding: 10px 20px; padding: 10px 20px;
border-radius: 6px; border-radius: 6px;
font-size: 14px; font-size: 14px;
font-weight: 500; font-weight: 500;
box-shadow: 0 3px 10px rgba(0,0,0,0.15); box-shadow: 0 3px 10px rgba(0,0,0,0.15);
z-index: 9999; z-index: 9999;
max-width: 300px; max-width: 300px;
text-align: center; text-align: center;
"> ">
{{ session('success') ?? session('error') }} {{ session('success') ?? session('error') }}
</div> </div>

View File

@ -6,7 +6,7 @@
@if($bookings->isEmpty()) @if($bookings->isEmpty())
<div class="bg-white rounded-lg shadow-md p-6 text-center"> <div class="bg-white rounded-lg shadow-md p-6 text-center">
<p class="text-gray-500">Anda belum memiliki riwayat booking.</p> <p class="text-gray-500">Anda belum memiliki riwayat booking.</p>
<a href="{{ route('venues.index') }}" class="mt-4 inline-block bg-blue-500 text-white px-4 py-2 rounded-lg">Cari <a href="{{ url('/') }}" class="mt-4 inline-block bg-blue-500 text-white px-4 py-2 rounded-lg">Cari
Venue</a> Venue</a>
</div> </div>
@else @else

View File

@ -8,12 +8,16 @@
use App\Http\Controllers\admin\BookingsController; use App\Http\Controllers\admin\BookingsController;
use App\Http\Controllers\admin\TableController; use App\Http\Controllers\admin\TableController;
use App\Http\Controllers\admin\AdminController; use App\Http\Controllers\admin\AdminController;
use App\Http\Controllers\Auth\VerificationController;
use Illuminate\Support\Facades\Route; use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Auth;
// Authentication Routes (dengan verifikasi email aktif) // Authentication Routes (dengan verifikasi email aktif)
Auth::routes(['verify' => true]); Auth::routes(['verify' => true]);
// Rute custom untuk verifikasi email
Route::get('/email/verified', [VerificationController::class, 'verified'])->name('verification.verified');
Route::get('/', [HomeController::class, "index"])->name('index'); Route::get('/', [HomeController::class, "index"])->name('index');
Route::get('/venue/{venueName}', [VenueController::class, "venue"])->name('venue'); Route::get('/venue/{venueName}', [VenueController::class, "venue"])->name('venue');
@ -23,8 +27,22 @@
Route::get('/booking/schedules', [BookingController::class, 'getBookedSchedules'])->name('booking.schedules'); Route::get('/booking/schedules', [BookingController::class, 'getBookedSchedules'])->name('booking.schedules');
Route::post('/payment/notification', [BookingController::class, 'handleNotification'])->name('payment.notification'); Route::post('/payment/notification', [BookingController::class, 'handleNotification'])->name('payment.notification');
// Booking history routes (authenticated only, tidak perlu verified) // Routes that require authentication but not email verification
Route::middleware(['auth'])->group(function () { Route::middleware(['auth'])->group(function () {
// Route untuk melihat halaman verifikasi email dan kirim ulang email verifikasi
// sudah ditangani oleh Auth::routes(['verify' => true])
// Profile route access without verification
Route::get('/profile', [AccountController::class, 'settings'])->name('profile');
// Account settings routes - moved from password confirmation middleware
Route::get('/account/settings', [AccountController::class, 'settings'])->name('account.settings');
Route::put('/account/update', [AccountController::class, 'update'])->name('account.update');
});
// Routes that require both authentication and email verification
Route::middleware(['auth', 'verified'])->group(function () {
// Booking history routes
Route::get('/booking/history', [BookingHistoryController::class, 'index'])->name('booking.history'); Route::get('/booking/history', [BookingHistoryController::class, 'index'])->name('booking.history');
// Pending bookings routes // Pending bookings routes
@ -32,20 +50,14 @@
Route::get('/booking/pending/{id}/resume', [BookingController::class, 'resumeBooking'])->name('booking.resume'); Route::get('/booking/pending/{id}/resume', [BookingController::class, 'resumeBooking'])->name('booking.resume');
Route::delete('/booking/pending/{id}', [BookingController::class, 'deletePendingBooking'])->name('booking.pending.delete'); Route::delete('/booking/pending/{id}', [BookingController::class, 'deletePendingBooking'])->name('booking.pending.delete');
// Profile route definition - use the existing account settings as the profile page // Routes that require password confirmation - moved account settings out of this group
Route::get('/profile', [AccountController::class, 'settings'])->name('profile');
// Routes that require password confirmation
Route::middleware(['password.confirm'])->group(function () { Route::middleware(['password.confirm'])->group(function () {
// Add routes that require password confirmation here // Any sensitive operations that should still require password confirmation can go here
// For example, account settings, deleting accounts, etc.
Route::get('/account/settings', [AccountController::class, 'settings'])->name('account.settings');
Route::put('/account/update', [AccountController::class, 'update'])->name('account.update');
}); });
}); });
// Admin routes (admin tetap perlu verified untuk keamanan) // Admin routes (admin tetap perlu verified untuk keamanan)
Route::middleware(['auth', 'is_admin'])->prefix('admin')->group(function () { Route::middleware(['auth', 'verified', 'is_admin'])->prefix('admin')->group(function () {
Route::get('/', [AdminController::class, 'index'])->name('admin.dashboard'); Route::get('/', [AdminController::class, 'index'])->name('admin.dashboard');
Route::get('/bookings', [BookingsController::class, 'index'])->name('admin.bookings.index'); Route::get('/bookings', [BookingsController::class, 'index'])->name('admin.bookings.index');
Route::get('/bookings/export', [BookingsController::class, 'export'])->name('admin.bookings.export'); Route::get('/bookings/export', [BookingsController::class, 'export'])->name('admin.bookings.export');