+41
-26
src/pages/Login.tsx
+41
-26
src/pages/Login.tsx
···
32
32
handle: "",
33
33
});
34
34
35
-
// Sync typeahead selection with form state and extract avatar
35
+
// Sync typeahead selection with form state and fetch avatar
36
36
useEffect(() => {
37
37
const input = inputRef.current;
38
38
if (!input) return;
39
39
40
+
let debounceTimer: ReturnType<typeof setTimeout>;
41
+
42
+
const fetchAvatar = async (handle: string) => {
43
+
if (!handle || handle.length < 3) {
44
+
setSelectedAvatar(null);
45
+
return;
46
+
}
47
+
48
+
try {
49
+
const url = new URL(
50
+
"xrpc/app.bsky.actor.searchActorsTypeahead",
51
+
"https://public.api.bsky.app"
52
+
);
53
+
url.searchParams.set("q", handle);
54
+
url.searchParams.set("limit", "1");
55
+
56
+
const res = await fetch(url);
57
+
const json = await res.json();
58
+
59
+
if (json.actors?.[0]?.avatar) {
60
+
setSelectedAvatar(json.actors[0].avatar);
61
+
} else {
62
+
setSelectedAvatar(null);
63
+
}
64
+
} catch (error) {
65
+
// Silently fail - avatar is optional
66
+
setSelectedAvatar(null);
67
+
}
68
+
};
69
+
40
70
const handleInputChange = () => {
41
71
let value = input.value.trim();
42
72
···
52
82
}
53
83
}
54
84
55
-
// Check if typeahead has selection data (avatar)
56
-
const typeaheadElement = input.closest("actor-typeahead");
57
-
if (typeaheadElement) {
58
-
const avatar = typeaheadElement.getAttribute("data-avatar");
59
-
if (avatar) {
60
-
setSelectedAvatar(avatar);
61
-
} else if (value === "") {
62
-
// Clear avatar when input is cleared
63
-
setSelectedAvatar(null);
64
-
}
65
-
}
66
-
67
85
// Update form state
68
86
setValue("handle", value);
87
+
88
+
// Debounce avatar fetch
89
+
clearTimeout(debounceTimer);
90
+
if (value === "") {
91
+
setSelectedAvatar(null);
92
+
} else {
93
+
debounceTimer = setTimeout(() => fetchAvatar(value), 300);
94
+
}
69
95
};
70
96
71
-
// Listen for input, change, and blur events to catch typeahead selections
97
+
// Listen for input and change events
72
98
input.addEventListener("input", handleInputChange);
73
99
input.addEventListener("change", handleInputChange);
74
-
input.addEventListener("blur", handleInputChange);
75
-
76
-
// Also listen for custom typeahead selection event if it exists
77
-
const handleSelection = (e: Event) => {
78
-
const customEvent = e as CustomEvent;
79
-
if (customEvent.detail?.avatar) {
80
-
setSelectedAvatar(customEvent.detail.avatar);
81
-
}
82
-
};
83
-
input.addEventListener("actor-select", handleSelection as EventListener);
84
100
85
101
return () => {
86
102
input.removeEventListener("input", handleInputChange);
87
103
input.removeEventListener("change", handleInputChange);
88
-
input.removeEventListener("blur", handleInputChange);
89
-
input.removeEventListener("actor-select", handleSelection as EventListener);
104
+
clearTimeout(debounceTimer);
90
105
};
91
106
}, [setValue, strippedAtMessage]);
92
107