Add comments pagination, login and register pages

This commit is contained in:
Aaron William Po
2023-02-13 10:56:09 -05:00
parent 912008e68d
commit 80261a713b
16 changed files with 422 additions and 69 deletions

View File

@@ -4,6 +4,7 @@ import BeerRecommendations from '@/components/BeerById/BeerRecommendations';
import CommentCard from '@/components/BeerById/CommentCard';
import Layout from '@/components/ui/Layout';
import UserContext from '@/contexts/userContext';
import DBClient from '@/prisma/DBClient';
import getAllBeerComments from '@/services/BeerComment/getAllBeerComments';
import { BeerCommentQueryResultArrayT } from '@/services/BeerComment/schema/BeerCommentQueryResult';
import getBeerPostById from '@/services/BeerPost/getBeerPostById';
@@ -13,32 +14,35 @@ import { BeerPost } from '@prisma/client';
import { NextPage, GetServerSideProps } from 'next';
import Head from 'next/head';
import Image from 'next/image';
import Link from 'next/link';
import { useRouter } from 'next/router';
import { useState, useEffect, useContext } from 'react';
interface BeerPageProps {
beerPost: BeerPostQueryResult;
beerRecommendations: (BeerPost & {
brewery: {
id: string;
name: string;
};
beerImages: {
id: string;
alt: string;
url: string;
}[];
brewery: { id: string; name: string };
beerImages: { id: string; alt: string; url: string }[];
})[];
beerComments: BeerCommentQueryResultArrayT;
commentsPageCount: number;
}
const BeerByIdPage: NextPage<BeerPageProps> = ({
beerPost,
beerRecommendations,
beerComments,
commentsPageCount,
}) => {
const { user } = useContext(UserContext);
const [comments, setComments] = useState(beerComments);
const router = useRouter();
const commentsPageNum = router.query.comments_page
? parseInt(router.query.comments_page as string, 10)
: 1;
useEffect(() => {
setComments(beerComments);
}, [beerComments]);
@@ -49,7 +53,7 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
<title>{beerPost.name}</title>
<meta name="description" content={beerPost.description} />
</Head>
<main>
<div>
{beerPost.beerImages[0] && (
<Image
alt={beerPost.beerImages[0].alt}
@@ -61,10 +65,10 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
)}
<div className="my-12 flex w-full items-center justify-center ">
<div className="w-10/12 space-y-3">
<div className="w-11/12 space-y-3 lg:w-9/12">
<BeerInfoHeader beerPost={beerPost} />
<div className="mt-4 flex space-x-3">
<div className="w-[60%] space-y-3">
<div className="mt-4 flex flex-col space-y-3 sm:flex-row sm:space-y-0 sm:space-x-3">
<div className="w-full space-y-3 sm:w-[60%]">
<div className="card h-96 bg-base-300">
<div className="card-body h-full">
{user ? (
@@ -78,19 +82,52 @@ const BeerByIdPage: NextPage<BeerPageProps> = ({
)}
</div>
</div>
<div className="card h-[135rem] bg-base-300">
<div className="card bg-base-300 pb-6">
{comments.map((comment) => (
<CommentCard key={comment.id} comment={comment} />
))}
<div className="flex items-center justify-center">
<div className="btn-group grid w-6/12 grid-cols-2">
<Link
className={`btn-outline btn ${
commentsPageNum === 1
? 'btn-disabled pointer-events-none'
: 'pointer-events-auto'
}`}
href={{
pathname: `/beers/${beerPost.id}`,
query: { comments_page: commentsPageNum - 1 },
}}
scroll={false}
>
Next Comments
</Link>
<Link
className={`btn-outline btn ${
commentsPageNum === commentsPageCount
? 'btn-disabled pointer-events-none'
: 'pointer-events-auto'
}`}
href={{
pathname: `/beers/${beerPost.id}`,
query: { comments_page: commentsPageNum + 1 },
}}
scroll={false}
>
Previous Comments
</Link>
</div>
</div>
</div>
</div>
<div className="w-[40%]">
<div className="sm:w-[40%]">
<BeerRecommendations beerRecommendations={beerRecommendations} />
</div>
</div>
</div>
</div>
</main>
</div>
</Layout>
);
};
@@ -98,21 +135,30 @@ 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) {
return { notFound: true };
}
const { type, brewery, id } = beerPost;
const beerRecommendations = await getBeerRecommendations({ type, brewery, id });
const pageSize = 5;
const beerComments = await getAllBeerComments(
{ id: beerPost.id },
{ pageSize: 9, pageNum: 1 },
{ pageSize, pageNum: beerCommentPageNum },
);
const beerRecommendations = await getBeerRecommendations({ type, brewery, id });
const numberOfPosts = await DBClient.instance.beerComment.count({
where: { beerPostId: beerPost.id },
});
const pageCount = numberOfPosts ? Math.ceil(numberOfPosts / pageSize) : 0;
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)),
};
return { props };

