base2 / auth.html
Ashok75's picture
Upload 3 files
72cdcd8 verified
<!DOCTYPE html>
<html lang="en" data-bs-theme="dark">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>GAKR AI - Login/Signup</title>
<link rel="stylesheet" href="https://cdn.replit.com/agent/bootstrap-agent-dark-theme.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<style>
:root {
--gakr-blue: #4285F4;
--gakr-blue-dark: #1a73e8;
--gakr-blue-light: #e8f0fe;
--gakr-grey-text: #5f6368;
--gakr-grey-hover-bg: rgba(95, 99, 104, 0.1);
}
:root[data-bs-theme="dark"] {
--gakr-blue: #8ab4f8;
--gakr-blue-dark: #669df6;
--gakr-blue-light: rgba(138, 180, 248, 0.1);
--gakr-grey-text: #bdc1c6;
--gakr-grey-hover-bg: rgba(189, 193, 198, 0.1);
}
@import url('https://fonts.googleapis.com/css?family=Poppins:400,500,600,700&display=swap');
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Poppins', sans-serif;
}
html, body {
display: flex;
flex-direction: column;
align-items: center;
height: 100%;
width: 100%;
background: -webkit-linear-gradient(left, #003366,#004080,#0059b3 , #0073e6);
color: var(--bs-body-color);
padding-top: calc(64px + 5vh);
padding-bottom: 5vh;
min-height: 100vh;
/* Allow space for modal overlay */
position: relative;
}
::selection {
background: var(--gakr-blue);
color: #fff;
}
.wrapper {
overflow: hidden;
max-width: 440px;
width: 90%;
background: var(--bs-body-bg);
box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.5);
padding: 30px;
border-radius: 15px;
margin-top: 10px;
margin-bottom: 10px;
z-index: 10; /* Ensure wrapper is below modal but above background */
}
.wrapper .title-text {
display: flex;
width: 200%;
}
.wrapper .title {
width: 50%;
font-size: 35px;
font-weight: 600;
text-align: center;
transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
color: var(--bs-body-color);
}
.wrapper .slide-controls {
position: relative;
display: flex;
height: 50px;
width: 100%;
overflow: hidden;
margin: 30px 0 10px 0;
justify-content: space-between;
border: 1px solid var(--bs-border-color);
border-radius: 15px;
}
.slide-controls .slide {
height: 100%;
width: 100%;
color: var(--bs-body-color);
font-size: 18px;
font-weight: 500;
text-align: center;
line-height: 48px;
cursor: pointer;
z-index: 1;
transition: all 0.6s ease;
}
.slide-controls label.signup{
color: var(--bs-body-color);
}
.slide-controls .slider-tab {
position: absolute;
height: 100%;
width: 50%;
left: 0;
z-index: 0;
border-radius: 15px;
background: var(--gakr-blue);
transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
input[type="radio"]{
display: none;
}
#signup:checked ~ .slider-tab{
left: 50%;
}
#signup:checked ~ label.signup{
color: #fff;
cursor: default;
user-select: none;
}
#signup:checked ~ label.login{
color: var(--bs-body-color);
}
#login:checked ~ label.signup{
color: var(--bs-body-color);
}
#login:checked ~ label.login{
color: #fff;
cursor: default;
user-select: none;
}
.wrapper .form-container {
width: 100%;
overflow: hidden;
}
.form-container .form-inner {
display: flex;
width: 200%;
}
.form-container .form-inner form {
width: 50%;
transition: all 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55);
}
.form-inner form .field {
height: 50px;
width: 100%;
margin-top: 20px;
position: relative;
}
.form-inner form .field:has(+ .field.btn) {
margin-bottom: 25px;
}
.form-inner form .field input{
height: 100%;
width: 100%;
outline: none;
padding-left: 15px;
border-radius: 15px;
border: 1px solid var(--bs-border-color);
border-bottom-width: 2px;
font-size: 17px;
transition: all 0.3s ease;
color: var(--bs-body-color);
background-color: var(--bs-body-bg);
}
.form-inner form .field input.error {
border-color: red;
}
.form-inner form .field input::placeholder{
color: var(--bs-secondary-color);
transition: all 0.3s ease;
}
form .field input:focus::placeholder{
color: var(--gakr-blue);
}
.form-inner form .pass-link{
margin-top: 5px;
font-size: 0.9rem;
text-align: right; /* Align to right */
}
.form-inner form .signup-link{
text-align: center;
margin-top: 30px;
font-size: 0.9rem;
}
.form-inner form .pass-link a,
.form-inner form .signup-link a{
color: var(--gakr-blue);
text-decoration: none;
transition: color 0.2s ease-in-out;
}
.form-inner form .pass-link a:hover,
form-inner form .signup-link a:hover{
text-decoration: underline;
color: var(--gakr-blue-dark);
}
form .btn {
height: 50px;
width: 100%;
margin-top: 20px;
border-radius: 15px;
position: relative;
overflow: hidden;
background: none;
transition: none;
}
form .btn input[type="submit"] {
height: 100%;
width: 100%;
z-index: 1;
position: relative;
background: var(--gakr-blue);
border: none;
color: #fff;
padding: 0;
border-radius: 15px;
font-size: 20px;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s ease-in-out, transform 0.1s ease-in-out;
display: flex;
align-items: center;
justify-content: center;
}
form .btn input[type="submit"]:hover {
background-color: var(--gakr-blue-dark);
}
form .btn input[type="submit"]:active {
transform: scale(0.98);
transition: background-color 0s, transform 0.1s;
}
.error-message {
color: red;
font-size: 0.8rem;
margin-top: 4px;
position: absolute;
bottom: -18px;
left: 15px;
white-space: nowrap;
}
.form-message {
text-align: center;
margin-top: 20px;
font-size: 0.9rem;
min-height: 1.2em;
}
.form-message.success {
color: green;
}
.form-message.error {
color: red;
}
.loading-spinner {
display: inline-block;
width: 18px;
height: 18px;
border: 3px solid rgba(255, 255, 255, .3);
border-radius: 50%;
border-top-color: #fff;
animation: spin 1s ease-in-out infinite;
margin-left: 10px;
vertical-align: middle;
}
@keyframes spin {
to { -webkit-transform: rotate(360deg); }
}
.form-inner form .btn input[type="submit"]:disabled {
opacity: 0.7;
cursor: not-allowed;
position: relative;
}
/* --- Modal Styles --- */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.6); /* Semi-transparent black overlay */
display: flex;
justify-content: center;
align-items: center;
z-index: 100; /* On top of everything */
visibility: hidden; /* Hidden by default */
opacity: 0;
transition: visibility 0s, opacity 0.3s ease-in-out;
}
.modal-overlay.show {
visibility: visible;
opacity: 1;
}
.modal-content {
background-color: var(--bs-body-bg);
padding: 30px;
border-radius: 15px;
box-shadow: 0px 5px 20px rgba(0, 0, 0, 0.7);
text-align: center;
max-width: 350px;
width: 80%;
transform: translateY(-20px); /* Slight animation on show */
transition: transform 0.3s ease-in-out;
}
.modal-overlay.show .modal-content {
transform: translateY(0);
}
.modal-message {
font-size: 1.1rem;
margin-bottom: 25px;
color: var(--bs-body-color);
}
.modal-message p {
margin-bottom: 10px; /* Spacing for paragraphs in modal message */
line-height: 1.4;
}
.modal-message strong {
font-size: 1.2rem; /* Heading for attempts message */
display: block;
margin-bottom: 15px;
}
.modal-message ul {
list-style: none;
padding: 0;
margin-bottom: 20px;
}
.modal-message ul li {
margin-bottom: 8px;
font-size: 1rem;
}
.modal-button {
background-color: var(--gakr-blue);
color: #fff;
border: none;
padding: 10px 25px;
border-radius: 10px;
font-size: 1rem;
cursor: pointer;
transition: background-color 0.2s ease-in-out, transform 0.1s ease-in-out;
}
.modal-button:hover {
background-color: var(--gakr-blue-dark);
}
.modal-button:active {
transform: scale(0.98);
}
.modal-buttons-stacked {
display: flex;
flex-direction: column; /* Stack buttons vertically */
gap: 10px;
margin-top: 20px;
}
.modal-buttons-stacked .modal-button {
width: 100%; /* Make buttons full width */
}
.modal-buttons-inline {
display: flex;
justify-content: space-between; /* Space them out */
gap: 15px;
margin-top: 20px;
}
.modal-buttons-inline .modal-button {
flex-grow: 1; /* Allow buttons to grow to fill space */
max-width: none; /* Override max-width from .modal-buttons */
}
/* Eye icon styles */
.field .toggle-password {
position: absolute;
right: 15px;
top: 50%;
transform: translateY(-50%);
cursor: pointer;
color: var(--bs-secondary-color);
font-size: 0.9rem;
}
.field .toggle-password:hover {
color: var(--gakr-blue);
}
/* Added styles for Forgot/Reset Modals */
.modal-form {
text-align: left;
margin-top: 20px;
}
.modal-form .field {
margin-bottom: 15px;
height: auto; /* Override default field height */
}
.modal-form label {
display: block;
margin-bottom: 5px;
font-size: 0.9rem;
color: var(--bs-body-color);
}
.otp-input-container {
display: flex;
gap: 5px;
justify-content: center;
margin-bottom: 10px;
}
.otp-digit {
width: 40px;
height: 40px;
text-align: center;
border: 1px solid var(--bs-border-color);
border-radius: 5px;
font-size: 1.2rem;
background-color: var(--bs-body-bg);
color: var(--bs-body-color);
}
.otp-digit:focus {
border-color: var(--gakr-blue);
outline: none;
}
.modal-form .error-message {
position: static; /* Remove absolute positioning */
margin-top: 5px;
margin-left: 0;
white-space: normal; /* Allow message to wrap */
}
.modal-buttons {
display: flex;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.modal-buttons button {
width: 100%;
max-width: 150px;
}
/* Header styles */
.gakr-chat-header {
padding: 0.75rem 1.5rem;
display: flex;
justify-content: space-between;
align-items: center;
border-bottom: 1px solid var(--bs-border-color);
height: 64px;
position: fixed;
top: 0;
left: 0;
right: 0;
width: 100%;
background-color: var(--bs-body-bg);
z-index: 10;
}
.gakr-logo-area {
display: flex;
align-items: center;
gap: 0.75rem;
}
.gakr-brand-logo {
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.gakr-brand-text {
font-size: 1.25rem;
font-weight: 500;
color: var(--gakr-blue);
}
.gakr-nav-controls {
display: flex;
align-items: center;
}
.gakr-login-button {
color: var(--gakr-blue);
text-decoration: none;
background: transparent;
border: 1px solid var(--gakr-blue);
padding: 0.5rem 1rem;
border-radius: 50px;
font-size: 0.9rem;
transition: background-color 0.2s;
}
.gakr-login-button:hover {
background-color: rgba(66, 133, 244, 0.1);
}
.gakr-profile-button {
color: var(--gakr-blue);
text-decoration: none;
background: transparent;
border: none;
padding: 0.5rem 1rem;
border-radius: 50px;
font-size: 0.9rem;
transition: background-color 0.2s;
display: flex;
align-items: center;
gap: 0.5rem;
}
.gakr-profile-button:hover {
background-color: rgba(66, 133, 244, 0.1);
}
</style>
</head>
<body>
<header class="gakr-chat-header">
<div class="gakr-logo-area">
<a href="/" class="gakr-brand-logo">
<i class="fas fa-robot" style="color: var(--gakr-blue);"></i>
</a>
<a href="/" class="gakr-brand-text" style="text-decoration: none;">GAKR AI</a>
</div>
<div class="gakr-nav-controls" id="authButtonContainer">
<a href="/auth" class="gakr-login-button">Sign in / Register</a>
</div>
</header>
<div class="wrapper">
<div class="title-text">
<div class="title login">Login Form</div>
<div class="title signup">Signup Form</div>
</div>
<div class="form-container">
<div class="slide-controls">
<input type="radio" name="slide" id="login" checked>
<input type="radio" name="slide" id="signup">
<label for="login" class="slide login">Login</label>
<label for="signup" class="slide signup">Signup</label>
<div class="slider-tab"></div>
</div>
<div class="form-inner">
<form action="#" class="login" id="loginForm">
<div class="field">
<input type="text" placeholder="Username or Email" required id="loginUsernameOrEmail" name="username_or_email">
<div class="error-message" id="loginUsernameOrEmailError"></div>
</div>
<div class="field">
<input type="password" placeholder="Password" required id="loginPassword" name="password">
<span class="toggle-password fa-solid fa-eye-slash"></span>
<div class="error-message" id="loginPasswordError"></div>
</div>
<div class="pass-link"><a href="#" id="forgotPasswordLink">Forgot password?</a></div>
<div class="field btn">
<input type="submit" value="Login">
</div>
<div class="signup-link">Not a member? <a href="">Signup now</a></div>
</form>
<form action="#" class="signup" id="signupForm">
<div class="field">
<input type="text" placeholder="Name" required id="signupName" name="name">
<div class="error-message" id="signupNameError"></div>
</div>
<div class="field">
<input type="text" placeholder="Email Address" required id="signupEmail" name="email">
<div class="error-message" id="signupEmailError"></div>
</div>
<div class="field">
<input type="password" placeholder="Password" required id="signupPassword" name="password">
<span class="toggle-password fa-solid fa-eye-slash"></span>
<div class="error-message" id="signupPasswordError"></div>
</div>
<div class="field">
<input type="password" placeholder="Confirm password" required id="signupConfirmPassword" name="confirm_password">
<span class="toggle-password fa-solid fa-eye-slash"></span>
<div class="error-message" id="signupConfirmPasswordError"></div>
</div>
<div class="field btn">
<input type="submit" value="Signup">
</div>
</form>
</div>
</div>
<div class="form-message" id="generalFormMessage"></div>
</div>
<div class="modal-overlay" id="customModal">
<div class="modal-content">
<div class="modal-message" id="modalMessage"></div>
<div id="modalButtonsContainer" class="modal-buttons">
</div>
</div>
</div>
<div class="modal-overlay" id="forgotPasswordRequestModal">
<div class="modal-content" style="max-width: 450px;">
<h3 style="text-align: center; margin-bottom: 20px; color: var(--gakr-blue);">Reset Password</h3>
<p class="modal-message" id="forgotModalMessage" style="text-align: center; margin-bottom: 25px;">Enter your registered email address to receive a password reset code.</p>
<form id="forgotPasswordRequestForm" class="modal-form">
<div class="field" style="margin-bottom: 30px;">
<label for="forgotEmail" style="display: block; margin-bottom: 8px; font-weight: 500; color: var(--bs-body-color);">Email Address:</label>
<input type="email" id="forgotEmail" name="email" required style="width: 100%; padding: 12px; border: 2px solid var(--bs-border-color); border-radius: 8px; font-size: 16px; background-color: var(--bs-body-bg); color: var(--bs-body-color);" placeholder="Enter your email">
<div class="error-message" id="forgotEmailError" style="margin-top: 8px;"></div>
</div>
<div class="modal-buttons-stacked">
<button type="submit" class="modal-button" id="forgotSubmitButton" style="width: 100%; padding: 12px; font-size: 16px;">Send Reset Code</button>
<button type="button" class="modal-button" id="forgotCancelButton" style="width: 100%; padding: 12px; font-size: 16px; background-color: var(--bs-secondary-color); margin-top: 10px;">Cancel</button>
</div>
</form>
<div id="otpSection" style="display: none;">
<h3 style="text-align: center; margin-bottom: 20px; color: var(--gakr-blue);">Enter Verification Code</h3>
<p style="text-align: center; margin-bottom: 25px; color: var(--bs-secondary-color);">We've sent a 8-digit code to your email. Enter it below to continue.</p>
<form id="verifyOtpForm" class="modal-form">
<div class="field" style="margin-bottom: 30px;">
<label style="display: block; margin-bottom: 15px; font-weight: 500; color: var(--bs-body-color); text-align: center;">Verification Code:</label>
<div class="otp-input-container" style="display: flex; justify-content: center; gap: 8px; margin-bottom: 10px;">
<input type="text" class="otp-digit" maxlength="1" pattern="\d" required style="width: 45px; height: 50px; text-align: center; font-size: 20px; font-weight: bold; border: 2px solid var(--bs-border-color); border-radius: 8px; background-color: var(--bs-body-bg); color: var(--bs-body-color);">
<input type="text" class="otp-digit" maxlength="1" pattern="\d" required style="width: 45px; height: 50px; text-align: center; font-size: 20px; font-weight: bold; border: 2px solid var(--bs-border-color); border-radius: 8px; background-color: var(--bs-body-bg); color: var(--bs-body-color);">
<input type="text" class="otp-digit" maxlength="1" pattern="\d" required style="width: 45px; height: 50px; text-align: center; font-size: 20px; font-weight: bold; border: 2px solid var(--bs-border-color); border-radius: 8px; background-color: var(--bs-body-bg); color: var(--bs-body-color);">
<input type="text" class="otp-digit" maxlength="1" pattern="\d" required style="width: 45px; height: 50px; text-align: center; font-size: 20px; font-weight: bold; border: 2px solid var(--bs-border-color); border-radius: 8px; background-color: var(--bs-body-bg); color: var(--bs-body-color);">
<input type="text" class="otp-digit" maxlength="1" pattern="\d" required style="width: 45px; height: 50px; text-align: center; font-size: 20px; font-weight: bold; border: 2px solid var(--bs-border-color); border-radius: 8px; background-color: var(--bs-body-bg); color: var(--bs-body-color);">
<input type="text" class="otp-digit" maxlength="1" pattern="\d" required style="width: 45px; height: 50px; text-align: center; font-size: 20px; font-weight: bold; border: 2px solid var(--bs-border-color); border-radius: 8px; background-color: var(--bs-body-bg); color: var(--bs-body-color);">
<input type="text" class="otp-digit" maxlength="1" pattern="\d" required style="width: 45px; height: 50px; text-align: center; font-size: 20px; font-weight: bold; border: 2px solid var(--bs-border-color); border-radius: 8px; background-color: var(--bs-body-bg); color: var(--bs-body-color);">
<input type="text" class="otp-digit" maxlength="1" pattern="\d" required style="width: 45px; height: 50px; text-align: center; font-size: 20px; font-weight: bold; border: 2px solid var(--bs-border-color); border-radius: 8px; background-color: var(--bs-body-bg); color: var(--bs-body-color);">
</div>
<input type="hidden" id="otpInput" name="otp">
<div class="error-message" id="otpError" style="text-align: center; margin-top: 10px;"></div>
</div>
<div class="modal-buttons-stacked">
<button type="submit" class="modal-button" id="verifyOtpButton" style="width: 100%; padding: 12px; font-size: 16px;">Verify Code</button>
<button type="button" class="modal-button" id="resendOtpButton" style="width: 100%; padding: 12px; font-size: 16px; margin-top: 10px;">Send Code Again</button>
<button type="button" class="modal-button" id="otpCancelButton" style="width: 100%; padding: 12px; font-size: 16px; background-color: var(--bs-secondary-color); margin-top: 10px;">Cancel</button>
</div>
</form>
</div>
</div>
</div>
<div class="modal-overlay" id="resetPasswordModal">
<div class="modal-content">
<p class="modal-message">Enter your new password.</p>
<form id="resetPasswordForm" class="modal-form">
<div class="field">
<label for="resetEmail">Email:</label>
<input type="email" id="resetEmail" name="email" required readonly style="background-color: var(--bs-secondary-bg); cursor: not-allowed;">
<div class="error-message" id="resetEmailError"></div>
</div>
<div class="field">
<label for="newPassword">New Password:</label>
<input type="password" id="newPassword" name="new_password" required>
<span class="toggle-password fa-solid fa-eye-slash"></span>
<div class="error-message" id="newPasswordError"></div>
</div>
<div class="field">
<label for="confirmNewPassword">Confirm New Password:</label>
<input type="password" id="confirmNewPassword" name="confirm_new_password" required>
<span class="toggle-password fa-solid fa-eye-slash"></span>
<div class="error-message" id="confirmNewPasswordError"></div>
</div>
<div class="modal-buttons-stacked">
<button type="submit" class="modal-button" id="resetSubmitButton">Reset Password</button>
<button type="button" class="modal-button" id="resetCancelButton" style="background-color: var(--bs-secondary-color);">Cancel</button>
</div>
</form>
</div>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// --- Authentication Status Check ---
function updateAuthButton() {
const authButtonContainer = document.getElementById('authButtonContainer');
const userIsLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
if (userIsLoggedIn) {
authButtonContainer.innerHTML = `
<a href="/profile" class="gakr-profile-button">
<i class="fas fa-user-circle me-2"></i>Profile
</a>
`;
const profileButton = authButtonContainer.querySelector('.gakr-profile-button');
if (profileButton) {
profileButton.addEventListener('click', function(event) {
console.log("Profile button clicked!");
});
}
} else {
authButtonContainer.innerHTML = `
<a href="/auth" class="gakr-login-button">Sign in / Register</a>
`;
}
}
updateAuthButton();
window.addEventListener('focus', updateAuthButton);
window.addEventListener('storage', updateAuthButton);
const loginText = document.querySelector(".title-text .login");
const loginFormDiv = document.querySelector("form.login");
const signupLink = document.querySelector("form .signup-link a");
const loginRadio = document.getElementById('login');
const signupRadio = document.getElementById('signup');
const loginForm = document.getElementById('loginForm');
const signupForm = document.getElementById('signupForm');
// Existing form inputs and error displays
const loginUsernameOrEmailInput = document.getElementById('loginUsernameOrEmail');
const loginPasswordInput = document.getElementById('loginPassword');
const loginSubmitButton = loginForm ? loginForm.querySelector('input[type="submit"]') : null;
const signupNameInput = document.getElementById('signupName');
const signupEmailInput = document.getElementById('signupEmail');
const signupPasswordInput = document.getElementById('signupPassword');
const signupConfirmPasswordInput = document.getElementById('signupConfirmPassword');
const signupSubmitButton = signupForm ? signupForm.querySelector('input[type="submit"]') : null;
const loginUsernameOrEmailError = document.getElementById('loginUsernameOrEmailError');
const loginPasswordError = document.getElementById('loginPasswordError');
const signupNameError = document.getElementById('signupNameError');
const signupEmailError = document.getElementById('signupEmailError');
const signupPasswordError = document.getElementById('signupPasswordError');
const signupConfirmPasswordError = document.getElementById('signupConfirmPasswordError');
const generalFormMessage = document.getElementById('generalFormMessage');
// Modal elements (main alert modal)
const customModal = document.getElementById('customModal');
const modalMessageDiv = document.getElementById('modalMessage');
const modalButtonsContainer = document.getElementById('modalButtonsContainer');
// OTP digit inputs
const otpDigits = document.querySelectorAll('.otp-digit');
otpDigits.forEach((input, index) => {
input.addEventListener('input', function() {
if (this.value.length === 1 && index < otpDigits.length - 1) {
otpDigits[index + 1].focus();
}
});
input.addEventListener('keydown', function(e) {
if (e.key === 'Backspace' && this.value === '' && index > 0) {
otpDigits[index - 1].focus();
}
});
});
const forgotPasswordLink = document.getElementById('forgotPasswordLink');
const forgotPasswordRequestModal = document.getElementById('forgotPasswordRequestModal'); // The main overlay for forgot/otp
const forgotModalMessage = document.getElementById('forgotModalMessage'); // Message within the forgot/otp modal
// Elements for Step 1: Request Email
const forgotPasswordRequestForm = document.getElementById('forgotPasswordRequestForm');
const forgotEmailInput = document.getElementById('forgotEmail');
const forgotEmailError = document.getElementById('forgotEmailError');
const forgotSubmitButton = document.getElementById('forgotSubmitButton');
const forgotCancelButton = document.getElementById('forgotCancelButton');
// Elements for Step 2: Enter OTP
const otpSection = document.getElementById('otpSection'); // The container for OTP input and buttons
const verifyOtpForm = document.getElementById('verifyOtpForm');
const otpInput = document.getElementById('otpInput');
const otpError = document.getElementById('otpError');
const verifyOtpButton = document.getElementById('verifyOtpButton');
const resendOtpButton = document.getElementById('resendOtpButton');
const otpCancelButton = document.getElementById('otpCancelButton');
// Reset Password Modal elements (Step 3: New Password)
const resetPasswordModal = document.getElementById('resetPasswordModal');
const resetPasswordForm = document.getElementById('resetPasswordForm');
const resetEmailInput = document.getElementById('resetEmail'); // Hidden but read-only email field
const newPasswordInput = document.getElementById('newPassword');
const confirmNewPasswordInput = document.getElementById('confirmNewPassword');
const resetEmailError = document.getElementById('resetEmailError'); // Error for reset email (should be rare)
const newPasswordError = document.getElementById('newPasswordError');
const confirmNewPasswordError = document.getElementById('confirmNewPasswordError');
const resetSubmitButton = document.getElementById('resetSubmitButton');
const resetCancelButton = document.getElementById('resetCancelButton');
// New: Login Attempt Counter
let loginAttempts = 0;
const MAX_LOGIN_ATTEMPTS = 3;
// Stores the email across the forgot password/OTP/reset flow
let currentForgotEmail = '';
// Resend OTP cooldown variables
let resendCooldown = 0;
let resendInterval = null;
// Function to start resend cooldown timer
function startResendCooldown() {
resendCooldown = 60; // 60 seconds
resendOtpButton.disabled = true;
resendOtpButton.textContent = `Send Again (60s)`;
resendOtpButton.style.backgroundColor = 'var(--bs-secondary-color)'; // Gray during cooldown
resendInterval = setInterval(() => {
resendCooldown--;
if (resendCooldown > 0) {
resendOtpButton.textContent = `Send Again (${resendCooldown}s)`;
} else {
clearInterval(resendInterval);
resendInterval = null;
resendOtpButton.disabled = false;
resendOtpButton.textContent = 'Send Code Again';
resendOtpButton.style.backgroundColor = ''; // Reset to default (primary blue)
}
}, 1000);
}
// Function to clear all error messages and input error classes
function clearErrors(formType = 'all') {
if (formType === 'all' || formType === 'main') {
document.querySelectorAll('.error-message').forEach(el => el.textContent = '');
document.querySelectorAll('.form-inner form .field input').forEach(el => el.classList.remove('error'));
if (generalFormMessage) {
generalFormMessage.textContent = '';
generalFormMessage.className = 'form-message';
}
}
if (formType === 'all' || formType === 'forgot') {
if (forgotEmailError) forgotEmailError.textContent = '';
if (forgotEmailInput) forgotEmailInput.classList.remove('error');
// Also clear the general forgotModalMessage when resetting for new flow
if (forgotModalMessage) forgotModalMessage.innerHTML = 'Enter your registered email to request a password reset.';
}
if (formType === 'all' || formType === 'otp') {
if (otpError) otpError.textContent = '';
if (otpInput) otpInput.classList.remove('error');
}
if (formType === 'all' || formType === 'reset') {
if (resetEmailError) resetEmailError.textContent = '';
if (newPasswordError) newPasswordError.textContent = '';
if (confirmNewPasswordError) confirmNewPasswordError.textContent = '';
if (resetEmailInput) resetEmailInput.classList.remove('error');
if (newPasswordInput) newPasswordInput.classList.remove('error');
if (confirmNewPasswordInput) confirmNewPasswordInput.classList.remove('error');
}
}
// Function to display general form-level message (below the forms)
function displayFormMessage(message, type = 'info') {
if (generalFormMessage) {
generalFormMessage.textContent = message;
generalFormMessage.className = 'form-message ' + type;
}
}
// Universal modal display function
function showCustomModal(modalElement) {
modalElement.classList.add('show');
}
// Universal modal hide function
function hideCustomModal(modalElement) {
modalElement.classList.remove('show');
clearErrors(); // Clear all errors when any modal is hidden
}
// Function to show a flexible custom modal (for general alerts/info)
function showFlexibleModal(messageHTML, buttonsConfig, callback = null) {
modalMessageDiv.innerHTML = messageHTML;
modalButtonsContainer.innerHTML = ''; // Clear previous buttons
if (buttonsConfig && buttonsConfig.length > 0) {
const isInline = buttonsConfig.every(btn => !btn.newline);
modalButtonsContainer.className = isInline ? 'modal-buttons-inline' : 'modal-buttons-stacked';
buttonsConfig.forEach(btnConf => {
const button = document.createElement('button');
button.className = 'modal-button';
button.textContent = btnConf.text;
if (btnConf.style) {
button.style = btnConf.style;
}
button.onclick = function() {
hideCustomModal(customModal);
if (btnConf.action && typeof btnConf.action === 'function') {
btnConf.action();
}
if (callback && typeof callback === 'function') {
callback();
}
};
modalButtonsContainer.appendChild(button);
});
} else {
const okButton = document.createElement('button');
okButton.className = 'modal-button';
okButton.textContent = 'OK';
okButton.onclick = function() {
hideCustomModal(customModal);
if (callback && typeof callback === 'function') {
callback();
}
};
modalButtonsContainer.classList.remove('modal-buttons-inline', 'modal-buttons-stacked');
modalButtonsContainer.classList.add('modal-buttons');
modalButtonsContainer.appendChild(okButton);
}
showCustomModal(customModal);
}
// Slide logic
function slideToSignup() {
if (loginFormDiv && loginText) {
loginFormDiv.style.marginLeft = "-50%";
loginText.style.marginLeft = "-50%";
clearErrors('main');
}
}
function slideToLogin() {
if (loginFormDiv && loginText) {
loginFormDiv.style.marginLeft = "0%";
loginText.style.marginLeft = "0%";
clearErrors('main');
}
}
// Event listeners for radio buttons and signup link
if (signupRadio) {
signupRadio.addEventListener('change', function() {
if (this.checked) {
slideToSignup();
}
});
}
if (loginRadio) {
loginRadio.addEventListener('change', function() {
if (this.checked) {
slideToLogin();
}
});
}
if (signupLink && signupRadio) {
signupLink.onclick = ((e) => {
e.preventDefault();
signupRadio.checked = true;
slideToSignup();
});
}
// Handle URL parameter for initial tab
const urlParams = new URLSearchParams(window.location.search);
const showSignup = urlParams.get('signup');
if (showSignup === 'true') {
if (signupRadio) {
signupRadio.checked = true;
slideToSignup();
}
} else {
if (loginRadio) {
loginRadio.checked = true;
slideToLogin();
}
}
// Function to toggle password visibility
function setupPasswordToggle(passwordInput, toggleIcon) {
if (passwordInput && toggleIcon) {
toggleIcon.addEventListener('click', function() {
const type = passwordInput.getAttribute('type') === 'password' ? 'text' : 'password';
passwordInput.setAttribute('type', type);
this.classList.toggle('fa-eye');
this.classList.toggle('fa-eye-slash');
});
}
}
// Setup password toggles for all password fields
setupPasswordToggle(loginPasswordInput, document.querySelector('#loginForm .toggle-password'));
setupPasswordToggle(signupPasswordInput, document.querySelector('#signupForm #signupPassword + .toggle-password'));
setupPasswordToggle(signupConfirmPasswordInput, document.querySelector('#signupForm #signupConfirmPassword + .toggle-password'));
setupPasswordToggle(newPasswordInput, document.querySelector('#resetPasswordForm #newPassword + .toggle-password'));
setupPasswordToggle(confirmNewPasswordInput, document.querySelector('#resetPasswordForm #confirmNewPassword + .toggle-password'));
// Login Form Submission Handler
if (loginForm) {
loginForm.addEventListener('submit', async function(event) {
event.preventDefault();
clearErrors('main'); // Clear main form errors
// Only proceed if login attempts are below limit
if (loginAttempts >= MAX_LOGIN_ATTEMPTS) {
showLoginAttemptsLimitModal();
return; // Stop submission
}
let isValid = true;
const usernameOrEmail = loginUsernameOrEmailInput.value.trim();
const password = loginPasswordInput.value.trim();
if (usernameOrEmail === '') {
isValid = false;
loginUsernameOrEmailError.textContent = 'Username or Email is required';
loginUsernameOrEmailInput.classList.add('error');
}
if (password === '') {
isValid = false;
loginPasswordError.textContent = 'Password is required';
loginPasswordInput.classList.add('error');
}
if (isValid) {
// Check if user exists
try {
const checkResponse = await fetch('/api/check_user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier: usernameOrEmail })
});
const checkData = await checkResponse.json();
if (!checkData.exists) {
loginUsernameOrEmailError.textContent = 'User does not exist.';
loginUsernameOrEmailInput.classList.add('error');
return;
}
} catch (error) {
console.error('Error checking user:', error);
// Proceed anyway
}
submitFormData(event.target, loginSubmitButton, '/api/login', 'main');
}
});
}
// Signup Form Submission Handler
if (signupForm) {
signupForm.addEventListener('submit', async function(event) {
event.preventDefault();
clearErrors('main');
let isValid = true;
const name = signupNameInput.value.trim();
const email = signupEmailInput.value.trim();
const password = signupPasswordInput.value.trim();
const confirmPassword = signupConfirmPasswordInput.value.trim();
if (name === '') {
isValid = false;
signupNameError.textContent = 'Name is required';
signupNameInput.classList.add('error');
}
if (email === '') {
isValid = false;
signupEmailError.textContent = 'Email is required';
signupEmailInput.classList.add('error');
} else if (!isValidEmail(email)) {
isValid = false;
signupEmailError.textContent = 'Enter a valid email address';
signupEmailInput.classList.add('error');
}
if (password === '') {
isValid = false;
signupPasswordError.textContent = 'Password is required';
signupPasswordInput.classList.add('error');
} else if (password.length < 6) {
isValid = false;
signupPasswordError.textContent = 'Password must be at least 6 characters';
signupPasswordInput.classList.add('error');
}
if (confirmPassword === '') {
isValid = false;
signupConfirmPasswordError.textContent = 'Confirm password is required';
signupConfirmPasswordInput.classList.add('error');
} else if (password !== confirmPassword) {
isValid = false;
signupConfirmPasswordError.textContent = 'Passwords do not match';
signupConfirmPasswordInput.classList.add('error');
signupPasswordInput.classList.add('error');
}
if (isValid) {
// Check if user already exists
try {
const checkResponse = await fetch('/api/check_user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ identifier: email })
});
const checkData = await checkResponse.json();
if (checkData.exists) {
signupEmailError.textContent = 'Email already registered.';
signupEmailInput.classList.add('error');
return;
}
} catch (error) {
console.error('Error checking user:', error);
// Proceed anyway
}
submitFormData(event.target, signupSubmitButton, '/api/signup', 'main');
}
});
}
// Forgot Password Link Handler (Initial Click)
if (forgotPasswordLink) {
forgotPasswordLink.addEventListener('click', function(event) {
event.preventDefault();
clearErrors('forgot'); // Clear errors for email request
clearErrors('otp'); // Clear errors for OTP section
forgotPasswordRequestForm.reset(); // Clear email input
otpInput.value = ''; // Clear OTP input
// Reset the general message for the forgot password modal
forgotModalMessage.innerHTML = 'Enter your registered email to request a password reset.';
// Show the email request form and hide the OTP section
forgotPasswordRequestForm.style.display = 'block';
if (otpSection) otpSection.style.display = 'none'; // Ensure OTP section is hidden initially
// Reset modal size for email input
const modalContent = forgotPasswordRequestModal.querySelector('.modal-content');
if (modalContent) {
modalContent.style.maxWidth = '450px';
}
showCustomModal(forgotPasswordRequestModal);
});
}
// Step 1: Forgot Password Request Form Submission Handler (Requests OTP)
if (forgotPasswordRequestForm) {
forgotPasswordRequestForm.addEventListener('submit', async function(event) {
event.preventDefault();
clearErrors('forgot'); // Clear errors specific to the email request form
let isValid = true;
const email = forgotEmailInput.value.trim();
if (email === '') {
isValid = false;
forgotEmailError.textContent = 'Email is required.';
forgotEmailInput.classList.add('error');
} else if (!isValidEmail(email)) {
isValid = false;
forgotEmailError.textContent = 'Enter a valid email address.';
forgotEmailInput.classList.add('error');
}
if (isValid) {
forgotSubmitButton.disabled = true;
forgotSubmitButton.innerHTML = '<span class="loading-spinner"></span> Sending OTP...';
try {
const response = await fetch('/api/forgot_password_request', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email })
});
const result = await response.json();
if (response.ok) {
currentForgotEmail = email; // Store email for subsequent steps
// Transition to OTP input section
forgotPasswordRequestForm.style.display = 'none';
if (otpSection) otpSection.style.display = 'block';
otpInput.value = ''; // Clear previous OTP in case of resend
// Increase modal size for OTP section
const modalContent = forgotPasswordRequestModal.querySelector('.modal-content');
if (modalContent) {
modalContent.style.maxWidth = '550px';
}
// Update message for OTP section
let messageText = `<p>An OTP has been sent to <strong>${email}</strong>. Please enter it below.</p>`;
// Note: Do not display simulated OTP in production. This is for testing convenience.
// if (result.simulated_otp) {
// messageText += `<p><strong>SIMULATED OTP (FOR TESTING):</strong> ${result.simulated_otp}</p>`;
// }
forgotModalMessage.innerHTML = messageText;
startResendCooldown(); // Start the cooldown timer for the resend button
} else {
// Handle different error types
if (response.status === 404) {
// Email not registered - show registration modal
hideCustomModal(forgotPasswordRequestModal);
showFlexibleModal(
'<p>The email address you entered is not registered yet.</p><p>Please sign up to create an account.</p>',
[
{ text: 'Sign Up', action: () => {
signupRadio.checked = true;
slideToSignup();
// Pre-fill the email in signup form
signupEmailInput.value = email;
}},
{ text: 'Try Again', style: 'background-color: var(--bs-secondary-color);' }
]
);
} else {
// Other errors (400, 500) - show in modal
result.status = response.status;
displayModalFormErrors(result, forgotPasswordRequestForm, 'forgot');
}
}
} catch (error) {
console.error('Fetch error:', error);
showFlexibleModal('<p>Network error. Could not request OTP. Please try again.</p>', [{ text: 'OK' }]);
} finally {
forgotSubmitButton.disabled = false;
forgotSubmitButton.innerHTML = 'Request OTP';
}
}
});
}
// Step 2: OTP Verification Form Submission Handler
if (verifyOtpForm) {
verifyOtpForm.addEventListener('submit', async function(event) {
event.preventDefault();
clearErrors('otp'); // Clear errors specific to the OTP verification form
let isValid = true;
const otpDigits = document.querySelectorAll('.otp-digit');
const otp = Array.from(otpDigits).map(d => d.value).join('');
if (otp.length !== 8 || !/^\d{8}$/.test(otp)) {
isValid = false;
otpError.textContent = 'Please enter all 8 digits of the OTP.';
otpDigits.forEach(d => d.classList.add('error'));
}
if (isValid) {
otpInput.value = otp; // Set hidden input
verifyOtpButton.disabled = true;
verifyOtpButton.innerHTML = '<span class="loading-spinner"></span> Verifying...';
try {
const response = await fetch('/api/verify_otp', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: currentForgotEmail, otp: otp })
});
const result = await response.json();
if (response.ok) {
hideCustomModal(forgotPasswordRequestModal); // Hide the OTP modal
resetEmailInput.value = currentForgotEmail; // Pre-fill email in new password form
newPasswordInput.value = ''; // Clear new password field
confirmNewPasswordInput.value = ''; // Clear confirm new password field
showCustomModal(resetPasswordModal); // Show the new password modal
} else {
otpError.textContent = result.message || 'OTP verification failed.';
otpInput.classList.add('error');
otpInput.classList.add('error'); // Ensure input field is marked
// Update the main message area of the OTP modal if a general message is returned
if (result.message) {
forgotModalMessage.innerHTML = `<p>${result.message}</p>`;
}
}
} catch (error) {
console.error('Fetch error:', error);
showFlexibleModal('<p>Network error. Could not verify OTP. Please try again.</p>', [{ text: 'OK' }]);
} finally {
verifyOtpButton.disabled = false;
verifyOtpButton.innerHTML = 'Verify OTP';
}
}
});
}
// Step 2: Resend OTP Button Handler
if (resendOtpButton) {
resendOtpButton.addEventListener('click', async function(event) {
event.preventDefault();
clearErrors('otp'); // Clear any previous OTP errors
resendOtpButton.disabled = true;
resendOtpButton.innerHTML = '<span class="loading-spinner"></span> Resending...';
try {
const response = await fetch('/api/forgot_password_request', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: currentForgotEmail })
});
const result = await response.json();
if (response.ok) {
let messageText = `<p>New OTP sent to <strong>${currentForgotEmail}</strong>. Please enter it below.</p>`;
// if (result.simulated_otp) { // Again, remove in production
// messageText += `<p><strong>SIMULATED OTP (FOR TESTING):</strong> ${result.simulated_otp}</p>`;
// }
forgotModalMessage.innerHTML = messageText;
otpInput.value = ''; // Clear previous OTP
otpInput.classList.remove('error'); // Clear error state
otpError.textContent = ''; // Clear error message
startResendCooldown(); // Start the cooldown timer
} else {
// If there's an error on resend, display it in the OTP section's error message
otpError.textContent = result.message || 'Failed to resend OTP.';
otpInput.classList.add('error'); // Potentially highlight OTP input again
if (result.message) {
forgotModalMessage.innerHTML = `<p>${result.message}</p>`;
}
}
} catch (error) {
console.error('Fetch error:', error);
showFlexibleModal('<p>Network error. Could not resend OTP. Please try again.</p>', [{ text: 'OK' }]);
} finally {
resendOtpButton.disabled = false;
resendOtpButton.innerHTML = 'Send Again';
}
});
}
// Handlers for Cancel buttons in the forgot/reset modals
if (forgotCancelButton) {
forgotCancelButton.addEventListener('click', function() {
hideCustomModal(forgotPasswordRequestModal);
forgotPasswordRequestForm.reset();
currentForgotEmail = ''; // Clear stored email
clearErrors('forgot'); // Ensure all forgot-related errors are cleared
// Clear resend cooldown
if (resendInterval) {
clearInterval(resendInterval);
resendInterval = null;
resendCooldown = 0;
}
// Reset resend button styling
resendOtpButton.disabled = false;
resendOtpButton.textContent = 'Send Code Again';
resendOtpButton.style.backgroundColor = '';
});
}
if (otpCancelButton) {
otpCancelButton.addEventListener('click', function() {
hideCustomModal(forgotPasswordRequestModal);
verifyOtpForm.reset();
currentForgotEmail = ''; // Clear stored email
clearErrors('otp'); // Ensure all OTP-related errors are cleared
// Clear resend cooldown
if (resendInterval) {
clearInterval(resendInterval);
resendInterval = null;
resendCooldown = 0;
}
// Reset resend button styling
resendOtpButton.disabled = false;
resendOtpButton.textContent = 'Send Code Again';
resendOtpButton.style.backgroundColor = '';
});
}
if (resetCancelButton) {
resetCancelButton.addEventListener('click', function() {
hideCustomModal(resetPasswordModal);
resetPasswordForm.reset();
currentForgotEmail = ''; // Clear stored email
clearErrors('reset'); // Ensure all reset-related errors are cleared
// Clear resend cooldown
if (resendInterval) {
clearInterval(resendInterval);
resendInterval = null;
resendCooldown = 0;
}
// Reset resend button styling
resendOtpButton.disabled = false;
resendOtpButton.textContent = 'Send Code Again';
resendOtpButton.style.backgroundColor = '';
});
}
// Step 3: Reset Password Form Submission Handler
if (resetPasswordForm) {
resetPasswordForm.addEventListener('submit', async function(event) {
event.preventDefault();
clearErrors('reset'); // Clear errors specific to the reset password form
let isValid = true;
const email = resetEmailInput.value.trim(); // This should be currentForgotEmail
const newPassword = newPasswordInput.value.trim();
const confirmNewPassword = confirmNewPasswordInput.value.trim();
if (email === '') { // Should be pre-filled, but a final check
isValid = false;
resetEmailError.textContent = 'Email is required.';
resetEmailInput.classList.add('error');
} else if (!isValidEmail(email)) {
isValid = false;
resetEmailError.textContent = 'Invalid email format.'; // Should not happen if pre-filled correctly
resetEmailInput.classList.add('error');
}
if (newPassword === '') {
isValid = false;
newPasswordError.textContent = 'New password is required.';
newPasswordInput.classList.add('error');
} else if (newPassword.length < 6) {
isValid = false;
newPasswordError.textContent = 'New password must be at least 6 characters.';
newPasswordInput.classList.add('error');
}
if (confirmNewPassword === '') {
isValid = false;
confirmNewPasswordError.textContent = 'Confirm new password is required.';
confirmNewPasswordInput.classList.add('error');
} else if (newPassword !== confirmNewPassword) {
isValid = false;
confirmNewPasswordError.textContent = 'Passwords do not match.';
confirmNewPasswordInput.classList.add('error');
newPasswordInput.classList.add('error'); // Highlight both if they don't match
}
if (isValid) {
resetSubmitButton.disabled = true;
resetSubmitButton.innerHTML = '<span class="loading-spinner"></span> Resetting...';
try {
const response = await fetch('/api/reset_password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: email, new_password: newPassword }) // Ensure correct variable name for new password
});
const result = await response.json();
if (response.ok) {
hideCustomModal(resetPasswordModal);
resetPasswordForm.reset();
currentForgotEmail = ''; // Clear stored email after successful reset
// Clear resend cooldown
if (resendInterval) {
clearInterval(resendInterval);
resendInterval = null;
resendCooldown = 0;
}
// Reset resend button styling
resendOtpButton.disabled = false;
resendOtpButton.textContent = 'Send Code Again';
resendOtpButton.style.backgroundColor = '';
showFlexibleModal(
'<p>Password reset successful. You can now login with your new password.</p>',
[{ text: 'OK', action: () => {
loginRadio.checked = true;
slideToLogin();
}}]
);
} else {
// Display error messages from the backend
result.status = response.status;
displayModalFormErrors(result, resetPasswordForm, 'reset');
if (result.message && (result.message.includes('OTP not verified') || result.message.includes('not initiated'))) {
showFlexibleModal(
`<p>${result.message}</p><p>Please start the password reset process again.</p>`,
[{ text: 'Start Over', action: () => {
hideCustomModal(resetPasswordModal);
forgotPasswordLink.click(); // Re-open the initial forgot password modal
}}],
null // No general callback
);
}
}
} catch (error) {
console.error('Fetch error:', error);
showFlexibleModal('<p>Network error. Could not reset password. Please try again.</p>', [{ text: 'OK' }]);
} finally {
resetSubmitButton.disabled = false;
resetSubmitButton.innerHTML = 'Reset Password';
}
}
});
}
// This function handles displaying errors from the backend for the main login/signup forms
function handleMainFormErrors(result, formElement, endpoint) {
if (endpoint === '/api/login') {
if (result.message && (result.message.includes('Account not found') || result.message.includes('Invalid credentials'))) {
showFlexibleModal(
`<p>${result.message}</p><p>If you are not registered, please sign up.</p>`,
[{ text: 'Sign Up', action: () => {
signupRadio.checked = true;
slideToSignup();
}}, { text: 'OK', style: 'background-color: var(--bs-secondary-color);' }]
);
}
} else if (endpoint === '/api/signup') {
if (result.message && (result.message.includes('Email already registered') || result.message.includes('Username already taken'))) {
showFlexibleModal(
`<p>${result.message}</p><p>If you are already registered, please log in.</p>`,
[{ text: 'Login', action: () => {
loginRadio.checked = true;
slideToLogin();
}}, { text: 'OK', style: 'background-color: var(--bs-secondary-color);' }]
);
}
}
if (result.errors) {
for (const field in result.errors) {
let inputElement, errorElement;
if (formElement.id === 'loginForm' && field === 'username_or_email') {
inputElement = loginUsernameOrEmailInput;
errorElement = loginUsernameOrEmailError;
} else if (formElement.id === 'signupForm' && field === 'name') {
inputElement = signupNameInput;
errorElement = signupNameError;
}
else {
// This handles password errors for login and email/password for signup
// Need to be careful with field names. Example: if backend returns 'password' for signup.
inputElement = formElement.querySelector(`[name="${field}"]`);
// This assumes predictable error element IDs based on input IDs
const inputId = inputElement ? inputElement.id : null;
errorElement = inputId ? document.getElementById(`${inputId}Error`) : null;
}
if (errorElement) errorElement.textContent = result.errors[field];
if (inputElement) inputElement.classList.add('error');
}
}
}
// Generic function for handling errors in modals (forgot, otp, reset)
function displayModalFormErrors(result, formElement, formContext) {
// Clear errors specific to the current modal's form context
clearErrors(formContext);
if (result.errors) {
for (const field in result.errors) {
// This is robust: Find the input element by its 'name' attribute
const inputElement = formElement.querySelector(`[name="${field}"]`);
// Then, find its associated error message element by looking for a sibling with the ID pattern
const errorElement = inputElement ? document.getElementById(`${inputElement.id}Error`) : null;
if (errorElement) errorElement.textContent = result.errors[field];
if (inputElement) inputElement.classList.add('error');
}
}
// Update the main message area of the modal if a general message is returned
const targetModalMessage = formElement.closest('.modal-content').querySelector('.modal-message');
if (targetModalMessage && result.message) {
// Check if this is an error response (status codes 400-599 indicate errors)
const isError = result.status >= 400 && result.status < 600;
const messageClass = isError ? 'error-message' : '';
targetModalMessage.innerHTML = `<p class="${messageClass}">${result.message}</p>`;
}
}
// Main data submission function (unchanged for main forms, adapted for modals)
async function submitFormData(formElement, submitButton, endpoint, formContext) {
if (!formElement || !submitButton || !endpoint) {
console.error("submitFormData called with missing arguments.");
return;
}
const formData = new FormData(formElement);
const data = {};
for (let [key, value] of formData.entries()) {
data[key] = value;
}
submitButton.disabled = true;
if (formContext === 'main') {
displayFormMessage("Submitting...", 'info');
} else {
// For modal buttons, update their innerHTML directly
submitButton.innerHTML = '<span class="loading-spinner"></span> Submitting...';
}
try {
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
const result = await response.json();
if (response.ok) {
if (formContext === 'main') {
clearErrors('main');
formElement.reset();
displayFormMessage('', 'info'); // Clear "Submitting..." message
loginAttempts = 0; // Reset login attempts on successful login
if (endpoint === '/api/signup') {
showFlexibleModal(
'<p>Registration successful! Please log in.</p>',
[{ text: 'OK', action: () => {
loginRadio.checked = true;
slideToLogin();
const url = new URL(window.location);
url.searchParams.delete('signup'); // Clean up URL
window.history.replaceState({}, '', url.toString());
}}]
);
} else if (endpoint === '/api/login') {
showFlexibleModal(
'<p>Login successful!</p>',
[{ text: 'OK', action: () => {
localStorage.setItem('isLoggedIn', 'true');
localStorage.setItem('user', JSON.stringify(result.user));
window.location.href = '/';
}}]
);
}
}
// For forgot/otp/reset, successful handling is now done directly in their respective event listeners
// so no generic handling here for those contexts.
} else { // Server returned an error (response.ok is false)
if (formContext === 'main') {
if (endpoint === '/api/login' && result.message && result.message.includes('Invalid credentials')) {
loginAttempts++;
loginPasswordInput.classList.add('error');
loginPasswordError.textContent = result.message;
if (loginAttempts >= MAX_LOGIN_ATTEMPTS) {
showLoginAttemptsLimitModal();
loginPasswordInput.value = ''; // Clear password after max attempts
} else {
displayFormMessage(`Incorrect password. You have ${MAX_LOGIN_ATTEMPTS - loginAttempts} attempts left.`, 'error');
}
} else {
displayFormMessage(result.message || 'An error occurred.', 'error');
handleMainFormErrors(result, formElement, endpoint);
}
} else if (formContext === 'forgot') {
result.status = response.status;
displayModalFormErrors(result, formElement, 'forgot');
} else if (formContext === 'otp') {
result.status = response.status;
displayModalFormErrors(result, formElement, 'otp');
} else if (formContext === 'reset') {
result.status = response.status;
displayModalFormErrors(result, formElement, 'reset');
}
}
} catch (error) {
console.error('Fetch error:', error);
if (formContext === 'main') {
displayFormMessage('Network error. Please try again.', 'error');
} else {
showFlexibleModal(
'<p>Network error. Please check your internet connection and try again.</p>',
[{ text: 'OK' }]
);
}
} finally {
submitButton.disabled = false;
// Reset button text specifically for modal forms if not handled by success/error flows above
if (formContext === 'forgot') {
submitButton.innerHTML = 'Send Reset Code';
} else if (formContext === 'otp') {
submitButton.innerHTML = 'Verify Code';
} else if (formContext === 'reset') {
submitButton.innerHTML = 'Reset Password';
}
}
}
function showLoginAttemptsLimitModal() {
const messageHtml = `
<strong>You have made three incorrect login attempts.</strong>
<ul>
<li>1. If you are not registered, please <a href="#" id="modalSignupLink">sign up now</a>.</li>
<li>2. If you are already registered, please <a href="#" id="modalLoginLink">login now</a> with correct credentials.</li>
<li>3. If you have forgotten your password, click <a href="#" id="modalForgotPasswordLink">forgot password</a> to reset it.</li>
</ul>
`;
const buttons = [
{ text: 'Login', action: () => {
loginRadio.checked = true;
slideToLogin();
loginAttempts = 0;
loginPasswordInput.value = '';
}},
{ text: 'Signup', action: () => {
signupRadio.checked = true;
slideToSignup();
loginAttempts = 0;
loginPasswordInput.value = '';
}}
];
showFlexibleModal(messageHtml, buttons, () => {
const modalSignupLink = document.getElementById('modalSignupLink');
const modalLoginLink = document.getElementById('modalLoginLink');
const modalForgotPasswordLink = document.getElementById('modalForgotPasswordLink');
if (modalSignupLink) {
modalSignupLink.onclick = (e) => {
e.preventDefault();
hideCustomModal(customModal);
signupRadio.checked = true;
slideToSignup();
loginAttempts = 0;
loginPasswordInput.value = '';
};
}
if (modalLoginLink) {
modalLoginLink.onclick = (e) => {
e.preventDefault();
hideCustomModal(customModal);
loginRadio.checked = true;
slideToLogin();
loginAttempts = 0;
loginPasswordInput.value = '';
};
}
if (modalForgotPasswordLink) {
modalForgotPasswordLink.onclick = (e) => {
e.preventDefault();
hideCustomModal(customModal);
forgotPasswordLink.click();
loginAttempts = 0;
loginPasswordInput.value = '';
};
}
});
}
function isValidEmail(email) {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
}
});
</script>
</body>
</html>