Skip to content

Commit 89f5ba4

Browse files
authored
Merge pull request #290 from Sookyeong02/Next-장수경-sprint9
[장수경] sprint9
2 parents 8d163dd + d1094f5 commit 89f5ba4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

77 files changed

+3668
-600
lines changed

api/api.tsx

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import { ProductListFetch } from "@/types/Types";
2+
3+
export async function getProducts({
4+
orderBy,
5+
pageSize,
6+
page = 1,
7+
}: ProductListFetch) {
8+
const query = `orderBy=${orderBy}&page=${page}&pageSize=${pageSize}`; // pageSize를 쿼리 파라미터에 추가
9+
try {
10+
const response = await fetch(
11+
`https://panda-market-api.vercel.app/products?${query}`
12+
);
13+
if (!response.ok) {
14+
throw new Error(`HTTP error: ${response.status}`);
15+
}
16+
const body = await response.json();
17+
return body;
18+
} catch (error) {
19+
console.error("패치 실패:", error);
20+
throw error;
21+
}
22+
}
23+
24+
export async function getDetailComments(productId: string) {
25+
if (!productId) {
26+
throw new Error("Invalid product ID");
27+
}
28+
29+
try {
30+
const response = await fetch(
31+
`https://panda-market-api.vercel.app/products/${productId}`
32+
);
33+
if (!response.ok) {
34+
throw new Error(`HTTP error: ${response.status}`);
35+
}
36+
const body = await response.json();
37+
return body;
38+
} catch (error) {
39+
console.error("패치 실패:", error);
40+
throw error;
41+
}
42+
}
43+
44+
export async function getProductComments({
45+
productId,
46+
limit = 10,
47+
}: {
48+
productId: number;
49+
limit?: number;
50+
}) {
51+
if (!productId) {
52+
throw new Error("Invalid product ID");
53+
}
54+
55+
try {
56+
// 올바르게 URLSearchParams 생성
57+
// const query = new URLSearchParams().toString(); // 빈 쿼리 문자열을 생성
58+
const query = `limit=${limit}`;
59+
const response = await fetch(
60+
`https://panda-market-api.vercel.app/products/${productId}/comments?${query}`
61+
); // api 호출
62+
63+
if (!response.ok) {
64+
throw new Error(`HTTP error: ${response.status}`);
65+
}
66+
67+
const body = await response.json();
68+
return body;
69+
} catch (error) {
70+
console.error("패치 실패", error);
71+
throw error;
72+
}
73+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
import React, { useState } from "react";
2+
import DeleteButton from "../ui/DeleteButton";
3+
4+
interface AddInputItemProps {
5+
productName: string;
6+
productContent: string;
7+
productPrice: number;
8+
productTag: string;
9+
onNameChange: (value: string) => void;
10+
onContentChange: (value: string) => void;
11+
onPriceChange: (value: number) => void;
12+
onTagChange: (value: string) => void;
13+
onTagsUpdate: (tags: string[]) => void;
14+
}
15+
16+
function AddInputItem({
17+
productName,
18+
productContent,
19+
productPrice,
20+
productTag,
21+
onNameChange,
22+
onContentChange,
23+
onPriceChange,
24+
onTagChange,
25+
onTagsUpdate,
26+
}: AddInputItemProps) {
27+
const [tags, setTags] = useState<string[]>([]); // 제네릭, 함수에서 인자를 받는 것처럼 타입도 인자로 받기
28+
29+
const handleTagSubmit = () => {
30+
if (productTag.trim() !== "") {
31+
const updatedTags = [...tags, productTag.trim()];
32+
setTags(updatedTags);
33+
onTagChange(""); // Clear input
34+
onTagsUpdate(updatedTags); // 부모 컴포넌트로 태그 배열 전달
35+
}
36+
};
37+
38+
const handleTagDelete = (index: number) => {
39+
const updatedTags = tags.filter((_, i) => i !== index);
40+
setTags(updatedTags);
41+
onTagsUpdate(updatedTags); // 변경된 태그 배열을 부모 컴포넌트에 전달
42+
};
43+
44+
const handleKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => {
45+
// 키보드 이벤트 처리 방식
46+
if (e.key === " " || e.key === "," || e.key === "Enter") {
47+
e.preventDefault();
48+
handleTagSubmit();
49+
}
50+
};
51+
52+
return (
53+
<form>
54+
<p>상품명</p>
55+
<input
56+
value={productName}
57+
onChange={(e) => onNameChange(e.target.value)}
58+
placeholder="상품명을 입력해주세요"
59+
/>
60+
<p>상품 소개</p>
61+
<textarea
62+
value={productContent}
63+
onChange={(e) => onContentChange(e.target.value)}
64+
placeholder="상품 소개를 입력해주세요"
65+
/>
66+
<p>판매가격</p>
67+
<input
68+
type="number"
69+
value={productPrice}
70+
onChange={(e) => onPriceChange(Number(e.target.value))}
71+
placeholder="판매 가격을 입력해주세요"
72+
/>
73+
<p>태그</p>
74+
<input
75+
value={productTag}
76+
onChange={(e) => onTagChange(e.target.value)}
77+
onKeyDown={handleKeyPress}
78+
placeholder="태그를 입력해주세요"
79+
/>
80+
81+
<div>
82+
{tags.map((tag, index) => (
83+
<p className="tagOutput" key={index}>
84+
#{tag}{" "}
85+
<DeleteButton
86+
label="삭제"
87+
onClick={() => handleTagDelete(index)} /*삭제 핸들러 전달*/
88+
/>
89+
</p>
90+
))}
91+
</div>
92+
</form>
93+
);
94+
}
95+
96+
export default AddInputItem;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// import React from "react";
2+
3+
// function AddItemButton({ onSubmit, disabled }) {
4+
// return (
5+
// <button
6+
// className={`addButton ${disabled ? "disabled" : "active"}`}
7+
// onClick={onSubmit}
8+
// disabled={disabled}
9+
// >
10+
// 등록
11+
// </button>
12+
// );
13+
// }
14+
15+
// export default AddItemButton;

components/boards/AllArticle.tsx

Whitespace-only changes.

components/boards/BestArticle.tsx

Whitespace-only changes.

components/layout/Header.tsx

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import React from "react";
2+
import Link from "next/link";
3+
import Image from "next/image";
4+
import Logo from "../../public/images/logo.svg";
5+
import Profile from "../../public/images/profile.svg";
6+
import "../../styles/Header.module.css";
7+
8+
const Header = () => {
9+
return (
10+
<header className="header">
11+
<div className="headerSection">
12+
<Link href="/" className="homeLogo">
13+
<Image src={Logo} alt="판다마켓 로고" className="logoImg" />
14+
</Link>
15+
16+
<nav>
17+
<div className="listSection">
18+
<Link href="/community" className="communityLink">
19+
자유게시판
20+
</Link>
21+
<Link href="/items" className="itemLink">
22+
중고마켓
23+
</Link>
24+
</div>
25+
</nav>
26+
</div>
27+
28+
<Link href="/login" className="progileSection">
29+
<Image src={Profile} alt="프로필" className="profileImg" />
30+
</Link>
31+
</header>
32+
);
33+
};
34+
35+
export default Header;

components/layout/Layout.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import React, { ReactNode } from "react";
2+
import Header from "./Header";
3+
import { useRouter } from "next/router";
4+
5+
interface LayoutProps {
6+
children: ReactNode;
7+
}
8+
9+
const Layout: React.FC<LayoutProps> = ({ children }) => {
10+
const router = useRouter();
11+
const isAuthPage =
12+
router.pathname === "/login" || router.pathname === "/signup";
13+
14+
return (
15+
<>
16+
{!isAuthPage && <Header />}
17+
<main className={isAuthPage ? "" : "withHeader"}>{children}</main>
18+
</>
19+
);
20+
};
21+
22+
export default Layout;

components/market/AllProduct.tsx

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import React from "react";
2+
import Link from "next/link";
3+
// import SortIcon from "../images/ic_sort.svg";
4+
import SearchIcon from "../..//../public/images/ic_search.svg";
5+
import Dropdown from "../../../component/Dropdown";
6+
import "./AllProduct.css";
7+
import { useEffect, useState } from "react";
8+
import ItemCard from "./ItemCard";
9+
import { getProducts } from "../../../api/api";
10+
import Pagination from "../../../component/Pagination";
11+
12+
// 화면 사이즈
13+
const getPageSize = () => {
14+
const width = window.innerWidth;
15+
if (width < 768) {
16+
// 모바일
17+
return 4;
18+
} else if (width < 1280) {
19+
// 태블릿
20+
return 6;
21+
} else {
22+
// pc
23+
return 10;
24+
}
25+
};
26+
27+
interface Product {
28+
id: string;
29+
name: string;
30+
price: number;
31+
images: string[];
32+
favoriteCount: number;
33+
}
34+
35+
function AllProduct() {
36+
const [orderBy, setOrderBy] = useState("recent");
37+
const [itemList, setItemList] = useState<Product[]>([]);
38+
// const pageSize = 4;
39+
const [page, setPage] = useState(1);
40+
const [pageSize, setPageSize] = useState(getPageSize());
41+
const [totalPageNum, setTotalPageNum] = useState<number>();
42+
43+
const sortedData = async ({
44+
orderBy,
45+
page,
46+
pageSize,
47+
}: {
48+
orderBy: string;
49+
page: number;
50+
pageSize: number;
51+
}) => {
52+
const products = await getProducts(orderBy, page, pageSize); // 객체로 전달하면 안됨?
53+
setItemList(products.list);
54+
setTotalPageNum(Math.ceil(products.totalCount / pageSize) || 1); // 기본값을 1로 설정
55+
};
56+
57+
const handleSortSelection = (sortOption: string) => {
58+
setOrderBy(sortOption);
59+
};
60+
61+
// useEffect(() => {
62+
// sortedData({ orderBy: "favorite", pageSize });
63+
// }, []);
64+
useEffect(() => {
65+
const handleResize = () => {
66+
setPageSize(getPageSize());
67+
};
68+
69+
// 화면 크기 변경할 때마다 pageSize를 다시 계산해 넣음
70+
window.addEventListener("resize", handleResize);
71+
sortedData({ orderBy, page, pageSize });
72+
}, [orderBy, page, pageSize]);
73+
74+
const onPageChange = (pageNumber: number) => {
75+
setPage(pageNumber);
76+
};
77+
78+
return (
79+
<div className="allItemsContainer">
80+
<h1>판매 중인 상품</h1>
81+
82+
<div className="productHeader">
83+
<Link href="/additem" className="addBtn">
84+
상품 등록하기
85+
</Link>
86+
87+
<div className="search">
88+
<SearchIcon />
89+
<input
90+
className="searchInput"
91+
placeholder="검색할 상품을 입력해 주세요"
92+
/>
93+
</div>
94+
95+
<Dropdown onSortSelection={handleSortSelection} />
96+
</div>
97+
98+
<div>
99+
{itemList &&
100+
itemList?.map((item) => (
101+
<ItemCard item={item} key={`all-item-${item.id}`} />
102+
))}
103+
</div>
104+
105+
<div>
106+
<Pagination
107+
totalPageNum={totalPageNum ?? 1}
108+
// totalPageNum이 undefined일 경우 1을 기본값으로 설정
109+
// ??: 널 병합 연산자, null 또는 undefined 값을 처리할 때 사용, 두 개의 값 중 첫 번째 값이 null 또는 undefined일 경우 두 번째 값을 반환 + 그렇지 않으면 첫 번째 값을 그대로 반환
110+
activePageNum={page}
111+
onPageChange={onPageChange}
112+
/>
113+
</div>
114+
</div>
115+
);
116+
}
117+
118+
export default AllProduct;

0 commit comments

Comments
 (0)