From 459fd2e29181025ab93c4a931517365aaa4ddaa5 Mon Sep 17 00:00:00 2001
From: annisadevin <adevi.nurmalasari@gmail.com>
Date: Wed, 11 May 2022 09:26:03 +0700
Subject: [PATCH] Integrasi login dan register ke backend

---
 package.json                     |   2 +
 src/App.js                       |  31 ++++---
 src/actions/auth.js              | 145 +++++++++++++++++++++++++++++++
 src/actions/types.js             |   7 ++
 src/components/Home/Home.css     |  80 -----------------
 src/components/Home/Home.js      |  37 ++++----
 src/components/Navbar/Navbar.css |   2 +
 src/components/Navbar/Navbar.js  |  31 ++++---
 src/pages/Login/Login.js         |  35 +++++---
 src/pages/SignUp/SignUp.js       |  62 ++++++-------
 src/reducer/auth.js              |  75 ++++++++++++++++
 src/reducer/index.js             |   6 ++
 src/store.js                     |  17 ++++
 src/util/prerender.js            |   9 ++
 14 files changed, 376 insertions(+), 163 deletions(-)
 create mode 100644 src/actions/auth.js
 create mode 100644 src/actions/types.js
 create mode 100644 src/reducer/auth.js
 create mode 100644 src/reducer/index.js
 create mode 100644 src/store.js
 create mode 100644 src/util/prerender.js

diff --git a/package.json b/package.json
index 37e99c5..ffaa100 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,8 @@
     "react-redux": "^8.0.1",
     "react-router-dom": "^6.3.0",
     "react-scripts": "5.0.1",
+    "redux": "^4.2.0",
+    "redux-thunk": "^2.4.1",
     "serve": "^13.0.2",
     "web-vitals": "^2.1.4"
   },
diff --git a/src/App.js b/src/App.js
index 2fb50fd..ef8b395 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,25 +1,34 @@
 import logo from './logo.svg';
 import './App.css';
+import { fetch_user } from './util/prerender';
+import { Provider } from 'react-redux';
 import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
 import Home from './components/Home/Home'
 import Navbar from './components/Navbar/Navbar';
 import SignUp from './pages/SignUp/SignUp';
 import Login from './pages/Login/Login';
+import store from './store';
+import { useState } from 'react';
 
 function App() {
+  const [loading, setLoading] = useState(true);
+  if (loading) fetch_user(store, setLoading);
+  if (loading) return <p>Loading...</p>;
   return (
-    <Router>
-      <div className='App'>
-        <Navbar/>
-        <div className='content'>
-          <Routes>
-            <Route path='/' element={<Home/>} />
-            <Route path='/signup' element={<SignUp/>} />
-            <Route path='/login' element={<Login/>} />
-          </Routes>
+    <Provider store={store}>
+      <Router>
+        <div className='App'>
+          <Navbar/>
+          <div className='content'>
+            <Routes>
+              <Route path='/' element={<Home/>} />
+              <Route path='/signup' element={<SignUp/>} />
+              <Route path='/login' element={<Login/>} />
+            </Routes>
+          </div>
         </div>
-      </div>
-    </Router>
+      </Router>
+    </Provider>
   );
 }
 
