-
Notifications
You must be signed in to change notification settings - Fork 35
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
The head ref may contain hidden characters: "React-\uC7A5\uC6A9\uD55C-sprint7"
[장용한]Sprint7 #210
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; | ||
} |
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> | ||
); | ||
}; |
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; | ||
} |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이렇게 |
||
|
||
return ( | ||
<ul> | ||
{itemList.map((item) => ( | ||
<li key={item.id}> | ||
<CommentItem item={item} /> | ||
</li> | ||
))} | ||
</ul> | ||
); | ||
} | ||
|
||
export default Comment; |
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)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 컨텍스트에 itemList를 저장해놓고 필터링을 하기보다 API문서에 있는대로 컨텍스트를 쓴것도 좋다고 한것은 그것도 요구사항을 어떻게든 만족시키는 방법이긴 하지만 There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; |
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
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 댓글작성과 같이 긴 텍스트를 작성해야 할 경우 |
||
<button disabled={!inputValue.trim()}>등록</button> | ||
</div> | ||
); | ||
} | ||
|
||
export default Inquiry; |
There was a problem hiding this comment.
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
가 필요없는 페이지에서도 데이터를 호출하고 들고있어야 해서 효율적이지 못하다고 생각합니다.