Skip to content

Commit b9e0354

Browse files
committed
Merge branch 'dev' into feat/189-SSE-connect
2 parents ce66186 + 74d35e3 commit b9e0354

File tree

12 files changed

+363
-127
lines changed

12 files changed

+363
-127
lines changed

.github/workflows/autoBuild.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ jobs:
3535
echo "VITE_SPOTIFY_CLIENT_SECRET=${{ secrets.VITE_SPOTIFY_CLIENT_SECRET }}" >> .env
3636
echo "VITE_KAKAO_CLIENT_ID=${{ secrets.VITE_KAKAO_CLIENT_ID }}" >> .env
3737
echo "VITE_KAKAO_REDIRECT_URI=${{ secrets.VITE_KAKAO_REDIRECT_URI }}" >> .env
38+
echo "VITE_APP_YOUTUBE_API_KEY=${{ secrets.VITE_APP_YOUTUBE_API_KEY }}" >> .env
3839
3940
# Install dependencies
4041
npm install

src/apis/chat.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,12 @@ export const closeChatroom = async (chatRoomId: number) => {
3030
const { data } = await axiosInstance.post(`/chat/close?chatRoomId=${chatRoomId}`, {});
3131
return data;
3232
};
33-
//채팅방 기록 불러오기
33+
//채팅방 기록 불러오기(개발서버)
34+
export const loadChatHistoryDev = async (chatRoomId: number) => {
35+
const { data } = await axiosInstance.get(`/chat/history?chatRoomId=${chatRoomId}`);
36+
return data;
37+
};
38+
//채팅방 기록 불러오기(배포)
3439
export const loadChatHistory = async (chatRoomId: number) => {
3540
const { data } = await axiosChatInstance.get(`/chat/history/${chatRoomId}`);
3641
return data;
Lines changed: 179 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,179 @@
1+
import logo from '@/assets/icons/logo.svg';
2+
import sad from '@/assets/icons/sad-icon.svg';
3+
import MusicAnimation from './MusicAnimation';
4+
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, createChatroom } from '@/apis/chat';
10+
import { useNavigate } from 'react-router';
11+
dayjs.extend(duration);
12+
13+
export default function ChatConnectLoadingSheet() {
14+
const navigate = useNavigate();
15+
16+
const { currentRecord, closeAllSheets, closeSheet } = useSheetStore();
17+
18+
const [timeLeft, setTimeLeft] = useState(60);
19+
20+
const [connetFail, setConnectFail] = useState(false);
21+
22+
const handleClickToHome = () => {
23+
closeAllSheets();
24+
navigate('/home');
25+
};
26+
27+
//타이머 60초
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+
const isReceiver = true;
54+
55+
//채팅 요청 취소(요청 보낸 사람)
56+
const cancel = async () => {
57+
if (!currentRecord?.recordId) {
58+
console.log('record가 존재하지 않습니다');
59+
return;
60+
}
61+
try {
62+
console.log(currentRecord);
63+
await cancelChatRequest(currentRecord.recordId);
64+
console.log('채팅 취소');
65+
66+
closeSheet('isChatLoadingSheetOpen');
67+
} catch (error) {
68+
console.log(error);
69+
}
70+
};
71+
72+
//채팅방 생성 (요청 받는 사람 입장에서 생성?)
73+
//sse로 받은 recordId로 채팅방 생성
74+
//sse로 받은 상대 정보로 '보내는 사람' 바꾸기
75+
//sse로 받은 requsetNickname(tag가 아니라 닉네임)
76+
const createChat = async () => {
77+
try {
78+
//1 = recordId
79+
const data = await createChatroom(1, '루루');
80+
console.log(data);
81+
const chatRoomId = data.data.chatRoomId;
82+
83+
//200
84+
//409 이면 이미 채팅방 있음 => 기존 채팅방으로
85+
navigate(`/chatroom/${chatRoomId}`);
86+
closeAllSheets();
87+
} catch (error) {
88+
console.log(error);
89+
}
90+
};
91+
92+
//채팅 요정 거절
93+
//거절 하면 거절한 사람은 바로 시트 닫기.
94+
//요청 거절당한 사람은 connectFail = true
95+
const refuseRequest = () => {
96+
closeSheet('isChatLoadingSheetOpen');
97+
};
98+
99+
//채팅 신청한 사람은 상대가 수락했다는 sse 받으면 closeAllSheet, chatroom으로 이동
100+
//sse로 채팅방 번호 받기
101+
102+
if (connetFail) {
103+
return (
104+
<div className="fixed inset-0 flex justify-center items-center z-50">
105+
<div className="relative w-full max-w-[600px] h-screen px-3 bg-background flex flex-col justify-center items-center">
106+
<img src={logo} className="w-[186px] h-[40px] mb-5" alt="로고" />
107+
<p className="h4-b text-center text-gray-50 mb-2">
108+
<span className="text-primary-normal">{currentRecord?.nickName}</span>
109+
<span>님과 연결되지 않았어요</span>
110+
</p>
111+
112+
<p className="caption-r text-center text-gray-60 mb-5">
113+
괜찮아요! 새로운 감정을 공유할 <br /> 다른 사람을 찾아볼까요?
114+
</p>
115+
<img src={sad} alt="연결실패" />
116+
117+
<div className="absolute bottom-10 flex gap-[6px] px-3 w-full">
118+
<Button onClick={handleClickToHome} variant="primary">
119+
홈으로 가기
120+
</Button>
121+
</div>
122+
</div>
123+
</div>
124+
);
125+
}
126+
return (
127+
<div className="fixed inset-0 flex justify-center items-center z-50">
128+
<div className="relative w-full max-w-[600px] h-screen px-3 bg-background flex flex-col justify-center items-center">
129+
<img src={logo} className="w-[186px] h-[40px] mb-5" alt="로고" />
130+
<p className="h4-b text-center text-gray-50 mb-2">
131+
{/* 요청하는 사람일 결우 */}
132+
{!isReceiver && (
133+
<span>
134+
<span className="text-primary-normal">{currentRecord?.nickName}</span>님에게 대화 요청
135+
중...
136+
</span>
137+
)}
138+
{/* 받는 사람일 경우 */}
139+
{isReceiver && <span className="text-primary-normal">보내는 사람</span>}
140+
</p>
141+
{/* 요청하는 사람일 결우 */}
142+
{!isReceiver && (
143+
<p className="caption-r text-center text-gray-60 mb-5">
144+
곧 새로운 연결이 시작됩니다. <br /> 잠시만 기다려주세요!
145+
</p>
146+
)}
147+
{/* 받는 사람일 경우 */}
148+
{isReceiver && (
149+
<p className="caption-r text-center text-gray-60 mb-5">
150+
<span>보내는 사람</span> 님이 대화를 요청했어요 <br /> 함께 이야기해 볼까요?
151+
</p>
152+
)}
153+
154+
<MusicAnimation />
155+
<p className="body-large-b text-primary-normal mt-[120px] mb-2">{formattedTime}</p>
156+
{/* 요청하는 사람일 결우 */}
157+
{!isReceiver && (
158+
<button
159+
onClick={cancel}
160+
className="caption-r text-gray-50 cursor-pointer border-b border-gray-50"
161+
>
162+
취소하기
163+
</button>
164+
)}
165+
{/* 받는 사람일 경우 */}
166+
{isReceiver && (
167+
<div className="absolute bottom-10 flex gap-[6px] px-3 w-full">
168+
<Button onClick={createChat} variant="primary">
169+
수락하기
170+
</Button>
171+
<Button onClick={refuseRequest} variant="secondary">
172+
거절하기
173+
</Button>
174+
</div>
175+
)}
176+
</div>
177+
</div>
178+
);
179+
}