View File

@@ -7,6 +7,7 @@ import Layout from '@/components/ui/Layout';
import Pagination from '@/components/BeerIndex/Pagination';
import BeerCard from '@/components/BeerIndex/BeerCard';
import BeerPostQueryResult from '@/services/BeerPost/schema/BeerPostQueryResult';
import Head from 'next/head';
interface BeerPageProps {
initialBeerPosts: BeerPostQueryResult[];
@@ -20,6 +21,10 @@ const BeerPage: NextPage<BeerPageProps> = ({ initialBeerPosts, pageCount }) => {
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">
<main className="my-10 flex w-10/12 flex-col space-y-4">
<div className="grid gap-5 md:grid-cols-2 xl:grid-cols-3">

View File

@@ -4,6 +4,10 @@ import { useRouter } from 'next/router';
import Layout from '@/components/ui/Layout';
import useUser from '@/hooks/useUser';
import LoginForm from '@/components/Login/LoginForm';
import Image from 'next/image';
import { FaUserCircle } from 'react-icons/fa';
import Head from 'next/head';
const LoginPage: NextPage = () => {
const { user } = useUser();
@@ -19,12 +23,29 @@ const LoginPage: NextPage = () => {
return (
<Layout>
<Head>
<title>Login</title>
<meta name="description" content="Login to your account" />
</Head>
<div className="flex h-full flex-row">
<div className="flex h-full w-[40%] flex-col items-center justify-center bg-base-100">
<h1>Login</h1>
<div className="flex h-full w-[60%] flex-col items-center justify-center bg-base-100">
<Image
src="https://picsum.photos/1040/1080"
alt="Login Image"
width={4920}
height={4080}
className="h-full w-full object-cover"
/>
</div>
<div className="flex h-full w-[60%] flex-col items-center justify-center bg-base-300">
<LoginForm />
<div className="flex h-full w-[40%] flex-col items-center space-y-5 bg-base-300">
<div className="mt-44 w-9/12">
<div className="flex flex-col items-center space-y-2">
<FaUserCircle className="text-3xl" />
<h1 className="text-4xl font-bold">Login</h1>
</div>
<LoginForm />
</div>
</div>
</div>
</Layout>

16
pages/logout/index.tsx Normal file
View File

@@ -0,0 +1,16 @@
import { NextPage } from 'next';
import { useRouter } from 'next/router';
import { useEffect } from 'react';
const LogoutPage: NextPage = () => {
const router = useRouter();
useEffect(() => {
document.cookie = 'token=; expires=Thu, 01 Jan 1970 00:00:01 GMT;';
router.reload();
router.push('/');
}, [router]);
return <div />;
};
export default LogoutPage;

165
pages/register/index.tsx Normal file
View File

@@ -0,0 +1,165 @@
import ErrorAlert from '@/components/ui/alerts/ErrorAlert';
import Button from '@/components/ui/forms/Button';
import FormError from '@/components/ui/forms/FormError';
import FormInfo from '@/components/ui/forms/FormInfo';
import FormLabel from '@/components/ui/forms/FormLabel';
import FormSegment from '@/components/ui/forms/FormSegment';
import FormTextInput from '@/components/ui/forms/FormTextInput';
import Layout from '@/components/ui/Layout';
import sendRegisterUserRequest from '@/requests/sendRegisterUserRequest';
import CreateUserValidationSchema from '@/services/User/schema/CreateUserValidationSchema';
import { zodResolver } from '@hookform/resolvers/zod';
import { NextPage } from 'next';
import { useState } from 'react';
import { useForm } from 'react-hook-form';
import { FaUserCircle } from 'react-icons/fa';
import { z } from 'zod';
interface RegisterUserProps {}
const RegisterUserPage: NextPage<RegisterUserProps> = () => {
const { reset, register, handleSubmit, formState } = useForm<
z.infer<typeof CreateUserValidationSchema>
>({
resolver: zodResolver(CreateUserValidationSchema),
});
const { errors } = formState;
const [serverResponseError, setServerResponseError] = useState('');
const onSubmit = async (data: z.infer<typeof CreateUserValidationSchema>) => {
try {
await sendRegisterUserRequest(data);
reset();
} catch (error) {
setServerResponseError(
error instanceof Error
? error.message
: 'Something went wrong. We could not register your account.',
);
}
};
return (
<Layout>
<div
className="flex h-full flex-col items-center justify-center space-y-6"
onSubmit={handleSubmit(onSubmit)}
>
<div className="flex flex-col items-center space-y-2">
<FaUserCircle className="text-3xl" />
<h1 className="text-4xl font-bold">Register</h1>
</div>
<form className="form-control w-7/12 space-y-5" noValidate>
{serverResponseError && (
<ErrorAlert error={serverResponseError} setError={setServerResponseError} />
)}
<div>
<div className="flex flex-row space-x-3">
<div className="w-[50%]">
<FormInfo>
<FormLabel htmlFor="firstName">First name</FormLabel>
<FormError>{errors.firstName?.message}</FormError>
</FormInfo>
<FormSegment>
<FormTextInput
id="firstName"
type="text"
formValidationSchema={register('firstName')}
error={!!errors.firstName}
placeholder="first name"
/>
</FormSegment>
</div>
<div className="w-[50%]">
<FormInfo>
<FormLabel htmlFor="lastName">Last name</FormLabel>
<FormError>{errors.lastName?.message}</FormError>
</FormInfo>
<FormSegment>
<FormTextInput
id="lastName"
type="text"
formValidationSchema={register('lastName')}
error={!!errors.lastName}
placeholder="last name"
/>
</FormSegment>
</div>
</div>
<FormInfo>
<FormLabel htmlFor="username">username</FormLabel>
<FormError>{errors.username?.message}</FormError>
</FormInfo>
<FormSegment>
<FormTextInput
id="username"
type="text"
formValidationSchema={register('username')}
error={!!errors.username}
placeholder="username"
/>
</FormSegment>
<FormInfo>
<FormLabel htmlFor="email">email</FormLabel>
<FormError>{errors.email?.message}</FormError>
</FormInfo>
<FormSegment>
<FormTextInput
id="email"
type="email"
formValidationSchema={register('email')}
error={!!errors.email}
placeholder="email"
/>
</FormSegment>
<FormInfo>
<FormLabel htmlFor="password">password</FormLabel>
<FormError>{errors.password?.message}</FormError>
</FormInfo>
<FormSegment>
<FormTextInput
id="password"
type="password"
formValidationSchema={register('password')}
error={!!errors.password}
placeholder="password"
/>
</FormSegment>
<FormInfo>
<FormLabel htmlFor="confirmPassword">confirm password</FormLabel>
<FormError>{errors.confirmPassword?.message}</FormError>
</FormInfo>
<FormSegment>
<FormTextInput
id="confirmPassword"
type="password"
formValidationSchema={register('confirmPassword')}
error={!!errors.confirmPassword}
placeholder="confirm password"
/>
</FormSegment>
<FormInfo>
<FormLabel htmlFor="dateOfBirth">Date of birth</FormLabel>
<FormError>{errors.dateOfBirth?.message}</FormError>
</FormInfo>
<FormSegment>
<FormTextInput
id="dateOfBirth"
type="date"
formValidationSchema={register('dateOfBirth')}
error={!!errors.dateOfBirth}
placeholder="date of birth"
/>
</FormSegment>
<Button type="submit">Register User</Button>
</div>
</form>
</div>
</Layout>
);
};
export default RegisterUserPage;