Skip to content

Commit 914020a

Browse files
authored
feat(fe): change styles (#223)
* feat: disconnect socket on session termination * feat: add DeleteConfirmModal component for deletion confirmation * feat: integrate DeleteConfirmModal for question and reply deletion * feat: add empty state for replies and questions * feat: change style for session terminate button
1 parent 55da8fb commit 914020a

File tree

7 files changed

+136
-46
lines changed

7 files changed

+136
-46
lines changed
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { useModalContext } from '@/features/modal';
2+
3+
import Button from '@/components/Button';
4+
5+
interface DeleteConfirmModalProps {
6+
onCancel?: () => void;
7+
onConfirm?: () => void;
8+
}
9+
10+
function DeleteConfirmModal({ onCancel, onConfirm }: DeleteConfirmModalProps) {
11+
const { closeModal } = useModalContext();
12+
13+
return (
14+
<div className='inline-flex flex-col items-center justify-center gap-2.5 rounded-lg bg-gray-50 p-8 shadow'>
15+
<div className='flex h-[8dvh] min-w-[20dvw] flex-col justify-center gap-2'>
16+
<div className='w-full text-center font-bold'>
17+
<span>정말 삭제하시겠습니까?</span>
18+
</div>
19+
<div className='mx-auto mt-4 inline-flex w-full items-start justify-center gap-2.5'>
20+
<Button
21+
className='w-full bg-gray-500'
22+
onClick={() => {
23+
onCancel?.();
24+
closeModal();
25+
}}
26+
>
27+
<span className='flex-grow text-sm font-medium text-white'>
28+
취소하기
29+
</span>
30+
</Button>
31+
<Button
32+
className='w-full bg-indigo-600 transition-colors duration-200'
33+
onClick={() => {
34+
onConfirm?.();
35+
closeModal();
36+
}}
37+
>
38+
<span className='flex-grow text-sm font-medium text-white'>
39+
삭제하기
40+
</span>
41+
</Button>
42+
</div>
43+
</div>
44+
</div>
45+
);
46+
}
47+
48+
export default DeleteConfirmModal;

apps/client/src/components/qna/QuestionDetail.tsx

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,16 +54,25 @@ function QuestionDetail() {
5454
{question.body}
5555
</Markdown>
5656
</div>
57-
{question.replies
58-
.sort((a, b) => {
59-
if (b.isHost !== a.isHost) {
60-
return b.isHost ? 1 : -1;
61-
}
62-
return a.replyId - b.replyId;
63-
})
64-
.map((r) => (
65-
<ReplyItem key={r.replyId} question={question} reply={r} />
66-
))}
57+
{question.replies.length === 0 ? (
58+
<div className='inline-flex h-full w-full select-none items-center justify-center'>
59+
<div className='font-header text-5xl opacity-30'>
60+
<span className='text-indigo-600'>A</span>
61+
<span className='text-black'>sk-It</span>
62+
</div>
63+
</div>
64+
) : (
65+
question.replies
66+
.sort((a, b) => {
67+
if (b.isHost !== a.isHost) {
68+
return b.isHost ? 1 : -1;
69+
}
70+
return a.replyId - b.replyId;
71+
})
72+
.map((r) => (
73+
<ReplyItem key={r.replyId} question={question} reply={r} />
74+
))
75+
)}
6776
</div>
6877
</div>
6978
{Modal}

apps/client/src/components/qna/QuestionItem.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
import { useToastStore } from '@/features/toast';
2121

2222
import { Button, CreateQuestionModal } from '@/components';
23+
import DeleteConfirmModal from '@/components/modal/DeleteConfirmModal';
2324

2425
interface QuestionItemProps {
2526
question: Question;
@@ -31,9 +32,8 @@ function QuestionItem({ question, onQuestionSelect }: QuestionItemProps) {
3132

3233
const { addToast } = useToastStore();
3334

34-
const { Modal, openModal } = useModal(
35-
<CreateQuestionModal question={question} />,
36-
);
35+
const { Modal: CreateQuestion, openModal: openCreateQuestionModal } =
36+
useModal(<CreateQuestionModal question={question} />);
3737

3838
const {
3939
sessionToken,
@@ -208,6 +208,10 @@ function QuestionItem({ question, onQuestionSelect }: QuestionItemProps) {
208208
});
209209
};
210210

211+
const { Modal: DeleteConfirm, openModal: openDeleteConfirmModal } = useModal(
212+
<DeleteConfirmModal onConfirm={handleDelete} />,
213+
);
214+
211215
return (
212216
<>
213217
<div
@@ -289,7 +293,7 @@ function QuestionItem({ question, onQuestionSelect }: QuestionItemProps) {
289293
question.replies.length === 0 && (
290294
<Button
291295
className='bg-gray-200/25 font-medium text-gray-500 hover:bg-gray-200/50 hover:transition-all'
292-
onClick={openModal}
296+
onClick={openCreateQuestionModal}
293297
>
294298
<FiEdit2 />
295299
</Button>
@@ -300,7 +304,7 @@ function QuestionItem({ question, onQuestionSelect }: QuestionItemProps) {
300304
question.replies.length === 0)) && (
301305
<Button
302306
className='bg-red-200/25 text-red-600 hover:bg-red-200/50 hover:transition-all'
303-
onClick={handleDelete}
307+
onClick={openDeleteConfirmModal}
304308
>
305309
<GrClose />
306310
</Button>
@@ -310,7 +314,8 @@ function QuestionItem({ question, onQuestionSelect }: QuestionItemProps) {
310314
</div>
311315
</div>
312316
</div>
313-
{Modal}
317+
{CreateQuestion}
318+
{DeleteConfirm}
314319
</>
315320
);
316321
}

apps/client/src/components/qna/QuestionList.tsx

Lines changed: 41 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,13 @@ function QuestionList() {
103103

104104
const sessionButtons = [
105105
{
106-
icon: <IoShareSocialOutline />,
107-
label: '공유',
106+
key: '공유',
107+
button: (
108+
<div className='flex w-full cursor-pointer flex-row items-center gap-2'>
109+
<IoShareSocialOutline />
110+
<p>공유</p>
111+
</div>
112+
),
108113
onClick: async () => {
109114
const shareUrl = `${window.location.origin}/session/${sessionId}`;
110115

@@ -125,13 +130,23 @@ function QuestionList() {
125130
},
126131
},
127132
{
128-
icon: <GrValidate />,
129-
label: '호스트 설정',
133+
key: '호스트 설정',
134+
button: (
135+
<div className='flex w-full cursor-pointer flex-row items-center gap-2'>
136+
<GrValidate />
137+
<p>호스트 설정</p>
138+
</div>
139+
),
130140
onClick: () => openSessionParticipantsModal(),
131141
},
132142
{
133-
icon: <IoClose />,
134-
label: '세션 종료',
143+
key: '세션 종료',
144+
button: (
145+
<div className='flex w-full cursor-pointer flex-row items-center gap-2 text-red-600'>
146+
<IoClose />
147+
<p>세션 종료</p>
148+
</div>
149+
),
135150
onClick: () => openSessionTerminateModal(),
136151
},
137152
];
@@ -170,17 +185,26 @@ function QuestionList() {
170185
</div>
171186
)}
172187
</div>
173-
<motion.div className='inline-flex h-full w-full flex-col items-start justify-start gap-4 overflow-y-auto px-8 py-4'>
174-
{sections.map((section) => (
175-
<QuestionSection
176-
key={section.title}
177-
title={section.title}
178-
initialOpen={section.initialOpen}
179-
questions={section.questions}
180-
onQuestionSelect={setSelectedQuestionId}
181-
/>
182-
))}
183-
</motion.div>
188+
{questions.length === 0 ? (
189+
<div className='inline-flex h-full w-full select-none items-center justify-center'>
190+
<div className='font-header text-5xl opacity-30'>
191+
<span className='text-indigo-600'>A</span>
192+
<span className='text-black'>sk-It</span>
193+
</div>
194+
</div>
195+
) : (
196+
<motion.div className='inline-flex h-full w-full flex-col items-start justify-start gap-4 overflow-y-auto px-8 py-4'>
197+
{sections.map((section) => (
198+
<QuestionSection
199+
key={section.title}
200+
title={section.title}
201+
initialOpen={section.initialOpen}
202+
questions={section.questions}
203+
onQuestionSelect={setSelectedQuestionId}
204+
/>
205+
))}
206+
</motion.div>
207+
)}
184208
</div>
185209
{CreateQuestion}
186210
{SessionParticipants}

apps/client/src/components/qna/ReplyItem.tsx

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
import { useToastStore } from '@/features/toast';
1717

1818
import { Button, CreateReplyModal } from '@/components';
19+
import DeleteConfirmModal from '@/components/modal/DeleteConfirmModal';
1920

2021
interface ReplyItemProps {
2122
question: Question;
@@ -28,7 +29,7 @@ function ReplyItem({ question, reply }: ReplyItemProps) {
2829
const { sessionId, sessionToken, isHost, expired, updateReply } =
2930
useSessionStore();
3031

31-
const { Modal, openModal } = useModal(
32+
const { Modal: CreateReply, openModal: openCreateReplyModal } = useModal(
3233
<CreateReplyModal question={question} reply={reply} />,
3334
);
3435

@@ -116,6 +117,10 @@ function ReplyItem({ question, reply }: ReplyItemProps) {
116117
});
117118
};
118119

120+
const { Modal: DeleteModal, openModal: openDeleteModal } = useModal(
121+
<DeleteConfirmModal onConfirm={() => handleDelete()} />,
122+
);
123+
119124
return (
120125
<>
121126
<div className='flex shrink basis-0 flex-col items-start justify-start gap-4 self-stretch px-12'>
@@ -155,15 +160,15 @@ function ReplyItem({ question, reply }: ReplyItemProps) {
155160
{reply.isOwner && (
156161
<Button
157162
className='bg-gray-200/25 hover:bg-gray-200/50 hover:transition-all'
158-
onClick={openModal}
163+
onClick={openCreateReplyModal}
159164
>
160165
<FiEdit2 />
161166
</Button>
162167
)}
163168
{(isHost || reply.isOwner) && (
164169
<Button
165170
className='bg-red-200/25 text-red-600 hover:bg-red-200/50 hover:transition-all'
166-
onClick={handleDelete}
171+
onClick={openDeleteModal}
167172
>
168173
<GrClose />
169174
</Button>
@@ -174,7 +179,8 @@ function ReplyItem({ question, reply }: ReplyItemProps) {
174179
</div>
175180
</div>
176181
</div>
177-
{Modal}
182+
{CreateReply}
183+
{DeleteModal}
178184
</>
179185
);
180186
}

apps/client/src/components/qna/SessionSettingsDropdown.tsx

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import Button from '@/components/Button';
44

55
interface SessionSettingsDropdownProps {
66
buttons: Array<{
7-
icon: React.ReactNode;
8-
label: string;
7+
key: string;
8+
button: React.ReactNode;
99
onClick: () => void;
1010
}>;
1111
triggerRef?: React.RefObject<HTMLButtonElement>;
@@ -36,26 +36,23 @@ function SessionSettingsDropdown({
3636
return () => {
3737
document.removeEventListener('mousedown', handleClickOutside);
3838
};
39-
}, [onClose]);
39+
}, [onClose, triggerRef]);
4040

4141
return (
4242
<div
4343
ref={dropdownRef}
4444
className='absolute z-10 flex w-max flex-col gap-2 rounded-md bg-white p-4 shadow-lg ring-1 ring-black ring-opacity-5'
4545
>
46-
{buttons.map(({ icon, label, onClick }) => (
46+
{buttons.map(({ key, button, onClick }) => (
4747
<Button
4848
className='w-full rounded text-sm font-medium text-black transition-all duration-100 hover:bg-gray-100'
49-
key={label}
49+
key={key}
5050
onClick={() => {
5151
onClick();
5252
onClose();
5353
}}
5454
>
55-
<div className='flex w-full cursor-pointer flex-row items-center gap-2'>
56-
{icon}
57-
{label}
58-
</div>
55+
{button}
5956
</Button>
6057
))}
6158
</div>

apps/client/src/features/socket/socket.service.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ export class SocketService {
140140
message: '세션이 종료되었습니다.',
141141
duration: 3000,
142142
});
143+
this.socket.disconnect();
143144
});
144145
}
145146

0 commit comments

Comments
 (0)