diff --git a/.prettierrc b/.prettierrc
index 42a08a0..ad4d5eb 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -2,5 +2,6 @@
"semi": true,
"trailingComma": "all",
"singleQuote": true,
- "printWidth": 90
+ "printWidth": 90,
+ "plugins": ["prettier-plugin-jsdoc"]
}
diff --git a/components/BeerById/BeerInfoHeader.tsx b/components/BeerById/BeerInfoHeader.tsx
new file mode 100644
index 0000000..13603c7
--- /dev/null
+++ b/components/BeerById/BeerInfoHeader.tsx
@@ -0,0 +1,78 @@
+import BeerPostQueryResult from '@/services/BeerPost/types/BeerPostQueryResult';
+import Link from 'next/link';
+import formatDistanceStrict from 'date-fns/formatDistanceStrict';
+import { useState } from 'react';
+import { FaRegThumbsUp, FaThumbsUp } from 'react-icons/fa';
+
+const BeerInfoHeader: React.FC<{ beerPost: BeerPostQueryResult }> = ({ beerPost }) => {
+ const createdAtDate = new Date(beerPost.createdAt);
+ const timeDistance = formatDistanceStrict(createdAtDate, Date.now());
+
+ const [isLiked, setIsLiked] = useState(false);
+
+ return (
+
+
{beerPost.name}
+
+ by{' '}
+
+ {beerPost.brewery.name}
+
+
+
+
+ posted by{' '}
+
+ {beerPost.postedBy.username}
+
+ {` ${timeDistance}`} ago
+
+
+
{beerPost.description}
+
+
+
+
+ {beerPost.type.name}
+
+
+
+ {beerPost.abv}% ABV
+ {beerPost.ibu} IBU
+
+
+
+
+
+
+
+ );
+};
+
+export default BeerInfoHeader;
diff --git a/components/BeerById/CommentCard.tsx b/components/BeerById/CommentCard.tsx
new file mode 100644
index 0000000..d4bab83
--- /dev/null
+++ b/components/BeerById/CommentCard.tsx
@@ -0,0 +1,21 @@
+import BeerPostQueryResult from '@/services/BeerPost/types/BeerPostQueryResult';
+import formatDistanceStrict from 'date-fns/formatDistanceStrict';
+
+const CommentCard: React.FC<{
+ comment: BeerPostQueryResult['beerComments'][number];
+}> = ({ comment }) => {
+ return (
+
+
+
{comment.postedBy.username}
+
{`posted ${formatDistanceStrict(
+ new Date(comment.createdAt),
+ new Date(),
+ )} ago`}
+
{comment.content}
+
+
+ );
+};
+
+export default CommentCard;
diff --git a/components/BeerForm.tsx b/components/BeerForm.tsx
index a720d5e..0700b52 100644
--- a/components/BeerForm.tsx
+++ b/components/BeerForm.tsx
@@ -1,12 +1,14 @@
-import { NewBeerInfo } from '@/pages/api/beers/create';
import BreweryPostQueryResult from '@/services/BreweryPost/types/BreweryPostQueryResult';
import { BeerType } from '@prisma/client';
import { FunctionComponent } from 'react';
import { SubmitHandler, useForm } from 'react-hook-form';
import { z } from 'zod';
-
-import Button from './ui/Button';
+import { zodResolver } from '@hookform/resolvers/zod';
+import BeerPostValidationSchema from '@/validation/BeerPostValidationSchema';
+import Router from 'next/router';
+import sendCreateBeerPostRequest from '@/requests/sendCreateBeerPostRequest';
+import Button from './ui/forms/Button';
import FormError from './ui/forms/FormError';
import FormInfo from './ui/forms/FormInfo';
import FormLabel from './ui/forms/FormLabel';
@@ -15,34 +17,18 @@ import FormSelect from './ui/forms/FormSelect';
import FormTextArea from './ui/forms/FormTextArea';
import FormTextInput from './ui/forms/FormTextInput';
-type IFormInput = z.infer;
+type BeerPostT = z.infer;
interface BeerFormProps {
- type: 'edit' | 'create';
+ formType: 'edit' | 'create';
// eslint-disable-next-line react/require-default-props
- defaultValues?: IFormInput;
+ defaultValues?: BeerPostT;
breweries?: BreweryPostQueryResult[];
types?: BeerType[];
}
-const sendCreateBeerPostRequest = async (data: IFormInput) => {
- // const body = JSON.stringify(data);
-
- const response = await fetch('/api/beers/create', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify(data),
- });
-
- const json = await response.json();
-
- console.log(json);
-};
-
const BeerForm: FunctionComponent = ({
- type,
+ formType,
defaultValues,
breweries = [],
types = [],
@@ -51,7 +37,8 @@ const BeerForm: FunctionComponent = ({
register,
handleSubmit,
formState: { errors },
- } = useForm({
+ } = useForm({
+ resolver: zodResolver(BeerPostValidationSchema),
defaultValues: {
name: defaultValues?.name,
description: defaultValues?.description,
@@ -60,47 +47,19 @@ const BeerForm: FunctionComponent = ({
},
});
- // const navigate = useNavigate();
-
- const nameValidationSchema = register('name', {
- required: 'Beer name is required.',
- });
- const breweryValidationSchema = register('breweryId', {
- required: 'Brewery name is required.',
- });
- const abvValidationSchema = register('abv', {
- required: 'ABV is required.',
- valueAsNumber: true,
- max: { value: 50, message: 'ABV must be less than 50%.' },
- min: {
- value: 0.1,
- message: 'ABV must be greater than 0.1%',
- },
- validate: (abv) => !Number.isNaN(abv) || 'ABV is invalid.',
- });
- const ibuValidationSchema = register('ibu', {
- required: 'IBU is required.',
- min: {
- value: 2,
- message: 'IBU must be greater than 2.',
- },
- valueAsNumber: true,
- validate: (ibu) => !Number.isNaN(ibu) || 'IBU is invalid.',
- });
-
- const descriptionValidationSchema = register('description', {
- required: 'Description is required.',
- });
-
- const typeIdValidationSchema = register('typeId', {
- required: 'Type is required.',
- });
-
- const onSubmit: SubmitHandler = async (data) => {
- switch (type) {
- case 'create':
- await sendCreateBeerPostRequest(data);
- break;
+ const onSubmit: SubmitHandler = async (data) => {
+ switch (formType) {
+ case 'create': {
+ try {
+ const response = await sendCreateBeerPostRequest(data);
+ Router.push(`/beers/${response.id}`);
+ break;
+ } catch (e) {
+ // eslint-disable-next-line no-console
+ console.error(e);
+ break;
+ }
+ }
case 'edit':
break;
default:
@@ -117,13 +76,13 @@ const BeerForm: FunctionComponent = ({
- {type === 'create' && breweries.length && (
+ {formType === 'create' && breweries.length && (
<>
Brewery
@@ -131,7 +90,7 @@ const BeerForm: FunctionComponent = ({
({
@@ -153,7 +112,7 @@ const BeerForm: FunctionComponent = ({
= ({
= ({
@@ -194,7 +153,7 @@ const BeerForm: FunctionComponent = ({
({
@@ -207,7 +166,7 @@ const BeerForm: FunctionComponent = ({
diff --git a/components/BeerIndex/BeerCard.tsx b/components/BeerIndex/BeerCard.tsx
new file mode 100644
index 0000000..74c073e
--- /dev/null
+++ b/components/BeerIndex/BeerCard.tsx
@@ -0,0 +1,27 @@
+import BeerPostQueryResult from '@/services/BeerPost/types/BeerPostQueryResult';
+import Link from 'next/link';
+import { FC } from 'react';
+import Image from 'next/image';
+
+const BeerCard: FC<{ post: BeerPostQueryResult }> = ({ post }) => {
+ return (
+
+
+ {post.beerImages.length > 0 && (
+
+ )}
+
+
+
+
+
+ {post.name}
+
+ {post.brewery.name}
+
+
+
+ );
+};
+
+export default BeerCard;
diff --git a/components/BeerIndex/Pagination.tsx b/components/BeerIndex/Pagination.tsx
new file mode 100644
index 0000000..dc18fb8
--- /dev/null
+++ b/components/BeerIndex/Pagination.tsx
@@ -0,0 +1,37 @@
+import { useRouter } from 'next/router';
+import { FC } from 'react';
+
+interface PaginationProps {
+ pageNum: number;
+ pageCount: number;
+}
+
+const Pagination: FC = ({ pageCount, pageNum }) => {
+ const router = useRouter();
+
+ return (
+
+
+
+
+
+ );
+};
+
+export default Pagination;
diff --git a/components/Layout.tsx b/components/ui/Layout.tsx
similarity index 100%
rename from components/Layout.tsx
rename to components/ui/Layout.tsx
diff --git a/components/Navbar.tsx b/components/ui/Navbar.tsx
similarity index 100%
rename from components/Navbar.tsx
rename to components/ui/Navbar.tsx
diff --git a/components/ui/Button.tsx b/components/ui/forms/Button.tsx
similarity index 80%
rename from components/ui/Button.tsx
rename to components/ui/forms/Button.tsx
index 0f1db4e..6b32a73 100644
--- a/components/ui/Button.tsx
+++ b/components/ui/forms/Button.tsx
@@ -3,18 +3,13 @@ import { FunctionComponent } from 'react';
interface FormButtonProps {
children: string;
type: 'button' | 'submit' | 'reset';
- className?: string;
}
-const Button: FunctionComponent = ({ children, type, className }) => (
+const Button: FunctionComponent = ({ children, type }) => (
// eslint-disable-next-line react/button-has-type
);
-Button.defaultProps = {
- className: '',
-};
-
export default Button;
diff --git a/components/ui/forms/FormError.tsx b/components/ui/forms/FormError.tsx
index 5d20030..8992c60 100644
--- a/components/ui/forms/FormError.tsx
+++ b/components/ui/forms/FormError.tsx
@@ -3,8 +3,6 @@ import { FunctionComponent } from 'react';
// import { faTriangleExclamation } from '@fortawesome/free-solid-svg-icons';
/**
- * Component for a styled form error message.
- *
* @example
* Something went wrong!;
*/
diff --git a/components/ui/forms/FormInfo.tsx b/components/ui/forms/FormInfo.tsx
index 5baeaa3..388dc2d 100644
--- a/components/ui/forms/FormInfo.tsx
+++ b/components/ui/forms/FormInfo.tsx
@@ -1,10 +1,16 @@
import { FunctionComponent, ReactNode } from 'react';
-/** A container for both the form error and form label. */
interface FormInfoProps {
- children: Array | ReactNode;
+ children: [ReactNode, ReactNode];
}
+/**
+ * @example
+ *
+ * Name
+ * {errors.name?.message}
+ * ;
+ */
const FormInfo: FunctionComponent = ({ children }) => (
{children}
);
diff --git a/components/ui/forms/FormLabel.tsx b/components/ui/forms/FormLabel.tsx
index 977a38e..ea72095 100644
--- a/components/ui/forms/FormLabel.tsx
+++ b/components/ui/forms/FormLabel.tsx
@@ -5,9 +5,13 @@ interface FormLabelProps {
children: string;
}
+/**
+ * @example
+ * Name;
+ */
const FormLabel: FunctionComponent = ({ htmlFor, children }) => (