ATlast — you'll never need to find your favorites on another platform again. Find your favs in the ATmosphere.
atproto

fix login typeahead autofill and auto-strip @ symbol

Typeahead fix:
- Add event listeners for input/change/blur to sync actor-typeahead selections with form state
- Ensures Enter/Tab selections from typeahead dropdown properly update form
- Allows Enter key to submit form after selection

@ stripping:
- Automatically remove leading @ from handle input
- Show helpful inline message when @ is stripped
- Inform users the @ symbol isn't needed

byarielm.fyi 4c3ae0db 8e9efd25

verified
Changed files
+61 -3
src
pages
+61 -3
src/pages/Login.tsx
··· 1 1 import { useState, useRef, useEffect } from "react"; 2 2 import "actor-typeahead"; 3 - import { Heart, Upload, Search, ArrowRight, AlertCircle } from "lucide-react"; 3 + import { 4 + Heart, 5 + Upload, 6 + Search, 7 + ArrowRight, 8 + AlertCircle, 9 + Info, 10 + } from "lucide-react"; 4 11 import FireflyLogo from "../assets/at-firefly-logo.svg?react"; 5 12 import { useFormValidation } from "../hooks/useFormValidation"; 6 13 import { validateHandle } from "../lib/validation"; ··· 20 27 }: LoginPageProps) { 21 28 const inputRef = useRef<HTMLInputElement>(null); 22 29 const [isSubmitting, setIsSubmitting] = useState(false); 30 + const [strippedAtMessage, setStrippedAtMessage] = useState(false); 23 31 24 32 const { fields, setValue, validate, getFieldProps } = useFormValidation({ 25 33 handle: "", 26 34 }); 27 35 36 + // Sync typeahead selection with form state 37 + useEffect(() => { 38 + const input = inputRef.current; 39 + if (!input) return; 40 + 41 + const handleInputChange = () => { 42 + let value = input.value.trim(); 43 + 44 + // Strip leading @ if present 45 + if (value.startsWith("@")) { 46 + value = value.substring(1); 47 + input.value = value; 48 + 49 + // Show message once 50 + if (!strippedAtMessage) { 51 + setStrippedAtMessage(true); 52 + setTimeout(() => setStrippedAtMessage(false), 3000); 53 + } 54 + } 55 + 56 + // Update form state 57 + setValue("handle", value); 58 + }; 59 + 60 + // Listen for input, change, and blur events to catch typeahead selections 61 + input.addEventListener("input", handleInputChange); 62 + input.addEventListener("change", handleInputChange); 63 + input.addEventListener("blur", handleInputChange); 64 + 65 + return () => { 66 + input.removeEventListener("input", handleInputChange); 67 + input.removeEventListener("change", handleInputChange); 68 + input.removeEventListener("blur", handleInputChange); 69 + }; 70 + }, [setValue, strippedAtMessage]); 71 + 28 72 const handleSubmit = async (e: React.FormEvent) => { 29 73 e.preventDefault(); 30 74 31 - // Get the value directly from the input 32 - const currentHandle = inputRef.current?.value || fields.handle.value; 75 + // Get the value directly from the input (in case form state is stale) 76 + let currentHandle = (inputRef.current?.value || fields.handle.value).trim(); 77 + 78 + // Strip leading @ one more time to be sure 79 + if (currentHandle.startsWith("@")) { 80 + currentHandle = currentHandle.substring(1); 81 + } 82 + 33 83 setValue("handle", currentHandle); 34 84 35 85 // Validate ··· 158 208 disabled={isSubmitting} 159 209 /> 160 210 </actor-typeahead> 211 + {strippedAtMessage && ( 212 + <div className="mt-2 flex items-center gap-2 text-sm text-cyan-700 dark:text-cyan-300"> 213 + <Info className="w-4 h-4 flex-shrink-0" /> 214 + <span> 215 + No need for the @ symbol - we've removed it for you! 216 + </span> 217 + </div> 218 + )} 161 219 {fields.handle.touched && fields.handle.error && ( 162 220 <div 163 221 id="handle-error"