Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Added post search suggestion when search on searchbar successfully issue 586 #591

Merged
merged 4 commits into from
Aug 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 33 additions & 1 deletion backend/src/routes/post/controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -893,4 +893,36 @@ export const getAllTagsController = async (req: Request, res: Response) => {
error: "Failed to fetch tags",
});
}
};
};

export const getSearchSuggestions = async (req: Request, res: Response) => {
try {
const searchQuery = req.query.searchQuery as string || "";

if (!searchQuery) {
return res.status(200).json({ suggestions: [] });
}

const suggestions = await prisma.post.findMany({
where: {
OR: [
{ title: { contains: searchQuery, mode: 'insensitive' } },
{ description: { contains: searchQuery, mode: 'insensitive' } }
]
},
select: {
id: true,
title: true,
},
orderBy: {
title: 'asc',
},
take: 10,
});

res.status(200).json({ suggestions });
} catch (error) {
console.error(error);
res.status(500).json({ error: 'Failed to fetch search suggestions' });
}
};
4 changes: 3 additions & 1 deletion backend/src/routes/post/route.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Router } from "express";
import authMiddleware from "../../middleware/auth"

import { checkFavoriteStatusController,aiCustomization, createCommentController, createPostController, deletePostController, favoritePostController, getAllTagsController, getCommentsController, getFavoritePostsController, getLeaderboardController, getPostController, getPostReactionsController, getPostsWithPagination, getUserReactionController, reactToPostController, removeReactionController, unfavoritePostController, updatePostController } from "./controller";
import { checkFavoriteStatusController,aiCustomization, createCommentController, createPostController, deletePostController, favoritePostController, getAllTagsController, getCommentsController, getFavoritePostsController, getLeaderboardController, getPostController, getPostReactionsController, getPostsWithPagination, getUserReactionController, reactToPostController, removeReactionController, unfavoritePostController, updatePostController, getSearchSuggestions } from "./controller";

const postRouter = Router();

Expand Down Expand Up @@ -43,4 +43,6 @@ postRouter.get('/all/tags', getAllTagsController);

postRouter.post('/customize', aiCustomization);

postRouter.get('/search/suggestions', getSearchSuggestions);

export default postRouter;
13 changes: 12 additions & 1 deletion frontend/src/hooks/usePosts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,16 @@ const usePosts = ({ initialPage = 1, pageSize = 12 }: Props) => {
fetchPosts(page, pageSize, searchQuery, initialTags);
}, [page, searchParams]);

const fetchSearchSuggestions = async (query: string) => {
try {
const response = await axios.get(`/api/v1/posts/search/suggestions?searchQuery=${query}`);
return response.data.suggestions;
} catch (error) {
console.error("Failed to fetch search suggestions");
return [];
}
};

const handlePreviousPage = () => {
if (page > 1) {
setPage(page - 1);
Expand Down Expand Up @@ -96,7 +106,8 @@ const usePosts = ({ initialPage = 1, pageSize = 12 }: Props) => {
searchQuery,
setSearchQuery,
tags,
fetchPosts
fetchPosts ,
fetchSearchSuggestions
};
};

Expand Down
46 changes: 46 additions & 0 deletions frontend/src/pages/Posts.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const Posts = () => {
searchQuery,
setSearchQuery,
fetchPosts,
fetchSearchSuggestions
} = usePosts({
initialPage: 1,
pageSize: 12,
Expand All @@ -32,6 +33,9 @@ const Posts = () => {
const [showFilterDialog, setShowFilterDialog] = useState(false);
const [tagInput, setTagInput] = useState("");
const [filterTags, setFilterTags] = useState<string[]>([]);
const [suggestions, setSuggestions] = useState<{ id: string; title: string }[]>([]);
const [showSuggestions, setShowSuggestions] = useState(false);
const timeoutRef = useRef<number | null>(null);
const filterRef = useRef<HTMLDivElement>(null);
const filteredPosts = posts;
const allTags = filteredPosts.map(post => post.tags).flat();
Expand Down Expand Up @@ -78,10 +82,39 @@ const Posts = () => {
}
};

useEffect(() => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}

timeoutRef.current = window.setTimeout(async () => {
if (searchQuery.length > 0) {
const results = await fetchSearchSuggestions(searchQuery);
setSuggestions(results);
setShowSuggestions(true);
} else {
setSuggestions([]);
setShowSuggestions(false);
}
}, 300);

return () => {
if (timeoutRef.current) {
clearTimeout(timeoutRef.current);
}
};
}, [searchQuery]);

const handleSearch = () => {
fetchPosts(page, 12, searchQuery, filterTags);
};

const handleSuggestionClick = (suggestion: { id: string; title: string }) => {
setSearchQuery(suggestion.title);
setShowSuggestions(false);
handleSearch();
};

if (loading) {
return <PostsPageSkeleton />;
}
Expand Down Expand Up @@ -180,6 +213,19 @@ const Posts = () => {
>
Search
</button>
{showSuggestions && (
<ul className="z-20 absolute top-full mt-2 bg-white rounded-md dark:bg-gray-800 border border-sky-400 px-3 py-2 ">
{suggestions.map((suggestion) => (
<li
key={suggestion.id}
onClick={() => handleSuggestionClick(suggestion)}
className="cursor-pointer p-2 hover:bg-gray-200 dark:hover:bg-gray-700 rounded-md"
>
{suggestion.title}
</li>
))}
</ul>
)}
</div>
</div>
{filteredPosts.length === 0 ? (
Expand Down
Loading