437 lines
17 KiB
PHP
437 lines
17 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
|
<title>Password Recovery</title>
|
|
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.4/jquery.min.js"></script>
|
|
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
|
background: #f4f6f9;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
}
|
|
.container-box {
|
|
background: #ffffff;
|
|
width: 100%;
|
|
max-width: 420px;
|
|
border-radius: 16px;
|
|
padding: 40px 36px;
|
|
box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
|
|
}
|
|
.icon-wrap {
|
|
width: 64px;
|
|
height: 64px;
|
|
background: #943835e3;
|
|
border-radius: 50%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin: 0 auto 20px;
|
|
}
|
|
.icon-wrap i { font-size: 24px; color: #eeff8f; }
|
|
h1 { font-size: 22px; color: #1f2937; text-align: center; margin-bottom: 8px; font-weight: 700; }
|
|
p.subtitle { text-align: center; color: #6b7280; font-size: 14px; margin-bottom: 28px; line-height: 1.5; }
|
|
.form-group { margin-bottom: 20px; position: relative; }
|
|
label { display: block; font-size: 13px; font-weight: 600; color: #374151; margin-bottom: 6px; }
|
|
input[type="email"], input[type="text"], input[type="password"] {
|
|
width: 100%; padding: 12px 14px; border: 1.5px solid #e5e7eb;
|
|
border-radius: 10px; font-size: 14px; color: #1f2937; outline: none;
|
|
transition: all 0.2s ease;
|
|
}
|
|
input:focus { border-color: #943835e3; box-shadow: 0 0 0 3px rgba(148, 56, 53, 0.15); }
|
|
.toggle-password { position: absolute; right: 14px; top: 38px; cursor: pointer; color: #6b7280; }
|
|
.err, .error { color: #dc2626; font-size: 12.5px; margin-top: 6px; display: block; }
|
|
.otp-input { letter-spacing: 4px; text-align: center; font-size: 20px; font-weight: 700; }
|
|
button[type="submit"] {
|
|
width: 100%; padding: 13px; background: #943835e3; color: #fff;
|
|
border: none; border-radius: 10px; font-size: 15px; font-weight: 600;
|
|
cursor: pointer; margin-top: 10px; transition: transform 0.15s ease;
|
|
}
|
|
button[type="submit"]:hover { transform: translateY(-1px); box-shadow: 0 8px 20px rgba(148, 56, 53, 0.35); }
|
|
button[type="submit"]:disabled { background: #94383580; cursor: not-allowed; }
|
|
.back-link { display: block; text-align: center; margin-top: 22px; font-size: 13.5px; color: #6b7280; }
|
|
.back-link a { color: #231d77e3; font-weight: 600; text-decoration: none; }
|
|
.powered { text-align: center; margin-top: 20px; font-size: 12px; color: #9ca3af; }
|
|
|
|
.otp-container {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 8px;
|
|
margin-top: 10px;
|
|
margin-bottom: 10px;
|
|
}
|
|
|
|
.otp-box {
|
|
width: 48px;
|
|
height: 52px;
|
|
text-align: center;
|
|
font-size: 22px;
|
|
font-weight: 700;
|
|
border: 1.5px solid #e5e7eb;
|
|
border-radius: 10px;
|
|
outline: none;
|
|
transition: all 0.2s ease;
|
|
}
|
|
|
|
.otp-box:focus {
|
|
border-color: #943835e3;
|
|
box-shadow: 0 0 0 3px rgba(148, 56, 53, 0.15);
|
|
}
|
|
|
|
.success-icon-wrap {
|
|
font-size: 60px;
|
|
color: #10b981; /* Green color */
|
|
margin-bottom: 15px;
|
|
}
|
|
|
|
.login-btn-link {
|
|
display: block;
|
|
width: 100%;
|
|
padding: 13px;
|
|
background: #943835e3;
|
|
color: #fff;
|
|
text-decoration: none;
|
|
border-radius: 10px;
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
margin-top: 20px;
|
|
transition: transform 0.15s ease;
|
|
}
|
|
|
|
.login-btn-link:hover {
|
|
color: #fff;
|
|
transform: translateY(-1px);
|
|
box-shadow: 0 8px 20px rgba(148, 56, 53, 0.35);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<div class="container-box">
|
|
<div class="icon-wrap" id="main-icon">
|
|
<i class="fa-solid fa-key"></i>
|
|
</div>
|
|
|
|
<!-- Step 1: Email Form -->
|
|
<form class="login-form" name="login-form" action="{{ route('password.email') }}" method="post" id="login-form">
|
|
@csrf
|
|
<h1>Recover Password</h1>
|
|
<p class="subtitle">Enter your registered email address to receive an OTP code.</p>
|
|
|
|
<div class="form-group">
|
|
<label for="email-login">Email Address</label>
|
|
<input type="email" name="email" id="email-login" placeholder="name@example.com" required>
|
|
<span id="email-err" class="err"></span>
|
|
@if ($errors->has('email'))
|
|
<span class="err">{{ $errors->first('email') }}</span>
|
|
@endif
|
|
</div>
|
|
|
|
<button type="submit" id="verify-btn" onclick="validateEmail(event)">Verify Email</button>
|
|
|
|
<div class="back-link">
|
|
Remember the password? <a href="/signin">Please Login</a>
|
|
</div>
|
|
</form>
|
|
|
|
<!-- Step 2: OTP Verification Form -->
|
|
<div id="otp_sec" style="display: none;">
|
|
<h1>Enter OTP Code</h1>
|
|
<p class="subtitle">Please check your email and enter the 6-digit verification code.</p>
|
|
|
|
<form name="emailOtpForm" id="emailOtpForm">
|
|
@csrf
|
|
<div class="form-group">
|
|
<label>Verification Code</label>
|
|
|
|
<div class="otp-container">
|
|
<input type="text" class="otp-box" maxlength="1" pattern="\d*" inputmode="numeric" required>
|
|
<input type="text" class="otp-box" maxlength="1" pattern="\d*" inputmode="numeric" required>
|
|
<input type="text" class="otp-box" maxlength="1" pattern="\d*" inputmode="numeric" required>
|
|
<input type="text" class="otp-box" maxlength="1" pattern="\d*" inputmode="numeric" required>
|
|
<input type="text" class="otp-box" maxlength="1" pattern="\d*" inputmode="numeric" required>
|
|
<input type="text" class="otp-box" maxlength="1" pattern="\d*" inputmode="numeric" required>
|
|
</div>
|
|
<input type="hidden" id="otp" name="otpm">
|
|
|
|
<span id="otpm_err" class="err"></span>
|
|
</div>
|
|
|
|
<button type="submit" onclick="showPassword(event)">Submit Code</button>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Step 3: Password Reset Form -->
|
|
<div id="password_sec" style="display: none;">
|
|
<h1>Reset Password</h1>
|
|
<p class="subtitle">Create a new password for your account.</p>
|
|
|
|
<form class="login-form" name="reset-password-form" id="reset-password-form" action="{{ route('password.update') }}" method="post" onsubmit="handlePasswordReset(event)">
|
|
@csrf
|
|
<input type="hidden" name="email" value="" id="hidden_email">
|
|
|
|
<div class="form-group">
|
|
<label for="password-m">New Password</label>
|
|
<input type="password" name="password_mo" id="password-m" placeholder="••••••••" required>
|
|
<i class="fa-solid fa-eye toggle-password" id="view1" onclick="togglePassword('password-m', 'view1', 'no-view1')"></i>
|
|
<i class="fa-solid fa-eye-slash toggle-password" id="no-view1" onclick="togglePassword('password-m', 'view1', 'no-view1')" style="display:none;"></i>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="confirm_password">Confirm Password</label>
|
|
<input type="password" name="password_mo_confirmation" id="confirm_password" placeholder="••••••••" required>
|
|
<i class="fa-solid fa-eye toggle-password" id="view-con1" onclick="togglePassword('confirm_password', 'view-con1', 'no-view-con1')"></i>
|
|
<i class="fa-solid fa-eye-slash toggle-password" id="no-view-con1" onclick="togglePassword('confirm_password', 'view-con1', 'no-view-con1')" style="display:none;"></i>
|
|
<span id="con-pass-err" class="error"></span>
|
|
</div>
|
|
|
|
<button type="submit" id="reset-btn">Reset Password</button>
|
|
|
|
<div class="back-link">
|
|
Remember password? <a href="/signin">Please Login</a>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
|
|
<!-- Step 4: Success Message Screen -->
|
|
<div id="success_sec" style="display: none; text-align: center;">
|
|
<div class="success-icon-wrap">
|
|
<i class="fa-solid fa-circle-check"></i>
|
|
</div>
|
|
<h1>Password Reset Successful!</h1>
|
|
<p class="subtitle">Your password has been reset successfully. You can now log in with your new password.</p>
|
|
|
|
<a href="/signin" class="login-btn-link">Proceed to Login</a>
|
|
</div>
|
|
|
|
<p class="powered">Powered by <a href="#" style="color: #6b7280; text-decoration: none; font-weight: 600;">GPiT</a></p>
|
|
</div>
|
|
|
|
<script>
|
|
function validateEmail(event) {
|
|
event.preventDefault();
|
|
|
|
var email = document.getElementById('email-login').value;
|
|
var emailErr = document.getElementById('email-err');
|
|
var csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
|
var verifyBtn = document.getElementById('verify-btn');
|
|
|
|
emailErr.textContent = '';
|
|
|
|
if (email.trim() === '') {
|
|
emailErr.textContent = 'Please enter your email';
|
|
return;
|
|
}
|
|
|
|
verifyBtn.disabled = true;
|
|
verifyBtn.textContent = 'Please Wait...';
|
|
|
|
fetch("{{ route('password.email') }}", {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'X-CSRF-TOKEN': csrfToken
|
|
},
|
|
body: JSON.stringify({ email: email })
|
|
})
|
|
.then(async response => {
|
|
const text = await response.text();
|
|
try {
|
|
return JSON.parse(text);
|
|
} catch (e) {
|
|
console.error("SERVER ERROR DETAILS:", text);
|
|
throw new Error("Server returned HTML response instead of JSON.");
|
|
}
|
|
})
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById("hidden_email").value = email;
|
|
document.getElementById("login-form").style.display = "none";
|
|
document.getElementById("otp_sec").style.display = "block";
|
|
} else {
|
|
if (data.errors && data.errors.email) {
|
|
emailErr.textContent = data.errors.email[0];
|
|
} else {
|
|
emailErr.textContent = data.message || 'Email not found in our records.';
|
|
}
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
emailErr.textContent = 'An error occurred. Check browser console (F12) for details.';
|
|
})
|
|
.finally(() => {
|
|
verifyBtn.disabled = false;
|
|
verifyBtn.textContent = 'Verify Email';
|
|
});
|
|
}
|
|
|
|
function showPassword(event) {
|
|
event.preventDefault();
|
|
|
|
const otp = document.getElementById('otp').value;
|
|
const otpm_err = document.getElementById('otpm_err');
|
|
const token = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
|
|
|
otpm_err.innerText = '';
|
|
|
|
if (otp.trim() === '' || otp.length < 6) {
|
|
otpm_err.innerText = 'Please enter a valid 6-digit OTP code.';
|
|
return;
|
|
}
|
|
|
|
fetch("{{ route('password.verify.otp') }}", {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-CSRF-TOKEN': token
|
|
},
|
|
body: JSON.stringify({ otp: otp })
|
|
})
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
if (data.success) {
|
|
document.getElementById("otp_sec").style.display = "none";
|
|
document.getElementById("password_sec").style.display = "block";
|
|
} else {
|
|
otpm_err.innerText = data.message || 'Invalid OTP code';
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
otpm_err.innerText = 'Verification failed. Try again.';
|
|
});
|
|
}
|
|
|
|
function handlePasswordReset(event) {
|
|
event.preventDefault();
|
|
|
|
const form = document.getElementById('reset-password-form');
|
|
const formData = new FormData(form);
|
|
const resetBtn = document.getElementById('reset-btn');
|
|
const passErr = document.getElementById('con-pass-err');
|
|
|
|
const pass = document.getElementById('password-m').value;
|
|
const confirmPass = document.getElementById('confirm_password').value;
|
|
|
|
if (pass !== confirmPass) {
|
|
passErr.innerText = "Passwords do not match!";
|
|
passErr.style.color = "#dc2626";
|
|
return;
|
|
}
|
|
|
|
resetBtn.disabled = true;
|
|
resetBtn.textContent = 'Resetting...';
|
|
|
|
fetch(form.action, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRF-TOKEN': document.querySelector('meta[name="csrf-token"]').getAttribute('content'),
|
|
'Accept': 'application/json'
|
|
},
|
|
body: formData
|
|
})
|
|
.then(async response => {
|
|
const data = await response.json();
|
|
if (response.ok || data.success) {
|
|
|
|
document.getElementById("main-icon").style.display = "none";
|
|
document.getElementById("password_sec").style.display = "none";
|
|
document.getElementById("success_sec").style.display = "block";
|
|
} else {
|
|
passErr.innerText = data.message || 'Failed to reset password.';
|
|
passErr.style.color = "#dc2626";
|
|
}
|
|
})
|
|
.catch(error => {
|
|
console.error('Error:', error);
|
|
// Unexpected redirect/success response handling
|
|
document.getElementById("main-icon").style.display = "none";
|
|
document.getElementById("password_sec").style.display = "none";
|
|
document.getElementById("success_sec").style.display = "block";
|
|
})
|
|
.finally(() => {
|
|
resetBtn.disabled = false;
|
|
resetBtn.textContent = 'Reset Password';
|
|
});
|
|
}
|
|
|
|
$('#password-m, #confirm_password').on('keyup', function() {
|
|
if ($('#password-m').val() !== '' && $('#password-m').val() === $('#confirm_password').val()) {
|
|
$('#con-pass-err').html('Passwords match').css('color', '#047857');
|
|
} else if ($('#confirm_password').val() !== '') {
|
|
$('#con-pass-err').html('Passwords do not match').css('color', '#dc2626');
|
|
}
|
|
});
|
|
|
|
function togglePassword(inputId, eyeId, eyeSlashId) {
|
|
var field = document.getElementById(inputId);
|
|
var eye = document.getElementById(eyeId);
|
|
var eyeSlash = document.getElementById(eyeSlashId);
|
|
|
|
if (field.type === "password") {
|
|
field.type = "text";
|
|
eye.style.display = "none";
|
|
eyeSlash.style.display = "block";
|
|
} else {
|
|
field.type = "password";
|
|
eye.style.display = "block";
|
|
eyeSlash.style.display = "none";
|
|
}
|
|
}
|
|
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const inputs = document.querySelectorAll(".otp-box");
|
|
const hiddenOtpInput = document.getElementById("otp");
|
|
|
|
inputs.forEach((input, index) => {
|
|
input.addEventListener("input", (e) => {
|
|
input.value = input.value.replace(/\D/g, '');
|
|
|
|
if (input.value && index < inputs.length - 1) {
|
|
inputs[index + 1].focus();
|
|
}
|
|
updateHiddenOtp();
|
|
});
|
|
|
|
input.addEventListener("keydown", (e) => {
|
|
if (e.key === "Backspace" && !input.value && index > 0) {
|
|
inputs[index - 1].focus();
|
|
}
|
|
});
|
|
|
|
input.addEventListener("paste", (e) => {
|
|
e.preventDefault();
|
|
const pasteData = e.clipboardData.getData("text").trim().replace(/\D/g, '');
|
|
if (pasteData.length === 6) {
|
|
pasteData.split("").forEach((char, i) => {
|
|
if (inputs[i]) inputs[i].value = char;
|
|
});
|
|
inputs[5].focus();
|
|
updateHiddenOtp();
|
|
}
|
|
});
|
|
});
|
|
|
|
function updateHiddenOtp() {
|
|
let fullOtp = "";
|
|
inputs.forEach(input => fullOtp += input.value);
|
|
hiddenOtpInput.value = fullOtp;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html> |