Skip to content

Commit 92b09fe

Browse files
committed
조인코드로 그룹 참여구현
1 parent 93b26a7 commit 92b09fe

File tree

6 files changed

+149
-54
lines changed

6 files changed

+149
-54
lines changed

polling-app-client/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "polling-app-client",
33
"version": "0.1.0",
44
"private": true,
5-
"proxy": "http://localhost:8080",
5+
"proxy": "http://localhost:8080",
66

77
"dependencies": {
88
"antd": "^3.2.2",

polling-app-client/src/components/Dashboard/GroupJoinModal.jsx

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { Component } from 'react';
22
import { Modal, Input, message } from 'antd';
3-
import { ACCESS_TOKEN } from '../../constants';
3+
import { joinGroupByCode } from '../../util/APIUtils';
44

55
class GroupJoinModal extends Component {
66
constructor(props) {
@@ -15,31 +15,46 @@ class GroupJoinModal extends Component {
1515
}
1616

1717
handleSubmit = () => {
18-
const { code } = this.state;
19-
const token = localStorage.getItem(ACCESS_TOKEN);
18+
const { code } = this.state;
2019

21-
fetch('/api/groups/join', {
22-
method: 'POST',
23-
headers: {
24-
'Content-Type': 'application/json',
25-
Authorization: `Bearer ${token}`,
26-
},
27-
body: JSON.stringify({ code }),
28-
})
29-
.then(res => {
30-
if (!res.ok) throw new Error('입장 실패');
31-
return res.json();
32-
})
33-
.then(data => {
34-
message.success(`"${data.name}" 그룹에 입장했어요!`);
35-
this.props.onJoined(); // 새로고침 콜백
36-
this.props.onClose(); // 모달 닫기
37-
})
38-
.catch(() => {
39-
message.error('유효하지 않은 코드입니다.');
40-
});
20+
joinGroupByCode(code)
21+
.then(data => {
22+
const messageText = (typeof data === 'string')
23+
? data
24+
: (data && data.name)
25+
? `"${data.name}" 그룹에 입장했어요!`
26+
: '그룹 참여 완료';
27+
message.success(messageText);
28+
29+
if (typeof this.props.onJoined === 'function') {
30+
this.props.onJoined();
31+
}
32+
33+
if (typeof this.props.onClose === 'function') {
34+
this.props.onClose();
4135
}
4236

37+
setTimeout(() => {
38+
window.location.reload();
39+
}, 500);
40+
})
41+
.catch(error => {
42+
const messageText = typeof error.message === 'string' ? error.message : JSON.stringify(error);
43+
console.error('Join group error:', error);
44+
if (error.status === 409 || messageText.includes('이미')) {
45+
message.success('이미 그룹에 참여하셨습니다.');
46+
47+
if (this.props.onClose) {
48+
this.props.onClose(); // ✅ 안전한 방식
49+
}
50+
51+
setTimeout(() => window.location.reload(), 500);
52+
} else {
53+
message.error('유효하지 않은 코드입니다.');
54+
}
55+
});
56+
}
57+
4358
render() {
4459
const { visible, onClose } = this.props;
4560

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
.group-member-selector-wrapper {
2+
display: flex;
3+
flex-direction: column;
4+
gap: 8px;
5+
}
6+
7+
.selected-user-tags {
8+
display: flex;
9+
flex-wrap: wrap;
10+
gap: 8px;
11+
}
12+
13+
.selected-user-tag {
14+
display: flex;
15+
align-items: center;
16+
padding: 4px 8px;
17+
background: #f0f0f0;
18+
border-radius: 16px;
19+
font-size: 14px;
20+
}
21+
22+
.selected-user-tag .avatar {
23+
background-color: #f56a00;
24+
margin-right: 6px;
25+
}
26+
27+
.selected-user-tag .name {
28+
margin-right: 6px;
29+
}
30+
31+
.selected-user-tag .remove {
32+
cursor: pointer;
33+
color: #888;
34+
font-weight: bold;
35+
}
36+
37+
.user-option {
38+
display: flex;
39+
justify-content: space-between;
40+
align-items: center;
41+
padding: 6px 8px;
42+
border-radius: 4px;
43+
}
44+
45+
.user-option.selected {
46+
background: #f5f5f5;
47+
}
48+
49+
.user-info {
50+
display: flex;
51+
align-items: center;
52+
gap: 8px;
53+
}
54+
55+
.check {
56+
color: #52c41a;
57+
font-size: 16px;
58+
}

polling-app-client/src/components/Dashboard/GroupMemberSelector.jsx

Lines changed: 45 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,56 @@
11
import React from 'react';
22
import { Select, Avatar } from 'antd';
3+
import './GroupMemberSelector.css';
34

45
const { Option } = Select;
56

67
const GroupMemberSelector = ({ users, selectedUserIds, onChange }) => {
8+
// 선택된 사용자 정보 추출
9+
const selectedUsers = users.filter(u => selectedUserIds.includes(u.id));
10+
711
return (
8-
<Select
9-
mode="multiple"
10-
showSearch
11-
placeholder="이름 검색"
12-
value={selectedUserIds}
13-
onChange={onChange}
14-
optionFilterProp="label"
15-
style={{ width: '100%', marginTop: 8 }}
16-
>
17-
{users.map(user => (
18-
<Option key={user.id} value={user.id} label={user.name}>
19-
<div
20-
style={{
21-
display: 'flex',
22-
alignItems: 'center',
23-
padding: '4px 8px',
24-
backgroundColor: selectedUserIds.includes(user.id) ? '#f5f5f5' : 'transparent',
25-
borderRadius: 4,
26-
justifyContent: 'space-between'
27-
}}
28-
>
29-
<div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
30-
<Avatar style={{ backgroundColor: '#f56a00' }}>{user.name[0]}</Avatar>
31-
<span>{user.name}</span>
32-
</div>
33-
{selectedUserIds.includes(user.id) && <span style={{ color: '#52c41a' }}></span>}
12+
<div className="group-member-selector-wrapper">
13+
{/* 입력창 위에 보여줄 선택된 유저 */}
14+
<div className="selected-user-tags">
15+
{selectedUsers.map(user => (
16+
<div key={user.id} className="selected-user-tag">
17+
<Avatar className="avatar">{user.name[0]}</Avatar>
18+
<span className="name">{user.name}</span>
19+
<span
20+
className="remove"
21+
onClick={() => onChange(selectedUserIds.filter(id => id !== user.id))}
22+
>
23+
×
24+
</span>
3425
</div>
35-
</Option>
36-
))}
37-
</Select>
26+
))}
27+
</div>
28+
29+
<Select
30+
mode="multiple"
31+
value={selectedUserIds}
32+
onChange={onChange}
33+
showSearch
34+
optionFilterProp="label"
35+
placeholder="이름 검색"
36+
tagRender={() => null} // 태그 숨기기
37+
maxTagCount={0}
38+
maxTagPlaceholder={() => null}
39+
style={{ width: '100%' }}
40+
>
41+
{users.map(user => (
42+
<Option key={user.id} value={user.id} label={user.name}>
43+
<div className={`user-option ${selectedUserIds.includes(user.id) ? 'selected' : ''}`}>
44+
<div className="user-info">
45+
<Avatar className="avatar">{user.name[0]}</Avatar>
46+
<span>{user.name}</span>
47+
</div>
48+
{selectedUserIds.includes(user.id) && <span className="check"></span>}
49+
</div>
50+
</Option>
51+
))}
52+
</Select>
53+
</div>
3854
);
3955
};
4056

polling-app-client/src/components/Dashboard/HomeDashboard.css

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,3 @@
7575
border-radius: 4px;
7676
cursor: pointer;
7777
}
78-

polling-app-client/src/util/APIUtils.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,10 @@ export function getAllUsers(){
146146
})
147147
}
148148

149+
export function joinGroupByCode(joinCode) {
150+
return request({
151+
url: API_BASE_URL + "/groups/join",
152+
method: "POST",
153+
body: JSON.stringify({ joinCode: String(joinCode) }) // ✅ 필드 이름 수정
154+
});
155+
}

0 commit comments

Comments
 (0)