diff --git a/README.md b/README.md
index 3eaeec280..b3bada84f 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,38 @@
-# react-deploy
\ No newline at end of file
+# react-deploy
+FE 카카오 선물하기 6주차 과제: 배포 & 협업
+### 🌱 1단계 - API 명세 협의 & 반영
+작성한 API 문서를 기반으로 팀 내에서 지금까지 만든 API를 검토하고 통일하여 변경 사항을 반영
+- 팀 내에서 일관된 기준을 정하여 API 명세를 결정
+- 때로는 클라이언트의 편의를 위해 API 명세를 결정하는 것이 좋음
+
+- [X] 팀 내에 배포될 API가 여러 개일 경우 상단 내비게이션 바에서 선택 가능
+ - 프론트엔드의 경우 사용자가 팀 내 여러 서버 중 하나를 선택하여 서비스를 이용
+ - [X] 팀 내 백엔드 엔지니어의 이름을 넣고, 이름을 선택하면 해당 엔지니어의 API로 API 통신을 하도록 구현
+ - [X] 기본 선택은 제일 첫 번째 이름
+- [X] 백엔드에서 협의된 API를 배포하기 전까지는 MSW로 동작 가능하도록 구현
+
+### 🌿 2단계 - 배포하기
+- github pages를 사용하여 배포
+
+### 🪴 3단계 - 포인트
+- 포인트 기능 구현
+- API 명세는 팀과 협의하여 결정하고 구현
+
+ - **포인트 조회**
+ 해당하는 멤버의 포인트를 리턴
+ **URL** : /api/member/point
+ **Request** : Header: Authorization: Bearer {token}
+ **Response** : 200 OK { "point": "number" }
+
+### 🌳 4단계 - 질문에 대한 답변
+**질문 1.** SPA 페이지를 정적 배포를 하려고 할 때 Vercel을 사용하지 않고 한다면 어떻게 할 수 있을까요?
+
+> 리포지토리에 SPA 페이지를 업로드하고 GitHub Pages를 설정하는 방법이나, AWS S3에 프로젝트를 올리고 CloudFront을 CDN으로 사용하는 방법을 사용해 배포할 수 있습니다.
+
+**질문 2.** CSRF나 XSS 공격을 막는 방법은 무엇일까요?
+
+> CSRF는 서버에서 생성한 토큰을 사용해 요청을 검증하거나, 쿠키에 SameSite 속성을 설정해 동일 출처에서만 쿠키가 전송되도록 하여 방어할 수 있습니다. XSS는 CSP(콘텐츠 보안 정책)을 설정하거나, HTML 인코딩을 사용하여 <, >와 같은 문자를 안전한 형식으로 변환하는 등 사용자 데이터를 적절히 인코딩하여 방어할 수 있습니다.
+
+**질문 3.** 브라우저 렌더링 원리에 대해 설명해주세요.
+
+> 브라우저는 서버로부터 HTML 파일을 받아 파싱하여 DOM 트리를 생성하고, CSS 파일을 파싱하여 CSSOM 트리가 생성합니다. DOM 트리와 CSSOM 트리가 결합하여 렌더 트리가 형성됩니다. 이후 레이아웃과 페인팅 과정을 거치면서 각 요소의 위치와 크기가 정확한 좌표로 변환되고, 배경색, 이미지 등이 화면에 그려집니다. 여러 레이어가 겹쳐지는 경우 브라우저는 각 레이어를 별도로 페인팅하고 최종적으로 이를 합성하여 최종 이미지를 생성하여 화면에 표시합니다.
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index 0609bb9c3..48a09e167 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -14,7 +14,9 @@
"@emotion/styled": "^11.11.0",
"@tanstack/react-query": "^5.24.1",
"axios": "^1.6.7",
+ "dotenv": "^16.4.5",
"framer-motion": "^11.0.6",
+ "gh-pages": "^6.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.12",
@@ -10949,6 +10951,15 @@
"node": ">=8"
}
},
+ "node_modules/array-uniq": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz",
+ "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/array.prototype.filter": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/array.prototype.filter/-/array.prototype.filter-1.0.3.tgz",
@@ -11126,8 +11137,7 @@
"node_modules/async": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
- "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
- "dev": true
+ "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg=="
},
"node_modules/async-limiter": {
"version": "1.0.1",
@@ -11834,8 +11844,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",
@@ -12743,8 +12752,7 @@
"node_modules/commondir": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
- "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==",
- "dev": true
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
},
"node_modules/compressible": {
"version": "2.0.18",
@@ -12814,8 +12822,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",
@@ -14204,7 +14211,7 @@
"version": "16.4.5",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
"integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
- "dev": true,
+ "license": "BSD-2-Clause",
"engines": {
"node": ">=12"
},
@@ -14309,6 +14316,12 @@
"integrity": "sha512-1PpuqJUFWoXZ1E54m8bsLPVYwIVCRzvaL+n5cjigGga4z854abDnFRc+cTa2th4S79kyGqya/1xoR7h+Y5G5lg==",
"dev": true
},
+ "node_modules/email-addresses": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz",
+ "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==",
+ "license": "MIT"
+ },
"node_modules/emittery": {
"version": "0.13.1",
"resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz",
@@ -16732,6 +16745,32 @@
"node": ">=10"
}
},
+ "node_modules/filename-reserved-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz",
+ "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/filenamify": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz",
+ "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==",
+ "license": "MIT",
+ "dependencies": {
+ "filename-reserved-regex": "^2.0.0",
+ "strip-outer": "^1.0.1",
+ "trim-repeated": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/filesize": {
"version": "8.0.7",
"resolved": "https://registry.npmjs.org/filesize/-/filesize-8.0.7.tgz",
@@ -16790,7 +16829,6 @@
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz",
"integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==",
- "dev": true,
"dependencies": {
"commondir": "^1.0.1",
"make-dir": "^3.0.2",
@@ -16807,7 +16845,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
"integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
- "dev": true,
"dependencies": {
"locate-path": "^5.0.0",
"path-exists": "^4.0.0"
@@ -16820,7 +16857,6 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
"integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
- "dev": true,
"dependencies": {
"p-locate": "^4.1.0"
},
@@ -16832,7 +16868,6 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
"integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
- "dev": true,
"dependencies": {
"p-try": "^2.0.0"
},
@@ -16847,7 +16882,6 @@
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
"integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
- "dev": true,
"dependencies": {
"p-limit": "^2.2.0"
},
@@ -16859,7 +16893,6 @@
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
"integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
- "dev": true,
"dependencies": {
"find-up": "^4.0.0"
},
@@ -17247,7 +17280,6 @@
"version": "11.2.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
"integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
- "dev": true,
"dependencies": {
"graceful-fs": "^4.2.0",
"jsonfile": "^6.0.1",
@@ -17296,8 +17328,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",
@@ -17487,6 +17518,108 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/gh-pages": {
+ "version": "6.1.1",
+ "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.1.1.tgz",
+ "integrity": "sha512-upnohfjBwN5hBP9w2dPE7HO5JJTHzSGMV1JrLrHvNuqmjoYHg6TBrCcnEoorjG/e0ejbuvnwyKMdTyM40PEByw==",
+ "license": "MIT",
+ "dependencies": {
+ "async": "^3.2.4",
+ "commander": "^11.0.0",
+ "email-addresses": "^5.0.0",
+ "filenamify": "^4.3.0",
+ "find-cache-dir": "^3.3.1",
+ "fs-extra": "^11.1.1",
+ "globby": "^6.1.0"
+ },
+ "bin": {
+ "gh-pages": "bin/gh-pages.js",
+ "gh-pages-clean": "bin/gh-pages-clean.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/gh-pages/node_modules/array-union": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz",
+ "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==",
+ "license": "MIT",
+ "dependencies": {
+ "array-uniq": "^1.0.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/gh-pages/node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/gh-pages/node_modules/commander": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz",
+ "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/gh-pages/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Glob versions prior to v9 are no longer supported",
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/gh-pages/node_modules/globby": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz",
+ "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==",
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^1.0.1",
+ "glob": "^7.0.3",
+ "object-assign": "^4.0.1",
+ "pify": "^2.0.0",
+ "pinkie-promise": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/gh-pages/node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
"node_modules/giget": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz",
@@ -17658,8 +17791,7 @@
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
- "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
- "dev": true
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
},
"node_modules/graphemer": {
"version": "1.4.0",
@@ -18367,7 +18499,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"
@@ -18376,8 +18507,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",
@@ -23203,7 +23333,6 @@
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz",
"integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==",
- "dev": true,
"dependencies": {
"universalify": "^2.0.0"
},
@@ -23606,7 +23735,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
- "dev": true,
"dependencies": {
"semver": "^6.0.0"
},
@@ -24692,7 +24820,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"
}
@@ -24898,7 +25025,6 @@
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
"integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
- "dev": true,
"engines": {
"node": ">=6"
}
@@ -25002,7 +25128,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
- "dev": true,
"engines": {
"node": ">=8"
}
@@ -25011,7 +25136,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"
}
@@ -25141,7 +25265,27 @@
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
"integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
- "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinkie": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz",
+ "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinkie-promise": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz",
+ "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==",
+ "license": "MIT",
+ "dependencies": {
+ "pinkie": "^2.0.0"
+ },
"engines": {
"node": ">=0.10.0"
}
@@ -30070,7 +30214,6 @@
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
- "dev": true,
"bin": {
"semver": "bin/semver.js"
}
@@ -31062,6 +31205,27 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/strip-outer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz",
+ "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/strip-outer/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/style-inject": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/style-inject/-/style-inject-0.3.0.tgz",
@@ -31947,6 +32111,27 @@
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"dev": true
},
+ "node_modules/trim-repeated": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz",
+ "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==",
+ "license": "MIT",
+ "dependencies": {
+ "escape-string-regexp": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/trim-repeated/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
"node_modules/tryer": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/tryer/-/tryer-1.0.1.tgz",
@@ -32568,7 +32753,6 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
- "dev": true,
"engines": {
"node": ">= 10.0.0"
}
@@ -34044,8 +34228,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 7e5bdc463..4998ab257 100644
--- a/package.json
+++ b/package.json
@@ -8,7 +8,9 @@
"build": "craco build",
"test": "craco test --transformIgnorePatterns \"node_modules/(?!axios)/\"",
"storybook": "storybook dev -p 6006",
- "build-storybook": "storybook build"
+ "build-storybook": "storybook build",
+ "predeploy": "npm run build",
+ "deploy": "gh-pages -d build"
},
"browserslist": {
"production": [
@@ -29,7 +31,9 @@
"@emotion/styled": "^11.11.0",
"@tanstack/react-query": "^5.24.1",
"axios": "^1.6.7",
+ "dotenv": "^16.4.5",
"framer-motion": "^11.0.6",
+ "gh-pages": "^6.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-error-boundary": "^4.0.12",
@@ -89,5 +93,6 @@
},
"overrides": {
"react-refresh": "0.11.0"
- }
+ },
+ "homepage": "https://KimJi-An.github.io/react-deploy"
}
diff --git a/src/App.tsx b/src/App.tsx
index 24715e671..45491ffb1 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -2,6 +2,7 @@ import { ChakraProvider } from '@chakra-ui/react';
import { QueryClientProvider } from '@tanstack/react-query';
import { queryClient } from './api/instance';
+import { ApiProvider } from './provider/Api';
import { AuthProvider } from './provider/Auth';
import { Routes } from './routes';
@@ -9,9 +10,11 @@ const App = () => {
return (
-
-
-
+
+
+
+
+
);
diff --git a/src/api/auth.ts b/src/api/auth.ts
index 42791565a..b945f1d2a 100644
--- a/src/api/auth.ts
+++ b/src/api/auth.ts
@@ -1,7 +1,7 @@
import { fetchInstance } from './instance';
export const register = async (email: string, password: string) => {
- const response = await fetchInstance.post('/api/members/register', {
+ const response = await fetchInstance.post(`/api/members/register`, {
email,
password,
});
@@ -9,7 +9,7 @@ export const register = async (email: string, password: string) => {
};
export const login = async (email: string, password: string) => {
- const response = await fetchInstance.post('/api/members/login', {
+ const response = await fetchInstance.post(`/api/members/login`, {
email,
password,
});
diff --git a/src/api/hooks/auth.mock.ts b/src/api/hooks/auth.mock.ts
index 4853553fe..7094dce07 100644
--- a/src/api/hooks/auth.mock.ts
+++ b/src/api/hooks/auth.mock.ts
@@ -3,21 +3,29 @@ import { rest } from 'msw';
const VALID_EMAIL = 'test@example.com';
const VALID_PASSWORD = 'password1234';
-export const authMockHandler = [
- rest.post('https://api.example.com/api/members/register', async (req, res, ctx) => {
- const { email, password } = await req.json<{ email: string; password: string }>();
+type RegisterResponse = {
+ message: string;
+}
- if (!email || !password) {
- return res(ctx.status(400), ctx.json({ message: 'Invalid input' }));
- }
+type LoginRequest = {
+ email: string;
+ password: string;
+}
- return res(ctx.status(201), ctx.json({ email, token: 'fake-token' }));
+type LoginResponse = {
+ token: string;
+}
+
+export const authMockHandler = [
+ rest.post(`/api/members/register`, async (_, res, ctx) => {
+ alert('회원가입이 완료되었습니다.');
+ return res(ctx.status(201), ctx.json({ message: 'User registered successfully' }));
}),
- rest.post('https://api.example.com/api/members/login', async (req, res, ctx) => {
- const { email, password } = await req.json<{ email: string; password: string }>();
+ rest.post('https://api.example.com/api/members/login', async (req, res, ctx) => {
+ const { email, password } = await req.json();
if (email === VALID_EMAIL && password === VALID_PASSWORD) {
- return res(ctx.status(200), ctx.json({ email, token: 'fake-token' }));
+ return res(ctx.status(200), ctx.json({ token: 'fake-token' }));
}
return res(ctx.status(403), ctx.json({ message: 'Invalid email or password' }));
diff --git a/src/api/hooks/categories.mock.ts b/src/api/hooks/categories.mock.ts
index f8d65843c..5ff47d3f0 100644
--- a/src/api/hooks/categories.mock.ts
+++ b/src/api/hooks/categories.mock.ts
@@ -2,13 +2,21 @@ import { rest } from 'msw';
import { getCategoriesPath } from './useGetCategorys';
+type Category = {
+ id: number;
+ name: string;
+ description: string;
+ color: string;
+ imageUrl: string;
+}
+
export const categoriesMockHandler = [
rest.get(getCategoriesPath(), (_, res, ctx) => {
- return res(ctx.json(CATEGORIES_RESPONSE_DATA));
+ return res(ctx.status(200), ctx.json(CATEGORIES_RESPONSE_DATA));
}),
];
-const CATEGORIES_RESPONSE_DATA = [
+const CATEGORIES_RESPONSE_DATA: Category[] = [
{
id: 2920,
name: '생일',
diff --git a/src/api/hooks/points.mock.ts b/src/api/hooks/points.mock.ts
new file mode 100644
index 000000000..69ba17487
--- /dev/null
+++ b/src/api/hooks/points.mock.ts
@@ -0,0 +1,16 @@
+import { rest } from 'msw';
+
+type PointResponse = {
+ point: number;
+}
+
+export const pointMockHandlers = [
+ rest.get('https://api.example.com/api/member/point', (req, res, ctx) => {
+ const authHeader = req.headers.get('Authorization');
+ if (!authHeader) {
+ return res(ctx.status(401));
+ }
+
+ return res(ctx.status(200), ctx.json({ point: 1000 }));
+ }),
+];
diff --git a/src/api/hooks/products.mock.ts b/src/api/hooks/products.mock.ts
index 6cef11235..1f6a72b47 100644
--- a/src/api/hooks/products.mock.ts
+++ b/src/api/hooks/products.mock.ts
@@ -4,13 +4,47 @@ import { getProductDetailPath } from './useGetProductDetail';
import { getProductOptionsPath } from './useGetProductOptions';
import { getProductsPath } from './useGetProducts';
+type Product = {
+ id: number;
+ name: string;
+ imageUrl: string;
+ price: number;
+ categoryId: string;
+}
+
+type Pageable = {
+ offset: number;
+ pageNumber: number;
+ pageSize: number;
+ unpaged: boolean;
+ paged: boolean;
+ sort: {
+ empty: boolean;
+ unsorted: boolean;
+ sorted: boolean;
+ };
+ last: boolean;
+ totalPages: number;
+ totalElements: number;
+ size: number;
+ number: number;
+ first: boolean;
+ numberOfElements: number;
+ empty: boolean;
+}
+
+type ProductsResponse = {
+ content: Product[];
+ pageable: Pageable;
+}
+
export const productsMockHandler = [
rest.get(
getProductsPath({
categoryId: '2920',
}),
(_, res, ctx) => {
- return res(ctx.json(PRODUCTS_MOCK_DATA));
+ return res(ctx.status(200), ctx.json(PRODUCTS_MOCK_DATA));
},
),
rest.get(
@@ -18,33 +52,32 @@ export const productsMockHandler = [
categoryId: '2930',
}),
(_, res, ctx) => {
- return res(ctx.json(PRODUCTS_MOCK_DATA));
+ return res(ctx.status(200), ctx.json(PRODUCTS_MOCK_DATA));
},
),
rest.get(getProductDetailPath(':productId'), (_, res, ctx) => {
- return res(ctx.json(PRODUCTS_MOCK_DATA.content[0]));
+ return res(ctx.status(200), ctx.json(PRODUCTS_MOCK_DATA.content[0]));
}),
rest.get(getProductOptionsPath(':productId'), (_, res, ctx) => {
return res(
+ ctx.status(200),
ctx.json([
{
id: 1,
name: 'Option A',
quantity: 10,
- productId: 1,
},
{
id: 2,
name: 'Option B',
quantity: 20,
- productId: 1,
},
]),
);
}),
];
-const PRODUCTS_MOCK_DATA = {
+const PRODUCTS_MOCK_DATA: ProductsResponse = {
content: [
{
id: 3245119,
@@ -52,6 +85,7 @@ const PRODUCTS_MOCK_DATA = {
imageUrl:
'https://st.kakaocdn.net/product/gift/product/20240215083306_8e1db057580145829542463a84971ae3.png',
price: 145000,
+ categoryId: '2920',
},
{
id: 2263833,
@@ -59,6 +93,7 @@ const PRODUCTS_MOCK_DATA = {
imageUrl:
'https://st.kakaocdn.net/product/gift/product/20200513102805_4867c1e4a7ae43b5825e9ae14e2830e3.png',
price: 100000,
+ categoryId: '2920',
},
{
id: 6502823,
@@ -66,6 +101,7 @@ const PRODUCTS_MOCK_DATA = {
imageUrl:
'https://st.kakaocdn.net/product/gift/product/20240215112140_11f857e972bc4de6ac1d2f1af47ce182.jpg',
price: 108000,
+ categoryId: '2920',
},
{
id: 1181831,
@@ -73,6 +109,7 @@ const PRODUCTS_MOCK_DATA = {
imageUrl:
'https://st.kakaocdn.net/product/gift/product/20240214150740_ad25267defa64912a7c030a7b57dc090.jpg',
price: 122000,
+ categoryId: '2920',
},
{
id: 1379982,
@@ -80,10 +117,27 @@ const PRODUCTS_MOCK_DATA = {
imageUrl:
'https://st.kakaocdn.net/product/gift/product/20240118135914_a6e1a7442ea04aa49add5e02ed62b4c3.jpg',
price: 133000,
+ categoryId: '2920',
},
],
- number: 0,
- totalElements: 5,
- size: 10,
- last: true,
+ pageable: {
+ offset: 0,
+ pageNumber: 0,
+ pageSize: 10,
+ unpaged: false,
+ paged: true,
+ sort: {
+ empty: false,
+ unsorted: true,
+ sorted: false,
+ },
+ last: true,
+ totalPages: 1,
+ totalElements: 5,
+ size: 10,
+ number: 0,
+ first: true,
+ numberOfElements: 5,
+ empty: false,
+ },
};
diff --git a/src/api/hooks/useGetCategorys.ts b/src/api/hooks/useGetCategorys.ts
index d93e4fc95..acb2b6997 100644
--- a/src/api/hooks/useGetCategorys.ts
+++ b/src/api/hooks/useGetCategorys.ts
@@ -2,11 +2,11 @@ import { useQuery } from '@tanstack/react-query';
import type { CategoryData } from '@/types';
-import { BASE_URL, fetchInstance } from '../instance';
+import { fetchInstance } from '../instance';
export type CategoryResponseData = CategoryData[];
-export const getCategoriesPath = () => `${BASE_URL}/api/categories`;
+export const getCategoriesPath = () => `/api/categories`;
const categoriesQueryKey = [getCategoriesPath()];
export const getCategories = async () => {
diff --git a/src/api/hooks/useGetPoints.ts b/src/api/hooks/useGetPoints.ts
new file mode 100644
index 000000000..c6e128438
--- /dev/null
+++ b/src/api/hooks/useGetPoints.ts
@@ -0,0 +1,27 @@
+import { useEffect, useState } from 'react';
+
+import { fetchInstance } from '../instance';
+
+export const useGetPoints = () => {
+ const [points, setPoints] = useState(() => {
+ const savedPoints = sessionStorage.getItem('points');
+ return savedPoints ? parseInt(savedPoints, 10) : 0;
+ });
+
+ useEffect(() => {
+ if (!sessionStorage.getItem('points')) {
+ const fetchPoints = async () => {
+ try {
+ const response = await fetchInstance.get('/api/member/point');
+ setPoints(response.data.point);
+ sessionStorage.setItem('points', response.data.point);
+ } catch (error) {
+ console.error('Failed to fetch points', error);
+ }
+ };
+ fetchPoints();
+ }
+ }, []);
+
+ return points;
+};
diff --git a/src/api/hooks/useGetProductDetail.ts b/src/api/hooks/useGetProductDetail.ts
index 539de0196..5d0e92df9 100644
--- a/src/api/hooks/useGetProductDetail.ts
+++ b/src/api/hooks/useGetProductDetail.ts
@@ -2,7 +2,7 @@ import { useSuspenseQuery } from '@tanstack/react-query';
import type { ProductData } from '@/types';
-import { BASE_URL, fetchInstance } from '../instance';
+import { fetchInstance } from '../instance';
export type ProductDetailRequestParams = {
productId: string;
@@ -12,7 +12,7 @@ type Props = ProductDetailRequestParams;
export type GoodsDetailResponseData = ProductData;
-export const getProductDetailPath = (productId: string) => `${BASE_URL}/api/products/${productId}`;
+export const getProductDetailPath = (productId: string) => `/api/products/${productId}`;
export const getProductDetail = async (params: ProductDetailRequestParams) => {
const response = await fetchInstance.get(
diff --git a/src/api/hooks/useGetProductOptions.ts b/src/api/hooks/useGetProductOptions.ts
index a3bdc538f..dda023a2c 100644
--- a/src/api/hooks/useGetProductOptions.ts
+++ b/src/api/hooks/useGetProductOptions.ts
@@ -2,15 +2,14 @@ import { useSuspenseQuery } from '@tanstack/react-query';
import type { ProductOptionsData } from '@/types';
-import { BASE_URL, fetchInstance } from '../instance';
+import { fetchInstance } from '../instance';
import type { ProductDetailRequestParams } from './useGetProductDetail';
type Props = ProductDetailRequestParams;
export type ProductOptionsResponseData = ProductOptionsData[];
-export const getProductOptionsPath = (productId: string) =>
- `${BASE_URL}/api/products/${productId}/options`;
+export const getProductOptionsPath = (productId: string) => `/api/products/${productId}/options`;
export const getProductOptions = async (params: ProductDetailRequestParams) => {
const response = await fetchInstance.get(
diff --git a/src/api/hooks/useGetProducts.ts b/src/api/hooks/useGetProducts.ts
index 432f90d93..d917d43fe 100644
--- a/src/api/hooks/useGetProducts.ts
+++ b/src/api/hooks/useGetProducts.ts
@@ -6,7 +6,6 @@ import {
import type { ProductData } from '@/types';
-import { BASE_URL } from '../instance';
import { fetchInstance } from './../instance/index';
type RequestParams = {
@@ -26,10 +25,31 @@ type ProductsResponseData = {
type ProductsResponseRawData = {
content: ProductData[];
- number: number;
+ pageable: {
+ offset: number;
+ sort: {
+ empty: boolean;
+ unsorted: boolean;
+ sorted: boolean;
+ };
+ unpaged: boolean;
+ paged: boolean;
+ pageSize: number;
+ pageNumber: number;
+ };
+ last: boolean;
+ totalPages: number;
totalElements: number;
size: number;
- last: boolean;
+ number: number;
+ sort: {
+ empty: boolean;
+ unsorted: boolean;
+ sorted: boolean;
+ };
+ first: boolean;
+ numberOfElements: number;
+ empty: boolean;
};
export const getProductsPath = ({ categoryId, pageToken, maxResults }: RequestParams) => {
@@ -40,7 +60,7 @@ export const getProductsPath = ({ categoryId, pageToken, maxResults }: RequestPa
if (pageToken) params.append('page', pageToken);
if (maxResults) params.append('size', maxResults.toString());
- return `${BASE_URL}/api/products?${params.toString()}`;
+ return `/api/products?${params.toString()}`;
};
export const getProducts = async (params: RequestParams): Promise => {
diff --git a/src/api/hooks/useGetWishlist.ts b/src/api/hooks/useGetWishlist.ts
new file mode 100644
index 000000000..5256040df
--- /dev/null
+++ b/src/api/hooks/useGetWishlist.ts
@@ -0,0 +1,93 @@
+import {
+ type InfiniteData,
+ useInfiniteQuery,
+ type UseInfiniteQueryResult,
+} from '@tanstack/react-query';
+import axios from 'axios';
+
+import { useApi } from '@/provider/Api';
+import { useAuth } from '@/provider/Auth';
+
+type WishlistItem = {
+ id: number;
+ name: string;
+ price: number;
+ imageUrl: string;
+};
+
+type WishlistResponseData = {
+ content: WishlistItem[];
+ pageable: {
+ sort: {
+ sorted: boolean;
+ empty: boolean;
+ };
+ pageNumber: number;
+ pageSize: number;
+ offset: number;
+ unpaged: boolean;
+ paged: boolean;
+ };
+ totalPages: number;
+ totalElements: number;
+ last: boolean;
+ number: number;
+ size: number;
+ numberOfElements: number;
+ first: boolean;
+ empty: boolean;
+};
+
+type RequestParams = {
+ pageToken?: string;
+ maxResults?: number;
+};
+
+export const getWishlistPath = ({ pageToken, maxResults }: RequestParams, apiUrl: string) => {
+ const params = new URLSearchParams();
+ params.append('sort', 'createdDate,desc');
+ if (pageToken) params.append('page', pageToken);
+ if (maxResults) params.append('size', maxResults.toString());
+
+ return `${apiUrl}api/wishes?${params.toString()}`;
+};
+
+export const getWishlist = async (
+ params: RequestParams,
+ token: string,
+ apiUrl: string,
+): Promise => {
+ const url = getWishlistPath(params, apiUrl);
+
+ const response = await axios.get(url, {
+ headers: {
+ 'Content-Type': 'application/json',
+ 'Authorization': `Bearer ${JSON.parse(token).token}`,
+ },
+ });
+
+ return response.data;
+};
+
+export const useGetWishlist = ({
+ maxResults = 10,
+ initPageToken,
+}: {
+ maxResults?: number;
+ initPageToken?: string;
+}): UseInfiniteQueryResult> => {
+ const authInfo = useAuth();
+ const { apiUrl } = useApi();
+
+ return useInfiniteQuery({
+ queryKey: ['wishlist', maxResults, initPageToken],
+ queryFn: async ({ pageParam = initPageToken }) => {
+ if (!authInfo?.token) {
+ throw new Error('Authentication token is missing');
+ }
+ return getWishlist({ pageToken: pageParam, maxResults }, JSON.parse(authInfo.token).token, apiUrl);
+ },
+ initialPageParam: initPageToken,
+ getNextPageParam: (lastPage) => (lastPage.last ? undefined : (lastPage.number + 1).toString()),
+ });
+};
diff --git a/src/api/hooks/wishlist.mock.ts b/src/api/hooks/wishlist.mock.ts
index fa1d155c3..3ac4e7703 100644
--- a/src/api/hooks/wishlist.mock.ts
+++ b/src/api/hooks/wishlist.mock.ts
@@ -1,67 +1,213 @@
import { rest } from 'msw';
+type Product = {
+ id: number;
+ name: string;
+ price: number;
+ imageUrl: string;
+}
+
+type WishlistItem = {
+ id: number;
+ product: Product;
+}
+
+type PageableResponse = {
+ content: T[];
+ totalPages: number;
+ totalElements: number;
+ number: number;
+ size: number;
+ first: boolean;
+ last: boolean;
+}
+
+const WISHLIST_MOCK_DATA: WishlistItem[] = [
+ {
+ id: 1,
+ product: {
+ id: 1,
+ name: 'Product A',
+ price: 100,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 2,
+ product: {
+ id: 2,
+ name: 'Product B',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 3,
+ product: {
+ id: 3,
+ name: 'Product C',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 4,
+ product: {
+ id: 4,
+ name: 'Product D',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 5,
+ product: {
+ id: 5,
+ name: 'Product E',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 6,
+ product: {
+ id: 6,
+ name: 'Product F',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 7,
+ product: {
+ id: 7,
+ name: 'Product G',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 8,
+ product: {
+ id: 8,
+ name: 'Product H',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 9,
+ product: {
+ id: 9,
+ name: 'Product I',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 10,
+ product: {
+ id: 10,
+ name: 'Product J',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+ {
+ id: 11,
+ product: {
+ id: 11,
+ name: 'Product K',
+ price: 150,
+ imageUrl:
+ 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
+ },
+ },
+];
+
+const getPagedData = (page: number, size: number) => {
+ const start = page * size;
+ const end = start + size;
+ return WISHLIST_MOCK_DATA.slice(start, end);
+};
+
export const wishlistMockHandler = [
- rest.get('/api/wishes', async (req, res, ctx) => {
- const token = req.headers.get('Authorization');
+ rest.get>('/api/wishes', (req, res, ctx) => {
+ const page = parseInt(req.url.searchParams.get('page') || '0', 10);
+ const size = parseInt(req.url.searchParams.get('size') || '10', 10);
- if (!token) {
- return res(ctx.status(401), ctx.json({ message: 'Invalid or missing token' }));
- }
+ const pagedContent = getPagedData(page, size);
+ const totalElements = WISHLIST_MOCK_DATA.length;
+ const totalPages = Math.ceil(totalElements / size);
- return res(ctx.status(200), ctx.json(WISHLIST_MOCK_DATA));
+ return res(
+ ctx.status(200),
+ ctx.json({
+ content: pagedContent.map(item => ({
+ id: item.id,
+ name: item.product.name,
+ price: item.product.price,
+ imageUrl: item.product.imageUrl,
+ })),
+ totalPages: totalPages,
+ totalElements: totalElements,
+ number: page,
+ size: size,
+ first: page === 0,
+ last: page === totalPages - 1,
+ })
+ );
}),
rest.delete('/api/wishes/:wishId', (req, res, ctx) => {
const { wishId } = req.params;
- const wishIndex = WISHLIST_MOCK_DATA.content.findIndex((item) => item.id === Number(wishId));
- if (wishIndex !== -1) {
- WISHLIST_MOCK_DATA.content.splice(wishIndex, 1);
- return res(ctx.status(204));
+ const index = WISHLIST_MOCK_DATA.findIndex(item => item.id.toString() === wishId);
+
+ if (index === -1) {
+ return res(ctx.status(404), ctx.json({ message: 'Wish not found' }));
}
- return res(ctx.status(404), ctx.json({ message: 'Wish not found' }));
+
+ WISHLIST_MOCK_DATA.splice(index, 1);
+ return res(ctx.status(204));
}),
-];
+ rest.post(`/api/wishes/:productId`, (req, res, ctx) => {
+ const { productId } = req.params;
+ const token = req.headers.get('Authorization');
-const WISHLIST_MOCK_DATA = {
- content: [
- {
- id: 1,
- product: {
- id: 1,
- name: 'Product A',
- price: 100,
- imageUrl:
- 'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
- },
- },
- {
- id: 2,
+ if (!token) {
+ return res(ctx.status(401), ctx.json({ message: 'Invalid or missing token' }));
+ }
+
+ const existingWish = WISHLIST_MOCK_DATA.find(
+ (item) => item.product.id === Number(productId),
+ );
+
+ if (existingWish) {
+ return res(ctx.status(400), ctx.json({ message: 'Product already in wishlist' }));
+ }
+
+ const newProduct = {
+ id: WISHLIST_MOCK_DATA.length + 1,
product: {
- id: 2,
- name: 'Product B',
+ id: Number(productId),
+ name: `Product ${String.fromCharCode(65 + WISHLIST_MOCK_DATA.length)}`,
price: 150,
imageUrl:
'https://img1.daumcdn.net/thumb/S104x104/?fname=https%3A%2F%2Ft1.daumcdn.net%2Fgift%2Fhome%2Ftheme%2F292020231106_MXMUB.png',
},
- },
- ],
- pageable: {
- sort: {
- sorted: true,
- unsorted: false,
- empty: false,
- },
- pageNumber: 0,
- pageSize: 10,
- offset: 0,
- unpaged: false,
- paged: true,
- },
- totalPages: 5,
- totalElements: 50,
- last: false,
- number: 0,
- size: 10,
- numberOfElements: 2,
- first: true,
- empty: false,
-};
+ };
+
+ WISHLIST_MOCK_DATA.push(newProduct);
+
+ return res(ctx.status(201), ctx.json(newProduct));
+ }),
+];
diff --git a/src/api/instance/index.ts b/src/api/instance/index.ts
index b83ca1407..c301ec70b 100644
--- a/src/api/instance/index.ts
+++ b/src/api/instance/index.ts
@@ -1,26 +1,24 @@
import { QueryClient } from '@tanstack/react-query';
-import type { AxiosInstance, AxiosRequestConfig } from 'axios';
import axios from 'axios';
-const initInstance = (config: AxiosRequestConfig): AxiosInstance => {
+import { backend } from '@/config/backendConfig';
+
+const initInstance = () => {
+ const apiUrl = localStorage.getItem('apiUrl') || backend.backend3;
+
const instance = axios.create({
+ baseURL: apiUrl,
timeout: 5000,
- ...config,
headers: {
Accept: 'application/json',
'Content-Type': 'application/json',
- ...config.headers,
},
});
return instance;
};
-export const BASE_URL = 'https://api.example.com';
-// TODO: 추후 서버 API 주소 변경 필요
-export const fetchInstance = initInstance({
- baseURL: 'https://api.example.com',
-});
+export const fetchInstance = initInstance();
export const queryClient = new QueryClient({
defaultOptions: {
diff --git a/src/components/features/Goods/Detail/OptionSection.tsx b/src/components/features/Goods/Detail/OptionSection.tsx
index 74a6b61da..c6bb400aa 100644
--- a/src/components/features/Goods/Detail/OptionSection.tsx
+++ b/src/components/features/Goods/Detail/OptionSection.tsx
@@ -8,6 +8,7 @@ import {
} from '@/api/hooks/useGetProductDetail';
import { useGetProductOptions } from '@/api/hooks/useGetProductOptions';
import { Button } from '@/components/common/Button';
+import { useApi } from '@/provider/Api';
import { useAuth } from '@/provider/Auth';
import { getDynamicPath, RouterPath } from '@/routes/path';
import { orderHistorySessionStorage } from '@/utils/storage';
@@ -19,7 +20,7 @@ type Props = ProductDetailRequestParams;
export const OptionSection = ({ productId }: Props) => {
const { data: detail } = useGetProductDetail({ productId });
const { data: options } = useGetProductOptions({ productId });
-
+ const { apiUrl } = useApi();
const [countAsString, setCountAsString] = useState('1');
const totalPrice = useMemo(() => {
return detail.price * Number(countAsString);
@@ -47,14 +48,22 @@ export const OptionSection = ({ productId }: Props) => {
const handleAddToWishlist = async () => {
try {
- await fetch('/api/wishes', {
- method: 'POST',
- headers: {
- 'Content-Type': 'application/json',
- },
- body: JSON.stringify({ productId }),
- });
- alert('관심 등록 완료');
+ const tokenString = sessionStorage.getItem('authToken');
+
+ if (tokenString) {
+ const token = JSON.parse(tokenString).token;
+
+ await fetch(`${apiUrl}api/wishes/${productId}`, {
+ credentials: 'include',
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({ productId }),
+ });
+ alert('관심 등록 완료');
+ }
} catch (error) {
console.error(error);
alert('관심 상품 등록에 실패했습니다.');
diff --git a/src/components/features/Layout/Header.tsx b/src/components/features/Layout/Header.tsx
index eeac55071..9fbb2466e 100644
--- a/src/components/features/Layout/Header.tsx
+++ b/src/components/features/Layout/Header.tsx
@@ -2,17 +2,24 @@ import styled from '@emotion/styled';
import { Link, useNavigate } from 'react-router-dom';
import { Container } from '@/components/common/layouts/Container';
+import { backend } from '@/config/backendConfig';
+import { useApi } from '@/provider/Api';
import { useAuth } from '@/provider/Auth';
import { getDynamicPath, RouterPath } from '@/routes/path';
export const Header = () => {
const navigate = useNavigate();
const authInfo = useAuth();
+ const { apiUrl, setApiUrl } = useApi();
const handleLogin = () => {
navigate(getDynamicPath.login());
};
+ const handleApiChange = (event: React.ChangeEvent) => {
+ setApiUrl(event.target.value);
+ };
+
return (
@@ -23,6 +30,11 @@ export const Header = () => {
/>
+
+ 김민지
+ 박상우
+ 배민수
+
{authInfo ? (
navigate(RouterPath.myAccount)}>내 계정
) : (
@@ -49,7 +61,13 @@ export const Wrapper = styled.header`
const Logo = styled.img`
height: ${HEADER_HEIGHT};
`;
-const RightWrapper = styled.div``;
+const RightWrapper = styled.div`
+ width: 100%;
+ display: flex;
+ justify-content: flex-end;
+ align-items: center;
+ gap: 20px;
+`;
const LinkButton = styled.p`
align-items: center;
@@ -58,3 +76,17 @@ const LinkButton = styled.p`
text-decoration: none;
cursor: pointer;
`;
+
+const ApiSelector = styled.select`
+ width: 200px;
+ height: 40px;
+ border: 2px solid #444;
+ border-radius: 4px;
+ padding: 5px 10px;
+ font-size: 16px;
+ cursor: pointer;
+
+ &:hover {
+ border-color: #888;
+ }
+`;
diff --git a/src/components/features/Wishlist/index.tsx b/src/components/features/Wishlist/index.tsx
index 9227cc3a3..b5e590abf 100644
--- a/src/components/features/Wishlist/index.tsx
+++ b/src/components/features/Wishlist/index.tsx
@@ -1,52 +1,44 @@
import { Box, Button, Heading, List, ListItem, Text } from '@chakra-ui/react';
import { useEffect, useState } from 'react';
+import { useGetWishlist } from '@/api/hooks/useGetWishlist';
+import { VisibilityLoader } from '@/components/common/VisibilityLoader';
+import { useApi } from '@/provider/Api';
import { useAuth } from '@/provider/Auth';
type WishlistItem = {
id: number;
- product: {
- id: number;
- name: string;
- price: number;
- imageUrl: string;
- };
+ name: string;
+ price: number;
+ imageUrl: string;
};
export const Wishlist = () => {
+ const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useGetWishlist({
+ maxResults: 10,
+ });
const authInfo = useAuth();
- const [wishlist, setWishList] = useState([]);
+ const { apiUrl } = useApi();
+ const [wishlist, setWishlist] = useState([]);
useEffect(() => {
- const fetchData = async () => {
- if (authInfo?.token) {
- try {
- const response = await fetch('/api/wishes', {
- headers: {
- Authorization: `Bearer ${authInfo.token}`,
- },
- });
- const wishData = await response.json();
- setWishList(wishData.content);
- } catch (error) {
- console.error(error);
- }
- }
- };
-
- fetchData();
- }, [authInfo]);
+ if (data) {
+ const flattenWishlist = data.pages.flatMap((page) => page.content).flat();
+ setWishlist(flattenWishlist);
+ }
+ }, [data]);
const handleDelete = async (wishId: number) => {
if (authInfo?.token) {
- const response = await fetch(`/api/wishes/${wishId}`, {
+ const token = JSON.parse(authInfo.token).token;
+ const response = await fetch(`${apiUrl}api/wishes/${wishId}`, {
method: 'DELETE',
headers: {
- Authorization: `Bearer ${authInfo.token}`,
+ Authorization: `Bearer ${token}`,
},
});
if (response.status === 204) {
- setWishList((prev) => prev.filter((item) => item.id !== wishId));
+ setWishlist((prevWishlist) => prevWishlist.filter((item) => item.id !== wishId));
} else {
alert('삭제 중 오류가 발생했습니다.');
}
@@ -67,12 +59,12 @@ export const Wishlist = () => {
-
+
- {item.product.name}
+ {item.name}
- {item.product.price}원
+ {item.price}원
handleDelete(item.id)}>삭제
@@ -81,6 +73,15 @@ export const Wishlist = () => {
))}
)}
+ {hasNextPage && (
+ {
+ if (!isFetchingNextPage) {
+ fetchNextPage();
+ }
+ }}
+ />
+ )}
);
diff --git a/src/config/backendConfig.ts b/src/config/backendConfig.ts
new file mode 100644
index 000000000..45700b7e6
--- /dev/null
+++ b/src/config/backendConfig.ts
@@ -0,0 +1,5 @@
+export const backend: { [key: string]: string } = {
+ backend1: 'http://52.78.23.209:8080/',
+ backend2: 'http://3.38.169.232:8080/',
+ backend3: 'http://43.202.41.105:8080/',
+};
diff --git a/src/mocks/browser.ts b/src/mocks/browser.ts
index d3454c161..8d06b1dd1 100644
--- a/src/mocks/browser.ts
+++ b/src/mocks/browser.ts
@@ -2,6 +2,7 @@ import { setupWorker } from 'msw';
import { authMockHandler } from '@/api/hooks/auth.mock';
import { categoriesMockHandler } from '@/api/hooks/categories.mock';
+import { pointMockHandlers } from '@/api/hooks/points.mock';
import { productsMockHandler } from '@/api/hooks/products.mock';
import { wishlistMockHandler } from '@/api/hooks/wishlist.mock';
@@ -10,4 +11,5 @@ export const worker = setupWorker(
...productsMockHandler,
...authMockHandler,
...wishlistMockHandler,
+ ...pointMockHandlers,
);
diff --git a/src/mocks/server.ts b/src/mocks/server.ts
index e4c6ea86a..3d22e3712 100644
--- a/src/mocks/server.ts
+++ b/src/mocks/server.ts
@@ -2,6 +2,7 @@ import { setupServer } from 'msw/node';
import { authMockHandler } from '@/api/hooks/auth.mock';
import { categoriesMockHandler } from '@/api/hooks/categories.mock';
+import { pointMockHandlers } from '@/api/hooks/points.mock';
import { productsMockHandler } from '@/api/hooks/products.mock';
import { wishlistMockHandler } from '@/api/hooks/wishlist.mock';
@@ -10,4 +11,5 @@ export const server = setupServer(
...productsMockHandler,
...authMockHandler,
...wishlistMockHandler,
+ ...pointMockHandlers,
);
diff --git a/src/pages/Login/index.tsx b/src/pages/Login/index.tsx
index 96970fc23..cc7dbc196 100644
--- a/src/pages/Login/index.tsx
+++ b/src/pages/Login/index.tsx
@@ -1,4 +1,5 @@
import styled from '@emotion/styled';
+import axios from 'axios';
import { useState } from 'react';
import { useNavigate, useSearchParams } from 'react-router-dom';
@@ -7,6 +8,7 @@ import KAKAO_LOGO from '@/assets/kakao_logo.svg';
import { Button } from '@/components/common/Button';
import { UnderlineTextField } from '@/components/common/Form/Input/UnderlineTextField';
import { Spacing } from '@/components/common/layouts/Spacing';
+import { useApi } from '@/provider/Api';
import { RouterPath } from '@/routes/path';
import { breakpoints } from '@/styles/variants';
import { authSessionStorage } from '@/utils/storage';
@@ -16,6 +18,7 @@ export const LoginPage = () => {
const [password, setPassword] = useState('');
const [queryParams] = useSearchParams();
const navigate = useNavigate();
+ const { apiUrl } = useApi();
const handleConfirm = async () => {
if (!email || !password) {
@@ -24,10 +27,14 @@ export const LoginPage = () => {
}
try {
- const data = await login(email, password);
+ const token = await login(email, password);
- sessionStorage.setItem('authEmail', data.email);
- authSessionStorage.set(data.token);
+ sessionStorage.setItem('authEmail', email);
+ sessionStorage.setItem('authToken', token);
+ authSessionStorage.set(token);
+
+ const points = await fetchPoints(token);
+ sessionStorage.setItem('points', points.toString());
const redirectUrl = queryParams.get('redirect') ?? `${window.location.origin}/`;
window.location.replace(redirectUrl);
@@ -37,6 +44,21 @@ export const LoginPage = () => {
}
};
+ const fetchPoints = async (token: string) => {
+ try {
+ const response = await axios.get(`${apiUrl}api/member/point`, {
+ headers: {
+ Authorization: `Bearer ${token}`,
+ 'Content-Type': 'application/json',
+ },
+ });
+ return response.data.point;
+ } catch (error) {
+ console.error('Failed to fetch points', error);
+ return 0;
+ }
+ };
+
return (
diff --git a/src/pages/MyAccount/index.tsx b/src/pages/MyAccount/index.tsx
index 5c59297f7..62b13f02b 100644
--- a/src/pages/MyAccount/index.tsx
+++ b/src/pages/MyAccount/index.tsx
@@ -1,5 +1,6 @@
import styled from '@emotion/styled';
+import { useGetPoints } from '@/api/hooks/useGetPoints';
import { Button } from '@/components/common/Button';
import { Spacing } from '@/components/common/layouts/Spacing';
import { Wishlist } from '@/components/features/Wishlist';
@@ -9,9 +10,12 @@ import { authSessionStorage } from '@/utils/storage';
export const MyAccountPage = () => {
const authInfo = useAuth();
+ const points = useGetPoints();
const handleLogout = () => {
authSessionStorage.set(undefined);
+ sessionStorage.removeItem('authEmail');
+ sessionStorage.removeItem('points');
const redirectURL = `${window.location.origin}${RouterPath.home}`;
window.location.replace(redirectURL);
@@ -19,7 +23,8 @@ export const MyAccountPage = () => {
return (
- {authInfo?.name}님 안녕하세요!
+ {authInfo?.name}님 안녕하세요!
+ 보유 포인트 : {points}p
{
const [email, setEmail] = useState('');
@@ -22,12 +21,12 @@ export const RegisterPage = () => {
}
try {
- const data = await register(email, password);
+ const response = await register(email, password);
- sessionStorage.setItem('authEmail', data.email);
- authSessionStorage.set(data.token);
-
- alert('회원가입이 완료되었습니다.');
+ if (response.status === 201) {
+ sessionStorage.setItem('authEmail', email);
+ alert('회원가입이 완료되었습니다.');
+ }
const redirectUrl = queryParams.get('redirect') ?? `${window.location.origin}/`;
return window.location.replace(redirectUrl);
diff --git a/src/provider/Api/index.tsx b/src/provider/Api/index.tsx
new file mode 100644
index 000000000..25f2ee50d
--- /dev/null
+++ b/src/provider/Api/index.tsx
@@ -0,0 +1,37 @@
+import { useQueryClient } from '@tanstack/react-query';
+import type { ReactNode } from 'react';
+import { createContext, useContext, useEffect, useState } from 'react';
+
+import { backend } from '@/config/backendConfig';
+
+interface ApiContextType {
+ apiUrl: string;
+ setApiUrl: (url: string) => void;
+}
+
+const defaultState: ApiContextType = {
+ apiUrl: backend.backend3,
+ setApiUrl: () => {},
+};
+
+const ApiContext = createContext(defaultState);
+
+interface ApiProviderProps {
+ children: ReactNode;
+}
+
+export const ApiProvider = ({ children }: ApiProviderProps) => {
+ const [apiUrl, setApiUrl] = useState(
+ () => localStorage.getItem('apiUrl') || backend.backend3,
+ );
+ const queryClient = useQueryClient();
+
+ useEffect(() => {
+ localStorage.setItem('apiUrl', apiUrl);
+ queryClient.invalidateQueries();
+ }, [apiUrl, queryClient]);
+
+ return {children} ;
+};
+
+export const useApi = () => useContext(ApiContext);
diff --git a/src/provider/Auth/index.tsx b/src/provider/Auth/index.tsx
index 77cbb73a9..d96bdf4ee 100644
--- a/src/provider/Auth/index.tsx
+++ b/src/provider/Auth/index.tsx
@@ -4,7 +4,7 @@ import { createContext, useContext, useEffect, useState } from 'react';
import { authSessionStorage } from '@/utils/storage';
type AuthInfo = {
- id: string;
+ email: string;
name: string;
token: string;
};
@@ -12,21 +12,34 @@ type AuthInfo = {
export const AuthContext = createContext(undefined);
export const AuthProvider = ({ children }: { children: ReactNode }) => {
+ const currentAuthEmail = sessionStorage.getItem('authEmail');
const currentAuthToken = authSessionStorage.get();
- const [isReady, setIsReady] = useState(!currentAuthToken);
- const [authInfo, setAuthInfo] = useState(undefined);
+ const [isReady, setIsReady] = useState(false);
+ const [authInfo, setAuthInfo] = useState(() => {
+ if (currentAuthEmail && currentAuthToken) {
+ const name = currentAuthEmail.split('@')[0];
+ return {
+ email: currentAuthEmail,
+ name: name,
+ token: currentAuthToken,
+ };
+ }
+ return undefined;
+ });
useEffect(() => {
- if (currentAuthToken) {
+ if (currentAuthEmail && currentAuthToken && !authInfo) {
+ const name = currentAuthEmail.split('@')[0];
+
setAuthInfo({
- id: currentAuthToken, // TODO: 임시로 로그인 페이지에서 입력한 이름을 ID, token, name으로 사용
- name: currentAuthToken,
+ email: currentAuthEmail,
+ name: name,
token: currentAuthToken,
});
- setIsReady(true);
}
- }, [currentAuthToken]);
+ setIsReady(true);
+ }, [currentAuthEmail, currentAuthToken, authInfo]);
if (!isReady) return <>>;
return {children} ;
diff --git a/src/types/index.ts b/src/types/index.ts
index 33c397459..2c994ffed 100644
--- a/src/types/index.ts
+++ b/src/types/index.ts
@@ -18,7 +18,6 @@ export type ProductOptionsData = {
id: number;
name: string;
quantity: number;
- productId: number;
};
export type GoodsDetailOptionItemData = {