diff --git a/package.json b/package.json index ceacd67..17dedb5 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "axios": "^1.6.7", "babel-loader": "^9.1.3", "eslint": "^8.56.0", + "js-cookie": "^3.0.5", "react": "^18.2.0", "react-daum-postcode": "^3.1.3", "react-dom": "^18.2.0", @@ -27,6 +28,7 @@ "react-select": "^5.8.0", "redux": "^5.0.1", "redux-persist": "^6.0.0", + "redux-persist-expire": "^1.1.1", "redux-toolkit": "^1.1.2", "styled-component": "^2.8.0", "styled-components": "^6.1.8", diff --git a/src/App.js b/src/App.js index 49a5906..46e58e6 100644 --- a/src/App.js +++ b/src/App.js @@ -46,13 +46,15 @@ import MorePage from "./Pages/MoreProductPage"; import Payment from "./Pages/Payment"; import NewProductPage from './Pages/NewProductPage'; import CategorySearchPage from "./Pages/CategoryPage"; - +import Like from "./Pages/Like"; +import TokenTrigger from "./Components/Login/TokenTrigger"; function App() { return (
+ } /> } /> diff --git a/src/Components/Header/Header.jsx b/src/Components/Header/Header.jsx index 03d2736..9f3981d 100644 --- a/src/Components/Header/Header.jsx +++ b/src/Components/Header/Header.jsx @@ -1,46 +1,56 @@ -import { useDispatch } from 'react-redux'; -import { Link } from 'react-router-dom' -import styled from 'styled-components' +import { useDispatch, useSelector } from "react-redux"; +import { Link } from "react-router-dom"; +import styled from "styled-components"; +import { setExpire, setToken } from "../../redux/loginSlice"; const StyledLink = styled(Link)` - text-decoration: none; /* underline 제거 */ - color: gray; /* 글씨 색상 설정 */ - margin: 0 10px; /* 좌우 여백 추가 */ + text-decoration: none; /* underline 제거 */ + color: gray; /* 글씨 색상 설정 */ + margin: 0 10px; /* 좌우 여백 추가 */ `; - function HeaderComponent() { - - return ( - <> - - - {/* bell + const dispatch = useDispatch(); + const token = useSelector((state) => state.login.token); + return ( + <> + + + {/* bell 알림 */} - 회원가입 - 로그인 - 비회원 주문조회 - - - - ) - + 회원가입 + {token == null ? ( + 로그인 + ) : ( + { + dispatch(setToken(null)); + dispatch(setExpire(null)); + }} + > + 로그아웃 + + )} + 비회원 주문조회 + + + + ); } -export default HeaderComponent +export default HeaderComponent; const SubHeaderContainer = styled.div` - width : 100%; - height : 47px; - background: rgba(156, 156, 156, 0.20); - display : flex; - justify-content : flex-end; - align-items : center; - - ` + width: 100%; + height: 47px; + background: rgba(156, 156, 156, 0.2); + display: flex; + justify-content: flex-end; + align-items: center; +`; const SubHeaderItemContainer = styled.div` - display : flex; - width : 300px; - height : 23px; - margin-right : 310px; -` \ No newline at end of file + display: flex; + width: 300px; + height: 23px; + margin-right: 310px; +`; diff --git a/src/Components/Login/LoginPage.jsx b/src/Components/Login/LoginPage.jsx index b860d01..ceaf0ef 100644 --- a/src/Components/Login/LoginPage.jsx +++ b/src/Components/Login/LoginPage.jsx @@ -6,7 +6,7 @@ import { useNavigate } from "react-router-dom"; import ErrorModal from "./ErrorModal"; import { CheckBox } from "../Global/CustomBox"; import { useDispatch, useSelector } from "react-redux"; -import { setRefreshToken, setToken } from "../../redux/loginSlice"; +import { setToken, setExpire } from "../../redux/loginSlice"; const K_REST_API_KEY = process.env.REACT_APP_K_REST_API_KEY; const N_REST_API_KEY = process.env.REACT_APP_N_REST_API_KEY; const K_REDIRECT_URI = `http://localhost:3000/api/members/kakao/callback`; @@ -213,7 +213,10 @@ const LoginPage = () => { if (response.data.isSuccess === true) { console.log(response); dispatch(setToken(response.data.result.accessToken)); - dispatch(setRefreshToken(response.data.result.refreshToken.token)); + if (!isNaN(Date.parse(response.data.result.accessExpireTime))) { + let expires = new Date(response.data.result.accessExpireTime); + dispatch(setExpire(expires)); + } navigate("/", { replace: true }); } } catch (error) { diff --git a/src/Components/Login/TokenTrigger.jsx b/src/Components/Login/TokenTrigger.jsx new file mode 100644 index 0000000..7584b04 --- /dev/null +++ b/src/Components/Login/TokenTrigger.jsx @@ -0,0 +1,60 @@ +import { useLocation } from "react-router-dom"; +import { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import axios from "axios"; +import { setToken, setExpire } from "../../redux/loginSlice"; +import Cookies from "js-cookie"; + +export default function TokenTrigger() { + const dispatch = useDispatch(); + const location = useLocation(); + const token = useSelector((state) => state.login.token); + const expire = useSelector((state) => state.login.expire); + + useEffect(() => { + if (token != null) { + console.log(Cookies); + let now = new Date(); + let expires = new Date(expire); + if (expires < now) { + fetchLogin(); + } + } + }, [location]); + + const fetchLogin = async () => { + try { + const endpoint = `/api/members/token/regenerate`; + const requestBody = { + refreshToken: Cookies.get("refreshToken"), + }; + + const response = await axios.post(endpoint, requestBody, { + headers: { + "Content-Type": "application/json", + }, + }); + + if (response.data.isSuccess === true) { + console.log(response); + dispatch(setToken(response.data.result.accessToken)); + if (!isNaN(Date.parse(response.data.result.accessExpireTime))) { + let expires = new Date(response.data.result.accessExpireTime); + dispatch(setExpire(expires)); + } + } + } catch (error) { + if (error.response) { + if (error.response.status === 400) { + } else if (error.response.status === 500) { + } else { + console.log("Unhandled error:", error.response.data); + } + } else if (error.request) { + console.log("No response received:", error.request); + } else { + console.log("Error:", error.message); + } + } + }; +} diff --git a/src/Components/Register/RegisterForm.jsx b/src/Components/Register/RegisterForm.jsx index afed6eb..5d9d7e3 100644 --- a/src/Components/Register/RegisterForm.jsx +++ b/src/Components/Register/RegisterForm.jsx @@ -605,13 +605,13 @@ const RegisterPage = () => { const endpoint = `/api/members/join`; const requestBody = { nickname: Nickname, + name: Name, password: Password, email: Email, birthday: fetchBirthDay, phone: Cell, gender: Gender, memberTerm: [AgreeAge, AgreeTermsOfUse, AgreePrivacy, AgreeNewsLetter], - memberCategory: [Category], }; const response = await axios.post(endpoint, requestBody, { diff --git a/src/redux/loginSlice.js b/src/redux/loginSlice.js index 0d71347..7a7a36b 100644 --- a/src/redux/loginSlice.js +++ b/src/redux/loginSlice.js @@ -2,7 +2,7 @@ import { createSlice } from "@reduxjs/toolkit"; const initialState = { token: null, - refreshToken: null, + expire: null, }; const loginSlice = createSlice({ @@ -12,11 +12,11 @@ const loginSlice = createSlice({ setToken: (state, action) => { state.token = action.payload; }, - setRefreshToken: (state, action) => { - state.refreshToken = action.payload; + setExpire: (state, action) => { + state.expire = action.payload; }, }, }); -export const { setToken, setRefreshToken } = loginSlice.actions; +export const { setToken, setExpire } = loginSlice.actions; export default loginSlice.reducer; diff --git a/src/store.js b/src/store.js index b348d8d..8dae7ab 100644 --- a/src/store.js +++ b/src/store.js @@ -1,6 +1,7 @@ import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import { persistReducer, persistStore } from "redux-persist"; +import expireReducer from "redux-persist-expire"; import storageSession from "redux-persist/lib/storage/session"; import storage from "redux-persist/lib/storage"; import loginReducer from "./redux/loginSlice"; @@ -12,6 +13,14 @@ import purchaseReducer from "./redux/purchaseSlice"; const GlobalConfig = { key: "login", storage: storage, + transforms: [ + expireReducer("login", { + expireSeconds: 3600, + autoExpire: true, + persistedAtKey: "auth_exp", + expiredState: { token: null, expire: null }, + }), + ], }; const rootReducer = combineReducers({ @@ -25,6 +34,7 @@ const rootReducer = combineReducers({ const persistConfig = { key: "root", storage: storageSession, + blacklist: ["login"], }; const persistedReducer = persistReducer(persistConfig, rootReducer); diff --git a/yarn.lock b/yarn.lock index c8837ba..2c044ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9156,6 +9156,11 @@ jiti@^1.19.1: resolved "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz" integrity sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q== +js-cookie@^3.0.5: + version "3.0.5" + resolved "https://registry.yarnpkg.com/js-cookie/-/js-cookie-3.0.5.tgz#0b7e2fd0c01552c58ba86e0841f94dc2557dcdbc" + integrity sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw== + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz" @@ -11822,7 +11827,7 @@ react-infinite-scroll-component@^6.1.0: integrity sha512-SQu5nCqy8DxQWpnUVLx7V7b7LcA37aM7tvoWjTLZp1dk6EJibM5/4EJKzOnl07/BsM1Y40sKLuqjCwwH/xV0TQ== dependencies: throttle-debounce "^2.1.0" - + react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: version "16.13.1" resolved "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz" @@ -12056,6 +12061,14 @@ redent@^3.0.0: indent-string "^4.0.0" strip-indent "^3.0.0" +redux-persist-expire@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/redux-persist-expire/-/redux-persist-expire-1.1.1.tgz#d911e4429a4d5110f1a64499d6c9b9effd395b30" + integrity sha512-CoVGJrIHpg7H1++PFRivY61hC9weN1SsK80YV6axr7SxIcZzlYvnXitlxIbByFfKDTujInw40abEPIzW1Hbp/g== + dependencies: + redux "^4.0.5" + redux-persist "^6.0.0" + redux-persist@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz"