Search

React x JWT x SpringSecurity

React x JWT x SpringSecurity

React

โ€ข
Preview
โ€ข
๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
โ—ฆ
react-router-dom
โ—ฆ
axios
โ—ฆ
js-cookie
โ—ฆ
sweetalert2
โ—ฆ
sweetalert2-react-content
โ€ข
package.json
โ€ข
client ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ
โ€ข
contexts
โ€ข
apis
โ€ข
components
โ€ข
pages
โ€ข
Root
โ—ฆ
App.css
โ—ฆ
App.js

Preview

โ€ข
๋ฉ”์ธ ํ™”๋ฉด
โ€ข
ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด
โ—ฆ
ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต
โ€ข
๋กœ๊ทธ์ธ ํ™”๋ฉด
โ—ฆ
๋กœ๊ทธ์ธ ์„ฑ๊ณต
โ—ฆ
๋กœ๊ทธ์ธ ์‹คํŒจ
โ€ข
๋กœ๊ทธ์•„์›ƒ
โ—ฆ
๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต
โ€ข
๋งˆ์ด ํŽ˜์ด์ง€
โ—ฆ
ํšŒ์› ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต
โ—ฆ
ํšŒ์› ๊ฐ€์ž… ์„ฑ๊ณต

๋ฉ”์ธ ํ™”๋ฉด

ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด

ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต

๋กœ๊ทธ์ธ ํ™”๋ฉด

๋กœ๊ทธ์ธ ์„ฑ๊ณต

๋กœ๊ทธ์ธ ์‹คํŒจ

๋กœ๊ทธ์•„์›ƒ

๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต

๋งˆ์ด ํŽ˜์ด์ง€

ํšŒ์› ์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต

ํšŒ์› ๊ฐ€์ž… ์„ฑ๊ณต

๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

โ€ข
react-router-dom
โ€ข
axios
โ€ข
js-cookie
โ€ข
sweetalert2
โ€ข
sweetalert2-react-content
# React X JWT X Spring Security ## ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ค์น˜ ### router npm install react-router-dom ### axios npm install axios ### cookie npm install js-cookie ### sweetalert2 npm install sweetalert2 npm install sweetalert2-react-content
Markdown
๋ณต์‚ฌ

package.json

{ "name": "client", "version": "0.1.0", "private": true, "dependencies": { "@testing-library/jest-dom": "^5.17.0", "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.7.2", "js-cookie": "^3.0.5", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.24.0", "react-scripts": "5.0.1", "sweetalert2": "^11.12.0", "sweetalert2-react-content": "^5.0.7", "web-vitals": "^2.1.4" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject" }, "eslintConfig": { "extends": [ "react-app", "react-app/jest" ] }, "browserslist": { "production": [ ">0.2%", "not dead", "not op_mini all" ], "development": [ "last 1 chrome version", "last 1 firefox version", "last 1 safari version" ] }, "proxy": "http://localhost:8080" }
JSON
๋ณต์‚ฌ

client ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

contexts

โ€ข
LoginContextProvider.jsx
โ€ข
LoginContextConsumer.jsx

LoginContextProvider.jsx

