diff --git a/login.html b/login.html
index 7c4f0e4..dca0515 100644
--- a/login.html
+++ b/login.html
@@ -17,6 +17,7 @@
Login
@@ -28,6 +29,7 @@
Login
+
diff --git a/scripts/auth.js b/scripts/auth.js
index dc3b505..e0558fe 100644
--- a/scripts/auth.js
+++ b/scripts/auth.js
@@ -57,11 +57,240 @@ function initializePasswordToggle() {
// Initialize password toggle when DOM is loaded
document.addEventListener('DOMContentLoaded', function () {
initializePasswordToggle();
+ // Initialize form validation for both login and signup pages
+ initializeFormValidation();
});
+/* ===============================================
+ FORM VALIDATION SYSTEM - START
+ =============================================== */
+
+// Validation configuration for reusability
+const VALIDATION_RULES = {
+ email: { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, minLength: 5, maxLength: 100 },
+ username: { pattern: /^[a-zA-Z0-9_-]+$/, minLength: 3, maxLength: 20 },
+ password: { login: { minLength: 6 }, signup: { minLength: 8, strengthCheck: true } }
+};
+
+/**
+ * Initialize form validation for both login and signup pages
+ */
+function initializeFormValidation() {
+ const formConfigs = {
+ 'login-form': [
+ { id: 'email', type: 'email' },
+ { id: 'password', type: 'password', mode: 'login' }
+ ],
+ 'signup-form': [
+ { id: 'username', type: 'username' },
+ { id: 'email', type: 'email' },
+ { id: 'password', type: 'password', mode: 'signup' }
+ ]
+ };
+
+ // Setup validation for each form
+ Object.entries(formConfigs).forEach(([formId, fields]) => {
+ if (document.getElementById(formId)) {
+ fields.forEach(field => setupFieldValidation(field));
+ }
+ });
+}
+
+/**
+ * Setup validation for a single field - removes duplicate event listener code
+ */
+function setupFieldValidation(field) {
+ const input = document.getElementById(field.id);
+ if (!input) return;
+
+ const validator = () => validateField(input, field.type, field.mode || 'login');
+ ['input', 'blur'].forEach(event => input.addEventListener(event, validator));
+}
+
+/**
+ * Universal field validation function - handles all types with common logic
+ */
+function validateField(input, type, mode) {
+ if (!input) return false;
+
+ const value = input.value.trim();
+ const messageId = `${input.id}-validation`;
+
+ if (!value) {
+ hideValidationMessage(messageId, input);
+ if (type === 'password' && mode === 'signup') hidePasswordStrengthIndicator(messageId);
+ return false;
+ }
+
+ switch (type) {
+ case 'email': return validateWithRules(input, value, messageId, 'email', '✓ Email format is valid');
+ case 'username': return validateWithRules(input, value, messageId, 'username', '✓ Username is valid');
+ case 'password': return validatePasswordField(input, value, messageId, mode);
+ default: return false;
+ }
+}
+
+/**
+ * Unified validation with rules - eliminates duplicate code
+ */
+function validateWithRules(input, value, messageId, type, successMsg) {
+ const rules = VALIDATION_RULES[type];
+ const errors = [
+ [type === 'email' && !rules.pattern.test(value), 'Please enter a valid email address'],
+ [type === 'email' && value.includes('..'), 'Email cannot contain consecutive dots'],
+ [type === 'email' && (value.startsWith('.') || value.endsWith('.')), 'Email cannot start or end with a dot'],
+ [type === 'username' && value.length < rules.minLength, `Username must be at least ${rules.minLength} characters (${value.length}/${rules.minLength})`],
+ [type === 'username' && value.length > rules.maxLength, `Username cannot exceed ${rules.maxLength} characters`],
+ [type === 'username' && !rules.pattern.test(value), 'Username can only contain letters, numbers, underscore, and hyphen'],
+ [type === 'username' && !/^[a-zA-Z0-9]/.test(value), 'Username must start with a letter or number']
+ ].filter(([condition]) => condition);
+
+ if (errors.length) {
+ showValidationMessage(messageId, errors[0][1], 'error', input);
+ return false;
+ }
+
+ showValidationMessage(messageId, successMsg, 'success', input);
+ return true;
+}
+
+/**
+ * Concise password validation with mode support
+ */
+function validatePasswordField(input, password, messageId, mode) {
+ const rules = VALIDATION_RULES.password[mode];
+ if (!rules) return false;
+
+ if (password.length < rules.minLength) {
+ showValidationMessage(messageId, `Password must be at least ${rules.minLength} characters (${password.length}/${rules.minLength})`, 'error', input);
+ if (mode === 'signup') hidePasswordStrengthIndicator(messageId);
+ return false;
+ }
+
+ if (mode === 'signup' && rules.strengthCheck) {
+ const strength = calculatePasswordStrength(password);
+ showPasswordStrengthIndicator(messageId, strength);
+ const messages = {
+ weak: ['Password is weak. ' + strength.suggestions[0], 'warning', false],
+ fair: ['Password is fair. ' + (strength.suggestions[0] || 'Consider adding more variety'), 'warning', true],
+ good: ['✓ Good password strength', 'success', true],
+ strong: ['✓ Strong password!', 'success', true]
+ };
+ const [message, type, isValid] = messages[strength.level];
+ showValidationMessage(messageId, message, type, input);
+ return isValid;
+ }
+
+ showValidationMessage(messageId, '✓ Password entered', 'success', input);
+ return true;
+}
+
+/**
+ * Efficient validation message utilities
+ */
+function showValidationMessage(messageId, message, type, inputElement) {
+ const messageElement = document.getElementById(messageId);
+ if (!messageElement || !inputElement) return;
+
+ messageElement.className = `validation-message show ${type}`;
+
+ // Remove all validation-* classes using classList methods
+ Array.from(inputElement.classList)
+ .filter(cls => /^validation-\w+$/.test(cls))
+ .forEach(cls => inputElement.classList.remove(cls));
+
+ // Add new validation class
+ inputElement.classList.add(`validation-${type}`);
+ messageElement.textContent = message;
+}
+
+function hideValidationMessage(messageId, inputElement) {
+ const messageElement = document.getElementById(messageId);
+ if (messageElement) messageElement.className = 'validation-message';
+
+ if (inputElement) {
+ // Remove all validation-* classes using classList methods
+ Array.from(inputElement.classList)
+ .filter(cls => /^validation-\w+$/.test(cls))
+ .forEach(cls => inputElement.classList.remove(cls));
+ }
+}
+
+/**
+ * Concise password strength calculation
+ */
+function calculatePasswordStrength(password) {
+ const checks = [
+ [password.length >= 8, 'Add more characters'],
+ [password.length >= 12, ''],
+ [/[a-z]/.test(password), 'Add lowercase letters'],
+ [/[A-Z]/.test(password), 'Add uppercase letters'],
+ [/[0-9]/.test(password), 'Add numbers'],
+ [/[^a-zA-Z0-9]/.test(password), 'Add special characters (!@#$%)']
+ ];
+
+ const score = checks.filter(([test]) => test).length;
+ const suggestions = checks.filter(([test, msg]) => !test && msg).map(([, msg]) => msg);
+ const levels = ['weak', 'weak', 'weak', 'fair', 'fair', 'good', 'strong'];
+
+ return { level: levels[score] || 'weak', score, suggestions };
+}
+
+/**
+ * Efficient password strength indicator
+ */
+function showPasswordStrengthIndicator(messageId, strength) {
+ const messageElement = document.getElementById(messageId);
+ if (!messageElement) return;
+
+ // Clean up existing indicator
+ const existing = messageElement.parentNode.querySelector('.password-strength-indicator');
+ if (existing) existing.remove();
+
+ const indicator = document.createElement('div');
+ indicator.innerHTML = `
+
+
+
Password strength: ${strength.level.charAt(0).toUpperCase() + strength.level.slice(1)}
+
+ `;
+
+ messageElement.parentNode.insertBefore(indicator.firstElementChild, messageElement.nextSibling);
+}
+
+function hidePasswordStrengthIndicator(messageId) {
+ const messageElement = document.getElementById(messageId);
+ const indicator = messageElement?.parentNode?.querySelector('.password-strength-indicator');
+ if (indicator) indicator.remove();
+}
+
+/**
+ * Efficient form validation for submission
+ */
+function validateForm(formType) {
+ const fieldConfigs = {
+ login: [['email', 'email'], ['password', 'password', 'login']],
+ signup: [['username', 'username'], ['email', 'email'], ['password', 'password', 'signup']]
+ };
+
+ const fields = fieldConfigs[formType];
+ return fields ? fields.every(([id, type, mode]) => {
+ const input = document.getElementById(id);
+ return input && validateField(input, type, mode || 'login');
+ }) : false;
+}
+
if (document.getElementById('signup-form')) {
document.getElementById('signup-form').addEventListener('submit', async (e) => {
e.preventDefault()
+
+ // Validate form before submission
+ if (!validateForm('signup')) {
+ return; // Stop submission if validation fails
+ }
+
const username = document.getElementById('username').value
const email = document.getElementById('email').value
const password = document.getElementById('password').value
@@ -79,6 +308,12 @@ if (document.getElementById('signup-form')) {
if (document.getElementById('login-form')) {
document.getElementById('login-form').addEventListener('submit', async (e) => {
e.preventDefault()
+
+ // Validate form before submission
+ if (!validateForm('login')) {
+ return; // Stop submission if validation fails
+ }
+
const email = document.getElementById('email').value
const password = document.getElementById('password').value
const res = await API.login({ email, password })
diff --git a/signup.html b/signup.html
index 7655631..f576ed6 100644
--- a/signup.html
+++ b/signup.html
@@ -17,10 +17,12 @@ Sign up
@@ -32,6 +34,7 @@
Sign up
+
diff --git a/styles/login.css b/styles/login.css
index dafe6d2..3afc41d 100644
--- a/styles/login.css
+++ b/styles/login.css
@@ -218,4 +218,128 @@ input[type="password"]::-webkit-credentials-auto-fill-button {
font-size: 1rem; /* Smaller font size for button text */
}
-}
\ No newline at end of file
+}
+/* ===============================================
+ FORM VALIDATION STYLES - START
+ =============================================== */
+
+
+.validation-message {
+ font-size: 0.85rem;
+ margin-top: 0;
+ margin-bottom: 0;
+ transition: all 0.3s ease;
+ opacity: 0;
+ transform: translateY(-10px);
+ visibility: hidden;
+ height: 0;
+ overflow: hidden;
+ font-weight: 500;
+}
+
+
+.validation-message.show {
+ opacity: 1;
+ transform: translateY(0);
+ visibility: visible;
+ height: auto;
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+
+.validation-message.error {
+ color: #780000; /* Keep red for errors */
+}
+
+.validation-message.success {
+ color: #28a745; /* Green for success */
+}
+
+
+.validation-message.warning {
+ color: #ffc107; /* Yellow for warnings */
+}
+
+.input-group input.validation-error {
+ border-color: #780000;
+ background-color: rgba(120, 0, 0, 0.05);
+ box-shadow: 0 0 0 2px rgba(120, 0, 0, 0.1);
+}
+
+.input-group input.validation-success {
+ border-color: #28a745; /* Green border for success */
+ background-color: rgba(40, 167, 69, 0.05); /* Light green background */
+ box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.1); /* Green box-shadow */
+}
+
+.input-group input.validation-warning {
+ border-color: #ffc107; /* Yellow border for warnings */
+ background-color: rgba(255, 193, 7, 0.05); /* Light yellow background */
+ box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.1); /* Yellow box-shadow */
+}
+
+
+.password-strength-indicator {
+ margin-top: 8px;
+ margin-bottom: 5px;
+}
+
+.password-strength-bar {
+ width: 100%;
+ height: 4px;
+ background-color: #e9ecef;
+ border-radius: 2px;
+ overflow: hidden;
+ margin-bottom: 5px;
+}
+
+.password-strength-fill {
+ height: 100%;
+ transition: all 0.3s ease;
+ border-radius: 2px;
+}
+
+.password-strength-fill.weak {
+ width: 25%;
+ background-color: #780000;
+}
+
+.password-strength-fill.fair {
+ width: 50%;
+ background-color: #a00000;
+}
+
+.password-strength-fill.good {
+ width: 75%;
+ background-color: #780000;
+}
+
+.password-strength-fill.strong {
+ width: 100%;
+ background-color: #780000;
+}
+
+.password-strength-text {
+ font-size: 0.8rem;
+ font-weight: 500;
+ text-align: center;
+ color: #780000;
+ /* Using theme color */
+}
+
+
+@media (max-width: 600px) {
+ .validation-message {
+ font-size: 0.8rem;
+ padding: 6px 10px;
+ }
+
+ .password-strength-text {
+ font-size: 0.75rem;
+ }
+}
+
+/* ===============================================
+ FORM VALIDATION STYLES - END
+ =============================================== */
\ No newline at end of file