mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-02-16 10:42:08 +00:00
Add beer search feature
This commit adds the necessary components and hooks to implement a beer search feature on the website. It includes the following changes: - Add a new BeerSearch API route that returns a list of beers matching a search query. - Implement a new hook useBeerPostSearch that utilizes SWR to fetch data from the API and parse it using a schema. - Add a new page SearchPage that displays a search input field and a list of beer search results. - Use lodash's debounce function to avoid making too many requests while the user is typing in the search input field.
This commit is contained in:
@@ -2,14 +2,11 @@ import { NextPage, GetServerSideProps } from 'next';
|
||||
import Head from 'next/head';
|
||||
import Image from 'next/image';
|
||||
|
||||
import { useState, useEffect } from 'react';
|
||||
|
||||
import BeerInfoHeader from '@/components/BeerById/BeerInfoHeader';
|
||||
import BeerPostCommentsSection from '@/components/BeerById/BeerPostCommentsSection';
|
||||
import BeerRecommendations from '@/components/BeerById/BeerRecommendations';
|
||||
import Layout from '@/components/ui/Layout';
|
||||
|
||||
import DBClient from '@/prisma/DBClient';
|
||||
import getAllBeerComments from '@/services/BeerComment/getAllBeerComments';
|
||||
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
|
||||
import getBeerRecommendations from '@/services/BeerPost/getBeerRecommendations';
|
||||
@@ -17,6 +14,8 @@ import getBeerRecommendations from '@/services/BeerPost/getBeerRecommendations';
|
||||
import { BeerCommentQueryResultArrayT } from '@/services/BeerComment/schema/BeerCommentQueryResult';
|
||||
import { BeerPostQueryResult } from '@/services/BeerPost/schema/BeerPostQueryResult';
|
||||
import { BeerPost } from '@prisma/client';
|
||||
import getBeerPostLikeCount from '@/services/BeerPostLike/getBeerPostLikeCount';
|
||||
import getBeerCommentCount from '@/services/BeerComment/getBeerCommentCount';
|
||||
|
||||
interface BeerPageProps {
|
||||
beerPost: BeerPostQueryResult;
|
||||
@@ -36,12 +35,6 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
|
||||
commentsPageCount,
|
||||
likeCount,
|
||||
}) => {
|
||||
const [comments, setComments] = useState(beerComments);
|
||||
|
||||
useEffect(() => {
|
||||
setComments(beerComments);
|
||||
}, [beerComments]);
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Head>
|
||||
@@ -65,8 +58,7 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
|
||||
<div className="mt-4 flex flex-col space-y-3 md:flex-row md:space-y-0 md:space-x-3">
|
||||
<BeerPostCommentsSection
|
||||
beerPost={beerPost}
|
||||
comments={comments}
|
||||
setComments={setComments}
|
||||
comments={beerComments}
|
||||
commentsPageCount={commentsPageCount}
|
||||
/>
|
||||
<div className="md:w-[40%]">
|
||||
@@ -82,7 +74,6 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
|
||||
|
||||
export const getServerSideProps: GetServerSideProps<BeerPageProps> = async (context) => {
|
||||
const beerPost = await getBeerPostById(context.params!.id! as string);
|
||||
|
||||
const beerCommentPageNum = parseInt(context.query.comments_page as string, 10) || 1;
|
||||
|
||||
if (!beerPost) {
|
||||
@@ -97,19 +88,17 @@ export const getServerSideProps: GetServerSideProps<BeerPageProps> = async (cont
|
||||
{ id: beerPost.id },
|
||||
{ pageSize, pageNum: beerCommentPageNum },
|
||||
);
|
||||
const numberOfPosts = await DBClient.instance.beerComment.count({
|
||||
where: { beerPostId: beerPost.id },
|
||||
});
|
||||
const pageCount = numberOfPosts ? Math.ceil(numberOfPosts / pageSize) : 0;
|
||||
const likeCount = await DBClient.instance.beerPostLike.count({
|
||||
where: { beerPostId: beerPost.id },
|
||||
});
|
||||
|
||||
const commentCount = await getBeerCommentCount(beerPost.id);
|
||||
|
||||
const commentPageCount = commentCount ? Math.ceil(commentCount / pageSize) : 0;
|
||||
const likeCount = await getBeerPostLikeCount(beerPost.id);
|
||||
|
||||
const props = {
|
||||
beerPost: JSON.parse(JSON.stringify(beerPost)),
|
||||
beerRecommendations: JSON.parse(JSON.stringify(beerRecommendations)),
|
||||
beerComments: JSON.parse(JSON.stringify(beerComments)),
|
||||
commentsPageCount: JSON.parse(JSON.stringify(pageCount)),
|
||||
commentsPageCount: JSON.parse(JSON.stringify(commentPageCount)),
|
||||
likeCount: JSON.parse(JSON.stringify(likeCount)),
|
||||
};
|
||||
|
||||
|
||||
79
pages/beers/search.tsx
Normal file
79
pages/beers/search.tsx
Normal file
@@ -0,0 +1,79 @@
|
||||
import Layout from '@/components/ui/Layout';
|
||||
import { NextPage } from 'next';
|
||||
|
||||
import { useRouter } from 'next/router';
|
||||
import BeerCard from '@/components/BeerIndex/BeerCard';
|
||||
import { ChangeEvent, useEffect, useState } from 'react';
|
||||
import Spinner from '@/components/ui/Spinner';
|
||||
|
||||
import debounce from 'lodash/debounce';
|
||||
import useBeerPostSearch from '@/hooks/useBeerPostSearch';
|
||||
import FormLabel from '@/components/ui/forms/FormLabel';
|
||||
|
||||
const DEBOUNCE_DELAY = 300;
|
||||
|
||||
const SearchPage: NextPage = () => {
|
||||
const router = useRouter();
|
||||
const querySearch = router.query.search as string | undefined;
|
||||
const [searchValue, setSearchValue] = useState(querySearch || '');
|
||||
const { searchResults, isLoading, searchError } = useBeerPostSearch(searchValue);
|
||||
|
||||
const debounceSearch = debounce((value: string) => {
|
||||
router.push({
|
||||
pathname: '/beers/search',
|
||||
query: { search: value },
|
||||
});
|
||||
}, DEBOUNCE_DELAY);
|
||||
|
||||
const onChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
const { value } = event.target;
|
||||
setSearchValue(value);
|
||||
debounceSearch(value);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
debounce(() => {
|
||||
if (!querySearch || searchValue) {
|
||||
return;
|
||||
}
|
||||
setSearchValue(searchValue);
|
||||
}, DEBOUNCE_DELAY)();
|
||||
}, [querySearch, searchValue]);
|
||||
|
||||
const showSearchResults = !isLoading && searchResults && !searchError;
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<div className="flex h-full w-full flex-col items-center justify-center">
|
||||
<div className="h-full w-full space-y-20">
|
||||
<div className="flex h-[50%] w-full items-center justify-center bg-base-200">
|
||||
<div className="w-8/12">
|
||||
<FormLabel htmlFor="search">What are you looking for?</FormLabel>
|
||||
<input
|
||||
type="text"
|
||||
id="search"
|
||||
className="input-bordered input w-full rounded-lg"
|
||||
onChange={onChange}
|
||||
value={searchValue}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col items-center justify-center">
|
||||
{!showSearchResults ? (
|
||||
<Spinner size="lg" />
|
||||
) : (
|
||||
<div className="grid w-8/12 gap-4 md:grid-cols-2 lg:grid-cols-3">
|
||||
{searchResults.map((result) => {
|
||||
return <BeerCard key={result.id} post={result} />;
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export default SearchPage;
|
||||
Reference in New Issue
Block a user