Skip to content

Commit 9bd4f80

Browse files
committed
feat: 로그인, 회원가입, 로그아웃 기능 구현
1 parent 87a1f99 commit 9bd4f80

6 files changed

Lines changed: 89 additions & 37 deletions

File tree

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,6 @@
7474
"last 1 safari version"
7575
]
7676
},
77-
"proxy": "https://api.devapi.store",
7877
"devDependencies": {
7978
"autoprefixer": "^10.4.21",
8079
"postcss": "^8.5.6",

src/index.js

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {RecoilRoot} from "recoil";
77
import {QueryClient, QueryClientProvider} from "react-query";
88
import axios from "axios";
99

10-
axios.defaults.baseURL = process.env.REACT_APP_API_URL || "https://api.devapi.store";
10+
axios.defaults.baseURL = process.env.REACT_APP_API_URL || "https://brainrace.duckdns.org:7080";
1111
// react-qeury사용을 위해 선언
1212
const queryClient = new QueryClient({
1313
defaultOptions: {
@@ -32,18 +32,18 @@ axios.interceptors.request.use(config => {
3232
});
3333

3434
// Axios 응답 인터셉터 - 응답 후에 실행됨
35-
// axios.interceptors.response.use(response => {
36-
// return response;
37-
// }, error => {
38-
// const skip = error.config?.skipAuthInterceptor;
39-
// // 401 또는 403 에러인 경우 로그인 페이지로 리다이렉트
40-
// if (!skip && error.response && (error.response.status === 401 || error.response.status === 403)) {
41-
// //이 부분은 원래는 navigate로 핸들링 해야하지만 현재 리액트 포팅 진행중이므로 window객체에 직접 접근함
42-
// //추후 navigate로 포팅되어야 함
43-
// window.location.href = "/login";
44-
// }
45-
// return Promise.reject(error);
46-
// });
35+
axios.interceptors.response.use(response => {
36+
return response;
37+
}, error => {
38+
const skip = error.config?.skipAuthInterceptor;
39+
// 401 또는 403 에러인 경우 로그인 페이지로 리다이렉트
40+
if (!skip && error.response && (error.response.status === 401 || error.response.status === 403)) {
41+
//이 부분은 원래는 navigate로 핸들링 해야하지만 현재 리액트 포팅 진행중이므로 window객체에 직접 접근함
42+
//추후 navigate로 포팅되어야 함
43+
window.location.href = "/login";
44+
}
45+
return Promise.reject(error);
46+
});
4747

4848
const root = ReactDOM.createRoot(document.getElementById('root'));
4949
root.render(

src/layout/main/Layout.js

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,19 @@ import {Outlet, useLocation, NavLink, useNavigate} from "react-router-dom";
22
import {Button, Image, Nav, Stack} from "react-bootstrap";
33
import mainLogoRect from "../../assets/images/main-logo-rect.png";
44
import styles from "./Layout.module.scss"
5+
import axios from "axios";
6+
import {useApiQuery} from "../../hooks/useApiQuery";
7+
import {useApiMutation} from "../../hooks/useApiMutation";
8+
9+
const authMeRequest = async () => {
10+
const response = await axios.get(`/auth/me`);
11+
return response.data;
12+
};
13+
14+
const logoutRequest = async () => {
15+
await axios.post(`/logout`);
16+
}
17+
518

619
const Layout = () => {
720
const location = useLocation();
@@ -106,6 +119,17 @@ const Layout = () => {
106119
}
107120
};
108121

122+
const { isLoading, data } = useApiQuery(
123+
["authme"],
124+
() => authMeRequest(),
125+
);
126+
127+
const { mutate: logoutMutate } = useApiMutation(logoutRequest, {
128+
onSuccess: () => {
129+
navigate("/login");
130+
},
131+
});
132+
109133
return (
110134
<>
111135
{/* Fixed Header */}
@@ -125,7 +149,7 @@ const Layout = () => {
125149
<div className="d-flex align-items-center gap-4">
126150
{/* User Badge */}
127151
<div style={f1Styles.userBadge}>
128-
빵야빵야님
152+
{data?.name}
129153
</div>
130154

131155
{/* Navigation */}
@@ -216,7 +240,7 @@ const Layout = () => {
216240

217241
<button
218242
style={f1Styles.logoutButton}
219-
onClick={() => navigate('/login')}
243+
onClick={() => logoutMutate()}
220244
onMouseEnter={(e) => {
221245
e.target.style.backgroundColor = '#38384a';
222246
e.target.style.borderColor = '#e10600';

src/pages/login/Login.js

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
"use client"
2-
31
import { useNavigate } from "react-router-dom"
42
import mainLogo from "../../assets/images/main-logo-rect.png"
53
import styles from "./Login.module.scss"
@@ -13,7 +11,10 @@ const Login = () => {
1311
<div className={styles.loginLogo}>
1412
<img src={mainLogo || "/placeholder.svg"} alt="뇌피셜 로고" className={styles.loginLogoImage} />
1513
</div>
16-
<button className={styles.loginButton} onClick={() => navigate("/signup")}>
14+
<button className={styles.loginButton}
15+
onClick={() => {
16+
window.location.href = "https://brainrace.duckdns.org:7080/oauth2/authorization/kakao";
17+
}}>
1718
뇌피셜 입장하기
1819
</button>
1920
</div>

src/pages/login/NicknameForm.js

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,25 @@
33
import { useState } from "react"
44
import styles from "./Login.module.scss"
55
import { isEmptyOrNull } from "../../utils/utils"
6+
import axios from "axios";
7+
import {useApiMutation} from "../../hooks/useApiMutation";
8+
9+
const checkNicknameRequest = async (nickname) => {
10+
const params = {
11+
nickname
12+
};
13+
const response = await axios.get(`/check-nickname`, { params });
14+
return response.data;
15+
};
616

717
const NicknameForm = ({ nickname, setNickname, error, setError, setInputStatus }) => {
8-
const [isChecked, setChecked] = useState(false)
18+
const [isChecked, setChecked] = useState(false);
19+
const { mutate: checkNicknameMutate } = useApiMutation(checkNicknameRequest, {
20+
onSuccess: () => {
21+
setChecked(true);
22+
setInputStatus((prev) => ({ ...prev, nickname: true }));
23+
},
24+
});
925

1026
const isValid = (value) => {
1127
const trimmed = value.trim()
@@ -15,8 +31,7 @@ const NicknameForm = ({ nickname, setNickname, error, setError, setInputStatus }
1531
}
1632

1733
const handleNicknameCheckClick = () => {
18-
setChecked(false)
19-
setInputStatus((prev) => ({ ...prev, nickname: false }))
34+
setChecked(false);
2035

2136
if (isEmptyOrNull(nickname)) {
2237
setError({ ...error, nickname: "닉네임을 먼저 입력해 주세요." })
@@ -27,10 +42,7 @@ const NicknameForm = ({ nickname, setNickname, error, setError, setInputStatus }
2742
setError({ ...error, nickname: "한글,영문,숫자로 6자 이하만 가능합니다." })
2843
return
2944
}
30-
31-
// 중복 api 호출 부분 (임시로 성공 처리)
32-
setChecked(true)
33-
setError({ ...error, nickname: "" })
45+
checkNicknameMutate(nickname);
3446
}
3547

3648
const handleNicknameChange = (e) => {

src/pages/login/Signup.js

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,42 @@ import { useState } from "react"
44
import { useNavigate } from "react-router-dom"
55
import NicknameForm from "./NicknameForm"
66
import styles from "./Login.module.scss"
7+
import axios from "axios";
8+
import {useApiMutation} from "../../hooks/useApiMutation";
9+
import {useApiQuery} from "../../hooks/useApiQuery";
10+
11+
const signupRequest = async (nickname) => {
12+
const params = {
13+
nickname
14+
};
15+
const response = await axios.post(`/signup`, params);
16+
return response.data;
17+
};
718

819
const Signup = () => {
920
const [nickname, setNickname] = useState("")
1021
const [error, setError] = useState({
1122
nickname: "",
12-
})
23+
});
1324
const [inputStatus, setInputStatus] = useState({
1425
nickname: false,
15-
})
16-
const navigate = useNavigate()
26+
});
27+
const navigate = useNavigate();
28+
const { mutate: signupMutate } = useApiMutation(signupRequest, {
29+
onSuccess: (data) => {
30+
console.log(data);
31+
// 회원가입 완료 후 방 목록으로 이동
32+
navigate("/room");
33+
},
34+
});
1735

1836
const handleSignupClick = (e) => {
19-
e.preventDefault()
20-
if (nickname.length === 0) {
21-
setError({ ...error, nickname: "별명을 입력하세요." })
22-
return
37+
e.preventDefault();
38+
if (inputStatus.nickname) {
39+
signupMutate(nickname);
40+
} else {
41+
setError({ ...error, nickname: "중복체크를 먼저 해주세요." })
2342
}
24-
25-
// 회원가입 완료 후 방 목록으로 이동
26-
navigate("/room")
2743
}
2844

2945
const handleCancel = () => {
@@ -48,7 +64,7 @@ const Signup = () => {
4864
/>
4965

5066
<div className={styles.buttonGroup}>
51-
<button type="submit" className={styles.primaryButton}>
67+
<button type="submit" className={styles.primaryButton} onClick={handleSignupClick}>
5268
회원가입 완료
5369
</button>
5470
<button type="button" className={styles.secondaryButton} onClick={handleCancel}>

0 commit comments

Comments
 (0)