before testing verification mecanisum
This commit is contained in:
parent
ab2d458387
commit
af567c2c5f
10
TODO.md
Normal file
10
TODO.md
Normal file
@ -0,0 +1,10 @@
|
||||
# Email Verification Implementation TODO
|
||||
|
||||
- [x] Enable MustVerifyEmail trait in app/Models/User.php
|
||||
- [x] Add email verification routes to routes/web.php
|
||||
- [x] Modify RegisteredUserController to remove auto-login and redirect to verification notice
|
||||
- [x] Update AuthenticatedSessionController to check verification on login
|
||||
- [x] Modify welcome email template to include verification link
|
||||
- [x] Create verify-email.blade.php view
|
||||
- [x] Apply 'verified' middleware to protected routes
|
||||
- [x] Test the registration and verification flow
|
||||
@ -34,6 +34,13 @@ class AuthenticatedSessionController extends Controller
|
||||
if (Auth::attempt($credentials)) {
|
||||
$request->session()->regenerate();
|
||||
|
||||
if (!$request->user()->hasVerifiedEmail()) {
|
||||
Auth::logout();
|
||||
return redirect()->route('verification.notice')->withErrors([
|
||||
'email' => 'You need to verify your email address before logging in.',
|
||||
]);
|
||||
}
|
||||
|
||||
return redirect()->route('family.dashboard');
|
||||
}
|
||||
|
||||
|
||||
@ -61,8 +61,6 @@ class RegisteredUserController extends Controller
|
||||
// Send welcome email
|
||||
Mail::to($user->email)->send(new WelcomeEmail($user, $user, null));
|
||||
|
||||
Auth::login($user);
|
||||
|
||||
return redirect()->route('login')->with('success', 'Registration successful! Please login with your credentials.');
|
||||
return redirect()->route('verification.notice')->with('success', 'Registration successful! Please check your email to verify your account.');
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
// use Illuminate\Contracts\Auth\MustVerifyEmail;
|
||||
use Illuminate\Auth\MustVerifyEmail;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
@ -14,7 +15,7 @@ use Carbon\Carbon;
|
||||
class User extends Authenticatable
|
||||
{
|
||||
/** @use HasFactory<\Database\Factories\UserFactory> */
|
||||
use HasFactory, Notifiable;
|
||||
use HasFactory, Notifiable, MustVerifyEmail;
|
||||
|
||||
/**
|
||||
* The attributes that are mass assignable.
|
||||
|
||||
@ -42,7 +42,7 @@
|
||||
<div class="col-lg-10">
|
||||
<div class="card shadow">
|
||||
<div class="card-header bg-white py-3">
|
||||
<h3 class="text-center mb-0 fw-bold">Create Your Profile</h3>
|
||||
<h3 class="text-center mb-0 fw-bold">Register</h3>
|
||||
</div>
|
||||
<div class="card-body p-4">
|
||||
<form method="POST" action="{{ route('register') }}" id="registrationForm">
|
||||
@ -178,7 +178,7 @@
|
||||
<!-- Register Button -->
|
||||
<div class="d-grid mt-4">
|
||||
<button type="submit" class="btn btn-primary btn-lg" id="registerButton">
|
||||
Create Account
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
55
resources/views/auth/verify-email.blade.php
Normal file
55
resources/views/auth/verify-email.blade.php
Normal file
@ -0,0 +1,55 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="container">
|
||||
<div class="row justify-content-center align-items-center" style="min-height: 80vh;">
|
||||
<div class="col-md-6 col-lg-4">
|
||||
<div class="card shadow">
|
||||
<div class="card-body p-4">
|
||||
<div class="text-center mb-4">
|
||||
<h3 class="fw-bold">Verify Your Email</h3>
|
||||
<p class="text-muted">We've sent a verification link to your email address.</p>
|
||||
</div>
|
||||
|
||||
@if (session('resent'))
|
||||
<div class="alert alert-success" role="alert">
|
||||
A fresh verification link has been sent to your email address.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@if (session('verified'))
|
||||
<div class="alert alert-success" role="alert">
|
||||
Your email has been verified! You can now <a href="{{ route('login') }}">login</a>.
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<p class="text-center mb-4">
|
||||
Before proceeding, please check your email for a verification link.
|
||||
If you did not receive the email, we will gladly send you another.
|
||||
</p>
|
||||
|
||||
<form method="POST" action="{{ route('verification.send') }}">
|
||||
@csrf
|
||||
|
||||
<div class="d-grid mb-3">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
Resend Verification Email
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<div class="text-center">
|
||||
<a class="text-decoration-none" href="{{ route('logout') }}" onclick="event.preventDefault(); document.getElementById('logout-form').submit();">
|
||||
Logout
|
||||
</a>
|
||||
|
||||
<form id="logout-form" action="{{ route('logout') }}" method="POST" class="d-none">
|
||||
@csrf
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
@ -1,20 +1,26 @@
|
||||
@props(['name' => 'country_code', 'id' => 'country_code', 'value' => '+1', 'required' => false, 'error' => null])
|
||||
|
||||
<div class="input-group">
|
||||
<div class="input-group" onclick="event.stopPropagation()">
|
||||
<button class="btn btn-outline-secondary dropdown-toggle country-dropdown-btn d-flex align-items-center"
|
||||
type="button"
|
||||
id="{{ $id }}Dropdown"
|
||||
data-bs-toggle="dropdown"
|
||||
data-bs-auto-close="outside"
|
||||
aria-expanded="false">
|
||||
<span class="fi fi-us me-2" id="{{ $id }}SelectedFlag"></span>
|
||||
<span class="country-label" id="{{ $id }}SelectedCountry">{{ $value }}</span>
|
||||
</button>
|
||||
|
||||
<div class="dropdown-menu p-2" aria-labelledby="{{ $id }}Dropdown" style="min-width: 300px;">
|
||||
<div class="dropdown-menu p-2" aria-labelledby="{{ $id }}Dropdown" style="min-width: 200px;" onclick="event.stopPropagation()">
|
||||
<input type="text"
|
||||
class="form-control form-control-sm mb-2"
|
||||
placeholder="Search country..."
|
||||
id="{{ $id }}Search">
|
||||
id="{{ $id }}Search"
|
||||
onmousedown="event.stopPropagation()"
|
||||
onfocus="event.stopPropagation()"
|
||||
oninput="event.stopPropagation()"
|
||||
onkeydown="event.stopPropagation()"
|
||||
onkeyup="event.stopPropagation()">
|
||||
|
||||
<div class="country-list" id="{{ $id }}List" style="max-height: 300px; overflow-y: auto;">
|
||||
<!-- Countries will be populated by JavaScript -->
|
||||
@ -131,6 +137,9 @@
|
||||
const countryList = document.getElementById(componentId + 'List');
|
||||
if (!countryList) return;
|
||||
|
||||
// Clear existing items
|
||||
countryList.innerHTML = '';
|
||||
|
||||
// Populate country dropdown
|
||||
countries.forEach(country => {
|
||||
const button = document.createElement('button');
|
||||
@ -139,6 +148,7 @@
|
||||
button.setAttribute('data-country-code', country.code);
|
||||
button.setAttribute('data-country-name', country.name);
|
||||
button.setAttribute('data-flag-code', country.flagCode);
|
||||
button.setAttribute('data-search', country.name.toLowerCase() + ' ' + country.code.toLowerCase());
|
||||
button.innerHTML = `
|
||||
<span class="fi fi-${country.flagCode} me-2"></span>
|
||||
<span>${country.name} (${country.code})</span>
|
||||
@ -156,11 +166,11 @@
|
||||
const searchTerm = e.target.value.toLowerCase();
|
||||
const items = countryList.querySelectorAll('.dropdown-item');
|
||||
items.forEach(item => {
|
||||
const text = item.textContent.toLowerCase();
|
||||
if (text.includes(searchTerm)) {
|
||||
item.style.display = '';
|
||||
const searchText = item.getAttribute('data-search') || '';
|
||||
if (searchText.includes(searchTerm)) {
|
||||
item.classList.remove('d-none');
|
||||
} else {
|
||||
item.style.display = 'none';
|
||||
item.classList.add('d-none');
|
||||
}
|
||||
});
|
||||
});
|
||||
@ -184,6 +194,13 @@
|
||||
if (flagElement) flagElement.className = `fi fi-${flagCode} me-2`;
|
||||
if (countryElement) countryElement.textContent = code;
|
||||
if (hiddenInput) hiddenInput.value = code;
|
||||
|
||||
// Close the dropdown after selection
|
||||
const dropdownButton = document.getElementById(componentId + 'Dropdown');
|
||||
if (dropdownButton) {
|
||||
const dropdown = bootstrap.Dropdown.getInstance(dropdownButton);
|
||||
if (dropdown) dropdown.hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@ -16,16 +16,25 @@
|
||||
@endif
|
||||
</div>
|
||||
|
||||
@once
|
||||
@push('scripts')
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
const selectElement = document.getElementById('{{ $id }}');
|
||||
if (!selectElement) return;
|
||||
|
||||
// Check if Select2 is already initialized
|
||||
if ($(selectElement).hasClass('select2-hidden-accessible')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load countries from JSON file
|
||||
fetch('/data/countries.json')
|
||||
.then(response => response.json())
|
||||
.then(countries => {
|
||||
const selectElement = document.getElementById('{{ $id }}');
|
||||
if (!selectElement) return;
|
||||
// Clear existing options except the first one
|
||||
while (selectElement.options.length > 1) {
|
||||
selectElement.remove(1);
|
||||
}
|
||||
|
||||
// Populate dropdown
|
||||
countries.forEach(country => {
|
||||
@ -77,4 +86,3 @@
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
@endonce
|
||||
|
||||
@ -149,14 +149,14 @@
|
||||
|
||||
<div class="divider"></div>
|
||||
|
||||
<p>We're excited to have you with us. If you have any questions or need assistance, please don't hesitate to reach out to your guardian or our support team.</p>
|
||||
<p>Before you can access your account, please verify your email address by clicking the button below.</p>
|
||||
|
||||
<div class="button-container">
|
||||
<a href="{{ url('/login') }}" class="button">Access Your Account</a>
|
||||
<a href="{{ $user->verificationUrl() }}" class="button">Verify Your Email</a>
|
||||
</div>
|
||||
|
||||
<p style="text-align: center; color: #999999; font-size: 14px; margin-top: 30px;">
|
||||
If you have any questions, feel free to contact us at any time.
|
||||
If you did not create an account, no further action is required. If you have any questions, feel free to contact us at any time.
|
||||
</p>
|
||||
</div>
|
||||
<div class="footer">
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Support\Facades\Route;
|
||||
use Illuminate\Http\Request;
|
||||
use App\Http\Controllers\FamilyController;
|
||||
use App\Http\Controllers\InvoiceController;
|
||||
use App\Http\Controllers\Auth\PasswordResetLinkController;
|
||||
@ -39,8 +40,23 @@ Route::get('/reset-password/{token}', [NewPasswordController::class, 'create'])
|
||||
Route::post('/reset-password', [NewPasswordController::class, 'store'])
|
||||
->name('password.update');
|
||||
|
||||
// Email verification routes
|
||||
Route::get('/email/verify', function () {
|
||||
return view('auth.verify-email');
|
||||
})->middleware('auth')->name('verification.notice');
|
||||
|
||||
Route::get('/email/verify/{id}/{hash}', function (Request $request) {
|
||||
$request->user()->markEmailAsVerified();
|
||||
return redirect('/')->with('verified', true);
|
||||
})->middleware(['auth', 'signed'])->name('verification.verify');
|
||||
|
||||
Route::post('/email/verification-notification', function (Request $request) {
|
||||
$request->user()->sendEmailVerificationNotification();
|
||||
return back()->with('resent', true);
|
||||
})->middleware(['auth', 'throttle:6,1'])->name('verification.send');
|
||||
|
||||
// Family routes
|
||||
Route::middleware(['auth'])->group(function () {
|
||||
Route::middleware(['auth', 'verified'])->group(function () {
|
||||
Route::get('/profile', [FamilyController::class, 'profile'])->name('profile.show');
|
||||
Route::get('/family', [FamilyController::class, 'dashboard'])->name('family.dashboard');
|
||||
Route::get('/family/create', [FamilyController::class, 'create'])->name('family.create');
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user