Skip to content

Commit d3807ed

Browse files
authored
Merge pull request #183 from prgrms-web-devcourse-final-project/feat/159-chat-request-and-cancel
[feat] 채팅방 기능 구현, searchYoutube 변경
2 parents ae0c23e + cb05ab9 commit d3807ed

File tree

17 files changed

+158
-117
lines changed

17 files changed

+158
-117
lines changed

src/App.tsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,13 @@ import KaKaoRedirection from '@/components/KaKaoRedirection';
3030

3131
function App() {
3232
const location = useLocation();
33-
// 실제 로그인 여부를 체크하는 함수 (임시로 false, 실제 인증 로직 적용 필요)
34-
// const isAuthenticated = true;
33+
3534
const { isAuthenticated } = useAuthStore();
3635
const spotifyAuth = useSpotifyAuth();
3736
const { isChatLoadingSheetOpen } = useSheetStore();
3837
// soundlink 로그인한 경우에만 spotify 로그인 후 토큰 가져오기
3938
useEffect(() => {
4039
if (isAuthenticated) {
41-
// isAuthenticated가 true일 때만 필요한 동작 실행
4240
console.log('Spotify Auth Initialized:', spotifyAuth);
4341
}
4442
}, [isAuthenticated]);
@@ -78,7 +76,7 @@ function App() {
7876
<Route path="/chat" element={<Chat />} />
7977
<Route path="/post" element={<Post />} />
8078
<Route path="/post/:postId/edit" element={<Post />} />
81-
<Route path="/chatroom" element={<ChatRoom />} />
79+
<Route path="/chatroom/:chatRoomId" element={<ChatRoom />} />
8280
<Route path="/mypage" element={<UserProfile isMyPage={true} />} />
8381
<Route path="/mypage/edit" element={<EditProfile />} />
8482
<Route path="/mypage/blocklist" element={<BlockList />} />

src/components/ChatConnectLoadingSheet.tsx

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ dayjs.extend(duration);
1313
export default function ChatConnectLoadingSheet() {
1414
const navigate = useNavigate();
1515

16-
const { currentRecord, closeAllSheets, closeSheet } = useSheetStore();
16+
const { currentRecord, setCurrentRecord, closeAllSheets, closeSheet } = useSheetStore();
1717

1818
const [timeLeft, setTimeLeft] = useState(60);
1919

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

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

53+
const isReceiver = true;
54+
5355
//채팅 요청 취소(요청 보낸 사람)
5456
const cancel = async () => {
55-
if (!currentRecord) {
57+
if (!currentRecord?.recordId) {
5658
console.log('record가 존재하지 않습니다');
5759
return;
5860
}
@@ -68,29 +70,36 @@ export default function ChatConnectLoadingSheet() {
6870
};
6971

7072
//채팅방 생성 (요청 받는 사람 입장에서 생성?)
73+
//sse로 받은 recordId로 채팅방 생성
74+
//sse로 받은 상대 정보로 '보내는 사람' 바꾸기
75+
//생성 시 currentRecord 에 id 저장
7176
const createChat = async () => {
7277
try {
73-
const data = await createChatroom(17);
78+
const data = await createChatroom(10);
7479
console.log(data);
80+
const chatRoomId = data.data.chatRoomId;
7581

7682
if (data.code === 200) {
77-
navigate('/chatroom');
83+
//임시
84+
setCurrentRecord({ recordId: 10 });
85+
//
86+
navigate(`/chatroom/10`);
7887
closeAllSheets();
7988
}
80-
//500 이면 이미 있음 => 기존 채팅방으로
89+
//409 이면 이미 채팅방 있음 => 기존 채팅방으로
8190
} catch (error) {
8291
console.log(error);
8392
}
8493
};
8594

8695
//채팅 요정 거절
96+
//거절 하면 거절한 사람은 바로 시트 닫기.
97+
//요청 거절당한 사람은 connectFail = true
8798
const refuseRequest = () => {
8899
closeSheet('isChatLoadingSheetOpen');
89100
};
90101

91-
//채팅 신청한 사람이 상대가 수락했다는 sse 받으면 closeAllSheet, chatroom으로 이동
92-
93-
const isReceiver = true;
102+
//채팅 신청한 사람은 상대가 수락했다는 sse 받으면 closeAllSheet, chatroom으로 이동
94103

95104
if (connetFail) {
96105
return (

src/components/MusicCard.tsx

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,19 @@ import Button from '@/components/Button';
22
import defaultImage from '@assets/images/default.png';
33
import play from '@assets/icons/play/play-circle.svg';
44
import pause from '@assets/icons/pause-circle.svg';
5-
import { useEffect } from 'react';
6-
import { useSearchYoutubeVideo } from '@/apis/youtube';
5+
import { useEffect, useState } from 'react';
76
import { useSheetStore } from '@/store/sheetStore';
87
import MusicSearchSheet from './modalSheet/MusicSearchSheet';
98
import { useYouTubeStore } from '@/store/youtubeStore';
109
import { useLocation, useParams } from 'react-router';
1110
import { twMerge } from 'tailwind-merge';
11+
import { getSpotifyVideoId } from '@/apis/emotionRecord';
1212

1313
interface MusicCardProps {
1414
image?: string | null; // 음악 이미지
1515
title?: string; // 음악 제목
1616
artist?: string; // 음악 설명
17+
spotifyId?: string;
1718
buttonContent?: string; // 버튼 텍스트
1819
isMusicSelect?: boolean; // 음악 선택 상태
1920
buttonType?: 'primary' | 'secondary'; // 버튼 타입
@@ -24,6 +25,7 @@ export default function MusicCard({
2425
image,
2526
title,
2627
artist,
28+
spotifyId,
2729
isMusicSelect = false,
2830
buttonContent = '등록',
2931
buttonType = 'primary',
@@ -41,8 +43,25 @@ export default function MusicCard({
4143
const { isMusicSheetOpen, openSheet } = useSheetStore();
4244

4345
//query
44-
const query = title && !isUserEditPage ? `${artist} - ${title} lyrics` : null;
45-
const { data: searchedVideoId } = useSearchYoutubeVideo(query);
46+
// const query = title && !isUserEditPage ? `${artist} - ${title} lyrics` : null;
47+
// const { data: searchedVideoId } = useSearchYoutubeVideo(query);
48+
const [currentVideoId, setCurrentVideoId] = useState<string | null>(null);
49+
50+
useEffect(() => {
51+
const getVideoId = async () => {
52+
if (!spotifyId) return;
53+
54+
try {
55+
const currentMusicId = spotifyId;
56+
const res = await getSpotifyVideoId(currentMusicId);
57+
const savedVideoId = res.data.videoId;
58+
setCurrentVideoId(savedVideoId);
59+
} catch (error) {
60+
console.log(error);
61+
}
62+
};
63+
getVideoId();
64+
}, []);
4665

4766
const { setVideoId, players, setIsPlaying } = useYouTubeStore();
4867

@@ -56,10 +75,10 @@ export default function MusicCard({
5675

5776
// videoId가 변경될 때마다 zustand store의 videoId를 업데이트
5877
useEffect(() => {
59-
if (shouldFetchYouTube && searchedVideoId && isUserPage) {
60-
setVideoId('1', searchedVideoId); // YouTube store의 videoId를 업데이트
78+
if (shouldFetchYouTube && currentVideoId && isUserPage && !isUserEditPage) {
79+
setVideoId('1', currentVideoId); // YouTube store의 videoId를 업데이트
6180
}
62-
}, [searchedVideoId]);
81+
}, [currentVideoId]);
6382

6483
useEffect(() => {
6584
return () => {

src/components/modalSheet/CardDetailModal.tsx

Lines changed: 40 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -6,38 +6,18 @@ import { useSheetStore } from '@/store/sheetStore';
66
import { useYouTubeStore } from '@/store/youtubeStore';
77
import defaultImage from '@assets/images/default.png';
88
import { useQuery } from '@tanstack/react-query';
9-
import { getEmotionRecordById } from '@/apis/emotionRecord';
9+
import { getEmotionRecordById, getSpotifyVideoId } from '@/apis/emotionRecord';
1010
import { formatDate } from '@/utils/formatDate';
11-
import { useEffect } from 'react';
12-
import { searchYoutubeVideo } from '@/apis/youtube';
11+
import { useEffect, useState } from 'react';
1312

1413
interface CardDetailModalProps {
15-
// emotion: string; // 감정
16-
// albumImage: string; // 앨범이미지
17-
// songTitle: string; // 노래 제목
18-
// artistName: string; // 가수
19-
// date: string; // 날짜
20-
// authorName: string; // 글작성자
2114
isChatting: boolean; // 현재 채팅중인지 (임시)
22-
// isOwnPost: boolean; // 본인 글 여부(임시)
2315
recordId: number; // 감정기록 id
2416
handleDelete?: () => void; // 삭제 함수
2517
handleEdit?: () => void; // 수정 함수
2618
}
2719

28-
function CardDetailModal({
29-
// emotion,
30-
// albumImage,
31-
// songTitle,
32-
// artistName,
33-
// date,
34-
// authorName,
35-
isChatting,
36-
// isOwnPost,
37-
recordId,
38-
handleDelete,
39-
handleEdit,
40-
}: CardDetailModalProps) {
20+
function CardDetailModal({ isChatting, recordId, handleDelete, handleEdit }: CardDetailModalProps) {
4121
const { isCardSheetOpen, setCurrentRecord } = useSheetStore();
4222
const { setVideoId, players, setIsPlaying } = useYouTubeStore();
4323
const isPlaying = players['3']?.isPlaying || false;
@@ -47,38 +27,54 @@ function CardDetailModal({
4727
queryFn: () => getEmotionRecordById(recordId),
4828
});
4929

30+
const [currentVideoId, setCurrentVideoId] = useState<string | null>(null);
31+
32+
useEffect(() => {
33+
const getVideoId = async () => {
34+
if (!data?.data?.spotifyMusic) return; // 데이터가 없으면 실행하지 않음
35+
36+
try {
37+
const currentMusicId = data.data.spotifyMusic.spotifyId;
38+
const res = await getSpotifyVideoId(currentMusicId);
39+
const savedVideoId = res.data.videoId;
40+
setCurrentVideoId(savedVideoId);
41+
} catch (error) {
42+
console.log(error);
43+
}
44+
};
45+
getVideoId();
46+
}, []);
47+
48+
// videoId가 변경될 때마다 zustand store의 videoId를 업데이트
49+
useEffect(() => {
50+
if (currentVideoId) {
51+
setVideoId('2', currentVideoId); // YouTube store의 videoId를 업데이트
52+
}
53+
}, [currentVideoId]);
54+
5055
useEffect(() => {
5156
if (!data?.data?.spotifyMusic) return; // 데이터가 없으면 실행하지 않음
5257
console.log(data?.data);
5358

5459
setCurrentRecord(data.data);
5560

56-
const artistName = data.data.spotifyMusic.artist;
57-
const songTitle = data.data.spotifyMusic.title;
58-
59-
const getVideoId = async () => {
60-
const id = await searchYoutubeVideo(`${artistName} - ${songTitle} lyrics`);
61-
setVideoId('3', id);
62-
};
63-
getVideoId();
64-
6561
return () => {
6662
setCurrentRecord(null);
6763
};
6864
}, [data]);
6965

70-
//sheet open 시 스크롤 제거
71-
useEffect(() => {
72-
if (isCardSheetOpen) {
73-
document.body.style.overflow = 'hidden'; // 스크롤 막기
74-
} else {
75-
document.body.style.overflow = 'auto'; // 스크롤 복원
76-
}
77-
78-
return () => {
79-
document.body.style.overflow = 'auto';
80-
};
81-
}, [isCardSheetOpen]);
66+
// //sheet open 시 스크롤 제거
67+
// useEffect(() => {
68+
// if (isCardSheetOpen) {
69+
// document.body.style.overflow = 'hidden'; // 스크롤 막기
70+
// } else {
71+
// document.body.style.overflow = 'auto'; // 스크롤 복원
72+
// }
73+
74+
// return () => {
75+
// document.body.style.overflow = 'auto';
76+
// };
77+
// }, [isCardSheetOpen]);
8278

8379
if (!isCardSheetOpen) {
8480
return null;

src/components/modalSheet/ChatActionButtons.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import pauseIcon from '@assets/icons/pause-icon-gray.svg';
77
import { useNavigate } from 'react-router';
88
import { useSheetStore } from '@/store/sheetStore';
99
import { requestChat } from '@/apis/chat';
10+
import { useModalStore } from '@/store/modalStore';
1011

1112
interface ChatActionButtonsProps {
1213
recordId: number;
@@ -29,13 +30,15 @@ function ChatActionButtons({
2930
const navigate = useNavigate();
3031

3132
const { openSheet } = useSheetStore(); // 모달 시트
33+
const { openModal, closeModal } = useModalStore();
3234

3335
const handleGoToUserPage = () => {
3436
// closeAllSheets(); // 모든 시트를 닫아야할지 카드모달시트만 닫으면 될지 고민중
3537
navigate(`/user/${authorId}`); // 유저 페이지로 이동
3638
};
3739

3840
//채팅 요청
41+
//요청 보내면 sse로 recordId, 보낸 사람 정보 보내줘야 함
3942
const request = async () => {
4043
if (!recordId) {
4144
console.log('recordId가 존재하지 않습니다.');
@@ -46,9 +49,16 @@ function ChatActionButtons({
4649
console.log(data);
4750
if (data.code === 200) {
4851
console.log('채팅 요청 성공');
49-
52+
5053
openSheet('isChatLoadingSheetOpen');
5154
} else {
55+
console.log('채팅 요청 실패');
56+
openModal({
57+
title: '잠시 후 다시 시도해 주세요',
58+
onConfirm: () => {
59+
closeModal();
60+
},
61+
});
5262
}
5363
} catch (error) {
5464
console.log(error);

src/layouts/header/HeaderChat.tsx

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@ import exitIcon from '@assets/icons/exit-icon.svg';
33
import { twMerge } from 'tailwind-merge';
44
import HedaerLayout from '@/layouts/header/HedaerLayout';
55
import { useModalStore } from '@/store/modalStore';
6+
import { useNavigate, useParams } from 'react-router';
7+
import { closeChatroom } from '@/apis/chat';
68

79
interface HeaderChatProps {
810
showLogo?: boolean; // 로고 표시 여부
911
showNickname?: boolean; // 닉네임 표시 여부
1012
}
1113

1214
function HeaderChat({ showLogo = false, showNickname = false }: HeaderChatProps) {
15+
const navigate = useNavigate();
16+
const { chatRoomId } = useParams();
1317
// 로고와 닉네임이 모두 숨겨진 경우 `justify-end`, 아니면 `justify-between`
1418
const headerClass = showLogo || showNickname ? 'justify-between' : 'justify-end';
1519
//채팅 기록 데이터 zustand
@@ -62,9 +66,18 @@ function HeaderChat({ showLogo = false, showNickname = false }: HeaderChatProps)
6266

6367
openModal({
6468
title: '이 대화를 마무리할까요?',
65-
message: '채팅을 종료하면 다시 복구할 수 없습니다.',
69+
message: '채팅을 종료하면 다시 복구할 수 없습니다',
6670
onConfirm: async () => {
71+
if (!chatRoomId) {
72+
console.log('chatroomid가 없습니다.');
73+
return;
74+
}
6775
console.log('확인');
76+
console.log(chatRoomId);
77+
const data = await closeChatroom(Number(10));
78+
console.log(data);
79+
80+
navigate('/home');
6881
closeModal();
6982
},
7083
onCancel: () => {

src/pages/chat/Chat.tsx

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -14,32 +14,6 @@ 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-
// ];
42-
4317
if (!chatList.length) {
4418
return (
4519
<div className="flex items-center justify-center w-full">

0 commit comments

Comments
 (0)