diff --git a/src/actions/auth.js b/src/actions/auth.js
new file mode 100644
index 0000000..e8a9c54
--- /dev/null
+++ b/src/actions/auth.js
@@ -0,0 +1,145 @@
+import {
+    LOGIN_SUCCESS,
+    LOGIN_FAIL,
+    USER_LOADED_SUCCESS,
+    USER_LOADED_FAIL,
+    SIGNUP_SUCCESS,
+    SIGNUP_FAIL,
+    LOGOUT
+} from './types';
+import axios from 'axios';
+
+// Dispatch store state
+export const load_user = () => async dispatch => {
+    if (localStorage.getItem('access')) {
+        const config = {
+            headers: {
+                'Content-Type': 'application/json',
+                'Authorization': `JWT ${localStorage.getItem('access')}`,
+                'Accept': 'application/json'
+            }
+        };
+
+        try {
+            const res = await axios.get(`${process.env.REACT_APP_BACKEND_API_URL}/api/resource/`, config);
+            dispatch({
+                type: USER_LOADED_SUCCESS,
+                payload: res.data
+            });
+            return {
+                login: true,
+                userLoaded: true,
+                res
+            }
+        } catch (err) {
+            dispatch({
+                type: USER_LOADED_FAIL
+            });
+            return {
+                login: true,
+                userLoaded: false,
+                err
+            }
+        }
+    } else {
+        dispatch({
+            type: USER_LOADED_FAIL
+        });
+        return {
+            login: false,
+            err: new Error('missing token')
+        }
+    }
+};
+
+export const login = (username, password) => async dispatch => {
+    const config = {
+        headers: {
+            'Content-Type': 'application/json'
+        }
+    };
+
+    const body = JSON.stringify({ username, password });
+
+    try {
+        const res = await axios.post(`${process.env.REACT_APP_BACKEND_API_URL}/api/token/`, body, config);
+        dispatch({
+            type: LOGIN_SUCCESS,
+            payload: res.data
+        });
+
+        const loadRes = await dispatch(load_user());
+        return loadRes;
+    } catch (err) {
+        dispatch({
+            type: LOGIN_FAIL
+        })
+        return {
+            login: false,
+            userLoaded: false,
+            err
+        }
+    }
+};
+
+export const logout = () => async dispatch => {
+    if (localStorage.getItem('access')) {
+        const config = {
+            headers: {
+                'Content-Type': 'application/json',
+                'Authorization': `JWT ${localStorage.getItem('access')}`,
+                'Accept': 'application/json'
+            }
+        };
+
+        const body = JSON.stringify({ "refresh_token" : localStorage.getItem('refresh')});
+
+        try {
+            const res = await axios.post(`${process.env.REACT_APP_BACKEND_API_URL}/api/logout/`, body, config);
+            dispatch({
+                type: LOGOUT,
+                payload: res.data
+            });
+
+        } catch (err) {
+            dispatch({
+                type: LOGOUT,
+                payload: err
+            });
+        }
+    }
+    else {
+        dispatch({
+            type: LOGOUT
+        });
+    }
+};
+
+export const signup = (first_name, last_name, username, password) => async dispatch => {
+    const config = {
+        headers: {
+            'Content-Type': 'application/json'
+        }
+    };
+
+    const body = JSON.stringify({ first_name, last_name, username, password});
+
+    try {
+        const res = await axios.post(`${process.env.REACT_APP_BACKEND_API_URL}/api/create-user/`, body, config);
+        dispatch({
+            type: SIGNUP_SUCCESS,
+            payload: res.data
+        });
+        return {
+            signup: true
+        }
+    } catch (err) {
+        dispatch({
+            type: SIGNUP_FAIL
+        })
+        return {
+            signup: false,
+            err
+        }
+    }
+};
\ No newline at end of file
diff --git a/src/actions/types.js b/src/actions/types.js
new file mode 100644
index 0000000..8240a02
--- /dev/null
+++ b/src/actions/types.js
@@ -0,0 +1,7 @@
+export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
+export const LOGIN_FAIL= 'LOGIN_FAIL';
+export const USER_LOADED_SUCCESS = 'USER_LOADED_SUCCESS';
+export const USER_LOADED_FAIL = 'USER_LOADED_FAIL';
+export const LOGOUT = 'LOGOUT';
+export const SIGNUP_SUCCESS = 'SIGNUP_SUCCESS';
+export const SIGNUP_FAIL = 'SIGNUP_FAIL';
\ No newline at end of file
diff --git a/src/components/Home/Home.css b/src/components/Home/Home.css
index e03be9c..a246bf8 100644
--- a/src/components/Home/Home.css
+++ b/src/components/Home/Home.css
@@ -123,34 +123,6 @@
     box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.25);
 }
 
