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