Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
9de317f
Merge branch 'React-김서연' of https://github.com/codeit-bootcamp-fronte…
Seon-K May 9, 2025
9d550cf
Feat: Banner -> Header로 변경 및 ItemCard css 수정 및 가격 .toLocaleString()적용
Seon-K May 9, 2025
eead0d6
Refactor: api 로직 try catch문으로 수정
Seon-K May 10, 2025
f45ff9c
Refactor: AllItem & BestItem resize 이벤트 debouce함수 제거
Seon-K May 10, 2025
51069b9
Refactor: Item Header 컴포넌트 두 개의 로고 이미지를 하나의 Link 태그 안으로 재배치
Seon-K May 10, 2025
c44cab1
Refactor: DropDown 컴포넌트 내부 옵션 항목을 버튼으로 변경하여 접근성 향상
Seon-K May 10, 2025
49dcf2c
Feat: routes 컴포넌트 생성 및 적용
Seon-K May 10, 2025
4c95b18
Feat:상세페이지 이동 라우트 경로 설정 및 아이템 카드 상단 Link태그로 상세 페이지 이동 설정
Seon-K May 10, 2025
9b809a5
Refactor: 좋아요 버튼 button 태그로 수정
Seon-K May 10, 2025
4a06b5f
Fix: 라우트 파일 내부 컴포넌트명 ItemDetail로 수정
Seon-K May 10, 2025
40edd61
Refactor: 라우트 경로 수정
Seon-K May 11, 2025
8c006a0
Feat: ItemContext 생성
Seon-K May 11, 2025
62cc687
Feat: itemID API 연동
Seon-K May 11, 2025
2b3f9d5
Feat: itemID API연동 후 Detail 페이지 Description 구현 완료
Seon-K May 11, 2025
43c8397
Feat: 문의하기 부분 구현 완료
Seon-K May 11, 2025
831a7ff
Feat:Comment API연동
Seon-K May 11, 2025
d84e333
Feat: Comment API 연동 및 Review 컴포넌트 생성
Seon-K May 11, 2025
06a4b27
Feat: Review 컴포넌트 UI 구현
Seon-K May 11, 2025
268c294
Feat: KebabMenu UI 구현
Seon-K May 11, 2025
e677ce9
Feat: 수정하기 UI 및 기능 구현
Seon-K May 11, 2025
4d56b10
Feat: 목록으로 돌아가기 버튼 구현
Seon-K May 11, 2025
5dfa0bd
Refactor: 이미지가 없는 경우 대체 이미지로 처리
Seon-K May 11, 2025
a3f3628
Feat: 반응형 구현
Seon-K May 11, 2025
56927fe
Feat: AllItem 컴포넌트 내부 검색 기능 추가 구현
Seon-K May 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions public/ic_back.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions public/ic_kebab.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions public/ic_profile.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
File renamed without changes
27 changes: 5 additions & 22 deletions src/App.jsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,13 @@
import { Routes, Route, BrowserRouter } from "react-router-dom";

import Home from "./pages/Home";
import Login from "./pages/Login";
import Signup from "./pages/Signup";
import Item from "./pages/Item";
import Privacy from "./pages/Privacy";
import FAQ from "./pages/FAQ";
import Community from "./pages/Community";
import { BrowserRouter as Router, Routes } from "react-router-dom";
import routes from "./routes";

import "./styles/common.css";
import AddItem from "./pages/AddItem";

function App() {
return (
<BrowserRouter>
<Routes>
<Route index element={<Home />} />
<Route path="/login" element={<Login />} />
<Route path="/signup" element={<Signup />} />
<Route path="/item" element={<Item />} />
<Route path="/privacy" element={<Privacy />} />
<Route path="/FAQ" element={<FAQ />} />
<Route path="/community" element={<Community />} />
<Route path="/item/additem" element={<AddItem />} />
</Routes>
</BrowserRouter>
<Router>
<Routes>{routes}</Routes>
</Router>
);
}

