ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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/

     

    Forms

    Examples and usage guidelines for form control styles, layout options, and custom components for creating a wide variety of forms.

    getbootstrap.com

    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}>
                        &times;
                    </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"
    }

     

     

     

     

     

     

     

     

     

     

Designed by Tistory.