Restructure codebase to use src directory

This commit is contained in:
Aaron William Po
2023-04-11 23:32:06 -04:00
parent 90f2cc2c0c
commit 08422fe24e
141 changed files with 6 additions and 4 deletions

View File

@@ -0,0 +1,66 @@
import { NextPage } from 'next';
import Head from 'next/head';
import React from 'react';
import Layout from '@/components/ui/Layout';
import withPageAuthRequired from '@/getServerSideProps/withPageAuthRequired';
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
import EditBeerPostForm from '@/components/EditBeerPostForm';
import FormPageLayout from '@/components/ui/forms/FormPageLayout';
import { BiBeer } from 'react-icons/bi';
import { z } from 'zod';
interface EditPageProps {
beerPost: z.infer<typeof beerPostQueryResult>;
}
const EditPage: NextPage<EditPageProps> = ({ beerPost }) => {
const pageTitle = `Edit "${beerPost.name}"`;
return (
<Layout>
<Head>
<title>{pageTitle}</title>
<meta name="description" content={pageTitle} />
</Head>
<FormPageLayout
headingText={pageTitle}
headingIcon={BiBeer}
backLink={`/beers/${beerPost.id}`}
backLinkText={`Back to "${beerPost.name}"`}
>
<EditBeerPostForm
previousValues={{
name: beerPost.name,
abv: beerPost.abv,
ibu: beerPost.ibu,
description: beerPost.description,
id: beerPost.id,
}}
/>
</FormPageLayout>
</Layout>
);
};
export default EditPage;
export const getServerSideProps = withPageAuthRequired<EditPageProps>(
async (context, session) => {
const beerPostId = context.params?.id as string;
const beerPost = await getBeerPostById(beerPostId);
const { id: userId } = session;
if (!beerPost) {
return { notFound: true };
}
const isBeerPostOwner = beerPost.postedBy.id === userId;
return isBeerPostOwner
? { props: { beerPost: JSON.parse(JSON.stringify(beerPost)) } }
: { redirect: { destination: `/beers/${beerPostId}`, permanent: false } };
},
);

View File

@@ -0,0 +1,122 @@
import { NextPage, GetServerSideProps } from 'next';
import Head from 'next/head';
import Image from 'next/image';
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 getBeerPostById from '@/services/BeerPost/getBeerPostById';
import getBeerRecommendations from '@/services/BeerPost/getBeerRecommendations';
import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
import { BeerPost } from '@prisma/client';
import { z } from 'zod';
import 'react-responsive-carousel/lib/styles/carousel.min.css'; // requires a loader
import { Carousel } from 'react-responsive-carousel';
import useMediaQuery from '@/hooks/useMediaQuery';
import { Tab } from '@headlessui/react';
interface BeerPageProps {
beerPost: z.infer<typeof beerPostQueryResult>;
beerRecommendations: (BeerPost & {
brewery: { id: string; name: string };
beerImages: { id: string; alt: string; url: string }[];
})[];
}
const BeerByIdPage: NextPage<BeerPageProps> = ({ beerPost, beerRecommendations }) => {
const isMd = useMediaQuery('(min-width: 768px)');
return (
<>
<Head>
<title>{beerPost.name}</title>
<meta name="description" content={beerPost.description} />
</Head>
<Layout>
<div>
<Carousel
className="w-full"
useKeyboardArrows
autoPlay
interval={10000}
infiniteLoop
showThumbs={false}
>
{beerPost.beerImages.map((image, index) => (
<div key={image.id} id={`image-${index}}`} className="w-full">
<Image
alt={image.alt}
src={image.path}
height={1080}
width={1920}
className="h-[42rem] w-full object-cover"
/>
</div>
))}
</Carousel>
<div className="mb-12 mt-10 flex w-full items-center justify-center ">
<div className="w-11/12 space-y-3 xl:w-9/12">
<BeerInfoHeader beerPost={beerPost} />
{isMd ? (
<div className="mt-4 flex flex-row space-x-3 space-y-0">
<div className="w-[60%]">
<BeerPostCommentsSection beerPost={beerPost} />
</div>
<div className="w-[40%]">
<BeerRecommendations beerRecommendations={beerRecommendations} />
</div>
</div>
) : (
<Tab.Group>
<Tab.List className="card flex flex-row bg-base-300">
<Tab className="ui-selected:bg-gray w-1/2 p-3 uppercase">
Comments
</Tab>
<Tab className="ui-selected:bg-gray w-1/2 p-3 uppercase">
Recommendations
</Tab>
</Tab.List>
<Tab.Panels className="mt-2">
<Tab.Panel>
<BeerPostCommentsSection beerPost={beerPost} />
</Tab.Panel>
<Tab.Panel>
<BeerRecommendations beerRecommendations={beerRecommendations} />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
)}
</div>
</div>
</div>
</Layout>
</>
);
};
export const getServerSideProps: GetServerSideProps<BeerPageProps> = async (context) => {
const beerPost = await getBeerPostById(context.params!.id! as string);
if (!beerPost) {
return { notFound: true };
}
const { type, brewery, id } = beerPost;
const beerRecommendations = await getBeerRecommendations({ type, brewery, id });
const props = {
beerPost: JSON.parse(JSON.stringify(beerPost)),
beerRecommendations: JSON.parse(JSON.stringify(beerRecommendations)),
};
return { props };
};
export default BeerByIdPage;

