Skip to content

Commit d45e508

Browse files
committed
User serach feature
1 parent 62ab0f3 commit d45e508

File tree

3 files changed

+127
-46
lines changed

3 files changed

+127
-46
lines changed

src/components/chat/searchBox.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { LuSearch } from "react-icons/lu";
2+
import { InputGroup } from "../ui/input-group";
3+
import { Input } from "@chakra-ui/react/input";
4+
import { useEffect, useState } from "react";
5+
import useDebounce from "@/utils/useDebounce";
6+
7+
interface SearchBoxProps {
8+
setSearch: (value: string) => void;
9+
setLoading: (value: boolean) => void;
10+
}
11+
12+
const SearchBox = ({ setSearch, setLoading }: SearchBoxProps) => {
13+
const [searchToken, setSearchToken] = useState<string | null>(null);
14+
const debouncedSearchToken = useDebounce(searchToken, 500);
15+
16+
useEffect(() => {
17+
setSearch(debouncedSearchToken || "");
18+
}, [debouncedSearchToken]);
19+
20+
return (
21+
<InputGroup flex="1" startElement={<LuSearch />}>
22+
<Input
23+
placeholder="Search contacts"
24+
value={searchToken || ""}
25+
onChange={(e) => {
26+
setLoading(true);
27+
setSearchToken(e.currentTarget.value)
28+
}
29+
}
30+
/>
31+
</InputGroup>
32+
);
33+
};
34+
35+
export default SearchBox;

src/components/chat/usersList.tsx

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,27 @@ import { ChatActionType } from "@/context/Chat/types";
88
import { unSubscribeDatabase } from "@/services";
99
import { firebaseDatabase } from "@/config";
1010
import { onValue, ref } from "firebase/database";
11-
import { numberFormatter } from "@/utils";
11+
import { DB_NAME, numberFormatter } from "@/utils";
12+
import SearchBox from "./searchBox";
1213

