ai-generated junk tool for migrating atproto identities in-browser

[feat] add new credential validation

Changed files
+47 -32
src
components
+47 -32
src/components/migration/accountDetailsForm.tsx
··· 1 - import { useState, useEffect } from 'react'; 1 + import { useState, useEffect, useCallback } from 'react'; 2 2 import { ServerDescription } from '../../lib/migration/serverDescription'; 3 3 import { validateHandle } from '../../lib/migration/accountDetailsValidation'; 4 4 ··· 33 33 const domainNames = Object.values(availableDomains); 34 34 const { isUsingDefaultDomain, customHandle } = validateHandle(currentHandle, domainNames); 35 35 36 + 36 37 setIsUsingDefaultDomain(isUsingDefaultDomain); 37 38 if (isUsingDefaultDomain) { 38 39 // Set initial handle value from current handle ··· 61 62 return ''; 62 63 }; 63 64 64 - const handleSubmit = (e: React.FormEvent) => { 65 - e.preventDefault(); 65 + // Debounced validation functions 66 + const debouncedValidateHandle = useCallback((value: string) => { 67 + const timeoutId = setTimeout(() => { 68 + setHandleError(validateHandleInput(value)); 69 + }, 500); 70 + return () => clearTimeout(timeoutId); 71 + }, []); 66 72 67 - // Reset errors 68 - setEmailError(''); 69 - setPasswordError(''); 70 - setHandleError(''); 73 + const debouncedValidateEmail = useCallback((value: string) => { 74 + const timeoutId = setTimeout(() => { 75 + setEmailError(validateEmail(value) ? '' : 'Please enter a valid email address'); 76 + }, 500); 77 + return () => clearTimeout(timeoutId); 78 + }, []); 71 79 72 - // Validate handle 80 + const debouncedValidatePassword = useCallback((value: string) => { 81 + const timeoutId = setTimeout(() => { 82 + setPasswordError(validatePassword(value) ? '' : 'Password must be at least 8 characters long'); 83 + }, 500); 84 + return () => clearTimeout(timeoutId); 85 + }, []); 86 + 87 + const handleSubmit = (e: React.FormEvent) => { 88 + e.preventDefault(); 89 + 90 + // Validate all fields 73 91 const handleValidationError = validateHandleInput(handle); 74 - if (handleValidationError) { 75 - setHandleError(handleValidationError); 76 - return; 77 - } 78 - 79 - // Validate email 80 - if (!validateEmail(email)) { 81 - setEmailError('Please enter a valid email address'); 82 - return; 83 - } 84 - 85 - // Validate password 86 - if (!validatePassword(password)) { 87 - setPasswordError('Password must be at least 8 characters long'); 88 - return; 92 + const emailValidationError = !validateEmail(email) ? 'Please enter a valid email address' : ''; 93 + const passwordValidationError = !validatePassword(password) ? 'Password must be at least 8 characters long' : ''; 94 + 95 + setHandleError(handleValidationError); 96 + setEmailError(emailValidationError); 97 + setPasswordError(passwordValidationError); 98 + 99 + // Only submit if all validations pass 100 + if (!handleValidationError && !emailValidationError && !passwordValidationError) { 101 + onSubmit(handle, email, password); 89 102 } 90 - 91 - onSubmit(handle, email, password); 92 103 }; 93 104 94 105 // Get the first available domain from the new server description ··· 114 125 id="handle" 115 126 value={handle} 116 127 onChange={(e) => { 117 - setHandle(e.target.value); 118 - setHandleError(''); 128 + const newValue = e.target.value; 129 + setHandle(newValue); 130 + debouncedValidateHandle(newValue); 119 131 }} 120 132 placeholder="alice" 121 133 className={`form-input ${handleError ? 'error' : ''}`} ··· 123 135 <span className="handle-domain">{newFirstAvailableDomain}</span> 124 136 </div> 125 137 {handleError && <div className="error-message">{handleError}</div>} 138 + {handleError && <div className="error-message">{handleError}</div>} 126 139 </div> 127 140 </div> 128 141 )} ··· 134 147 id="email" 135 148 value={email} 136 149 onChange={(e) => { 137 - setEmail(e.target.value); 138 - setEmailError(''); 150 + const newValue = e.target.value; 151 + setEmail(newValue); 152 + debouncedValidateEmail(newValue); 139 153 }} 140 154 placeholder="popbob@example.com" 141 155 required ··· 151 165 id="password" 152 166 value={password} 153 167 onChange={(e) => { 154 - setPassword(e.target.value); 155 - setPasswordError(''); 168 + const newValue = e.target.value; 169 + setPassword(newValue); 170 + debouncedValidatePassword(newValue); 156 171 }} 157 172 placeholder="hunter2" 158 173 required ··· 161 176 {passwordError && <div className="error-message">{passwordError}</div>} 162 177 </div> 163 178 <small>We recommend using a new, unique password for your new account.</small> 164 - 179 + 165 180 <div className="button-container"> 166 181 <button 167 182 type="button"