From e76eab8b7edd01789a73901abb32aaada5186c08 Mon Sep 17 00:00:00 2001 From: unknown Date: Tue, 2 Jul 2024 22:09:50 +0900 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EA=B2=BD=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Main.js | 6 +++++- src/pages/ItemPage.js | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 src/pages/ItemPage.js 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/pages/ItemPage.js b/src/pages/ItemPage.js new file mode 100644 index 000000000..3f228150f --- /dev/null +++ b/src/pages/ItemPage.js @@ -0,0 +1,12 @@ +import Header from "../components/header/Header"; + +function ItemPage() { + + return( + <> +
+ + ) +} + +export default ItemPage; \ No newline at end of file From 45dc1f54ec9645edd2fdd6395e462b83ceb5ccb6 Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 4 Jul 2024 00:09:53 +0900 Subject: [PATCH 2/8] =?UTF-8?q?feat:=20=EC=83=81=ED=92=88=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=EC=99=80=20=EB=8B=B9=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=EC=9D=98=20=EC=95=84=EC=9D=B4=ED=85=9C=20=EC=A0=95?= =?UTF-8?q?=EB=B3=B4=20=EC=84=B9=EC=85=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 115 +++++++++++++++++++++++++---- package.json | 1 + src/api/api.js | 11 ++- src/components/ItemInfo.js | 39 ++++++++++ src/components/ItemInfo.module.css | 104 ++++++++++++++++++++++++++ src/components/main/Card.css | 9 +++ src/components/main/Card.js | 3 +- src/components/styles.js | 8 ++ src/pages/ItemPage.js | 36 +++++++++ 9 files changed, 310 insertions(+), 16 deletions(-) create mode 100644 src/components/ItemInfo.js create mode 100644 src/components/ItemInfo.module.css create mode 100644 src/components/styles.js 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/api/api.js b/src/api/api.js index 111a7d89b..4904c74c4 100644 --- a/src/api/api.js +++ b/src/api/api.js @@ -22,4 +22,13 @@ 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; +} + +export { getProducts, postProducts, getProductById }; \ No newline at end of file diff --git a/src/components/ItemInfo.js b/src/components/ItemInfo.js new file mode 100644 index 000000000..6d2b2fa28 --- /dev/null +++ b/src/components/ItemInfo.js @@ -0,0 +1,39 @@ +import styles from "./ItemInfo.module.css" +import { ReactComponent as HeartIcon } from '../assets/ic_heart.svg'; + +function ItemInfo({ item }) { + + return ( +
+ {item.name} +
+
+ {item.name} + {item.price.toLocaleString()}원 +
+
+
+ 상품 소개 +
{item.description}
+
+
+ 상품 태그 +
    + {item.tags.map((tag, index) => { + return (
  • #{tag}
  • ) + })} +
+
+
+
+ +
+
+
+ ) +} + +export default ItemInfo; \ No newline at end of file diff --git a/src/components/ItemInfo.module.css b/src/components/ItemInfo.module.css new file mode 100644 index 000000000..c6aa1f7f3 --- /dev/null +++ b/src/components/ItemInfo.module.css @@ -0,0 +1,104 @@ +.itemInfo { + width: 100%; + min-height: 30.375rem; + padding: 1.5rem 0 2rem; + border-bottom: solid 1px var(--gray200); + display: flex; + justify-content: space-between; + gap: 1.5rem; + + .itemImage { + height: 100%; + max-width: 30.375rem; + border-radius: 1rem; + } + + .itemContents { + flex-grow: 1; + display: flex; + flex-direction: column; + + .itemHeader { + padding-bottom: 1rem; + border-bottom: solid 1px var(--gray200); + display: flex; + flex-direction: column; + gap: 1rem; + + .itemName { + font-size: 1.5rem; + font-weight: 600; + color: var(--gray800); + } + + .itemPrice { + font-size: 2.5rem; + font-weight: 600; + color: var(--gray800); + } + } + + .itemBody { + padding-top: 1rem; + display: flex; + flex-direction: column; + gap: 1.5rem; + flex-grow: 1; + + .itemBodyCategory { + font-size: 0.875rem; + font-weight: 500; + color: var(--gray600); + margin-bottom: 0.5rem; + display: block; + } + + .itemBodyDescription { + font-size: 1rem; + font-weight: 400; + color: var(--gray800); + } + + .itemBodyTagList { + list-style: none; + padding: 0; + margin: 0; + display: flex; + gap: 0.5rem; + + .itemBodyTag { + height: 2.25rem; + font-size: 1rem; + font-weight: 400; + color: var(--gray800); + padding: 0.375rem 1rem; + background-color: var(--gray100); + border-radius: 1.625rem; + &:hover { + cursor: default; + } + } + } + } + + .favoriteButton { + background-color: #fff; + border: 1px solid var(--gray200); + border-radius: 2.25rem; + display: flex; + justify-content: center; + align-items: center; + padding: 0.25rem 0.75rem; + gap: 0.25rem; + + font-size: 1rem; + font-weight: 500; + color: var(--gray500); + + .heartIcon { + width: 2rem; + height: 2rem; + } + } + } +} \ No newline at end of file diff --git a/src/components/main/Card.css b/src/components/main/Card.css index 6fe90c38b..c8546333a 100644 --- a/src/components/main/Card.css +++ b/src/components/main/Card.css @@ -14,6 +14,15 @@ main .card.item-name { font-size: 0.875rem; font-weight: 500; color: var(--gray800); + text-decoration: none; +} + +main .card.item-name a { + color: var(--gray800); +} + +main .card.item-name:visited { + color: var(--gray800); } main .card.item-price { diff --git a/src/components/main/Card.js b/src/components/main/Card.js index d36ac3470..76f2de345 100644 --- a/src/components/main/Card.js +++ b/src/components/main/Card.js @@ -1,5 +1,6 @@ import './Card.css'; import { ReactComponent as HeartIcon } from '../../assets/ic_heart.svg'; +import { Link } from 'react-router-dom'; function Card({item}) { @@ -8,7 +9,7 @@ function Card({item}) { return ( <> {item.name} - {item.name} + {item.name} {price}원 {item.favoriteCount} diff --git a/src/components/styles.js b/src/components/styles.js new file mode 100644 index 000000000..66a940a22 --- /dev/null +++ b/src/components/styles.js @@ -0,0 +1,8 @@ +import styled from "styled-components"; + +const Container = styled.div` + width: 75rem; + margin: 0 auto; +` + +export { Container } \ No newline at end of file diff --git a/src/pages/ItemPage.js b/src/pages/ItemPage.js index 3f228150f..b4b894d4b 100644 --- a/src/pages/ItemPage.js +++ b/src/pages/ItemPage.js @@ -1,10 +1,46 @@ +import { Navigate, useParams } from "react-router-dom"; import Header from "../components/header/Header"; +import { getProductById } from "../api/api"; +import ItemInfo from "../components/ItemInfo"; +import useAsync from "../hooks/useAsync"; +import { useCallback, useEffect, useState } from "react"; +import { Container } from "../components/styles"; + +const INITIAL_ITEM_VALUE = { + "id": 0, + "name": '', + "description": '', + "price": 0, + "tags": [], + "images": [], + "favoriteCount": 0, + "isFavorite": false, +} function ItemPage() { + const { itemId } = useParams(); + const [item, setItem] = useState(INITIAL_ITEM_VALUE); + const [isLoading, error, getProductByIdAsync] = useAsync(getProductById); + + const handleLoad = useCallback (async () => { + const result = await getProductByIdAsync(itemId); + + setItem(result); + }, [getProductByIdAsync, itemId]) + + useEffect(() => { + handleLoad(); + }, [handleLoad]); + + if(!item) {return } + return( <>
+ + + ) } From cf97d2a0adae299073df755e77bbae5ea4e83d4b Mon Sep 17 00:00:00 2001 From: unknown Date: Thu, 4 Jul 2024 05:06:20 +0900 Subject: [PATCH 3/8] =?UTF-8?q?feat:=20=EB=8C=93=EA=B8=80=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=20=ED=8F=BC=20=EA=B5=AC=ED=98=84(=EB=A6=AC=ED=80=98?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EB=AF=B8=EA=B5=AC=ED=98=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ItemComments.js | 51 +++++++++++++++++++++ src/components/ItemComments.module.css | 62 ++++++++++++++++++++++++++ src/components/ItemInfo.module.css | 1 + src/pages/ItemPage.js | 2 + 4 files changed, 116 insertions(+) create mode 100644 src/components/ItemComments.js create mode 100644 src/components/ItemComments.module.css diff --git a/src/components/ItemComments.js b/src/components/ItemComments.js new file mode 100644 index 000000000..e9388a588 --- /dev/null +++ b/src/components/ItemComments.js @@ -0,0 +1,51 @@ +import { useState } from "react"; +import styles from "./ItemComments.module.css"; + +function CommentSubmitForm() { + + const [inputContent, setInputContent] = useState(''); + + const handleInputChange = (e) => { + setInputContent(e.target.value); + } + + const handleButtonClick = (e) => { + e.preventDefault(); + } + + const commentPlaceholder = "개인정보를 공유 및 요청하거나, 명예 훼손, 무단 광고, 불법 정보 유포시 모니터링 후 삭제될 수 있으며, 이에 대한 민형사상 책임은 게시자에게 있습니다."; + + return ( +
+ +