Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[장용한]Sprint7 #210

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
12 changes: 12 additions & 0 deletions src/API/CommentsAPI.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export async function getComments(productId = 9, limit = 5) {
const path = `/products/${productId}/comments?`;
const query = `limit=${limit}`;
const response = await fetch(
`https://panda-market-api.vercel.app${path}${query}`
);
if (!response.ok) {
throw new Error("댓글을 불러오는데 실패했습니다");
}
const result = await response.json();
return result;
}
27 changes: 16 additions & 11 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,27 @@
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Header from "./Layout/Header.jsx";
import HomePage from "./pages/HomePage/HomePage";
import HomePage from "./pages/HomePage/HomePage.jsx";
import AddItemPage from "./pages/AddItemPage/AddItemPage.jsx";
import MarketPage from "./pages/MarketPage/MarketPage.jsx";
import ProductDetail from "./pages/MarketPage/components/ProductDetail.jsx";
import { React } from "react";
import { ItemProvider } from "../src/context/ItemContext.jsx";

function App() {
return (
<BrowserRouter>
<div>
<Header />
<Routes>
<Route index element={<HomePage />} />
<Route path="items" element={<MarketPage />} />
<Route path="/additem" element={<AddItemPage />} />
</Routes>
</div>
</BrowserRouter>
<ItemProvider>
<BrowserRouter>
<div>
<Header />
<Routes>
<Route index element={<HomePage />} />
<Route path="items" element={<MarketPage />} />
<Route path="/additem" element={<AddItemPage />} />
<Route path="/product/:id" element={<ProductDetail />} />
</Routes>
</div>
</BrowserRouter>
</ItemProvider>
);
}

Expand Down
24 changes: 24 additions & 0 deletions src/context/ItemContext.jsx
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

itemList를 가져오기 위해서 컨텍스트에서 전역적으로 데이터 fetching을 하셨네요. fetching을 하는건 좋다고 생각하는데 App 파일보다 ProductDetail로 컨텍스트의 범위를 좁히는게 더 나을 것 같아요.
App 파일은 리액트 프로젝트가 시작될때 가장 먼저 실행되는 루트파일이라서 itemList가 필요없는 페이지에서도 데이터를 호출하고 들고있어야 해서 효율적이지 못하다고 생각합니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { createContext, useState, useEffect } from "react";
import { getProducts } from "../API/ItemAPI";

export const ItemContext = createContext();

export const ItemProvider = ({ children }) => {
const [itemList, setItemList] = useState([]);

useEffect(() => {
const fetchAndProcessData = async () => {
try {
const data = await getProducts();
setItemList(data.list);
} catch (error) {
console.error("데이터를 가져오지 못했습니다", error);
}
};
fetchAndProcessData();
}, []);

return (
<ItemContext.Provider value={itemList}>{children}</ItemContext.Provider>
);
};
5 changes: 5 additions & 0 deletions src/images/icons/ic_seemore.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
79 changes: 78 additions & 1 deletion src/pages/MarketPage/MarketPage.css
Original file line number Diff line number Diff line change
@@ -1,5 +1,82 @@
.MarketPage {
display: flex;
flex-direction: column;
gap: 100px;
gap: 40px;
margin: 0px auto;
width: 1200px;
}

.allItemsSectionHeader {
display: flex;
justify-content: space-between;
align-items: center;
}

.bestItemsContainer {
padding-top: 17px;
padding-bottom: 24px;
list-style-type: none;
}

.bestItemsCardSection {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
}

.sectionTitle {
color: #111827;
font-weight: bold;
font-size: 20px;
line-height: normal;
}

.itemCard {
color: #1f2937;
overflow: hidden;
cursor: pointer;
}

.itemCardThumbnail {
width: 100%;
height: auto;
object-fit: cover;
border-radius: 16px;
overflow: hidden;
aspect-ratio: 1;
margin-bottom: 16px;
}

