diff --git a/.eslintrc.json b/.eslintrc.json index bffb357a7..4d765f281 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,3 +1,3 @@ { - "extends": "next/core-web-vitals" + "extends": ["next/core-web-vitals", "prettier"] } diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..5fda2c589 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,30 @@ +{ + "arrowParens": "always", + "endOfLine": "lf", + "printWidth": 80, + "quoteProps": "as-needed", + "semi": true, + "singleQuote": true, + "tabWidth": 2, + "useTabs": false, + "trailingComma": "es5", + + "importOrder": [ + "", + + "^@/apis/(.*)$", + "^@/hooks/(.*)$", + "^@/components/(.*)$", + "^@/utils/(.*)$", + "^@/types/(.*)$", + + "^[./]" + ], + "importOrderSeparation": true, + "importOrderSortSpecifiers": true, + + "plugins": [ + "@trivago/prettier-plugin-sort-imports", + "prettier-plugin-tailwindcss" + ] +} diff --git a/apis/board.ts b/apis/board.ts new file mode 100644 index 000000000..7f6a35e5b --- /dev/null +++ b/apis/board.ts @@ -0,0 +1,16 @@ +import axios from './instance'; + +export async function getBoardById(id: number) { + const res = await axios.get(`/articles/${id}`); + return res.data; +} + +export async function getBoardComments(boardId: number) { + const res = await axios.get(`/articles/${boardId}/comments?limit=100`); + return res.data.list; +} + +export async function postBoardComment(boardId: number, content: string) { + const res = await axios.post(`/articles/${boardId}/comments`, { content }); + return res.data; +} diff --git a/apis/instance.ts b/apis/instance.ts new file mode 100644 index 000000000..7521f1e28 --- /dev/null +++ b/apis/instance.ts @@ -0,0 +1,7 @@ +import axios from 'axios'; + +const instance = axios.create({ + baseURL: 'https://panda-market-api.vercel.app', +}); + +export default instance; diff --git a/apis/products.ts b/apis/products.ts new file mode 100644 index 000000000..97c8f8da8 --- /dev/null +++ b/apis/products.ts @@ -0,0 +1,16 @@ +import axios from './instance'; + +export async function getProductById(id: number) { + const res = await axios.get(`/products/${id}`); + return res.data; +} + +export async function getProductComments(productId: number) { + const res = await axios.get(`/products/${productId}/comments?limit=100`); + return res.data.list; +} + +export async function postProductComment(productId: number, content: string) { + const res = await axios.post(`/products/${productId}/comments`, { content }); + return res.data; +} diff --git a/components/BackButton.tsx b/components/BackButton.tsx new file mode 100644 index 000000000..d893ee99c --- /dev/null +++ b/components/BackButton.tsx @@ -0,0 +1,33 @@ +import { Url } from 'next/dist/shared/lib/router/router'; +import Image from 'next/image'; +import { useRouter } from 'next/router'; + +import Button from './Button'; + +type BackButtonProp = { + to: Url; +}; + +export default function BackButton({ to }: BackButtonProp) { + const { push } = useRouter(); + + const handleButtonClick = () => { + push(to); + }; + + return ( + + ); +} diff --git a/components/Button.tsx b/components/Button.tsx new file mode 100644 index 000000000..7c401270b --- /dev/null +++ b/components/Button.tsx @@ -0,0 +1,28 @@ +import { MouseEventHandler, ReactNode } from 'react'; + +type Props = { + style?: { + shape?: 'square' | 'rounded'; + size?: 'small' | 'large'; + }; + disabled?: boolean; + onClick: MouseEventHandler; + children: ReactNode; +}; + +export default function Button({ + style = { shape: 'square', size: 'small' }, + disabled = false, + onClick, + children, +}: Props) { + return ( + + ); +} diff --git a/components/Comment.tsx b/components/Comment.tsx new file mode 100644 index 000000000..446064f1e --- /dev/null +++ b/components/Comment.tsx @@ -0,0 +1,41 @@ +import Image from 'next/image'; + +import { formatDateToTimeAgo } from '@/utils/formatDateToString'; + +import { BoardComment } from '@/types/board'; + +type CommentProp = { + comment: BoardComment; +}; + +export default function Comment({ comment }: CommentProp) { + const { content, createdAt, writer } = comment; + + return ( +
+ 댓글 메뉴 아이콘 +

{content}

+
+ 댓글쓴이 프로필 이미지 +
+

{writer.nickname}

+

+ {formatDateToTimeAgo(new Date(createdAt))} +

+
+
+
+ ); +} diff --git a/components/CommentForm.tsx b/components/CommentForm.tsx new file mode 100644 index 000000000..29e17e5ac --- /dev/null +++ b/components/CommentForm.tsx @@ -0,0 +1,44 @@ +import { ChangeEvent, useState } from 'react'; + +import Button from '@/components/Button'; + +type CommentFormProp = { + placeholder?: string; + onSubmit: (inputValue: string) => void; +}; + +export default function CommentForm({ + placeholder = '', + onSubmit, +}: CommentFormProp) { + const [value, setValue] = useState(''); + + const handleTextareaChange = (e: ChangeEvent) => { + setValue(e.target.value); + }; + + const handleSubmitButtonClick = () => { + onSubmit(value); + setValue(''); + }; + + return ( + <> +