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 (
-
- );
-}
-
-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;