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/next.config.mjs b/next.config.mjs index d19b6b74c..0c1302c6a 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -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: "**", + }, ], }, + env: { + BASE_URL: process.env.BASE_URL, + }, }; export default nextConfig; 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/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/apis/product/getProducts.ts b/src/apis/product/getProducts.ts new file mode 100644 index 000000000..0b1f66975 --- /dev/null +++ b/src/apis/product/getProducts.ts @@ -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( + `/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/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/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..43ca08bba 100644 --- a/src/pages/_app.tsx +++ b/src/pages/_app.tsx @@ -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 ; + const [queryClient] = React.useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + staleTime: 60 * 1000, + gcTime: 60 * 1000 * 10, + }, + }, + }) + ); + + return ( + + + + + ); } diff --git a/src/pages/items/index.tsx b/src/pages/items/index.tsx index 4b7d322bb..5a9353796 100644 --- a/src/pages/items/index.tsx +++ b/src/pages/items/index.tsx @@ -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); + + 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 }), + initialData: allProducts, + }); -function Items() { 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; +}