diff --git a/.env b/.env new file mode 100644 index 000000000..44e96575b --- /dev/null +++ b/.env @@ -0,0 +1,2 @@ +# timeago warning 해결 +GENERATE_SOURCEMAP=false diff --git a/package-lock.json b/package-lock.json index 9aaf27d11..282c2cd2f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,9 @@ "react-responsive": "^10.0.0", "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", + "react-timeago": "^7.2.0", "styled-components": "^6.1.13", + "timeago.js": "^4.0.2", "web-vitals": "^2.1.4" } }, @@ -669,7 +671,7 @@ }, "node_modules/@babel/plugin-proposal-private-property-in-object": { "version": "7.21.0-placeholder-for-preset-env.2", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-env.2.tgz", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.21.0-placeholder-for-preset-2.tgz", "integrity": "sha512-SOSkfJDddaM7mak6cPEpswyTRnuRltl429hMraQEglW+OkovnCzsiszTmsrlY//qLFjCpQDFRvjdm2wA5pPm9w==", "license": "MIT", "engines": { @@ -14486,6 +14488,15 @@ } } }, + "node_modules/react-timeago": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/react-timeago/-/react-timeago-7.2.0.tgz", + "integrity": "sha512-2KsBEEs+qRhKx/kekUVNSTIpop3Jwd7SRBm0R4Eiq3mPeswRGSsftY9FpKsE/lXLdURyQFiHeHFrIUxLYskG5g==", + "license": "MIT", + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -16570,6 +16581,12 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "license": "MIT" }, + "node_modules/timeago.js": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/timeago.js/-/timeago.js-4.0.2.tgz", + "integrity": "sha512-a7wPxPdVlQL7lqvitHGGRsofhdwtkoSXPGATFuSOA2i1ZNQEPLrGnj68vOp2sOJTCFAQVXPeNMX/GctBaO9L2w==", + "license": "MIT" + }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", diff --git a/package.json b/package.json index 2f15f28da..3941ade99 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "react-responsive": "^10.0.0", "react-router-dom": "^6.28.0", "react-scripts": "5.0.1", + "react-timeago": "^7.2.0", "styled-components": "^6.1.13", "web-vitals": "^2.1.4" }, diff --git a/public/index.html b/public/index.html index c608c495a..910a669c4 100644 --- a/public/index.html +++ b/public/index.html @@ -12,14 +12,17 @@ - - + + 판다마켓 diff --git a/src/Api/api.js b/src/Api/api.js index 5c4ebcd7b..42a4a972a 100644 --- a/src/Api/api.js +++ b/src/Api/api.js @@ -1,8 +1,9 @@ -async function getProductData({ orderBy, pageSize, search, page }) { - const query = `page=${page}&orderBy=${orderBy}&pageSize=${pageSize}&keyword=${search}`; - const response = await fetch( - `https://panda-market-api.vercel.app/products?${query}` - ); +const BASE_URL = "https://panda-market-api.vercel.app"; + +// 베스트/전체 상품 리스트 +async function getProductData(params = {}) { + const query = new URLSearchParams(params).toString(); + const response = await fetch(`${BASE_URL}/products?${query}`); if (!response.ok) { throw new Error("상품을 불러오는 데 실패했습니다."); @@ -11,4 +12,57 @@ async function getProductData({ orderBy, pageSize, search, page }) { return body; } +// 디테일 상품 정보 +export async function getProductId(productId, setProductData) { + const response = await fetch(`${BASE_URL}/products/${productId}`); + + try { + const body = await response.json(); + setProductData(body); + } catch (error) { + console.log(error); + } + + if (!response.ok) { + throw new Error("정보를 불러오는 데 실패했습니다."); + } +} + +// 디테일 댓글 +export async function getComments(productId, setCommentsData) { + const response = await fetch( + `${BASE_URL}/products/${productId}/comments?limit=10` + ); + try { + const body = await response.json(); + setCommentsData(body); + } catch (error) { + console.log(error); + } + + if (!response.ok) { + throw new Error("정보를 불러오는 데 실패했습니다."); + } +} + +// 디테일 댓글 등록 +// 로그인 기능 구현 전이므로 코멘트 API POST 구현 보류 (24.12.08) +// export async function createComment(productId, comment) { +// const surveyData = { +// content: comment, +// }; +// const response = await fetch(`${BASE_URL}/products/${productId}/comments`, { +// method: "POST", +// // 자바스크립트 객체를 JSON 문자열로 변환하여 post 보내기 +// headers: { +// "Content-Type": "application/json", +// accept: "application/json", +// Authorization: +// "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6NTE3LCJzY29wZSI6ImFjY2VzcyIsImlhdCI6MTczMzU2MzU1NCwiZXhwIjoxNzMzNTY1MzU0LCJpc3MiOiJzcC1wYW5kYS1tYXJrZXQifQ.Wpof71osRiwDAxNq34xcc4JGYqkEb_KaXYUKVc3Pz3M", +// }, +// body: JSON.stringify(surveyData), +// }); +// const data = await response.json(); +// } + export default getProductData; diff --git a/src/Assets/images/app/button/btn_back.svg b/src/Assets/images/app/button/btn_back.svg new file mode 100644 index 000000000..253a47d7b --- /dev/null +++ b/src/Assets/images/app/button/btn_back.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/Assets/images/productDetail/default_profile.svg b/src/Assets/images/productDetail/default_profile.svg new file mode 100644 index 000000000..21e1ed3ea --- /dev/null +++ b/src/Assets/images/productDetail/default_profile.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/images/productDetail/empty_comment.svg b/src/Assets/images/productDetail/empty_comment.svg new file mode 100644 index 000000000..714640a5d --- /dev/null +++ b/src/Assets/images/productDetail/empty_comment.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/src/Assets/images/productDetail/favorite.svg b/src/Assets/images/productDetail/favorite.svg new file mode 100644 index 000000000..e59db2bc4 --- /dev/null +++ b/src/Assets/images/productDetail/favorite.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/Assets/images/productDetail/option_menu.svg b/src/Assets/images/productDetail/option_menu.svg new file mode 100644 index 000000000..dd7ed7f5e --- /dev/null +++ b/src/Assets/images/productDetail/option_menu.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/Components/App/Footer.js b/src/Components/App/Footer.js index c023fae2b..23a5f6879 100644 --- a/src/Components/App/Footer.js +++ b/src/Components/App/Footer.js @@ -1,9 +1,9 @@ -import styles from "../../Styles/App/Footer.module.css"; import { Link } from "react-router-dom"; -import iconInsta from "../../Assets/images/app/home/ic_instagram.svg"; -import iconFacebook from "../../Assets/images/app/home/ic_facebook.svg"; -import iconYoutube from "../../Assets/images/app/home/ic_youtube.svg"; -import iconTwitter from "../../Assets/images/app/home/ic_twitter.svg"; +import styles from "../../styles/app/footer.module.css"; +import iconInsta from "../../assets/images/app/home/ic_instagram.svg"; +import iconFacebook from "../../assets/images/app/home/ic_facebook.svg"; +import iconYoutube from "../../assets/images/app/home/ic_youtube.svg"; +import iconTwitter from "../../assets/images/app/home/ic_twitter.svg"; function Footer() { return ( diff --git a/src/Components/App/ItemListNav.js b/src/Components/App/ItemsListNav.js similarity index 85% rename from src/Components/App/ItemListNav.js rename to src/Components/App/ItemsListNav.js index 5e616f787..91b3cfa42 100644 --- a/src/Components/App/ItemListNav.js +++ b/src/Components/App/ItemsListNav.js @@ -1,7 +1,7 @@ -import styles from "../../Styles/App/Navi.module.css"; -import navLogo from "../../Assets/images/app/navi/logo.svg"; -import profileDefaultImg from "../../Assets/images/app/navi/profile_default.png"; import { Link, NavLink } from "react-router-dom"; +import styles from "../../styles/app/navi.module.css"; +import navLogoImg from "../../assets/images/app/navi/logo.svg"; +import profileDefaultImg from "../../assets/images/app/navi/profile_default.png"; function getLinkStyle({ isActive }) { return { @@ -17,7 +17,7 @@ function ItemListNav() {
- 판다마켓 + 판다마켓

