PageSourceSearch

https://www.ushandball.org/wp-content/mu-plugins/flykod-custom-login/assets/js/frontend.js?ver=1.0.3

js ushandball.org collected 2026-10-02 10:19:44 UTC 13,809 bytes, 374 lines download raw bytes

1/**
2 * Custom Login Frontend JavaScript
3 * 
4 * Handles form validation, password strength, accessibility features,
5 * and animations for the custom login page.
6 * 
7 * @package Flykod_Custom_Login
8 * @version 1.0.0
9 */
10
11(function() {
12    'use strict';
13    
14    /**
15     * Main Custom Login Class
16     */
17    class FlykodCustomLogin {
18        constructor() {
19            this.init();
20        }
21        
22        init() {
23            this.setupFormValidation();
24            this.setupPasswordStrength();
25            this.setupAccessibility();
26            this.setupAnimations();
27            this.setupPasswordToggles();
28        }
29        
30        /**
31         * Setup form validation
32         */
33        setupFormValidation() {
34            const forms = document.querySelectorAll('.login-form');
35            
36            forms.forEach(form => {
37                // Only prevent submit for password reset forms
38                form.addEventListener('submit', (e) => {
39                    if (form.id === 'resetpassform') {
40                        this.validateForm(e);
41                    } else {
42                        // For login form, just show loading state
43                        const submitBtn = form.querySelector('input[type="submit"]');
44                        if (submitBtn) {
45                            submitBtn.disabled = true;
46                            submitBtn.value = 'Processing...';
47                        }
48                    }
49                });
50            });
51        }
52        
53        /**
54         * Validate form on submit
55         * 
56         * @param {Event} e Submit event
57         */
58        validateForm(e) {
59            const form = e.target;
60            const inputs = form.querySelectorAll('input[required]');
61            let isValid = true;
62            
63            inputs.forEach(input => {
64                if (!this.validateField(input)) {
65                    isValid = false;
66                }
67            });
68            
69            // Special validation for password reset
70            if (form.id === 'resetpassform') {
71                const pass1 = form.querySelector('#pass1');
72                const pass2 = form.querySelector('#pass2');
73                
74                if (pass1 && pass2) {
75                    if (pass1.value !== pass2.value) {
76                        this.showFieldError(pass2, 'Passwords do not match');
77                        isValid = false;
78                    }
79                    
80                    if (pass1.value.length < 8) {
81                        this.showFieldError(pass1, 'Password must be at least 8 characters');
82                        isValid = false;
83                    }
84                }
85            }
86            
87            if (!isValid) {
88                e.preventDefault();
89                this.showFormError(form, 'Please correct the errors below');
90            } else {
91                // Show loading state
92                const submitBtn = form.querySelector('input[type="submit"]');
93                if (submitBtn) {
94                    submitBtn.disabled = true;
95                    const originalValue = submitBtn.value;
96                    submitBtn.value = 'Processing...';
97                    submitBtn.dataset.originalValue = originalValue;
98                }
99            }
100        }
101        
102        /**
103         * Validate individual field
104         * 
105         * @param {HTMLElement} input Input element
106         * @return {boolean} Is valid
107         */
108        validateField(input) {
109            const value = input.value.trim();
110            
111            if (!value) {
112                this.showFieldError(input, 'This field is required');
113                return false;
114            }
115            
116            // Skip email validation for now (username OR email allowed)
117            // Password validation only for reset forms
118            if (input.type === 'password' && input.name.startsWith('pass')) {
119                if (value.length < 8) {
120                    this.showFieldError(input, 'Password must be at least 8 characters');
121                    return false;
122                }
123            }
124            
125            this.clearFieldError(input);
126            return true;
127        }
128        
129        /**
130         * Show field error
131         * 
132         * @param {HTMLElement} input Input element
133         * @param {string} message Error message
134         */
135        showFieldError(input, message) {
136            this.clearFieldError(input);
137            
138            input.classList.add('error');
139            input.setAttribute('aria-invalid', 'true');
140            
141            const errorDiv = document.createElement('div');
142            errorDiv.className = 'field-error';
143            errorDiv.textContent = message;
144            errorDiv.style.color = '#c53030';
145            errorDiv.style.fontSize = '0.8rem';
146            errorDiv.style.marginTop = '4px';
147            errorDiv.setAttribute('role', 'alert');
148            
149            input.parentNode.appendChild(errorDiv);
150        }
151        
152        /**
153         * Clear field error
154         * 
155         * @param {HTMLElement} input Input element
156         */
157        clearFieldError(input) {
158            input.classList.remove('error');
159            input.removeAttribute('aria-invalid');
160            
161            const existingError = input.parentNode.querySelector('.field-error');
162            if (existingError) {
163                existingError.remove();
164            }
165        }
166        
167        /**
168         * Show form-level error
169         * 
170         * @param {HTMLElement} form Form element
171         * @param {string} message Error message
172         */
173        showFormError(form, message) {
174            let errorDiv = form.querySelector('.form-error');
175            
176            if (!errorDiv) {
177                errorDiv = document.createElement('div');
178                errorDiv.className = 'form-error login-error';
179                form.insertBefore(errorDiv, form.firstChild);
180            }
181            
182            errorDiv.innerHTML = `<p>${message}</p>`;
183            errorDiv.style.display = 'block';
184            errorDiv.setAttribute('role', 'alert');
185            
186            // Scroll to error
187            errorDiv.scrollIntoView({ behavior: 'smooth', block: 'center' });
188        }
189        
190        /**
191         * Setup password strength indicator
192         */
193        setupPasswordStrength() {
194            const pass1Input = document.querySelector('#pass1');
195            if (!pass1Input) return;
196            
197            pass1Input.addEventListener('input', (e) => {
198                this.updatePasswordStrength(e.target.value);
199            });
200        }
201        
202        /**
203         * Update password strength indicator
204         * 
205         * @param {string} password Password value
206         */
207        updatePasswordStrength(password) {
208            let strength = 0;
209            let feedback = '';
210            
211            // Length check
212            if (password.length >= 8) strength += 1;
213            if (password.length >= 12) strength += 1;
214            
215            // Character variety checks
216            if (/[a-z]/.test(password)) strength += 1;
217            if (/[A-Z]/.test(password)) strength += 1;
218            if (/[0-9]/.test(password)) strength += 1;
219            if (/[^A-Za-z0-9]/.test(password)) strength += 1;
220            
221            // Determine strength level
222            let strengthClass = 'weak';
223            if (strength >= 4) {
224                strengthClass = 'strong';
225                feedback = 'Strong password';
226            } else if (strength >= 2) {
227                strengthClass = 'medium';
228                feedback = 'Medium strength password';
229            } else {
230                feedback = 'Weak password';
231            }
232            
233            // Update or create strength indicator
234            let indicator = document.querySelector('.password-strength');
235            if (!indicator) {
236                indicator = document.createElement('div');
237                indicator.className = 'password-strength';
238                // Insert AFTER the password-input-wrapper, not inside it
239                const pass1Wrapper = document.querySelector('#pass1').closest('.password-input-wrapper');
240                if (pass1Wrapper && pass1Wrapper.parentNode) {
241                    pass1Wrapper.parentNode.insertBefore(indicator, pass1Wrapper.nextSibling);
242                }
243            }
244            
245            indicator.textContent = feedback;
246            indicator.className = `password-strength ${strengthClass}`;
247            indicator.setAttribute('aria-live', 'polite');
248        }
249        
250        /**
251         * Setup accessibility features
252         */
253        setupAccessibility() {
254            // Add ARIA labels
255            const inputs = document.querySelectorAll('input');
256            inputs.forEach(input => {
257                if (!input.getAttribute('aria-label') && input.type !== 'hidden') {
258                    const label = document.querySelector(`label[for="${input.id}"]`);
259                    if (label) {
260                        input.setAttribute('aria-label', label.textContent.trim());
261                    }
262                }
263            });
264            
265            // Keyboard navigation
266            document.addEventListener('keydown', (e) => {
267                if (e.key === 'Enter' && e.target.tagName === 'INPUT' && e.target.type !== 'submit') {
268                    const form = e.target.closest('form');
269                    if (form) {
270                        const submitBtn = form.querySelector('input[type="submit"]');
271                        if (submitBtn && !submitBtn.disabled) {
272                            submitBtn.click();
273                        }
274                    }
275                }
276            });
277        }
278        
279        /**
280         * Setup password toggle buttons
281         */
282        setupPasswordToggles() {
283            const passwordToggles = document.querySelectorAll('.password-toggle');
284            
285            passwordToggles.forEach(toggle => {
286                const wrapper = toggle.closest('.password-input-wrapper');
287                if (!wrapper) return;
288                
289                const passwordInput = wrapper.querySelector('input[type="password"], input[type="text"]');
290                if (!passwordInput) return;
291                
292                toggle.addEventListener('click', () => {
293                    const isShowing = passwordInput.type === 'text';
294                    
295                    if (isShowing) {
296                        passwordInput.type = 'password';
297                        toggle.classList.remove('showing');
298                        toggle.setAttribute('aria-label', 'Show password');
299                        this.announceToScreenReader('Password hidden');
300                    } else {
301                        passwordInput.type = 'text';
302                        toggle.classList.add('showing');
303                        toggle.setAttribute('aria-label', 'Hide password');
304                        this.announceToScreenReader('Password visible');
305                    }
306                });
307                
308                // Keyboard support
309                toggle.addEventListener('keydown', (e) => {
310                    if (e.key === 'Enter' || e.key === ' ') {
311                        e.preventDefault();
312                        toggle.click();
313                    }
314                });
315            });
316        }
317        
318        /**
319         * Setup animations
320         */
321        setupAnimations() {
322            // Add entrance animations
323            const elements = document.querySelectorAll('.form-content > *');
324            elements.forEach((element, index) => {
325                element.style.opacity = '0';
326                element.style.transform = 'translateY(20px)';
327                element.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
328                
329                setTimeout(() => {
330                    element.style.opacity = '1';
331                    element.style.transform = 'translateY(0)';
332                }, index * 100);
333            });
334        }
335        
336        /**
337         * Announce message to screen readers
338         * 
339         * @param {string} message Message to announce
340         */
341        announceToScreenReader(message) {
342            let announcement = document.getElementById('flykod-sr-announcement');
343            if (!announcement) {
344                announcement = document.createElement('div');
345                announcement.id = 'flykod-sr-announcement';
346                announcement.setAttribute('aria-live', 'polite');
347                announcement.setAttribute('aria-atomic', 'true');
348                announcement.style.position = 'absolute';
349                announcement.style.left = '-10000px';
350                announcement.style.width = '1px';
351                announcement.style.height = '1px';
352                announcement.style.overflow = 'hidden';
353                document.body.appendChild(announcement);
354            }
355            
356            announcement.textContent = message;
357            
358            setTimeout(() => {
359                announcement.textContent = '';
360            }, 1000);
361        }
362    }
363    
364    // Initialize when DOM is loaded
365    if (document.readyState === 'loading') {
366        document.addEventListener('DOMContentLoaded', () => {
367            new FlykodCustomLogin();
368        });
369    } else {
370        new FlykodCustomLogin();
371    }
372    
373})();
374

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.