diff --git a/package-lock.json b/package-lock.json index a1e590ee..f77685f8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,9 +11,14 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "eslint-config-react-app": "^7.0.1", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^7.1.1", "react-scripts": "5.0.1", + "styled-components": "^6.1.14", + "styled-reset": "^4.5.2", "web-vitals": "^2.1.4" } }, @@ -2270,6 +2275,27 @@ "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==", + "license": "MIT", + "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==", + "license": "MIT" + }, + "node_modules/@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==", + "license": "MIT" + }, "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", @@ -3953,6 +3979,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "8.44.2", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.44.2.tgz", @@ -4412,6 +4444,12 @@ "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==", + "license": "MIT" + }, "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", @@ -5826,6 +5864,15 @@ "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==", + "license": "MIT", + "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", @@ -6261,6 +6308,15 @@ "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==", + "license": "ISC", + "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", @@ -6442,6 +6498,17 @@ "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==", + "license": "MIT", + "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", @@ -6635,9 +6702,10 @@ "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==", + "license": "MIT" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -7337,6 +7405,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/eslint-config-react-app/-/eslint-config-react-app-7.0.1.tgz", "integrity": "sha512-K6rNzvkIeHaTd8m/QEh1Zko0KI7BACWkkneSs6s9cKZC/J27X3eZR6Upt1jkmZ/4FK+XUOPPxMEN7+lbUXfSlA==", + "license": "MIT", "dependencies": { "@babel/core": "^7.16.0", "@babel/eslint-parser": "^7.16.3", @@ -12086,7 +12155,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.debounce": { "version": "4.0.8", @@ -12424,15 +12494,16 @@ } }, "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.8", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", + "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", "funding": [ { "type": "github", "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "bin": { "nanoid": "bin/nanoid.cjs" }, @@ -13085,9 +13156,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", @@ -13102,10 +13173,11 @@ "url": "https://github.com/sponsors/ai" } ], + "license": "MIT", "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" @@ -14671,6 +14743,55 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.1.tgz", + "integrity": "sha512-39sXJkftkKWRZ2oJtHhCxmoCrBCULr/HAH4IT5DHlgu/Q0FCPV0S4Lx+abjDTx/74xoZzNYDYbOZWlJjruyuDQ==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.1.tgz", + "integrity": "sha512-vSrQHWlJ5DCfyrhgo0k6zViOe9ToK8uT5XGSmnuC2R3/g261IdIMpZVqfjD6vWSXdnf5Czs4VA/V60oVR6/jnA==", + "license": "MIT", + "dependencies": { + "react-router": "7.1.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -15480,11 +15601,23 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "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==", + "license": "MIT" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -15567,9 +15700,10 @@ } }, "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.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -15980,6 +16114,46 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.14", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.14.tgz", + "integrity": "sha512-KtfwhU5jw7UoxdM0g6XU9VZQFV4do+KrM8idiVCH5h4v49W+3p3yMe0icYwJgZQZepa5DbH04Qv8P0/RdcLcgg==", + "license": "MIT", + "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/styled-reset": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/styled-reset/-/styled-reset-4.5.2.tgz", + "integrity": "sha512-dbAaaVEhweBs2FGfqGBdW6oMcMK8238C2X5KCxBhUQJX92m/QyUfzRADOXhdXiXNkIPELtMCd72YY9eCdORfIw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "styled-components": ">=4.0.0 || >=5.0.0 || >=6.0.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -15995,6 +16169,12 @@ "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==", + "license": "MIT" + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -16512,6 +16692,12 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 7ff0d6b5..e5f77843 100644 --- a/package.json +++ b/package.json @@ -6,9 +6,14 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "eslint-config-react-app": "^7.0.1", + "lodash": "^4.17.21", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-router-dom": "^7.1.1", "react-scripts": "5.0.1", + "styled-components": "^6.1.14", + "styled-reset": "^4.5.2", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/public/favicon.ico b/public/favicon.ico deleted file mode 100644 index a11777cc..00000000 Binary files a/public/favicon.ico and /dev/null differ diff --git a/public/index.html b/public/index.html index aa069f27..50b0fc64 100644 --- a/public/index.html +++ b/public/index.html @@ -2,42 +2,10 @@ - - - - - - - - React App + 판다마켓 -
- diff --git a/public/logo192.png b/public/logo192.png deleted file mode 100644 index fc44b0a3..00000000 Binary files a/public/logo192.png and /dev/null differ diff --git a/public/logo512.png b/public/logo512.png deleted file mode 100644 index a4e47a65..00000000 Binary files a/public/logo512.png and /dev/null differ diff --git a/public/manifest.json b/public/manifest.json deleted file mode 100644 index 080d6c77..00000000 --- a/public/manifest.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "short_name": "React App", - "name": "Create React App Sample", - "icons": [ - { - "src": "favicon.ico", - "sizes": "64x64 32x32 24x24 16x16", - "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" - } - ], - "start_url": ".", - "display": "standalone", - "theme_color": "#000000", - "background_color": "#ffffff" -} diff --git a/public/robots.txt b/public/robots.txt deleted file mode 100644 index e9e57dc4..00000000 --- a/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/src/App.css b/src/App.css deleted file mode 100644 index 74b5e053..00000000 --- a/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/src/App.js b/src/App.js deleted file mode 100644 index 37845757..00000000 --- a/src/App.js +++ /dev/null @@ -1,25 +0,0 @@ -import logo from './logo.svg'; -import './App.css'; - -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} - -export default App; diff --git a/src/App.jsx b/src/App.jsx new file mode 100644 index 00000000..5d401251 --- /dev/null +++ b/src/App.jsx @@ -0,0 +1,40 @@ +import { createGlobalStyle } from "styled-components"; +import Routers from "./components/Routers"; +import reset from "styled-reset"; +import React from "react"; + +const GlobalStyle = createGlobalStyle` + ${reset} + + *{ + text-decoration: none; + list-style: none; + box-sizing: border-box; + } + @font-face { + font-family: 'Pretendard-Regular'; + src: url('https://fastly.jsdelivr.net/gh/Project-Noonnu/noonfonts_2107@1.1/Pretendard-Regular.woff') format('woff'); + font-weight: 400; + font-style: normal; +} +@font-face { + font-family: 'ROKAF_Sans_Bold'; + src: url('./fonts/ROKAF_Sans_Bold.ttf') format('woff2'); + font-weight: 700; + font-size: normal; +} + body{ + font-family: Pretendard-Regular,ROKAF_Sans_Bold; + } +`; + +function App() { + return ( + + + + + ); +} + +export default App; diff --git a/src/App.test.js b/src/App.test.js deleted file mode 100644 index 1f03afee..00000000 --- a/src/App.test.js +++ /dev/null @@ -1,8 +0,0 @@ -import { render, screen } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - render(); - const linkElement = screen.getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/src/apis/api.js b/src/apis/api.js new file mode 100644 index 00000000..ba2fa388 --- /dev/null +++ b/src/apis/api.js @@ -0,0 +1,7 @@ +const baseUrl = "https://panda-market-api.vercel.app"; + +export const getItems = async (url) => { + const response = await fetch(`${baseUrl}${url}`); + const body = response.json(); + return body; +}; diff --git a/src/assets/images/heartImg.svg b/src/assets/images/heartImg.svg new file mode 100644 index 00000000..cad016c1 --- /dev/null +++ b/src/assets/images/heartImg.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/pandaLogo.svg b/src/assets/images/pandaLogo.svg new file mode 100644 index 00000000..e6848692 --- /dev/null +++ b/src/assets/images/pandaLogo.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/images/searchImg.svg b/src/assets/images/searchImg.svg new file mode 100644 index 00000000..d9cc31ea --- /dev/null +++ b/src/assets/images/searchImg.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/sortImg.svg b/src/assets/images/sortImg.svg new file mode 100644 index 00000000..657b44f9 --- /dev/null +++ b/src/assets/images/sortImg.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/userImg.svg b/src/assets/images/userImg.svg new file mode 100644 index 00000000..04b6503d --- /dev/null +++ b/src/assets/images/userImg.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/components/AddProductButton.jsx b/src/components/AddProductButton.jsx new file mode 100644 index 00000000..27e07ab1 --- /dev/null +++ b/src/components/AddProductButton.jsx @@ -0,0 +1,30 @@ +import React from "react"; +import { useNavigate } from "react-router-dom"; +import styled from "styled-components"; + +const AddProductButton = () => { + const navigate = useNavigate(); + + const handleMoveAddProduct = () => { + navigate("/additem"); + }; + + return ( + + 상품 등록하기 + + ); +}; + +export default AddProductButton; + +const AddProductButtonStyle = styled.span` + padding: 8px 23px; + background-color: #3692ff; + border-radius: 8px; + color: #f3f4f6; + font-size: 16px; + font-weight: 300; + line-height: 26px; + cursor: pointer; +`; diff --git a/src/components/Cards.jsx b/src/components/Cards.jsx new file mode 100644 index 00000000..0b667e80 --- /dev/null +++ b/src/components/Cards.jsx @@ -0,0 +1,121 @@ +import React from "react"; +import styled from "styled-components"; +import heartImg from "../assets/images/heartImg.svg"; + +const Cards = ({ items, browserWidth, name }) => { + return ( + + {items.map((item) => { + return ( + + + + {item.name} + {item.price} + + + {item.favoriteCount} + + + + ); + })} + + ); +}; + +export default Cards; + +const ProductsListWrapper = styled.ul` + display: flex; + width: ${({ browserWidth }) => { + if (767 < browserWidth && browserWidth < 1200) { + return "696px"; + } + if (374 < browserWidth && browserWidth < 768) { + return "344px"; + } + return "1200px"; + }}; + flex-wrap: wrap; + gap: ${({ browserWidth }) => { + if (374 < browserWidth && browserWidth < 1200) { + if (browserWidth < 767) { + return "8px"; + } + return "10px"; + } + + return "23px"; + }}; +`; + +const ProductsList = styled.li` + display: flex; + flex-direction: column; + gap: 10px; +`; + +const ProductsImg = styled.img` + width: ${({ browserWidth, name }) => { + if (browserWidth > 1200 && name === "all") { + return "221px"; + } + if (374 < browserWidth && browserWidth < 1200) { + if (name === "all") { + if (browserWidth < 768) { + return "168px"; + } + return "221px"; + } + return "343px"; + } + + return "282px"; + }}; + height: ${({ browserWidth, name }) => { + if (browserWidth > 1200 && name === "all") { + return "221px"; + } + if (374 < browserWidth && browserWidth < 1200) { + if (name === "all") { + if (browserWidth < 768) { + return "168px"; + } + return "221px"; + } + return "343px"; + } + return "282px"; + }}; + border-radius: 16px; +`; + +const ProductsInformationWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 6px; +`; + +const ProductInformation = styled.span` + font-size: 14px; + font-weight: 500; + line-height: 24px; + color: #1f2937; +`; + +const FavoriteWrapper = styled.div` + display: flex; + align-items: center; + gap: 4px; +`; + +const FavoriteImg = styled.img` + width: 16px; + height: 16px; +`; diff --git a/src/components/DropdownProduct.jsx b/src/components/DropdownProduct.jsx new file mode 100644 index 00000000..8ffb7841 --- /dev/null +++ b/src/components/DropdownProduct.jsx @@ -0,0 +1,97 @@ +import React, { useState } from "react"; +import styled from "styled-components"; +import sortImg from "../assets/images/sortImg.svg"; + +const DropdownProduct = ({ onSortOrderChange, browserWidth }) => { + const [isOpen, setIsOpen] = useState(false); + const [selectedValue, setSelectedValue] = useState("최신순"); + + const handleToggle = () => setIsOpen((prev) => !prev); + const handleSelect = (optionValue, optionLabel) => { + setSelectedValue(optionLabel); + setIsOpen(false); + onSortOrderChange(optionValue); + }; + + return ( + + + {browserWidth < 768 ? ( + + ) : ( + selectedValue + )} + + + handleSelect("orderByLatest", "최신순")}> + 최신순 + + handleSelect("orderByFavoriteCount", "좋아요순")} + > + 좋아요순 + + + + ); +}; + +export default DropdownProduct; + +const DropdownWrapper = styled.div` + position: relative; + display: inline-block; + width: ${({ browserWidth }) => (browserWidth < 768 ? "42px" : "130px")}; + height: 42px; +`; + +const DropdownButton = styled.button` + width: 100%; + height: 100%; + border: 1px solid #e5e7eb; + border-radius: 10px; + background-color: #ffffff; + font-size: 14px; + color: #1f2937; + cursor: pointer; + text-align: left; + + &:after { + content: ${({ browserWidth }) => (browserWidth < 768 ? "" : "▼")}; + float: right; + font-size: 12px; + } +`; + +const DropdownMenu = styled.ul` + position: absolute; + top: 100%; + right: 0; + width: 130px; + border: 1px solid #e5e7eb; + border-radius: 10px; + background-color: #ffffff; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); + list-style: none; + padding: 0; + margin: 5px 0; + display: ${({ open }) => (open ? "block" : "none")}; + z-index: 1; +`; + +const DropdownItem = styled.li` + padding: 10px; + font-size: 14px; + border-radius: 10px; + color: #1f2937; + cursor: pointer; + + &:hover { + background-color: #f1f1f1; + } +`; + +const SortImg = styled.img` + width: 24px; + height: 24px; +`; diff --git a/src/components/Footer.jsx b/src/components/Footer.jsx new file mode 100644 index 00000000..6c261a9f --- /dev/null +++ b/src/components/Footer.jsx @@ -0,0 +1,7 @@ +import React from "react"; + +const Footer = () => { + return
Footer
; +}; + +export default Footer; diff --git a/src/components/Header.jsx b/src/components/Header.jsx new file mode 100644 index 00000000..6e663f43 --- /dev/null +++ b/src/components/Header.jsx @@ -0,0 +1,80 @@ +import React from "react"; +import logoImg from "../assets/images/pandaLogo.svg"; +import userImg from "../assets/images/userImg.svg"; +import styled from "styled-components"; +import responsive from "../utils/responsive"; + +const Header = () => { + return ( + + ); +}; + +export default Header; + +const Nav = styled.nav` + padding: 10px 200px; + @media ${({ responsive }) => responsive.device.tablet} { + padding: 10px 24px; + } + @media ${({ responsive }) => responsive.device.mobile} { + padding: 10px 16px; + } + + display: flex; + position: sticky; + top: 0; + left: 0; + right: 0; + justify-content: space-between; + align-items: center; + border-bottom: 1px solid #dfdfdf; + z-index: 1; + background-color: #ffffff; +`; + +const NavList = styled.div` + display: flex; + gap: 32px; + align-items: center; +`; + +const LogoWrapper = styled.div` + display: flex; + align-items: center; + gap: 8px; +`; + +const HeaderTitle = styled.span` + font-family: ROKAF_Sans_Bold; + font-size: 25px; + font-weight: 700; + line-height: 34px; + color: #3692ff; +`; + +const NavItemWrapper = styled.div` + display: flex; + gap: 32px; +`; + +const NavListItem = styled.a` + font-size: 18px; + font-weight: 700; + line-height: 26px; + text-align: center; + color: #4b5563; +`; diff --git a/src/components/Layout.jsx b/src/components/Layout.jsx new file mode 100644 index 00000000..3ddfd749 --- /dev/null +++ b/src/components/Layout.jsx @@ -0,0 +1,29 @@ +import React from "react"; +import { Outlet } from "react-router-dom"; +import Header from "./Header"; +import styled from "styled-components"; + +const Layout = () => { + return ( + +
+
+ +
+ + ); +}; + +export default Layout; + +const LayoutStyle = styled.div` + display: flex; + flex-direction: column; +`; + +const Main = styled.main` + display: flex; + align-items: center; + justify-content: center; + padding-top: 24px; +`; diff --git a/src/components/Pagination.jsx b/src/components/Pagination.jsx new file mode 100644 index 00000000..1bec0143 --- /dev/null +++ b/src/components/Pagination.jsx @@ -0,0 +1,45 @@ +import React from "react"; +import styled from "styled-components"; + +const Pagination = () => { + return ( + + {`<`} + 1 + 2 + 3 + 4 + 5 + {`>`} + + ); +}; + +export default Pagination; + +const PaginationWrapper = styled.ul` + display: flex; + align-items: center; + justify-content: center; + padding: 43px 0 58px; + gap: 4px; +`; +const PaginationItem = styled.li` + display: flex; + align-items: center; + justify-content: center; + width: 40px; + height: 40px; + border-radius: 40px; + border: 1px solid #e5e7eb; + color: #6b7280; + font-weight: 600; + background-color: transparent; + + &:hover { + color: #f9fafb; + background-color: #2f80ed; + border-color: #2f80ed; + cursor: pointer; + } +`; diff --git a/src/components/Routers.jsx b/src/components/Routers.jsx new file mode 100644 index 00000000..4c0970b8 --- /dev/null +++ b/src/components/Routers.jsx @@ -0,0 +1,22 @@ +import React from "react"; +import { BrowserRouter, Route, Routes } from "react-router-dom"; +import Item from "../pages/Items"; +import AddItem from "../pages/AddItem"; +import Layout from "./Layout"; +import FreeBoard from "../pages/FreeBoard"; + +const Routers = () => { + return ( + + + }> + } /> + } /> + } /> + + + + ); +}; + +export default Routers; diff --git a/src/components/SearchProducts.jsx b/src/components/SearchProducts.jsx new file mode 100644 index 00000000..3b8c146b --- /dev/null +++ b/src/components/SearchProducts.jsx @@ -0,0 +1,48 @@ +import React from "react"; +import searchImg from "../assets/images/searchImg.svg"; +import styled from "styled-components"; + +const SearchProducts = ({ onFilterItems, browserWidth }) => { + const handleFilterItems = (e) => { + onFilterItems(e.target.value); + }; + + return ( + + 검색 이미지 + + + ); +}; + +export default SearchProducts; + +const SearchInputWrapper = styled.div` + display: flex; + width: ${({ browserWidth }) => { + if (767 < browserWidth && browserWidth < 1200) { + return "242px"; + } + if (374 < browserWidth && browserWidth < 767) { + return "288px"; + } + + return "325px"; + }}; + gap: 8px; + align-items: center; + background-color: #f3f4f6; + padding: 9px 16px; + border-radius: 12px; +`; + +const SearchInput = styled.input` + border: none; + background-color: transparent; + min-width: 202px; + outline: none; +`; diff --git a/src/components/Utils.jsx b/src/components/Utils.jsx new file mode 100644 index 00000000..8d6e044a --- /dev/null +++ b/src/components/Utils.jsx @@ -0,0 +1,26 @@ +import React from "react"; +import styled from "styled-components"; +import SearchProducts from "./SearchProducts"; +import DropdownProduct from "./DropdownProduct"; +import AddProductButton from "./AddProductButton"; + +const Utils = ({ onFilterItems, onSortOrderChange, browserWidth }) => { + return ( + + + + + + ); +}; + +export default Utils; + +const Wrapper = styled.div` + display: flex; + gap: 12px; + align-items: center; +`; diff --git a/src/fonts/ROKAF_Sans_Bold.ttf b/src/fonts/ROKAF_Sans_Bold.ttf new file mode 100644 index 00000000..ed84f350 Binary files /dev/null and b/src/fonts/ROKAF_Sans_Bold.ttf differ diff --git a/src/fonts/ROKAF_Sans_Medium.ttf b/src/fonts/ROKAF_Sans_Medium.ttf new file mode 100644 index 00000000..ac1f2841 Binary files /dev/null and b/src/fonts/ROKAF_Sans_Medium.ttf differ diff --git a/src/index.css b/src/index.css deleted file mode 100644 index ec2585e8..00000000 --- a/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/src/index.js b/src/index.js index d563c0fb..71a354b9 100644 --- a/src/index.js +++ b/src/index.js @@ -1,17 +1,6 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import './index.css'; -import App from './App'; -import reportWebVitals from './reportWebVitals'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render( - - - -); - -// If you want to start measuring performance in your app, pass a function -// to log results (for example: reportWebVitals(console.log)) -// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals -reportWebVitals(); +const root = ReactDOM.createRoot(document.getElementById("root")); +root.render(); diff --git a/src/logo.svg b/src/logo.svg deleted file mode 100644 index 9dfc1c05..00000000 --- a/src/logo.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/src/pages/AddItem.jsx b/src/pages/AddItem.jsx new file mode 100644 index 00000000..fc696bdb --- /dev/null +++ b/src/pages/AddItem.jsx @@ -0,0 +1,7 @@ +import React from "react"; + +const AddItem = () => { + return
AddItem
; +}; + +export default AddItem; diff --git a/src/pages/FreeBoard.jsx b/src/pages/FreeBoard.jsx new file mode 100644 index 00000000..07fd231e --- /dev/null +++ b/src/pages/FreeBoard.jsx @@ -0,0 +1,7 @@ +import React from "react"; + +const FreeBoard = () => { + return
FreeBoard
; +}; + +export default FreeBoard; diff --git a/src/pages/Items.jsx b/src/pages/Items.jsx new file mode 100644 index 00000000..64f72dd0 --- /dev/null +++ b/src/pages/Items.jsx @@ -0,0 +1,155 @@ +import React, { useEffect, useMemo, useState } from "react"; +import styled from "styled-components"; +import Cards from "../components/Cards"; +import Utils from "../components/Utils"; +import Pagination from "../components/Pagination"; +import { getItems } from "../apis/api"; +import { debounce } from "lodash"; +import AddProductButton from "../components/AddProductButton"; +import SearchProducts from "../components/SearchProducts"; +import DropdownProduct from "../components/DropdownProduct"; + +const Items = () => { + const [items, setItems] = useState([]); + const [keyword, setKeyword] = useState(""); + const [sortOrder, setSortOrder] = useState("orderByLatest"); + const [browserWidth, setBrowserWidth] = useState(window.innerWidth); + const ONE_ITEM = 1; + const TWO_ITEMS = 2; + const FOUR_ITEMS = 4; + + const bestItems = useMemo(() => { + if (768 < browserWidth && browserWidth < 1200) { + return [...items] + .sort((a, b) => b["favoriteCount"] - a["favoriteCount"]) + .slice(0, TWO_ITEMS); + } + if (374 < browserWidth && browserWidth < 769) { + return [...items] + .sort((a, b) => b["favoriteCount"] - a["favoriteCount"]) + .slice(0, ONE_ITEM); + } + return [...items] + .sort((a, b) => b["favoriteCount"] - a["favoriteCount"]) + .slice(0, FOUR_ITEMS); + }, [browserWidth, items]); + + const filteredAndSortedItems = useMemo(() => { + let filtered = items.filter((item) => item.name.includes(keyword)); + if (sortOrder === "orderByFavoriteCount") { + filtered = filtered.sort( + (a, b) => b["favoriteCount"] - a["favoriteCount"] + ); + } else { + filtered = filtered.sort( + (a, b) => new Date(b["createdAt"]) - new Date(a["createdAt"]) + ); + } + return filtered; + }, [items, keyword, sortOrder]); + + useEffect(() => { + const handleResizeBrowserWidth = debounce(() => { + setBrowserWidth(window.innerWidth); + }, 200); + + getItems("/products").then((result) => setItems(result.list)); + window.addEventListener("resize", handleResizeBrowserWidth); + return () => { + window.removeEventListener("resize", handleResizeBrowserWidth); + }; + }, []); + + return ( +
+ + + 베스트 상품 + + + + + {browserWidth < 768 ? ( + + + 전체 상품 + + + + + + + + ) : ( + + 전체 상품 + + + )} + + + + + +
+ ); +}; + +export default Items; + +const ProductsWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 40px; +`; + +const Title = styled.span` + font-size: 20px; + font-weight: 700; + line-height: 32px; + color: #111827; +`; + +const BestProductsList = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +const AllProductsList = styled.div` + display: flex; + flex-direction: column; + gap: 24px; +`; + +const UtilsWrapper = styled.div` + display: flex; + justify-content: space-between; + align-items: center; +`; + +const UtilBoxWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; +const UtilBox = styled.div` + display: flex; + justify-content: space-between; + gap: 14px; + align-items: center; +`; diff --git a/src/reportWebVitals.js b/src/reportWebVitals.js deleted file mode 100644 index 5253d3ad..00000000 --- a/src/reportWebVitals.js +++ /dev/null @@ -1,13 +0,0 @@ -const reportWebVitals = onPerfEntry => { - if (onPerfEntry && onPerfEntry instanceof Function) { - import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { - getCLS(onPerfEntry); - getFID(onPerfEntry); - getFCP(onPerfEntry); - getLCP(onPerfEntry); - getTTFB(onPerfEntry); - }); - } -}; - -export default reportWebVitals; diff --git a/src/setupTests.js b/src/setupTests.js deleted file mode 100644 index 8f2609b7..00000000 --- a/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom'; diff --git a/src/utils/responsive.js b/src/utils/responsive.js new file mode 100644 index 00000000..47bf6ba3 --- /dev/null +++ b/src/utils/responsive.js @@ -0,0 +1,11 @@ +const device = { + mobile: `screen and (max-width: 768px)`, + tablet: `screen and (max-width: 1200px)`, + desktop: `screen and (min-width: 1200px)`, +}; + +const responsive = { + device, +}; + +export default responsive;