Skip to content
This repository was archived by the owner on May 12, 2026. It is now read-only.

Commit 822401c

Browse files
committed
UserSearchWidget
1 parent 02993d7 commit 822401c

1 file changed

Lines changed: 86 additions & 17 deletions

File tree

‎src/lib/components/UserSearchWidget.svelte‎

Lines changed: 86 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,28 @@
3636
let searchResults = $state<UserResult[]>([]);
3737
let showResults = $state(false);
3838
let searchError = $state("");
39+
let searchType = $state<"email" | "userid" | "username">("username");
3940
let debounceTimer: number | null = null;
4041
42+
// Detect search type based on input
43+
function detectSearchType(input: string): "email" | "userid" | "username" {
44+
if (input.includes("@") && input.includes(".")) {
45+
return "email";
46+
}
47+
const uuidRegex =
48+
/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
49+
if (uuidRegex.test(input)) {
50+
return "userid";
51+
}
52+
return "username";
53+
}
54+
55+
const searchTypeLabels: Record<string, string> = {
56+
email: "Searching by email",
57+
userid: "Looking up User ID",
58+
username: "Searching by username",
59+
};
60+
4161
// Trigger search on mount if initialUsername is provided
4262
$effect(() => {
4363
if (initialUsername && !selectedUserId) {
@@ -46,19 +66,29 @@
4666
});
4767
4868
async function searchUsers(query: string) {
49-
if (!query.trim()) {
69+
const trimmed = query.trim();
70+
if (!trimmed) {
5071
searchResults = [];
5172
showResults = false;
5273
return;
5374
}
5475
76+
const type = detectSearchType(trimmed);
77+
searchType = type;
5578
isSearching = true;
5679
searchError = "";
5780
5881
try {
59-
const response = await trackedFetch(
60-
`/api/users/search?q=${encodeURIComponent(query)}`,
61-
);
82+
let url: string;
83+
if (type === "email") {
84+
url = `/api/users/search-by-email?email=${encodeURIComponent(trimmed)}`;
85+
} else if (type === "userid") {
86+
url = `/api/users/search-by-userid?user_id=${encodeURIComponent(trimmed)}`;
87+
} else {
88+
url = `/api/users/search?q=${encodeURIComponent(trimmed)}`;
89+
}
90+
91+
const response = await trackedFetch(url);
6292
6393
if (!response.ok) {
6494
const errorDetails = await extractErrorFromResponse(
@@ -72,18 +102,40 @@
72102
}
73103
74104
const data = await response.json();
75-
// Filter out users without a username AND filter by search query
76-
const searchLower = query.toLowerCase();
77-
searchResults = (data.users || []).filter((user: UserResult) => {
78-
if (!user.username || user.username.trim() === "") {
79-
return false;
80-
}
81-
// Match if username or email contains the search query
82-
const usernameMatch = user.username.toLowerCase().includes(searchLower);
83-
const emailMatch = user.email?.toLowerCase().includes(searchLower);
84-
return usernameMatch || emailMatch;
85-
});
86-
showResults = true;
105+
106+
// Normalize response: userid endpoint returns {user: {...}}, others return {users: [...]}
107+
let users: UserResult[];
108+
if (type === "userid") {
109+
users = data.user ? [data.user] : [];
110+
} else {
111+
users = data.users || [];
112+
}
113+
114+
// Filter out users without a username
115+
users = users.filter(
116+
(user: UserResult) => user.username && user.username.trim() !== "",
117+
);
118+
119+
// For username searches, also filter by search query match
120+
if (type === "username") {
121+
const searchLower = trimmed.toLowerCase();
122+
users = users.filter((user: UserResult) => {
123+
const usernameMatch = user.username
124+
.toLowerCase()
125+
.includes(searchLower);
126+
const emailMatch = user.email?.toLowerCase().includes(searchLower);
127+
return usernameMatch || emailMatch;
128+
});
129+
}
130+
131+
searchResults = users;
132+
133+
// Auto-select if userid lookup returned exactly one result
134+
if (type === "userid" && users.length === 1) {
135+
handleSelectUser(users[0]);
136+
} else {
137+
showResults = true;
138+
}
87139
} catch (error) {
88140
console.error("User search error:", error);
89141
searchError =
@@ -141,7 +193,7 @@
141193
<input
142194
type="text"
143195
class="search-input"
144-
placeholder="Search users by username or email..."
196+
placeholder="Enter username, email, or user ID..."
145197
value={searchQuery}
146198
oninput={handleSearchInput}
147199
onfocus={() => {
@@ -165,6 +217,12 @@
165217
{/if}
166218
</div>
167219

220+
{#if searchQuery.trim() && !selectedUserId}
221+
<div class="search-type-indicator">
222+
{searchTypeLabels[searchType]}
223+
</div>
224+
{/if}
225+
168226
{#if showResults && searchResults.length > 0}
169227
<div class="search-results">
170228
{#each searchResults as user}
@@ -319,6 +377,17 @@
319377
}
320378
}
321379
380+
.search-type-indicator {
381+
font-size: 0.7rem;
382+
color: #6b7280;
383+
padding: 0.25rem 0;
384+
font-style: italic;
385+
}
386+
387+
:global([data-mode="dark"]) .search-type-indicator {
388+
color: var(--color-surface-400);
389+
}
390+
322391
.search-results {
323392
width: 100%;
324393
max-height: 300px;

0 commit comments

Comments
 (0)