From 32a2815086b0864fbd3758064a76261a42ace22b Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Wed, 10 Jul 2024 15:57:06 +0900 Subject: [PATCH 01/16] =?UTF-8?q?README.md=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/README.md b/README.md index e69de29b..be523d49 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,20 @@ +# 요구사항 +- 첨부된 oas.yaml 파일을 토대로 Request, Response Type을 정의 +- React Query를 사용하지 말고 axiou를 사용해서 구현 +- 첨부된 oas.yaml 파일과 목 API URL을 사용하여 API를 구현 + +### 메인 페이지 - Themes 카테고리 섹션 +- /api/vi/thmes API를 사용하여 Section을 구현 +- API는 Axios 또는 React Query 등을 모두 활용해서 구현해도 가능 + +### 메인 페이지 - 실시간 급상승 선물생킹 섹션 +- /api/v1/ranking/products API를 사용하여 Section을 구현 +- 필터 조건을 선택하면 해당 조건에 맞게 API를 요청하여 보여지게 하기 + +### Theme 페이지 - header +- url의 pathParams와 /api/v1/themes API를 사용하여 Section을 구현 +- themeKey 가 잘못 된 경우 메인 페이지로 연결 + +### Theme 페이지 - 상품 목록 섹션 +- /api/v1/themes/{themeKey}/products API를 사용하여 상품 목록을 구현 +- API 요청 시 한번에 20개의 상품 목록이 내려오도록 \ No newline at end of file From 50999ec50cd63e8fb0c621d59158634571f0b6d4 Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Wed, 10 Jul 2024 16:02:35 +0900 Subject: [PATCH 02/16] =?UTF-8?q?README.md=20=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index be523d49..c8f99d7d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # 요구사항 + - 첨부된 oas.yaml 파일을 토대로 Request, Response Type을 정의 - React Query를 사용하지 말고 axiou를 사용해서 구현 - 첨부된 oas.yaml 파일과 목 API URL을 사용하여 API를 구현 From df7dd47de27a18ecea102a15d33a36b9d5978fe7 Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Thu, 11 Jul 2024 10:52:53 +0900 Subject: [PATCH 03/16] =?UTF-8?q?=EB=A9=94=EC=9D=B8=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20-=20Theme=20=EC=B9=B4=ED=85=8C=EA=B3=A0=EB=A6=AC=20?= =?UTF-8?q?=EC=84=B9=EC=85=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 142 ++++++++++++++---- package.json | 2 + src/api/api.ts | 18 +++ .../Home/ThemeCategorySection/index.tsx | 109 +++++--------- src/types/index.ts | 5 + src/types/mock.ts | 7 +- 6 files changed, 176 insertions(+), 107 deletions(-) create mode 100644 src/api/api.ts diff --git a/package-lock.json b/package-lock.json index 89581c64..ac244099 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,10 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-query": "^3.39.3", "react-router-dom": "^6.22.1" }, "devDependencies": { @@ -11988,8 +11990,7 @@ "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, "node_modules/at-least-node": { "version": "1.0.0", @@ -12061,6 +12062,16 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.2.tgz", + "integrity": "sha512-2A8QhOMrbomlDuiLeK9XibIBzuHeRcqqNOHp0Cyp5EoJ1IFDh+XZH3A6BkXtv0K4gFGCI0Y4BM7B1wOEi0Rmgw==", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/axobject-query": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz", @@ -12666,8 +12677,7 @@ "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "node_modules/base64-js": { "version": "1.5.1", @@ -12727,7 +12737,6 @@ "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "dev": true, "engines": { "node": ">=0.6" } @@ -12870,6 +12879,21 @@ "node": ">=8" } }, + "node_modules/broadcast-channel": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/broadcast-channel/-/broadcast-channel-3.7.0.tgz", + "integrity": "sha512-cIAKJXAxGJceNZGTZSBzMxzyOn72cVgPnKx4dc6LRjQgbaJUQqhy5rzL3zbMxkMWsGKkv2hSFkPRMEXfoMZ2Mg==", + "dependencies": { + "@babel/runtime": "^7.7.2", + "detect-node": "^2.1.0", + "js-sha3": "0.8.0", + "microseconds": "0.2.0", + "nano-time": "1.0.0", + "oblivious-set": "1.0.0", + "rimraf": "3.0.2", + "unload": "2.2.0" + } + }, "node_modules/browser-assert": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", @@ -13498,7 +13522,6 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, "dependencies": { "delayed-stream": "~1.0.0" }, @@ -13599,8 +13622,7 @@ "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -15481,7 +15503,6 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, "engines": { "node": ">=0.4.0" } @@ -15535,8 +15556,7 @@ "node_modules/detect-node": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-node/-/detect-node-2.1.0.tgz", - "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==", - "dev": true + "integrity": "sha512-T0NIuQpnTvFDATNuHN5roPwSBG83rFsuO+MXXH9/3N1eFbn4wcPjttvjMLEPWJ0RGUYgQE7cGgS3tNxbqCGM7g==" }, "node_modules/detect-node-es": { "version": "1.1.0", @@ -18405,10 +18425,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "dev": true, + "version": "1.15.6", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz", + "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==", "funding": [ { "type": "individual", @@ -18614,7 +18633,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", - "dev": true, "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -18714,8 +18732,7 @@ "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -19740,7 +19757,6 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -19749,8 +19765,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -23850,6 +23865,11 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-sha3": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz", + "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q==" + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -24626,6 +24646,15 @@ "react": ">= 0.14.0" } }, + "node_modules/match-sorter": { + "version": "6.3.4", + "resolved": "https://registry.npmjs.org/match-sorter/-/match-sorter-6.3.4.tgz", + "integrity": "sha512-jfZW7cWS5y/1xswZo8VBOdudUiSd9nifYRWphc9M5D/ee4w4AoXLgBEdRbgVaxbMuagBPeUC5y2Hi8DO6o9aDg==", + "dependencies": { + "@babel/runtime": "^7.23.8", + "remove-accents": "0.5.0" + } + }, "node_modules/mdast-util-definitions": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/mdast-util-definitions/-/mdast-util-definitions-4.0.0.tgz", @@ -24728,6 +24757,11 @@ "node": ">=8.6" } }, + "node_modules/microseconds": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/microseconds/-/microseconds-0.2.0.tgz", + "integrity": "sha512-n7DHHMjR1avBbSpsTBj6fmMGh2AGrifVV4e+WYc3Q9lO+xnSZ3NyhcBND3vzzatt05LFhoKFRxrIyklmLlUtyA==" + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -24744,7 +24778,6 @@ "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -24753,7 +24786,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -24970,6 +25002,14 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nano-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/nano-time/-/nano-time-1.0.0.tgz", + "integrity": "sha512-flnngywOoQ0lLQOTRNexn2gGSNuM9bKj9RZAWSzhQ+UJYaAFG9bac4DW9VHjUAzrOaIcajHybCTHe/bkvozQqA==", + "dependencies": { + "big-integer": "^1.6.16" + } + }, "node_modules/nanoid": { "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", @@ -25514,6 +25554,11 @@ "integrity": "sha512-eJJDYkhJFFbBBAxeh8xW+weHlkI28n2ZdQV/J/DNfWfSKlGEf2xcfAbZTv3riEXHAhL9SVOTs2pRmXiSTf78xg==", "dev": true }, + "node_modules/oblivious-set": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/oblivious-set/-/oblivious-set-1.0.0.tgz", + "integrity": "sha512-z+pI07qxo4c2CulUHCDf9lcqDlMSo72N/4rLUpRXf6fu+q8vjt8y0xS+Tlf8NTJDdTXHbdeO1n3MlbctwEoXZw==" + }, "node_modules/obuf": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz", @@ -25551,7 +25596,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -25835,7 +25879,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -27576,8 +27619,7 @@ "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==", - "dev": true + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" }, "node_modules/psl": { "version": "1.9.0", @@ -28271,6 +28313,31 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/react-query": { + "version": "3.39.3", + "resolved": "https://registry.npmjs.org/react-query/-/react-query-3.39.3.tgz", + "integrity": "sha512-nLfLz7GiohKTJDuT4us4X3h/8unOh+00MLb2yJoGTPjxKs2bc1iDhkNx2bd5MKklXnOD3NrVZ+J2UXujA5In4g==", + "dependencies": { + "@babel/runtime": "^7.5.5", + "broadcast-channel": "^3.4.1", + "match-sorter": "^6.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -28941,6 +29008,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remove-accents": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/remove-accents/-/remove-accents-0.5.0.tgz", + "integrity": "sha512-8g3/Otx1eJaVD12e31UbJj1YzdtVvzH85HV7t+9MJYk/u3XmkOUJ5Ys9wQrf9PCPK8+xn4ymzqYCiZl6QWKn+A==" + }, "node_modules/renderkid": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/renderkid/-/renderkid-3.0.0.tgz", @@ -29135,7 +29207,6 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dev": true, "dependencies": { "glob": "^7.1.3" }, @@ -29150,7 +29221,6 @@ "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -29160,7 +29230,6 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -29180,7 +29249,6 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -31842,6 +31910,15 @@ "node": ">= 10.0.0" } }, + "node_modules/unload": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/unload/-/unload-2.2.0.tgz", + "integrity": "sha512-B60uB5TNBLtN6/LsgAf3udH9saB5p7gqJwcFfbOEZ8BcBHnGwCf6G/TGiEqkRAxX7zAFIUtzdrXQSdL3Q/wqNA==", + "dependencies": { + "@babel/runtime": "^7.6.2", + "detect-node": "^2.0.4" + } + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", @@ -33313,8 +33390,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/package.json b/package.json index 0a6f0b8f..734257e1 100644 --- a/package.json +++ b/package.json @@ -24,8 +24,10 @@ "dependencies": { "@emotion/react": "^11.11.3", "@emotion/styled": "^11.11.0", + "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-query": "^3.39.3", "react-router-dom": "^6.22.1" }, "devDependencies": { diff --git a/src/api/api.ts b/src/api/api.ts new file mode 100644 index 00000000..23f7de91 --- /dev/null +++ b/src/api/api.ts @@ -0,0 +1,18 @@ +import axios from 'axios'; + +import { ThemesResponse } from '@/types'; +import { ThemeMockList } from '@/types/mock'; + +const API_URL = 'http://localhost:3000'; // 목 API URL + +export const fetchThemes = async (): Promise => { + // 실제 API 호출 + try { + const response = await axios.get(`${API_URL}/api/v1/themes`); + return response.data; + } catch (error) { + // 실패하면 Mock 데이터를 반환 + console.warn('Failed to fetch themes, using mock data instead.'); + return { themes: ThemeMockList }; + } +}; diff --git a/src/components/features/Home/ThemeCategorySection/index.tsx b/src/components/features/Home/ThemeCategorySection/index.tsx index d82e3afe..a44daf84 100644 --- a/src/components/features/Home/ThemeCategorySection/index.tsx +++ b/src/components/features/Home/ThemeCategorySection/index.tsx @@ -1,14 +1,44 @@ import styled from '@emotion/styled'; +import React, { useEffect, useState } from 'react'; import { Link } from 'react-router-dom'; +import { fetchThemes } from '@/api/api'; import { Container } from '@/components/common/layouts/Container'; import { Grid } from '@/components/common/layouts/Grid'; import { getDynamicPath } from '@/routes/path'; import { breakpoints } from '@/styles/variants'; +import { ThemeData } from '@/types'; import { ThemeCategoryItem } from './ThemeCategoryItem'; -export const ThemeCategorySection = () => { +export const ThemeCategorySection: React.FC = () => { + const [themes, setThemes] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [errorMessage, setErrorMessage] = useState(null); + + useEffect(() => { + const loadThemes = async () => { + try { + const response = await fetchThemes(); + setThemes(response.themes); + setIsLoading(false); + } catch (error) { + setErrorMessage('Failed to load themes'); + setIsLoading(false); + } + }; + + loadThemes(); + }, []); + + if (isLoading) { + return
Loading...
; + } + + if (errorMessage) { + return
{errorMessage}
; + } + return ( @@ -18,78 +48,11 @@ export const ThemeCategorySection = () => { md: 6, }} > - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + {themes.map((theme) => ( + + + + ))} diff --git a/src/types/index.ts b/src/types/index.ts index 9d76b97b..867a6f63 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -5,8 +5,13 @@ export type ThemeData = { title: string; description?: string; backgroundColor: string; + imageURL: string; }; +export interface ThemesResponse { + themes: ThemeData[]; +} + export type RankingFilterOption = { targetType: 'ALL' | 'FEMALE' | 'MALE' | 'TEEN'; rankType: 'MANY_WISH' | 'MANY_RECEIVE' | 'MANY_WISH_RECEIVE'; diff --git a/src/types/mock.ts b/src/types/mock.ts index cdd90cf7..133d85e1 100644 --- a/src/types/mock.ts +++ b/src/types/mock.ts @@ -7,6 +7,8 @@ export const ThemeMockData: ThemeData = { title: '예산은 가볍게, 감동은 무겁게❤️', description: '당신의 센스를 뽐내줄 부담 없는 선물', backgroundColor: '#4b4d50', + imageURL: + 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png', }; export const ThemeMockList = [ThemeMockData]; @@ -33,4 +35,7 @@ export const GoodsMockData: GoodsData = { }, }; -export const GoodsMockList: GoodsData[] = Array.from({ length: 21 }, () => GoodsMockData); +export const GoodsMockList: GoodsData[] = Array.from({ length: 21 }, (_, index) => ({ + ...GoodsMockData, + id: GoodsMockData.id + index, +})); From 341b2bbcca29c6236fa11faa1e2d9620e9476aec Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Thu, 11 Jul 2024 11:01:01 +0900 Subject: [PATCH 04/16] =?UTF-8?q?=20docs=20:=20README=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20step1=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c8f99d7d..32b6ec78 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,25 @@ # 요구사항 - 첨부된 oas.yaml 파일을 토대로 Request, Response Type을 정의 -- React Query를 사용하지 말고 axiou를 사용해서 구현 +- React Query를 사용하지 말고 axiou를 사용해서 구현 - 첨부된 oas.yaml 파일과 목 API URL을 사용하여 API를 구현 ### 메인 페이지 - Themes 카테고리 섹션 + - /api/vi/thmes API를 사용하여 Section을 구현 -- API는 Axios 또는 React Query 등을 모두 활용해서 구현해도 가능 +- API는 Axios 또는 React Query 등을 모두 활용해서 구현해도 가능 ### 메인 페이지 - 실시간 급상승 선물생킹 섹션 + - /api/v1/ranking/products API를 사용하여 Section을 구현 - 필터 조건을 선택하면 해당 조건에 맞게 API를 요청하여 보여지게 하기 ### Theme 페이지 - header + - url의 pathParams와 /api/v1/themes API를 사용하여 Section을 구현 - themeKey 가 잘못 된 경우 메인 페이지로 연결 ### Theme 페이지 - 상품 목록 섹션 + - /api/v1/themes/{themeKey}/products API를 사용하여 상품 목록을 구현 -- API 요청 시 한번에 20개의 상품 목록이 내려오도록 \ No newline at end of file +- API 요청 시 한번에 20개의 상품 목록이 내려오도록 From 1ae692ecec591110ed2fa6ab396c922fe21dd4ad Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Thu, 11 Jul 2024 11:05:17 +0900 Subject: [PATCH 05/16] =?UTF-8?q?=20docs=20:=20README=20=ED=8C=8C=EC=9D=BC?= =?UTF-8?q?=20step1=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 32b6ec78..7aa424ab 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# 요구사항 +## 요구사항 - 첨부된 oas.yaml 파일을 토대로 Request, Response Type을 정의 - React Query를 사용하지 말고 axiou를 사용해서 구현 From db72ac0e29a31cecebb65db7d4fb8604b731ff01 Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Thu, 11 Jul 2024 15:53:56 +0900 Subject: [PATCH 06/16] =?UTF-8?q?feat=20:=20=EB=A9=94=EC=9D=B8=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20Theme=20=EC=B9=B4=ED=85=8C=EA=B3=A0?= =?UTF-8?q?=EB=A6=AC=20=EC=84=B9=EC=85=98=20=EB=B0=8F=20=EC=84=A0=EB=AC=BC?= =?UTF-8?q?=EB=9E=AD=ED=82=B9=20=EC=84=B9=EC=85=98=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/api.ts | 35 ++++++++++--- .../Theme/ThemeGoodsSection/index.tsx | 52 +++++++++++++++++-- src/pages/Theme/index.tsx | 2 +- src/types/index.ts | 4 ++ src/types/mock.ts | 6 ++- 5 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/api/api.ts b/src/api/api.ts index 23f7de91..173edd86 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,18 +1,41 @@ import axios from 'axios'; -import { ThemesResponse } from '@/types'; -import { ThemeMockList } from '@/types/mock'; +import { RankingProductsResponse, ThemesResponse } from '@/types'; +import { RankingProductsMockList, ThemeMockList } from '@/types/mock'; -const API_URL = 'http://localhost:3000'; // 목 API URL +const API_URL = 'https://react-gift-mock-api-eunkyung.vercel.app'; + +const apiClient = axios.create({ + baseURL: API_URL, + headers: { + 'Content-Type': 'application/json', + }, +}); export const fetchThemes = async (): Promise => { - // 실제 API 호출 try { - const response = await axios.get(`${API_URL}/api/v1/themes`); + const response = await apiClient.get('/api/v1/themes'); return response.data; } catch (error) { - // 실패하면 Mock 데이터를 반환 console.warn('Failed to fetch themes, using mock data instead.'); return { themes: ThemeMockList }; } }; + +export const fetchRankingProducts = async ( + targetType: string, + rankType: string, +): Promise => { + try { + const response = await axios.get('/api/v1/ranking/products', { + params: { + targetType, + rankType, + }, + }); + return response.data; + } catch (error) { + console.warn('랭킹상품 실패..'); + return { products: RankingProductsMockList }; + } +}; diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index 8edbf70e..5e0a8772 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -1,19 +1,65 @@ import styled from '@emotion/styled'; +import { useEffect, useState } from 'react'; +import { fetchRankingProducts } from '@/api/api'; import { DefaultGoodsItems } from '@/components/common/GoodsItem/Default'; import { Container } from '@/components/common/layouts/Container'; import { Grid } from '@/components/common/layouts/Grid'; import { breakpoints } from '@/styles/variants'; -import { GoodsMockList } from '@/types/mock'; +import type { GoodsData, RankingFilterOption } from '@/types'; + +import { GoodsRankingFilter } from '../../Home/GoodsRankingSection/Filter'; + +const initialFilterOption: RankingFilterOption = { + targetType: 'ALL', + rankType: 'MANY_WISH', +}; type Props = { themeKey: string; }; -export const ThemeGoodsSection = ({}: Props) => { +export const ThemeGoodsSection: React.FC = ({}) => { + const [goodsList, setGoodsList] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [errorMessage, setErrorMessage] = useState(null); + const [filterOption, setFilterOption] = useState(initialFilterOption); + + useEffect(() => { + const loadRankingProducts = async () => { + try { + const response = await fetchRankingProducts(filterOption.targetType, filterOption.rankType); + setGoodsList(response.products); + setIsLoading(false); + } catch (error) { + setErrorMessage('Failed to load ranking products'); + setIsLoading(false); + } + }; + + loadRankingProducts(); + }, [filterOption]); + + const handleFilterOptionChange = (option: RankingFilterOption) => { + setFilterOption(option); + setIsLoading(true); + }; + + if (isLoading) { + return
Loading...
; + } + + if (errorMessage) { + return
{errorMessage}
; + } + return ( + { }} gap={16} > - {GoodsMockList.map(({ id, imageURL, name, price, brandInfo }) => ( + {goodsList.map(({ id, imageURL, name, price, brandInfo }) => ( { +export const ThemePage: React.FC = () => { const { themeKey = '' } = useParams<{ themeKey: string }>(); const currentTheme = getCurrentTheme(themeKey, ThemeMockList); diff --git a/src/types/index.ts b/src/types/index.ts index 867a6f63..7506ac03 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -36,3 +36,7 @@ export type GoodsData = { imageURL: string; }; }; + +export interface RankingProductsResponse { + products: GoodsData[]; +} diff --git a/src/types/mock.ts b/src/types/mock.ts index 133d85e1..076abba8 100644 --- a/src/types/mock.ts +++ b/src/types/mock.ts @@ -8,7 +8,7 @@ export const ThemeMockData: ThemeData = { description: '당신의 센스를 뽐내줄 부담 없는 선물', backgroundColor: '#4b4d50', imageURL: - 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png', + 'https://st.kakaocdn.net/product/gift/gift_brand/20220216170226_38ba26d8eedf450683200d6730757204.png', }; export const ThemeMockList = [ThemeMockData]; @@ -17,7 +17,7 @@ export const GoodsMockData: GoodsData = { id: 123, name: 'BBQ 양념치킨+크림치즈볼+콜라1.25L', imageURL: - 'https://st.kakaocdn.net/product/gift/product/20231030175450_53e90ee9708f45ffa45b3f7b4bc01c7c.jpg', + 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png', wish: { wishCount: 201, isWished: false, @@ -39,3 +39,5 @@ export const GoodsMockList: GoodsData[] = Array.from({ length: 21 }, (_, index) ...GoodsMockData, id: GoodsMockData.id + index, })); + +export const RankingProductsMockList = GoodsMockList; From 405b60e40e78eb7d2dcbe52857ed9c4d4553763c Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Thu, 11 Jul 2024 16:58:59 +0900 Subject: [PATCH 07/16] =?UTF-8?q?feat:=20Theme=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20header=20=EC=84=A4=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/api.ts | 21 ++++++++- .../features/Theme/ThemeHeroSection/index.tsx | 13 ++---- src/pages/Theme/index.tsx | 46 ++++++++++++++----- src/types/index.ts | 4 ++ src/types/mock.ts | 1 + 5 files changed, 61 insertions(+), 24 deletions(-) diff --git a/src/api/api.ts b/src/api/api.ts index 173edd86..6a3456d9 100644 --- a/src/api/api.ts +++ b/src/api/api.ts @@ -1,7 +1,7 @@ import axios from 'axios'; -import { RankingProductsResponse, ThemesResponse } from '@/types'; -import { RankingProductsMockList, ThemeMockList } from '@/types/mock'; +import { RankingProductsResponse, ThemeProductsResponse, ThemesResponse } from '@/types'; +import { RankingProductsMockList, ThemeMockList, ThemeProductsMockList } from '@/types/mock'; const API_URL = 'https://react-gift-mock-api-eunkyung.vercel.app'; @@ -39,3 +39,20 @@ export const fetchRankingProducts = async ( return { products: RankingProductsMockList }; } }; + +export const fetchThemeProducts = async (themeKey: string): Promise => { + try { + const response = await axios.get( + `${API_URL}/api/v1/themes/${themeKey}/products`, + { + params: { + maxResults: 20, + }, + }, + ); + return response.data; + } catch (error) { + console.warn('Failed to fetch theme products, using mock data instead.'); + return { products: ThemeProductsMockList }; + } +}; diff --git a/src/components/features/Theme/ThemeHeroSection/index.tsx b/src/components/features/Theme/ThemeHeroSection/index.tsx index 36cfc038..6bbe7f73 100644 --- a/src/components/features/Theme/ThemeHeroSection/index.tsx +++ b/src/components/features/Theme/ThemeHeroSection/index.tsx @@ -3,20 +3,13 @@ import styled from '@emotion/styled'; import { Container } from '@/components/common/layouts/Container'; import { breakpoints } from '@/styles/variants'; import type { ThemeData } from '@/types'; -import { ThemeMockList } from '@/types/mock'; type Props = { - themeKey: string; + theme: ThemeData; }; -export const ThemeHeroSection = ({ themeKey }: Props) => { - const currentTheme = getCurrentTheme(themeKey, ThemeMockList); - - if (!currentTheme) { - return null; - } - - const { backgroundColor, label, title, description } = currentTheme; +export const ThemeHeroSection: React.FC = ({ theme }) => { + const { backgroundColor, label, title, description } = theme; return ( diff --git a/src/pages/Theme/index.tsx b/src/pages/Theme/index.tsx index 5a72f64d..1477a28e 100644 --- a/src/pages/Theme/index.tsx +++ b/src/pages/Theme/index.tsx @@ -1,22 +1,44 @@ +import { useEffect, useState } from 'react'; import { Navigate, useParams } from 'react-router-dom'; -import { ThemeGoodsSection } from '@/components/features/Theme/ThemeGoodsSection'; +import { fetchThemes } from '@/api/api'; import { getCurrentTheme, ThemeHeroSection } from '@/components/features/Theme/ThemeHeroSection'; -import { RouterPath } from '@/routes/path'; -import { ThemeMockList } from '@/types/mock'; +import { ThemeData } from '@/types'; export const ThemePage: React.FC = () => { const { themeKey = '' } = useParams<{ themeKey: string }>(); - const currentTheme = getCurrentTheme(themeKey, ThemeMockList); + const [currentTheme, setCurrentTheme] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const [errorMessage, setErrorMessage] = useState(null); - if (!currentTheme) { - return ; + useEffect(() => { + const loadThemeData = async () => { + try { + const themesResponse = await fetchThemes(); + const theme = getCurrentTheme(themeKey, themesResponse.themes); + if (!theme) { + setErrorMessage('Invalid theme key'); + setIsLoading(false); + return; + } + setCurrentTheme(theme); + setIsLoading(false); + } catch (error) { + setErrorMessage('Failed to load theme data'); + setIsLoading(false); + } + }; + + loadThemeData(); + }, [themeKey]); + + if (isLoading) { + return
Loading...
; + } + + if (errorMessage) { + return ; } - return ( - <> - - - - ); + return <>{currentTheme && }; }; diff --git a/src/types/index.ts b/src/types/index.ts index 7506ac03..22dd7db9 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -12,6 +12,10 @@ export interface ThemesResponse { themes: ThemeData[]; } +export interface ThemeProductsResponse { + products: GoodsData[]; +} + export type RankingFilterOption = { targetType: 'ALL' | 'FEMALE' | 'MALE' | 'TEEN'; rankType: 'MANY_WISH' | 'MANY_RECEIVE' | 'MANY_WISH_RECEIVE'; diff --git a/src/types/mock.ts b/src/types/mock.ts index 076abba8..f8cab013 100644 --- a/src/types/mock.ts +++ b/src/types/mock.ts @@ -41,3 +41,4 @@ export const GoodsMockList: GoodsData[] = Array.from({ length: 21 }, (_, index) })); export const RankingProductsMockList = GoodsMockList; +export const ThemeProductsMockList = GoodsMockList; From 3a311cdce692b0f0ac101610b840b9ff016a74a6 Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Thu, 11 Jul 2024 17:20:17 +0900 Subject: [PATCH 08/16] =?UTF-8?q?feat=20:=20Theme=20=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=A7=80=20-=EC=83=81=ED=92=88=20=EB=AA=A9=EB=A1=9D=EC=84=B9?= =?UTF-8?q?=EC=85=98=20=EA=B8=B0=EB=8A=A5=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Theme/ThemeGoodsSection/index.tsx | 27 ++++--------------- src/pages/Theme/index.tsx | 8 +++++- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index 5e0a8772..51774e09 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -1,34 +1,26 @@ import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; -import { fetchRankingProducts } from '@/api/api'; +import { fetchThemeProducts } from '@/api/api'; import { DefaultGoodsItems } from '@/components/common/GoodsItem/Default'; import { Container } from '@/components/common/layouts/Container'; import { Grid } from '@/components/common/layouts/Grid'; import { breakpoints } from '@/styles/variants'; -import type { GoodsData, RankingFilterOption } from '@/types'; - -import { GoodsRankingFilter } from '../../Home/GoodsRankingSection/Filter'; - -const initialFilterOption: RankingFilterOption = { - targetType: 'ALL', - rankType: 'MANY_WISH', -}; +import type { GoodsData } from '@/types'; type Props = { themeKey: string; }; -export const ThemeGoodsSection: React.FC = ({}) => { +export const ThemeGoodsSection: React.FC = ({ themeKey }) => { const [goodsList, setGoodsList] = useState([]); const [isLoading, setIsLoading] = useState(true); const [errorMessage, setErrorMessage] = useState(null); - const [filterOption, setFilterOption] = useState(initialFilterOption); useEffect(() => { const loadRankingProducts = async () => { try { - const response = await fetchRankingProducts(filterOption.targetType, filterOption.rankType); + const response = await fetchThemeProducts(themeKey); setGoodsList(response.products); setIsLoading(false); } catch (error) { @@ -38,12 +30,7 @@ export const ThemeGoodsSection: React.FC = ({}) => { }; loadRankingProducts(); - }, [filterOption]); - - const handleFilterOptionChange = (option: RankingFilterOption) => { - setFilterOption(option); - setIsLoading(true); - }; + }, [themeKey]); if (isLoading) { return
Loading...
; @@ -56,10 +43,6 @@ export const ThemeGoodsSection: React.FC = ({}) => { return ( - { return ; } - return <>{currentTheme && }; + return ( + <> + {currentTheme && } + ; + + ); }; From 47be16daeb8d80c97571d3f116a86dd515e56127 Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Fri, 12 Jul 2024 06:36:23 +0900 Subject: [PATCH 09/16] =?UTF-8?q?docs=20:=20step2=20README.md=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7aa424ab..736bab71 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -## 요구사항 +# 요구사항 + +## Step1 - 첨부된 oas.yaml 파일을 토대로 Request, Response Type을 정의 - React Query를 사용하지 말고 axiou를 사용해서 구현 @@ -23,3 +25,11 @@ - /api/v1/themes/{themeKey}/products API를 사용하여 상품 목록을 구현 - API 요청 시 한번에 20개의 상품 목록이 내려오도록 + +--- + +## Step2 + +- 각API에서 Loading 상태에 대한 UI 대응 +- 데이터가 없는 경우에 대한 UI 대응 +- Http Status 에 따라 Error를 다르게 처리 From 368ccc0e59792c7911ed6feaabf05849290e2803 Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Fri, 12 Jul 2024 07:09:19 +0900 Subject: [PATCH 10/16] =?UTF-8?q?feat:=20=EA=B0=81=20api=EC=97=90=EC=84=9C?= =?UTF-8?q?=20=EB=A1=9C=EB=94=A9=EA=B3=BC=20=EB=8D=B0=EC=9D=B4=ED=84=B0?= =?UTF-8?q?=EA=B0=80=20=EC=97=86=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20=20UI=20?= =?UTF-8?q?=EB=8C=80=EC=9D=91=20=EA=B8=B0=EB=8A=A5=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../features/Theme/ThemeGoodsSection/index.tsx | 17 +++++++++++++++-- src/pages/Theme/index.tsx | 12 +++++++++++- 2 files changed, 26 insertions(+), 3 deletions(-) diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index 51774e09..9e16d855 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -33,11 +33,15 @@ export const ThemeGoodsSection: React.FC = ({ themeKey }) => { }, [themeKey]); if (isLoading) { - return
Loading...
; + return Loading...; } if (errorMessage) { - return
{errorMessage}
; + return {errorMessage}; + } + + if (goodsList.length === 0) { + return 상품을 찾을 수 없습니다.; } return ( @@ -73,3 +77,12 @@ const Wrapper = styled.section` padding: 40px 16px 360px; } `; + +const Message = styled.div` + display: flex; + justify-content: center; + align-item: center; + height: 100%; + font-size: 1.5em; + color: #999; +`; diff --git a/src/pages/Theme/index.tsx b/src/pages/Theme/index.tsx index 3be29c0e..a8f6d398 100644 --- a/src/pages/Theme/index.tsx +++ b/src/pages/Theme/index.tsx @@ -1,3 +1,4 @@ +import styled from '@emotion/styled'; import { useEffect, useState } from 'react'; import { Navigate, useParams } from 'react-router-dom'; @@ -34,7 +35,7 @@ export const ThemePage: React.FC = () => { }, [themeKey]); if (isLoading) { - return
Loading...
; + return Loading...; } if (errorMessage) { @@ -48,3 +49,12 @@ export const ThemePage: React.FC = () => { ); }; + +const Message = styled.div` + display: flex; + justify-content: center; + align-items: center; + height: 100%; + font-size: 1.5em; + color: #999; +`; From 73de3a8fdfe5ac8b78ea904eed5644358d9a527c Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Fri, 12 Jul 2024 10:38:12 +0900 Subject: [PATCH 11/16] =?UTF-8?q?feat=20:=20http=20status=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=9D=BC=20error=EC=B2=98=EB=A6=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Theme/ThemeGoodsSection/index.tsx | 26 ++++++++++++++++--- src/pages/Theme/index.tsx | 22 +++++++++++++++- 2 files changed, 44 insertions(+), 4 deletions(-) diff --git a/src/components/features/Theme/ThemeGoodsSection/index.tsx b/src/components/features/Theme/ThemeGoodsSection/index.tsx index 9e16d855..8144fe00 100644 --- a/src/components/features/Theme/ThemeGoodsSection/index.tsx +++ b/src/components/features/Theme/ThemeGoodsSection/index.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled'; +import axios from 'axios'; import { useEffect, useState } from 'react'; import { fetchThemeProducts } from '@/api/api'; @@ -18,18 +19,37 @@ export const ThemeGoodsSection: React.FC = ({ themeKey }) => { const [errorMessage, setErrorMessage] = useState(null); useEffect(() => { - const loadRankingProducts = async () => { + const loadThemeProducts = async () => { try { const response = await fetchThemeProducts(themeKey); setGoodsList(response.products); setIsLoading(false); } catch (error) { - setErrorMessage('Failed to load ranking products'); + if (axios.isAxiosError(error)) { + if (error.response) { + switch (error.response.status) { + case 404: + setErrorMessage('상품을 찾을 수 없습니다.'); + break; + case 500: + setErrorMessage('서버 오류'); + break; + default: + setErrorMessage('예기치 않은 오류 발생'); + } + } else if (error.request) { + setErrorMessage('요청이 있지만 응답을 받지 못한 경우'); + } else { + setErrorMessage('오류 설정문제발생'); + } + } else { + setErrorMessage('예기치 않은 오류 발생'); + } setIsLoading(false); } }; - loadRankingProducts(); + loadThemeProducts(); }, [themeKey]); if (isLoading) { diff --git a/src/pages/Theme/index.tsx b/src/pages/Theme/index.tsx index a8f6d398..498b4b01 100644 --- a/src/pages/Theme/index.tsx +++ b/src/pages/Theme/index.tsx @@ -1,4 +1,5 @@ import styled from '@emotion/styled'; +import axios from 'axios'; import { useEffect, useState } from 'react'; import { Navigate, useParams } from 'react-router-dom'; @@ -26,7 +27,26 @@ export const ThemePage: React.FC = () => { setCurrentTheme(theme); setIsLoading(false); } catch (error) { - setErrorMessage('Failed to load theme data'); + if (axios.isAxiosError(error)) { + if (error.response) { + switch (error.response.status) { + case 404: + setErrorMessage('상품을 찾을 수 없습니다.'); + break; + case 500: + setErrorMessage('서버 오류가 발생했습니다.'); + break; + default: + setErrorMessage('예기치 않은 오류가 발생했습니다.'); + } + } else if (error.request) { + setErrorMessage('요청이 있지만 응답을 받지 못한 경우'); + } else { + setErrorMessage('오류 설정문제발생'); + } + } else { + setErrorMessage('예기치 않은 오류가 발생했습니다.'); + } setIsLoading(false); } }; From cdebebf01905d3a2a6de7e7985aa8b404e812c67 Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Fri, 12 Jul 2024 14:45:50 +0900 Subject: [PATCH 12/16] =?UTF-8?q?docs=20:=20README.md=20step3=EC=B6=94?= =?UTF-8?q?=EA=B0=80=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 736bab71..a500add5 100644 --- a/README.md +++ b/README.md @@ -33,3 +33,10 @@ - 각API에서 Loading 상태에 대한 UI 대응 - 데이터가 없는 경우에 대한 UI 대응 - Http Status 에 따라 Error를 다르게 처리 + +--- + +### step3 + +- 스크롤을 내리면 추가로 데이터를 요청하여 보여지게 하기 +- 1단계에서 구현한 API를 react-query를 사용해서 구현 From c92f0c0c29b852e5a267a78e917680aca9759693 Mon Sep 17 00:00:00 2001 From: dmsrud1218 Date: Fri, 12 Jul 2024 23:05:34 +0900 Subject: [PATCH 13/16] =?UTF-8?q?chore:=20step1,2=EC=97=90=EC=84=9C=20?= =?UTF-8?q?=EB=9E=AD=ED=82=B9=EA=B3=BC=20=EC=98=A4=EB=A5=98=EB=93=A4?= =?UTF-8?q?=EC=9D=84=20=EB=8B=A4=EC=8B=9C=20=EC=88=98=EC=A0=95=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=9E=AC=EC=97=85=EB=A1=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 30 +++++++---- package.json | 3 +- src/App.tsx | 21 ++++++-- src/api/api.ts | 50 +++++++------------ .../Home/GoodsRankingSection/Filter.tsx | 2 +- .../Home/GoodsRankingSection/List.tsx | 27 +++++----- .../Home/GoodsRankingSection/index.tsx | 38 +++++++++++--- .../Theme/ThemeGoodsSection/index.tsx | 32 +++++++----- src/index.tsx | 7 ++- src/pages/Theme/index.tsx | 13 +++-- src/types/mock.ts | 4 +- 11 files changed, 136 insertions(+), 91 deletions(-) diff --git a/package-lock.json b/package-lock.json index ac244099..26510f45 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-intersection-observer": "^9.13.0", "react-query": "^3.39.3", "react-router-dom": "^6.22.1" }, @@ -33,7 +34,7 @@ "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.82", - "@types/react": "^18.2.57", + "@types/react": "^18.3.3", "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", @@ -10547,13 +10548,12 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.58", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.58.tgz", - "integrity": "sha512-TaGvMNhxvG2Q0K0aYxiKfNDS5m5ZsoIBBbtfUorxdH4NGSXIlYvZxLJI+9Dd3KjeB3780bciLyAb7ylO8pLhPw==", + "version": "18.3.3", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.3.tgz", + "integrity": "sha512-hti/R0pS0q1/xx+TsI73XIqk26eBsISZ2R0wUijXIngRK9R/e7Xw/cXVxQK7R5JjW+SV4zGcn5hXjudkN/pLIw==", "dev": true, "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, @@ -10578,12 +10578,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, - "node_modules/@types/scheduler": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.8.tgz", - "integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==", - "dev": true - }, "node_modules/@types/semver": { "version": "7.5.7", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.7.tgz", @@ -28308,6 +28302,20 @@ "integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg==", "dev": true }, + "node_modules/react-intersection-observer": { + "version": "9.13.0", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.0.tgz", + "integrity": "sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "16.13.1", "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", diff --git a/package.json b/package.json index 734257e1..47f5299a 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,7 @@ "axios": "^1.7.2", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-intersection-observer": "^9.13.0", "react-query": "^3.39.3", "react-router-dom": "^6.22.1" }, @@ -47,7 +48,7 @@ "@testing-library/user-event": "^13.5.0", "@types/jest": "^27.5.2", "@types/node": "^16.18.82", - "@types/react": "^18.2.57", + "@types/react": "^18.3.3", "@types/react-dom": "^18.2.19", "@typescript-eslint/eslint-plugin": "^6.21.0", "@typescript-eslint/parser": "^6.21.0", diff --git a/src/App.tsx b/src/App.tsx index 26d8766c..cd1c045c 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,11 +1,22 @@ -import { AuthProvider } from './provider/Auth'; -import { Routes } from './routes'; +import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; + +import { Footer } from './components/features/Layout/Footer'; +import { Header } from './components/features/Layout/Header'; +import { HomePage } from './pages/Home'; +//import { LoginPage } from './pages/Login'; +//import { MyAccountPage } from './pages/MyAccount'; +import { ThemePage } from './pages/Theme'; const App = () => { return ( - - - + +
+ + } /> + } /> + +