From 4057b00d92b731a11d0b9040973060ce2705aaed Mon Sep 17 00:00:00 2001 From: Darkthos Date: Fri, 19 Jul 2024 18:55:20 +0900 Subject: [PATCH 1/3] feat: reactQuery --- .env | 1 + package.json | 2 + pnpm-lock.yaml | 38 +++++++++++++++++++ src/apis/article/getArticles.ts | 2 +- src/apis/product/getProducts.ts | 30 +++++++++++++++ .../addBoardComponents/FormTitle.tsx | 4 +- src/components/itemsComponents/AllProduct.tsx | 5 +++ .../itemsComponents/BestProducts.tsx | 4 ++ src/components/itemsComponents/ControlBar.tsx | 14 +++++++ src/hooks/useResponsive.ts | 17 +++++++-- src/pages/_app.tsx | 21 +++++++++- src/pages/items/index.tsx | 28 +++++++++++++- 12 files changed, 157 insertions(+), 9 deletions(-) create mode 100644 .env create mode 100644 src/apis/product/getProducts.ts create mode 100644 src/components/itemsComponents/AllProduct.tsx create mode 100644 src/components/itemsComponents/BestProducts.tsx create mode 100644 src/components/itemsComponents/ControlBar.tsx diff --git a/.env b/.env new file mode 100644 index 000000000..e5a5d097b --- /dev/null +++ b/.env @@ -0,0 +1 @@ +BASE_URL = https://panda-market-api.vercel.app \ No newline at end of file diff --git a/package.json b/package.json index 7706f3e38..9cee67da5 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,8 @@ "lint": "next lint" }, "dependencies": { + "@tanstack/react-query": "^5.51.4", + "@tanstack/react-query-devtools": "^5.51.4", "axios": "^1.7.2", "next": "14.2.3", "react": "18.2.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 39926fa18..cc2294c93 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,6 +8,12 @@ importers: .: dependencies: + '@tanstack/react-query': + specifier: ^5.51.4 + version: 5.51.4(react@18.2.0) + '@tanstack/react-query-devtools': + specifier: ^5.51.4 + version: 5.51.4(@tanstack/react-query@5.51.4(react@18.2.0))(react@18.2.0) axios: specifier: ^1.7.2 version: 1.7.2 @@ -429,6 +435,23 @@ packages: '@swc/helpers@0.5.5': resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==} + '@tanstack/query-core@5.51.4': + resolution: {integrity: sha512-KdXY86vf+1+rUaDkasEgZJx0uTaqJieAFUWECLoic2DNZrH7hqi6WvuTUE+OVRGj+u4PgMwOIv9blb3M5TEkwg==} + + '@tanstack/query-devtools@5.51.1': + resolution: {integrity: sha512-rehG0WmL3EXER6MAI2uHQia/n0b5c3ZROohpYm7u3G7yg4q+HsfQy6nuAo6uy40NzHUe3FmnfWCZQ0Vb/3lE6g==} + + '@tanstack/react-query-devtools@5.51.4': + resolution: {integrity: sha512-D++OQ4+5P4xOZCPUdSn0IhYDSyQb3JJCPSamyFpc33lt3MnKwPIFvZ1XX9vsDbcPY+I/oiE2eD49f7J4qVIkqg==} + peerDependencies: + '@tanstack/react-query': ^5.51.4 + react: ^18 || ^19 + + '@tanstack/react-query@5.51.4': + resolution: {integrity: sha512-GkEZ/AfG+ExLhvTazZ0jTGRmKCGYCZOhVdXjpzjB+jGRfRPCuvQvKNZ3fod4Qa326Nc5E9KOepHXt56aKJTIxA==} + peerDependencies: + react: ^18.0.0 + '@ts-morph/common@0.22.0': resolution: {integrity: sha512-HqNBuV/oIlMKdkLshXd1zKBqNQCsuPEsgQOkfFQ/eUKjRlwndXW1AjN9LVkBEIukm00gGXSRmfkl0Wv5VXLnlw==} @@ -2498,6 +2521,21 @@ snapshots: '@swc/counter': 0.1.3 tslib: 2.6.2 + '@tanstack/query-core@5.51.4': {} + + '@tanstack/query-devtools@5.51.1': {} + + '@tanstack/react-query-devtools@5.51.4(@tanstack/react-query@5.51.4(react@18.2.0))(react@18.2.0)': + dependencies: + '@tanstack/query-devtools': 5.51.1 + '@tanstack/react-query': 5.51.4(react@18.2.0) + react: 18.2.0 + + '@tanstack/react-query@5.51.4(react@18.2.0)': + dependencies: + '@tanstack/query-core': 5.51.4 + react: 18.2.0 + '@ts-morph/common@0.22.0': dependencies: fast-glob: 3.3.2 diff --git a/src/apis/article/getArticles.ts b/src/apis/article/getArticles.ts index 1b1bea45b..616612be7 100644 --- a/src/apis/article/getArticles.ts +++ b/src/apis/article/getArticles.ts @@ -22,7 +22,7 @@ interface GetArticlesParams { const getArticles = async ( option: GetArticlesParams = { page: 1, - pageSize: "", + pageSize: 10, orderBy: "like", keyword: "", } diff --git a/src/apis/product/getProducts.ts b/src/apis/product/getProducts.ts new file mode 100644 index 000000000..61b9acf99 --- /dev/null +++ b/src/apis/product/getProducts.ts @@ -0,0 +1,30 @@ +import createQueryParams from "@/utils/createQueryParams"; +import axiosInstance from "../axiosInstance"; +import { GetArticlesResponse } from "@/types/articles"; + +interface GetProductsParams { + page?: number; + pageSize?: number | string; + orderBy?: "recent" | "favorite"; +} + +const getProducts = async ( + option: GetProductsParams = { + page: 1, + pageSize: 10, + orderBy: "favorite", + } +): Promise => { + const ProductsParams = createQueryParams(option); + try { + const { data } = await axiosInstance.get( + `/products?${ProductsParams}` + ); + return data; + } catch (error) { + console.error(`Failed to fetch data: ${error}`); + throw error; + } +}; + +export default getProducts; diff --git a/src/components/addBoardComponents/FormTitle.tsx b/src/components/addBoardComponents/FormTitle.tsx index e88d4a6ae..ef21d829a 100644 --- a/src/components/addBoardComponents/FormTitle.tsx +++ b/src/components/addBoardComponents/FormTitle.tsx @@ -13,9 +13,7 @@ function FormTitle({ isValid, handleSubmit }: FormTitleProps) {

