| <!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; |
| |
| 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; |
| } |
| |
| .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; |
| } |
| |
| .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-overlay { |
| position: fixed; |
| top: 0; |
| left: 0; |
| width: 100%; |
| height: 100%; |
| background-color: rgba(0, 0, 0, 0.6); |
| display: flex; |
| justify-content: center; |
| align-items: center; |
| z-index: 100; |
| visibility: hidden; |
| 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); |
| 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; |
| line-height: 1.4; |
| } |
| .modal-message strong { |
| font-size: 1.2rem; |
| 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; |
| gap: 10px; |
| margin-top: 20px; |
| } |
| |
| .modal-buttons-stacked .modal-button { |
| width: 100%; |
| } |
| |
| .modal-buttons-inline { |
| display: flex; |
| justify-content: space-between; |
| gap: 15px; |
| margin-top: 20px; |
| } |
| .modal-buttons-inline .modal-button { |
| flex-grow: 1; |
| max-width: none; |
| } |
| |
| |
| |
| .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); |
| } |
| |
| |
| .modal-form { |
| text-align: left; |
| margin-top: 20px; |
| } |
| .modal-form .field { |
| margin-bottom: 15px; |
| height: auto; |
| } |
| .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; |
| margin-top: 5px; |
| margin-left: 0; |
| white-space: normal; |
| } |
| .modal-buttons { |
| display: flex; |
| justify-content: center; |
| gap: 15px; |
| margin-top: 20px; |
| } |
| .modal-buttons button { |
| width: 100%; |
| max-width: 150px; |
| } |
| |
| |
| .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() { |
| |
| 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'); |
| |
| |
| 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'); |
| |
| |
| const customModal = document.getElementById('customModal'); |
| const modalMessageDiv = document.getElementById('modalMessage'); |
| const modalButtonsContainer = document.getElementById('modalButtonsContainer'); |
| |
| |
| 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'); |
| const forgotModalMessage = document.getElementById('forgotModalMessage'); |
| |
| |
| 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'); |
| |
| |
| const otpSection = document.getElementById('otpSection'); |
| 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'); |
| |
| |
| const resetPasswordModal = document.getElementById('resetPasswordModal'); |
| const resetPasswordForm = document.getElementById('resetPasswordForm'); |
| const resetEmailInput = document.getElementById('resetEmail'); |
| const newPasswordInput = document.getElementById('newPassword'); |
| const confirmNewPasswordInput = document.getElementById('confirmNewPassword'); |
| const resetEmailError = document.getElementById('resetEmailError'); |
| const newPasswordError = document.getElementById('newPasswordError'); |
| const confirmNewPasswordError = document.getElementById('confirmNewPasswordError'); |
| const resetSubmitButton = document.getElementById('resetSubmitButton'); |
| const resetCancelButton = document.getElementById('resetCancelButton'); |
| |
| |
| let loginAttempts = 0; |
| const MAX_LOGIN_ATTEMPTS = 3; |
| |
| |
| let currentForgotEmail = ''; |
| |
| |
| let resendCooldown = 0; |
| let resendInterval = null; |
| |
| |
| function startResendCooldown() { |
| resendCooldown = 60; |
| resendOtpButton.disabled = true; |
| resendOtpButton.textContent = `Send Again (60s)`; |
| resendOtpButton.style.backgroundColor = 'var(--bs-secondary-color)'; |
| |
| 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 = ''; |
| } |
| }, 1000); |
| } |
| |
| |
| 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'); |
| |
| 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 displayFormMessage(message, type = 'info') { |
| if (generalFormMessage) { |
| generalFormMessage.textContent = message; |
| generalFormMessage.className = 'form-message ' + type; |
| } |
| } |
| |
| |
| function showCustomModal(modalElement) { |
| modalElement.classList.add('show'); |
| } |
| |
| |
| function hideCustomModal(modalElement) { |
| modalElement.classList.remove('show'); |
| clearErrors(); |
| } |
| |
| |
| function showFlexibleModal(messageHTML, buttonsConfig, callback = null) { |
| modalMessageDiv.innerHTML = messageHTML; |
| modalButtonsContainer.innerHTML = ''; |
| |
| 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); |
| } |
| |
| |
| 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'); |
| } |
| } |
| |
| |
| 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(); |
| }); |
| } |
| |
| |
| 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 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'); |
| }); |
| } |
| } |
| |
| |
| 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')); |
| |
| |
| |
| if (loginForm) { |
| loginForm.addEventListener('submit', async function(event) { |
| event.preventDefault(); |
| clearErrors('main'); |
| |
| |
| if (loginAttempts >= MAX_LOGIN_ATTEMPTS) { |
| showLoginAttemptsLimitModal(); |
| return; |
| } |
| |
| 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) { |
| |
| 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); |
| |
| } |
| submitFormData(event.target, loginSubmitButton, '/api/login', 'main'); |
| } |
| }); |
| } |
| |
| |
| 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) { |
| |
| 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); |
| |
| } |
| submitFormData(event.target, signupSubmitButton, '/api/signup', 'main'); |
| } |
| }); |
| } |
| |
| |
| if (forgotPasswordLink) { |
| forgotPasswordLink.addEventListener('click', function(event) { |
| event.preventDefault(); |
| clearErrors('forgot'); |
| clearErrors('otp'); |
| |
| forgotPasswordRequestForm.reset(); |
| otpInput.value = ''; |
| |
| forgotModalMessage.innerHTML = 'Enter your registered email to request a password reset.'; |
| |
| |
| forgotPasswordRequestForm.style.display = 'block'; |
| if (otpSection) otpSection.style.display = 'none'; |
| |
| |
| const modalContent = forgotPasswordRequestModal.querySelector('.modal-content'); |
| if (modalContent) { |
| modalContent.style.maxWidth = '450px'; |
| } |
| |
| showCustomModal(forgotPasswordRequestModal); |
| }); |
| } |
| |
| |
| if (forgotPasswordRequestForm) { |
| forgotPasswordRequestForm.addEventListener('submit', async function(event) { |
| event.preventDefault(); |
| clearErrors('forgot'); |
| |
| 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; |
| |
| |
| forgotPasswordRequestForm.style.display = 'none'; |
| if (otpSection) otpSection.style.display = 'block'; |
| otpInput.value = ''; |
| |
| |
| const modalContent = forgotPasswordRequestModal.querySelector('.modal-content'); |
| if (modalContent) { |
| modalContent.style.maxWidth = '550px'; |
| } |
| |
| |
| let messageText = `<p>An OTP has been sent to <strong>${email}</strong>. Please enter it below.</p>`; |
| |
| |
| |
| |
| forgotModalMessage.innerHTML = messageText; |
| startResendCooldown(); |
| |
| } else { |
| |
| if (response.status === 404) { |
| |
| 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(); |
| |
| signupEmailInput.value = email; |
| }}, |
| { text: 'Try Again', style: 'background-color: var(--bs-secondary-color);' } |
| ] |
| ); |
| } else { |
| |
| 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'; |
| } |
| } |
| }); |
| } |
| |
| |
| if (verifyOtpForm) { |
| verifyOtpForm.addEventListener('submit', async function(event) { |
| event.preventDefault(); |
| clearErrors('otp'); |
| |
| 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; |
| 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); |
| resetEmailInput.value = currentForgotEmail; |
| newPasswordInput.value = ''; |
| confirmNewPasswordInput.value = ''; |
| showCustomModal(resetPasswordModal); |
| |
| } else { |
| otpError.textContent = result.message || 'OTP verification failed.'; |
| otpInput.classList.add('error'); |
| otpInput.classList.add('error'); |
| |
| 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'; |
| } |
| } |
| }); |
| } |
| |
| |
| if (resendOtpButton) { |
| resendOtpButton.addEventListener('click', async function(event) { |
| event.preventDefault(); |
| clearErrors('otp'); |
| 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>`; |
| |
| |
| |
| forgotModalMessage.innerHTML = messageText; |
| otpInput.value = ''; |
| otpInput.classList.remove('error'); |
| otpError.textContent = ''; |
| startResendCooldown(); |
| } else { |
| |
| otpError.textContent = result.message || 'Failed to resend OTP.'; |
| otpInput.classList.add('error'); |
| 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'; |
| } |
| }); |
| } |
| |
| |
| if (forgotCancelButton) { |
| forgotCancelButton.addEventListener('click', function() { |
| hideCustomModal(forgotPasswordRequestModal); |
| forgotPasswordRequestForm.reset(); |
| currentForgotEmail = ''; |
| clearErrors('forgot'); |
| |
| if (resendInterval) { |
| clearInterval(resendInterval); |
| resendInterval = null; |
| resendCooldown = 0; |
| } |
| |
| resendOtpButton.disabled = false; |
| resendOtpButton.textContent = 'Send Code Again'; |
| resendOtpButton.style.backgroundColor = ''; |
| }); |
| } |
| |
| if (otpCancelButton) { |
| otpCancelButton.addEventListener('click', function() { |
| hideCustomModal(forgotPasswordRequestModal); |
| verifyOtpForm.reset(); |
| currentForgotEmail = ''; |
| clearErrors('otp'); |
| |
| if (resendInterval) { |
| clearInterval(resendInterval); |
| resendInterval = null; |
| resendCooldown = 0; |
| } |
| |
| resendOtpButton.disabled = false; |
| resendOtpButton.textContent = 'Send Code Again'; |
| resendOtpButton.style.backgroundColor = ''; |
| }); |
| } |
| |
| if (resetCancelButton) { |
| resetCancelButton.addEventListener('click', function() { |
| hideCustomModal(resetPasswordModal); |
| resetPasswordForm.reset(); |
| currentForgotEmail = ''; |
| clearErrors('reset'); |
| |
| if (resendInterval) { |
| clearInterval(resendInterval); |
| resendInterval = null; |
| resendCooldown = 0; |
| } |
| |
| resendOtpButton.disabled = false; |
| resendOtpButton.textContent = 'Send Code Again'; |
| resendOtpButton.style.backgroundColor = ''; |
| }); |
| } |
| |
| |
| if (resetPasswordForm) { |
| resetPasswordForm.addEventListener('submit', async function(event) { |
| event.preventDefault(); |
| clearErrors('reset'); |
| |
| let isValid = true; |
| const email = resetEmailInput.value.trim(); |
| const newPassword = newPasswordInput.value.trim(); |
| const confirmNewPassword = confirmNewPasswordInput.value.trim(); |
| |
| |
| if (email === '') { |
| isValid = false; |
| resetEmailError.textContent = 'Email is required.'; |
| resetEmailInput.classList.add('error'); |
| } else if (!isValidEmail(email)) { |
| isValid = false; |
| resetEmailError.textContent = 'Invalid email format.'; |
| 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'); |
| } |
| |
| |
| 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 }) |
| }); |
| |
| const result = await response.json(); |
| |
| if (response.ok) { |
| hideCustomModal(resetPasswordModal); |
| resetPasswordForm.reset(); |
| currentForgotEmail = ''; |
| |
| if (resendInterval) { |
| clearInterval(resendInterval); |
| resendInterval = null; |
| resendCooldown = 0; |
| } |
| |
| 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 { |
| |
| 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(); |
| }}], |
| null |
| ); |
| } |
| } |
| } 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'; |
| } |
| } |
| }); |
| } |
| |
| |
| 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 { |
| |
| |
| inputElement = formElement.querySelector(`[name="${field}"]`); |
| |
| 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'); |
| } |
| } |
| } |
| |
| |
| function displayModalFormErrors(result, formElement, formContext) { |
| |
| clearErrors(formContext); |
| |
| if (result.errors) { |
| for (const field in result.errors) { |
| |
| const inputElement = formElement.querySelector(`[name="${field}"]`); |
| |
| const errorElement = inputElement ? document.getElementById(`${inputElement.id}Error`) : null; |
| |
| if (errorElement) errorElement.textContent = result.errors[field]; |
| if (inputElement) inputElement.classList.add('error'); |
| } |
| } |
| |
| const targetModalMessage = formElement.closest('.modal-content').querySelector('.modal-message'); |
| if (targetModalMessage && result.message) { |
| |
| const isError = result.status >= 400 && result.status < 600; |
| const messageClass = isError ? 'error-message' : ''; |
| targetModalMessage.innerHTML = `<p class="${messageClass}">${result.message}</p>`; |
| } |
| } |
| |
| |
| 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 { |
| |
| 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'); |
| loginAttempts = 0; |
| |
| 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'); |
| 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 = '/'; |
| }}] |
| ); |
| } |
| } |
| |
| |
| } else { |
| 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 = ''; |
| } 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; |
| |
| 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> |
|
|