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
๋ณต์ฌ