diff --git a/package-lock.json b/package-lock.json index 9f51b7570..0e51b121a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "react-helmet": "^6.1.0", "react-router-dom": "^6.24.0", "react-scripts": "5.0.1", + "styled-components": "^6.1.11", "web-vitals": "^2.1.4" } }, @@ -2272,6 +2273,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -4422,6 +4441,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -5836,6 +5860,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -6271,6 +6303,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -6452,6 +6492,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -6645,9 +6695,9 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -12434,9 +12484,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -13095,9 +13145,9 @@ } }, "node_modules/postcss": { - "version": "8.4.29", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz", - "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -13113,9 +13163,9 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -15552,6 +15602,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15634,9 +15689,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -16047,6 +16102,33 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.11", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.11.tgz", + "integrity": "sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -16062,6 +16144,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", diff --git a/package.json b/package.json index 21f54aa69..5ab37b45d 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "react-helmet": "^6.1.0", "react-router-dom": "^6.24.0", "react-scripts": "5.0.1", + "styled-components": "^6.1.11", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/src/Main.js b/src/Main.js index 8cac6a38d..26ce743f2 100644 --- a/src/Main.js +++ b/src/Main.js @@ -3,6 +3,7 @@ import HomePage from "./pages/HomePage"; import ItemsPage from "./pages/ItemsPage"; import AddItemPage from "./pages/AddItemPage"; import App from "./components/App"; +import ItemPage from "./pages/ItemPage"; function Main() { return ( @@ -10,7 +11,10 @@ function Main() { }> } /> - } /> + + } /> + } /> + } /> diff --git a/src/api/api.js b/src/api/api.js index 111a7d89b..fe48f633f 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -1,6 +1,6 @@ const BASE_URL = "https://panda-market-api.vercel.app"; -async function getProducts({ page = 1, pageSize = 10, orderBy = '', keyword = '' }) { +async function getProducts({ page = 1, pageSize, orderBy = '', keyword = '' }) { const query = `?page=${page}&pageSize=${pageSize}&orderBy=${orderBy}&keyword=${keyword}` const response = await fetch(`${BASE_URL}/products` + query); @@ -22,4 +22,23 @@ async function postProducts(formData) { return result; } -export { getProducts, postProducts }; \ No newline at end of file +async function getProductById(id) { + + const response = await fetch(`${BASE_URL}/products/${id}`) + if (!response.ok) throw new Error("데이터를 불러오는데 실패했습니다."); + const result = await response.json(); + + return result; +} + +async function getComments({ productId, limit = 10, cursor = 0 }) { + + const query = `/products/${productId}/comments?limit=${limit}&cursor=${cursor}`; + const response = await fetch(`${BASE_URL}${query}`); + if (!response.ok) throw new Error("데이터를 불러오는데 실패했습니다."); + const result = await response.json(); + + return result; +} + +export { getProducts, postProducts, getProductById, getComments }; \ No newline at end of file diff --git a/src/assets/Img_inquiry_empty.png b/src/assets/Img_inquiry_empty.png new file mode 100644 index 000000000..586bef6ad Binary files /dev/null and b/src/assets/Img_inquiry_empty.png differ diff --git a/src/assets/ic_back.svg b/src/assets/ic_back.svg new file mode 100644 index 000000000..f8d47f89d --- /dev/null +++ b/src/assets/ic_back.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/ic_sort.svg b/src/assets/ic_sort.svg new file mode 100644 index 000000000..657b44f93 --- /dev/null +++ b/src/assets/ic_sort.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/panda_blog-imagery-_21_Khajitted.jpg b/src/assets/panda_blog-imagery-_21_Khajitted.jpg deleted file mode 100644 index 09ae4ad18..000000000 Binary files a/src/assets/panda_blog-imagery-_21_Khajitted.jpg and /dev/null differ diff --git a/src/components/ErrorScreen.js b/src/components/ErrorScreen.js new file mode 100644 index 000000000..0d4684418 --- /dev/null +++ b/src/components/ErrorScreen.js @@ -0,0 +1,16 @@ +import styles from "./ErrorScreen.module.css"; +import pandaIsConfused from "../assets/Img_inquiry_empty.png" + +function ErrorScreen({ error }) { + + return ( +
+ 열일하는 판다 + {error.message + ? {error.message} + : 요청을 수행하는데 오류가 발생했습니다.} +
+ ) +} + +export default ErrorScreen; \ No newline at end of file diff --git a/src/components/ErrorScreen.module.css b/src/components/ErrorScreen.module.css new file mode 100644 index 000000000..7f70e9660 --- /dev/null +++ b/src/components/ErrorScreen.module.css @@ -0,0 +1,14 @@ +.errorComponent { + width: 100%; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + margin-bottom: 1.5rem; + + .errorMessage { + font-size: 1rem; + font-weight: 400; + color: var(--gray400); + } +} \ No newline at end of file diff --git a/src/components/ItemComments.js b/src/components/ItemComments.js new file mode 100644 index 000000000..765441660 --- /dev/null +++ b/src/components/ItemComments.js @@ -0,0 +1,142 @@ +import { useCallback, useEffect, useState } from "react"; +import styles from "./ItemComments.module.css"; +import useAsync from "../hooks/useAsync"; +import { getComments } from "../api/api"; +import { ReactComponent as GoBackIcon } from '../assets/ic_back.svg'; +import emptyCommentPanda from "../assets/Img_inquiry_empty.png" +import { useNavigate } from "react-router-dom"; +import LoadingErrorHandler from "./LoadingErrorHandler"; +import formatComparedTime from "../utils/formatComparedTime"; + +const COMMENTS_LIMIT = 5; + +function CommentSubmitForm() { + + const [inputContent, setInputContent] = useState(''); + + const handleInputChange = (e) => { + setInputContent(e.target.value); + } + + const handleButtonClick = (e) => { + e.preventDefault(); + } + + const commentPlaceholder = "개인정보를 공유 및 요청하거나, 명예 훼손, 무단 광고, 불법 정보 유포시 모니터링 후 삭제될 수 있으며, 이에 대한 민형사상 책임은 게시자에게 있습니다."; + + return ( +
+ +