From a38e5df2f66bf8b2ed718a9a24baf07dc0c25606 Mon Sep 17 00:00:00 2001 From: KangYeonbae Date: Thu, 21 Nov 2024 13:37:55 +0900 Subject: [PATCH] =?UTF-8?q?=EC=82=AC=EC=9A=A9=EC=9E=90=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EA=B0=80=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 로그인 - 마이페이지에서 확인가능 --- .env.development | 1 + .env.production | 1 + package-lock.json | 116 +++++++++++++++++++++- package.json | 9 +- src/App.js | 174 ++++++++++++++++----------------- src/components/UserInfo.js | 34 +++++++ src/constants/type/ItemType.js | 22 ++--- src/index.js | 10 +- src/utils/AuthContext.js | 46 +++++++++ src/utils/authService.js | 129 ++++++++++++++++++++++++ src/utils/cookie.js | 19 ++++ 11 files changed, 449 insertions(+), 112 deletions(-) create mode 100644 .env.development create mode 100644 .env.production create mode 100644 src/components/UserInfo.js create mode 100644 src/utils/AuthContext.js create mode 100644 src/utils/authService.js create mode 100644 src/utils/cookie.js diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..3bd8019 --- /dev/null +++ b/.env.development @@ -0,0 +1 @@ +REACT_APP_SPACE_API=http://localhost:60000/api/v1 \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..96589b4 --- /dev/null +++ b/.env.production @@ -0,0 +1 @@ +REACT_APP_API="https://api." \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index d3e6342..46846c5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,12 +14,14 @@ "assert": "^2.1.0", "axios": "^1.7.7", "bootstrap": "^5.3.3", + "dotenv": "^16.4.5", "https-browserify": "^1.0.0", "lucide-react": "^0.454.0", "moment": "^2.30.1", "react": "^18.3.1", "react-bootstrap": "^2.10.5", "react-calendar": "^5.1.0", + "react-cookie": "^7.2.2", "react-dom": "^18.3.1", "react-icons": "^5.3.0", "react-kakao-maps-sdk": "^1.1.27", @@ -30,6 +32,9 @@ "swiper": "^11.1.14", "url": "^0.11.4", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "env-cmd": "^10.1.0" } }, "node_modules/@adobe/css-tools": { @@ -3816,6 +3821,12 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/eslint": { "version": "8.56.12", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.12.tgz", @@ -3877,6 +3888,16 @@ "@types/node": "*" } }, + "node_modules/@types/hoist-non-react-statics": { + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz", + "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==", + "license": "MIT", + "dependencies": { + "@types/react": "*", + "hoist-non-react-statics": "^3.3.0" + } + }, "node_modules/@types/html-minifier-terser": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz", @@ -7247,12 +7268,15 @@ } }, "node_modules/dotenv": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", - "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", "license": "BSD-2-Clause", "engines": { - "node": ">=10" + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" } }, "node_modules/dotenv-expand": { @@ -7358,6 +7382,33 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/env-cmd": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/env-cmd/-/env-cmd-10.1.0.tgz", + "integrity": "sha512-mMdWTT9XKN7yNth/6N6g2GuKuJTsKMDHlQFUDacb/heQRRWOTIZ42t1rMHnQu4jYxU1ajdTeJM+9eEETlqToMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^4.0.0", + "cross-spawn": "^7.0.0" + }, + "bin": { + "env-cmd": "bin/env-cmd.js" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/env-cmd/node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -9275,6 +9326,21 @@ "he": "bin/he" } }, + "node_modules/hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "license": "BSD-3-Clause", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -14358,6 +14424,20 @@ } } }, + "node_modules/react-cookie": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/react-cookie/-/react-cookie-7.2.2.tgz", + "integrity": "sha512-e+hi6axHcw9VODoeVu8WyMWyoosa1pzpyjfvrLdF7CexfU+WSGZdDuRfHa4RJgTpfv3ZjdIpHE14HpYBieHFhg==", + "license": "MIT", + "dependencies": { + "@types/hoist-non-react-statics": "^3.3.5", + "hoist-non-react-statics": "^3.3.2", + "universal-cookie": "^7.0.0" + }, + "peerDependencies": { + "react": ">= 16.3.0" + } + }, "node_modules/react-dev-utils": { "version": "12.0.1", "resolved": "https://registry.npmjs.org/react-dev-utils/-/react-dev-utils-12.0.1.tgz", @@ -14631,6 +14711,15 @@ } } }, + "node_modules/react-scripts/node_modules/dotenv": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-10.0.0.tgz", + "integrity": "sha512-rlBi9d8jpv9Sf1klPjNfFAuWDjKLwTIJJ/VxtoTwIR6hnZxcEOQCZg2oIL3MWBYw5GpUDKOEnND7LXTbIpQ03Q==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=10" + } + }, "node_modules/react-transition-group": { "version": "4.4.5", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", @@ -17051,6 +17140,25 @@ "node": ">=8" } }, + "node_modules/universal-cookie": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-7.2.2.tgz", + "integrity": "sha512-fMiOcS3TmzP2x5QV26pIH3mvhexLIT0HmPa3V7Q7knRfT9HG6kTwq02HZGLPw0sAOXrAmotElGRvTLCMbJsvxQ==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^0.7.2" + } + }, + "node_modules/universal-cookie/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", diff --git a/package.json b/package.json index ef785a3..d5cdf73 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,14 @@ "assert": "^2.1.0", "axios": "^1.7.7", "bootstrap": "^5.3.3", + "dotenv": "^16.4.5", "https-browserify": "^1.0.0", "lucide-react": "^0.454.0", "moment": "^2.30.1", "react": "^18.3.1", "react-bootstrap": "^2.10.5", "react-calendar": "^5.1.0", + "react-cookie": "^7.2.2", "react-dom": "^18.3.1", "react-icons": "^5.3.0", "react-kakao-maps-sdk": "^1.1.27", @@ -27,8 +29,8 @@ "web-vitals": "^2.1.4" }, "scripts": { - "start": "react-scripts start", - "build": "react-scripts build", + "start": "env-cmd -f .env.development react-scripts start", + "build": "env-cmd -f .env.production react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, @@ -49,5 +51,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "env-cmd": "^10.1.0" } } diff --git a/src/App.js b/src/App.js index bb77057..ec8752a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,6 +1,5 @@ -// App.js import './App.css'; -import React, { useState } from 'react'; +import React, { useState, useContext } from 'react'; import { BrowserRouter as Router, Routes, Route, Link, NavLink } from 'react-router-dom'; import { Container, Nav, Navbar, NavDropdown } from 'react-bootstrap'; import 'bootstrap/dist/css/bootstrap.min.css'; @@ -8,126 +7,117 @@ import LoginModal from './pages/login'; import RenterMypage from './pages/renter-mypage'; import CategorySection from './pages/category'; import MyMapPage from './pages/mapPage'; -import ImageCarousel from './pages/mainpage' +import ImageCarousel from './pages/mainpage'; import Booking from './pages/booking'; import IntroPage from './pages/intropage'; import OwnerMypage from './pages/owner-mypag'; import SpaceList from './pages/spaceList'; import SpaceDetail from './pages/spaceDetail'; import ItemType from './constants/type/ItemType'; - - +import { AuthContext } from './utils/AuthContext'; function App() { + const { user, isAuthenticated, logout } = useContext(AuthContext); const [isDropdownOpen, setIsDropdownOpen] = useState(false); const [isLoginModalOpen, setIsLoginModalOpen] = useState(false); - const [isAuthenticated, setIsAuthenticated] = useState(false); - const [user, setUser] = useState(null); const toggleDropdown = () => { setIsDropdownOpen(!isDropdownOpen); }; const handleLogin = (userData) => { - setUser(userData); - setIsAuthenticated(true); setIsLoginModalOpen(false); }; const handleLogout = () => { - setUser(null); - setIsAuthenticated(false); + logout(); setIsDropdownOpen(false); + window.location.href = '/'; }; - - return ( - -
-
- - - Spaces Place - - - + + + + +
+ + setIsLoginModalOpen(false)} + onLogin={handleLogin} + /> + + + + +
+ +
+ + } /> - - - -
- -
- - } - /> - - {ItemType.map(({type}) => ( - } /> - ))} - - {ItemType.map(({type}) => ( - } /> - ))} - - } /> - } /> - } /> - } /> - } /> - -
-
+ {ItemType.map(({type}) => ( + } /> + ))} + + {ItemType.map(({type}) => ( + } /> + ))} + + } /> + } /> + } /> + } /> + } /> + + ); } -export default App; +export default App; \ No newline at end of file diff --git a/src/components/UserInfo.js b/src/components/UserInfo.js new file mode 100644 index 0000000..465253b --- /dev/null +++ b/src/components/UserInfo.js @@ -0,0 +1,34 @@ +import React from "react"; + + + +const UserInfomation = ({userInfo, loading, error}) => { + + if(loading) return
...로딩중
+ if (error) return
{error}
; + if (!userInfo) return null; + + return ( +
+

마이페이지

+
+
ID
+
{userInfo.user_id}
+
+
+
이름
+
{userInfo.name}
+
+
+
이메일
+
{userInfo.email}
+
+
+
연락처
+
{userInfo.phone}
+
+
+ ); + }; + + export default UserInfomation; \ No newline at end of file diff --git a/src/constants/type/ItemType.js b/src/constants/type/ItemType.js index 3f63b4b..d98be51 100644 --- a/src/constants/type/ItemType.js +++ b/src/constants/type/ItemType.js @@ -11,17 +11,17 @@ import { FaBookOpenReader } from "react-icons/fa6"; // 카테고리별 아이템 매핑 const ItemType = [ - { type: 'PLAYING', title: "악기연주(합주실)", icon: , label:"악기연주(합주실)", isNew: true,to: "/playing" }, - { type: 'PARTY', title: '파티룸', icon: , label:"파티룸", to: "/party" }, - { type: 'DANCE', title: "댄스연습실", icon: , label:"댄스연습실", to: "/dance" }, - { type: 'KARAOKE', title: "노래방", icon: , label:"노래방", to: "/karaoke" }, - { type: 'STUDIO', title: "스튜디오", icon: , label:"스튜디오", to: "/studio" }, - { type: 'CAMPING', title: "캠핑장", icon: , label:"캠핑장", to: "/camping" }, - { type: 'gym', title: "헬스장", icon: , label:"헬스장", to: "/gym" }, - { type: 'office', title: "사무실", icon: , label:"사무실",to: "/office" }, - { type: 'accommodation', title: "숙박", icon: , label:"숙박",to: "/accommodation" }, - { type: 'kitchen', title: "공용주방", icon: , label:"공용주방", to: "/kitchen" }, - { type: 'studyroom', title: "스터디룸", icon: , label:"스터디룸",to: "/studyroom" } + { type: 'PLAYING', title: "악기연주(합주실)", icon: , label:"악기연주(합주실)", isNew: true,to: "/space/playing" }, + { type: 'PARTY', title: '파티룸', icon: , label:"파티룸", to: "/space/party" }, + { type: 'DANCE', title: "댄스연습실", icon: , label:"댄스연습실", to: "/space/dance" }, + { type: 'KARAOKE', title: "노래방", icon: , label:"노래방", to: "/space/karaoke" }, + { type: 'STUDIO', title: "스튜디오", icon: , label:"스튜디오", to: "/space/studio" }, + { type: 'CAMPING', title: "캠핑장", icon: , label:"캠핑장", to: "/space/camping" }, + { type: 'gym', title: "헬스장", icon: , label:"헬스장", to: "/space/gym" }, + { type: 'office', title: "사무실", icon: , label:"사무실",to: "/space/office" }, + { type: 'accommodation', title: "숙박", icon: , label:"숙박",to: "/space/accommodation" }, + { type: 'kitchen', title: "공용주방", icon: , label:"공용주방", to: "/space/kitchen" }, + { type: 'studyroom', title: "스터디룸", icon: , label:"스터디룸",to: "/space/studyroom" } ]; diff --git a/src/index.js b/src/index.js index a111724..311ab22 100644 --- a/src/index.js +++ b/src/index.js @@ -4,15 +4,19 @@ import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import { BrowserRouter } from 'react-router-dom'; -import { AuthProvider } from './utils/AuthContext/AuthContext'; +import { CookiesProvider } from 'react-cookie'; +import { AuthProvider } from './utils/AuthContext'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - + + + + + ); // If you want to start measuring performance in your app, pass a function diff --git a/src/utils/AuthContext.js b/src/utils/AuthContext.js new file mode 100644 index 0000000..c474d20 --- /dev/null +++ b/src/utils/AuthContext.js @@ -0,0 +1,46 @@ +// AuthContext.js +import React, { createContext, useEffect, useState } from 'react'; +import authService from './authService'; +import {useCookies} from 'react-cookie'; + +export const AuthContext = createContext(null); + +export const AuthProvider = ({ children }) => { + const [cookies, setCookie, removeCookie] = useCookies(['access_token', 'user_type', 'user_id']) + + const isAuthenticated = Boolean(cookies.access_token); + const user = cookies.access_token ? { + userid : cookies.user_id, + token: cookies.access_token, + type: cookies.user_type + }: null; + + const login = async (userid, password, type) => { + try { + const response = await authService.login(userid, password, type); + return response; + } catch (error) { + console.error('Login failed:', error); + throw error; + } + }; + + const logout = () => { + removeCookie('access_token', {path: '/'}); + removeCookie('user_type', {path:'/'}); + removeCookie('user_id', {path: '/'}); + authService.setAuthHeader(null); + }; + + useEffect(()=> { + if(cookies.access_token){ + authService.setAuthHeader(cookies.access_token); + } + }, [cookies.access_token]); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/src/utils/authService.js b/src/utils/authService.js new file mode 100644 index 0000000..fdb607e --- /dev/null +++ b/src/utils/authService.js @@ -0,0 +1,129 @@ +// authService.js +import axios from 'axios'; +import { Cookies } from 'react-cookie'; + + +const cookies = new Cookies(); +const API_URL = process.env.REACT_APP_SPACE_API; +console.log('API URL:', API_URL); + +console.log('NODE_ENV:', process.env.NODE_ENV); + +const api = axios.create({ + baseURL: API_URL, + headers: { + 'accept': 'application/json', + 'Content-Type': 'application/json' + } +}); + +const COOKIE_OPTIONS = { + path: '/', + secure: process.env.NODE_ENV === 'production', + sameSite: 'strict' +}; + +const setAuthHeader = (token) => { + if (token) { + axios.defaults.headers.common['Authorization'] = `Bearer ${token}`; + } else { + delete axios.defaults.headers.common['Authorization']; + } +}; + +const login = async (userid, password, type) => { + try { + const response = await api.post('/members/sign-in', { + user_id: userid, + password: password, + type: type + }); + + + const { access_token, user_id } = response.data; + + if (access_token) { + cookies.set('access_token', access_token, COOKIE_OPTIONS); + cookies.set('user_id', user_id, COOKIE_OPTIONS); + cookies.set('user_type', type, COOKIE_OPTIONS); + setAuthHeader(access_token); + } + + return { + user: { + userid: user_id, + token: access_token, + type: type + } + }; + } catch (error) { + console.error('Login error details:', error.response?.data); + if (error.response?.status === 422) { + const validationErrors = error.response.data.detail; + const errorMessage = Array.isArray(validationErrors) + ? validationErrors.map(err => err.msg).join(', ') + : '필수 필드가 누락되었습니다.'; + throw new Error(errorMessage); + } + const errorMessage = error.response?.data?.detail; + if (Array.isArray(errorMessage)) { + throw new Error(errorMessage[0]?.msg || '로그인에 실패했습니다.'); + } + throw new Error(typeof errorMessage === 'string' ? errorMessage : '로그인에 실패했습니다.'); + } +}; + +const getUserInfo = async(userId) => { + try { + const token = cookies.get('access_token'); + if(!token) { + throw new Error('인증토큰없음') + } + const response = await api.get(`/members/${userId}`, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + return response.data; + }catch(error){ + throw new Error(error.response?.data?.detail || '사용자 정보 조회에 실패했습니다.'); + } +} + +const validateToken = async () => { + const token = cookies.get('access_token'); + if (!token) return false; + + try { + return true; + } catch (error) { + console.error('Token validation failed:', error); + return false; + } +}; + +const register = async (formData) => { + try { + const signUpData = { + user_id: formData.userid, + name: formData.name, + password: formData.password, + email: formData.email, + phone: formData.phone, + type: formData.type + }; + const response = await api.post(`/members/sign-up`, signUpData); + return response.data.message || '회원가입이 완료되었습니다.'; + } catch (error) { + console.error('Registration error:', error.response?.data); + throw new Error(error.response?.data?.detail || '회원가입에 실패했습니다.'); + } +}; + +export default { + login, + register, + validateToken, + setAuthHeader, + getUserInfo +}; \ No newline at end of file diff --git a/src/utils/cookie.js b/src/utils/cookie.js new file mode 100644 index 0000000..850ddc1 --- /dev/null +++ b/src/utils/cookie.js @@ -0,0 +1,19 @@ +import { Cookies } from 'react-cookie'; + +const cookies = new Cookies(); + +export const setCookie = (name, value, options = {}) => { + return cookies.set(name, value, { + path: '/', + ...options + }); +}; + +export const getCookies = (name) => { + return cookies.get(name); +}; + + +export const removeCookie = (name) => { + return cookies.remove(name, {path:'/'}); +}; \ No newline at end of file