게시글 쓰기

+ +
+ ); +} + +export default ControlBar; diff --git a/src/hooks/useResponsive.ts b/src/hooks/useResponsive.ts index 924ebad16..cf7b4d05b 100644 --- a/src/hooks/useResponsive.ts +++ b/src/hooks/useResponsive.ts @@ -1,3 +1,4 @@ +import { useEffect, useState } from "react"; 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 }; } diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index fef9ae5c0..258eb6ae4 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -1,6 +1,25 @@ +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 ; + const [queryClient] = React.useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + }, + }, + }) + ); + + return ( + + + + + ); } diff --git a/src/pages/items/index.tsx b/src/pages/items/index.tsx index 4b7d322bb..2384d51f0 100644 --- a/src/pages/items/index.tsx +++ b/src/pages/items/index.tsx @@ -1,9 +1,35 @@ +import getArticles from "@/apis/article/getArticles"; +import getProducts from "@/apis/product/getProducts"; +import AllProduct from "@/components/itemsComponents/AllProduct"; +import BestProducts from "@/components/itemsComponents/BestProducts"; +import ControlBar from "@/components/itemsComponents/ControlBar"; import Header from "@/components/shared/Header/Header"; +import { GetArticlesResponse } from "@/types/articles"; +import { useQuery } from "@tanstack/react-query"; -function Items() { +export async function getServerSideProps() { + const Products = await getProducts(); + return { props: { Products } }; +} + +function Items({ Products }: { Products: GetArticlesResponse }) { + const { data } = useQuery({ + queryKey: ["posts"], + queryFn: () => getProducts(), // 리액트 쿼리 수업이나 들어라 ㅋㅋㅋ + initialData: Products, + }); + + console.log(data); return (
+

베스트 상품

+ +
+

전체 상품

+ +
+
); } From 149030b19953b51dd413fbac8cc0897724e749a4 Mon Sep 17 00:00:00 2001 From: Darkthos Date: Sat, 20 Jul 2024 03:06:53 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=EA=B8=B0=EB=B3=B8=ED=8B=80?= =?UTF-8?q?=EC=9D=80=20=EB=A7=8C=EB=93=A4=EC=96=B4=EB=86=93=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- next.config.mjs | 16 ++++- src/apis/product/getProducts.ts | 8 +-- src/components/itemsComponents/AllProduct.tsx | 5 -- .../itemsComponents/AllProductList.tsx | 18 ++++++ .../itemsComponents/BestProducts.tsx | 19 +++++- .../itemsComponents/ProductCard.tsx | 37 ++++++++++++ src/pages/_app.tsx | 1 + src/pages/items/index.tsx | 60 +++++++++++++------ src/types/products.type.ts | 16 +++++ 9 files changed, 148 insertions(+), 32 deletions(-) delete mode 100644 src/components/itemsComponents/AllProduct.tsx create mode 100644 src/components/itemsComponents/AllProductList.tsx create mode 100644 src/components/itemsComponents/ProductCard.tsx create mode 100644 src/types/products.type.ts diff --git a/next.config.mjs b/next.config.mjs index d19b6b74c..758b62bbe 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -2,9 +2,19 @@ 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: "**", + }, ], }, }; diff --git a/src/apis/product/getProducts.ts b/src/apis/product/getProducts.ts index 61b9acf99..0b1f66975 100644 --- a/src/apis/product/getProducts.ts +++ b/src/apis/product/getProducts.ts @@ -1,8 +1,8 @@ import createQueryParams from "@/utils/createQueryParams"; import axiosInstance from "../axiosInstance"; -import { GetArticlesResponse } from "@/types/articles"; +import { GetProductsResponse } from "@/types/products.type"; -interface GetProductsParams { +export interface GetProductsParams { page?: number; pageSize?: number | string; orderBy?: "recent" | "favorite"; @@ -14,10 +14,10 @@ const getProducts = async ( pageSize: 10, orderBy: "favorite", } -): Promise => { +) => { const ProductsParams = createQueryParams(option); try { - const { data } = await axiosInstance.get( + const { data } = await axiosInstance.get( `/products?${ProductsParams}` ); return data; diff --git a/src/components/itemsComponents/AllProduct.tsx b/src/components/itemsComponents/AllProduct.tsx deleted file mode 100644 index 07ddab010..000000000 --- a/src/components/itemsComponents/AllProduct.tsx +++ /dev/null @@ -1,5 +0,0 @@ -function AllProduct() { - return
나는 상품이야!
; -} - -export default AllProduct; diff --git a/src/components/itemsComponents/AllProductList.tsx b/src/components/itemsComponents/AllProductList.tsx new file mode 100644 index 000000000..412dcba75 --- /dev/null +++ b/src/components/itemsComponents/AllProductList.tsx @@ -0,0 +1,18 @@ +import { GetProductsResponse } from "@/types/products.type"; +import ProductCard from "./ProductCard"; + +function AllProductList({ + products: { list }, +}: { + products: GetProductsResponse; +}) { + return ( +
+ {list.map((item, index) => ( + + ))} +
+ ); +} + +export default AllProductList; diff --git a/src/components/itemsComponents/BestProducts.tsx b/src/components/itemsComponents/BestProducts.tsx index 9e13d65d7..bb6e86d09 100644 --- a/src/components/itemsComponents/BestProducts.tsx +++ b/src/components/itemsComponents/BestProducts.tsx @@ -1,4 +1,17 @@ -function BestProducts() { - return
ghkdsa
; +import { GetProductsResponse } from "@/types/products.type"; +import ProductCard from "./ProductCard"; + +function BestProductList({ + products: { list }, +}: { + products: GetProductsResponse; +}) { + return ( +
+ {list.map((item, index) => ( + + ))} +
+ ); } -export default BestProducts; +export default BestProductList; diff --git a/src/components/itemsComponents/ProductCard.tsx b/src/components/itemsComponents/ProductCard.tsx new file mode 100644 index 000000000..685d112c2 --- /dev/null +++ b/src/components/itemsComponents/ProductCard.tsx @@ -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 }) { + const { images, name, price, favoriteCount } = item; + const imageSize = index ? 343 : 211; + return ( +
0 ? "none" : "block", + md: typeof index === "number" && index > 1 ? "none" : "block", + xl: "block", + }, + })} + > + {`${name}" +

{name}

+

{price}

+
+ 좋아요 아이콘 +

{favoriteCount}

+
+
+ ); +} + +export default ProductCard; diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx index 258eb6ae4..43ca08bba 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -11,6 +11,7 @@ export default function App({ Component, pageProps }: AppProps) { defaultOptions: { queries: { staleTime: 60 * 1000, + gcTime: 60 * 1000 * 10, }, }, }) diff --git a/src/pages/items/index.tsx b/src/pages/items/index.tsx index 2384d51f0..5a9353796 100644 --- a/src/pages/items/index.tsx +++ b/src/pages/items/index.tsx @@ -1,35 +1,61 @@ -import getArticles from "@/apis/article/getArticles"; -import getProducts from "@/apis/product/getProducts"; -import AllProduct from "@/components/itemsComponents/AllProduct"; -import BestProducts from "@/components/itemsComponents/BestProducts"; +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 { GetArticlesResponse } from "@/types/articles"; +import { GetProductsResponse } from "@/types/products.type"; import { useQuery } from "@tanstack/react-query"; -export async function getServerSideProps() { - const Products = await getProducts(); - return { props: { Products } }; +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); + + return { props: { bestProducts, allProducts } }; } -function Items({ Products }: { Products: GetArticlesResponse }) { - const { data } = useQuery({ - queryKey: ["posts"], - queryFn: () => getProducts(), // 리액트 쿼리 수업이나 들어라 ㅋㅋㅋ - initialData: Products, +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 }), + initialData: allProducts, }); - console.log(data); return (

베스트 상품

- +
-

전체 상품

+

판매중인 상품

+
-
); } diff --git a/src/types/products.type.ts b/src/types/products.type.ts new file mode 100644 index 000000000..bd2811c79 --- /dev/null +++ b/src/types/products.type.ts @@ -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; +} From 3496976c500510d7381b2b2a80b49c0cf06acb38 Mon Sep 17 00:00:00 2001 From: Darkthos Date: Sat, 20 Jul 2024 03:14:44 +0900 Subject: [PATCH 3/3] fix: build error --- next.config.mjs | 3 +++ src/apis/axiosInstance.ts | 4 ++-- src/components/itemsComponents/ControlBar.tsx | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/next.config.mjs b/next.config.mjs index 758b62bbe..0c1302c6a 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -17,6 +17,9 @@ const nextConfig = { }, ], }, + env: { + BASE_URL: process.env.BASE_URL, + }, }; export default nextConfig; diff --git a/src/apis/axiosInstance.ts b/src/apis/axiosInstance.ts index 582d23b57..d23c448e9 100644 --- a/src/apis/axiosInstance.ts +++ b/src/apis/axiosInstance.ts @@ -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", diff --git a/src/components/itemsComponents/ControlBar.tsx b/src/components/itemsComponents/ControlBar.tsx index d6d360614..38d5304b5 100644 --- a/src/components/itemsComponents/ControlBar.tsx +++ b/src/components/itemsComponents/ControlBar.tsx @@ -4,9 +4,9 @@ import SortBy from "../shared/SortBy"; function ControlBar() { return (
- + {/* - + */}
); }