Halaman Account Settings

This commit is contained in:
Stephen Gesityan 2025-05-09 17:53:18 +07:00
parent 2e9086a761
commit 3d4a23ddcd
16 changed files with 1097 additions and 186 deletions

View File

@ -0,0 +1,98 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Validation\Rule;
class AccountController extends Controller
{
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
// Hapus middleware verified karena kita akan mengizinkan user yang belum terverifikasi
// untuk mengakses pengaturan akun mereka
$this->middleware(['auth']);
}
/**
* Show the account settings page.
*
* @return \Illuminate\View\View
*/
public function settings()
{
$user = Auth::user();
return view('account.settings', compact('user'));
}
/**
* Update the user's account information.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse
*/
public function update(Request $request)
{
$user = Auth::user();
$validated = $request->validate([
'name' => ['required', 'string', 'max:255'],
'email' => [
'required',
'string',
'email',
'max:255',
Rule::unique('users')->ignore($user->id),
],
'current_password' => ['nullable', 'required_with:password', 'string'],
'password' => ['nullable', 'string', 'min:8', 'confirmed'],
]);
// Check if current password is provided and is correct
if ($request->filled('current_password')) {
if (!Hash::check($request->current_password, $user->password)) {
return back()->withErrors(['current_password' => 'Password saat ini tidak valid.']);
}
}
// Update user information
$user->name = $validated['name'];
// Check if email is changed
$emailChanged = $user->email !== $validated['email'];
if ($emailChanged) {
$user->email = $validated['email'];
// Set email_verified_at ke null hanya jika sebelumnya sudah terverifikasi
// Ini untuk memastikan user harus verifikasi email baru
if ($user->hasVerifiedEmail()) {
$user->email_verified_at = null;
$emailNeedsVerification = true;
}
}
// Update password if provided
if ($request->filled('password')) {
$user->password = Hash::make($validated['password']);
}
$user->save();
// Jika email diubah dan sebelumnya sudah terverifikasi, kirim email verifikasi baru
if ($emailChanged && isset($emailNeedsVerification)) {
$user->sendEmailVerificationNotification();
session()->flash('success', 'Profil berhasil diperbarui. Silakan verifikasi alamat email baru Anda.');
} else {
session()->flash('success', 'Profil berhasil diperbarui.');
}
return back();
}
}

View File

@ -3,7 +3,9 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ConfirmsPasswords;
use Illuminate\Http\Request;
class ConfirmPasswordController extends Controller
{
@ -14,7 +16,7 @@ class ConfirmPasswordController extends Controller
|
| This controller is responsible for handling password confirmations and
| uses a simple trait to include the behavior. You're free to explore
| this trait and override any functions that require customization.
| this trait and override any methods you wish to tweak.
|
*/
@ -25,7 +27,7 @@ class ConfirmPasswordController extends Controller
*
* @var string
*/
protected $redirectTo = '/home';
protected $redirectTo = '/';
/**
* Create a new controller instance.
@ -36,4 +38,62 @@ public function __construct()
{
$this->middleware('auth');
}
/**
* Display the password confirmation view.
*
* @return \Illuminate\View\View
*/
public function showConfirmForm()
{
return view('auth.passwords.confirm');
}
/**
* Confirm the given user's password.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function confirm(Request $request)
{
$request->validate($this->rules(), $this->validationErrorMessages());
$this->resetPasswordConfirmationTimeout($request);
return redirect()->intended($this->redirectPath());
}
/**
* Reset the password confirmation timeout.
*
* @param \Illuminate\Http\Request $request
* @return void
*/
protected function resetPasswordConfirmationTimeout(Request $request)
{
$request->session()->put('auth.password_confirmed_at', time());
}
/**
* Get the password confirmation validation rules.
*
* @return array
*/
protected function rules()
{
return [
'password' => 'required|password',
];
}
/**
* Get the password confirmation validation error messages.
*
* @return array
*/
protected function validationErrorMessages()
{
return [];
}
}

View File

