Skip to content

Commit 1202cc5

Browse files
authored
Login (#20)
1 parent e02fecc commit 1202cc5

File tree

13 files changed

+1499
-233
lines changed

13 files changed

+1499
-233
lines changed

package-lock.json

Lines changed: 1234 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,7 @@
1313
"check-all": "yarn check:types && yarn check:format && yarn check:unused"
1414
},
1515
"dependencies": {
16-
"@react-oauth/google": "^0.13.4",
1716
"axios": "^1.13.2",
18-
"jwt-decode": "^4.0.0",
1917
"react": "^19.1.0",
2018
"react-dom": "^19.1.0",
2119
"react-icons": "^5.5.0",

src/App.tsx

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,44 @@
1+
// import { useEffect } from 'react';
2+
// import apiClient from './api';
3+
// import { getMe } from './api/auth';
4+
// import { userState } from './common/user';
15
import Router from './router/Router';
26

37
const App = () => {
8+
/*
9+
useEffect(() => {
10+
const checkUser = async () => {
11+
const searchParams = new URLSearchParams(window.location.search);
12+
const urlToken = searchParams.get('token');
13+
14+
if (urlToken) {
15+
localStorage.setItem('accessToken', urlToken);
16+
apiClient.defaults.headers.common['Authorization'] =
17+
`Bearer ${urlToken}`;
18+
// Clean up the URL
19+
window.history.replaceState({}, document.title, '/');
20+
}
21+
22+
23+
const token = localStorage.getItem('accessToken');
24+
if (token) {
25+
apiClient.defaults.headers.common['Authorization'] = `Bearer ${token}`;
26+
try {
27+
const user = await getMe();
28+
userState.isLoggedIn = true;
29+
userState.email = user.email;
30+
userState.nickname = user.username; // Changed from nickname to username
31+
userState.profileImage = user.profileImageUrl; // Changed from profileImage to profileImageUrl
32+
} catch (error) {
33+
console.error('Failed to fetch user:', error);
34+
localStorage.removeItem('accessToken');
35+
}
36+
}
37+
};
38+
checkUser();
39+
}, []);
40+
*/
41+
442
return <Router />;
543
};
644

src/api/auth.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import apiClient from './index';
2+
3+
// export interface User {
4+
// email: string;
5+
// username: string;
6+
// profileImageUrl: string | null;
7+
// role: 'USER';
8+
// }
9+
10+
// export const getMe = async (): Promise<User> => {
11+
// const response = await apiClient.get<User>('/user/profile');
12+
// return response.data;
13+
// };
14+
15+
export const logout = async () => {
16+
await apiClient.post('/logout');
17+
localStorage.removeItem('accessToken');
18+
delete apiClient.defaults.headers.common['Authorization'];
19+
};

src/api/constants.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export const BACKEND_URL = 'http://3.39.210.8.nip.io:8080';

src/api/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import axios from 'axios';
2+
3+
const apiClient = axios.create({
4+
baseURL: 'http://3.39.210.8.nip.io:8080',
5+
});
6+
7+
export default apiClient;

src/api/room.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import apiClient from './index';
2+
3+
interface RoomCreationRequest {
4+
departureId: number;
5+
destinationId: number;
6+
departureTime: string;
7+
minCapacity: number;
8+
maxCapacity: number;
9+
estimatedFee: number;
10+
}
11+
12+
interface RoomCreationResponse {
13+
createdPotId: number;
14+
}
15+
16+
export const createRoom = async (
17+
roomDetails: RoomCreationRequest
18+
): Promise<RoomCreationResponse> => {
19+
const response = await apiClient.post<RoomCreationResponse>(
20+
'/room',
21+
roomDetails
22+
);
23+
return response.data;
24+
};

src/components/NavBar/index.tsx

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
import { FaComment, FaPlus, FaSearch, FaUser } from 'react-icons/fa';
22
import { Link, useLocation } from 'react-router-dom';
3+
import { logout } from '../../api/auth';
4+
import { BACKEND_URL } from '../../api/constants';
5+
import { userState } from '../../common/user';
36
import './navBar.css';
47

58
const NavBar = () => {
@@ -19,6 +22,29 @@ const NavBar = () => {
1922
return location.pathname === path;
2023
};
2124

25+
const handleGoogleLogin = () => {
26+
const frontendRedirectUri = window.location.origin;
27+
28+
const encodedUri = encodeURIComponent(frontendRedirectUri);
29+
const googleLoginUrl = `${BACKEND_URL}/login?redirect_uri=${encodedUri}`;
30+
31+
window.location.href = googleLoginUrl;
32+
};
33+
34+
const handleLogout = async () => {
35+
try {
36+
await logout();
37+
userState.isLoggedIn = false;
38+
userState.email = null;
39+
userState.nickname = '학부생';
40+
userState.profileImage = null;
41+
localStorage.removeItem('accessToken');
42+
window.location.href = '/';
43+
} catch (error) {
44+
console.error('Logout failed:', error);
45+
}
46+
};
47+
2248
return (
2349
<div className="nav-wrapper">
2450
<nav className="navBar">
@@ -40,9 +66,23 @@ const NavBar = () => {
4066
))}
4167
</div>
4268
<div className="login">
43-
<Link to="/login" className="login-button">
44-
로그인
45-
</Link>
69+
{userState.isLoggedIn ? (
70+
<button
71+
type="button"
72+
onClick={handleLogout}
73+
className="login-button"
74+
>
75+
로그아웃
76+
</button>
77+
) : (
78+
<button
79+
type="button"
80+
onClick={handleGoogleLogin}
81+
className="login-button"
82+
>
83+
로그인
84+
</button>
85+
)}
4686
</div>
4787
</nav>
4888
</div>

src/components/NavBar/navBar.css

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,9 @@
6565
.login-button {
6666
color: #333;
6767
text-decoration: none;
68+
background: none;
69+
border: none;
70+
cursor: pointer;
71+
font: inherit;
72+
padding: 0;
6873
}

src/pages/CreateRoom/index.tsx

Lines changed: 30 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,58 @@
1-
import axios from 'axios';
21
import { useState } from 'react';
2+
import { useNavigate } from 'react-router-dom';
3+
import { createRoom } from '../../api/room';
34
import { userState } from '../../common/user';
45
import './CreateRoom.css';
56

67
const landmarks = ['서울대입구역', '낙성대역', '자연대', '행정관'];
78

89
const CreateRoom = () => {
10+
const navigate = useNavigate();
911
const [start, setStart] = useState('');
1012
const [end, setEnd] = useState('');
11-
const [dateTime, setDateTime] = useState('');
12-
const [roomName, setRoomName] = useState('');
13-
const [minParticipants, setMinParticipants] = useState(2);
13+
const [departureTime, setDepartureTime] = useState('');
14+
const [minCapacity, setMinCapacity] = useState(2);
1415

15-
const handleParticipantChange = (amount: number) => {
16-
setMinParticipants((prev) => {
16+
const handleCapacityChange = (amount: number) => {
17+
setMinCapacity((prev) => {
1718
const newValue = prev + amount;
18-
if (newValue >= 2 && newValue <= 4) {
19+
if (newValue >= 1 && newValue <= 4) {
1920
return newValue;
2021
}
2122
return prev;
2223
});
2324
};
2425

2526
const handleCreateRoom = async () => {
26-
if (!start || !end || !dateTime || !roomName) {
27+
// Adjusted validation
28+
if (!start || !end || !departureTime || !minCapacity) {
2729
alert('모든 필드를 채워주세요.');
2830
return;
2931
}
3032

31-
// check for development
3233
if (!userState.isLoggedIn) {
3334
alert('로그인이 필요합니다.');
34-
window.location.href = '/login';
35+
window.location.href = '/';
3536
return;
3637
}
37-
if (import.meta.env.DEV) {
38-
userState.email = 'dev@snu.ac.kr';
39-
}
38+
39+
const departureId = landmarks.indexOf(start) + 1;
40+
const destinationId = landmarks.indexOf(end) + 1;
41+
42+
const departureTimeISO = new Date(departureTime).toISOString();
4043

4144
try {
42-
const response = await axios.post('/room', {
43-
name: roomName,
44-
from: start,
45-
to: end,
46-
time: dateTime,
47-
minHeadcount: minParticipants,
48-
user: userState.email, // Send user's email
45+
await createRoom({
46+
departureId,
47+
destinationId,
48+
departureTime: departureTimeISO,
49+
minCapacity,
50+
maxCapacity: 4,
51+
estimatedFee: 0, // Hardcoded placeholder for estimatedFee
4952
});
5053

51-
if (response.status === 201 || response.status === 200) {
52-
alert('방이 성공적으로 개설되었습니다!');
53-
// Redirect to the new room's page?
54-
// window.location.href = `/rooms/${response.data.roomId}`;
55-
} else {
56-
alert('방 개설에 실패했습니다.');
57-
}
54+
alert('방이 성공적으로 개설되었습니다!');
55+
navigate('/search-room');
5856
} catch (error) {
5957
console.error('Error creating room:', error);
6058
alert('방 개설 중 오류가 발생했습니다.');
@@ -94,20 +92,8 @@ const CreateRoom = () => {
9492
<label>날짜 및 시간</label>
9593
<input
9694
type="datetime-local"
97-
value={dateTime}
98-
onChange={(e) => setDateTime(e.target.value)}
99-
/>
100-
</div>
101-
</div>
102-
103-
<div className="card">
104-
<div className="input-group">
105-
<label>방 이름</label>
106-
<input
107-
type="text"
108-
placeholder="방 이름을 입력하세요"
109-
value={roomName}
110-
onChange={(e) => setRoomName(e.target.value)}
95+
value={departureTime}
96+
onChange={(e) => setDepartureTime(e.target.value)}
11197
/>
11298
</div>
11399
</div>
@@ -116,9 +102,9 @@ const CreateRoom = () => {
116102
<div className="input-group">
117103
<label>최소 인원</label>
118104
<div className="participant-control">
119-
<button onClick={() => handleParticipantChange(-1)}>-</button>
120-
<span>{minParticipants}</span>
121-
<button onClick={() => handleParticipantChange(1)}>+</button>
105+
<button onClick={() => handleCapacityChange(-1)}>-</button>
106+
<span>{minCapacity}</span>
107+
<button onClick={() => handleCapacityChange(1)}>+</button>
122108
</div>
123109
</div>
124110
</div>

0 commit comments

Comments
 (0)