Skip to content

Commit

Permalink
feat(search): global search + integration with backend (#132)
Browse files Browse the repository at this point in the history
* fix: change avatar size to be normal instead of large

* feat: change NoResultsFound to be more reusable

* feat: add search logic infratstructure

* feat: add search integration with the backend

* feat: search tab smart rendering and refactor
  • Loading branch information
amir-kedis authored Dec 18, 2024
1 parent e6816ed commit 84e839b
Show file tree
Hide file tree
Showing 13 changed files with 494 additions and 97 deletions.
16 changes: 12 additions & 4 deletions app/src/features/search/components/NoResultsFound.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,22 @@ const NoResultsSubText = styled.p`
color: var(--color-text-secondary);
`;

const NoResultsFound: React.FC = () => {
interface NoResultsFoundProps {
message?: string;
subMessage?: string;
}

const NoResultsFound: React.FC<NoResultsFoundProps> = ({
message = "No results found",
subMessage = "Try searching for something else",
}) => {
return (
<NoResultsContainer>
<NoResultsIcon>
<Lottie animationData={noResultsAnimation} />
<Lottie animationData={noResultsAnimation} key="no-results-image" />
</NoResultsIcon>
<NoResultsText>No results found</NoResultsText>
<NoResultsSubText>Try searching for something else</NoResultsSubText>
<NoResultsText>{message}</NoResultsText>
<NoResultsSubText>{subMessage}</NoResultsSubText>
</NoResultsContainer>
);
};
Expand Down
81 changes: 41 additions & 40 deletions app/src/features/search/components/SearchBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -79,48 +79,9 @@ const SearchBar: React.FC<SearchBarProps> = ({ onClose }) => {
const currentChat = useSelector((state: RootState) =>
state.chats.chats.find((chat) => chat._id === chatId),
);
const messages = currentChat?.messages ?? [];

const [isSearchLoading, setIsSearchLoading] = useState(false);

const performSearch = (term: string) => {
if (!term) {
dispatch(setSearchResults([]));
setIsSearchLoading(false);
return;
}

const results = messages.reduce(
(
acc: Array<{
messageId: string;
highlightIndex: number;
}>,
message,
) => {
const lowerCaseTerm = term.toLowerCase();
const lowerCaseContent = message.content.toLowerCase();

if (lowerCaseContent.includes(lowerCaseTerm)) {
const matchIndices = findAllMatchIndices(message.content, term);
matchIndices.forEach((highlightIndex) => {
acc.push({
messageId: message._id,
highlightIndex,
});
});
}

return acc;
},
[],
);

dispatch(setSearchTerm(term));
dispatch(setSearchResults(results));
setIsSearchLoading(false);
};

const findAllMatchIndices = (text: string, term: string) => {
const indices = [];
let index = text.toLowerCase().indexOf(term.toLowerCase());
Expand All @@ -132,11 +93,51 @@ const SearchBar: React.FC<SearchBarProps> = ({ onClose }) => {
};

useEffect(() => {
const messages = currentChat?.messages ?? [];

const performSearch = (term: string) => {
if (!term) {
dispatch(setSearchResults([]));
setIsSearchLoading(false);
return;
}

const results = messages.reduce(
(
acc: Array<{
messageId: string;
highlightIndex: number;
}>,
message,
) => {
const lowerCaseTerm = term.toLowerCase();
const lowerCaseContent = message.content.toLowerCase();

if (lowerCaseContent.includes(lowerCaseTerm)) {
const matchIndices = findAllMatchIndices(message.content, term);
matchIndices.forEach((highlightIndex) => {
acc.push({
messageId: message._id,
highlightIndex,
});
});
}

return acc;
},
[],
);

dispatch(setSearchTerm(term));
dispatch(setSearchResults(results));
setIsSearchLoading(false);
};

const timer = setTimeout(() => {
performSearch(searchTerm);
}, 100);
return () => clearTimeout(timer);
}, [searchTerm]);
}, [searchTerm, dispatch, currentChat?.messages]);

const handleSearch = (e: React.ChangeEvent<HTMLInputElement>) => {
dispatch(setSearchTerm(e.target.value));
Expand Down
112 changes: 112 additions & 0 deletions app/src/features/search/components/SearchTab.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
import React, { useMemo } from "react";
import { RootState } from "@state/store";
import { useSelector } from "react-redux";
import { useSearch } from "@features/search/hooks/useSearch";
import { SEARCH_TABS } from "@features/search/data/tabs-components-map";
import NoResultsFound from "./NoResultsFound";
import {
hasResults,
renderGlobalResults,
renderMessageResults,
} from "../utils/searchTabsHelpers";
import ChannelResult from "./result-items/ChannelResult";
import {
SearchResultChannel,
SearchResultGroup,
SearchResultUser,
} from "../types/search";

const SearchTab: React.FC = () => {
const { searchTerm, selectedTab } = useSelector(
(state: RootState) => state.globalSearch,
);

const currentTab = SEARCH_TABS.find((tab) => tab.title === selectedTab);

const searchRequest = useMemo(
() => ({
query: searchTerm,
filter: currentTab?.filter || [],
searchSpace: currentTab?.searchSpace || [],
isGlobalSearch: currentTab?.isGlobalSearch || false,
}),
[searchTerm, currentTab],
);

const { data, isLoading, error } = useSearch(searchRequest);

if (isLoading)
return (
<NoResultsFound
message="Loading..."
subMessage="Wait for a moment please"
/>
);

if (error)
return (
<NoResultsFound
message="An error occurred"
subMessage="Please try again later"
/>
);

if (!hasResults(data)) return <NoResultsFound />;

return (
<div style={{ height: "100%" }}>
{renderMessageResults(data!.searchResult, ["text"], searchTerm)}
{renderMessageResults(
data!.searchResult,
["image", "video", "GIF", "sticker"],
searchTerm,
)}
{renderMessageResults(data!.searchResult, ["link"], searchTerm)}
{renderMessageResults(data!.searchResult, ["file"], searchTerm)}
{renderMessageResults(data!.searchResult, ["audio"], searchTerm)}

{renderGlobalResults<SearchResultGroup>(
data?.globalSearchResult?.groups || [],
"Groups",
(group) => (
<ChannelResult
key={`group-${group?.id}`}
title={group?.name}
image={group?.photo}
username={group?.name}
subscribers={group?.numberOfMembers}
/>
),
)}

{renderGlobalResults<SearchResultChannel>(
data?.globalSearchResult?.channels || [],
"Channels",
(channel) => (
<ChannelResult
key={`channel-${channel?.id}`}
title={channel?.name}
image={channel?.photo}
username={channel?.name}
subscribers={channel?.numberOfMembers}
/>
),
)}

{renderGlobalResults<SearchResultUser>(
data?.globalSearchResult?.users || [],
"Users",
(user) => (
<ChannelResult
key={`user-${user?.id}`}
title={`${user?.screenFirstName} ${user?.screenLastName}`}
image={user?.photo}
username={user?.username}
/>
),
)}
</div>
);
};

export default SearchTab;
6 changes: 3 additions & 3 deletions app/src/features/search/components/TabedSearch.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { RootState } from "@state/store";
import { motion, AnimatePresence, LayoutGroup } from "motion/react";
import { useDispatch, useSelector } from "react-redux";
import styled from "styled-components";
import { SEARCH_TABS_MOCK } from "../data/tabs-components-map";
import { SEARCH_TABS } from "../data/tabs-components-map";
import { setSelectedTab } from "@state/messages/global-search";

const TabedSearchContainer = styled(motion.div)`
Expand Down Expand Up @@ -78,7 +78,7 @@ const TabedSearch: React.FC = () => {
>
<LayoutGroup>
<TabListWrapper>
{SEARCH_TABS_MOCK.map((tab) => (
{SEARCH_TABS.map((tab) => (
<TabButton
key={tab.title}
onClick={() => dispatch(setSelectedTab(tab.title))}
Expand Down Expand Up @@ -106,7 +106,7 @@ const TabedSearch: React.FC = () => {
exit={{ opacity: 0 }}
transition={{ type: "tween", duration: 0.2 }}
>
{SEARCH_TABS_MOCK.find(
{SEARCH_TABS.find(
(tab) => tab.title === selectedTab,
)?.component()}
</TabContent>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ const ChannelResult: React.FC<ChannelResultProps> = ({
return (
<ItemWrapper>
<ImageWrapper>
<Avatar name={title} image={image} size="large" />
<Avatar name={title} image={image} />
</ImageWrapper>
<ContentWrapper>
<ItemHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const FileResult: React.FC<FileResultProps> = ({
return (
<ItemWrapper>
<ImageWrapper>
<Avatar name={title} image={image} size="large" />
<Avatar name={title} image={image} />
</ImageWrapper>
<ContentWrapper>
<ItemHeader>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,6 @@ const DateSpan = styled.span`
const Message = styled.p`
font-size: 0.875rem;
color: var(--color-text-secondary);
display: flex;
align-items: center;
`;

const MediaPreview = styled.img`
Expand All @@ -55,7 +53,6 @@ const MediaPreview = styled.img`
const Highlight = styled.span`
background: #cae3f7;
color: #000000;
padding: 0 0.125rem;
border-radius: 2px;
`;

Expand Down Expand Up @@ -98,7 +95,7 @@ const MessageResult: React.FC<MessageResultProps> = ({
return (
<ItemWrapper>
<ImageWrapper>
<Avatar name={title} image={image} size="large" />
<Avatar name={title} image={image} />
</ImageWrapper>
<ContentWrapper>
<ItemHeader>
Expand Down
34 changes: 0 additions & 34 deletions app/src/features/search/components/tabs/MessagesTab.tsx

This file was deleted.

Loading

0 comments on commit 84e839b

Please sign in to comment.