.itemSummary {
display: flex;
flex-direction: column;
gap: 10px;
flex-grow: 1;
}

.itemName {
font-size: 16px;
font-weight: 400;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}

.itemPrice {
font-size: 16px;
font-weight: bold;
}

.favoriteCount {
display: flex;
align-items: center;
gap: 4px;
color: #4b5563;
font-size: 12px;
}

.allItemsCardSection {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 32px 8px;
}
33 changes: 21 additions & 12 deletions src/pages/MarketPage/components/AllCard.jsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,28 @@
import { NavLink } from "react-router-dom";
import { useContext } from "react";
import { Link } from "react-router-dom";
import "./style/AllCard.css";

function getLinkStyle({ isActive }) {
return {
color: isActive ? "#3692ff" : "",
textDecoration: isActive ? "none" : "",
};
}
import ItemCard from "./ItemCard";
import { ItemContext } from "../../../context/ItemContext";

function AllCard() {
const itemList = useContext(ItemContext);

return (
<div className="register">
<NavLink style={getLinkStyle} to="/additem">
상품 등록하기
</NavLink>
<div>
<div className="allItemsSectionHeader">
<h1 className="sectionTitle">판매 중인 상품</h1>
<Link to="/additem" className="loginLinkButton">
상품 등록하기
</Link>
</div>

<div className="allItemsCardSection">
{itemList.map((item) => (
<Link to={`/product/${item.id}`} key={item.id} className="itemLink">
<ItemCard item={item} />
</Link>
))}
</div>
</div>
);
}
Expand Down
36 changes: 15 additions & 21 deletions src/pages/MarketPage/components/BestCard.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Fragment, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import "./style/BestCard.css";
import { getProducts } from "../../../API/ItemAPI";
import ItemCard from "./ItemCard";