View File

@@ -0,0 +1,45 @@
import CreateBeerPostForm from '@/components/CreateBeerPostForm';
import FormPageLayout from '@/components/ui/forms/FormPageLayout';
import Layout from '@/components/ui/Layout';
import withPageAuthRequired from '@/getServerSideProps/withPageAuthRequired';
import DBClient from '@/prisma/DBClient';
import getAllBreweryPosts from '@/services/BreweryPost/getAllBreweryPosts';
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
import { BeerType } from '@prisma/client';
import { NextPage } from 'next';
import { BiBeer } from 'react-icons/bi';
import { z } from 'zod';
interface CreateBeerPageProps {
breweries: z.infer<typeof BreweryPostQueryResult>[];
types: BeerType[];
}
const Create: NextPage<CreateBeerPageProps> = ({ breweries, types }) => {
return (
<Layout>
<FormPageLayout
headingText="Create a new beer"
headingIcon={BiBeer}
backLink="/beers"
backLinkText="Back to beers"
>
<CreateBeerPostForm breweries={breweries} types={types} />
</FormPageLayout>
</Layout>
);
};
export const getServerSideProps = withPageAuthRequired<CreateBeerPageProps>(async () => {
const breweryPosts = await getAllBreweryPosts();
const beerTypes = await DBClient.instance.beerType.findMany();
return {
props: {
breweries: JSON.parse(JSON.stringify(breweryPosts)),
types: JSON.parse(JSON.stringify(beerTypes)),
},
};
});
export default Create;

78
src/pages/beers/index.tsx Normal file
View File

@@ -0,0 +1,78 @@
import { GetServerSideProps, NextPage } from 'next';
import getAllBeerPosts from '@/services/BeerPost/getAllBeerPosts';
import { useRouter } from 'next/router';
import DBClient from '@/prisma/DBClient';
import Layout from '@/components/ui/Layout';
import BeerIndexPaginationBar from '@/components/BeerIndex/BeerIndexPaginationBar';
import BeerCard from '@/components/BeerIndex/BeerCard';
import beerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
import Head from 'next/head';
import { z } from 'zod';
import Link from 'next/link';
import UserContext from '@/contexts/userContext';
import { useContext } from 'react';
interface BeerPageProps {
initialBeerPosts: z.infer<typeof beerPostQueryResult>[];
pageCount: number;
}
const BeerPage: NextPage<BeerPageProps> = ({ initialBeerPosts, pageCount }) => {
const router = useRouter();
const { query } = router;
const { user } = useContext(UserContext);
const pageNum = parseInt(query.page_num as string, 10) || 1;
return (
<Layout>
<Head>
<title>Beer</title>
<meta name="description" content="Beer posts" />
</Head>
<div className="flex items-center justify-center bg-base-100">
<div className="my-10 flex w-10/12 flex-col space-y-4">
<header className="my-10 flex justify-between">
<div className="space-y-2">
<h1 className="text-6xl font-bold">The Biergarten Index</h1>
<h2 className="text-2xl font-bold">
Page {pageNum} of {pageCount}
</h2>
</div>
{!!user && (
<div>
<Link href="/beers/create" className="btn-primary btn">
Create a new beer post
</Link>
</div>
)}
</header>
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-3">
{initialBeerPosts.map((post) => {
return <BeerCard post={post} key={post.id} />;
})}
</div>
<div className="flex justify-center">
<BeerIndexPaginationBar pageNum={pageNum} pageCount={pageCount} />
</div>
</div>
</div>
</Layout>
);
};
export const getServerSideProps: GetServerSideProps<BeerPageProps> = async (context) => {
const { query } = context;
const pageNumber = parseInt(query.page_num as string, 10) || 1;
const pageSize = 12;
const numberOfPosts = await DBClient.instance.beerPost.count();
const pageCount = numberOfPosts ? Math.ceil(numberOfPosts / pageSize) : 0;
const beerPosts = await getAllBeerPosts(pageNumber, pageSize);
return {
props: { initialBeerPosts: JSON.parse(JSON.stringify(beerPosts)), pageCount },
};
};
export default BeerPage;

View 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) || '';
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(querySearch);
}, 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;