1314
const UsersList = () => {
1415
const [loading, setLoading] = useState(true);
1516
const { user: authUser } = useAuth();
1617
const { chatRoomId, oneToOneRoomId } = useChat();
1718
const dispatch = useChatDispatch();
19+
const { userList: storeUserList } = useChat();
1820
const [userList, setUserList] = useState<User[] | []>([]);
1921
const [userCount, setUserCount] = useState<number>(0);
22+
const [startSearch, setStartSearch] = useState(false);
2023

2124
useEffect(() => {
2225
setLoading(true);
23-
const collectionRef = ref(firebaseDatabase, "usersTable");
26+
const collectionRef = ref(firebaseDatabase, DB_NAME.USER_TABLE);
2427
const unsubscribe = onValue(collectionRef, (snapshot) => {
2528
const data = snapshot.val();
2629
if (data) {
2730
const users = Object.values(data) as User[];
2831
setUserCount(users.length || 0);
29-
// Remove duplicate users and the current user
30-
const uniqueUsers = users.filter(
31-
(user, index, self) =>
32-
user.uid && user.uid !== authUser?.uid && index === self.findIndex((u) => u.uid === user.uid)
33-
);
34-
setUserList(uniqueUsers);
3532
dispatch({
3633
type: ChatActionType.SET_USER_LIST,
3734
payload: data,
@@ -45,11 +42,13 @@ const UsersList = () => {
4542
};
4643
}, [authUser?.uid, dispatch]);
4744

48-
const handleUserClick = (user: User | "chatRoom") => {
45+
const handleUserClick = (user: User | string) => {
4946
const newChatRoomId =
50-
user === "chatRoom" ? "chatRoom" : `${authUser?.uid}+${user.uid}`;
47+
typeof user === "string"
48+
? DB_NAME.CHAT_ROOM
49+
: `${authUser?.uid}+${user.uid}`;
5150
const newOneToOneRoomId =
52-
user !== "chatRoom" ? `${user.uid}+${authUser?.uid}` : "";
51+
typeof user !== "string" ? `${user.uid}+${authUser?.uid}` : "";
5352

5453
// Unsubscribe from the database
5554
const roomList = [chatRoomId, oneToOneRoomId].filter(Boolean);
@@ -65,6 +64,16 @@ const UsersList = () => {
6564
});
6665
};
6766

67+
const handleSearchUserList = (token: string) => {
68+
const sourceList = Object.values(storeUserList) as User[];
69+
const regex = new RegExp(token, "i");
70+
const result = sourceList
71+
.filter((user) => user.userName.toLowerCase().match(regex))
72+
.sort((a, b) => (a.createdAt < b.createdAt ? 1 : -1));
73+
setUserList(result);
74+
setStartSearch(false);
75+
};
76+
6877
return (
6978
<Box
7079
p={2}
@@ -78,61 +87,81 @@ const UsersList = () => {
7887
{loading && <Spinner />}
7988
{!loading && (
8089
<Stack gap="2">
90+
<SearchBox
91+
setSearch={handleSearchUserList}
92+
setLoading={setStartSearch}
93+
/>
8194
<HStack
8295
cursor={"pointer"}
8396
_hover={{ bg: "orange.50", color: "orange.400" }}
84-
bg={chatRoomId === "chatRoom" ? "orange.50" : "gray.subtle"}
97+
bg={chatRoomId === DB_NAME.CHAT_ROOM ? "orange.50" : "gray.subtle"}
8598
color={"orange.400"}
8699
borderRadius={"md"}
87100
title="Common Group"
88101
p={"2"}
89-
onClick={() => handleUserClick("chatRoom")}
102+
onClick={() => handleUserClick(DB_NAME.CHAT_ROOM)}
90103
>
91104
<AvatarGroup size="sm">
92105
<Avatar
93106
name={authUser?.userName}
94107
src={authUser?.profile_picture}
95108
colorPalette={"orange"}
96109
/>
97-
<Avatar colorPalette={'orange'} fallback={`+${numberFormatter.format(userCount)}`} />
110+
<Avatar
111+
colorPalette={"orange"}
112+
fallback={`+${numberFormatter.format(userCount)}`}
113+
/>
98114
</AvatarGroup>
99115
<Text fontWeight="medium" textStyle={"sm"} lineClamp={1}>
100116
Common Group
101117
</Text>
102118
</HStack>
103-
{userList?.map((user) => (
104-
<HStack
105-
key={user.uid}
106-
cursor={"pointer"}
107-
bg={
108-
chatRoomId === `${authUser?.uid}+${user.uid}`
109-
? "orange.50"
110-
: "gray.subtle"
111-
}
112-
color={
113-
chatRoomId === `${authUser?.uid}+${user.uid}`
114-
? "orange.400"
115-
: "initial"
116-
}
117-
_hover={{ bg: "orange.50", color: "orange.400" }}
118-
borderRadius={"md"}
119-
title={user.userName}
120-
p={"2"}
121-
onClick={() => handleUserClick(user)}
122-
>
123-
<Avatar
124-
name={user.userName}
125-
size="sm"
126-
src={user.profile_picture}
127-
captionSide={"bottom"}
128-
/>
129-
<Stack gap="0">
130-
<Text fontWeight="medium" textStyle={"sm"} lineClamp={1}>
131-
{user.userName}
132-
</Text>
133-
</Stack>
119+
120+
{startSearch ? (
121+
<Stack alignItems={"center"}>
122+
<Spinner />
123+
</Stack>
124+
) : userList.length < 1 ? (
125+
<HStack justifyContent={"center"}>
126+
<Text fontWeight="medium" textStyle={"xs"}>
127+
No Search result found
128+
</Text>
134129
</HStack>
135-
))}
130+
) : (
131+
userList?.map((user) => (
132+
<HStack
133+
key={user.uid}
134+
cursor={"pointer"}
135+
bg={
136+
chatRoomId === `${authUser?.uid}+${user.uid}`
137+
? "orange.50"
138+
: "gray.subtle"
139+
}
140+
color={
141+
chatRoomId === `${authUser?.uid}+${user.uid}`
142+
? "orange.400"
143+
: "initial"
144+
}
145+
_hover={{ bg: "orange.50", color: "orange.400" }}
146+
borderRadius={"md"}
147+
title={user.userName}
148+
p={"2"}
149+
onClick={() => handleUserClick(user)}
150+
>
151+
<Avatar
152+
name={user.userName}
153+
size="sm"
154+
src={user.profile_picture}
155+
captionSide={"bottom"}
156+
/>
157+
<Stack gap="1" direction={"column"} align={"flex-start"}>
158+
<Text fontWeight="medium" textStyle={"sm"} lineClamp={1}>
159+
{user.userName}
160+
</Text>
161+
</Stack>
162+
</HStack>
163+
))
164+
)}
136165
</Stack>
137166
)}
138167
</Box>

src/utils/useDebounce.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { useState, useEffect } from "react";
2+
3+
const useDebounce = <T>(value: T, delay = 500) => {
4+
const [debouncedValue, setDebouncedValue] = useState(value);
5+
6+
useEffect(() => {
7+
const timer = setTimeout(() => {
8+
setDebouncedValue(value);
9+
}, delay);
10+
11+
return () => clearTimeout(timer);
12+
}, [value, delay]);
13+
14+
return debouncedValue;
15+
};
16+
17+
export default useDebounce;

0 commit comments

Comments
 (0)