export const getTopFavoriteItems = (data, n) => {
const sortedItems = data.list.sort(
Expand All @@ -9,17 +10,6 @@ export const getTopFavoriteItems = (data, n) => {
return sortedItems.slice(0, n);
};

export function CardItem({ item }) {
return (
<Fragment>
<img className="CardImg" src={item.images} alt={item.name} />
<h1>{item.name}</h1>
<p>{item.price}원</p>
<p>{item.ownerId}</p>
</Fragment>
);
}

function BestCard() {
const [itemList, setItemList] = useState([]);

Expand All @@ -37,15 +27,19 @@ function BestCard() {
}, []);

return (
<ul className="card-container">
{itemList.map((item) => {
return (
<li key={item.id}>
<CardItem item={item} />
</li>
);
})}
</ul>
<div className="bestItemsContainer">
<h1 className="sectionTitle">베스트 상품</h1>

<div className="bestItemsCardSection">
{itemList.map((item) => {
return (
<li key={item.id}>
<ItemCard item={item} />
</li>
);
})}
</div>
</div>
);
}

Expand Down
66 changes: 66 additions & 0 deletions src/pages/MarketPage/components/Comments.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import { getComments } from "../../../API/CommentsAPI";

const timeSince = (date) => {
const now = new Date();
const updatedAt = new Date(date);
const seconds = Math.floor((now - updatedAt) / 1000);

let interval = Math.floor(seconds / 31536000);
if (interval > 1) return `${interval}년 전`;

interval = Math.floor(seconds / 2592000);
if (interval > 1) return `${interval}개월 전`;

interval = Math.floor(seconds / 86400);
if (interval > 1) return `${interval}일 전`;

interval = Math.floor(seconds / 3600);
if (interval > 1) return `${interval}시간 전`;

interval = Math.floor(seconds / 60);
if (interval > 1) return `${interval}분 전`;

return `${Math.floor(seconds)}초 전`;
};
Comment on lines +5 to +26
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

화면의 리렌더링과 관계없는 유틸성함수는 따로 빼주신 점 좋습니다 !!


function CommentItem({ item }) {
return (
<div>
<h1>{item.content}</h1>
<img src={item.images} alt={item.name} />
<p>{item.nickname}</p>
<p>{timeSince(item.updatedAt)}</p>
</div>
);
}

function Comment() {
const { id } = useParams(); // URL에서 id 값을 가져옴
const [itemList, setItemList] = useState([]);

useEffect(() => {
const fetchAndProcessData = async () => {
try {
const data = await getComments(id);
setItemList(data.list);
} catch (error) {
console.error("데이터를 가져오지 못했습니다", error);
}
};
fetchAndProcessData();
}, [id]);
Comment on lines +40 to +53
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게 id를 기반으로 댓글을 가져올때 이렇게 하는것이 맞습니다 ~ !! ㅎㅎ
조금 더 도전해서 이 로직을 커스텀훅으로 분리해보는건 어떨까요?


return (
<ul>
{itemList.map((item) => (
<li key={item.id}>
<CommentItem item={item} />
</li>
))}
</ul>
);
}

export default Comment;
29 changes: 29 additions & 0 deletions src/pages/MarketPage/components/DetailCard.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import React, { useContext } from "react";
import { useParams } from "react-router-dom";
import { ItemContext } from "../../../context/ItemContext";
import { ReactComponent as HeartIcon } from "../../../images/icons/ic_heart.svg";
import { ReactComponent as SeeMoreIcon } from "../../../images/icons/ic_seemore.svg";
function DetailCard() {
const { id } = useParams();
const itemList = useContext(ItemContext);
const item = itemList.find((item) => item.id === parseInt(id));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

컨텍스트에 itemList를 저장해놓고 필터링을 하기보다 API문서에 있는대로 productId를 이용해 id를 가져오는 방법이 더 좋을 것 같아요.
아래의 API를 이용해 필요한 아이템 하나만 가져오세요
https://panda-market-api.vercel.app/docs/#/Product/RetrieveProduct

컨텍스트를 쓴것도 좋다고 한것은 그것도 요구사항을 어떻게든 만족시키는 방법이긴 하지만
정석적으로는 id값을 이용해 하나만 가져와야합니다. ㅎㅎ

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const fetchProduct = async (productId) => {
    // fetch product detail
    const response = await fetch(
      `https://panda-market-api.vercel.app/products/${productId}`
    );
    if (!response.ok) {
      throw new Error("제품을 불러오는데 실패했습니다");
    }
    const result = await response.json();
    return result;
  };


if (!item) {
return <h2>Product not found</h2>;
}

return (
<div>
<img src={item.images} alt={item.name} />
<h1>{item.name}</h1>
<SeeMoreIcon />
<h2>{item.price}</h2>
<p>{item.description}</p>
<p>{item.tags}</p>
<HeartIcon />
<p>{item.favoriteCount}</p>
</div>
);
}

export default DetailCard;
24 changes: 24 additions & 0 deletions src/pages/MarketPage/components/Inquiry.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React, { useState } from "react";

function Inquiry() {
const [inputValue, setInputValue] = useState("");

const handleInputChange = (event) => {
setInputValue(event.target.value);
};

return (
<div>
문의하기
<input
type="text"
value={inputValue}
onChange={handleInputChange}
placeholder="개인정보를 공유 및 요청하거나, 명예 훼손, 무단 광고, 불법 정보 유포시 모니터링 후 삭제될 수 있으며, 이에 대한 민형사상 책임은 게시자에게 있습니다."
/>
Comment on lines +13 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

댓글작성과 같이 긴 텍스트를 작성해야 할 경우 input 태그보다 textarea 태그를 이용하면 좀 더 요구사항에 맞다고 생각합니다. 한 예로 textarea는 입력인풋의 크기를 조절 할 수 있어 긴 문장을 유저가 한눈에 볼 수 있습니다.

<button disabled={!inputValue.trim()}>등록</button>
</div>
);
}

export default Inquiry;
Loading
Loading