From 0e0a85ea1ce73c9edc021e7534ea896f7236adb3 Mon Sep 17 00:00:00 2001 From: seongminn Date: Wed, 15 Feb 2023 14:06:13 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20fetcher=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 1 + src/App.tsx | 23 ++++++++++++++++++++-- src/config/queryClient.ts | 22 +++++++++++++++++++++ src/constants/baseUrl.ts | 1 + src/constants/queryKeys.ts | 3 +++ src/hook/queries/fetcher.ts | 38 +++++++++++++++++++++++++++++++++++++ 6 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 src/config/queryClient.ts create mode 100644 src/constants/baseUrl.ts create mode 100644 src/constants/queryKeys.ts create mode 100644 src/hook/queries/fetcher.ts diff --git a/package.json b/package.json index 5ad8d5c..7586621 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@emotion/react": "^11.10.5", "@emotion/styled": "^11.10.5", "@types/node": "^18.11.19", + "axios": "^1.3.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-query": "^3.39.3", diff --git a/src/App.tsx b/src/App.tsx index bf6dbcf..c340ebc 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,24 @@ +import { BrowserRouter, Route, Routes } from 'react-router-dom'; +import { QueryClientProvider } from 'react-query'; +import { getClient } from '@/config/queryClient'; +import { Home, ProductDetail, ProductList } from '@/pages'; +import { Header } from '@/components/Header'; + +const queryClient = getClient(); + function App() { - return
hello rentive
+ return ( + + +
+ + } /> + } /> + } /> + + + + ); } -export default App +export default App; diff --git a/src/config/queryClient.ts b/src/config/queryClient.ts new file mode 100644 index 0000000..a2052f2 --- /dev/null +++ b/src/config/queryClient.ts @@ -0,0 +1,22 @@ +import { QueryClient } from 'react-query'; + +export const getClient = (() => { + let client: QueryClient | null = null; + + return () => { + if (!client) + client = new QueryClient({ + defaultOptions: { + queries: { + cacheTime: 1000 * 60 * 60 * 24, // query 인스턴스가 unmount된 후 캐시 데이터를 유지하는 시간 + staleTime: 1000, // 데이터가 mount된 후, stale 상태로 변경되기까지의 시간 + refetchOnMount: false, // 쿼리에 새 인스턴스가 마운트될 때 + refetchOnReconnect: false, // 네트워크가 끊어졌다 다시 연결될 때 + refetchOnWindowFocus: false, // Window에 다시 Focus 될 때 + }, + }, + }); + + return client; + }; +})(); diff --git a/src/constants/baseUrl.ts b/src/constants/baseUrl.ts new file mode 100644 index 0000000..39402a9 --- /dev/null +++ b/src/constants/baseUrl.ts @@ -0,0 +1 @@ +export const BASE_URL = 'http://39.118.50.38:400'; diff --git a/src/constants/queryKeys.ts b/src/constants/queryKeys.ts new file mode 100644 index 0000000..012dd68 --- /dev/null +++ b/src/constants/queryKeys.ts @@ -0,0 +1,3 @@ +export const QueryKeys = { + PRODUCT: 'PRODUCT', +}; diff --git a/src/hook/queries/fetcher.ts b/src/hook/queries/fetcher.ts new file mode 100644 index 0000000..9db5ae3 --- /dev/null +++ b/src/hook/queries/fetcher.ts @@ -0,0 +1,38 @@ +import axios from 'axios'; + +type AnyOBJ = { [key: string]: any }; + +// const BASE_URL = `39.118.50.38:400`; + +export async function fetcher({ + method, + path, + params = {}, + body = {}, +}: { + method: 'get' | 'post' | 'put' | 'delete' | 'patch'; + path: string; + params?: AnyOBJ; + body?: AnyOBJ; +}) { + try { + const url = `${path}`; + + const { data } = await axios({ + url, + method, + data: { body }, + params: { params }, + withCredentials: true, + headers: { + 'Content-Type': 'application/json', + }, + }); + + return data; + } catch (err) { + console.error(err); + + return null; + } +} From 975743c50b6445a66a73eb6f4ebeb317301a9829 Mon Sep 17 00:00:00 2001 From: seongminn Date: Thu, 16 Feb 2023 15:23:32 +0900 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20Product=20List=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit API 작업이 진행중이라서 `MOCK DATA`를 생성하여 작업하였음 --- public/mockdata.json | 162 +++++++++++++++++++++++++ src/App.tsx | 2 + src/components/Product/ProductCard.tsx | 54 +++++++++ src/components/Product/index.ts | 1 + src/pages/ProductList.tsx | 61 ++++++++++ 5 files changed, 280 insertions(+) create mode 100644 public/mockdata.json create mode 100644 src/components/Product/ProductCard.tsx create mode 100644 src/components/Product/index.ts create mode 100644 src/pages/ProductList.tsx diff --git a/public/mockdata.json b/public/mockdata.json new file mode 100644 index 0000000..d8f556d --- /dev/null +++ b/public/mockdata.json @@ -0,0 +1,162 @@ +[ + { + "id": 1, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66f604e7b0e6900f9ac53a43965300eb9a", + "title": "test1", + "text": "test1", + "category": "test", + "limitDate": 7 + }, + { + "id": 2, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66effd194bae87d73dd00522794070855d", + "title": "test2", + "text": "test2", + "category": "test", + "limitDate": 7 + }, + { + "id": 3, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66b3a18fdf58bc66ec3f4b6084b7d0b570", + "title": "test3", + "text": "test3", + "category": "test", + "limitDate": 7 + }, + { + "id": 4, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e668f324a0b9c48f77dbce3a43bd11ce785", + "title": "test4", + "text": "test4", + "category": "test", + "limitDate": 7 + }, + { + "id": 5, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66effd194bae87d73dd00522794070855d", + "title": "test5", + "text": "test5", + "category": "test", + "limitDate": 7 + }, + { + "id": 6, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e669cbcbe2de7f4969efc79ab353e0c19e8", + "title": "test6", + "text": "test6", + "category": "test", + "limitDate": 7 + }, + { + "id": 7, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e6671b950f0a05bd8dba8b11dbe535ebe4b", + "title": "test7", + "text": "test7", + "category": "test", + "limitDate": 7 + }, + { + "id": 8, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66f604e7b0e6900f9ac53a43965300eb9a", + "title": "test8", + "text": "test8", + "category": "test", + "limitDate": 7 + }, + { + "id": 9, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e669f5287469802eca457586a25a096fd31", + "title": "test9", + "text": "test9", + "category": "test", + "limitDate": 7 + }, + { + "id": 10, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66f604e7b0e6900f9ac53a43965300eb9a", + "title": "test10", + "text": "test10", + "category": "test", + "limitDate": 7 + }, + { + "id": 11, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66f604e7b0e6900f9ac53a43965300eb9a", + "title": "test11", + "text": "test11", + "category": "test", + "limitDate": 7 + }, + { + "id": 12, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e668b566dca82634c93f811198148a26065", + "title": "test12", + "text": "test12", + "category": "test", + "limitDate": 7 + }, + { + "id": 13, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66f604e7b0e6900f9ac53a43965300eb9a", + "title": "test13", + "text": "test13", + "category": "test", + "limitDate": 7 + }, + { + "id": 14, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66b3a18fdf58bc66ec3f4b6084b7d0b570", + "title": "test14", + "text": "test14", + "category": "test", + "limitDate": 7 + }, + { + "id": 15, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66effd194bae87d73dd00522794070855d", + "title": "test15", + "text": "test15", + "category": "test", + "limitDate": 7 + }, + { + "id": 16, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e669cbcbe2de7f4969efc79ab353e0c19e8", + "title": "test16", + "text": "test16", + "category": "test", + "limitDate": 7 + }, + { + "id": 17, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e668f324a0b9c48f77dbce3a43bd11ce785", + "title": "test17", + "text": "test17", + "category": "test", + "limitDate": 7 + }, + { + "id": 18, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e668b566dca82634c93f811198148a26065", + "title": "test18", + "text": "test18", + "category": "test", + "limitDate": 7 + }, + { + "id": 19, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66b3a18fdf58bc66ec3f4b6084b7d0b570", + "title": "test19", + "text": "test19", + "category": "test", + "limitDate": 7 + }, + { + "id": 20, + "imageUrl": "https://item.kakaocdn.net/do/493188dee481260d5c89790036be0e66c37d537a8f2c6f426591be6b8dc7b36a", + "title": "test20", + "text": "test20", + "category": "test", + "limitDate": 7 + } +] diff --git a/src/App.tsx b/src/App.tsx index c340ebc..9764196 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,6 @@ import { BrowserRouter, Route, Routes } from 'react-router-dom'; import { QueryClientProvider } from 'react-query'; +import { ReactQueryDevtools } from 'react-query/devtools'; import { getClient } from '@/config/queryClient'; import { Home, ProductDetail, ProductList } from '@/pages'; import { Header } from '@/components/Header'; @@ -16,6 +17,7 @@ function App() { } /> } /> + ); diff --git a/src/components/Product/ProductCard.tsx b/src/components/Product/ProductCard.tsx new file mode 100644 index 0000000..74a7926 --- /dev/null +++ b/src/components/Product/ProductCard.tsx @@ -0,0 +1,54 @@ +import { Link } from 'react-router-dom'; +import styled from '@emotion/styled'; +import { Product } from '@/pages/ProductList'; + +const StyledList = styled.li` + display: flex; + justify-content: center; + align-items: center; + + & a:visited, + a { + color: #000; + } + + & .product-list__image-bg { + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background-color: #eaeaea; + border-radius: 5px; + margin: 1rem 0 0.3rem 0; + } + + & .product-list__image { + } + + & .product-list__title { + font-weight: 600; + margin-bottom: 0.5rem; + } +`; + +function ProductCard({ + id, + title, + imageUrl, + limitDate, +}: Omit) { + return ( + + +
+ {title} +
+

{title}

+

{limitDate}일 후 대여 가능

+ +
+ ); +} + +export default ProductCard; diff --git a/src/components/Product/index.ts b/src/components/Product/index.ts new file mode 100644 index 0000000..46e83ef --- /dev/null +++ b/src/components/Product/index.ts @@ -0,0 +1 @@ +export { default as ProductCard } from './ProductCard'; diff --git a/src/pages/ProductList.tsx b/src/pages/ProductList.tsx new file mode 100644 index 0000000..5041eab --- /dev/null +++ b/src/pages/ProductList.tsx @@ -0,0 +1,61 @@ +import { useQuery } from 'react-query'; +import { useSearchParams } from 'react-router-dom'; +import styled from '@emotion/styled'; +import { QueryKeys } from '../constants/queryKeys'; +import { fetcher } from '../hook/queries/fetcher'; +import { ProductCard } from '@/components/Product'; + +export type ProductPostType = { + title: string; + text: string; + imageUrl: string; + category: string; + limitDate: number; +}; +export type Product = ProductPostType & { id: number; favorite: number }; + +const ProductListWrapper = styled.ul` + margin: 0 auto; + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 10px; + place-items: center; + + & li { + list-style: none; + } + + & img { + margin: 0; + } +`; + +function ProductList() { + const [searchParams] = useSearchParams(); + const page = Number(searchParams.get('page')) as number; + + if (!page) return null; + + const { data: productData } = useQuery(QueryKeys.PRODUCT, () => + fetcher({ + method: 'get', + path: `http://localhost:5173/mockdata.json`, + }), + ); + + return ( + + {productData?.map(({ title, imageUrl, limitDate, id }) => ( + + ))} + + ); +} + +export default ProductList;