From c9f64197994e3a3ab16713af522c76d73b3ae0aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=A5=EC=88=98=EA=B2=BD?= Date: Sat, 7 Dec 2024 09:29:15 +0900 Subject: [PATCH 1/4] =?UTF-8?q?board/index.tsx=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/market/AllProduct.tsx | 2 +- pages/MarketPage/[id].tsx | 0 pages/board/index.tsx | 69 ++++++++++++++++++++++++-------- 3 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 pages/MarketPage/[id].tsx diff --git a/components/market/AllProduct.tsx b/components/market/AllProduct.tsx index aac27c26f..97d80bb62 100644 --- a/components/market/AllProduct.tsx +++ b/components/market/AllProduct.tsx @@ -4,7 +4,7 @@ import Link from "next/link"; import SearchIcon from "@/public/images/ic_search.svg"; // import Image from "next/image"; import Dropdown from "@/components/ui/Dropdown"; -import "@/styles/AllProduct.module.css"; +// import styles from "@/styles/AllProduct.module.css"; import { useEffect, useState } from "react"; import ItemCard from "@/components/market/ItemCard"; import { getProducts } from "@/api/api"; diff --git a/pages/MarketPage/[id].tsx b/pages/MarketPage/[id].tsx new file mode 100644 index 000000000..e69de29bb diff --git a/pages/board/index.tsx b/pages/board/index.tsx index 42b5d5ea7..276604b38 100644 --- a/pages/board/index.tsx +++ b/pages/board/index.tsx @@ -1,30 +1,65 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import BestArticle from "@/components/board/BestArticle"; import AllArticle from "@/components/board/AllArticle"; import { Article, ArticleList } from "@/types/Types"; -export async function getServerSideProps() { - const response = await fetch( - `https://panda-market-api.vercel.app/articles?orderBy=recent` - ); - const data: ArticleList = await response.json(); +// export async function getStaticProps() { +// const response = await fetch( +// `https://panda-market-api.vercel.app/articles?orderBy=recent` +// ); +// const data: ArticleList = await response.json(); - return { - props: { - initialArticles: data.list, - }, - }; -} +// return { +// props: { +// initialArticles: data.list, +// }, +// }; +// } -interface BoardsPageProps { - initialArticles: Article[]; -} +// interface BoardsPageProps { +// initialArticles: Article[]; +// } + +// export default function BoardsPage({ initialArticles }: BoardsPageProps) { +// return ( +//
+// +// +//
+// ); +// } + +export default function BoardsPage() { + const [articles, setArticles] = useState([]); + const [loading, setLoading] = useState(true); + + useEffect(() => { + const fetchArticles = async () => { + setLoading(true); // 로딩 시작 + try { + const response = await fetch( + `https://panda-market-api.vercel.app/articles?orderBy=recent` + ); + const data = await response.json(); + setArticles(data.list); + } catch (error) { + console.error("오류: ", error); + } finally { + setLoading(false); // 로딩 종료 + } + }; + + fetchArticles(); + }, []); + + if (loading) { + return
Loading...
; + } -export default function BoardsPage({ initialArticles }: BoardsPageProps) { return (
- +
); } From 9ff8a24059b4494a217984254da24ca2cb2c8525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=A5=EC=88=98=EA=B2=BD?= Date: Mon, 9 Dec 2024 03:06:09 +0900 Subject: [PATCH 2/4] =?UTF-8?q?fix:=20MarketPage=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/board/ArticleInfo.tsx | 3 ++- components/board/BestArticle.tsx | 7 ++++--- components/market/AllProduct.tsx | 3 ++- components/market/ItemCard.tsx | 5 ++++- components/ui/Dropdown.tsx | 9 ++------- components/ui/ImageUpload.tsx | 2 +- components/ui/Pagination.tsx | 6 +++--- components/ui/Search.tsx | 3 ++- next.config.js | 9 +++++++++ pages/MarketPage/[id].tsx | 1 + 10 files changed, 30 insertions(+), 18 deletions(-) diff --git a/components/board/ArticleInfo.tsx b/components/board/ArticleInfo.tsx index a8095690d..0581ee1d2 100644 --- a/components/board/ArticleInfo.tsx +++ b/components/board/ArticleInfo.tsx @@ -1,6 +1,7 @@ import Profile from "@/public/images/profile.svg"; import { Article } from "@/types/Types"; import { formatDate } from "date-fns"; +import Image from "next/image"; interface ArticleInfoProps { article: Article; @@ -11,7 +12,7 @@ const ArticleInfo = ({ article }: ArticleInfoProps) => { return (
- + 프로필 {article.writer.nickname} {formetDate}
); diff --git a/components/board/BestArticle.tsx b/components/board/BestArticle.tsx index cabd720f0..e21ac1554 100644 --- a/components/board/BestArticle.tsx +++ b/components/board/BestArticle.tsx @@ -12,7 +12,7 @@ const BestArticleCard = ({ article }: { article: Article }) => { <>
- + 베스트 게시글 Best
@@ -22,10 +22,11 @@ const BestArticleCard = ({ article }: { article: Article }) => { {article.image && (
{`${article.id}번
)} diff --git a/components/market/AllProduct.tsx b/components/market/AllProduct.tsx index 97d80bb62..258978da0 100644 --- a/components/market/AllProduct.tsx +++ b/components/market/AllProduct.tsx @@ -11,6 +11,7 @@ import { getProducts } from "@/api/api"; import Pagination from "@/components/ui/Pagination"; import { Product, ProductListResponse, ProductSortOption } from "@/types/Types"; import { useRouter } from "next/router"; +import Image from "next/image"; // 화면 사이즈 const getPageSize = (width: number) => { @@ -152,7 +153,7 @@ const AllProduct = () => { width={24} height={24} /> */} - + Search Icon { src={item.images[0]} alt={`${item.name} 상품 썸네일`} className="itemCardImg" + layout="responsive" + width={1} // 비율 유지 (가로 1) + height={1} // 비율 유지 (세로 1) />

{item.name}

{item.price.toLocaleString()}원

- + 좋아요 {/* Heart Icon {isDropdownView && ( diff --git a/components/ui/ImageUpload.tsx b/components/ui/ImageUpload.tsx index 3d9e3ecc4..2b9d17e6a 100644 --- a/components/ui/ImageUpload.tsx +++ b/components/ui/ImageUpload.tsx @@ -60,7 +60,7 @@ const ImageUpload = ({ title }: ImageUploadProps) => { >

- + Plus
이미지 등록

diff --git a/components/ui/Pagination.tsx b/components/ui/Pagination.tsx index 762df54d4..cd305195e 100644 --- a/components/ui/Pagination.tsx +++ b/components/ui/Pagination.tsx @@ -2,7 +2,7 @@ import React from "react"; import Left from "@/public/images/left.svg"; import Right from "@/public/images/right.svg"; import "../../styles/Pagination.module.css"; -// import Image from "next/image"; +import Image from "next/image"; interface PaginationProps { totalPageNum: number; @@ -37,7 +37,7 @@ export default function Pagination({ disabled={activePageNum === 1} onClick={() => onPageChange(activePageNum - 1)} > - + Previous {/* Previous */} {pages.map((page) => ( @@ -56,7 +56,7 @@ export default function Pagination({ disabled={activePageNum === totalPageNum} onClick={() => onPageChange(activePageNum + 1)} > - + Next {/* Next */}
diff --git a/components/ui/Search.tsx b/components/ui/Search.tsx index 11e35f55c..1b8e35d75 100644 --- a/components/ui/Search.tsx +++ b/components/ui/Search.tsx @@ -1,6 +1,7 @@ import SearchIcon from "@/public/images/ic_search.svg"; import { useEffect, useState } from "react"; import { useRouter } from "next/router"; +import Image from "next/image"; interface SearchProps { onSearch: (keyword: string) => void; @@ -31,7 +32,7 @@ const Search = ({ return (
- + 검색 아이콘 Date: Mon, 9 Dec 2024 03:58:55 +0900 Subject: [PATCH 3/4] =?UTF-8?q?fix:=20=EB=A7=88=EC=BC=93=20=EC=83=81?= =?UTF-8?q?=EC=84=B8=ED=8E=98=EC=9D=B4=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/api.tsx | 9 ++- components/productdetail/DetailComment.tsx | 22 ++---- components/productdetail/DetailInput.tsx | 10 ++- components/productdetail/ItemContent.tsx | 23 +++--- components/productdetail/LikeButton.tsx | 4 +- pages/MarketPage/[id].tsx | 82 ++++++++++++++++++++++ pages/ProductDetailPage/index.tsx | 71 ------------------- types/Types.tsx | 17 +++++ 8 files changed, 130 insertions(+), 108 deletions(-) delete mode 100644 pages/ProductDetailPage/index.tsx diff --git a/api/api.tsx b/api/api.tsx index f0423b6ce..1afb9936f 100644 --- a/api/api.tsx +++ b/api/api.tsx @@ -21,7 +21,7 @@ export async function getProducts({ } } -export async function getDetailComments(productId: string) { +export async function getDetailComments(productId: number) { if (!productId) { throw new Error("Invalid product ID"); } @@ -52,10 +52,15 @@ export async function getProductComments({ throw new Error("Invalid product ID"); } + const params = { + limit: String(limit), + }; + try { // 올바르게 URLSearchParams 생성 // const query = new URLSearchParams().toString(); // 빈 쿼리 문자열을 생성 - const query = `limit=${limit}`; + // const query = `limit=${limit}`; + const query = new URLSearchParams(params).toString(); const response = await fetch( `https://panda-market-api.vercel.app/products/${productId}/comments?${query}` ); // api 호출 diff --git a/components/productdetail/DetailComment.tsx b/components/productdetail/DetailComment.tsx index 8e56c4f76..e090a1cda 100644 --- a/components/productdetail/DetailComment.tsx +++ b/components/productdetail/DetailComment.tsx @@ -4,19 +4,7 @@ import { getProductComments } from "@/api/api"; import InquiryEmpty from "../../../public/images/inquiry_empty.svg"; import ProfileImg from "../../../images/profile.png"; import Kebab from "../../../public/images/ic_kebab.svg"; - -// 댓글 데이터 타입 정의 -interface Comment { - id: string; - content: string; - updatedAt: Date; - createdAt: Date; - writer: { - image: string | null; - nickname: string; - id: string; - }; -} +import { Comment } from "@/types/Types"; // CommentItem 컴포넌트 type CommentItemProps = { @@ -106,7 +94,7 @@ const EmptyState = () => { // DetailComment 컴포넌트 type DetailCommentProps = { - productId: string; + productId: number; }; interface ProductCommentList { @@ -126,9 +114,9 @@ function DetailComment({ productId }: DetailCommentProps) { setIsLoading(true); try { - const response: ProductCommentList = await getProductComments( - productId - ); + const response: ProductCommentList = await getProductComments({ + productId, + }); setComments(response.list); setError(null); } catch (error) { diff --git a/components/productdetail/DetailInput.tsx b/components/productdetail/DetailInput.tsx index c54292622..128d19424 100644 --- a/components/productdetail/DetailInput.tsx +++ b/components/productdetail/DetailInput.tsx @@ -1,10 +1,14 @@ -import React, { useState } from "react"; +import React, { ChangeEvent, useState } from "react"; import DetailComment from "./DetailComment"; -function DetailInput({ productId }) { +interface DetailInputProps { + productId: number; +} + +function DetailInput({ productId }: DetailInputProps) { const [inputValue, setInputValue] = useState(""); - const handleInputChange = (e) => { + const handleInputChange = (e: ChangeEvent) => { setInputValue(e.target.value); }; diff --git a/components/productdetail/ItemContent.tsx b/components/productdetail/ItemContent.tsx index 54f2ceb2c..03b8c6778 100644 --- a/components/productdetail/ItemContent.tsx +++ b/components/productdetail/ItemContent.tsx @@ -1,18 +1,9 @@ import React from "react"; import TagList from "./TagList"; import LikeButton from "./LikeButton"; -import { ReactComponent as Kebab } from "../../../images/ic_kebab.svg"; - -interface Product { - id: string; - name: string; - description: string; - price: number; - tags: string[]; - images: string[]; - isFavorite: boolean; - favoriteCount: number; -} +import Kebab from "@/public/images/ic_kebab.svg"; +import { Product } from "@/types/Types"; +import Image from "next/image"; interface ItemContentProps { product: Product; @@ -30,7 +21,13 @@ function ItemContent({ product }: ItemContentProps) {
{/* 더보기 버튼 */}
diff --git a/components/productdetail/LikeButton.tsx b/components/productdetail/LikeButton.tsx index 17187f24d..22bc6f007 100644 --- a/components/productdetail/LikeButton.tsx +++ b/components/productdetail/LikeButton.tsx @@ -1,8 +1,8 @@ import React, { useState } from "react"; -import { ReactComponent as Heart } from "../../../image/ic_heart.svg"; +import Heart from "@/public/images/ic_heart.svg"; interface LikeButtonProps { - productId: string; + productId: number; isFavorite: boolean; favoriteCount: number; } diff --git a/pages/MarketPage/[id].tsx b/pages/MarketPage/[id].tsx index 8b1378917..cc8a03ea6 100644 --- a/pages/MarketPage/[id].tsx +++ b/pages/MarketPage/[id].tsx @@ -1 +1,83 @@ +import { getDetailComments } from "@/api/api"; +import DetailInput from "@/components/productdetail/DetailInput"; +import ItemContent from "@/components/productdetail/ItemContent"; +import { Product } from "@/types/Types"; +import Image from "next/image"; +import BackIcon from "@/public/images/ic_back.svg"; +import Link from "next/link"; +import { useRouter } from "next/router"; +import { useEffect, useState } from "react"; +const ItemPage = () => { + const [product, setProduct] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [error, setError] = useState(null); + + const router = useRouter(); + const { id } = router.query; + + const productId = Number(id); + + useEffect(() => { + async function fetchProduct() { + if (!productId) { + setError("상품 아이디가 제공되지 않았어요."); + setIsLoading(false); + return; + } + + setIsLoading(true); + try { + const data: Product = await getDetailComments(productId); + if (!data) { + throw new Error("해당 상품의 데이터를 찾을 수 없습니다."); + } + setProduct(data); + } catch (error: any) { + setError(error.message); + } finally { + setIsLoading(false); + } + } + + fetchProduct(); + }, [productId]); + + if (error) { + alert(`오류: ${error}`); + } + + if (!productId || !product) return null; + + return ( +
+ {isLoading && ( +
+
+
+ )} + +
+ + +
+ + + + {/* 목록으로 돌아가기 버튼 */} + + 목록으로 돌아가기 + 돌아가기 + +
+
+ ); +}; + +export default ItemPage; diff --git a/pages/ProductDetailPage/index.tsx b/pages/ProductDetailPage/index.tsx deleted file mode 100644 index 7ee4ff50e..000000000 --- a/pages/ProductDetailPage/index.tsx +++ /dev/null @@ -1,71 +0,0 @@ -import { useParams } from "react-router-dom"; -import React, { useState, useEffect } from "react"; -import Link from "next/link"; - -import { getDetailComments } from "../../api/api"; -import BackIcon from "../../images/ic_back.svg"; -import DetailInput from "../../components/productdetail/DetailInput"; -import ItemContent from "../../components/productdetail/ItemContent"; - -export default function ProductDetail() { - const [product, setProduct] = useState(null); - const [isLoading, setIsLoading] = useState(true); - const [error, setError] = useState(null); - - const { productId } = useParams(); - - useEffect(() => { - async function fetchProduct() { - if (!productId) { - setError("상품 아이디가 제공되지 않았어요."); - setIsLoading(false); - return; - } - - setIsLoading(true); - try { - const data = await getDetailComments(productId); - if (!data) { - throw new Error("해당 상품의 데이터를 찾을 수 없습니다."); - } - setProduct(data); - } catch (error) { - setError(error.message); - } finally { - setIsLoading(false); - } - } - - fetchProduct(); - }, [productId]); - - if (error) { - alert(`오류: ${error}`); - } - - if (!productId || !product) return null; - - return ( -
- {isLoading && ( -
-
-
- )} - -
- - -
- - - - {/* 목록으로 돌아가기 버튼 */} - - 목록으로 돌아가기 - - -
-
- ); -} diff --git a/types/Types.tsx b/types/Types.tsx index fa93e692c..4bf33494d 100644 --- a/types/Types.tsx +++ b/types/Types.tsx @@ -57,3 +57,20 @@ export interface ArticleList { } export type ArticleSortOption = "recent" | "like"; + +export interface Comment { + writer: { + image: string; + nickname: string; + id: number; + }; + updatedAt: Date; + createdAt: Date; + content: string; + id: number; +} + +export interface CommentList { + nextCursor: number; + list: Comment[]; +} From 9565ceb5fb687f81cd48dc5455a44684e01e287a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9E=A5=EC=88=98=EA=B2=BD?= Date: Mon, 9 Dec 2024 04:21:22 +0900 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/board/AllArticle.tsx | 3 +- components/board/ArticleContent.tsx | 3 +- components/board/ArticleItem.tsx | 4 ++- components/ui/DeleteButton.tsx | 5 +-- components/ui/ImageUpload.tsx | 2 +- pages/addboard/index.tsx | 49 +++++++++++++++++++++++++++++ 6 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 pages/addboard/index.tsx diff --git a/components/board/AllArticle.tsx b/components/board/AllArticle.tsx index b64586295..e6325da31 100644 --- a/components/board/AllArticle.tsx +++ b/components/board/AllArticle.tsx @@ -4,6 +4,7 @@ import { useEffect, useState } from "react"; import ArticleItem from "./ArticleItem"; import Search from "@/components/ui/Search"; import Dropdown from "@/components/ui/Dropdown"; +import Link from "next/link"; interface AllArticlesProps { initialArticles: Article[]; @@ -52,7 +53,7 @@ const AllArticle = ({ initialArticles }: AllArticlesProps) => {

게시글

- + 글쓰기
diff --git a/components/board/ArticleContent.tsx b/components/board/ArticleContent.tsx index 518164934..4e7f99f44 100644 --- a/components/board/ArticleContent.tsx +++ b/components/board/ArticleContent.tsx @@ -1,6 +1,7 @@ import { Article } from "@/types/Types"; import Kebob from "@/public/images/ic_kebab.svg"; import ArticleInfo from "./ArticleInfo"; +import Image from "next/image"; interface ArticleContentProps { article: Article; @@ -13,7 +14,7 @@ const ArticleContent = ({ article }: ArticleContentProps) => {

{article.title}

diff --git a/components/board/ArticleItem.tsx b/components/board/ArticleItem.tsx index 4a12ae4d9..3d5ec8a58 100644 --- a/components/board/ArticleItem.tsx +++ b/components/board/ArticleItem.tsx @@ -17,10 +17,12 @@ const ArticleItem = ({ article }: ArticleItemProps) => {
{`${article.id}번
diff --git a/components/ui/DeleteButton.tsx b/components/ui/DeleteButton.tsx index 8693e93f1..19618dba3 100644 --- a/components/ui/DeleteButton.tsx +++ b/components/ui/DeleteButton.tsx @@ -1,5 +1,6 @@ import React from "react"; -import CloseIcon from "../public/images/ic_close.svg"; +import CloseIcon from "@/public/images/ic_close.svg"; +import Image from "next/image"; interface DeleteButtonProps { onClick: () => void; @@ -9,7 +10,7 @@ interface DeleteButtonProps { function DeleteButton({ onClick, label }: DeleteButtonProps) { return ( ); } diff --git a/components/ui/ImageUpload.tsx b/components/ui/ImageUpload.tsx index 2b9d17e6a..59ea4853d 100644 --- a/components/ui/ImageUpload.tsx +++ b/components/ui/ImageUpload.tsx @@ -1,6 +1,6 @@ import React, { useRef, useState } from "react"; // import { useEffect } from "react"; -import PlusIcon from "../public/images/ic_plus.svg"; +import PlusIcon from "@/public/images/ic_plus.svg"; import DeleteButton from "./DeleteButton"; import Image from "next/image"; diff --git a/pages/addboard/index.tsx b/pages/addboard/index.tsx new file mode 100644 index 000000000..18e93fdef --- /dev/null +++ b/pages/addboard/index.tsx @@ -0,0 +1,49 @@ +import ImageUpload from "@/components/ui/ImageUpload"; +import InputItem from "@/components/ui/InputItem"; +import { FormEvent, useState } from "react"; + +const AddBoard = () => { + const [title, setTitle] = useState(""); + const [content, setContent] = useState(""); + + // 이미지 제외 모든 input에 값이 입력 + const isSubmit = !title.trim() || !content.trim(); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + }; + + return ( +
+
+
+

게시글 쓰기

+ +
+ +
+ setTitle(e.target.value)} + placeholder="제목을 입력해 주세요" + /> + setContent(e.target.value)} + placeholder="내용을 입력해 주세요" + isTextArea + /> + +
+
+
+ ); +}; + +export default AddBoard;