-.remindme-green-text {
-    color: #146A5F;
-}
-
-.remindme-blue-text {
-    color: var(--remindme-blue);
-}
-
-.remindme-red-text {
-    color: var(--remindme-red);
-}
-
-.remindme-red-button {
-    background-color: var(--remindme-red);
-    color: var(--remindme-white);
-}
-
-.remindme-blue-button {
-    background-color: var(--remindme-blue);
-    color: var(--remindme-white);
-}
-
-.remindme-blue-border-button {
-    background-color: rgba(0, 0, 0, 0);
-    border: 2px solid var(--remindme-blue);
-    color: var(--remindme-blue);
-}
-
 .remindme-home-sect-2-icon {
     width: 30%;
     height: 30%;
@@ -166,55 +138,3 @@
     padding: 80px;
 }
 
-.remindme-home-banner-card {
-    position: relative;
-    z-index: 2;
-    margin: -6% auto 0;
-    width: 100%;
-    border-radius: 50px;
-    box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.25);
-    background-color: white;
-    padding: 5%;
-    box-sizing: initial;
-}
-
-.remindme-home-roles-container {
-    display: flex;
-    justify-content: space-evenly;
-    width: 80%;
-    margin: 30px auto 0px;
-}
-
-.remindme-home-roles {
-    width: 100%;
-    border-radius: 50px;
-    box-shadow: 5px 5px 15px rgba(0, 0, 0, 0.25);
-    background-color: var(--remindme-white);
-    padding: 3% 2%;
-    box-sizing: initial;
-    align-items: center;
-    margin: 0px 20px
-}
-
-.remindme-home-roles-icon {
-    margin-bottom: 38px;
-    margin-left: auto;
-    margin-right: auto;
-    width: 320px;
-    height: 320px;
-    border-radius: 50%;
-    background: conic-gradient(from 180deg at 50% 50%, rgba(255, 255, 255, 0) 0deg, #D3F1EE 63.75deg, rgba(255, 255, 255, 0) 360deg);
-    display: flex;
-    justify-content: center;
-    align-items: center;
-}
-
-.remindme-home-roles-icon-investor{
-    width: 85%;
-    height: 85%;
-}
-
-.remindme-home-roles-icon-mitra{
-    width: 75%;
-    height: 75%;
-}
\ No newline at end of file
diff --git a/src/components/Home/Home.js b/src/components/Home/Home.js
index 8b540f5..6bbf41e 100644
--- a/src/components/Home/Home.js
+++ b/src/components/Home/Home.js
@@ -1,25 +1,28 @@
 import './Home.css'
 import Icon from './Icon.svg'
+import { connect } from 'react-redux';
 
-const Home = () => {
-    return (
-        <div className="remindme-home-sect-2-bg">
-            <div className="remindme-home-sect-2-container">
-                <div className="remindme-home-sect-2-text">
-                    <h1 className="remindme-home-headings">RemindMe!</h1>
-                    <p className="remindme-home-p">RemindMe! is a reminder web application that can remind and schedule important tasks.</p>
-                </div>
-                <div className="remindme-home-sect-2-icon">
-                    <img src={Icon} alt="Walkiddie Icon"></img>
+const Home = ({ user, isAuthenticated }) => {
+    if (!isAuthenticated) {
+        return (
+            <div className="remindme-home-sect-2-bg">
+                <div className="remindme-home-sect-2-container">
+                    <div className="remindme-home-sect-2-text">
+                        <h1 className="remindme-home-headings">RemindMe!</h1>
+                        <p className="remindme-home-p">RemindMe! is a reminder web application that can remind and schedule important tasks.</p>
+                    </div>
+                    <div className="remindme-home-sect-2-icon">
+                        <img src={Icon} alt="Walkiddie Icon"></img>
+                    </div>
                 </div>
             </div>
-        </div>
-    );
+        );
+    }
 }
 
-// const mapStateToProps = (state) => ({
-//     user: state.auth.user
-// })
+const mapStateToProps = (state) => ({
+    isAuthenticated: state.auth.isAuthenticated,
+    user: state.auth.user
+})
 
-// export default connect(mapStateToProps)(Home);
-export default Home;
\ No newline at end of file
+export default connect(mapStateToProps)(Home);
\ No newline at end of file
diff --git a/src/components/Navbar/Navbar.css b/src/components/Navbar/Navbar.css
index a8bc2b9..84dcfad 100644
--- a/src/components/Navbar/Navbar.css
+++ b/src/components/Navbar/Navbar.css
@@ -29,6 +29,8 @@
     max-width: 100%;
     max-height: 100%;
     object-fit: contain;
+    width: 6rem;
+    height: 5rem;
 }
 
 .remindme-nav-buttons {
diff --git a/src/components/Navbar/Navbar.js b/src/components/Navbar/Navbar.js
index 48e788f..d9edf79 100644
--- a/src/components/Navbar/Navbar.js
+++ b/src/components/Navbar/Navbar.js
@@ -1,8 +1,11 @@
 import './Navbar.css'
 import { NavLink, Link } from 'react-router-dom'
 import Logo from './RemindMe.svg'
+import { connect } from 'react-redux';
+import { logout as handleLogout } from '../../actions/auth';
 
-const Navbar = () => {
+const Navbar = ({ user, isAuthenticated, logout }) => {
+    const isLoggedIn = isAuthenticated;
     return (
         <div className="remindme-navbar-container">
             <nav className="remindme-navbar d-flex">
@@ -11,18 +14,26 @@ const Navbar = () => {
                         <img src={Logo} alt="RemindMe Icon"></img>
                     </NavLink>
                 </div>
-                <div className='button-wrap p-2'>
-                    <button className="remindme-nav-button remindme-blue-border-button mr-1"><Link to="/login"><span>Login</span></Link></button>
-                    <button className="remindme-nav-button remindme-blue-border-button ml-1"><Link to="/signup"><span>SignUp</span></Link></button>
-                </div>
+                { !isLoggedIn &&
+                    <div className='button-wrap p-2'>
+                        <button className="remindme-nav-button remindme-blue-border-button mr-1"><Link to="/login"><span>Login</span></Link></button>
+                        <button className="remindme-nav-button remindme-blue-border-button ml-1"><Link to="/signup"><span>SignUp</span></Link></button>
+                    </div>
+                }
+                { isLoggedIn &&
+                    <div className='button-wrap p-2'>
+                        <button className="remindme-nav-button remindme-blue-border-button mr-1" onClick={logout}><Link to="/"><span>Logout</span></Link></button>
+                    </div>
+                }
             </nav>
         </div>
     );
 }
 
-// const mapStateToProps = (state) => ({
-//     user: state.auth.user
-// })
+const mapStateToProps = (state) => ({
+    isAuthenticated: state.auth.isAuthenticated,
+    user: state.auth.user
+})
+
 
-// export default connect(mapStateToProps)(Navbar);
-export default Navbar;
\ No newline at end of file
+export default connect(mapStateToProps, {logout: handleLogout})(Navbar);
\ No newline at end of file
diff --git a/src/pages/Login/Login.js b/src/pages/Login/Login.js
index 25bb7a8..ae4cb08 100644
--- a/src/pages/Login/Login.js
+++ b/src/pages/Login/Login.js
@@ -1,26 +1,42 @@
 import './Login.css';
 import React, { useState } from 'react';
-import { Link, Redirect } from 'react-router-dom';
+import { Link, Navigate } from 'react-router-dom';
 import { Row } from "react-bootstrap";
-import { connect } from 'react-redux';
 import JamKalender from './JamKalender.svg'
-// import { signup as signupAction } from '../../actions/auth';
+import { connect } from 'react-redux';
+import axios from 'axios';
+import { login as loginAction } from '../../actions/auth';
 
-const Login = ({ signup, isAuthenticated, role }) => {
+const Login = ({ login, isAuthenticated }) => {
     const [formData, setFormData] = useState({
         username: '',
         password: ''
     });
-    const [loading, setLoading] = useState(false);
 
     const { username, password } = formData;
 
     const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value });
 
     const onSubmit = async e => {
-
+        e.preventDefault();
+        const res = await login(username, password);
+        console.log(res)
+        if (res.login){
+            console.log(isAuthenticated);
+            alert('logged in successfully!');
+        } else {
+            console.log(res.err.response.data);
+            var err_response = res.err.response.data;
+            var [first_key_error] = Object.keys(err_response);
+            var val_error = err_response[first_key_error];
+            alert(val_error);
+        }
     };
 
+    if (isAuthenticated) {
+        return <Navigate to='/' />
+    }
+
     return (
         <div id="login-signup">
             <div className="login-square-box mt-5">
@@ -70,7 +86,7 @@ const Login = ({ signup, isAuthenticated, role }) => {
                             </button>
                         </Row>
                         <Row className="justify-content-center login-to-login">
-                            <p>Sudah punya akun? <span><Link to='/masuk'>Masuk disini</Link></span></p>
+                            <p>Sudah punya akun? <span><Link to='/signup'>Masuk disini</Link></span></p>
                         </Row>
                     </form>
                 </div>
@@ -81,7 +97,6 @@ const Login = ({ signup, isAuthenticated, role }) => {
 
 const mapStateToProps = state => ({
     isAuthenticated: state.auth.isAuthenticated
-});
+})
 
-// export default connect(mapStateToProps, { signup: signupAction })(SignUp);
-export default Login;
+export default connect(mapStateToProps, { login: loginAction })(Login);
diff --git a/src/pages/SignUp/SignUp.js b/src/pages/SignUp/SignUp.js
index 3f8e581..7e26aa6 100644
--- a/src/pages/SignUp/SignUp.js
+++ b/src/pages/SignUp/SignUp.js
@@ -1,19 +1,20 @@
 import './SignUp.css';
 import React, { useState } from 'react';
-import { Link, Redirect } from 'react-router-dom';
+import { Link, Navigate } from 'react-router-dom';
 import { Row } from "react-bootstrap";
+import JamKalender from './JamKalender.svg';
 import { connect } from 'react-redux';
-import JamKalender from './JamKalender.svg'
-// import { signup as signupAction } from '../../actions/auth';
+import { signup as signupAction } from '../../actions/auth';
 
-const SignUp = ({ signup, isAuthenticated, role }) => {
+const SignUp = ({ signup, isAuthenticated }) => {
+    console.log(isAuthenticated)
     const [formData, setFormData] = useState({
         first_name: '',
         last_name: '',
         username: '',
         password: ''
     });
-    const [loading, setLoading] = useState(false);
+
     const [registered, setRegister] = useState(false);
 
     const { first_name, last_name, username, password } = formData;
@@ -21,35 +22,27 @@ const SignUp = ({ signup, isAuthenticated, role }) => {
     const onChange = e => setFormData({ ...formData, [e.target.name]: e.target.value });
 
     const onSubmit = async e => {
-        // e.preventDefault();
-        // if (password === re_password) {
-        //     setLoading(true);
-        //     const res = await signup(first_name, last_name, username, password, re_password, role);
-        //     if (check(first_name, password) || check(last_name, password) || check(username, password)
-        //         || check(password, first_name) || check(password, last_name) || check(password, username)) {
-        //         alert('Password yang anda masukan terlalu mirip dengan username maupun nama anda')
-        //     } else {
-        //         if (res.signup) {
-        //             alert('Akun berhasil didaftarkan. Silahkan cek username Anda untuk melakukan aktivasi akun.')
-        //             setLoading(false);
-        //             setRegister(true);
-        //         }
-        //         else {
-        //             setLoading(false);
-        //             alert('username yang anda masukan telah terdaftar')
-        //         }
-        //     }
-        // } else {
-        //     alert("Password anda harus sama")
-        // }
+        e.preventDefault();
+        const res = await signup(first_name, last_name, username, password);
+        console.log(res)
+        if (res.signup){
+            setRegister(true);
+            alert('signed up successfully!');
+        } else {
+            console.log(res.err.response.data);
+            var err_response = res.err.response.data;
+            var [first_key_error] = Object.keys(err_response);
+            var val_error = err_response[first_key_error];
+            alert(val_error);
+        }
     };
 
-    // if (isAuthenticated) {
-    //     return <Redirect to='/' />
-    // }
-    // if (registered === true) {
-    //     return <Redirect to="/masuk" />
-    // }
+    if (isAuthenticated) {
+        return <Navigate to='/' />
+    }
+    if (registered === true) {
+        return <Navigate to="/login" />
+    }
 
     return (
         <div id="regist-signup">
@@ -132,7 +125,7 @@ const SignUp = ({ signup, isAuthenticated, role }) => {
                             </button>
                         </Row>
                         <Row className="justify-content-center regist-to-login">
-                            <p>Sudah punya akun? <span><Link to='/masuk'>Masuk disini</Link></span></p>
+                            <p>Sudah punya akun? <span><Link to='/login'>Masuk disini</Link></span></p>
                         </Row>
                     </form>
                 </div>
@@ -145,5 +138,4 @@ const mapStateToProps = state => ({
     isAuthenticated: state.auth.isAuthenticated
 });
 
-// export default connect(mapStateToProps, { signup: signupAction })(SignUp);
-export default SignUp;
+export default connect(mapStateToProps, { signup: signupAction })(SignUp);
diff --git a/src/reducer/auth.js b/src/reducer/auth.js
new file mode 100644
index 0000000..b795ab0
--- /dev/null
+++ b/src/reducer/auth.js
@@ -0,0 +1,75 @@
+//auth.js
+import {
+    LOGIN_SUCCESS,
+    LOGIN_FAIL,
+    USER_LOADED_SUCCESS,
+    USER_LOADED_FAIL,
+    SIGNUP_SUCCESS,
+    SIGNUP_FAIL,
+    LOGOUT
+  } from '../actions/types';
+
+const initialState = {
+    access: localStorage.getItem('access'),
+    refresh: localStorage.getItem('refresh'),
+    isAuthenticated: null,
+    user: null
+};
+
+function reducer(state = initialState, action) {
+    const { type, payload } = action;
+
+    switch (type) {
+        case LOGIN_SUCCESS:
+            console.log("masuk reducer login sukses")
+            localStorage.setItem('access', payload.access);
+            localStorage.setItem('refresh', payload.refresh);
+            return{
+                ...state,
+                isAuthenticated:true,
+                access : payload.access,
+                refresh : payload.refresh
+            }
+        case SIGNUP_SUCCESS:
+            return {
+                ...state,
+                isAuthenticated: false
+            }
+        case USER_LOADED_SUCCESS:
+            return {
+                ...state,
+                isAuthenticated: true,
+                user: payload
+            }
+        case LOGIN_FAIL:
+            localStorage.removeItem('access');
+            localStorage.removeItem('refresh');
+            return {
+                ...state,
+                access: null,
+                refresh: null,
+                isAuthenticated: false,
+                user: null
+            }
+        case USER_LOADED_FAIL:
+            console.log("masuk user loaded fail")
+            return {
+                ...state,
+                user: null
+            }
+        case SIGNUP_FAIL:
+        case LOGOUT:
+            localStorage.removeItem('access');
+            localStorage.removeItem('refresh');
+            return {
+                access: null,
+                refresh: null,
+                isAuthenticated: false,
+                user: null
+            }
+        default:
+            return state
+    }
+}
+
+export default reducer;
diff --git a/src/reducer/index.js b/src/reducer/index.js
new file mode 100644
index 0000000..a94d3ce
--- /dev/null
+++ b/src/reducer/index.js
@@ -0,0 +1,6 @@
+import { combineReducers } from 'redux';
+import auth from './auth';
+
+export default combineReducers({
+  auth
+})
diff --git a/src/store.js b/src/store.js
new file mode 100644
index 0000000..dfdae8b
--- /dev/null
+++ b/src/store.js
@@ -0,0 +1,17 @@
+import { createStore, applyMiddleware } from 'redux';
+import thunk from 'redux-thunk';
+import rootReducer from './reducer';
+
+// Initial state declared in reducer
+const initialState = {};
+
+const middleware = [thunk];
+
+// Store
+const store = createStore(
+  rootReducer,
+  initialState,
+  applyMiddleware(...middleware)
+);
+
+export default store;
diff --git a/src/util/prerender.js b/src/util/prerender.js
new file mode 100644
index 0000000..db26ac9
--- /dev/null
+++ b/src/util/prerender.js
@@ -0,0 +1,9 @@
+import { load_user } from '../actions/auth';
+
+export const fetch_user = async (store, setLoading) => {
+  if (localStorage.getItem('access')) {
+    await load_user()(store.dispatch);
+    console.log("masuk sini ces")
+  }
+  setLoading(false);
+}
-- 
GitLab