Skip to content

Commit 1a637ef

Browse files
authored
Merge pull request #166 from prgrms-web-devcourse-final-project/feat/159-chat-request-and-cancel
[feat] 헤더 클릭 시 상단으로 스크롤, 채팅 수락 시 로직 구현
2 parents e6acf1e + 1068205 commit 1a637ef

File tree

12 files changed

+127
-52
lines changed

12 files changed

+127
-52
lines changed

src/apis/chat.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,13 @@ export const cancelChatRequest = async (emotionRecordId: number) => {
3434
//채팅방 생성
3535
export const createChatroom = async (emotionRecordId: number) => {
3636
const { data } = await axiosInstance.post(
37-
`/chat/chatroom/create?recordId=${emotionRecordId}`,
37+
`/chat/create?recordId=${emotionRecordId}`,
3838
{},
3939
);
4040
return data;
4141
};
4242
//채팅방 닫기
4343
export const closeChatroom = async (chatRoomId: number) => {
44-
const { data } = await axiosInstance.post(`/chat/chatroom/close?chatRoomId=${chatRoomId}`, {});
44+
const { data } = await axiosInstance.post(`/chat/close?chatRoomId=${chatRoomId}`, {});
4545
return data;
4646
};

src/components/ChatConnectLoadingSheet.tsx

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,13 @@ import { useSheetStore } from '@/store/sheetStore';
66
import { useEffect, useState } from 'react';
77
import dayjs from 'dayjs';
88
import duration from 'dayjs/plugin/duration';
9-
import { cancelChatRequest } from '@/apis/chat';
9+
import { cancelChatRequest, createChatroom } from '@/apis/chat';
1010
import { useNavigate } from 'react-router';
1111
dayjs.extend(duration);
1212

1313
export default function ChatConnectLoadingSheet() {
1414
const navigate = useNavigate();
15-
//zustand로 관리
16-
//거절, 취소하거나 홈으로 가기 누르면 false로
15+
1716
const { currentRecord, closeAllSheets, closeSheet } = useSheetStore();
1817

1918
const [timeLeft, setTimeLeft] = useState(60);
@@ -25,6 +24,7 @@ export default function ChatConnectLoadingSheet() {
2524
navigate('/home');
2625
};
2726

27+
//타이머 60초
2828
useEffect(() => {
2929
const endTime = new Date().getTime() + 60 * 1000; // 현재 시간 + 60초
3030

@@ -50,14 +50,14 @@ export default function ChatConnectLoadingSheet() {
5050

5151
const formattedTime = dayjs.duration(timeLeft, 'seconds').format('mm:ss');
5252

53-
//채팅 요청 취소
53+
//채팅 요청 취소(요청 보낸 사람)
5454
const cancel = async () => {
55-
console.log(currentRecord);
5655
if (!currentRecord) {
5756
console.log('record가 존재하지 않습니다');
5857
return;
5958
}
6059
try {
60+
console.log(currentRecord);
6161
await cancelChatRequest(currentRecord.recordId);
6262
console.log('채팅 취소');
6363

@@ -67,7 +67,30 @@ export default function ChatConnectLoadingSheet() {
6767
}
6868
};
6969

70-
const isReceiver = false;
70+
//채팅방 생성 (요청 받는 사람 입장에서 생성?)
71+
const createChat = async () => {
72+
try {
73+
const data = await createChatroom(17);
74+
console.log(data);
75+
76+
if (data.code === 200) {
77+
navigate('/chatroom');
78+
closeAllSheets();
79+
}
80+
//500 이면 이미 방 있음 => 기존 채팅방으로
81+
} catch (error) {
82+
console.log(error);
83+
}
84+
};
85+
86+
//채팅 요정 거절
87+
const refuseRequest = () => {
88+
closeSheet('isChatLoadingSheetOpen');
89+
};
90+
91+
//채팅 신청한 사람이 상대가 수락했다는 sse 받으면 closeAllSheet, chatroom으로 이동
92+
93+
const isReceiver = true;
7194

7295
if (connetFail) {
7396
return (
@@ -135,8 +158,12 @@ export default function ChatConnectLoadingSheet() {
135158
{/* 받는 사람일 경우 */}
136159
{isReceiver && (
137160
<div className="absolute bottom-10 flex gap-[6px] px-3 w-full">
138-
<Button variant="primary">수락하기</Button>
139-
<Button variant="secondary">거절하기</Button>
161+
<Button onClick={createChat} variant="primary">
162+
수락하기
163+
</Button>
164+
<Button onClick={refuseRequest} variant="secondary">
165+
거절하기
166+
</Button>
140167
</div>
141168
)}
142169
</div>

src/components/Modal.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { createPortal } from 'react-dom';
44
// import { spawn } from 'child_process';
55

66
export default function Modal() {
7-
const { modal, closeModal } = useModalStore();
7+
const { modal } = useModalStore();
88
if (!modal.isOpen) return null;
99
return createPortal(
1010
<div

src/components/MoreOptionsSelect.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ function MoreOptionsSelect({ items }: MoreOptionsSelectProps) {
2929
<div className="relative flex" ref={selectRef}>
3030
<button
3131
className="flex justify-center items-center cursor-pointer w-6 h-6"
32-
onClick={() => setIsSelectOpen((prev) => !prev)}
32+
onClick={(e) => {
33+
e.stopPropagation();
34+
setIsSelectOpen((prev) => !prev);
35+
}}
3336
>
3437
<img src={ellipsisIcon} alt="더보기" />
3538
</button>

src/components/modalSheet/ChatActionButtons.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ function ChatActionButtons({
2828
}: ChatActionButtonsProps) {
2929
const navigate = useNavigate();
3030

31-
const { openSheet, closeAllSheets } = useSheetStore(); // 모달 시트
31+
const { openSheet } = useSheetStore(); // 모달 시트
3232

3333
const handleGoToUserPage = () => {
3434
// closeAllSheets(); // 모든 시트를 닫아야할지 카드모달시트만 닫으면 될지 고민중

src/layouts/Layout.tsx

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import HeaderWithBack from '@/layouts/header/HeaderWithBack';
55
import HeaderChat from '@/layouts/header/HeaderChat';
66
import { twMerge } from 'tailwind-merge';
77
import PostButton from '@/components/PostButton';
8+
import { useRef } from 'react';
89

910
function Layout() {
1011
const location = useLocation(); // 현재 URL 가져오기
@@ -47,16 +48,28 @@ function Layout() {
4748

4849
const hasPostButton = location.pathname === '/home' || location.pathname === '/mypage';
4950

51+
//헤더 클릭 시 스크롤
52+
const scrollContainerRef = useRef<HTMLDivElement>(null); // 스크롤 컨테이너 참조
53+
54+
const handleScrollToTop = () => {
55+
if (scrollContainerRef.current) {
56+
scrollContainerRef.current.scrollTo({ top: 0, behavior: 'smooth' });
57+
}
58+
};
59+
5060
return (
51-
<div className="relative max-w-[600px] min-w-[320px] w-full min-h-screen mx-auto bg-background flex flex-col">
61+
<div
62+
ref={scrollContainerRef}
63+
className="relative max-w-[600px] min-w-[320px] w-full h-screen mx-auto bg-background flex flex-col overflow-y-auto scroll"
64+
>
5265
{/* 헤더 */}
53-
{renderHeader()}
66+
<div onClick={handleScrollToTop}>{renderHeader()}</div>
5467

5568
{/* 메인 컨텐츠 영역 */}
5669
<div
5770
className={twMerge(
5871
'pt-[44px] flex-1 flex justify-center w-full px-3',
59-
// showNav && 'pb-[62px] ', // 하단 네비게이션 숨길때만 padding주기
72+
showNav && 'pb-[62px] ', // 하단 네비게이션 숨길때만 padding주기
6073
)}
6174
>
6275
<Outlet />

src/layouts/header/HeaderChat.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,9 @@ function HeaderChat({ showLogo = false, showNickname = false }: HeaderChatProps)
5757

5858
{/* 나가기 버튼 */}
5959
<button
60-
onClick={() =>
60+
onClick={(e) => {
61+
e.stopPropagation();
62+
6163
openModal({
6264
title: '이 대화를 마무리할까요?',
6365
message: '채팅을 종료하면 다시 복구할 수 없습니다.',
@@ -69,8 +71,8 @@ function HeaderChat({ showLogo = false, showNickname = false }: HeaderChatProps)
6971
console.log('취소');
7072
closeModal();
7173
},
72-
})
73-
}
74+
});
75+
}}
7476
className="cursor-pointer"
7577
>
7678
<img src={exitIcon} alt="나가기" />

src/layouts/header/HeaderWithBack.tsx

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { addBlockList } from '@/apis/blockList';
22
import MoreOptionsSelect from '@/components/MoreOptionsSelect';
33
import HedaerLayout from '@/layouts/header/HedaerLayout';
4+
import { useModalStore } from '@/store/modalStore';
5+
import { useSheetStore } from '@/store/sheetStore';
46
import backIcon from '@assets/icons/back-icon.svg';
57
import { useNavigate, useParams } from 'react-router';
68

@@ -13,21 +15,48 @@ interface HeaderWithBackProps {
1315
function HeaderWithBack({ showMoreOptions = false, text }: HeaderWithBackProps) {
1416
const navigate = useNavigate();
1517
const param = useParams();
18+
19+
const { openModal, closeModal } = useModalStore();
20+
const { currentRecord } = useSheetStore();
1621
// 임시함수
1722
const handleBlockUser = async () => {
18-
console.log(param.userId);
19-
if (param.userId) {
20-
const data = await addBlockList(param.userId);
21-
console.log(data);
23+
if (!param.userId || !currentRecord) {
24+
console.log('차단 실패');
25+
return;
2226
}
27+
28+
openModal({
29+
title: `${currentRecord?.nickName}님을 차단할까요?`,
30+
message: '차단된 사용자는 더이상 피드에 나타나지 않습니다',
31+
onConfirm: async () => {
32+
if (param.userId) {
33+
try {
34+
const data = await addBlockList(param.userId);
35+
closeModal();
36+
console.log(data);
37+
} catch (error) {
38+
console.log(error);
39+
}
40+
}
41+
},
42+
onCancel: () => {
43+
closeModal();
44+
},
45+
});
2346
};
2447

2548
return (
2649
<HedaerLayout>
2750
<div className="flex items-center justify-between w-full">
2851
{/* 뒤로가기 / 회원가입 */}
2952
<div className="gap-[10px] flex items-center">
30-
<button onClick={() => navigate(-1)} className="px-2 py-3 cursor-pointer">
53+
<button
54+
onClick={(e) => {
55+
e.stopPropagation();
56+
navigate(-1);
57+
}}
58+
className="px-2 py-3 cursor-pointer"
59+
>
3160
<img src={backIcon} alt="뒤로가기 아이콘" />
3261
</button>
3362
{text && <span className="h4-b text-primary-normal">{text}</span>}

src/pages/chat/Chat.tsx

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,31 +14,31 @@ export default function Chat({}) {
1414
getChatList();
1515
}, []);
1616

17-
const mockData = [
18-
{
19-
chatroom_id: 3,
20-
nickname: '길동이',
21-
emotion: '행복한',
22-
spotify_id: 33,
23-
title: '라일락',
24-
artist: '아이유',
25-
album_image:
26-
'https://i.namu.wiki/i/L4gbrOjwTsNcvpsCq8b4P-3eX9Cs0lrIvwHxtFE7S5jaeMsbdelvBqCLMwe6AJJw2zBQqSI4wE0_Qn-EwaeZdnLvseFvt1w9dg-xo9KrFF_GacO_R7BnHI6XRyDDXvr-PHMmSEnqgcrzLjdbQF9obA.webp',
27-
created_at: '2025-02-19',
28-
comment: '흠흠',
29-
},
30-
{
31-
chatroom_id: 1,
32-
nickname: '철수',
33-
emotion: '우울한',
34-
spotify_id: 88,
35-
title: 'Blue Sky',
36-
artist: 'Cool Band',
37-
album_image: null,
38-
created_at: '2025-02-10',
39-
comment: '김밥',
40-
},
41-
];
17+
// const mockData = [
18+
// {
19+
// chatroom_id: 3,
20+
// nickname: '길동이',
21+
// emotion: '행복한',
22+
// spotify_id: 33,
23+
// title: '라일락',
24+
// artist: '아이유',
25+
// album_image:
26+
// 'https://i.namu.wiki/i/L4gbrOjwTsNcvpsCq8b4P-3eX9Cs0lrIvwHxtFE7S5jaeMsbdelvBqCLMwe6AJJw2zBQqSI4wE0_Qn-EwaeZdnLvseFvt1w9dg-xo9KrFF_GacO_R7BnHI6XRyDDXvr-PHMmSEnqgcrzLjdbQF9obA.webp',
27+
// created_at: '2025-02-19',
28+
// comment: '흠흠',
29+
// },
30+
// {
31+
// chatroom_id: 1,
32+
// nickname: '철수',
33+
// emotion: '우울한',
34+
// spotify_id: 88,
35+
// title: 'Blue Sky',
36+
// artist: 'Cool Band',
37+
// album_image: null,
38+
// created_at: '2025-02-10',
39+
// comment: '김밥',
40+
// },
41+
// ];
4242

4343
if (!chatList.length) {
4444
return (

src/pages/home/Home.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,8 +119,9 @@ function Home() {
119119
</h2>
120120
<EmotionFilter onEmotionClick={onEmotionClick} selectedEmotion={selectedEmotion} />
121121
</div>
122+
122123
{/* 메인카드 리스트 */}
123-
<div className="h-[calc(100vh-334px-env(safe-area-inset-bottom,16px))] overflow-y-auto scroll">
124+
<div className='flex-1'>
124125
{emotionRecords?.pages[0].records.length > 0 ? (
125126
<>
126127
<div className="flex flex-col items-center gap-2.5 pb-5 ">

0 commit comments

Comments
 (0)