Skip to content

Commit b24b885

Browse files
authored
Merge pull request #160 from prgrms-web-devcourse-final-project/feat/159-chat-request-and-cancel
[feat] 채팅 요청, 채팅 요청 취소 구현 + 유저 페이지 이동시 자연스럽게 수정
2 parents 1e3d0a6 + 5825b95 commit b24b885

File tree

9 files changed

+178
-40
lines changed

9 files changed

+178
-40
lines changed

src/App.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,13 +25,15 @@ import YouTubeAudioPlayer from './components/YouTubeAudioPlayer';
2525
import TestLoginModal from '@/components/testLogin/TestLoginModal';
2626
import { AnimatePresence, motion } from 'framer-motion';
2727
import { twMerge } from 'tailwind-merge';
28+
import { useSheetStore } from './store/sheetStore';
2829

2930
function App() {
3031
const location = useLocation();
3132
// 실제 로그인 여부를 체크하는 함수 (임시로 false, 실제 인증 로직 적용 필요)
3233
// const isAuthenticated = true;
3334
const { isAuthenticated } = useAuthStore();
3435
const spotifyAuth = useSpotifyAuth();
36+
const { isChatLoadingSheetOpen } = useSheetStore();
3537
// soundlink 로그인한 경우에만 spotify 로그인 후 토큰 가져오기
3638
useEffect(() => {
3739
if (isAuthenticated) {
@@ -54,7 +56,8 @@ function App() {
5456
location.pathname === '/signup' ||
5557
location.pathname === '/post' ||
5658
location.pathname === '/mypage/edit' ||
57-
location.pathname === '/mypage/blocklist'
59+
location.pathname === '/mypage/blocklist' ||
60+
location.pathname.includes('user')
5861
) {
5962
return {
6063
initial: { x: '100%' },
@@ -128,7 +131,7 @@ function App() {
128131
<div className="w-full h-full bg-white z-50"></div>
129132
</div>
130133
<Modal />
131-
<ChatConnectLoadingSheet />
134+
{isChatLoadingSheetOpen && <ChatConnectLoadingSheet />}
132135
<YouTubeAudioPlayer playerId="1" />
133136
<YouTubeAudioPlayer playerId="2" />
134137
<YouTubeAudioPlayer playerId="3" />

src/apis/chat.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,35 @@ export const loadChatMessages = async (roomId: number) => {
1212
const { data } = await axiosInstance.get(`/chat/messages/${roomId}`);
1313
return data;
1414
};
15-
1615
//채팅 요청
17-
export const requestChat = async (tag: string) => {
18-
const { data } = await axiosInstance.post(`/chat/chatroom`, {
19-
participantTag: tag,
16+
export const requestChat = async (emotionRecordId: number) => {
17+
const { data } = await axiosInstance.post(`/chat/request`, emotionRecordId, {
18+
headers: {
19+
'Content-Type': 'application/json',
20+
},
21+
});
22+
return data;
23+
};
24+
//채팅 요청 취소
25+
export const cancelChatRequest = async (emotionRecordId: number) => {
26+
const { data } = await axiosInstance.delete(`/chat/request`, {
27+
headers: {
28+
'Content-Type': 'application/json',
29+
},
30+
data: JSON.stringify(emotionRecordId),
2031
});
2132
return data;
2233
};
34+
//채팅방 생성
35+
export const createChatroom = async (emotionRecordId: number) => {
36+
const { data } = await axiosInstance.post(
37+
`/chat/chatroom/create?recordId=${emotionRecordId}`,
38+
{},
39+
);
40+
return data;
41+
};
42+
//채팅방 닫기
43+
export const closeChatroom = async (chatRoomId: number) => {
44+
const { data } = await axiosInstance.post(`/chat/chatroom/close?chatRoomId=${chatRoomId}`, {});
45+
return data;
46+
};

src/components/ChatConnectLoadingSheet.tsx

Lines changed: 81 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,80 @@ import logo from '@/assets/icons/logo.svg';
22
import sad from '@/assets/icons/sad-icon.svg';
33
import MusicAnimation from './MusicAnimation';
44
import Button from './Button';
5+
import { useSheetStore } from '@/store/sheetStore';
6+
import { useEffect, useState } from 'react';
7+
import dayjs from 'dayjs';
8+
import duration from 'dayjs/plugin/duration';
9+
import { cancelChatRequest } from '@/apis/chat';
10+
import { useNavigate } from 'react-router';
11+
dayjs.extend(duration);
512

613
export default function ChatConnectLoadingSheet() {
14+
const navigate = useNavigate();
715
//zustand로 관리
816
//거절, 취소하거나 홈으로 가기 누르면 false로
9-
const isConnecting = false;
17+
const { currentRecord, closeAllSheets, closeSheet } = useSheetStore();
1018

11-
const isFail = false;
12-
const isReceiver = true;
13-
if (!isConnecting) return null;
14-
if (isFail) {
19+
const [timeLeft, setTimeLeft] = useState(60);
20+
21+
const [connetFail, setConnectFail] = useState(false);
22+
23+
const handleClickToHome = () => {
24+
closeAllSheets();
25+
navigate('/home');
26+
};
27+
28+
useEffect(() => {
29+
const endTime = new Date().getTime() + 60 * 1000; // 현재 시간 + 60초
30+
31+
const interval = setInterval(() => {
32+
const diff = Math.max(0, Math.ceil((endTime - new Date().getTime()) / 1000)); // 남은 초 계산
33+
34+
setTimeLeft(diff);
35+
36+
if (diff <= 0) {
37+
clearInterval(interval);
38+
setTimeLeft(0); // 0초로 고정
39+
}
40+
}, 1000);
41+
42+
return () => clearInterval(interval); //interval 정리
43+
}, []);
44+
45+
useEffect(() => {
46+
if (timeLeft === 0) {
47+
setConnectFail(true);
48+
}
49+
}, [timeLeft]);
50+
51+
const formattedTime = dayjs.duration(timeLeft, 'seconds').format('mm:ss');
52+
53+
//채팅 요청 취소
54+
const cancel = async () => {
55+
console.log(currentRecord);
56+
if (!currentRecord) {
57+
console.log('record가 존재하지 않습니다');
58+
return;
59+
}
60+
try {
61+
await cancelChatRequest(currentRecord.recordId);
62+
console.log('채팅 취소');
63+
64+
closeSheet('isChatLoadingSheetOpen');
65+
} catch (error) {
66+
console.log(error);
67+
}
68+
};
69+
70+
const isReceiver = false;
71+
72+
if (connetFail) {
1573
return (
1674
<div className="fixed inset-0 flex justify-center items-center z-50">
1775
<div className="relative w-full max-w-[600px] h-screen px-3 bg-background flex flex-col justify-center items-center">
1876
<img src={logo} className="w-[186px] h-[40px] mb-5" alt="로고" />
1977
<p className="h4-b text-center text-gray-50 mb-2">
20-
<span className="text-primary-normal">하입뽀이일곱자</span>
78+
<span className="text-primary-normal">{currentRecord?.nickName}</span>
2179
<span>님과 연결되지 않았어요</span>
2280
</p>
2381

@@ -27,7 +85,9 @@ export default function ChatConnectLoadingSheet() {
2785
<img src={sad} alt="연결실패" />
2886

2987
<div className="absolute bottom-10 flex gap-[6px] px-3 w-full">
30-
<Button variant="primary">홈으로 가기</Button>
88+
<Button onClick={handleClickToHome} variant="primary">
89+
홈으로 가기
90+
</Button>
3191
</div>
3292
</div>
3393
</div>
@@ -38,9 +98,15 @@ export default function ChatConnectLoadingSheet() {
3898
<div className="relative w-full max-w-[600px] h-screen px-3 bg-background flex flex-col justify-center items-center">
3999
<img src={logo} className="w-[186px] h-[40px] mb-5" alt="로고" />
40100
<p className="h4-b text-center text-gray-50 mb-2">
41-
<span className="text-primary-normal">하입뽀이일곱자</span>
42101
{/* 요청하는 사람일 결우 */}
43-
{!isReceiver && <span>님에게 대화 요청 중...</span>}
102+
{!isReceiver && (
103+
<span>
104+
<span className="text-primary-normal">{currentRecord?.nickName}</span>님에게 대화 요청
105+
중...
106+
</span>
107+
)}
108+
{/* 받는 사람일 경우 */}
109+
{isReceiver && <span className="text-primary-normal">보내는 사람</span>}
44110
</p>
45111
{/* 요청하는 사람일 결우 */}
46112
{!isReceiver && (
@@ -51,15 +117,18 @@ export default function ChatConnectLoadingSheet() {
51117
{/* 받는 사람일 경우 */}
52118
{isReceiver && (
53119
<p className="caption-r text-center text-gray-60 mb-5">
54-
<span>하입뽀이일곱자</span> 님이 대화를 요청했어요 <br /> 함께 이야기해 볼까요?
120+
<span>보내는 사람</span> 님이 대화를 요청했어요 <br /> 함께 이야기해 볼까요?
55121
</p>
56122
)}
57123

58124
<MusicAnimation />
59-
<p className="body-large-b text-primary-normal mt-[120px] mb-2">03:00</p>
125+
<p className="body-large-b text-primary-normal mt-[120px] mb-2">{formattedTime}</p>
60126
{/* 요청하는 사람일 결우 */}
61127
{!isReceiver && (
62-
<button className="caption-r text-gray-50 cursor-pointer border-b border-gray-50">
128+
<button
129+
onClick={cancel}
130+
className="caption-r text-gray-50 cursor-pointer border-b border-gray-50"
131+
>
63132
취소하기
64133
</button>
65134
)}

src/components/modalSheet/CardDetailModal.tsx

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ function CardDetailModal({
3838
handleDelete,
3939
handleEdit,
4040
}: CardDetailModalProps) {
41-
const { isCardSheetOpen } = useSheetStore();
41+
const { isCardSheetOpen, setCurrentRecord } = useSheetStore();
4242
const { setVideoId, players, setIsPlaying } = useYouTubeStore();
4343
const isPlaying = players['3']?.isPlaying || false;
4444

@@ -49,6 +49,9 @@ function CardDetailModal({
4949

5050
useEffect(() => {
5151
if (!data?.data?.spotifyMusic) return; // 데이터가 없으면 실행하지 않음
52+
console.log(data?.data);
53+
54+
setCurrentRecord(data.data);
5255

5356
const artistName = data.data.spotifyMusic.artist;
5457
const songTitle = data.data.spotifyMusic.title;
@@ -58,6 +61,10 @@ function CardDetailModal({
5861
setVideoId('3', id);
5962
};
6063
getVideoId();
64+
65+
return () => {
66+
setCurrentRecord(null);
67+
};
6168
}, [data]);
6269

6370
//sheet open 시 스크롤 제거
@@ -122,6 +129,7 @@ function CardDetailModal({
122129
</div>
123130
<div className="flex gap-10">
124131
<ChatActionButtons
132+
recordId={recordId}
125133
isChatting={isChatting}
126134
isPlaying={isPlaying}
127135
isOwnPost={!data?.data?.disable}

src/components/modalSheet/ChatActionButtons.tsx

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import playIcon from '@assets/icons/play/play-icon-gray.svg';
66
import pauseIcon from '@assets/icons/pause-icon-gray.svg';
77
import { useNavigate } from 'react-router';
88
import { useSheetStore } from '@/store/sheetStore';
9+
import { requestChat } from '@/apis/chat';
910

1011
interface ChatActionButtonsProps {
12+
recordId: number;
1113
isChatting: boolean;
1214
isPlaying: boolean;
1315
isOwnPost: boolean; // 본인 글 여부(임시)
@@ -17,6 +19,7 @@ interface ChatActionButtonsProps {
1719

1820
// 카드상세 모달에서 채팅중인지에 따라서 버튼 속성 결정
1921
function ChatActionButtons({
22+
recordId,
2023
isChatting,
2124
isPlaying,
2225
isOwnPost,
@@ -25,17 +28,37 @@ function ChatActionButtons({
2528
}: ChatActionButtonsProps) {
2629
const navigate = useNavigate();
2730

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

3033
const handleGoToUserPage = () => {
31-
closeAllSheets(); // 모든 시트를 닫아야할지 카드모달시트만 닫으면 될지 고민중
34+
// closeAllSheets(); // 모든 시트를 닫아야할지 카드모달시트만 닫으면 될지 고민중
3235
navigate(`/user/${authorId}`); // 유저 페이지로 이동
3336
};
3437

38+
//채팅 요청
39+
const request = async () => {
40+
if (!recordId) {
41+
console.log('recordId가 존재하지 않습니다.');
42+
return;
43+
}
44+
try {
45+
const data = await requestChat(recordId);
46+
console.log(data);
47+
if (data.code === 200) {
48+
console.log('채팅 요청 성공');
49+
50+
openSheet('isChatLoadingSheetOpen');
51+
} else {
52+
}
53+
} catch (error) {
54+
console.log(error);
55+
}
56+
};
57+
3558
// 채팅 유무에 따라서 props다르게
3659
const chatButtonProps = isChatting
3760
? { icon: headsetIcon, label: '대화 중...' }
38-
: { icon: commentIcon, label: '대화하기' };
61+
: { icon: commentIcon, label: '대화하기', onClick: request };
3962

4063
// 노래 재생 유무에 따라서 다르게
4164
const playButtonProps = isPlaying

src/layouts/Layout.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ function Layout() {
2626
};
2727

2828
const headerConfig: { [key: string]: React.ReactNode } = {
29-
'/home': <Header showPostButton />,
30-
'/chat': <Header showPostButton />,
31-
'/mypage': <Header showMoreOptions showPostButton />,
29+
'/home': <Header />,
30+
'/chat': <Header />,
31+
'/mypage': <Header showMoreOptions />,
3232
'/post': <HeaderWithBack text="글 등록" />,
3333
'/signup': <HeaderWithBack text="회원가입" />,
3434
'/mypage/blocklist': <HeaderWithBack text="차단 목록" />,

src/layouts/ModalSheetLayout.tsx

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ function ModalSheetLayout({
3333
const modalVariants = {
3434
hidden: { opacity: 0, y: -200 }, // 모달이 화면 밖에 위치하도록
3535
visible: { opacity: 1, y: 0 }, // 화면 안으로 날아오는 효과
36+
exit: { opacity: 0, y: -200 },
3637
};
3738

3839
useEffect(() => {
@@ -51,14 +52,15 @@ function ModalSheetLayout({
5152
}, []);
5253

5354
return (
54-
<div className="fixed inset-0 z-50 flex items-center justify-center">
55-
<motion.div
56-
className="max-w-[600px] w-full h-screen flex flex-col bg-white rounded-[8px] card-shadow border border-gray-5 overflow-y-auto scroll"
57-
initial="hidden"
58-
animate="visible"
59-
variants={modalVariants}
60-
transition={{ duration: 0.3 }}
61-
>
55+
<motion.div
56+
className="fixed inset-0 z-50 flex items-center justify-center"
57+
initial="hidden"
58+
animate="visible"
59+
exit="exit"
60+
variants={modalVariants}
61+
transition={{ duration: 0.3 }}
62+
>
63+
<div className="max-w-[600px] w-full h-screen flex flex-col bg-white rounded-[8px] card-shadow border border-gray-5 overflow-y-auto scroll">
6264
{/* 헤더 */}
6365
<div className="sticky top-0 flex min-h-[60px] h-[60px] items-center px-4 justify-between bg-white ">
6466
<button
@@ -77,8 +79,8 @@ function ModalSheetLayout({
7779
)}
7880
</div>
7981
{children}
80-
</motion.div>
81-
</div>
82+
</div>
83+
</motion.div>
8284
);
8385
}
8486

src/pages/home/Home.tsx

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,7 @@ function Home() {
156156
)}
157157
</div>
158158
{selectedRecordId !== null && (
159-
<CardDetailModal recordId={selectedRecordId} isChatting={true} />
160-
)}
161-
{selectedRecordId !== null && (
162-
<CardDetailModal recordId={selectedRecordId} isChatting={true} />
159+
<CardDetailModal recordId={selectedRecordId} isChatting={false} />
163160
)}
164161
{isLoading && <Loading />}
165162
{isMusicSheetOpen && <MusicSearchSheet />}

0 commit comments

Comments
 (0)