Expand Down
16 changes: 16 additions & 0 deletions src/api/getItemById.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export default async function getProductById(id) {
try {
const response = await fetch(
`https://panda-market-api.vercel.app/products/${id}`
);
if (!response.ok) {
throw new Error("상품 정보를 불러오는데 실패했습니다.");
}

const data = await response.json();
return data;
} catch (error) {
console.error("상품 상세 에러:", error);
throw error;
}
}
15 changes: 15 additions & 0 deletions src/api/getItemComment.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export default function getItemComment({ id, limit = 10 }) {
return fetch(
`https://panda-market-api.vercel.app/products/${id}/comments?limit=${limit}`
)
.then((response) => {
if (!response.ok) {
throw new Error("Failed to fetch comments");
}
return response.json();
})
.catch((error) => {
console.error("Error fetching comments:", error);
throw error;
});
}
24 changes: 14 additions & 10 deletions src/api/itemAPI.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
export async function getProducts(params = {}) {
const query = new URLSearchParams(params).toString();
try {
const query = new URLSearchParams(params).toString();
const response = await fetch(
`https://panda-market-api.vercel.app/products?${query}`
);

const response = await fetch(
`https://panda-market-api.vercel.app/products?${query}`
);
if (!response.ok) {
throw new Error("데이터를 불러오는데 실패했습니다.");
}

const data = await response.json();
if (!response.ok) {
throw new Error("데이터를 불러오는데 실패했습니다.");
}

return data;
const data = await response.json();
return data;
} catch (error) {
console.error("에러 발생:", error);
throw error;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Link } from "react-router-dom";
import logo from "/logo.svg";
import logo_title from "/logo_title.svg";

function Banner() {
function Header() {
return (
<div className="banner-content">
<div className="logo-content">
Expand All @@ -21,4 +21,4 @@ function Banner() {
);
}

export default Banner;
export default Header;
39 changes: 23 additions & 16 deletions src/components/Item/AllItems.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState, useEffect, useCallback } from "react";
import debounce from "lodash/debounce";

import { getProducts } from "../../api/itemAPI";
import { Link } from "react-router-dom";
Expand Down Expand Up @@ -30,33 +29,34 @@ function AllItem() {
const [totalPage, setTotalPage] = useState();
const [pageSize, setPageSize] = useState(getPageSize());
const [page, setPage] = useState(1);
const [keyword, setKeyword] = useState("");

const handleResize = useCallback(() => {
const newSize = getPageSize();
setPageSize((prevSize) => (prevSize !== newSize ? newSize : prevSize));
}, []);

const debouncedResize = useCallback(debounce(handleResize, 300), [
handleResize,
]);

useEffect(() => {
window.addEventListener("resize", debouncedResize);

window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", debouncedResize);
debouncedResize.cancel();
window.removeEventListener("resize", handleResize);
};
}, [debouncedResize]);
}, []);

useEffect(() => {
const fetchItems = async () => {
try {
const data = await getProducts({
const params = {
orderBy: sort,
pageSize: pageSize,
page: page,
});
pageSize,
page,
};

if (keyword.trim() !== "") {
params.keyword = keyword;
}

const data = await getProducts(params);
setItems(data.list);
setTotalPage(Math.ceil(data.totalCount / pageSize));
} catch (error) {
Expand All @@ -65,7 +65,12 @@ function AllItem() {
};

fetchItems();
}, [sort, pageSize, page]);
}, [sort, pageSize, page, keyword]);

const handleKeywordChange = (e) => {
setKeyword(e.target.value);
setPage(1);
};

return (
<div className="all-item">
Expand All @@ -76,8 +81,10 @@ function AllItem() {
<input
className="item-search"
placeholder="검색할 상품을 입력해주세요"
value={keyword}
onChange={handleKeywordChange}
/>
<Link to="/item/additem" className="item-add-item">
<Link to="additem" className="item-add-item">
상품 등록하기
</Link>
<Dropdown sort={sort} setSort={setSort} className="dropdown" />
Expand Down
13 changes: 3 additions & 10 deletions src/components/Item/BestItem.jsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useState, useEffect, useCallback } from "react";
import debounce from "lodash/debounce";

import ItemList from "./component/ItemList";
import { getProducts } from "../../api/itemAPI";
Expand Down Expand Up @@ -27,18 +26,12 @@ function BestItem() {
setPageSize((prevSize) => (prevSize !== newSize ? newSize : prevSize));
}, []);

