+41
-26
src/pages/Login.tsx
+41
-26
src/pages/Login.tsx
···
32
handle: "",
33
});
34
35
-
// Sync typeahead selection with form state and extract avatar
36
useEffect(() => {
37
const input = inputRef.current;
38
if (!input) return;
39
40
const handleInputChange = () => {
41
let value = input.value.trim();
42
···
52
}
53
}
54
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
// Update form state
68
setValue("handle", value);
69
};
70
71
-
// Listen for input, change, and blur events to catch typeahead selections
72
input.addEventListener("input", handleInputChange);
73
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
85
return () => {
86
input.removeEventListener("input", handleInputChange);
87
input.removeEventListener("change", handleInputChange);
88
-
input.removeEventListener("blur", handleInputChange);
89
-
input.removeEventListener("actor-select", handleSelection as EventListener);
90
};
91
}, [setValue, strippedAtMessage]);
92
···
32
handle: "",
33
});
34
35
+
// Sync typeahead selection with form state and fetch avatar
36
useEffect(() => {
37
const input = inputRef.current;
38
if (!input) return;
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
+
70
const handleInputChange = () => {
71
let value = input.value.trim();
72
···
82
}
83
}
84
85
// Update form state
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
+
}
95
};
96
97
+
// Listen for input and change events
98
input.addEventListener("input", handleInputChange);
99
input.addEventListener("change", handleInputChange);
100
101
return () => {
102
input.removeEventListener("input", handleInputChange);
103
input.removeEventListener("change", handleInputChange);
104
+
clearTimeout(debounceTimer);
105
};
106
}, [setValue, strippedAtMessage]);
107