src/components/KaKaoRedirection.tsx

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,33 @@
1-
// import { getKakaoLogin } from '@/apis/auth';
1+
import { getKakaoLogin } from '@/apis/auth';
22
import Loading from '@/components/loading/Loading';
3-
// import { useAuthStore } from '@/store/authStore';
3+
import { useAuthStore } from '@/store/authStore';
44
import { useEffect } from 'react';
5-
// import { useNavigate } from 'react-router';
5+
import { useNavigate } from 'react-router';
66

77
function KaKaoRedirection() {
8-
// const navigate = useNavigate();
8+
const navigate = useNavigate();
99
const kakaoCode = new URL(window.location.href).searchParams.get('code');
10-
// const { setAccessToken } = useAuthStore();
10+
const { setAccessToken } = useAuthStore();
1111

12-
// const handleKaKaoLogin = async () => {
13-
// try {
14-
// console.log('kakaoCode', kakaoCode);
15-
// const { code, data } = await getKakaoLogin(kakaoCode as string);
16-
// if (code === 200) {
17-
// setAccessToken(data.accessToken);
18-
// navigate('/home');
19-
// } else {
20-
// throw new Error('로그인 에러');
21-
// }
22-
// } catch (error) {
23-
// console.error('로그인 에러가 발생했습니다.');
24-
// }
25-
// };
12+
const handleKaKaoLogin = async () => {
13+
try {
14+
console.log('kakaoCode', kakaoCode);
15+
const { code, data } = await getKakaoLogin(kakaoCode as string);
16+
if (code === 200) {
17+
setAccessToken(data.accessToken);
18+
navigate('/home');
19+
} else {
20+
throw new Error('로그인 에러');
21+
}
22+
} catch (error) {
23+
console.error('로그인 에러가 발생했습니다.');
24+
}
25+
};
2626

2727
useEffect(() => {
2828
if (kakaoCode) {
2929
console.log('🔴 kakaoCode', kakaoCode);
30-
// handleKaKaoLogin();
30+
handleKaKaoLogin();
3131
}
3232
}, []);
3333
return <Loading />;
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { InfiniteData, useMutation, useQueryClient } from '@tanstack/react-query';
2+
import { deleteEmotionRecord } from '@/apis/emotionRecord';
3+
4+
export const useDeleteEmotionRecord = (userId: string) => {
5+
const queryClient = useQueryClient(); // useMutation 사용
6+
7+
return useMutation({
8+
mutationFn: (recordId: number) => deleteEmotionRecord(recordId),
9+
onMutate: async (recordId) => {
10+
// 낙관적 업데이트 전에 사용자 목록 쿼리를 취소해 잠재적인 충돌 방지!
11+
await queryClient.cancelQueries({
12+
queryKey: ['emotionRecords', userId],
13+
});
14+
15+
// 캐시된 데이터(사용자 목록) 가져오기!
16+
const previousRecords = queryClient.getQueryData<InfiniteData<EmotionRecordPages>>([
17+
'emotionRecords',
18+
userId,
19+
]);
20+
21+
// 기존 데이터 확인 (없으면 오류 발생 방지)
22+
if (!previousRecords?.pages) {
23+
console.error('❌ 기존 데이터가 없습니다.');
24+
throw new Error('기존 데이터 없음');
25+
}
26+
27+
// 기존 데이터를 기반으로 낙관적 업데이트 수행
28+
queryClient.setQueryData(
29+
['emotionRecords', userId],
30+
(oldData: InfiniteData<EmotionRecordPages>) => {
31+
return {
32+
...oldData,
33+
pages: oldData.pages.map((page: EmotionRecordPages) => ({
34+
...page,
35+
data: {
36+
...page.data,
37+
records: page.data.records.filter((r: EmotionRecord) => r.recordId !== recordId),
38+
},
39+
})),
40+
};
41+
},
42+
);
43+
44+
// 각 콜백의 context로 전달할 데이터 반환!
45+
return { previousRecords };
46+
},
47+
onError: (_, __, context) => {
48+
if (context?.previousRecords) {
49+
queryClient.setQueryData(['emotionRecords', userId], context.previousRecords);
50+
}
51+
},
52+
onSettled: () => {
53+
queryClient.invalidateQueries({
54+
queryKey: ['emotionRecords', userId],
55+
});
56+
},
57+
});
58+
};

src/hooks/useMoreOptions.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,30 @@ export const useMoreOptions = () => {
3838
}
3939

4040
openModal({
41-
title: `${userData?.nickname}을 차단할까요?`,
41+
title: [
42+
{ text: `${userData?.nickname}`, className: 'text-primary-normal' },
43+
{ text: ' 님을 차단할까요?' },
44+
],
4245
message: '차단된 사용자는 더이상 피드에 나타나지 않습니다',
4346
onConfirm: async () => {
4447
if (param.userId) {
4548
try {
4649
const data = await addBlockList(param.userId);
47-
closeModal();
4850
console.log(data);
51+
//이미 차단 한 유저일 경우
52+
if (data.code === 400) {
53+
closeModal();
54+
openModal({
55+
title: `이미 차단한 유저입니다`,
56+
onConfirm: () => {
57+
closeModal();
58+
},
59+
});
60+
} else {
61+
//차단 후 홈으로 이동
62+
navigate('/home');
63+
closeModal();
64+
}
4965
} catch (error) {
5066
console.log(error);
5167
}

0 commit comments

Comments
 (0)