const debouncedResize = useCallback(debounce(handleResize, 300), [
handleResize,
]);

useEffect(() => {
window.addEventListener("resize", debouncedResize);

window.addEventListener("resize", handleResize);
return () => {
window.removeEventListener("resize", debouncedResize);
debouncedResize.cancel(); // 꼭 필요!
window.removeEventListener("resize", handleResize);
};
}, [debouncedResize]);
}, []);

useEffect(() => {
const fetchItems = async () => {
Expand Down
File renamed without changes.
19 changes: 8 additions & 11 deletions src/components/Item/Banner.jsx → src/components/Item/Header.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,27 +3,24 @@ import { Link, NavLink } from "react-router-dom";
import logo from "/logo.svg";
import logo_title from "/logo_title.svg";

import "./Banner.css";
import "./Header.css";

function getLinkStyle({ isActive }) {
return {
color: isActive ? "var(--blue)" : undefined,
};
}

function Banner() {
function Header() {
return (
<header className="item-banner">
<div className="item-banner-content">
<div className="item-banner-left">
<div className="item-logo-content">
<Link className="item-logo" to="/">
<img src={logo} alt="판다마켓 로고 이미지" />
</Link>
<Link className="item-logo-text" to="/">
<img src={logo_title} alt="판다마켓" />
</Link>
</div>
<Link className="item-logo-content" to="/">
<img className="item-logo" src={logo} alt="판다마켓 로고 이미지" />
<img className="item-logo-text" src={logo_title} alt="판다마켓" />
</Link>

<nav className="item-nav-content">
<ul>
<li>
Expand All @@ -48,4 +45,4 @@ function Banner() {
);
}

export default Banner;
export default Header;
14 changes: 8 additions & 6 deletions src/components/Item/component/Dropdown.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,14 @@ export default function Dropdown({ sort, setSort }) {
{isOpen && (
<ul className="dropdown-menu">
{options.map((option) => (
<li
key={option.value}
className="dropdown-item"
onClick={() => handleOptionClick(option.value)}
>
{option.label}
<li key={option.value} className="dropdown-item">
<button
type="button"
value={option.value}
onClick={() => handleOptionClick(option.value)}
>
{option.label}
</button>
</li>
))}
</ul>
Expand Down
3 changes: 3 additions & 0 deletions src/components/Item/component/ItemCard.css
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
align-items: flex-start;
gap: 16px;

margin-top: 16px;

cursor: pointer;
}

Expand Down Expand Up @@ -32,6 +34,7 @@
flex-direction: column;
justify-content: center;
align-items: start;
gap: 0.6rem;
}

.item-name {
Expand Down
12 changes: 7 additions & 5 deletions src/components/Item/component/ItemCard.jsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Link } from "react-router-dom";
import heart_icon from "/heart_icon.svg";

import "./ItemCard.css";
Expand All @@ -12,7 +13,7 @@ function isValidImage(url) {

function ItemCard({ item, className }) {
return (
<>
<Link to={`/item/${item.id}`} className="item-card-link">
<div className="item-card-container">
<img
src={isValidImage(item.images[0]) ? item.images[0] : "/no_image.png"}
Expand All @@ -23,20 +24,21 @@ function ItemCard({ item, className }) {
alt={item.name}
className={`${className} item-image`}
/>

<div className="item-card-description">
<span className="item-name">{item.name}</span>
<h2 className="item-price">{item.price}</h2>
<div className="item-heart-content">
<h2 className="item-price">{item.price.toLocaleString()}</h2>
<button className="item-heart-content" type="button">
<img
src={heart_icon}
alt="좋아요 누르는 하트 버튼"
className="item-heart-icon"
/>
<span className="item-heart-count">{item.favoriteCount}</span>
</div>
</button>
</div>
</div>
</>
</Link>
);
}

Expand Down
Loading
Loading