Text Generation
Transformers
Safetensors
English
phi3
phi
nlp
math
code
chat
conversational
reasoning
text-generation-inference
Instructions to use Ashok75/base2 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Ashok75/base2 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="Ashok75/base2") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoTokenizer, AutoModelForCausalLM tokenizer = AutoTokenizer.from_pretrained("Ashok75/base2") model = AutoModelForCausalLM.from_pretrained("Ashok75/base2") messages = [ {"role": "user", "content": "Who are you?"}, ] inputs = tokenizer.apply_chat_template( messages, add_generation_prompt=True, tokenize=True, return_dict=True, return_tensors="pt", ).to(model.device) outputs = model.generate(**inputs, max_new_tokens=40) print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:])) - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- vLLM
How to use Ashok75/base2 with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "Ashok75/base2" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Ashok75/base2", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/Ashok75/base2
- SGLang
How to use Ashok75/base2 with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "Ashok75/base2" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Ashok75/base2", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "Ashok75/base2" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "Ashok75/base2", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Docker Model Runner
How to use Ashok75/base2 with Docker Model Runner:
docker model run hf.co/Ashok75/base2
| <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> | |