diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..c3f502a --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 디폴트 무시된 파일 +/shelf/ +/workspace.xml +# 에디터 기반 HTTP 클라이언트 요청 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/humanicare.iml b/.idea/humanicare.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/humanicare.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..03d9549 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..07115cd --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..594f21c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 42a86c8..1e73e38 100755 --- a/package-lock.json +++ b/package-lock.json @@ -12,9 +12,12 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^13.5.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "axios": "^1.8.4", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.17.0", "react-scripts": "5.0.1", + "react-time-picker": "^7.0.0", "web-vitals": "^2.1.4" } }, @@ -3090,6 +3093,15 @@ } } }, + "node_modules/@remix-run/router": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.10.0.tgz", + "integrity": "sha512-Lm+fYpMfZoEucJ7cMxgt4dYt8jLfbpwRCzAjm9UgSLOkmlqo9gupxt6YX3DY0Fk155NT9l17d/ydi+964uS9Lw==", + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rollup/plugin-babel": { "version": "5.3.1", "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz", @@ -4310,6 +4322,15 @@ "@xtuc/long": "4.2.2" } }, + "node_modules/@wojtekmaj/date-utils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@wojtekmaj/date-utils/-/date-utils-1.5.1.tgz", + "integrity": "sha512-+i7+JmNiE/3c9FKxzWFi2IjRJ+KzZl1QPu6QNrsgaa2MuBgXvUy4gA1TVzf/JMdIIloB76xSKikTWuyYAIVLww==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/date-utils?sponsor=1" + } + }, "node_modules/@xtuc/ieee754": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", @@ -4897,6 +4918,32 @@ "node": ">=4" } }, + "node_modules/axios": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz", + "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", @@ -5635,6 +5682,15 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -6572,6 +6628,15 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/detect-element-overflow": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/detect-element-overflow/-/detect-element-overflow-1.4.2.tgz", + "integrity": "sha512-4m6cVOtvm/GJLjo7WFkPfwXoEIIbM7GQwIh4WEa4g7IsNi1YzwUsGL5ApNLrrHL29bHeNeQ+/iZhw+YHqgE2Fw==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/detect-element-overflow?sponsor=1" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -8575,6 +8640,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-user-locale": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/get-user-locale/-/get-user-locale-2.3.2.tgz", + "integrity": "sha512-O2GWvQkhnbDoWFUJfaBlDIKUEdND8ATpBXD6KXcbhxlfktyD/d8w6mkzM/IlQEqGZAMz/PW6j6Hv53BiigKLUQ==", + "license": "MIT", + "dependencies": { + "mem": "^8.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/get-user-locale?sponsor=1" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -11262,6 +11339,15 @@ "semver": "bin/semver.js" } }, + "node_modules/make-event-props": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/make-event-props/-/make-event-props-1.6.2.tgz", + "integrity": "sha512-iDwf7mA03WPiR8QxvcVHmVWEPfMY1RZXerDVNCRYW7dUr2ppH3J58Rwb39/WG39yTZdRSxr3x+2v22tvI0VEvA==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/make-event-props?sponsor=1" + } + }, "node_modules/makeerror": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.12.tgz", @@ -11271,6 +11357,18 @@ "tmpl": "1.0.5" } }, + "node_modules/map-age-cleaner": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz", + "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==", + "license": "MIT", + "dependencies": { + "p-defer": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", @@ -11295,6 +11393,31 @@ "node": ">= 0.6" } }, + "node_modules/mem": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/mem/-/mem-8.1.1.tgz", + "integrity": "sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA==", + "license": "MIT", + "dependencies": { + "map-age-cleaner": "^0.1.3", + "mimic-fn": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/mem?sponsor=1" + } + }, + "node_modules/mem/node_modules/mimic-fn": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-3.1.0.tgz", + "integrity": "sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/memfs": { "version": "3.5.3", "resolved": "https://registry.npmjs.org/memfs/-/memfs-3.5.3.tgz", @@ -11885,6 +12008,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/p-defer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", + "integrity": "sha512-wB3wfAxZpk2AzOfUMJNL+d36xothRSyj8EXOa4f6GMqYDN9BJaaSISbsk+wS9abmnebVw95C2Kb5t85UmpCxuw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -13629,6 +13761,12 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/psl": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz", @@ -13757,10 +13895,13 @@ } }, "node_modules/react": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", - "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + }, "engines": { "node": ">=0.10.0" } @@ -13788,6 +13929,30 @@ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", "license": "MIT" }, + "node_modules/react-clock": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/react-clock/-/react-clock-5.1.0.tgz", + "integrity": "sha512-DKmr29VOK6M8wpbzGUZZa9PwGnG9uC6QXtDLwGwcc2r3vdS/HxNhf5xMMjudXLk7m096mNJQf7AgfjiDpzAYYw==", + "license": "MIT", + "dependencies": { + "@wojtekmaj/date-utils": "^1.5.0", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-clock?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -13894,15 +14059,16 @@ } }, "node_modules/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-4GV5sHFG0e/0AD4X+ySy6UJd3jVl1iNsNHdpad0qhABJ11twS3TTBnseqsKurKcsNqCEFeGL3uLpVChpIO3QfQ==", + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", "dependencies": { - "scheduler": "^0.25.0" + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" }, "peerDependencies": { - "react": "^19.0.0" + "react": "^18.3.1" } }, "node_modules/react-error-overlay": { @@ -13911,6 +14077,32 @@ "integrity": "sha512-SN/U6Ytxf1QGkw/9ve5Y+NxBbZM6Ht95tuXNMKs8EJyFa/Vy/+Co3stop3KBHARfn/giv+Lj1uUnTfOJ3moFEQ==", "license": "MIT" }, + "node_modules/react-fit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/react-fit/-/react-fit-2.0.1.tgz", + "integrity": "sha512-Eip6ALs/+6Jv82Si0I9UnfysdwVlAhkkZRycgmMdnj7jwUg69SVFp84ICxwB8zszkfvJJ2MGAAo9KAYM8ZUykQ==", + "license": "MIT", + "dependencies": { + "detect-element-overflow": "^1.4.0", + "warning": "^4.0.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-fit?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -13926,6 +14118,38 @@ "node": ">=0.10.0" } }, + "node_modules/react-router": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.17.0.tgz", + "integrity": "sha512-YJR3OTJzi3zhqeJYADHANCGPUu9J+6fT5GLv82UWRGSxu6oJYCKVmxUcaBQuGm9udpWmPsvpme/CdHumqgsoaA==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.10.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.17.0.tgz", + "integrity": "sha512-qWHkkbXQX+6li0COUUPKAUkxjNNqPJuiBd27dVwQGDNsuFBdMbrS6UZ0CLYc4CsbdLYTckn4oB4tGDuPZpPhaQ==", + "license": "MIT", + "dependencies": { + "@remix-run/router": "1.10.0", + "react-router": "6.17.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-scripts": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", @@ -13999,6 +14223,34 @@ } } }, + "node_modules/react-time-picker": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/react-time-picker/-/react-time-picker-7.0.0.tgz", + "integrity": "sha512-k6mUjkI+OsY73mg0yjMxqkLXv/UXR1LN7AARNqfyGZOwqHqo1JrjL3lLHTHWQ86HmPTBL/dZACbIX/fV1NLmWg==", + "license": "MIT", + "dependencies": { + "@wojtekmaj/date-utils": "^1.1.3", + "clsx": "^2.0.0", + "get-user-locale": "^2.2.1", + "make-event-props": "^1.6.0", + "react-clock": "^5.0.0", + "react-fit": "^2.0.0", + "update-input-width": "^1.4.0" + }, + "funding": { + "url": "https://github.com/wojtekmaj/react-time-picker?sponsor=1" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -14602,10 +14854,13 @@ } }, "node_modules/scheduler": { - "version": "0.25.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", - "integrity": "sha512-xFVuu11jh+xcO7JOAGJNOXld8/TcEHK/4CituBUeUb5hqxJLj9YuemAEuvm9gQ/+pgXYfbQuqAkiYu+u7YEsNA==", - "license": "MIT" + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.1.0" + } }, "node_modules/schema-utils": { "version": "4.3.0", @@ -16508,6 +16763,15 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/update-input-width": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/update-input-width/-/update-input-width-1.4.2.tgz", + "integrity": "sha512-/p0XLhrQQQ4bMWD7bL9duYObwYCO1qGr8R19xcMmoMSmXuQ7/1//veUnCObQ7/iW6E2pGS6rFkS4TfH4ur7e/g==", + "license": "MIT", + "funding": { + "url": "https://github.com/wojtekmaj/update-input-width?sponsor=1" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -16632,6 +16896,15 @@ "makeerror": "1.0.12" } }, + "node_modules/warning": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/warning/-/warning-4.0.3.tgz", + "integrity": "sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/watchpack": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.2.tgz", diff --git a/package.json b/package.json index ba83d74..9317187 100755 --- a/package.json +++ b/package.json @@ -7,9 +7,12 @@ "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.2.0", "@testing-library/user-event": "^13.5.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "axios": "^1.8.4", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.17.0", "react-scripts": "5.0.1", + "react-time-picker": "^7.0.0", "web-vitals": "^2.1.4" }, "scripts": { diff --git a/public/images/heart.png b/public/images/heart.png new file mode 100644 index 0000000..ab3a958 Binary files /dev/null and b/public/images/heart.png differ diff --git a/public/images/kakao_login.svg b/public/images/kakao_login.svg new file mode 100644 index 0000000..40762a0 --- /dev/null +++ b/public/images/kakao_login.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/public/images/memo_background.png b/public/images/memo_background.png new file mode 100644 index 0000000..13dc555 Binary files /dev/null and b/public/images/memo_background.png differ diff --git a/public/images/mic_icon.png b/public/images/mic_icon.png new file mode 100644 index 0000000..eba5a2e Binary files /dev/null and b/public/images/mic_icon.png differ diff --git a/src/App.js b/src/App.js index 9f3def5..3187003 100755 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,5 @@ import React from "react"; +import KakaoRedirectPage from "./components/oauth/KakaoRedirectPage"; import {BrowserRouter as Router, Routes, Route } from "react-router-dom"; import LoginPage from "./pages/LoginPage"; import SignupPage from "./pages/SignupPage"; @@ -16,6 +17,9 @@ function App() { } /> } /> } /> + + {/* 카카오 로그인 리다이렉트 경로 추가 */} + } /> ); diff --git a/src/axios/TokenInterceptor.js b/src/axios/TokenInterceptor.js new file mode 100644 index 0000000..b5f28d1 --- /dev/null +++ b/src/axios/TokenInterceptor.js @@ -0,0 +1,84 @@ +import axios from 'axios'; +import {LOCAL_SPRING_API_URL} from "../constants/api"; + +const instance = axios.create(); + +instance.defaults.withCredentials = true; + +// 요청 인터셉터 : API call 하기 전에 실행 +instance.interceptors.request.use(function (config) { + + // 로컬 스토리지에서 accessToken을 가져온다 + const accessToken = localStorage.getItem('accessToken'); + console.log(accessToken); + + // accessToken이 있으면 요청 헤더에 추가한다. + if (accessToken) + config.headers['Authorization'] = `Bearer ${accessToken}`; + return config; + +}, function (error) { + // 요청 오류 처리 + return Promise.reject(error); +}); + +// 응답 인터셉터 : 응답을 받고 then, catch 처리하기 전에 실행 +// 토큰 재인증, 자동 로그아웃 등 처리 +instance.interceptors.response.use(async function (response) { + + // 응답 데이터 있는 작업 수행 + // 2xx 범위에 있는 상태 코드인 경우 + return response; +}, async function (error) { + + // 응답 오류가 있는 작업 수행 + // 2xx 범위 밖에 있는 상태 코드인 경우 + + const {config, response} = error; + if (!response) { + console.error('Network or server error', error); + return Promise.reject(error); + } + + // 백엔드단의 JWT Filter에서 걸리는 에러 처리 + const {status, data} = response; + console.log(response); + if (status === 401) { + if (data.message === "토큰이 없습니다") { + await Logout(); + } + if (data.message === "유효하지 않은 토큰") { + try { + const tokenReissueResult = await instance.post(`${LOCAL_SPRING_API_URL}/reissue`); + if (tokenReissueResult.status === 200) { + // 재발급 성공시 로컬스토리지에 토큰 저장 + const accessToken = tokenReissueResult.headers['authorization'] || tokenReissueResult.headers['Authorization']; + localStorage.setItem('accessToken', accessToken); + // 토큰 재발급 성공, API 재요청 + console.log("토큰 재발급 성공"); + return instance(config) + } else { + await Logout(); + } + } catch (e) { + await Logout(); + } + } + } + + return Promise.reject(error); +}); + + +const Logout = async () => { + try { + // 로그아웃 API 호출 + await axios.post(`${LOCAL_SPRING_API_URL}/logout`); + localStorage.removeItem('accessToken'); + window.location.href = '/'; // 로그인 페이지 이동 + } catch (error) { + console.error('로그아웃 오류 발생:', error); + } +}; + +export default instance; \ No newline at end of file diff --git a/src/components/Header.js b/src/components/Header.js new file mode 100644 index 0000000..d65a0c0 --- /dev/null +++ b/src/components/Header.js @@ -0,0 +1,9 @@ +// components/Header.js + +export const getAccessToken = () => { + const token = localStorage.getItem("accessToken"); + if (!token) { + console.warn("accessToken이 없습니다."); + } + return token; +}; diff --git a/src/components/oauth/KakaoRedirectPage.js b/src/components/oauth/KakaoRedirectPage.js new file mode 100644 index 0000000..c2cbb35 --- /dev/null +++ b/src/components/oauth/KakaoRedirectPage.js @@ -0,0 +1,60 @@ +import React, { useEffect } from "react"; +import { useLocation, useNavigate } from "react-router-dom"; +import instance from "../../axios/TokenInterceptor"; // instance를 계속 사용하려면 +import { LOCAL_SPRING_API_URL } from "../../constants/api"; + +// 카카오 OAuth 인증 후 리디렉션 처리 페이지 +const KakaoRedirectPage = () => { + const location = useLocation(); // 현재 URL 정보 + const navigate = useNavigate(); // 페이지 이동을 위한 navigate 함수 + + useEffect(() => { + // 카카오 로그인 인증 코드 처리 함수 + const handleOAuthKakao = async (code) => { + try { + // 카카오 인증 코드로 서버에 로그인 요청 (여기서 instance 사용) + const response = await instance.get( + `${ LOCAL_SPRING_API_URL}/oauth/login/kakao?code=${code}` + ); + + if (response.data.isSuccess) { + // 로그인 성공 시, Authorization 헤더에서 액세스 토큰 가져오기 + const accessToken = response.headers["Authorization"] || response.headers["authorization"]; + localStorage.setItem("accessToken", accessToken); // 토큰을 로컬스토리지에 저장 + + // 사용자의 역할(role)에 따라 페이지 이동 + const role = response.data.result; + if (role === "GUEST") { + navigate("/voice-training"); // 게스트 페이지로 이동 + } else if (role === "USER") { + navigate("/voice-training"); // 사용자 메인 페이지로 이동 + } + } else { + // 로그인 실패 처리 + console.error("OAuth2 로그인 오류"); + console.log(response.data.code); + console.log(response.data.message); + } + } catch (error) { + // 요청 실패 시 처리 + console.error("로그인 실패", error); + } + }; + + // URL에서 카카오 인증 코드 추출 + const searchParams = new URLSearchParams(location.search); + const code = searchParams.get("code"); // URL에서 'code' 파라미터 추출 + + // 인증 코드가 존재하면 로그인 처리 + if (code) { + handleOAuthKakao(code); + } + }, [location, navigate]); // 의존성 배열에 'location'과 'navigate' 추가 + + return ( +
+
+ ); +}; + +export default KakaoRedirectPage; diff --git a/src/pages/KeywordSelectionPage.js b/src/pages/KeywordSelectionPage.js index e0e6d72..251fbd9 100644 --- a/src/pages/KeywordSelectionPage.js +++ b/src/pages/KeywordSelectionPage.js @@ -2,8 +2,11 @@ import React, { useState } from "react"; import { useNavigate } from "react-router-dom"; import TimePicker from "react-time-picker"; import "react-time-picker/dist/TimePicker.css"; +import { LOCAL_SPRING_API_URL } from "../constants/api"; import "react-clock/dist/Clock.css"; import Logo from "../components/Logo"; +import axios from "axios"; +import { getAccessToken } from "../components/Header"; // 토큰 불러오기 const keywords = { "수면 여부 확인": ["아침", "밤"], @@ -109,6 +112,58 @@ const KeywordSelectionPage = () => { })); }; + const handleComplete = async () => { + const token = getAccessToken(); + if (!token) { + alert("로그인이 필요합니다."); + return; + } + + // 요일 변환 맵 + const dayMap = { + "월": "MONDAY", + "화": "TUESDAY", + "수": "WEDNESDAY", + "목": "THURSDAY", + "금": "FRIDAY", + "토": "SATURDAY", + "일": "SUNDAY" + }; + + // selected 가공 + const payload = []; + + for (const category in selected) { + for (const keyword in selected[category]) { + const item = selected[category][keyword]; + if (item.selected && item.days.length > 0) { + payload.push({ + scheduleTitle: `${category} - ${keyword}`, + startTime: item.time + ":00", + days: item.days.map(day => dayMap[day]) + }); + } + } + } + + try { + const response = await axios.post( + `${ LOCAL_SPRING_API_URL }/basic-schedules`, // 실제 API 주소로 교체 + payload, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + console.log("서버 응답:", response.data); + navigate("/final"); + } catch (error) { + console.error("데이터 전송 실패:", error); + alert("데이터 전송 중 오류가 발생했습니다."); + } + }; + return (
@@ -144,7 +199,7 @@ const KeywordSelectionPage = () => {
))} - + ); }; diff --git a/src/pages/LoginPage.js b/src/pages/LoginPage.js index b16bba8..644963c 100644 --- a/src/pages/LoginPage.js +++ b/src/pages/LoginPage.js @@ -2,12 +2,12 @@ import React from "react"; import { useNavigate } from "react-router-dom"; import Button from "../components/Button"; import Logo from "../components/Logo"; +import { LOCAL_SPRING_API_URL } from "../../constants/api"; const LoginPage = () => { const navigate = useNavigate(); const handleKakaoLogin = () => { - //window.location.href = "나중에 카카오 로그인 키. " - navigate("/signup"); + window.location.href = `${LOCAL_SPRING_API_URL}/oauth/kakao`; }; return (