-
Notifications
You must be signed in to change notification settings - Fork 78
[김민재] Sprint12 #723
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
base: Next.js-김민재
Are you sure you want to change the base?
The head ref may contain hidden characters: "Next.js-\uAE40\uBBFC\uC7AC-sprint12"
[김민재] Sprint12 #723
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 @@ | ||
BASE_URL = https://panda-market-api.vercel.app |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,8 @@ | |
"lint": "next lint" | ||
}, | ||
"dependencies": { | ||
"@tanstack/react-query": "^5.51.4", | ||
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. react-query가 5버젼부터 정말 많이 바꼈는데 4버젼과 비교되는 부분을 한번 봐도 재밌어요! |
||
"@tanstack/react-query-devtools": "^5.51.4", | ||
"axios": "^1.7.2", | ||
"next": "14.2.3", | ||
"react": "18.2.0", | ||
|
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import createQueryParams from "@/utils/createQueryParams"; | ||
import axiosInstance from "../axiosInstance"; | ||
import { GetProductsResponse } from "@/types/products.type"; | ||
|
||
export interface GetProductsParams { | ||
page?: number; | ||
pageSize?: number | string; | ||
orderBy?: "recent" | "favorite"; | ||
} | ||
|
||
const getProducts = async ( | ||
option: GetProductsParams = { | ||
page: 1, | ||
pageSize: 10, | ||
orderBy: "favorite", | ||
} | ||
) => { | ||
const ProductsParams = createQueryParams(option); | ||
try { | ||
const { data } = await axiosInstance.get<GetProductsResponse>( | ||
`/products?${ProductsParams}` | ||
); | ||
return data; | ||
} catch (error) { | ||
console.error(`Failed to fetch data: ${error}`); | ||
throw error; | ||
} | ||
}; | ||
|
||
export default getProducts; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
import { GetProductsResponse } from "@/types/products.type"; | ||
import ProductCard from "./ProductCard"; | ||
|
||
function AllProductList({ | ||
products: { list }, | ||
}: { | ||
products: GetProductsResponse; | ||
}) { | ||
return ( | ||
<div> | ||
{list.map((item, index) => ( | ||
<ProductCard item={item} key={item.id} /> | ||
))} | ||
</div> | ||
Comment on lines
+9
to
+14
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. key값을 id로 한 것 좋아요! |
||
); | ||
} | ||
|
||
export default AllProductList; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,17 @@ | ||||||||||||||||||||||||||
import { GetProductsResponse } from "@/types/products.type"; | ||||||||||||||||||||||||||
import ProductCard from "./ProductCard"; | ||||||||||||||||||||||||||
|
||||||||||||||||||||||||||
function BestProductList({ | ||||||||||||||||||||||||||
products: { list }, | ||||||||||||||||||||||||||
}: { | ||||||||||||||||||||||||||
products: GetProductsResponse; | ||||||||||||||||||||||||||
}) { | ||||||||||||||||||||||||||
return ( | ||||||||||||||||||||||||||
<div> | ||||||||||||||||||||||||||
{list.map((item, index) => ( | ||||||||||||||||||||||||||
<ProductCard item={item} key={item.id} index={index} /> | ||||||||||||||||||||||||||
))} | ||||||||||||||||||||||||||
</div> | ||||||||||||||||||||||||||
Comment on lines
+9
to
+14
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. semantic
Suggested change
시멘틱 태그를 준수하기 위해 ul태그로 추후 바꿔주세요:) |
||||||||||||||||||||||||||
); | ||||||||||||||||||||||||||
} | ||||||||||||||||||||||||||
export default BestProductList; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
import SearchBar from "../shared/SearchBar"; | ||
import SortBy from "../shared/SortBy"; | ||
|
||
function ControlBar() { | ||
return ( | ||
<div> | ||
{/* <SearchBar /> | ||
<button>상품 등록하기</button> | ||
<SortBy sortByText="최애순" /> */} | ||
</div> | ||
); | ||
} | ||
|
||
export default ControlBar; |
Original file line number | Diff line number | Diff line change | ||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,37 @@ | ||||||||||||||||||
import { css } from "@/styled-system/css"; | ||||||||||||||||||
import { Product } from "@/types/products.type"; | ||||||||||||||||||
import favoriteIcon from "@/assets/icons/heart_ic.svg"; | ||||||||||||||||||
import Image from "next/image"; | ||||||||||||||||||
import React from "react"; | ||||||||||||||||||
import { hstack } from "@/styled-system/patterns/hstack.mjs"; | ||||||||||||||||||
// index를 프롭으로 받게되면서 범용성이 많이 떨어짐 전체 상품에서도 쓸만했는데.. | ||||||||||||||||||
function ProductCard({ item, index }: { item: Product; index?: number }) { | ||||||||||||||||||
Comment on lines
+7
to
+8
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. 해당 컴포넌트의 props를 위에 따로 타입으로 빼줘도 좋겠네요!
Suggested change
|
||||||||||||||||||
const { images, name, price, favoriteCount } = item; | ||||||||||||||||||
const imageSize = index ? 343 : 211; | ||||||||||||||||||
return ( | ||||||||||||||||||
<div | ||||||||||||||||||
className={css({ | ||||||||||||||||||
display: { | ||||||||||||||||||
base: typeof index === "number" && index > 0 ? "none" : "block", | ||||||||||||||||||
md: typeof index === "number" && index > 1 ? "none" : "block", | ||||||||||||||||||
xl: "block", | ||||||||||||||||||
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. index > 0값을 따로 변수로 빼 imageSize에도 같이 적용해주면 좋겠네요. |
||||||||||||||||||
}, | ||||||||||||||||||
})} | ||||||||||||||||||
> | ||||||||||||||||||
<Image | ||||||||||||||||||
src={images[0]} | ||||||||||||||||||
alt={`${name}" 사진"`} | ||||||||||||||||||
width={imageSize} | ||||||||||||||||||
height={imageSize} | ||||||||||||||||||
/> | ||||||||||||||||||
<h1>{name}</h1> | ||||||||||||||||||
<p>{price}</p> | ||||||||||||||||||
<div className={hstack()}> | ||||||||||||||||||
<Image src={favoriteIcon} alt="좋아요 아이콘" width={16} height={16} /> | ||||||||||||||||||
<p>{favoriteCount}</p> | ||||||||||||||||||
</div> | ||||||||||||||||||
</div> | ||||||||||||||||||
); | ||||||||||||||||||
} | ||||||||||||||||||
|
||||||||||||||||||
export default ProductCard; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
import { useEffect, useState } from "react"; | ||
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. 좋네요! |
||
import { useMediaQuery } from "react-responsive"; | ||
|
||
interface Responsive { | ||
|
@@ -7,11 +8,21 @@ interface Responsive { | |
} | ||
|
||
function useResponsive(): Responsive { | ||
const isPc = useMediaQuery({ query: "(min-width: 1280px)" }); | ||
const isTablet = useMediaQuery({ | ||
const [isPc, setIsPc] = useState(false); | ||
const [isTablet, setIsTablet] = useState(false); | ||
const [isMobile, setIsMobile] = useState(true); // 초기값을 모바일로 설정 | ||
|
||
const pcQuery = useMediaQuery({ query: "(min-width: 1280px)" }); | ||
const tabletQuery = useMediaQuery({ | ||
query: "(min-width: 768px) and (max-width: 1280px)", | ||
}); | ||
const isMobile = useMediaQuery({ query: "(max-width: 767px)" }); | ||
const mobileQuery = useMediaQuery({ query: "(max-width: 767px)" }); | ||
|
||
useEffect(() => { | ||
setIsPc(pcQuery); | ||
setIsTablet(tabletQuery); | ||
setIsMobile(mobileQuery); | ||
}, [pcQuery, tabletQuery, mobileQuery]); | ||
|
||
return { isPc, isTablet, isMobile }; | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,6 +1,26 @@ | ||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; | ||
import "../css/common/index.css"; | ||
import type { AppProps } from "next/app"; | ||
import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; | ||
import React from "react"; | ||
|
||
export default function App({ Component, pageProps }: AppProps) { | ||
return <Component {...pageProps} />; | ||
const [queryClient] = React.useState( | ||
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. useState를 import해서 쓰셔도 되고 React객체에 직접 접근해서 쓰셔도 되는데 컨벤션 통일을 위해서 하나로 부탁드릴게요. |
||
() => | ||
new QueryClient({ | ||
defaultOptions: { | ||
queries: { | ||
staleTime: 60 * 1000, | ||
gcTime: 60 * 1000 * 10, | ||
}, | ||
}, | ||
}) | ||
); | ||
|
||
return ( | ||
<QueryClientProvider client={queryClient}> | ||
<Component {...pageProps} /> | ||
<ReactQueryDevtools initialIsOpen={false} /> | ||
</QueryClientProvider> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,61 @@ | ||
import getProducts, { GetProductsParams } from "@/apis/product/getProducts"; | ||
import AllProductList from "@/components/itemsComponents/AllProductList"; | ||
import BestProductList from "@/components/itemsComponents/BestProducts"; | ||
import ControlBar from "@/components/itemsComponents/ControlBar"; | ||
import Header from "@/components/shared/Header/Header"; | ||
import { GetProductsResponse } from "@/types/products.type"; | ||
import { useQuery } from "@tanstack/react-query"; | ||
|
||
export async function getServerSideProps(context: any) { | ||
const userAgent = context.req.headers["user-agent"]; | ||
const isMobile = /mobile/i.test(userAgent); | ||
const bestProductsOption: GetProductsParams = { | ||
page: 1, | ||
pageSize: 3, | ||
orderBy: "favorite", | ||
}; | ||
console.log(isMobile); | ||
const allProductsOption: GetProductsParams = { | ||
page: 1, | ||
pageSize: isMobile ? 5 : 10, | ||
orderBy: "recent", | ||
}; | ||
|
||
const bestProducts = await getProducts(bestProductsOption); | ||
const allProducts = await getProducts(allProductsOption); | ||
|
||
Comment on lines
+24
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. 서버사이드의 용도가 이렇게 값들을 조합해서 클라에 내려주는 용도 이기도 해요!🌞 |
||
return { props: { bestProducts, allProducts } }; | ||
} | ||
|
||
function Items({ | ||
bestProducts, | ||
allProducts, | ||
}: { | ||
bestProducts: GetProductsResponse; | ||
allProducts: GetProductsResponse; | ||
}) { | ||
const { data: bestProductsData } = useQuery({ | ||
queryKey: ["bestProductsData"], | ||
queryFn: () => getProducts({ orderBy: "favorite", pageSize: 3, page: 1 }), | ||
initialData: bestProducts, | ||
}); | ||
|
||
const { data: allProductsData } = useQuery({ | ||
queryKey: ["allProductsData"], | ||
queryFn: () => getProducts({ orderBy: "recent", pageSize: 10, page: 1 }), | ||
Comment on lines
+38
to
+45
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. queryKey를 세분화해서ㅏ orderBy, pageSize, page의 값들도 key로 관리하면 추후 데이터 관리에 이점이 있어요~ |
||
initialData: allProducts, | ||
}); | ||
|
||
function Items() { | ||
return ( | ||
<div> | ||
<Header /> | ||
<h1>베스트 상품</h1> | ||
<BestProductList products={bestProductsData} /> | ||
<div> | ||
<h1>판매중인 상품</h1> | ||
<ControlBar /> | ||
<AllProductList products={allProductsData} /> | ||
</div> | ||
</div> | ||
); | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export interface GetProductsResponse { | ||
list: Product[]; | ||
totalCount: number; | ||
} | ||
export interface Product { | ||
createdAt: string; | ||
description: string; | ||
favoriteCount: number; | ||
id: number; | ||
images: string[]; | ||
name: string; | ||
ownerId: number; | ||
price: number; | ||
tags: string[]; | ||
updatedAt: string; | ||
} |
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.
혹시나 이미지를 개발할 떄 이 값을 추가해줘야 하는 원인이 궁금하시면 nextJSImage를 읽어보면 좋아요!