@@ -57,11 +57,240 @@ function initializePasswordToggle() {
57
57
// Initialize password toggle when DOM is loaded
58
58
document . addEventListener ( 'DOMContentLoaded' , function ( ) {
59
59
initializePasswordToggle ( ) ;
60
+ // Initialize form validation for both login and signup pages
61
+ initializeFormValidation ( ) ;
60
62
} ) ;
61
63
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 - z A - Z 0 - 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 - z A - Z 0 - 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 => / ^ v a l i d a t i o n - \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 => / ^ v a l i d a t i o n - \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 - z A - Z 0 - 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
+
62
285
if ( document . getElementById ( 'signup-form' ) ) {
63
286
document . getElementById ( 'signup-form' ) . addEventListener ( 'submit' , async ( e ) => {
64
287
e . preventDefault ( )
288
+
289
+ // Validate form before submission
290
+ if ( ! validateForm ( 'signup' ) ) {
291
+ return ; // Stop submission if validation fails
292
+ }
293
+
65
294
const username = document . getElementById ( 'username' ) . value
66
295
const email = document . getElementById ( 'email' ) . value
67
296
const password = document . getElementById ( 'password' ) . value
@@ -79,6 +308,12 @@ if (document.getElementById('signup-form')) {
79
308
if ( document . getElementById ( 'login-form' ) ) {
80
309
document . getElementById ( 'login-form' ) . addEventListener ( 'submit' , async ( e ) => {
81
310
e . preventDefault ( )
311
+
312
+ // Validate form before submission
313
+ if ( ! validateForm ( 'login' ) ) {
314
+ return ; // Stop submission if validation fails
315
+ }
316
+
82
317
const email = document . getElementById ( 'email' ) . value
83
318
const password = document . getElementById ( 'password' ) . value
84
319
const res = await API . login ( { email, password } )
0 commit comments