@ -4,6 +4,8 @@
use App\Http\Controllers\Controller;
use Illuminate\Foundation\Auth\SendsPasswordResetEmails;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Password;
class ForgotPasswordController extends Controller
{
@ -19,4 +21,73 @@ class ForgotPasswordController extends Controller
*/
use SendsPasswordResetEmails;
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Display the form to request a password reset link.
*
* @return \Illuminate\View\View
*/
public function showLinkRequestForm()
{
return view('auth.passwords.email');
}
/**
* Send a reset link to the given user.
*
* @param \Illuminate\Http\Request $request
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
public function sendResetLinkEmail(Request $request)
{
$this->validateEmail($request);
// We will send the password reset link to this user. Once we have attempted
// to send the link, we will examine the response then see the message we
// need to show to the user. Finally, we'll send out a proper response.
$response = $this->broker()->sendResetLink(
$request->only('email')
);
return $response == Password::RESET_LINK_SENT
? $this->sendResetLinkResponse($request, $response)
: $this->sendResetLinkFailedResponse($request, $response);
}
/**
* Get the response for a successful password reset link.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkResponse(Request $request, $response)
{
session()->flash('success', trans($response));
return back();
}
/**
* Get the response for a failed password reset link.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetLinkFailedResponse(Request $request, $response)
{
session()->flash('error', trans($response));
return back()
->withInput($request->only('email'));
}
}

View File

@ -3,7 +3,9 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\ResetsPasswords;
use Illuminate\Http\Request;
class ResetPasswordController extends Controller
{
@ -25,5 +27,43 @@ class ResetPasswordController extends Controller
*
* @var string
*/
protected $redirectTo = '/home';
protected $redirectTo = '/';
/**
* Create a new controller instance.
*
* @return void
*/
public function __construct()
{
$this->middleware('guest');
}
/**
* Get the response for a successful password reset.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetResponse(Request $request, $response)
{
session()->flash('success', trans($response));
return redirect($this->redirectPath());
}
/**
* Get the response for a failed password reset.
*
* @param \Illuminate\Http\Request $request
* @param string $response
* @return \Illuminate\Http\RedirectResponse|\Illuminate\Http\JsonResponse
*/
protected function sendResetFailedResponse(Request $request, $response)
{
session()->flash('error', trans($response));
return redirect()->back()
->withInput($request->only('email'))
->withErrors(['email' => trans($response)]);
}
}

View File

@ -3,7 +3,9 @@
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Providers\RouteServiceProvider;
use Illuminate\Foundation\Auth\VerifiesEmails;
use Illuminate\Http\Request;
class VerificationController extends Controller
{
@ -25,7 +27,7 @@ class VerificationController extends Controller
*
* @var string
*/
protected $redirectTo = '/home';
protected $redirectTo = '/';
/**
* Create a new controller instance.
@ -38,4 +40,55 @@ public function __construct()
$this->middleware('signed')->only('verify');
$this->middleware('throttle:6,1')->only('verify', 'resend');
}
/**
* Show the email verification notice.
*
* @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
* @return \Illuminate\Http\RedirectResponse
*/
public function resend(Request $request)
{
// Jika user sudah terverifikasi, tidak perlu kirim ulang
if ($request->user()->hasVerifiedEmail()) {
return redirect($this->redirectPath())
->with('success', 'Email anda sudah terverifikasi.');
}
// Kirim email verifikasi baru
$request->user()->sendEmailVerificationNotification();
return back()->with('success', 'Link verifikasi baru telah dikirim ke email anda.');
}
}

View File

@ -61,7 +61,7 @@ class Kernel extends HttpKernel
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'password.confirm' => \Illuminate\Auth\Middleware\RequirePassword::class,
'precognitive' => \Illuminate\Foundation\Http\Middleware\HandlePrecognitiveRequests::class,
'signed' => \App\Http\Middleware\ValidateSignature::class,
'signed' => \Illuminate\Routing\Middleware\ValidateSignature::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class,
'is_admin' => \App\Http\Middleware\IsAdmin::class,

View File

@ -1,8 +1,10 @@
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Symfony\Component\HttpFoundation\Response;
class IsAdmin
@ -10,14 +12,17 @@ class IsAdmin
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if (auth()->check() && auth()->user()->role === 'admin') {
public function handle(Request $request, Closure $next): Response
{
if (Auth::check() && Auth::user()->role === 'admin') {
return $next($request);
}
abort(403); // atau redirect('/login')
}
session()->flash('error', 'Anda tidak memiliki akses ke halaman tersebut!');
return redirect('/');
}
}

