-
Social Media 만들기 - 3) Redux , RefreshToken (Login 기능 만들기)NODE.JS 2021. 7. 5. 22:13
home.js파일을 pages 폴더에 만들어준다.
App.js에 페이지 추가해준다.
<Route exact path="/" component={login}/>
부트스트랩의 forms에서 가져와서 login 페이지에 폼을 추가해준다.
https://getbootstrap.com/docs/4.3/components/forms/
import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { Link } from 'react-router-dom'; import {login} from '../redux/actions/authActions'; const Login = () => { const initialState = {email:'', password:''}; const [userData, setuserData] = useState(initialState); const {email, password} = userData; const dispatch = useDispatch(); const handleCangeInput= e=>{ const { name, value} = e.target; setuserData({...userData, [name]:value}); } return ( <div className="auth_page"> <form onSubmit={handleSubmit}> <h3 className="text-uppercase text-center mb-4">Social Media</h3> <div className="form-group"> <label htmlFor="exampleInputEmail1">Email address</label> <input type="email" className="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email" onChange={handleCangeInput} value={email} name="email"/> <small id="emailHelp" className="form-text text-muted">We'll never share your email with anyone else.</small> </div> <div className="form-group"> <label htmlFor="exampleInputPassword1">Password</label> <input type="password" className="form-control" id="exampleInputPassword1" placeholder="Password" onChange={handleCangeInput} value={password} name="password"/> </div> <button type="submit" className="btn btn-dark w-100" disabled={email&&password? false: true}>Login</button> <p className="my-2"> You don't have an account? <Link to="/register" style={{color:"crimson"}}>Register Now</Link> </p> </form> </div> ) } export default Login
reduxt 폴더안에 store.js, actions, reducers폴더를 만들어준다.
store.js
import { createStore, applyMiddleware} from 'redux'; import {Provider} from 'react-redux'; import thunk from 'redux-thunk'; import rootReducer from './reducers/index'; import {composeWithDevTools} from 'redux-devtools-extension'; const store = createStore( rootReducer, composeWithDevTools(applyMiddleware(thunk)) ) const DataProvider = ({children})=>{ return( <Provider store={store}> {children} </Provider> ) } export default DataProvider;
reducers안에 index.js를 만들어준다.
import { combineReducers } from "redux"; export default combineReducers({ })
src 폴더의 index.js에 DataProvider로 감싸주자.
import React from 'react'; import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; import DataProvider from './redux/store'; ReactDOM.render( <React.StrictMode> <DataProvider> <App /> </DataProvider> </React.StrictMode>, document.getElementById('root') ); // If you want to start measuring performance in your app, pass a function // to log results (for example: reportWebVitals(console.log)) // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals reportWebVitals();
actions폴더안에 authAction.js파일을 만들어준다.
import {postDataAPI} from '../../utils/fetchData' export const TYPES = { AUTH:'AUTH' } export const login = (data)=> async (dispatch)=>{ try{ }catch (err){ } }
reducers폴더에 authReducer.js파일을 만들어준다.
import {TYPES} from '../actions/authActions'; const initialState = {}; const authReducer = (state=initialState, action) =>{ switch(action.type){ case TYPES.AUTH: return action.payload; default : return state; } } export default authReducer;
reducers안의 index.js로 가서 다음과 같이 작성해준다.
import { combineReducers } from "redux"; import auth from './authReducers'; export default combineReducers({ auth })
login 페이지로 가서 submit 메소드를 작성해준다.
import { useDispatch } from 'react-redux'; import {login} from '../redux/actions/authActions'; const dispatch = useDispatch(); const handleSubmit=e=>{ e.preventDefault(); dispatch(login(userData)); }
로그인 페이지의 css를 추가해줄 것이다.
styles안에 auth.css를 추가해준다.
.auth_page{ width: 100%; min-height: 100vh; background-color: #fdfdfd; /* display: flex; justify-content: center; align-items: center; */ position: relative; } .auth_page form{ background-color: #fff; max-width: 400px; width: 100%; border: 1px solid #ddd; padding: 50px 25px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
styles.css에 다음과 같이 auth 스타일을 추가해주자.
/* ------AUTH--------- */ @import url("./styles/auth.css");
notifyAction.js를 추가한다.
export const TYPES = { NOTIFY:'NOTIFY' }
notifyReducer를 추가한다.
import {TYPES} from '../actions/notifyActions'; const initialState = {}; const notifyReducer = (state=initialState, action) =>{ switch(action.type){ case TYPES.NOTIFY: return action.payload; default : return state; } } export default notifyReducer;
utils폴더안에 fetchData.js 파일을 작성해준다.
import axios from 'axios'; export const getDataAPI = async(url, token)=>{ const res = await axios.get(`/api/${url}`, { headers:{Authorization:token} }) return res; } export const postDataAPI = async(url,post, token)=>{ const res = await axios.post(`/api/${url}`, post, { headers:{Authorization:token} }) return res; } export const putDataAPI = async(url,post, token)=>{ const res = await axios.put(`/api/${url}`, post, { headers:{Authorization:token} }) return res; } export const patchDataAPI = async(url,post, token)=>{ const res = await axios.patch(`/api/${url}`, post, { headers:{Authorization:token} }) return res; } export const deleteDataAPI = async(url, token)=>{ const res = await axios.delete(`/api/${url}`, { headers:{Authorization:token} }) return res; }
authAction을 작성해주자.
import {postDataAPI} from '../../utils/fetchData' export const TYPES = { AUTH:'AUTH' } export const login = (data)=> async (dispatch)=>{ try{ dispatch({type:'NOTIFY', payload:{loading:true}}) const res = await postDataAPI('login', data) console.log(res) }catch (err){ } }
client에 proxy를 추가해서 주소를 맞춰주자.
package.json
"proxy":"http://localhost:5000",
이제 로그인해서 로딩이 완료되면 auth를 생성하고 localstorage에 access token을 생성하도록 하자.
authActions.js
import {postDataAPI} from '../../utils/fetchData' export const TYPES = { AUTH:'AUTH' } export const login = (data)=> async (dispatch)=>{ try{ dispatch({ type:'NOTIFY', payload:{loading:true} }) const res = await postDataAPI('login', data); dispatch({ type:'AUTH', payload:{ token:res.data.access_token, user:res.data.user } }) localStorage.setItem("firstLogin", true); }catch (err){ dispatch({ type:'NOTIFY', payload:{ error: err.response.data.msg } }) } }
이제 비밀번호를 hide show 선택할 수 있게 설정해주자.
import React, { useState } from 'react'; import { useDispatch } from 'react-redux'; import { Link } from 'react-router-dom'; import {login} from '../redux/actions/authActions'; const Login = () => { const [typePass, setTypePass] = useState(false); return ( <div className="form-group"> <label htmlFor="exampleInputPassword1">Password</label> <div className="pass"> <input type={typePass? "text" : "password"} className="form-control" id="exampleInputPassword1" placeholder="Password" onChange={handleCangeInput} value={password} name="password"/> <small onClick={()=>setTypePass(!typePass)}> {typePass? 'Hide': 'Show'} </small> </div> ) } export default Login
auth.css를 주자.
.auth_page form .pass{ position:relative; } .auth_page form .pass small{ position: absolute; top: 50%; right: 5px; transform: translateY(-50%); cursor: pointer; opacity: 0.5; }
여기서 notify component를 생성해서
notify 종류에 따라서 다르게 나타나도록 설정하자.
compoenent>notify>notify.js를 만들자.
그다음 app.js로가서 전체를 notify를 추가해주자.
import {BrowserRouter as Router, Route} from 'react-router-dom' import PageRender from './PageRender'; import login from './pages/login'; import Notify from './components/notify/Notify' function App() { return ( <Router> <Notify/> .... </Router> ); }
Loading.js
import React from 'react' const Loading = () => { return ( <div className="position-fixed w-100 h-100 text-center loading" style={{background:"#0008", color:"white", top:0, left:0, zIndex:50}}> <svg width="205" height="250" viewBox="0 0 40 50"> <polygon stroke="#fff" strokeWidth="1" fill="none" points="20,1 40,40 1, 40"/> <text fill ="#fff" x="5" y = "47" >Loading</text> </svg> </div> ) } export default Loading
styles>loading.css를 만들어준다.
.loading{ display: flex; justify-content: center; align-items: center; } .loading svg{ font-size: 5px; font-weight: 600; text-transform: uppercase; letter-spacing: 1px; animation: text 1s ease-in-out infinite; } @keyframes text{ 50%{opacity: 0.1} } .loading polygon{ stroke-dasharray: 22; stroke-dashoffset: 1; animation: dash 4s cubic-bezier(0.455, 0.03, 0.515, 0.955) infinite alternate-reverse; } @keyframes dash{ to { stroke-dashoffset: 234;} }
style에 추가해준다.
/* ------LOADING--------- */ @import url("./styles/loading.css");
notify.js
import React from 'react' import {useSelector, useDispatch} from 'react-redux' import Loading from './Loading' const Notify = () => { const {auth, notify} = useSelector(state => state); return ( <div> {notify.loading && <Loading/>} </div> ) } export default Notify
notify>Toast.js를 만들어준다.
import React from 'react' const Toast = ({msg, handleShow, bgColor}) => { return ( <div className={`toast show position-fixed text-light ${bgColor}`} style={{top: '5px', right: '5px', minWidth: '200px', zIndex: 50}}> <div className={`toast-header text-light ${bgColor}`}> <strong className="mr-auto text-light">{msg.title}</strong> <button className="ml-2 mb-1 close text-light" data-dismiss="toast" style={{outline: 'none'}} onClick={handleShow}> × </button> </div> <div className="toast-body"> {msg.body} </div> </div> ) } export default Toast
notify.js
import React from 'react' import {useSelector, useDispatch} from 'react-redux' import Loading from './Loading' import Toast from './Toast' const Notify = () => { const {auth, notify} = useSelector(state => state); return ( <div> {notify.loading && <Loading/>} {notify.error && <Toast msg = {{title:'Error', body:notify.error}} handleShow="" bgColor="bg-danger"/>} {notify.success && <Toast msg = {{title:'Success', body:notify.success}} handleShow="" bgColor="bg-success"/>} </div> ) } export default Notify
handleShow를 작성해주자.
import React from 'react' import {useSelector, useDispatch} from 'react-redux' import Loading from './Loading' import Toast from './Toast' const Notify = () => { const {auth, notify} = useSelector(state => state); const dispatch = useDispatch(); return ( <div> {notify.loading && <Loading/>} {notify.error && <Toast msg = {{title:'Error', body:notify.error}} handleShow={()=> dispatch({type:'NOTIFY', payload:{}})} bgColor="bg-danger"/>} {notify.success && <Toast msg = {{title:'Success', body:notify.success}} handleShow={()=> dispatch({type:'NOTIFY', payload:{}})} bgColor="bg-success"/>} </div> ) } export default Notify
authActions에 refreshToken을 만들어주자.
import {postDataAPI} from '../../utils/fetchData' export const TYPES = { AUTH:'AUTH' } export const login = (data)=> async (dispatch)=>{ try{ dispatch({ type:'NOTIFY', payload:{loading:true} }) const res = await postDataAPI('login', data); dispatch({ type:'AUTH', payload:{ token:res.data.access_token, user:res.data.user } }) localStorage.setItem("firstLogin", true); dispatch({ type: GLOBALTYPES.ALERT, payload: { success: res.data.msg } }) }catch (err){ dispatch({ type:'NOTIFY', payload:{ error: err.response.data.msg } }) } } export const refreshToken = () => async(dispatch)=>{ const firstLogin = localStorage.getItem("firstLogin"); if(firstLogin){ dispatch({type:'NOTIFY', payload:{loading:true}}) try{ const res = await postDataAPI('refresh_token') dispatch({ type:'AUTH', payload:{ token:res.data.access_token, user:res.data.user } }) dispatch({type:'NOTIFY', payload:{}}) }catch(err){ dispatch({ type:'NOTIFY', payload:{ error: err.response.data.msg } }) } } }
App.js에 다음과 같이 리프레시 토큰 액션을 실행해주고, 토큰이 있으면 홈화면으로, 토큰이 없으면 로그인 페이지로 이동하도록 해주자.
import {BrowserRouter as Router, Route} from 'react-router-dom' import PageRender from './PageRender'; import Login from './pages/login'; import Home from './pages/home'; import Notify from './components/notify/Notify' import { useDispatch, useSelector } from 'react-redux'; import { useEffect } from 'react'; import { refreshToken } from './redux/actions/authActions'; function App() { const {auth} = useSelector(state => state); const dispatch = useDispatch(); useEffect(() => { dispatch(refreshToken()) }, [dispatch]) return ( <Router> <Notify/> <input type="checkbox" id="theme" /> <div className="App"> <div className="main"> <Route exact path="/" component={auth.token? Home:Login}/> <Route exact path="/:page" component={PageRender}/> <Route exact path="/:page/:id" component={PageRender}/> </div> </div> </Router> ); } export default App;
모든페이지에서 NOTIFY는 ALERt로 바꿔주고,
GLOBALTYPES.js에 따로 타입을 뺴서 정리후 이용하는 것으로 변경해준다.
export const GLOBALTYPES={ AUTH:"AUTH", ALERT:"ALERT" }
'NODE.JS' 카테고리의 다른 글
Social Media 만들기 - 5) logout 기능 만들기 (Header) (0) 2021.07.06 Social Media 만들기 - 4) Register 기능 만들기 (0) 2021.07.06 Social Media 만들기 - 2) Authentification 기능 만들기 (0) 2021.06.16 아마존 E-commerce 클론 -33) 채팅 기능 만들기(socket io) (0) 2021.05.30 아마존 E-commerce 클론 -32) 차트 스크린 만들기 (0) 2021.05.30