Skip to content

[김민재] 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

Open
wants to merge 3 commits into
base: Next.js-김민재
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
BASE_URL = https://panda-market-api.vercel.app
19 changes: 16 additions & 3 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,24 @@
const nextConfig = {
reactStrictMode: true,
images: {
domains: [
"sprint-fe-project.s3.ap-northeast-2.amazonaws.com",
"example.com",
remotePatterns: [
{
protocol: "https",
hostname: "sprint-fe-project.s3.ap-northeast-2.amazonaws.com",
port: "",
pathname: "**",
},
{
protocol: "https",
hostname: "example.com",
port: "",
pathname: "**",
},
Comment on lines +5 to +17
Copy link
Collaborator

Choose a reason for hiding this comment

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

혹시나 이미지를 개발할 떄 이 값을 추가해줘야 하는 원인이 궁금하시면 nextJSImage를 읽어보면 좋아요!

],
},
env: {
BASE_URL: process.env.BASE_URL,
},
};

export default nextConfig;
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
"lint": "next lint"
},
"dependencies": {
"@tanstack/react-query": "^5.51.4",
Copy link
Collaborator

Choose a reason for hiding this comment

The 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",
Expand Down
38 changes: 38 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/apis/article/getArticles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ interface GetArticlesParams {
const getArticles = async (
option: GetArticlesParams = {
page: 1,
pageSize: "",
pageSize: 10,
orderBy: "like",
keyword: "",
}
Expand Down
4 changes: 2 additions & 2 deletions src/apis/axiosInstance.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import axios, { AxiosRequestConfig } from "axios";
// git push origin Next.js-김민재-sprint10 실수하지않을려고 push config설정 안했는데 브랜치이름 너무 치기어려워서 넣었습니다.

const axiosConfig: AxiosRequestConfig = {
baseURL: "https://panda-market-api.vercel.app/",
baseURL: process.env.BASE_URL,
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Expand Down
30 changes: 30 additions & 0 deletions src/apis/product/getProducts.ts
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;
4 changes: 1 addition & 3 deletions src/components/addBoardComponents/FormTitle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ function FormTitle({ isValid, handleSubmit }: FormTitleProps) {
<div className={hstack({ justifyContent: "space-between" })}>
<h2 className={subTitle}>게시글 쓰기</h2>
<button
className={buttonRecipe(
isValid ? { visual: "small" } : { visual: "smallDisabled" }
)}
className={buttonRecipe({ visual: isValid ? "small" : "large" })}
onClick={handleSubmit}
>
글쓰기
Expand Down
18 changes: 18 additions & 0 deletions src/components/itemsComponents/AllProductList.tsx
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

key값을 id로 한 것 좋아요!
다만 추후에 비슷한 성질의 데이터를 map할때 모두 id로 돌리면 key값 중복이 되있으므로 리터럴 문자로 id값과 uniuqe한 string의 조합으로 해주시면 좋아요!

);
}

export default AllProductList;
17 changes: 17 additions & 0 deletions src/components/itemsComponents/BestProducts.tsx
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

semantic

Suggested change
return (
<div>
{list.map((item, index) => (
<ProductCard item={item} key={item.id} index={index} />
))}
</div>
return (
<ul>
{list.map((item, index) => (
<ProductCard item={item} key={item.id} index={index} />
))}
</ul>

시멘틱 태그를 준수하기 위해 ul태그로 추후 바꿔주세요:)

);
}
export default BestProductList;
14 changes: 14 additions & 0 deletions src/components/itemsComponents/ControlBar.tsx
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;
37 changes: 37 additions & 0 deletions src/components/itemsComponents/ProductCard.tsx
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
Copy link
Collaborator

Choose a reason for hiding this comment

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

해당 컴포넌트의 props를 위에 따로 타입으로 빼줘도 좋겠네요!

Suggested change
// index를 프롭으로 받게되면서 범용성이 많이 떨어짐 전체 상품에서도 쓸만했는데..
function ProductCard({ item, index }: { item: Product; index?: number }) {
interface ProductCardProps {
item: Product;
index?: number
}
function ProductCard({ item, index }: ProductCardProps) {

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",
Copy link
Collaborator

Choose a reason for hiding this comment

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

index > 0값을 따로 변수로 빼 imageSize에도 같이 적용해주면 좋겠네요.
자바스크립트의 이상한 동작때문에 추후 빈스트링이이나 빈 배열은 Truthy한 값이라 추후 런타임 에러를 발생할 확률이 높아져요

},
})}
>
<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;
17 changes: 14 additions & 3 deletions src/hooks/useResponsive.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { useEffect, useState } from "react";
Copy link
Collaborator

Choose a reason for hiding this comment

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

좋네요!
다만 화면의 반응형은 주로 클라이언트에서 적용되니 use client를 붙여주면 추후 런타임에러가 발생하지 않을 것 같아요.

import { useMediaQuery } from "react-responsive";

interface Responsive {
Expand All @@ -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 };
}
Expand Down
22 changes: 21 additions & 1 deletion src/pages/_app.tsx
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(
Copy link
Collaborator

Choose a reason for hiding this comment

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

useState를 import해서 쓰셔도 되고 React객체에 직접 접근해서 쓰셔도 되는데 컨벤션 통일을 위해서 하나로 부탁드릴게요.
만약 사용하시지 않는다면 리액트 16.8버젼 이상부터는 따로 React를 import 하지 않아도 동작해요

() =>
new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000,
gcTime: 60 * 1000 * 10,
},
},
})
);

return (
<QueryClientProvider client={queryClient}>
<Component {...pageProps} />
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
54 changes: 53 additions & 1 deletion src/pages/items/index.tsx
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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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
Copy link
Collaborator

Choose a reason for hiding this comment

The 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>
);
}
Expand Down
16 changes: 16 additions & 0 deletions src/types/products.type.ts
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;
}