View File

@ -2,13 +2,13 @@
namespace App\Models;
// use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Contracts\Auth\MustVerifyEmail;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Laravel\Sanctum\HasApiTokens;
class User extends Authenticatable
class User extends Authenticatable implements MustVerifyEmail
{
use HasApiTokens, HasFactory, Notifiable;
@ -42,4 +42,14 @@ class User extends Authenticatable
'email_verified_at' => 'datetime',
'password' => 'hashed',
];
/**
* Check if the user is an admin.
*
* @return bool
*/
public function isAdmin()
{
return $this->role === 'admin';
}
}

View File

@ -0,0 +1,245 @@
@extends('layouts.app')
@section('content')
<div class="py-12 animated-bg">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card shadow-lg border-0 rounded-3 overflow-hidden">
<div class="card-header bg-gradient-to-r from-indigo-600 to-blue-500 text-white p-3">
<h2 class="mb-0 d-flex align-items-center">
<i class="fas fa-user-cog me-2"></i>{{ __('Account Settings') }}
</h2>
</div>
<div class="card-body p-4">
@if (session('message'))
<div class="alert alert-success d-flex align-items-center border-0 shadow-sm mb-4" role="alert">
<div class="me-2">
<i class="fas fa-check-circle"></i>
</div>
<div>
{{ session('message') }}
</div>
</div>
@endif
@if (session('success'))
<div class="alert alert-success d-flex align-items-center border-0 shadow-sm mb-4" role="alert">
<div class="me-2">
<i class="fas fa-check-circle"></i>
</div>
<div>
{{ session('success') }}
</div>
</div>
@endif
<form method="POST" action="{{ route('account.update') }}">
@csrf
@method('PUT')
<!-- Name -->
<div class="mb-4">
<label for="name" class="form-label fw-medium">
<i class="fas fa-user text-primary me-1"></i>{{ __('Name') }}
</label>
<div class="input-group">
<span class="input-group-text bg-light">
<i class="fas fa-user-edit"></i>
</span>
<input id="name" type="text" name="name" value="{{ old('name', $user->name) }}"
required autofocus class="form-control border-start-0"
placeholder="Enter your name">
</div>
@error('name')
<div class="text-danger mt-1 small"><i
class="fas fa-exclamation-circle me-1"></i>{{ $message }}</div>
@enderror
</div>
<!-- Email -->
<div class="mb-4">
<label for="email" class="form-label fw-medium">
<i class="fas fa-envelope text-primary me-1"></i>{{ __('Email') }}
</label>
<div class="input-group">
<span class="input-group-text bg-light">
<i class="fas fa-at"></i>
</span>
<input id="email" type="email" name="email" value="{{ old('email', $user->email) }}"
required class="form-control border-start-0" placeholder="Enter your email">
</div>
@error('email')
<div class="text-danger mt-1 small"><i
class="fas fa-exclamation-circle me-1"></i>{{ $message }}</div>
@enderror
@if ($user->email)
<div class="mt-2 small d-flex align-items-center">
@if ($user->hasVerifiedEmail())
<span class="text-success d-flex align-items-center">
<i class="fas fa-check-circle me-1"></i> {{ __('Email verified') }}
</span>
@else
<span class="text-warning d-flex align-items-center">
<i class="fas fa-exclamation-circle me-1"></i> {{ __('Not Verified') }}
</span>
<a href="{{ route('verification.resend') }}"
class="ms-2 text-primary text-decoration-none">
{{ __('Resend verification email') }}
</a>
@endif
</div>
@endif
</div>
<div class="card mt-4 mb-4 shadow-sm border-0">
<div class="card-header bg-light">
<h3 class="mb-0 fs-5 fw-semibold text-primary">
<i class="fas fa-lock me-2"></i>{{ __('Change Password') }}
</h3>
</div>
<div class="card-body">
<!-- Current Password -->
<div class="mb-3">
<label for="current_password" class="form-label fw-medium">
<i class="fas fa-key text-primary me-1"></i>{{ __('Current Password') }}
</label>
<div class="input-group">
<span class="input-group-text bg-light">
<i class="fas fa-lock"></i>
</span>
<input id="current_password" type="password" name="current_password"
class="form-control border-start-0"
placeholder="Enter current password">
<button class="btn btn-outline-secondary" type="button"
id="toggleCurrentPassword">
<i class="fas fa-eye"></i>
</button>
</div>
@error('current_password')
<div class="text-danger mt-1 small"><i
class="fas fa-exclamation-circle me-1"></i>{{ $message }}</div>
@enderror
</div>
<!-- New Password -->
<div class="mb-3">
<label for="password" class="form-label fw-medium">
<i class="fas fa-lock-open text-primary me-1"></i>{{ __('New Password') }}
</label>
<div class="input-group">
<span class="input-group-text bg-light">
<i class="fas fa-key"></i>
</span>
<input id="password" type="password" name="password"
class="form-control border-start-0" placeholder="Enter new password">
<button class="btn btn-outline-secondary" type="button"
id="toggleNewPassword">
<i class="fas fa-eye"></i>
</button>
</div>
@error('password')
<div class="text-danger mt-1 small"><i
class="fas fa-exclamation-circle me-1"></i>{{ $message }}</div>
@enderror
</div>
<!-- Confirm New Password -->
<div class="mb-3">
<label for="password_confirmation" class="form-label fw-medium">
<i class="fas fa-check-double text-primary me-1"></i>{{ __('Confirm New Password') }}
</label>
<div class="input-group">
<span class="input-group-text bg-light">
<i class="fas fa-key"></i>
</span>
<input id="password_confirmation" type="password" name="password_confirmation"
class="form-control border-start-0" placeholder="Confirm new password">
<button class="btn btn-outline-secondary" type="button"
id="toggleConfirmPassword">
<i class="fas fa-eye"></i>
</button>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-end mt-4">
<button type="submit"
class="btn btn-primary-custom d-flex align-items-center">
<i class="fas fa-save me-2"></i>{{ __('Update Account') }}
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
<style>
@keyframes fade-in-down {
0% {
opacity: 0;
transform: translateY(-10px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
.animate-fade-in-down {
animation: fade-in-down 0.5s ease-out;
}
</style>
<script>
// Toggle password visibility
document.addEventListener('DOMContentLoaded', function() {
const toggleCurrentPassword = document.getElementById('toggleCurrentPassword');
const toggleNewPassword = document.getElementById('toggleNewPassword');
const toggleConfirmPassword = document.getElementById('toggleConfirmPassword');
const currentPassword = document.getElementById('current_password');
const newPassword = document.getElementById('password');
const confirmPassword = document.getElementById('password_confirmation');
if(toggleCurrentPassword) {
toggleCurrentPassword.addEventListener('click', function() {
togglePasswordVisibility(currentPassword, this);
});
}
if(toggleNewPassword) {
toggleNewPassword.addEventListener('click', function() {
togglePasswordVisibility(newPassword, this);
});
}
if(toggleConfirmPassword) {
toggleConfirmPassword.addEventListener('click', function() {
togglePasswordVisibility(confirmPassword, this);
});
}
function togglePasswordVisibility(input, button) {
const type = input.getAttribute('type') === 'password' ? 'text' : 'password';
input.setAttribute('type', type);
// Toggle icon
const icon = button.querySelector('i');
if (type === 'text') {
icon.classList.remove('fa-eye');
icon.classList.add('fa-eye-slash');
} else {
icon.classList.remove('fa-eye-slash');
icon.classList.add('fa-eye');
}
}
});
</script>
@endsection

View File

@ -1,23 +1,26 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Confirm Password') }}</div>
<div class="card-body">
{{ __('Please confirm your password before continuing.') }}
<p class="mb-3">{{ __('Please confirm your password before continuing.') }}</p>
<form method="POST" action="{{ route('password.confirm') }}">
@csrf
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<label for="password"
class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="current-password">
<input id="password" type="password"
class="form-control @error('password') is-invalid @enderror" name="password"
required autocomplete="current-password">
@error('password')
<span class="invalid-feedback" role="alert">
@ -28,7 +31,7 @@
</div>
<div class="row mb-0">
<div class="col-md-8 offset-md-4">
<div class="col-md-6 offset-md-4">
<button type="submit" class="btn btn-primary">
{{ __('Confirm Password') }}
</button>
@ -45,5 +48,5 @@
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -1,7 +1,7 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
@ -18,10 +18,12 @@
@csrf
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<label for="email"
class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror"
name="email" value="{{ old('email') }}" required autocomplete="email" autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
@ -43,5 +45,5 @@
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -1,7 +1,7 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
@ -14,10 +14,13 @@
<input type="hidden" name="token" value="{{ $token }}">
<div class="row mb-3">
<label for="email" class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<label for="email"
class="col-md-4 col-form-label text-md-end">{{ __('Email Address') }}</label>
<div class="col-md-6">
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror" name="email" value="{{ $email ?? old('email') }}" required autocomplete="email" autofocus>
<input id="email" type="email" class="form-control @error('email') is-invalid @enderror"
name="email" value="{{ $email ?? old('email') }}" required autocomplete="email"
autofocus>
@error('email')
<span class="invalid-feedback" role="alert">
@ -28,10 +31,13 @@
</div>
<div class="row mb-3">
<label for="password" class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<label for="password"
class="col-md-4 col-form-label text-md-end">{{ __('Password') }}</label>
<div class="col-md-6">
<input id="password" type="password" class="form-control @error('password') is-invalid @enderror" name="password" required autocomplete="new-password">
<input id="password" type="password"
class="form-control @error('password') is-invalid @enderror" name="password"
required autocomplete="new-password">
@error('password')
<span class="invalid-feedback" role="alert">
@ -42,10 +48,12 @@
</div>
<div class="row mb-3">
<label for="password-confirm" class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<label for="password-confirm"
class="col-md-4 col-form-label text-md-end">{{ __('Confirm Password') }}</label>
<div class="col-md-6">
<input id="password-confirm" type="password" class="form-control" name="password_confirmation" required autocomplete="new-password">
<input id="password-confirm" type="password" class="form-control"
name="password_confirmation" required autocomplete="new-password">
</div>
</div>
@ -61,5 +69,5 @@
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -1,28 +1,49 @@
@extends('layouts.app')
@section('content')
<div class="container">
<div class="row justify-content-center">
<div class="col-md-8">
<div class="card">
<div class="card-header">{{ __('Verify Your Email Address') }}</div>
<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-blue-600 px-6 py-4">
<h2 class="text-xl font-bold text-white">{{ __('Verifikasi Email Anda') }}</h2>
</div>
<div class="card-body">
<div class="p-6">
@if (session('resent'))
<div class="alert alert-success" role="alert">
{{ __('A fresh verification link has been sent to your email address.') }}
<div class="bg-green-100 border-l-4 border-green-500 text-green-700 p-4 mb-4" role="alert">
<p>{{ __('Link verifikasi baru telah dikirim ke alamat email Anda.') }}</p>
</div>
@endif
{{ __('Before proceeding, please check your email for a verification link.') }}
{{ __('If you did not receive the email') }},
<form class="d-inline" method="POST" action="{{ route('verification.resend') }}">
<div class="mb-6">
<div class="flex items-center mb-4">
<i class="fas fa-envelope-open-text text-blue-500 text-3xl mr-4"></i>
<div>
<p class="text-gray-700">
{{ __('Sebelum melanjutkan, silakan periksa email Anda untuk link verifikasi.') }}</p>
<p class="text-sm text-gray-500 mt-1">
{{ __('Email verifikasi biasanya dikirim dalam beberapa menit.') }}</p>
</div>
</div>
<div class="bg-gray-50 p-4 rounded-lg mb-4">
<p class="text-gray-600 text-sm">
<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.') }}
</p>
</div>
</div>
<div class="text-center">
<p class="text-gray-700 mb-4">{{ __('Tidak menerima email verifikasi?') }}</p>
<form method="POST" action="{{ route('verification.resend') }}">
@csrf
<button type="submit" class="btn btn-link p-0 m-0 align-baseline">{{ __('click here to request another') }}</button>.
<button type="submit"
class="bg-blue-600 hover:bg-blue-700 text-white font-medium py-2 px-6 rounded-lg transition duration-200">
{{ __('Kirim Ulang Verifikasi') }}
</button>
</form>
</div>
</div>
</div>
</div>
</div>
@endsection

View File

@ -4,27 +4,220 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- CSRF Token -->
<meta name="csrf-token" content="{{ csrf_token() }}">
<title>{{ config('app.name', 'Laravel') }}</title>
<!-- Fonts -->
<link rel="dns-prefetch" href="//fonts.bunny.net">
<link href="https://fonts.bunny.net/css?family=Nunito" rel="stylesheet">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600;700&display=swap"
rel="stylesheet">
<!-- Font Awesome -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<!-- Scripts -->
@vite(['resources/sass/app.scss', 'resources/js/app.js'])
<style>
:root {
--primary-color: #4F46E5;
--primary-hover: #4338CA;
--secondary-color: #38BDF8;
--accent-color: #8B5CF6;
--bg-gradient-start: #EEF2FF;
--bg-gradient-end: #E0E7FF;
}
body {
font-family: 'Poppins', sans-serif;
background: linear-gradient(135deg, var(--bg-gradient-start) 0%, var(--bg-gradient-end) 100%);
min-height: 100vh;
}
#app {
display: flex;
flex-direction: column;
min-height: 100vh;
}
.navbar-brand {
font-weight: 700;
font-size: 1.5rem;
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
transition: all 0.3s ease;
}
.navbar-brand:hover {
transform: scale(1.05);
}
.nav-link {
font-weight: 500;
position: relative;
transition: all 0.3s ease;
}
.nav-link:after {
content: '';
position: absolute;
width: 0;
height: 2px;
bottom: 0;
left: 0;
background-color: var(--primary-color);
transition: width 0.3s ease;
}
.nav-link:hover:after {
width: 100%;
}
.dropdown-menu {
border-radius: 0.5rem;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
border: none;
padding: 0.5rem;
}
.dropdown-item {
border-radius: 0.375rem;
transition: all 0.2s ease;
padding: 0.5rem 1rem;
}
.dropdown-item:hover {
background-color: #EEF2FF;
color: var(--primary-color);
}
main {
flex-grow: 1;
}
/* Custom animated navbar */
.custom-navbar {
background-color: rgba(255, 255, 255, 0.9) !important;
backdrop-filter: blur(10px);
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
padding: 1rem 0;
transition: all 0.3s ease;
}
.custom-navbar.scrolled {
padding: 0.5rem 0;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.btn-primary-custom {
background: linear-gradient(90deg, var(--primary-color), var(--accent-color));
border: none;
border-radius: 0.5rem;
color: white;
font-weight: 500;
padding: 0.5rem 1.25rem;
transition: all 0.3s ease;
}
.btn-primary-custom:hover {
transform: translateY(-2px);
box-shadow: 0 10px 15px -3px rgba(79, 70, 229, 0.4);
}
.notification-badge {
position: absolute;
top: -5px;
right: -5px;
background: var(--accent-color);
color: white;
border-radius: 50%;
width: 18px;
height: 18px;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.user-avatar {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
border: 2px solid var(--primary-color);
}
.avatar-wrapper {
position: relative;
margin-right: 0.5rem;
}
.status-indicator {
position: absolute;
bottom: 0;
right: 0;
width: 10px;
height: 10px;
border-radius: 50%;
background-color: #10B981;
border: 2px solid white;
}
/* Footer */
footer {
background-color: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
padding: 1.5rem 0;
margin-top: auto;
}
/* Animated background for special sections */
.animated-bg {
position: relative;
overflow: hidden;
z-index: 1;
}
.animated-bg:before {
content: "";
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background: linear-gradient(45deg,
rgba(79, 70, 229, 0.1) 0%,
rgba(56, 189, 248, 0.1) 33%,
rgba(139, 92, 246, 0.1) 66%,
rgba(79, 70, 229, 0.1) 100%);
animation: rotate 20s linear infinite;
z-index: -1;
}
@keyframes rotate {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
</style>
</head>
<body>
<div id="app">
<nav class="navbar navbar-expand-md navbar-light bg-white shadow-sm">
<nav class="navbar navbar-expand-md custom-navbar navbar-light sticky-top">
<div class="container">
<a class="navbar-brand" href="{{ url('/') }}">
<a class="navbar-brand d-flex align-items-center" href="{{ url('/') }}">
<i class="fas fa-cube me-2"></i>
{{ config('app.name', 'Laravel') }}
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse"
data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="{{ __('Toggle navigation') }}">
@ -34,7 +227,16 @@
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<!-- Left Side Of Navbar -->
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{{ url('/') }}">
<i class="fas fa-tachometer-alt me-1"></i> Venue
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{{ url('/booking/history') }}">
<i class="fas fa-tasks me-1"></i> Riwayat Booking
</a>
</li>
</ul>
<!-- Right Side Of Navbar -->
@ -43,26 +245,44 @@
@guest
@if (Route::has('login'))
<li class="nav-item">
<a class="nav-link" href="{{ route('login') }}">{{ __('Login') }}</a>
<a class="nav-link" href="{{ route('login') }}">
<i class="fas fa-sign-in-alt me-1"></i> {{ __('Login') }}
</a>
</li>
@endif
@if (Route::has('register'))
<li class="nav-item">
<a class="nav-link" href="{{ route('register') }}">{{ __('Register') }}</a>
<a class="btn btn-primary-custom" href="{{ route('register') }}">
<i class="fas fa-user-plus me-1"></i> {{ __('Register') }}
</a>
</li>
@endif
@else
<li class="nav-item dropdown">
<a id="navbarDropdown" class="nav-link dropdown-toggle" href="#" role="button"
data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false" v-pre>
{{ Auth::user()->name }}
<a id="navbarDropdown" class="nav-link dropdown-toggle d-flex align-items-center" href="#"
role="button" data-bs-toggle="dropdown" aria-haspopup="true" aria-expanded="false"
v-pre>
<div class="avatar-wrapper">
<img src="https://ui-avatars.com/api/?name={{ urlencode(Auth::user()->name) }}&background=4F46E5&color=fff"
alt="User Avatar" class="user-avatar">
<span class="status-indicator"></span>
</div>
<span>{{ Auth::user()->name }}</span>
</a>
<div class="dropdown-menu dropdown-menu-end" aria-labelledby="navbarDropdown">
<a class="dropdown-item" href="{{ route('logout') }}" onclick="event.preventDefault();
<a class="dropdown-item" href="{{ route('account.settings') }}">
<i class="fas fa-cog me-2"></i> {{ __('Settings') }}
</a>
<a class="dropdown-item" href="{{ route('account.settings') }}">
<i class="fas fa-user-circle me-2"></i> {{ __('Profile') }}
</a>
<hr class="dropdown-divider">
<a class="dropdown-item text-danger" href="{{ route('logout') }}"
onclick="event.preventDefault();
document.getElementById('logout-form').submit();">
{{ __('Logout') }}
<i class="fas fa-sign-out-alt me-2"></i> {{ __('Logout') }}
</a>
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
@ -79,7 +299,36 @@
<main class="py-4">
@yield('content')
</main>
<footer class="mt-auto">
<div class="container">
<div class="row">
<div class="col-md-6">
<p class="mb-0">&copy; {{ date('Y') }} {{ config('app.name', 'Laravel') }}. All rights reserved.
</p>
</div>
<div class="col-md-6 text-md-end">
<a href="#" class="text-decoration-none me-3"><i class="fab fa-facebook-f"></i></a>
<a href="#" class="text-decoration-none me-3"><i class="fab fa-twitter"></i></a>
<a href="#" class="text-decoration-none me-3"><i class="fab fa-instagram"></i></a>
<a href="#" class="text-decoration-none"><i class="fab fa-linkedin-in"></i></a>
</div>
</div>
</div>
</footer>
</div>
<script>
// Navbar scroll effect
window.addEventListener('scroll', function () {
const navbar = document.querySelector('.custom-navbar');
if (window.scrollY > 10) {
navbar.classList.add('scrolled');
} else {
navbar.classList.remove('scrolled');
}
});
</script>
</body>
</html>

View File

@ -39,9 +39,6 @@ class="block lg:hidden border-l pl-4 border-gray-300 focus:outline-none">
<!-- Desktop buttons -->
<div class="hidden lg:flex items-center space-x-4">
@auth
<a href="{{ route('booking.history') }}"
class="text-sm font-medium text-gray-700 hover:text-primary transition">Riwayat Booking</a>
<div x-data="{ open: false }" class="relative">
<button @click="open = !open"
class="flex items-center space-x-2 text-sm font-medium text-gray-700 hover:text-primary focus:outline-none">
@ -58,6 +55,17 @@ class="absolute right-0 mt-2 w-40 bg-white rounded-lg shadow-lg py-2 z-50">
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
Riwayat Booking
</a>
<a href="{{ route('account.settings') }}"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
Pengaturan Akun
</a>
@if (Auth::user()->email_verified_at === null)
<a href="{{ route('verification.notice') }}"
class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<span class="text-orange-500"><i class="fas fa-exclamation-circle mr-1"></i></span>
Verifikasi Email
</a>
@endif
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit"
@ -83,6 +91,17 @@ class="absolute top-full left-0 right-0 bg-white shadow-md mt-1 p-4 z-50">
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
Riwayat Booking
</a>
<a href="{{ route('account.settings') }}"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
Pengaturan Akun
</a>
@if (Auth::user()->email_verified_at === null)
<a href="{{ route('verification.notice') }}"
class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<span class="text-orange-500"><i class="fas fa-exclamation-circle mr-1"></i></span>
Verifikasi Email
</a>
@endif
<form method="POST" action="{{ route('logout') }}">
@csrf
<button type="submit"
@ -125,6 +144,19 @@ class="absolute top-3 right-4 text-gray-500 hover:text-gray-700 text-xl">
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">
<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>
<button type="submit"
class="w-full bg-blue-600 hover:bg-blue-700 text-white py-2 rounded">Masuk</button>
</form>

View File

@ -1,5 +1,6 @@
<?php
use App\Http\Controllers\AccountController;
use App\Http\Controllers\pages\HomeController;
use App\Http\Controllers\pages\VenueController;
use App\Http\Controllers\pages\BookingController;
@ -10,7 +11,9 @@
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Auth;
Auth::routes();
// Authentication Routes (dengan verifikasi email aktif)
Auth::routes(['verify' => true]);
Route::get('/', [HomeController::class, "index"])->name('index');
Route::get('/venue/{venueName}', [VenueController::class, "venue"])->name('venue');
@ -20,7 +23,7 @@
Route::get('/booking/schedules', [BookingController::class, 'getBookedSchedules'])->name('booking.schedules');
Route::post('/payment/notification', [BookingController::class, 'handleNotification'])->name('payment.notification');
// Booking history routes (authenticated only)
// Booking history routes (authenticated only, tidak perlu verified)
Route::middleware(['auth'])->group(function () {
Route::get('/booking/history', [BookingHistoryController::class, 'index'])->name('booking.history');
@ -28,9 +31,20 @@
Route::get('/booking/pending', [BookingController::class, 'getPendingBookings'])->name('booking.pending');
Route::get('/booking/pending/{id}/resume', [BookingController::class, 'resumeBooking'])->name('booking.resume');
Route::delete('/booking/pending/{id}', [BookingController::class, 'deletePendingBooking'])->name('booking.pending.delete');
// Profile route definition - use the existing account settings as the profile page
Route::get('/profile', [AccountController::class, 'settings'])->name('profile');
// Routes that require password confirmation
Route::middleware(['password.confirm'])->group(function () {
// Add routes that require password confirmation 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 routes (admin tetap perlu verified untuk keamanan)
Route::middleware(['auth', 'is_admin'])->prefix('admin')->group(function () {
Route::get('/', [AdminController::class, 'index'])->name('admin.dashboard');
Route::get('/bookings', [BookingsController::class, 'index'])->name('admin.bookings.index');