판다마켓

diff --git a/src/Components/App/Loading.js b/src/Components/App/Loading.js index ca6af766d..1174f4580 100644 --- a/src/Components/App/Loading.js +++ b/src/Components/App/Loading.js @@ -1,5 +1,5 @@ -import styles from "../../Styles/App/Loading.module.css"; import React from "react"; +import styles from "../../styles/app/loading.module.css"; export const Loading = () => { return ( diff --git a/src/Components/App/Pagination.js b/src/Components/App/Pagination.js index a430c1d15..b8d6e1f12 100644 --- a/src/Components/App/Pagination.js +++ b/src/Components/App/Pagination.js @@ -1,16 +1,28 @@ -import styles from "../../Styles/App/Pagination.module.css"; -import arrowPrevImg from "../../Assets/images/app/pagination/arrow_left.svg"; -import arrowNextImg from "../../Assets/images/app/pagination/arrow_right.svg"; +import { calculatePagination } from "../../utils/calculatorPagination"; +import styles from "../../styles/app/pagination.module.css"; +import arrowPrevImg from "../../assets/images/app/pagination/arrow_left.svg"; +import arrowNextImg from "../../assets/images/app/pagination/arrow_right.svg"; function PaginationContainer({ page, setPage, pageCount, isDataCount }) { const itemCountPerPage = Math.ceil(pageCount / isDataCount); // 페이지 당 보여줄 데이터 개수 - const btnPage = 5; // 한 페이지당 pagination 5개 출력 - const currentSet = Math.ceil(page / btnPage); - const totalPages = Math.ceil(pageCount / isDataCount); + const ITEMS_PER_PAGINATION = 5; // 한 페이지당 pagination 5개 출력 + + const { totalPages, currentSet, startPage, endPage } = calculatePagination({ + page, + pageCount, + isDataCount, + ITEMS_PER_PAGINATION, + }); + const noPrev = page === 1; const noNext = page + itemCountPerPage - 1 >= totalPages; - const startPage = (currentSet - 1) * btnPage + 1; - const endPage = Math.min(startPage + btnPage - 1, totalPages); + + function generatePageNumbers(startPage, endPage) { + return Array.from( + { length: endPage - startPage + 1 }, + (_, i) => startPage + i + ); + } return (