Version 1.3.0
- Implemented authentication and billing routes for Jpn region. - Refactored and changed the project structure from CommonJS to ES Modules
|
|
@ -1,2 +1,2 @@
|
|||
[LAUNCHER]
|
||||
version=1.2.0
|
||||
version=1.4.0
|
||||
|
|
@ -4,7 +4,7 @@
|
|||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>News Panel</title>
|
||||
<link rel="stylesheet" href="/launcher/news/style.css">
|
||||
<link rel="stylesheet" href="/launcher/news/css/style.css">
|
||||
</head>
|
||||
<body oncontextmenu="return false;">
|
||||
<div class="slider-container">
|
||||
|
|
@ -28,20 +28,20 @@
|
|||
<div class="tab-link" data-tab="info">Info</div>
|
||||
</div>
|
||||
<div class="tab-content tab events active">
|
||||
<a href="https://your-website.com/news/Halloween.html">Halloween</a> - <span class="tab-date">20/10/2023</span><br>
|
||||
<a href="https://your-website.com/news/Winter.html">Winter</a> - <span class="tab-date">10/12/2023</span><br>
|
||||
<a href="https://your-website.com/news/Happy_New_Year.html">Happy New Year</a> - <span class="tab-date">01/01/2024</span><br>
|
||||
<a href="https://your-website.com/news/Halloween.html">Halloween</a> - <span class="tab-date">20/10/2025</span><br>
|
||||
<a href="https://your-website.com/news/Winter.html">Winter</a> - <span class="tab-date">10/12/2025</span><br>
|
||||
<a href="https://your-website.com/news/Happy_New_Year.html">Happy New Year</a> - <span class="tab-date">01/01/2025</span><br>
|
||||
</div>
|
||||
<div class="tab-content tab notices">
|
||||
<a href="#">Notice 1</a> - <span class="tab-date">01/01/2023</span><br>
|
||||
<a href="#">Notice 2</a> - <span class="tab-date">02/01/2023</span><br>
|
||||
<a href="#">Notice 3</a> - <span class="tab-date">03/01/2023</span><br>
|
||||
<a href="#">Notice 1</a> - <span class="tab-date">01/01/2025</span><br>
|
||||
<a href="#">Notice 2</a> - <span class="tab-date">02/01/2025</span><br>
|
||||
<a href="#">Notice 3</a> - <span class="tab-date">03/01/2025</span><br>
|
||||
</div>
|
||||
<div class="tab-content tab info">
|
||||
<a href="#">Info 1</a> - <span class="tab-date">01/01/2023</span><br>
|
||||
<a href="#">Info 2</a> - <span class="tab-date">02/01/2023</span><br>
|
||||
<a href="#">Info 3</a> - <span class="tab-date">03/01/2023</span><br>
|
||||
<a href="#">Info 1</a> - <span class="tab-date">01/01/2025</span><br>
|
||||
<a href="#">Info 2</a> - <span class="tab-date">02/01/2025</span><br>
|
||||
<a href="#">Info 3</a> - <span class="tab-date">03/01/2025</span><br>
|
||||
</div>
|
||||
<script src="/launcher/news/script.js"></script>
|
||||
<script src="/launcher/news/js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
134
public/site/Signup.html
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Rusty Hearts - Account Management</title>
|
||||
<link rel="stylesheet" href="/site/css/style.css">
|
||||
<link href="https://fonts.googleapis.com/css2?family=MedievalSharp&family=Open+Sans:wght@400;600&display=swap" rel="stylesheet">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<div class="left-panel">
|
||||
<div class="logo-container">
|
||||
<img src="/site/images/rh_logo.png" alt="Rusty Hearts Logo" class="logo">
|
||||
</div>
|
||||
<div class="game-description">
|
||||
<h3>Join the Battle</h3>
|
||||
<p>Experience a action hack'n'slash multiplayer online game with fast-paced and highly-stylized brawling combat combined with a solo or team-based dungeon exploration experience.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="right-panel">
|
||||
<div class="form-tabs">
|
||||
<button class="tab-button active" data-tab="register">Create Account</button>
|
||||
<button class="tab-button" data-tab="reset">Reset Password</button>
|
||||
</div>
|
||||
|
||||
<!-- Register Form -->
|
||||
<div id="register-form" class="form-content active">
|
||||
<form id="signupForm">
|
||||
<div class="form-group">
|
||||
<label for="userName">Username</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-user"></i>
|
||||
<input type="text" id="userName" name="userName" placeholder="6-16 alphanumeric characters" required>
|
||||
</div>
|
||||
<div class="error-message" id="userNameError"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<input type="email" id="email" name="email" placeholder="your@email.com" required>
|
||||
<button type="button" id="sendVerificationBtn" class="verification-btn">Send Code</button>
|
||||
</div>
|
||||
<div class="error-message" id="emailError"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="verificationCode">Verification Code</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<input type="text" id="verificationCode" name="verificationCode" placeholder="Enter verification code" required>
|
||||
</div>
|
||||
<div class="error-message" id="verificationCodeError"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-lock"></i>
|
||||
<input type="password" id="password" name="password" placeholder="8-16 characters" required>
|
||||
</div>
|
||||
<div class="error-message" id="passwordError"></div>
|
||||
</div>
|
||||
<div id="registerResponse" class="response-message"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Reset Password Form -->
|
||||
<div id="reset-form" class="form-content">
|
||||
<form id="resetPasswordForm">
|
||||
<div class="form-group">
|
||||
<label for="resetEmail">Email</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-envelope"></i>
|
||||
<input type="email" id="resetEmail" name="email" placeholder="your@email.com" required>
|
||||
<button type="button" id="sendResetVerificationBtn" class="verification-btn">Send Code</button>
|
||||
</div>
|
||||
<div class="error-message" id="resetEmailError"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="resetVerificationCode">Verification Code</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<input type="text" id="resetVerificationCode" name="verificationCode" placeholder="Enter verification code" required>
|
||||
</div>
|
||||
<div class="error-message" id="resetVerificationCodeError"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="newPassword">New Password</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-lock"></i>
|
||||
<input type="password" id="newPassword" name="password" placeholder="8-16 characters" required>
|
||||
</div>
|
||||
<div class="error-message" id="newPasswordError"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirmPassword">Confirm Password</label>
|
||||
<div class="input-with-icon">
|
||||
<i class="fas fa-lock"></i>
|
||||
<input type="password" id="confirmPassword" placeholder="Repeat your password" required>
|
||||
</div>
|
||||
<div class="error-message" id="confirmPasswordError"></div>
|
||||
</div>
|
||||
<div id="resetResponse" class="response-message"></div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Dynamic Footer -->
|
||||
<div class="form-footer">
|
||||
<div class="footer-content register-footer active">
|
||||
<button type="submit" form="signupForm" class="submit-btn">Create Account</button>
|
||||
<div class="footer-links">
|
||||
<div class="reset-link">
|
||||
Forgot password? <a href="#" class="switch-tab" data-tab="reset">Reset Password</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="footer-content reset-footer">
|
||||
<button type="submit" form="resetPasswordForm" class="submit-btn">Reset Password</button>
|
||||
<div class="footer-links">
|
||||
<div class="register-link">
|
||||
Need an account? <a href="#" class="switch-tab" data-tab="register">Create Account</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/site/js/script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
442
public/site/css/style.css
Normal file
|
|
@ -0,0 +1,442 @@
|
|||
/* Reset and Base Styles */
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
background-color: #1a1a1a;
|
||||
background: linear-gradient(rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0.2)), url('/site/images/rh1920x1200.jpg');
|
||||
background-size: cover;
|
||||
background-repeat: no-repeat;
|
||||
color: #e0e0e0;
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.container {
|
||||
display: flex;
|
||||
max-width: 1200px;
|
||||
width: 90%;
|
||||
max-height: 90vh;
|
||||
min-height: 600px;
|
||||
background-color: #2a2a2a;
|
||||
border-radius: 10px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 0 20px rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
/* Left Panel Styles */
|
||||
.left-panel {
|
||||
background-color: white;
|
||||
flex: 1;
|
||||
background-position: center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
padding: 0px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: relative;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.logo-container {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.game-description {
|
||||
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.game-description h2, h3 {
|
||||
font-family: 'MedievalSharp', cursive;
|
||||
color: #c62828;
|
||||
margin-bottom: 10px;
|
||||
font-size: 24px;
|
||||
}
|
||||
|
||||
.game-description p {
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Right Panel Styles */
|
||||
.right-panel {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.form-container h2 {
|
||||
font-family: 'MedievalSharp', cursive;
|
||||
color: #c62828;
|
||||
margin-bottom: 30px;
|
||||
text-align: center;
|
||||
font-size: 28px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 600;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
#signupForm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.input-with-icon {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.input-with-icon i {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
color: #777;
|
||||
}
|
||||
|
||||
.input-with-icon input {
|
||||
width: 100%;
|
||||
padding: 12px 120px 12px 40px;
|
||||
border: 2px solid #444;
|
||||
border-radius: 5px;
|
||||
background-color: #333;
|
||||
color: #e0e0e0;
|
||||
font-size: 14px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.input-with-icon input:focus {
|
||||
border-color: #c62828;
|
||||
outline: none;
|
||||
box-shadow: 0 0 5px rgba(198, 40, 40, 0.5);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #ff5252;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.form-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.form-header {
|
||||
flex-shrink: 0;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.form-scrollable {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
/* Better scrollbar styling */
|
||||
.form-content::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
}
|
||||
|
||||
.form-content::-webkit-scrollbar-track {
|
||||
background: #333;
|
||||
}
|
||||
|
||||
.form-content::-webkit-scrollbar-thumb {
|
||||
background: #c62828;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
/* Form Footer - Always Visible */
|
||||
.form-footer {
|
||||
flex-shrink: 0;
|
||||
padding: 4px 0;
|
||||
margin-top: auto;
|
||||
border-top: 1px solid #444;
|
||||
background-color: #2a2a2a;
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
}
|
||||
|
||||
.submit-btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #c62828;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.submit-btn:hover {
|
||||
background-color: #b71c1c;
|
||||
}
|
||||
|
||||
.login-link,
|
||||
.reset-link,
|
||||
.register-link {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.login-link a {
|
||||
color: #c62828;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.login-link a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.response-message {
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
border-radius: 5px;
|
||||
text-align: center;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.response-message.success {
|
||||
background-color: rgba(76, 175, 80, 0.2);
|
||||
color: #4CAF50;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.response-message.error {
|
||||
background-color: rgba(244, 67, 54, 0.2);
|
||||
color: #f44336;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.legal-links {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 20px;
|
||||
margin-top: 20px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.legal-links a {
|
||||
color: #777;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.legal-links a:hover {
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
/* Footer Styles */
|
||||
.footer-content {
|
||||
display: none;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.footer-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.footer-links {
|
||||
margin-top: 15px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.switch-tab {
|
||||
color: #c62828;
|
||||
text-decoration: none;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.switch-tab:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* Responsive Styles */
|
||||
@media (max-width: 480px) {
|
||||
.footer-links {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-height: 700px) {
|
||||
.container {
|
||||
max-height: 95vh;
|
||||
min-height: 400px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.input-with-icon input {
|
||||
padding: 10px 100px 10px 35px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.container {
|
||||
flex-direction: column;
|
||||
max-height: none;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.left-panel {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.game-screenshot {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* Verification Button Styles */
|
||||
.verification-btn {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
background-color: #333;
|
||||
color: #c62828;
|
||||
border: 1px solid #c62828;
|
||||
border-radius: 3px;
|
||||
padding: 5px 10px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.verification-btn:hover {
|
||||
background-color: #c62828;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.verification-btn:disabled {
|
||||
background-color: #333;
|
||||
color: #777;
|
||||
border-color: #444;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
/* Tab Styles */
|
||||
.form-tabs {
|
||||
display: flex;
|
||||
border-bottom: 1px solid #444;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.tab-button {
|
||||
flex: 1;
|
||||
padding: 12px 0;
|
||||
background: none;
|
||||
border: none;
|
||||
color: #777;
|
||||
font-family: 'MedievalSharp', cursive;
|
||||
font-size: 18px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.tab-button.active {
|
||||
color: #c62828;
|
||||
}
|
||||
|
||||
.tab-button.active::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: -1px;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 2px;
|
||||
background-color: #c62828;
|
||||
}
|
||||
|
||||
/* Form Content */
|
||||
.form-content {
|
||||
display: none;
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 10px 0;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.form-content.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Submit Buttons */
|
||||
#resetSubmit {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Animation */
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(10px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
.form-content.active {
|
||||
animation: fadeIn 0.3s ease-out;
|
||||
}
|
||||
|
||||
|
||||
.password-match {
|
||||
border-color: #4CAF50 !important;
|
||||
box-shadow: 0 0 5px rgba(76, 175, 80, 0.5) !important;
|
||||
}
|
||||
|
||||
.password-mismatch {
|
||||
border-color: #f44336 !important;
|
||||
box-shadow: 0 0 5px rgba(244, 67, 54, 0.5) !important;
|
||||
}
|
||||
BIN
public/site/images/001.jpg
Normal file
|
After Width: | Height: | Size: 53 KiB |
BIN
public/site/images/002.jpg
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
public/site/images/006.jpg
Normal file
|
After Width: | Height: | Size: 135 KiB |
BIN
public/site/images/012.jpg
Normal file
|
After Width: | Height: | Size: 25 KiB |
BIN
public/site/images/020.jpg
Normal file
|
After Width: | Height: | Size: 122 KiB |
BIN
public/site/images/021.jpg
Normal file
|
After Width: | Height: | Size: 170 KiB |
BIN
public/site/images/022.jpg
Normal file
|
After Width: | Height: | Size: 72 KiB |
BIN
public/site/images/023.jpg
Normal file
|
After Width: | Height: | Size: 73 KiB |
BIN
public/site/images/rh.png
Normal file
|
After Width: | Height: | Size: 3 MiB |
BIN
public/site/images/rh1920x1200.jpg
Normal file
|
After Width: | Height: | Size: 903 KiB |
BIN
public/site/images/rh_logo.png
Normal file
|
After Width: | Height: | Size: 137 KiB |
461
public/site/js/script.js
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
document.addEventListener("DOMContentLoaded", function () {
|
||||
function setRandomBackground() {
|
||||
const images = ["001.jpg", "002.jpg", "006.jpg", "012.jpg", "020.jpg", "021.jpg", "022.jpg", "023.jpg"];
|
||||
const randomImage = images[Math.floor(Math.random() * images.length)];
|
||||
const panel = document.querySelector(".left-panel");
|
||||
if (panel) {
|
||||
panel.style.backgroundImage = `linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url('/site/images/${randomImage}')`;
|
||||
}
|
||||
}
|
||||
|
||||
setRandomBackground();
|
||||
// Tab switching functionality
|
||||
const tabs = document.querySelectorAll(".tab-button");
|
||||
const formContents = document.querySelectorAll(".form-content");
|
||||
const footers = document.querySelectorAll(".footer-content");
|
||||
|
||||
// Switch tab function
|
||||
function switchTab(tabName) {
|
||||
// Update tabs
|
||||
tabs.forEach((tab) => {
|
||||
tab.classList.toggle("active", tab.getAttribute("data-tab") === tabName);
|
||||
});
|
||||
|
||||
// Update forms
|
||||
formContents.forEach((form) => {
|
||||
form.classList.toggle("active", form.id === `${tabName}-form`);
|
||||
});
|
||||
|
||||
// Update footers
|
||||
footers.forEach((footer) => {
|
||||
footer.classList.toggle(
|
||||
"active",
|
||||
footer.classList.contains(`${tabName}-footer`)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Tab click event
|
||||
tabs.forEach((tab) => {
|
||||
tab.addEventListener("click", function () {
|
||||
const tabName = this.getAttribute("data-tab");
|
||||
switchTab(tabName);
|
||||
});
|
||||
});
|
||||
|
||||
// Switch tab link click event
|
||||
document.querySelectorAll(".switch-tab").forEach((link) => {
|
||||
link.addEventListener("click", function (e) {
|
||||
e.preventDefault();
|
||||
const tabName = this.getAttribute("data-tab");
|
||||
switchTab(tabName);
|
||||
});
|
||||
});
|
||||
|
||||
// Register form functionality
|
||||
const signupForm = document.getElementById("signupForm");
|
||||
const registerResponse = document.getElementById("registerResponse");
|
||||
const sendVerificationBtn = document.getElementById("sendVerificationBtn");
|
||||
let cooldownInterval;
|
||||
|
||||
// Password reset form functionality
|
||||
const resetPasswordForm = document.getElementById("resetPasswordForm");
|
||||
const resetResponse = document.getElementById("resetResponse");
|
||||
const sendResetVerificationBtn = document.getElementById(
|
||||
"sendResetVerificationBtn"
|
||||
);
|
||||
let resetCooldownInterval;
|
||||
|
||||
// Shared functions
|
||||
function startCooldown(button, intervalVar, seconds = 60) {
|
||||
let secondsLeft = seconds;
|
||||
updateButtonText(button, secondsLeft);
|
||||
|
||||
intervalVar = setInterval(() => {
|
||||
secondsLeft--;
|
||||
updateButtonText(button, secondsLeft);
|
||||
|
||||
if (secondsLeft <= 0) {
|
||||
clearInterval(intervalVar);
|
||||
resetVerificationButton(button);
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
return intervalVar;
|
||||
}
|
||||
|
||||
function updateButtonText(button, seconds) {
|
||||
button.textContent = `Resend (${seconds}s)`;
|
||||
}
|
||||
|
||||
function resetVerificationButton(button) {
|
||||
button.disabled = false;
|
||||
button.textContent = "Send Code";
|
||||
}
|
||||
|
||||
function showError(elementId, message) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element) {
|
||||
element.textContent = message;
|
||||
}
|
||||
}
|
||||
|
||||
function clearErrorMessages(formPrefix = "") {
|
||||
const errorElements = document.querySelectorAll(
|
||||
`.error-message${formPrefix ? `[id^=${formPrefix}]` : ""}`
|
||||
);
|
||||
errorElements.forEach((element) => {
|
||||
element.textContent = "";
|
||||
});
|
||||
}
|
||||
|
||||
function showResponseMessage(element, message, type) {
|
||||
element.textContent = message;
|
||||
element.className = "response-message " + type;
|
||||
}
|
||||
|
||||
// Verification code sending functionality for REGISTER form
|
||||
sendVerificationBtn.addEventListener("click", async function () {
|
||||
const email = document.getElementById("email").value.trim();
|
||||
|
||||
// Clear previous error
|
||||
document.getElementById("emailError").textContent = "";
|
||||
|
||||
// Basic email validation
|
||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
showError("emailError", "Please enter a valid email address");
|
||||
return;
|
||||
}
|
||||
|
||||
// Disable button and start cooldown
|
||||
sendVerificationBtn.disabled = true;
|
||||
cooldownInterval = startCooldown(sendVerificationBtn, cooldownInterval);
|
||||
|
||||
try {
|
||||
const response = await fetch("/launcher/SendVerificationEmailAction", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({ email }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
if (data.message === "AccountExists") {
|
||||
showError("emailError", "Email is already registered");
|
||||
} else {
|
||||
showError(
|
||||
"emailError",
|
||||
"Failed to send verification code: " + data.message
|
||||
);
|
||||
}
|
||||
resetVerificationButton(sendVerificationBtn);
|
||||
} else {
|
||||
showResponseMessage(
|
||||
registerResponse,
|
||||
"Verification code sent to your email",
|
||||
"success"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error sending verification:", error);
|
||||
showError("emailError", "Failed to send verification code");
|
||||
resetVerificationButton(sendVerificationBtn);
|
||||
}
|
||||
});
|
||||
|
||||
// Verification code sending functionality for PASSWORD RESET form
|
||||
sendResetVerificationBtn.addEventListener("click", async function () {
|
||||
const email = document.getElementById("resetEmail").value.trim();
|
||||
|
||||
clearErrorMessages("reset");
|
||||
document.getElementById("resetEmailError").textContent = "";
|
||||
|
||||
if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) {
|
||||
showError("resetEmailError", "Please enter a valid email address");
|
||||
return;
|
||||
}
|
||||
|
||||
sendResetVerificationBtn.disabled = true;
|
||||
resetCooldownInterval = startCooldown(
|
||||
sendResetVerificationBtn,
|
||||
resetCooldownInterval
|
||||
);
|
||||
|
||||
try {
|
||||
const response = await fetch("/launcher/SendPasswordResetEmailAction", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams({ email }),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok || !data.success) {
|
||||
if (data.message === "AccountNotFound") {
|
||||
showError("resetEmailError", "No account found with this email");
|
||||
} else {
|
||||
showError(
|
||||
"resetEmailError",
|
||||
"Failed to send verification code: " + data.message
|
||||
);
|
||||
}
|
||||
resetVerificationButton(sendResetVerificationBtn);
|
||||
} else {
|
||||
showResponseMessage(
|
||||
resetResponse,
|
||||
"Password reset code sent to your email",
|
||||
"success"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error sending verification:", error);
|
||||
showError("resetEmailError", "Failed to send verification code");
|
||||
resetVerificationButton(sendResetVerificationBtn);
|
||||
}
|
||||
});
|
||||
|
||||
// Form submission handlers
|
||||
signupForm.addEventListener("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
clearErrorMessages();
|
||||
showResponseMessage(registerResponse, "", "");
|
||||
|
||||
const formData = {
|
||||
userName: document.getElementById("userName").value.trim(),
|
||||
email: document.getElementById("email").value.trim(),
|
||||
password: document.getElementById("password").value.trim(),
|
||||
verificationCode: document
|
||||
.getElementById("verificationCode")
|
||||
.value.trim(),
|
||||
};
|
||||
|
||||
// Validation
|
||||
|
||||
try {
|
||||
const response = await fetch("/launcher/SignupAction", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
if (data.success) {
|
||||
showResponseMessage(
|
||||
registerResponse,
|
||||
"Account created successfully!",
|
||||
"success"
|
||||
);
|
||||
} else {
|
||||
handleServerErrors(data.message, formData, "");
|
||||
}
|
||||
} else {
|
||||
showResponseMessage(
|
||||
registerResponse,
|
||||
data.message || "An error occurred. Please try again.",
|
||||
"error"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showResponseMessage(
|
||||
registerResponse,
|
||||
"An error occurred. Please try again.",
|
||||
"error"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
resetPasswordForm.addEventListener("submit", async function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
clearErrorMessages("reset");
|
||||
showResponseMessage(resetResponse, "", "");
|
||||
|
||||
const formData = {
|
||||
email: document.getElementById("resetEmail").value.trim(),
|
||||
password: document.getElementById("newPassword").value.trim(),
|
||||
verificationCode: document
|
||||
.getElementById("resetVerificationCode")
|
||||
.value.trim(),
|
||||
};
|
||||
|
||||
// Validate the form
|
||||
if (!validateResetForm(formData)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch("/launcher/ResetPasswordAction", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/x-www-form-urlencoded",
|
||||
},
|
||||
body: new URLSearchParams(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
if (data.success) {
|
||||
showResponseMessage(
|
||||
resetResponse,
|
||||
"Password changed successfully!",
|
||||
"success"
|
||||
);
|
||||
// Clear password fields on success
|
||||
document.getElementById("newPassword").value = "";
|
||||
document.getElementById("confirmPassword").value = "";
|
||||
} else {
|
||||
handleServerErrors(data.message, formData, "reset");
|
||||
}
|
||||
} else {
|
||||
showResponseMessage(
|
||||
resetResponse,
|
||||
data.message || "An error occurred. Please try again.",
|
||||
"error"
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error:", error);
|
||||
showResponseMessage(
|
||||
resetResponse,
|
||||
"An error occurred. Please try again.",
|
||||
"error"
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
function validateResetForm(formData) {
|
||||
let isValid = true;
|
||||
|
||||
// Email validation
|
||||
if (!formData.email) {
|
||||
showError("resetEmailError", "Email is required");
|
||||
isValid = false;
|
||||
} else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(formData.email)) {
|
||||
showError("resetEmailError", "Please enter a valid email address");
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Verification code validation
|
||||
if (!formData.verificationCode) {
|
||||
showError("resetVerificationCodeError", "Verification code is required");
|
||||
isValid = false;
|
||||
} else if (!/^[0-9]+$/.test(formData.verificationCode)) {
|
||||
showError(
|
||||
"resetVerificationCodeError",
|
||||
"Verification code must contain only numbers"
|
||||
);
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
// Password validation
|
||||
const newPassword = document.getElementById("newPassword").value.trim();
|
||||
const confirmPassword = document
|
||||
.getElementById("confirmPassword")
|
||||
.value.trim();
|
||||
|
||||
if (!newPassword) {
|
||||
showError("newPasswordError", "Password is required");
|
||||
isValid = false;
|
||||
} else if (newPassword.length < 8 || newPassword.length > 16) {
|
||||
showError("newPasswordError", "Password must be 8-16 characters");
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (!confirmPassword) {
|
||||
showError("confirmPasswordError", "Please confirm your password");
|
||||
isValid = false;
|
||||
} else if (newPassword !== confirmPassword) {
|
||||
showError("confirmPasswordError", "Passwords do not match");
|
||||
showError("newPasswordError", "Passwords do not match");
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
return isValid;
|
||||
}
|
||||
|
||||
// real-time password matching feedback
|
||||
const newPasswordInput = document.getElementById("newPassword");
|
||||
const confirmPasswordInput = document.getElementById("confirmPassword");
|
||||
|
||||
function checkPasswordMatch() {
|
||||
const newPassword = newPasswordInput.value;
|
||||
const confirmPassword = confirmPasswordInput.value;
|
||||
|
||||
if (newPassword && confirmPassword) {
|
||||
if (newPassword === confirmPassword) {
|
||||
newPasswordInput.classList.add("password-match");
|
||||
newPasswordInput.classList.remove("password-mismatch");
|
||||
confirmPasswordInput.classList.add("password-match");
|
||||
confirmPasswordInput.classList.remove("password-mismatch");
|
||||
showError("confirmPasswordError", "");
|
||||
showError("newPasswordError", "");
|
||||
} else {
|
||||
newPasswordInput.classList.add("password-mismatch");
|
||||
newPasswordInput.classList.remove("password-match");
|
||||
confirmPasswordInput.classList.add("password-mismatch");
|
||||
confirmPasswordInput.classList.remove("password-match");
|
||||
}
|
||||
} else {
|
||||
newPasswordInput.classList.remove("password-match", "password-mismatch");
|
||||
confirmPasswordInput.classList.remove(
|
||||
"password-match",
|
||||
"password-mismatch"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
newPasswordInput.addEventListener("input", checkPasswordMatch);
|
||||
confirmPasswordInput.addEventListener("input", checkPasswordMatch);
|
||||
|
||||
function handleServerErrors(errorCode, formData, prefix = "") {
|
||||
switch (errorCode) {
|
||||
case "UsernameExists":
|
||||
showError(`${prefix}userNameError`, "Username is already in use");
|
||||
break;
|
||||
case "EmailExists":
|
||||
showError(`${prefix}EmailError`, "Email is already registered");
|
||||
break;
|
||||
case "AccountNotFound":
|
||||
showError(`${prefix}EmailError`, "No account found with this email");
|
||||
break;
|
||||
case "InvalidVerificationCode":
|
||||
showError(
|
||||
`${prefix}VerificationCodeError`,
|
||||
"Invalid verification code"
|
||||
);
|
||||
break;
|
||||
case "ExpiredVerificationCode":
|
||||
showError(
|
||||
`${prefix}VerificationCodeError`,
|
||||
"Verification code has expired, please request a new one"
|
||||
);
|
||||
break;
|
||||
case "SamePassword":
|
||||
showResponseMessage(
|
||||
resetResponse,
|
||||
"New password cannot be the same as the old password",
|
||||
"error"
|
||||
);
|
||||
break;
|
||||
default:
|
||||
const responseElement = prefix ? resetResponse : registerResponse;
|
||||
showResponseMessage(
|
||||
responseElement,
|
||||
"An error occurred: " + errorCode,
|
||||
"error"
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||