이전 글에서 backend에서 세팅은 했으니 이제 React에서 로그인/회원가입 페이지와 로그아웃을 구현해 보자
현재 구조는 이렇다
① frontend/src/App.js
import {Route, Switch} from 'react-router-dom';
import React, {Suspense} from 'react';
import './App.css';
import NavBar from './components/NavBar/NavBar'; // 추가
import LoginPage from './components/UserPage/LoginPage'; // 추가
import SignupPage from './components/UserPage/SignupPage'; // 추가
function App() {
return (
<Suspense fallback={(<div>...</div>)}>
<NavBar />
<div className="App">
<Switch>
<Route exact path="/login" component={LoginPage}></Route> // 추가
<Route exact path="/signup" component={SignupPage}></Route> // 추가
</Switch>
</div>
</Suspense>
);
}
export default App;
② frontend/src/components/NavBar/NavBar.js
import React, { useState, useEffect } from 'react';
import { Menu, Button } from 'antd';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
const MenuList = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
.ant-menu {
display: flex;
justify-content: flex-end;
width: 100%;
}
`;
function NavBar() {
const [auth, setAuth] = useState('')
useEffect(() => {
if (localStorage.getItem('token') !== null) {
setAuth(true);
}
}, [])
const handleLogout = () => {
fetch('http://127.0.0.1:8000/api/v1/mall/auth/logout/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Token ${localStorage.getItem('token')}`
}
})
.then(res => res.json())
.then(data => {
console.log(data);
localStorage.clear();
window.location.replace('http://localhost:3000/login');
});
};
return(
<div>
<MenuList>
<Menu>
{ auth ?
<Menu.Item key="logout" onClick={handleLogout}>
로그아웃
</Menu.Item>
:
<Menu.Item key="signin">
<Link to="/login">
로그인
</Link>
</Menu.Item>
}
{ auth ?
<></>
:
<Menu.Item key="signup">
<Link to="/signup">
회원가입
</Link>
</Menu.Item>
}
</Menu>
</MenuList>
</div>
)
}
export default NavBar;
styled-components, antd를 설치해야 한다
npm install --save styled-components
npm install --save antd
네비게이션 바에서 로그인하면 회원가입/로그인 메뉴는 노출되지 않고 로그아웃만 노츨되도록 했다
반대로 로그아웃 하면 다시 회원가입/로그인 메뉴가 노출된다
③ frontend/src/components/UserPage/LoginPage.js
import React, { useState, useEffect } from 'react';
const LoginPage = () => {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [errors, setErrors] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (localStorage.getItem('token') !== null) {
window.location.replace('http://localhost:3000/dashboard');
} else {
setLoading(false);
}
}, []);
const onSubmit = e => {
e.preventDefault();
const user = {
email: email,
password: password
};
fetch('http://127.0.0.1:8000/api/v1/mall/auth/login/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(user)
})
.then(res => res.json())
.then(data => {
if (data.key) {
localStorage.clear();
localStorage.setItem('token', data.key);
window.location.replace('http://localhost:3000/dashboard');
} else {
setEmail('');
setPassword('');
localStorage.clear();
setErrors(true);
}
});
};
return (
<div>
{loading === false && <h1>Login</h1>}
{errors === true && <h2>Cannot log in with provided credentials</h2>}
{loading === false && (
<form onSubmit={onSubmit}>
<label htmlFor='email'>Email address:</label> <br />
<input
name='email'
type='email'
value={email}
required
onChange={e => setEmail(e.target.value)}
/>{' '}
<br />
<label htmlFor='password'>Password:</label> <br />
<input
name='password'
type='password'
value={password}
required
onChange={e => setPassword(e.target.value)}
/>{' '}
<br />
<input type='submit' value='Login' />
</form>
)}
</div>
);
};
export default LoginPage;
localhost:3000/login을 입력하거나 NavBar에 로그인을 클릭하면 아래처럼 페이지가 뜨는 것을 확인할 수 있다
④ frontend/src/components/UserPage/SignupPage.js
import React, { useState, useEffect } from 'react';
const SignupPage = () => {
const [email, setEmail] = useState('');
const [password1, setPassword1] = useState('');
const [password2, setPassword2] = useState('');
const [errors, setErrors] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
if (localStorage.getItem('token') !== null) {
window.location.replace('http://localhost:3000/dashboard');
} else {
setLoading(false);
}
}, []);
const onSubmit = e => {
e.preventDefault();
const user = {
email: email,
password1: password1,
password2: password2
};
fetch('http://127.0.0.1:8000/api/v1/mall/auth/register/', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(user)
})
.then(res => res.json())
.then(data => {
if (data.key) {
localStorage.clear();
localStorage.setItem('token', data.key);
window.location.replace('http://localhost:3000/dashboard');
} else {
setEmail('');
setPassword1('');
setPassword2('');
localStorage.clear();
setErrors(true);
}
});
};
return (
<div>
{loading === false && <h1>Signup</h1>}
{errors === true && <h2>Cannot signup with provided credentials</h2>}
<form onSubmit={onSubmit}>
<label htmlFor='email'>Email address:</label> <br />
<input
name='email'
type='email'
value={email}
onChange={e => setEmail(e.target.value)}
required
/>{' '}
<br />
<label htmlFor='password1'>Password:</label> <br />
<input
name='password1'
type='password'
value={password1}
onChange={e => setPassword1(e.target.value)}
required
/>{' '}
<br />
<label htmlFor='password2'>Confirm password:</label> <br />
<input
name='password2'
type='password'
value={password2}
onChange={e => setPassword2(e.target.value)}
required
/>{' '}
<br />
<input type='submit' value='Signup' />
</form>
</div>
);
};
export default SignupPage;
localhost:3000/signup을 입력하거나 NavBar에서 회원가입을 클릭하면 아래처럼 페이지가 뜨는 것을 확인할 수 있다
나는 axios를 사용할 것이기 때문에 fetch로 로그인/회원가입 하는 부분을 axios를 사용하도록 수정할 것이다
⑤ frontend/src/components/UserPage/LoginPage.js 수정
import React, { useState } from 'react';
import Axios from 'axios';
import { Input } from 'antd';
import styled from 'styled-components';
const LoginDiv = styled.div`
padding: 3rem;
form {
width: 320px;
display: inline-block;
label {
margin-bottom: 1rem;
}
input {
margin-bottom: 1.5rem;
&[type=submit] {
background: black;
color: white;
margin-top: 1rem;
}
}
}
`;
const LoginPage = () => {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [errors, setErrors] = useState(false)
const onSubmit = (e) => {
e.preventDefault()
const user = {
email: email,
password: password
}
Axios.post('/api/v1/mall/auth/login/', user)
.then(res => {
if (res.data.key) {
localStorage.clear()
localStorage.setItem('token', res.data.key)
// 사용하려면 App.js에서 /로 라우팅해야 한다
window.location.replace('/')
} else {
setEmail('')
setPassword('')
localStorage.clear()
setErrors(true)
}
})
.catch(err => {
console.clear()
alert('아이디 또는 비밀번호가 일치하지 않습니다')
setEmail('')
setPassword('')
})
}
return (
<LoginDiv>
<h1>로그인</h1>
<br />
{errors === true && <h2>Cannot log in with provided credentials</h2>}
<form onSubmit={onSubmit}>
<label>이메일 주소:</label>
<Input
type='email'
value={email}
required
onChange={e => setEmail(e.target.value)}
/>
<label>비밀번호:</label>
<Input
type='password'
value={password}
required
onChange={e => setPassword(e.target.value)}
/>
<Input type='submit' size="large" value='로그인' />
</form>
</LoginDiv>
)
}
export default LoginPage;
⑥ frontend/src/components/UserPage/SignupPage.js 수정
import React, { useState } from 'react';
import Axios from 'axios';
import { Input } from 'antd';
import styled from 'styled-components';
const SignupDiv = styled.div`
padding: 3rem;
form {
width: 320px;
display: inline-block;
label {
margin-bottom: 1rem;
}
input {
margin-bottom: 1.5rem;
&[type=submit] {
background: black;
color: white;
margin-top: 1rem;
}
}
}
`;
const SignupPage = () => {
const [email, setEmail] = useState('')
const [password1, setPassword1] = useState('')
const [password2, setPassword2] = useState('')
const [errors, setErrors] = useState(false)
const onChangeEmail = (e) => {
setEmail(e.target.value)
}
const onChangePwd1 = (e) => {
setPassword1(e.target.value)
}
const onChangePwd2 = (e) => {
setPassword2(e.target.value)
}
const onSubmit = (e) => {
e.preventDefault()
const user = {
email: email,
password1: password1,
password2: password2
}
// 유효성 검사
if(password1 !== password2) {
alert('비밀번호와 비밀번호 확인이 일치하지 않습니다')
return false
}
Axios.post('/api/v1/mall/auth/register/', user)
.then(res => {
if (res.data.key) {
localStorage.clear()
localStorage.setItem('token', res.data.key)
// 사용하려면 App.js에서 /로 라우팅해야 한다
window.location.replace('/')
} else {
setEmail('')
setPassword1('')
setPassword2('')
localStorage.clear()
setErrors(true)
}
})
.catch(err => {
console.clear()
alert('아이디 혹은 비밀번호가 일치하지 않습니다')
})
}
return (
<SignupDiv>
<h1>회원가입</h1>
<br />
{errors === true && <h2>Cannot signup with provided credentials</h2>}
<form onSubmit={onSubmit}>
<label htmlFor='email'>이메일 주소:</label>
<Input
type='email'
value={email}
onChange={onChangeEmail}
required
/>
<label htmlFor='password1'>비밀번호(소문자, 숫자, 특수문자 포함 8~16자):</label>
<Input
type='password'
value={password1}
onChange={onChangePwd1}
minLength='8'
pattern='^(?=.*[a-z])(?=.*\d)(?=.*[$@$!%*#?&])[a-z\d$@$!%*#?&]{8,16}$'
required
/>
<br />
<label htmlFor='password2'>비밀번호 확인(소문자, 숫자, 특수문자 포함 8~16자):</label>
<Input
type='password'
value={password2}
onChange={onChangePwd2}
minLength='8'
pattern='^(?=.*[a-z])(?=.*\d)(?=.*[$@$!%*#?&])[a-z\d$@$!%*#?&]{8,16}$'
required
/>
<Input type='submit' size="large" value='가입하기' />
</form>
</SignupDiv>
)
}
export default SignupPage;
⑦ frontend/src/components/NavBar/NavBar.js
import React, { useState, useEffect } from 'react';
import { Menu, Button } from 'antd';
import styled from 'styled-components';
import { Link } from 'react-router-dom';
import Axios from 'axios'; // 추가
const MenuList = styled.div`
display: flex;
flex-direction: row;
justify-content: space-between;
.ant-menu {
display: flex;
justify-content: flex-end;
width: 100%;
}
`;
function NavBar() {
const [auth, setAuth] = useState('')
useEffect(() => {
if (localStorage.getItem('token') !== null) {
setAuth(true)
}
}, [])
// fetch to axios 수정
const handleLogout = () => {
let token = localStorage.getItem('token')
Axios.post('/api/v1/mall/auth/logout/', token)
.then(res => {
localStorage.clear()
// 사용하려면 App.js에서 /로 라우팅해야 한다
window.location.replace('/')
});
}
return(
<div>
<MenuList>
<Menu>
{ auth ?
<Menu.Item key="logout" onClick={handleLogout}>
로그아웃
</Menu.Item>
:
<Menu.Item key="signin">
<Link to="/login">
로그인
</Link>
</Menu.Item>
}
{ auth ?
<></>
:
<Menu.Item key="signup">
<Link to="/signup">
회원가입
</Link>
</Menu.Item>
}
</Menu>
</MenuList>
</div>
)
}
export default NavBar;
완료했다
아래 사이트를 참고했다
medium.com/dev-genius/django-react-authentication-part-2-ea626688165e