diff --git a/.gitignore b/.gitignore index 4d29575d..ae4f4f35 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,6 @@ npm-debug.log* yarn-debug.log* yarn-error.log* + +#환경변수 무시 +.env \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index f3860200..741da1f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.9", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.3", @@ -5495,6 +5496,32 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -6014,6 +6041,19 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -7323,6 +7363,20 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -7494,12 +7548,10 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" }, "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dependencies": { - "get-intrinsic": "^1.2.4" - }, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -7572,13 +7624,15 @@ } }, "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" }, "engines": { "node": ">= 0.4" @@ -9009,15 +9063,21 @@ } }, "node_modules/get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", + "license": "MIT", "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -9039,6 +9099,19 @@ "node": ">=8.0.0" } }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -9178,11 +9251,12 @@ } }, "node_modules/gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "dependencies": { - "get-intrinsic": "^1.1.3" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -9261,9 +9335,10 @@ } }, "node_modules/has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -12664,6 +12739,15 @@ "tmpl": "1.0.5" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -14935,6 +15019,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", @@ -22450,6 +22540,29 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.7.0.tgz", "integrity": "sha512-M0JtH+hlOL5pLQwHOLNYZaXuhqmvS8oExsqB1SBYgA4Dk7u/xx+YdGHXaK5pyUfed5mYXdlYiphWq3G8cRi5JQ==" }, + "axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", + "requires": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + } + } + } + }, "axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -22836,6 +22949,15 @@ "set-function-length": "^1.2.1" } }, + "call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + } + }, "callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -23753,6 +23875,16 @@ "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-5.1.0.tgz", "integrity": "sha512-YXQl1DSa4/PQyRfgrv6aoNjhasp/p4qs9FjJ4q4cQk+8m4r6k4ZSiEyytKG8f8W9gi8WsQtIObNmKd+tMzNTmA==" }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + } + }, "duplexer": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", @@ -23890,12 +24022,9 @@ "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "requires": { - "get-intrinsic": "^1.2.4" - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, "es-errors": { "version": "1.3.0", @@ -23953,13 +24082,14 @@ } }, "es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "requires": { - "get-intrinsic": "^1.2.4", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "hasown": "^2.0.2" } }, "es-shim-unscopables": { @@ -24972,15 +25102,20 @@ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" }, "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", + "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-define-property": "^1.0.1", "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" + "get-proto": "^1.0.0", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" } }, "get-own-enumerable-property-symbols": { @@ -24993,6 +25128,15 @@ "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==" }, + "get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "requires": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + } + }, "get-stream": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", @@ -25089,12 +25233,9 @@ } }, "gopd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", - "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", - "requires": { - "get-intrinsic": "^1.1.3" - } + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, "graceful-fs": { "version": "4.2.11", @@ -25148,9 +25289,9 @@ "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==" }, "has-symbols": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", - "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, "has-tostringtag": { "version": "1.0.2", @@ -27578,6 +27719,11 @@ "tmpl": "1.0.5" } }, + "math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" + }, "mdn-data": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.4.tgz", @@ -29002,6 +29148,11 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "psl": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz", diff --git a/package.json b/package.json index 8ab90bee..af52cec1 100644 --- a/package.json +++ b/package.json @@ -6,6 +6,7 @@ "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", + "axios": "^1.7.9", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.22.3", diff --git a/src/api/itemApi.js b/src/api/itemApi.js index 7dbc5dbb..0a086253 100644 --- a/src/api/itemApi.js +++ b/src/api/itemApi.js @@ -1,40 +1,16 @@ //itemApi.js -const BASE_URL = process.env.REACT_APP_BASE_URL; - -// export async function getProducts(params = {}) { -// // URLSearchParams을 이용하면 파라미터 값을 자동으로 쉽게 인코딩할 수 있어요. -// const query = new URLSearchParams(params).toString(); +import axios from "axios"; -// try { -// const response = await fetch(`${BASE_URL}/products?${query}`); -// if (!response.ok) { -// throw new Error(`HTTP error: ${response.status}`); -// } -// const body = await response.json(); -// return body; -// } catch (error) { -// console.error("Failed to fetch products:", error); -// throw error; -// } -// } +const BASE_URL = process.env.REACT_APP_BASE_URL; export async function getProducts({ page, pageSize, orderBy, keyword }) { - const queryParams = new URLSearchParams({ - page, - pageSize, - orderBy, - }); + try { + const response = await axios.get(`${BASE_URL}/products`, { + params: { page, pageSize, orderBy, keyword }, + }); - if (keyword) { - queryParams.append("keyword", keyword); + return response.data; + } catch (error) { + throw new Error(`HTTP error: ${error.response?.status || error.message}`); } - - const response = await fetch( - `https://panda-market-api.vercel.app/products?${queryParams.toString()}` - ); - if (!response.ok) { - throw new Error(`HTTP error: ${response.status}`); - } - - return response.json(); } diff --git a/src/assets/images/icons/ic_X.svg b/src/assets/images/icons/ic_X.svg new file mode 100644 index 00000000..f6674f7f --- /dev/null +++ b/src/assets/images/icons/ic_X.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/icons/upload_de.svg b/src/assets/images/icons/upload_de.svg new file mode 100644 index 00000000..f5ffa8bd --- /dev/null +++ b/src/assets/images/icons/upload_de.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/assets/images/icons/upload_sm.svg b/src/assets/images/icons/upload_sm.svg new file mode 100644 index 00000000..e56d43e1 --- /dev/null +++ b/src/assets/images/icons/upload_sm.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/src/components/ImageUploader.jsx b/src/components/ImageUploader.jsx new file mode 100644 index 00000000..41999e79 --- /dev/null +++ b/src/components/ImageUploader.jsx @@ -0,0 +1,68 @@ +// ImageUploader.jsx +import * as S from "./ImageUploader.styles"; +import upload_de from "../assets/images/icons/upload_de.svg"; +import ic_X from "../assets/images/icons/ic_X.svg"; +import { useState } from "react"; + +export default function ImageUploader() { + const [previewImage, setPreviewimage] = useState(null); + const [showLimitText, setShowLimitText] = useState(false); + + const handleImageChange = (e) => { + const file = e.target.files[0]; + if (file) { + const imageUrl = URL.createObjectURL(file); + setPreviewimage(imageUrl); + setShowLimitText(false); // 새로 업로드 성공했으니 문구 지우기 + } + }; + + const handleRemoveImage = () => { + setPreviewimage(null); + setShowLimitText(false); // 이미지 삭제했으니 문구 지우기 + }; + + const handleUploadClick = (e) => { + if (previewImage) { + e.preventDefault(); + setShowLimitText(true); // 문구 보여주기 + } + }; + + const isImageUploaded = !!previewImage; + + return ( + + {/* 업로드 버튼 */} +
+ + + + + {showLimitText && ( + *이미지 등록은 최대 1장까지 가능합니다. + )} +
+ + {/* 이미지 미리보기 */} + {isImageUploaded && ( +
+ + + + + + + + +
+ )} +
+ ); +} diff --git a/src/components/ImageUploader.styles.jsx b/src/components/ImageUploader.styles.jsx new file mode 100644 index 00000000..aeba57a9 --- /dev/null +++ b/src/components/ImageUploader.styles.jsx @@ -0,0 +1,56 @@ +import styled from "styled-components"; +import theme from "../styles/theme"; + +export const UploadContainer = styled.div` + display: flex; + gap: 24px; +`; + +export const UploadLabel = styled.label` + width: 100%; + height: 100%; +`; + +export const ImageUploadIcon = styled.img``; + +export const HiddenInput = styled.input` + display: none; +`; + +export const PreviewWrapper = styled.div` + position: relative; + display: inline-block; +`; + +export const ImageContainer = styled.div` + position: relative; + width: 282px; + height: 282px; + border-radius: 12px; + overflow: hidden; +`; + +export const PreviewImage = styled.img` + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 8px; +`; + +export const RemoveButton = styled.button` + position: absolute; + top: 12px; + right: 12px; + cursor: pointer; +`; + +export const CloseIcon = styled.img` + width: 24px; + height: 24px; +`; + +export const LimitText = styled.p` + color: ${theme.colors.Error100}; + font: ${theme.fonts.H5Regular}; + margin-top: 8px; +`; diff --git a/src/components/TagInput.jsx b/src/components/TagInput.jsx new file mode 100644 index 00000000..57984940 --- /dev/null +++ b/src/components/TagInput.jsx @@ -0,0 +1,50 @@ +// TagInput.jsx +import * as S from "./TagInput.styles"; +import ic_x from "../assets/images/icons/ic_X.svg"; +import { useState } from "react"; + +export default function TagInput({ onChange }) { + const [tags, setTags] = useState([]); + const [inputValue, setInputValue] = useState(""); + + const handleKeyDown = (e) => { + if (e.key === "Enter" && inputValue.trim().length > 1) { + e.preventDefault(); + + if (!tags.includes(inputValue.trim())) { + const updatedTags = [...tags, inputValue.trim()]; + setTags(updatedTags); + onChange(updatedTags); + } + setTimeout(() => setInputValue(""), 0); // 입력 후 초기화 + 엔터 눌렀을 때 새로고침 방지 비동기처리 + } + }; + + const handleDeleteTag = (tagToDelete) => { + const updatedTags = tags.filter((tag) => tag !== tagToDelete); + setTags(updatedTags); + onChange(updatedTags); + }; + + return ( + + setInputValue(e.target.value)} + onKeyDown={handleKeyDown} + /> + + {tags.map((tag) => ( + + {tag} + handleDeleteTag(tag)}> + 삭제 + + + ))} + + + ); +} diff --git a/src/components/TagInput.styles.jsx b/src/components/TagInput.styles.jsx new file mode 100644 index 00000000..c0b1fad6 --- /dev/null +++ b/src/components/TagInput.styles.jsx @@ -0,0 +1,42 @@ +import styled from "styled-components"; +import theme from "../styles/theme"; + +export const TagWrapper = styled.div` + display: flex; + flex-direction: column; + gap: 14px; +`; + +export const StyledInput = styled.input` + width: 100%; + padding: 16px 24px; + gap: 10px; + border: none; + border-radius: 12px; + background-color: ${theme.colors.Gray100}; + font: ${theme.fonts.H5Regular}; +`; + +export const TagList = styled.div` + display: flex; + flex-wrap: wrap; + height: 36px; + gap: 12px; +`; + +export const TagItem = styled.div` + display: flex; + align-items: center; + justify-content: center; + height: 36px; + padding: 6px 12px 6px 16px; + border-radius: 26px; + background-color: ${theme.colors.Gray100}; + color: ${theme.fonts.H5Regular}; + gap: 10px; +`; + +export const DeleteButton = styled.button` + width: 24px; + height: 24px; +`; diff --git a/src/components/common/Card.jsx b/src/components/common/Card.jsx index d08be080..276fa067 100644 --- a/src/components/common/Card.jsx +++ b/src/components/common/Card.jsx @@ -4,8 +4,7 @@ import ic_heart from "../../assets/images/icons/ic_heart.svg"; import placeholder from "../../assets/images/placeholder.jpg"; export default function Card({ item, size }) { - const imageURL = - item.images && item.images.length > 0 ? item.images[0] : placeholder; + const imageURL = item?.images?.[0] ?? placeholder; return ( diff --git a/src/components/common/CardList.jsx b/src/components/common/CardList.jsx index 77f262c0..a2da536a 100644 --- a/src/components/common/CardList.jsx +++ b/src/components/common/CardList.jsx @@ -1,22 +1,45 @@ // CardList.jsx +import { useState, useEffect } from "react"; import styled from "styled-components"; import Card from "./Card"; +import { BREAKPOINTS } from "../../constants/constants"; export default function CardList({ items, size }) { - const displayItems = - size === "large" - ? items.slice( - 0, - window.innerWidth <= 480 ? 1 : window.innerWidth <= 768 ? 2 : 4 - ) - : items; + const [visibleItems, setVisibleItems] = useState([]); + + useEffect(() => { + const updateVisibleItems = () => { + const maxItems = + window.innerWidth <= BREAKPOINTS.MOBILE + ? size === "large " + ? 1 + : 4 + : window.innerWidth <= BREAKPOINTS.TABLET + ? size === "large" + ? 2 + : 6 + : window.innerWidth <= BREAKPOINTS.LAPTOP + ? size === "large" + ? 3 + : 8 + : size === "large" + ? 4 + : 10; + + setVisibleItems(items.slice(0, maxItems)); + }; + + updateVisibleItems(); + window.addEventListener("resize", updateVisibleItems); + return () => window.removeEventListener("resize", updateVisibleItems); + }, [items, size]); return ( - {items.length === 0 ? ( + {visibleItems.length === 0 ? ( 상품이 없습니다. ) : ( - displayItems.map((item) => ( + visibleItems.map((item) => ( )) )} @@ -33,20 +56,26 @@ const CardListWrapper = styled.div` size === "large" ? ` grid-template-columns: repeat(4, 1fr); // 베스트 상품 (기본) - @media (max-width: 768px) { - grid-template-columns: repeat(2, 1fr); // 태블릿 + @media (max-width: ${BREAKPOINTS.LAPTOP}) { + grid-template-columns: repeat(3, 1fr); + } + @media (max-width: ${BREAKPOINTS.TABLET}px) { + grid-template-columns: repeat(2, 1fr); } - @media (max-width: 480px) { - grid-template-columns: repeat(1, 1fr); // 모바일 + @media (max-width: ${BREAKPOINTS.MOBILE}px) { + grid-template-columns: repeat(1, 1fr); } ` : ` grid-template-columns: repeat(5, 1fr); // 전체 상품 (기본) - @media (max-width: 768px) { - grid-template-columns: repeat(3, 1fr); // 태블릿 + @media (max-width: ${BREAKPOINTS.LAPTOP}px) { + grid-template-columns: repeat(4, 1fr); + } + @media (max-width: ${BREAKPOINTS.TABLET}px) { + grid-template-columns: repeat(3, 1fr); } - @media (max-width: 480px) { - grid-template-columns: repeat(2, 1fr); // 모바일 + @media (max-width: ${BREAKPOINTS.MOBILE}px) { + grid-template-columns: repeat(2, 1fr); } `} `; diff --git a/src/components/common/Input.jsx b/src/components/common/Input.jsx new file mode 100644 index 00000000..27ff7641 --- /dev/null +++ b/src/components/common/Input.jsx @@ -0,0 +1,23 @@ +import styled from "styled-components"; +import theme from "../../styles/theme"; + +export default function Input({ placeholder, value, onChange, ...props }) { + return ( + + ); +} + +const StyledInput = styled.input` + width: 100%; + padding: 16px 24px; + gap: 10px; + border: none; + border-radius: 12px; + background-color: ${theme.colors.Gray100}; + font: ${theme.fonts.H5Regular}; +`; diff --git a/src/constants/constants.js b/src/constants/constants.js new file mode 100644 index 00000000..310f57e6 --- /dev/null +++ b/src/constants/constants.js @@ -0,0 +1,5 @@ +export const BREAKPOINTS = { + MOBILE: 480, + TABLET: 768, + LAPTOP: 1024, +}; diff --git a/src/pages/AddItemPage/AddItemPage.css b/src/pages/AddItemPage/AddItemPage.css deleted file mode 100644 index e69de29b..00000000 diff --git a/src/pages/AddItemPage/AddItemPage.jsx b/src/pages/AddItemPage/AddItemPage.jsx index e7ba934c..f943f21b 100644 --- a/src/pages/AddItemPage/AddItemPage.jsx +++ b/src/pages/AddItemPage/AddItemPage.jsx @@ -1,7 +1,70 @@ -import React from "react"; +// AddItemPage.jsx +import * as S from "./AddItemPage.styles"; +import { useState, useEffect } from "react"; +import Input from "../../components/common/Input"; +import ImageUploader from "../../components/ImageUploader"; +import TagInput from "../../components/TagInput"; -function AddItemPage() { - return
AddItemPage
; -} +export default function AddItemPage() { + const [productName, setProductName] = useState(""); + const [description, setDescription] = useState(""); + const [price, setPrice] = useState(""); + const [tags, setTags] = useState([]); + const [isFormValid, setIsFormValid] = useState(false); + + useEffect(() => { + setIsFormValid( + productName.trim() !== "" && + description.trim() !== "" && + Number(price) > 0 && + tags.length > 0 + ); + }, [productName, description, price, tags]); + + return ( +
+ + + 상품 등록하기 + + 등록 + + + + + 상품 이미지 + + -export default AddItemPage; + 상품명 + setProductName(e.target.value)} + /> + + 상품 소개 + setDescription(e.target.value)} + style={{ height: "282px", overflowY: "auto" }} + /> + + 판매가격 + setPrice(e.target.value)} + /> + + 태그 + + +
+ ); +} diff --git a/src/pages/AddItemPage/AddItemPage.styles.jsx b/src/pages/AddItemPage/AddItemPage.styles.jsx new file mode 100644 index 00000000..946b1694 --- /dev/null +++ b/src/pages/AddItemPage/AddItemPage.styles.jsx @@ -0,0 +1,47 @@ +import styled from "styled-components"; +import theme from "../../styles/theme"; + +export const Container = styled.div` + max-width: 1200px; + margin: 0 auto; + padding: 40px; + display: flex; + flex-direction: column; + gap: 24px; +`; + +export const AddHeader = styled.div` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; +`; + +export const Wrapper = styled.div` + display: flex; + flex-direction: column; + gap: 16px; +`; + +export const Title = styled.div` + font: ${theme.fonts.H3Bold}; +`; + +export const SubmitBtn = styled.button` + width: 74px; + height: 42px; + background-color: ${({ $isActive }) => + $isActive ? theme.colors.Primary200 : theme.colors.Gray400}; + font: ${theme.fonts.H5Regular}; + color: ${theme.colors.Gray100}; + border: none; + border-radius: 8px; + display: flex; + justify-content: center; + align-items: center; + cursor: pointer; +`; + +export const SubTitle = styled.div` + font: ${theme.fonts.H4Bold}; +`; diff --git a/src/pages/MarketPage/ItemsHeader.jsx b/src/pages/MarketPage/ItemsHeader.jsx index 104272ca..967bae40 100644 --- a/src/pages/MarketPage/ItemsHeader.jsx +++ b/src/pages/MarketPage/ItemsHeader.jsx @@ -59,8 +59,7 @@ const SearchInput = styled.input` border-radius: 12px; width: 325px; height: 42px; - /* position: fixed; */ - /* flex-grow: 1; */ + color: ${theme.colors.Gray400}; font: ${theme.fonts.H5Bold};