diff --git a/src/components/BeerStyleById/BeerStyleBeerSection.tsx b/src/components/BeerStyleById/BeerStyleBeerSection.tsx new file mode 100644 index 0000000..c0c4d45 --- /dev/null +++ b/src/components/BeerStyleById/BeerStyleBeerSection.tsx @@ -0,0 +1,100 @@ +import Link from 'next/link'; +import { FC, MutableRefObject, useRef } from 'react'; +import { useInView } from 'react-intersection-observer'; +import { z } from 'zod'; + +import BeerStyleQueryResult from '@/services/BeerStyles/schema/BeerStyleQueryResult'; +import useBeerPostsByBeerStyle from '@/hooks/data-fetching/beer-posts/useBeerPostsByBeerStyles'; +import BeerRecommendationLoadingComponent from '../BeerById/BeerRecommendationLoadingComponent'; + +interface BeerStyleBeerSectionProps { + beerStyle: z.infer; +} + +const BeerStyleBeerSection: FC = ({ beerStyle }) => { + const PAGE_SIZE = 2; + + const { beerPosts, isAtEnd, isLoadingMore, setSize, size } = useBeerPostsByBeerStyle({ + beerStyleId: beerStyle.id, + pageSize: PAGE_SIZE, + }); + const { ref: penultimateBeerPostRef } = useInView({ + /** + * When the last beer post comes into view, call setSize from useBeerPostsByBeerStyle + * to load more beer posts. + */ + onChange: (visible) => { + if (!visible || isAtEnd) return; + setSize(size + 1); + }, + }); + + const beerRecommendationsRef: MutableRefObject = useRef(null); + + return ( +
+
+ <> +
+
+

Brews

+
+
+ + {!!beerPosts.length && ( +
+ {beerPosts.map((beerPost, index) => { + const isPenultimateBeerPost = index === beerPosts.length - 2; + + /** + * Attach a ref to the second last beer post in the list. When it comes + * into view, the component will call setSize to load more beer posts. + */ + + return ( +
+
+ + {beerPost.name} + +
+
+ + + {beerPost.brewery.name} + + +
+ +
+ {beerPost.abv}% ABV + {beerPost.ibu} IBU +
+
+ ); + })} +
+ )} + + { + /** + * If there are more beer posts to load, show a loading component with a + * skeleton loader and a loading spinner. + */ + !!isLoadingMore && !isAtEnd && ( + + ) + } + +
+
+ ); +}; + +export default BeerStyleBeerSection; diff --git a/src/pages/api/beers/styles/[id]/beers/index.ts b/src/pages/api/beers/styles/[id]/beers/index.ts new file mode 100644 index 0000000..ca66936 --- /dev/null +++ b/src/pages/api/beers/styles/[id]/beers/index.ts @@ -0,0 +1,71 @@ +import NextConnectOptions from '@/config/nextConnect/NextConnectOptions'; +import validateRequest from '@/config/nextConnect/middleware/validateRequest'; +import DBClient from '@/prisma/DBClient'; +import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult'; +import APIResponseValidationSchema from '@/validation/APIResponseValidationSchema'; +import { NextApiRequest, NextApiResponse } from 'next'; +import { createRouter } from 'next-connect'; +import { z } from 'zod'; + +interface GetAllBeersByBeerStyleRequest extends NextApiRequest { + query: { page_size: string; page_num: string; id: string }; +} + +const getAllBeersByBeerStyle = async ( + req: GetAllBeersByBeerStyleRequest, + res: NextApiResponse>, +) => { + // eslint-disable-next-line @typescript-eslint/naming-convention + const { page_size, page_num, id } = req.query; + + const beers: z.infer[] = + await DBClient.instance.beerPost.findMany({ + where: { styleId: id }, + take: parseInt(page_size, 10), + skip: parseInt(page_num, 10) * parseInt(page_size, 10), + select: { + id: true, + name: true, + ibu: true, + abv: true, + createdAt: true, + updatedAt: true, + description: true, + postedBy: { select: { username: true, id: true } }, + brewery: { select: { name: true, id: true } }, + style: { select: { name: true, id: true, description: true } }, + beerImages: { select: { alt: true, path: true, caption: true, id: true } }, + }, + }); + + const pageCount = await DBClient.instance.beerPost.count({ where: { styleId: id } }); + + res.setHeader('X-Total-Count', pageCount); + + res.status(200).json({ + message: 'Beers fetched successfully', + statusCode: 200, + payload: beers, + success: true, + }); +}; + +const router = createRouter< + GetAllBeersByBeerStyleRequest, + NextApiResponse> +>(); + +router.get( + validateRequest({ + querySchema: z.object({ + page_size: z.string().min(1), + page_num: z.string().min(1), + id: z.string().min(1), + }), + }), + getAllBeersByBeerStyle, +); + +const handler = router.handler(NextConnectOptions); + +export default handler;