Skip to content

Commit afe0de2

Browse files
authored
Feat: Implement Real-Time Form Validation Message with Instant User Feedback for Better User Experience (#615)
* docs: Add comprehensive style guide for The Cawnpore Magazine Document brand identity, typography, colors, components, layout, animations, and accessibility standards to ensure consistent design across the * docs: Improve style guide with proper CSS selectors and URL safety notes Add CSS classes to replace inline styles, improve code blocks with proper selectors, and enhance security with notes about external URL verification * style(login): Enhance password toggle styling and browser compatibility * feat(auth): Add password visibility toggle functionality * feat(login&signUP): Add password visibility toggle to login and sign up page * refactor(auth.js): replace inline hover styling with CSS class * chore(html): add SRI and crossorigin to Font Awesome CDN link * feat: add validation message containers to login and signup forms * feat(validation): implement comprehensive form validation system * fix: use green for success validation and improve class handling
1 parent 9eec462 commit afe0de2

File tree

4 files changed

+365
-1
lines changed

4 files changed

+365
-1
lines changed

login.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ <h2>Login</h2>
1717
<div class="input-group">
1818
<label for="email">Email</label>
1919
<input id="email" type="email" placeholder="Email" required>
20+
<div class="validation-message" id="email-validation"></div>
2021
</div>
2122
<div class="input-group">
2223
<label for="password">Password</label>
@@ -28,6 +29,7 @@ <h2>Login</h2>
2829
<i class="fas fa-eye" aria-hidden="true"></i>
2930
</button>
3031
</div>
32+
<div class="validation-message" id="password-validation"></div>
3133
</div>
3234
<button type="submit">Login</button>
3335
</form>

scripts/auth.js

Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,11 +57,240 @@ function initializePasswordToggle() {
5757
// Initialize password toggle when DOM is loaded
5858
document.addEventListener('DOMContentLoaded', function () {
5959
initializePasswordToggle();
60+
// Initialize form validation for both login and signup pages
61+
initializeFormValidation();
6062
});
6163

64+
/* ===============================================
65+
FORM VALIDATION SYSTEM - START
66+
=============================================== */
67+
68+
// Validation configuration for reusability
69+
const VALIDATION_RULES = {
70+
email: { pattern: /^[^\s@]+@[^\s@]+\.[^\s@]+$/, minLength: 5, maxLength: 100 },
71+
username: { pattern: /^[a-zA-Z0-9_-]+$/, minLength: 3, maxLength: 20 },
72+
password: { login: { minLength: 6 }, signup: { minLength: 8, strengthCheck: true } }
73+
};
74+
75+
/**
76+
* Initialize form validation for both login and signup pages
77+
*/
78+
function initializeFormValidation() {
79+
const formConfigs = {
80+
'login-form': [
81+
{ id: 'email', type: 'email' },
82+
{ id: 'password', type: 'password', mode: 'login' }
83+
],
84+
'signup-form': [
85+
{ id: 'username', type: 'username' },
86+
{ id: 'email', type: 'email' },
87+
{ id: 'password', type: 'password', mode: 'signup' }
88+
]
89+
};
90+
91+
// Setup validation for each form
92+
Object.entries(formConfigs).forEach(([formId, fields]) => {
93+
if (document.getElementById(formId)) {
94+
fields.forEach(field => setupFieldValidation(field));
95+
}
96+
});
97+
}
98+
99+
/**
100+
* Setup validation for a single field - removes duplicate event listener code
101+
*/
102+
function setupFieldValidation(field) {
103+
const input = document.getElementById(field.id);
104+
if (!input) return;
105+
106+
const validator = () => validateField(input, field.type, field.mode || 'login');
107+
['input', 'blur'].forEach(event => input.addEventListener(event, validator));
108+
}
109+
110+
/**
111+
* Universal field validation function - handles all types with common logic
112+
*/
113+
function validateField(input, type, mode) {
114+
if (!input) return false;
115+
116+
const value = input.value.trim();
117+
const messageId = `${input.id}-validation`;
118+
119+
if (!value) {
120+
hideValidationMessage(messageId, input);
121+
if (type === 'password' && mode === 'signup') hidePasswordStrengthIndicator(messageId);
122+
return false;
123+
}
124+
125+
switch (type) {
126+
case 'email': return validateWithRules(input, value, messageId, 'email', '✓ Email format is valid');
127+
case 'username': return validateWithRules(input, value, messageId, 'username', '✓ Username is valid');
128+
case 'password': return validatePasswordField(input, value, messageId, mode);
129+
default: return false;
130+
}
131+
}
132+
133+
/**
134+
* Unified validation with rules - eliminates duplicate code
135+
*/
136+
function validateWithRules(input, value, messageId, type, successMsg) {
137+
const rules = VALIDATION_RULES[type];
138+
const errors = [
139+
[type === 'email' && !rules.pattern.test(value), 'Please enter a valid email address'],
140+
[type === 'email' && value.includes('..'), 'Email cannot contain consecutive dots'],
141+
[type === 'email' && (value.startsWith('.') || value.endsWith('.')), 'Email cannot start or end with a dot'],
142+
[type === 'username' && value.length < rules.minLength, `Username must be at least ${rules.minLength} characters (${value.length}/${rules.minLength})`],
143+
[type === 'username' && value.length > rules.maxLength, `Username cannot exceed ${rules.maxLength} characters`],
144+
[type === 'username' && !rules.pattern.test(value), 'Username can only contain letters, numbers, underscore, and hyphen'],
145+
[type === 'username' && !/^[a-zA-Z0-9]/.test(value), 'Username must start with a letter or number']
146+
].filter(([condition]) => condition);
147+
148+
if (errors.length) {
149+
showValidationMessage(messageId, errors[0][1], 'error', input);
150+
return false;
151+
}
152+
153+
showValidationMessage(messageId, successMsg, 'success', input);
154+
return true;
155+
}
156+
157+
/**
158+
* Concise password validation with mode support
159+
*/
160+
function validatePasswordField(input, password, messageId, mode) {
161+
const rules = VALIDATION_RULES.password[mode];
162+
if (!rules) return false;
163+
164+
if (password.length < rules.minLength) {
165+
showValidationMessage(messageId, `Password must be at least ${rules.minLength} characters (${password.length}/${rules.minLength})`, 'error', input);
166+
if (mode === 'signup') hidePasswordStrengthIndicator(messageId);
167+
return false;
168+
}
169+
170+
if (mode === 'signup' && rules.strengthCheck) {
171+
const strength = calculatePasswordStrength(password);
172+
showPasswordStrengthIndicator(messageId, strength);
173+
const messages = {
174+
weak: ['Password is weak. ' + strength.suggestions[0], 'warning', false],
175+
fair: ['Password is fair. ' + (strength.suggestions[0] || 'Consider adding more variety'), 'warning', true],
176+
good: ['✓ Good password strength', 'success', true],
177+
strong: ['✓ Strong password!', 'success', true]
178+
};
179+
const [message, type, isValid] = messages[strength.level];
180+
showValidationMessage(messageId, message, type, input);
181+
return isValid;
182+
}
183+
184+
showValidationMessage(messageId, '✓ Password entered', 'success', input);
185+
return true;
186+
}
187+
188+
/**
189+
* Efficient validation message utilities
190+
*/
191+
function showValidationMessage(messageId, message, type, inputElement) {
192+
const messageElement = document.getElementById(messageId);
193+
if (!messageElement || !inputElement) return;
194+
195+
messageElement.className = `validation-message show ${type}`;
196+
197+
// Remove all validation-* classes using classList methods
198+
Array.from(inputElement.classList)
199+
.filter(cls => /^validation-\w+$/.test(cls))
200+
.forEach(cls => inputElement.classList.remove(cls));
201+
202+
// Add new validation class
203+
inputElement.classList.add(`validation-${type}`);
204+
messageElement.textContent = message;
205+
}
206+
207+
function hideValidationMessage(messageId, inputElement) {
208+
const messageElement = document.getElementById(messageId);
209+
if (messageElement) messageElement.className = 'validation-message';
210+
211+
if (inputElement) {
212+
// Remove all validation-* classes using classList methods
213+
Array.from(inputElement.classList)
214+
.filter(cls => /^validation-\w+$/.test(cls))
215+
.forEach(cls => inputElement.classList.remove(cls));
216+
}
217+
}
218+
219+
/**
220+
* Concise password strength calculation
221+
*/
222+
function calculatePasswordStrength(password) {
223+
const checks = [
224+
[password.length >= 8, 'Add more characters'],
225+
[password.length >= 12, ''],
226+
[/[a-z]/.test(password), 'Add lowercase letters'],
227+
[/[A-Z]/.test(password), 'Add uppercase letters'],
228+
[/[0-9]/.test(password), 'Add numbers'],
229+
[/[^a-zA-Z0-9]/.test(password), 'Add special characters (!@#$%)']
230+
];
231+
232+
const score = checks.filter(([test]) => test).length;
233+
const suggestions = checks.filter(([test, msg]) => !test && msg).map(([, msg]) => msg);
234+
const levels = ['weak', 'weak', 'weak', 'fair', 'fair', 'good', 'strong'];
235+
236+
return { level: levels[score] || 'weak', score, suggestions };
237+
}
238+
239+
/**
240+
* Efficient password strength indicator
241+
*/
242+
function showPasswordStrengthIndicator(messageId, strength) {
243+
const messageElement = document.getElementById(messageId);
244+
if (!messageElement) return;
245+
246+
// Clean up existing indicator
247+
const existing = messageElement.parentNode.querySelector('.password-strength-indicator');
248+
if (existing) existing.remove();
249+
250+
const indicator = document.createElement('div');
251+
indicator.innerHTML = `
252+
<div class="password-strength-indicator">
253+
<div class="password-strength-bar">
254+
<div class="password-strength-fill ${strength.level}"></div>
255+
</div>
256+
<div class="password-strength-text">Password strength: ${strength.level.charAt(0).toUpperCase() + strength.level.slice(1)}</div>
257+
</div>
258+
`;
259+
260+
messageElement.parentNode.insertBefore(indicator.firstElementChild, messageElement.nextSibling);
261+
}
262+
263+
function hidePasswordStrengthIndicator(messageId) {
264+
const messageElement = document.getElementById(messageId);
265+
const indicator = messageElement?.parentNode?.querySelector('.password-strength-indicator');
266+
if (indicator) indicator.remove();
267+
}
268+
269+
/**
270+
* Efficient form validation for submission
271+
*/
272+
function validateForm(formType) {
273+
const fieldConfigs = {
274+
login: [['email', 'email'], ['password', 'password', 'login']],
275+
signup: [['username', 'username'], ['email', 'email'], ['password', 'password', 'signup']]
276+
};
277+
278+
const fields = fieldConfigs[formType];
279+
return fields ? fields.every(([id, type, mode]) => {
280+
const input = document.getElementById(id);
281+
return input && validateField(input, type, mode || 'login');
282+
}) : false;
283+
}
284+
62285
if (document.getElementById('signup-form')) {
63286
document.getElementById('signup-form').addEventListener('submit', async (e) => {
64287
e.preventDefault()
288+
289+
// Validate form before submission
290+
if (!validateForm('signup')) {
291+
return; // Stop submission if validation fails
292+
}
293+
65294
const username = document.getElementById('username').value
66295
const email = document.getElementById('email').value
67296
const password = document.getElementById('password').value
@@ -79,6 +308,12 @@ if (document.getElementById('signup-form')) {
79308
if (document.getElementById('login-form')) {
80309
document.getElementById('login-form').addEventListener('submit', async (e) => {
81310
e.preventDefault()
311+
312+
// Validate form before submission
313+
if (!validateForm('login')) {
314+
return; // Stop submission if validation fails
315+
}
316+
82317
const email = document.getElementById('email').value
83318
const password = document.getElementById('password').value
84319
const res = await API.login({ email, password })

signup.html

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ <h2>Sign up</h2>
1717
<div class="input-group">
1818
<label for="username">Username</label>
1919
<input id="username" type="text" placeholder="Username" required>
20+
<div class="validation-message" id="username-validation"></div>
2021
</div>
2122
<div class="input-group">
2223
<label for="email">Email</label>
2324
<input id="email" type="email" placeholder="Email" required>
25+
<div class="validation-message" id="email-validation"></div>
2426
</div>
2527
<div class="input-group">
2628
<label for="password">Password</label>
@@ -32,6 +34,7 @@ <h2>Sign up</h2>
3234
<i class="fas fa-eye" aria-hidden="true"></i>
3335
</button>
3436
</div>
37+
<div class="validation-message" id="password-validation"></div>
3538
</div>
3639
<button type="submit">Sign up</button>
3740
</form>

0 commit comments

Comments
 (0)