import React, { createContext, useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import Cookies from 'js-cookie' import api from '../apis/api' import * as auth from '../apis/auth' import * as Swal from '../apis/alert' // ๐Ÿ“ฆ์ปจํ…์ŠคํŠธ ์ƒ์„ฑ export const LoginContext = createContext() const LoginContextProvider = ({ children }) => { /* -----------------------[State]-------------------------- */ // ๋กœ๊ทธ์ธ ์—ฌ๋ถ€ const [isLogin, setLogin] = useState(false); // ์œ ์ € ์ •๋ณด const [userInfo, setUserInfo] = useState(null) // ๊ถŒํ•œ ์ •๋ณด const [roles, setRoles] = useState({isUser : false, isAmdin : false}) /* -------------------------------------------------------- */ // ํŽ˜์ด์ง€ ์ด๋™ const navigate = useNavigate() // ๐Ÿชโžก๐Ÿ’ ๋กœ๊ทธ์ธ ์ฒดํฌ const loginCheck = async () => { // ๐Ÿช accessToken ์ฟ ํ‚ค ํ™•์ธ const accessToken = Cookies.get("accessToken") console.log(`accessToken : ${accessToken}`); // ๐Ÿ’in๐Ÿช โŒ if( !accessToken ) { console.log(`์ฟ ํ‚ค์— accessToken(jwt) ๊ฐ€ ์—†์Œ`); // ๋กœ๊ทธ์•„์›ƒ ์„ธํŒ… logoutSetting() return } // ๐Ÿ’in๐Ÿช โญ• console.log(`์ฟ ํ‚ค์— JWT(accessToken) ์ด ์ €์žฅ๋˜์–ด ์žˆ์Œ`); // axios common header ์— ๋“ฑ๋ก api.defaults.headers.common.Authorization = `Bearer ${accessToken}` // ๐Ÿ‘ฉโ€๐Ÿ’ผ ์‚ฌ์šฉ์ž ์ •๋ณด ์š”์ฒญ let response let data try { response = await auth.info() } catch (error) { console.log(`error : ${error}`); console.log(`status : ${response.status}`); return } data = response.data // data = ๐Ÿ‘ฉโ€๐Ÿ’ผ ์‚ฌ์šฉ์ž ์ •๋ณด console.log(`data : ${data}`); // ์ธ์ฆ ์‹คํŒจ โŒ if( data == 'UNAUTHORIZED' || response.status == 401 ) { console.log(`accessToek(jwt) ์ด ๋งŒ๋ฃŒ๋˜์—ˆ๊ฑฐ๋‚˜ ์ธ์ฆ์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.`); return } // ์ธ์ฆ ์„ฑ๊ณต โœ… console.log(`accessToken(jwt) ํ† ํฐ์œผ๋กœ ์‚ฌ์šฉ์ž ์ •๋ณด ์š”์ฒญ ์„ฑ๊ณต!`); // ๋กœ๊ทธ์ธ ์„ธํŒ… loginSetting( data, accessToken ) } // ๐Ÿ” ๋กœ๊ทธ์ธ const login = async (username, password) => { console.log(`username: ${username}`); console.log(`password: ${password}`); try { const response = await auth.login(username, password) const data = response.data const status = response.status const headers = response.headers const authorization = headers.authorization // ๐Ÿ’ JWT const accessToken = authorization.replace("Bearer ", "") console.log(`data : ${data}`); console.log(`status : ${status}`); console.log(`headers : ${headers}`); console.log(`jwt : ${accessToken}`); // ๋กœ๊ทธ์ธ ์„ฑ๊ณต โœ… if( status == 200 ) { Cookies.set("accessToken", accessToken) // ๋กœ๊ทธ์ธ ์ฒดํฌ loginCheck() Swal.alert("๋กœ๊ทธ์ธ ์„ฑ๊ณต", "๋ฉ”์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค", "success", () => { navigate("/") } ) // ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ // navigate("/") } } catch (error) { Swal.alert("๋กœ๊ทธ์ธ ์‹คํŒจ", "์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค", "error") console.log(`๋กœ๊ทธ์ธ ์‹คํŒจ`); } } // ๐Ÿ” ๋กœ๊ทธ์ธ ์„ธํŒ… // ๐Ÿ‘ฉโ€๐Ÿ’ผ userData, ๐Ÿ’ accessToken(jwt) const loginSetting = (userData, accessToken) => { const { no, userId, authList } = userData // ๐Ÿ‘ฉโ€๐Ÿ’ผ Users (DTO) [JSON] const roleList = authList.map((auth) => auth.auth) // ๐Ÿ’ณ [ROLE_USER,ROLE_ADMIN] console.log(`no : ${no}`); console.log(`userId : ${userId}`); console.log(`authList : ${authList}`); console.log(`roleList : ${roleList}`); // axios common header - Authorizaion ํ—ค๋”์— jwt ๋“ฑ๋ก api.defaults.headers.common.Authorization = `Bearer ${accessToken}` // ๐Ÿ“ฆ Context ์— ์ •๋ณด ๋“ฑ๋ก // ๐Ÿ” ๋กœ๊ทธ์ธ ์—ฌ๋ถ€ ์„ธํŒ… setLogin(true) // ๐Ÿ‘ฉโ€๐Ÿ’ผ ์œ ์ € ์ •๋ณด ์„ธํŒ… const updatedUserInfo = {no, userId, roleList} setUserInfo(updatedUserInfo) // ๐Ÿ‘ฎโ€โ™€๏ธ ๊ถŒํ•œ ์ •๋ณด ์„ธํŒ… const updatedRoles = { isUser : false, isAdmin : false } roleList.forEach( (role) => { if( role == 'ROLE_USER' ) updatedRoles.isUser = true if( role == 'ROLE_ADMIN' ) updatedRoles.isAdmin = true }) setRoles(updatedRoles) } // ๋กœ๊ทธ์•„์›ƒ ์„ธํŒ… const logoutSetting = () => { // ๐Ÿš€โŒ axios ํ—ค๋” ์ดˆ๊ธฐํ™” api.defaults.headers.common.Authorization = undefined; // ๐ŸชโŒ ์ฟ ํ‚ค ์ดˆ๊ธฐํ™” Cookies.remove("accessToken") // ๐Ÿ”โŒ ๋กœ๊ทธ์ธ ์—ฌ๋ถ€ : false setLogin(false) // ๐Ÿ‘ฉโ€๐Ÿ’ผโŒ ์œ ์ € ์ •๋ณด ์ดˆ๊ธฐํ™” setUserInfo(null) // ๐Ÿ‘ฎโ€โ™€๏ธโŒ ๊ถŒํ•œ ์ •๋ณด ์ดˆ๊ธฐํ™” setRoles(null) } // ๐Ÿ”“ ๋กœ๊ทธ์•„์›ƒ const logout = (force=false) => { if( force ) { // ๋กœ๊ทธ์•„์›ƒ ์„ธํŒ… logoutSetting() // ํŽ˜์ด์ง€ ์ด๋™ โžก "/" (๋ฉ”์ธ) navigate("/") return } Swal.confirm("๋กœ๊ทธ์•„์›ƒํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?", "๋กœ๊ทธ์•„์›ƒ์„ ์ง„ํ–‰ํ•ฉ๋‹ˆ๋‹ค.", "warning", (result) => { // isConfirmed : ํ™•์ธ ๋ฒ„ํŠผ ํด๋ฆญ ์—ฌ๋ถ€ if( result.isConfirmed ) { Swal.alert("๋กœ๊ทธ์•„์›ƒ ์„ฑ๊ณต", "", "success") logoutSetting() // ๋กœ๊ทธ์•„์›ƒ ์„ธํŒ… navigate("/") // ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ } } ) // const check = window.confirm("์ •๋ง๋กœ ๋กœ๊ทธ์•„์›ƒํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?") // if( check ) { // // ๋กœ๊ทธ์•„์›ƒ ์„ธํŒ… // logoutSetting() // // ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ // navigate("/") // } } // Mount / Update useEffect( () => { // ๋กœ๊ทธ์ธ ์ฒดํฌ loginCheck() // 1๏ธโƒฃ ๐Ÿช ์ฟ ํ‚ค์—์„œ jwt๐Ÿ’ ์„ ๊บผ๋‚ธ๋‹ค // 2๏ธโƒฃ jwt ๐Ÿ’ ์žˆ์œผ๋ฉด, ์„œ๋ฒ„ํ•œํ…Œ ๐Ÿ‘ฉโ€๐Ÿ’ผ ์‚ฌ์šฉ์ž์ •๋ณด๋ฅผ ๋ฐ›์•„์˜จ๋‹ค // 3๏ธโƒฃ ๋กœ๊ทธ์ธ ์„ธํŒ…์„ ํ•œ๋‹ค. (๐Ÿ“ฆ ๋กœ๊ทธ์ธ์—ฌ๋ถ€, ์‚ฌ์šฉ์ž์ •๋ณด, ๊ถŒํ•œ์ •๋ณด ๋“ฑ๋ก) }, []) return ( // ์ปจํ…์ŠคํŠธ ๊ฐ’ ์ง€์ • โžก value={ ?, ? } <LoginContext.Provider value={ {isLogin, userInfo, roles, login, loginCheck, logout} }> {children} </LoginContext.Provider> ) } export default LoginContextProvider
JavaScript
๋ณต์‚ฌ

LoginContextConsumer.jsx

import React, { useContext } from 'react' import { LoginContext } from './LoginContextProvider' const LoginContextConsumer = () => { const { isLogin } = useContext(LoginContext) return ( <div> <h3>๋กœ๊ทธ์ธ ์—ฌ๋ถ€ : {isLogin ? '๋กœ๊ทธ์ธ' : '๋กœ๊ทธ์•„์›ƒ'} </h3> </div> ) } export default LoginContextConsumer
JavaScript
๋ณต์‚ฌ

apis

โ€ข
api.js
โ€ข
auth.js
โ€ข
alert.js

api.js

import axios from 'axios' // axios ๊ฐ์ฒด ์ƒ์„ฑ const api = axios.create() export default api
JavaScript
๋ณต์‚ฌ

auth.js

import api from './api'; // ๋กœ๊ทธ์ธ export const login = (username, password) => api.post(`/login?username=${username}&password=${password}`) // ์‚ฌ์šฉ์ž ์ •๋ณด export const info = () => api.get(`/users/info`) // ํšŒ์› ๊ฐ€์ž… export const join = (data) => api.post(`/users`, data) // ํšŒ์› ์ •๋ณด ์ˆ˜์ • export const update = (data) => api.put(`/users`, data) // ํšŒ์› ํƒˆํ‡ด export const remove = (userId) => api.delete(`/users/${userId}`)
JavaScript
๋ณต์‚ฌ

alert.js

import Swal from 'sweetalert2'; import withReactContent from 'sweetalert2-react-content'; /** * icon : success, error, warning, info, question */ const MySwal = withReactContent(Swal) // ๊ธฐ๋ณธ alert โš  export const alert = (title, text, icon, callback) => { MySwal.fire({ title: title, text: text, icon: icon }) .then( callback ) // ๊ฒฝ๊ณ ์ฐฝ ์ถœ๋ ฅ ์ดํ›„ ์‹คํ–‰ํ•  ์ฝœ๋ฐฑํ•จ์ˆ˜ } // confirm ๐Ÿ‘ฉโ€๐Ÿซ export const confirm = (title, text, icon, callback) => { MySwal.fire({ title: title, text: text, icon: icon, showCancelButton: true, cancelButtonColor: "#d33", cancelButtonText: "No", confirmButtonColor: "#3085d6", confirmButtonText: "Yes", }) .then( callback ) }
JavaScript
๋ณต์‚ฌ

components

โ€ข
Header
โ—ฆ
Header.jsx
โ—ฆ
Header.css
โ€ข
Join
โ—ฆ
JoinForm.jsx
โ—ฆ
JoinForm.css
โ€ข
Login
โ—ฆ
LoginForm.jsx
โ—ฆ
LoginForm.css
โ€ข
User
โ—ฆ
UserForm.jsx
โ—ฆ
UserForm.css

Header

Header.jsx

import React, { useContext } from 'react' import { Link } from 'react-router-dom' import './Header.css' import { LoginContext } from '../../contexts/LoginContextProvider' const Header = () => { // ๐Ÿ“ฆ LoginContext ๊ฐ€์ ธ์˜ค๊ธฐ // ๐ŸงŠ isLogin // ๐ŸŒž logout const { isLogin, logout } = useContext(LoginContext) return ( <header> <div className="logo"> <Link to="/"> <img src="https://i.imgur.com/fzADqJo.png" alt='logo' className='logo' /> </Link> </div> <div className="util"> <ul> {/* ๋กœ๊ทธ์ธ ์—ฌ๋ถ€(isLogin)์— ๋”ฐ๋ผ์„œ ์กฐ๊ฑด๋ถ€ ๋ Œ๋”๋ง */} { isLogin ? <> <li><Link to="/user">๋งˆ์ดํŽ˜์ด์ง€</Link></li> <li><button className='link' onClick={ () => logout() }>๋กœ๊ทธ์•„์›ƒ</button></li> </> : <> <li><Link to="/login">๋กœ๊ทธ์ธ</Link></li> <li><Link to="/join">ํšŒ์›๊ฐ€์ž…</Link></li> <li><Link to="/about">์†Œ๊ฐœ</Link></li> </> } </ul> </div> </header> ) } export default Header
JavaScript
๋ณต์‚ฌ

Header.css

header { display: flex; justify-content: space-between; align-items: center; padding: 20px; border-bottom: 1px solid #000; } .logo { width: 80px; } .util ul { display: flex; justify-content: space-between; align-items: center; column-gap: 12px; }
CSS
๋ณต์‚ฌ

Join

JoinForm.jsx

import React from 'react' const JoinForm = ({ join }) => { const onJoin = (e) => { e.preventDefault() // submit ๊ธฐ๋ณธ ๋™์ž‘ ๋ฐฉ์ง€ const form = e.target const userId = form.username.value const userPw = form.password.value const name = form.name.value const email = form.email.value console.log(userId, userPw, name, email); join( {userId, userPw, name, email} ) } return ( <div className="form"> <h2 className="login-title">Join</h2> <form className='login-form' onSubmit={ (e) => onJoin(e) }> <div> <label htmlFor="name">username</label> <input type="text" id='username' placeholder='username' name='username' autoComplete='username' required /> </div> <div> <label htmlFor="password">password</label> <input type="password" id='passowrd' placeholder='password' name='password' autoComplete='password' required /> </div> <div> <label htmlFor="name">Name</label> <input type="text" id='name' placeholder='name' name='name' autoComplete='name' required /> </div> <div> <label htmlFor="name">Email</label> <input type="text" id='email' placeholder='email' name='email' autoComplete='email' required /> </div> <button type='submit' className='btn btn--form btn-login'> Join </button> </form> </div> ) } export default JoinForm
JavaScript
๋ณต์‚ฌ

JoinForm.css

.form { width: 400px; margin: auto; padding: 36px 48px 48px 48px; background-color: #f2efee; border-radius: 11px; box-shadow: 0 2.4rem 4.8rem rgba(0, 0, 0, 0.15); } .login-title { padding: 15px; font-size: 22px; font-weight: 600; text-align: center; } .login-form { display: grid; grid-template-columns: 1fr; row-gap: 16px; } .login-form label { display: block; margin-bottom: 8px; } .login-form input { width: 100%; padding: 1.2rem; border-radius: 9px; border: none; } .login-form input:focus { outline: none; box-shadow: 0 0 0 4px rgba(253, 242, 233, 0.5); } .btn--form { background-color: #f48982; color: #fdf2e9; align-self: end; padding: 8px; } .btn, .btn:link, .btn:visited { display: inline-block; text-decoration: none; font-size: 20px; font-weight: 600; border-radius: 9px; border: none; cursor: pointer; font-family: inherit; transition: all 0.3s; } .btn-login:hover { outline: 1px solid #f48982; } .btn--form:hover { background-color: #fdf2e9; color: #f48982; }
CSS
๋ณต์‚ฌ

Login

LoginForm.jsx

import React, { useContext } from 'react' import { LoginContext } from '../../contexts/LoginContextProvider' import './LoginForm.css' const LoginForm = () => { const { login } = useContext(LoginContext) // ๐Ÿ“ฆ LoginContext ์˜ login ํ•จ์ˆ˜ const onLogin = (e) => { e.preventDefault() // ๊ธฐ๋ณธ ์ด๋ฒคํŠธ ๋ฐฉ์ง€ const form = e.target // <form> ์š”์†Œ const username = form.username.value // ์•„์ด๋”” - <form> ์•„๋ž˜ input name="username" ์˜ value const password = form.password.value // ๋น„๋ฐ€๋ฒˆํ˜ธ - <form> ์•„๋ž˜ input name="passwword" ์˜ value login( username, password ) // ๋กœ๊ทธ์ธ ์ฒ˜๋ฆฌ ์š”์ฒญ } return ( <div className="form"> <h2 className="login-title">Login</h2> <form className='login-form' onSubmit={ (e) => onLogin(e) }> <div> <label htmlFor="name">username</label> <input type="text" id='username' placeholder='username' name='username' autoComplete='username' required /> </div> <div> <label htmlFor="password">password</label> <input type="password" id='passowrd' placeholder='password' name='password' autoComplete='password' required /> </div> <button type='submit' className='btn btn--form btn-login'> Login </button> </form> </div> ) } export default LoginForm
JavaScript
๋ณต์‚ฌ

LoginForm.css

.form { width: 400px; margin: auto; padding: 36px; background-color: beige; border-radius: 12px; box-shadow: 2px 2px 2px rgba(0,0,0,0.15); } .login-title { text-align: center; padding: 15px; font-size: 22px; font-weight: 600; } .login-form label { display: block; margin-bottom: 8px; } .login-form input { width: 100%; padding: 1.2rem; border-radius: 10px; border: none; } .login-form input:focus { outline: none; box-shadow: 0 0 0 4px rgba(253, 242, 233, 0.5); } .btn--form { background-color: #f48982; color: #fdf2e9; padding: 8px; } .btn { display: inline-block; text-decoration: none; font-size: 20px; font-weight: 600; border-radius: 10px; border: none; cursor: pointer; transition: all 0.3s; width: 100%; margin-top: 12px; } .btn-login:hover { outline: 1px solid #f48982; }
CSS
๋ณต์‚ฌ

User

UserForm.jsx

import React from 'react' const UserForm = ({ userInfo, updateUser, deleteUser }) => { const onUpdate = (e) => { e.preventDefault() const form = e.target const userId = form.username.value const userPw = form.password.value const name = form.name.value const email = form.email.value console.log(userId, userPw, name, email); updateUser( {userId, userPw, name, email } ) } return ( <div className="form"> <h2 className="login-title">UserInfo</h2> <form className='login-form' onSubmit={ (e) => onUpdate(e) }> <div> <label htmlFor="name">username</label> <input type="text" id='username' placeholder='username' name='username' autoComplete='username' required readOnly defaultValue={ userInfo?.userId } /> </div> <div> <label htmlFor="password">password</label> <input type="password" id='passowrd' placeholder='password' name='password' autoComplete='password' required /> </div> <div> <label htmlFor="name">Name</label> <input type="text" id='name' placeholder='name' name='name' autoComplete='name' required defaultValue={ userInfo?.name } /> </div> <div> <label htmlFor="name">Email</label> <input type="text" id='email' placeholder='email' name='email' autoComplete='email' required defaultValue={ userInfo?.email } /> </div> <button type='submit' className='btn btn--form btn-login'> ์ •๋ณด ์ˆ˜์ • </button> <button type='button' className='btn btn--form btn-login' onClick={ () => deleteUser(userInfo.userId)} > ํšŒ์› ํƒˆํ‡ด </button> </form> </div> ) } export default UserForm
JavaScript
๋ณต์‚ฌ

UserForm.css

CSS
๋ณต์‚ฌ

pages

โ€ข
Home.jsx
โ€ข
Login.jsx
โ€ข
Join.jsx
โ€ข
User.jsx
โ€ข
Admin.jsx
โ€ข
About.jsx

Home.jsx

import React from 'react' import Header from '../components/Header/Header' import LoginContextConsumer from '../contexts/LoginContextConsumer' const Home = () => { return ( <> <Header /> <div className="container"> <h1>Home</h1> <hr /> <h2>๋ฉ”์ธ ํŽ˜์ด์ง€</h2> <LoginContextConsumer /> </div> </> ) } export default Home
JavaScript
๋ณต์‚ฌ

Login.jsx

import React from 'react' import Header from '../components/Header/Header' import LoginForm from '../components/Login/LoginForm' const Login = () => { return ( <> <Header /> <div className="container"> <LoginForm /> </div> </> ) } export default Login
JavaScript
๋ณต์‚ฌ

Join.jsx

import React from 'react' import Header from '../components/Header/Header' import JoinForm from '../components/Join/JoinForm' import * as auth from '../apis/auth' import { useNavigate } from 'react-router-dom' import * as Swal from '../apis/alert'; const Join = () => { const navigate = useNavigate() // ํšŒ์›๊ฐ€์ž… ์š”์ฒญ const join = async ( form ) => { console.log(form); let response let data try { response = await auth.join(form) } catch (error) { console.error(`${error}`); console.error(`ํšŒ์›๊ฐ€์ž… ์š”์ฒญ ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.`); return } data = response.data const status = response.status console.log(`data : ${data}`); console.log(`status : ${status}`); if( status === 200 ) { console.log(`ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต!`); Swal.alert("ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต", "๋ฉ”์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.", "success", () => { navigate("/login") }) // alert(`ํšŒ์›๊ฐ€์ž… ์„ฑ๊ณต!`) // navigate("/login") } else { console.log(`ํšŒ์›๊ฐ€์ž… ์‹คํŒจ!`); // alert(`ํšŒ์›๊ฐ€์ž…์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.`) Swal.alert("ํšŒ์›๊ฐ€์ž… ์‹คํŒจ", "ํšŒ์›๊ฐ€์ž…์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.", "error" ) } } return ( <> <Header /> <div className="container"> <JoinForm join={ join } /> </div> </> ) } export default Join
JavaScript
๋ณต์‚ฌ

User.jsx

import React, { useContext, useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import * as auth from '../apis/auth' import Header from '../components/Header/Header' import UserForm from '../components/User/UserForm' import { LoginContext } from '../contexts/LoginContextProvider' import * as Swal from '../apis/alert'; const User = () => { const { isLogin, roles, logout } = useContext(LoginContext) const [ userInfo, setUserInfo ] = useState() const navigate = useNavigate() // ํšŒ์› ์ •๋ณด ์กฐํšŒ - /user/info const getUserInfo = async () => { // ๋น„๋กœ๊ทธ์ธ ๋˜๋Š” USER ๊ถŒํ•œ์ด ์—†์œผ๋ฉด โžก ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ if( !isLogin || !roles.isUser ) { console.log(`isLogin : ${isLogin}`); console.log(`roles.isUser : ${roles.isUser}`); navigate("/login") return } const response = await auth.info() const data = response.data console.log(`getUserInfo`); console.log(data); setUserInfo(data) } // ํšŒ์› ์ •๋ณด ์ˆ˜์ • const updateUser = async ( form ) => { console.log(form); let response let data try { response = await auth.update(form) } catch (error) { console.error(`${error}`); console.error(`ํšŒ์›์ •๋ณด ์ˆ˜์ • ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.`); return } data = response.data const status = response.status console.log(`data : ${data}`); console.log(`status : ${status}`); if( status === 200 ) { console.log(`ํšŒ์›์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต!`); // alert(`ํšŒ์›์ •๋ณด ์ˆ˜์ • ์„ฑ๊ณต!`) // logout() Swal.alert("ํšŒ์›์ˆ˜์ • ์„ฑ๊ณต", "๋กœ๊ทธ์•„์›ƒ ํ›„, ๋‹ค์‹œ ๋กœ๊ทธ์ธํ•ด์ฃผ์„ธ์š”.", "success", () => { logout(true) }) } else { console.log(`ํšŒ์›์ •๋ณด ์ˆ˜์ • ์‹คํŒจ!`); // alert(`ํšŒ์›์ •๋ณด ์ˆ˜์ • ์‹คํŒจ!`) Swal.alert("ํšŒ์›์ˆ˜์ • ์‹คํŒจ", "ํšŒ์›์ˆ˜์ •์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.", "error" ) } } // ํšŒ์› ํƒˆํ‡ด const deleteUser = async (userId) => { console.log(userId); let response let data try { response = await auth.remove(userId) } catch (error) { console.error(`${error}`); console.error(`ํšŒ์›์‚ญ์ œ ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.`); return } data = response.data const status = response.status console.log(`data : ${data}`); console.log(`status : ${status}`); if( status === 200 ) { console.log(`ํšŒ์›์‚ญ์ œ ์„ฑ๊ณต!`); // alert(`ํšŒ์›์‚ญ์ œ ์„ฑ๊ณต!`) // logout() Swal.alert("ํšŒ์›ํƒˆํ‡ด ์„ฑ๊ณต", "๊ทธ๋™์•ˆ ๊ฐ์‚ฌํ–ˆ์Šต๋‹ˆ๋‹ค:)", "success", () => { logout(true) }) } else { console.log(`ํšŒ์›์‚ญ์ œ ์‹คํŒจ!`); // alert(`ํšŒ์›์‚ญ์ œ ์‹คํŒจ!`) Swal.alert("ํšŒ์›ํƒˆํ‡ด ์‹คํŒจ", "ํšŒ์›ํƒˆํ‡ด์— ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.", "error" ) } } useEffect( () => { if( !isLogin ) { return } getUserInfo() }, [isLogin]) return ( <> <Header /> <div className="container"> <UserForm userInfo={userInfo} updateUser={updateUser} deleteUser={deleteUser} /> </div> </> ) } export default User
JavaScript
๋ณต์‚ฌ

Admin.jsx

import React, { useContext, useEffect } from 'react' import Header from '../components/Header/Header' import { LoginContext } from '../contexts/LoginContextProvider' import { useNavigate } from 'react-router-dom'; import * as Swal from '../apis/alert'; const Admin = () => { const { isLogin, userInfo, roles } = useContext(LoginContext); const navigate = useNavigate() useEffect( () => { if( !isLogin || !userInfo ) { // alert(`๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.`) // navigate("/login") Swal.alert("๋กœ๊ทธ์ธ์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.", "๋กœ๊ทธ์ธ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.", "warning", () => { navigate("/login") }) return } if( !roles.isAdmin ) { // alert(`๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค`) // navigate(-1) Swal.alert("๊ถŒํ•œ์ด ์—†์Šต๋‹ˆ๋‹ค.", "์ด์ „ ํ™”๋ฉด์œผ๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.", "warning", () => { navigate(-1) }) return } }, [userInfo]) return ( <> { isLogin && roles.isAdmin && <> <Header /> <div className="container"> <h1>Admin</h1> <hr /> <h2>๊ด€๋ฆฌ์ž ํŽ˜์ด์ง€</h2> <center> <img src="/img/loading.webp" alt="loading" /> </center> </div> </> } </> ) } export default Admin
JavaScript
๋ณต์‚ฌ

About.jsx

import React from 'react' import Header from '../components/Header/Header' import LoginContextConsumer from '../contexts/LoginContextConsumer' const About = () => { return ( <> <Header /> <div className="container"> <h1>About</h1> <hr /> <h2>์†Œ๊ฐœ ํŽ˜์ด์ง€</h2> <LoginContextConsumer /> </div> </> ) } export default About
JavaScript
๋ณต์‚ฌ

Root

โ€ข
App.css
โ€ข
App.js

App.css

.container { width: 960px; margin: 50px auto; } hr { margin-bottom: 50px; }
CSS
๋ณต์‚ฌ

App.js

import { BrowserRouter, Route, Routes } from 'react-router-dom'; import './App.css'; import Home from './pages/Home'; import Login from './pages/Login'; import Join from './pages/Join'; import User from './pages/User'; import About from './pages/About'; import LoginContextProvider from './contexts/LoginContextProvider'; import Admin from './pages/Admin'; function App() { return ( <BrowserRouter> <LoginContextProvider> <Routes> <Route path="/" element={ <Home /> }></Route> <Route path="/login" element={ <Login /> }></Route> <Route path="/join" element={ <Join /> }></Route> <Route path="/user" element={ <User /> }></Route> <Route path="/about" element={ <About /> }></Route> <Route path="/admin" element={ <Admin /> }></Route> </Routes> </LoginContextProvider> </BrowserRouter> ); } export default App;
JavaScript
๋ณต์‚ฌ