Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
…tep3/Team9_FE into feat/#108-api
  • Loading branch information
seung365 committed Nov 14, 2024
2 parents 2942b28 + c8fe67a commit f9d1b30
Show file tree
Hide file tree
Showing 17 changed files with 699 additions and 165 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@types/node": "^22.7.3",
"@types/sockjs-client": "^1.5.4",
"axios": "^1.7.7",
"date-fns": "^4.1.0",
"eslint-plugin-import": "^2.31.0",
"framer-motion": "^11.9.0",
"react": "^18.3.1",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

160 changes: 110 additions & 50 deletions src/apis/chats/index.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,136 @@
import { Client, Stomp } from '@stomp/stompjs';
import SockJS from 'sockjs-client';
import { CompatClient } from '@stomp/stompjs';
// import { Client, CompatClient, Stomp } from '@stomp/stompjs';
// import SockJS from 'sockjs-client';

// import type { ChatMessage } from './types';

export const BASE_URL = import.meta.env.VITE_APP_BASE_URL_CHAT;

export type ChatMessage = {
sender: { email: string };
content: string;
imageUrl?: string;
};
// let stompClient: Client | null = null;

// const accessToken = localStorage.getItem('accessToken');
// /**
// * WebSocket과 서버와의 연결 설정 함수
// * @param onMessageReceived - 수신된 메시지를 처리할 콜백 함수
// * @param onError - 에러 시 호출될 콜백 함수
// */
// export function connectWebSocket(
// chatRoomId: number,
// onMessageReceived?: (message: ChatMessage) => void,
// onError?: (error: string) => void,
// ): void {
// // WebSocket 연결, STOMP 클라이언트 설정
// const socket = new SockJS(`${BASE_URL}/ws`);
// stompClient = Stomp.over(() => socket);

let stompClient: Client | null = null;
// // 연결이 열렸을 때 호출될 콜백 함수
// stompClient.onConnect = () => {
// // SUBSCRIBE
// stompClient?.subscribe(`/v1/sub/chat/rooms/${chatRoomId}`, (message) => {
// console.log('received messages: ', message);

/**
* WebSocket과 서버와의 연결 설정 함수
* @param onMessageReceived - 수신된 메시지를 처리할 콜백 함수
* @param onError - 에러 시 호출될 콜백 함수
*/
export function connectWebSocket(
chatRoomId: number,
onMessageReceived?: (message: ChatMessage) => void,
onError?: (error: string) => void,
): void {
// WebSocket 연결, STOMP 클라이언트 설정
const socket = new SockJS(`${BASE_URL}/ws`);
stompClient = Stomp.over(socket);
// // const parsedMessage: ChatMessage = JSON.parse(message.body);

// 연결이 열렸을 때 호출될 콜백 함수
stompClient.onConnect = (frame) => {
console.log('Connected: ' + frame);
// // if (onMessageReceived) {
// // onMessageReceived(parsedMessage);
// // }
// });
// };

// 토픽 구독
stompClient?.subscribe(`/sub/chat/rooms/${chatRoomId}`, (message) => {
const parsedMessage: ChatMessage = JSON.parse(message.body);

if (onMessageReceived) {
onMessageReceived(parsedMessage);
}
});
// // 에러 났을 때 호출될 콜백 함수
// stompClient.onStompError = (errorFrame) => {
// console.error('Broker reported error: ' + errorFrame.headers['message']);

console.log('WebSocket 연결 성공');
};
// if (onError) {
// onError(errorFrame.headers['message']);
// }
// };

// 에러 났을 때 호출될 콜백 함수
stompClient.onStompError = (errorFrame) => {
console.error('Broker reported error: ' + errorFrame.headers['message']);
// // CONNECT
// stompClient.activate();
// }

if (onError) {
onError(errorFrame.headers['message']);
}
// 메시지(TEXT) 전송 함수
export function sendMessage(
stompClient: CompatClient,
chatRoomId: number,
email: string,
content: string,
): void {
const message = {
sender: email,
content,
messageType: 'TEXT',
};

stompClient.activate();
}

// 메시지 전송 함수
export function sendMessage(chatRoomId: number, message: ChatMessage): void {
if (stompClient && stompClient.connected) {
// SEND
stompClient.publish({
destination: `/pub/chat/${chatRoomId} `,
destination: `/v1/pub/chat/${chatRoomId}`,
body: JSON.stringify(message),
// headers: { receipt: 'message-12345' },
});
} else {
throw new Error('STOMP 클라이언트를 먼저 연결해주세요');
console.log('STOMP 클라이언트를 먼저 연결해주세요');
throw new Error('연결이 끊겼습니다. 새로고침 해주세요.');
}
}

// 파일 전송 함수
export async function sendFile(
stompClient: CompatClient,
chatRoomId: number,
email: string,
file: File,
): Promise<void> {
// File을 바이너리 데이터로 변환
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsArrayBuffer(file);

reader.onload = () => {
if (stompClient && stompClient.connected && reader.result) {
// const fileBuffer = reader.result as ArrayBuffer;
const fileBytes = new Uint8Array(reader.result as ArrayBuffer);
const fileBase64 = btoa(
fileBytes.reduce((data, byte) => data + String.fromCharCode(byte), ''),
);

const message = {
chatRoomId,
userEmail: email,
// fileBytes: fileBuffer,
// fileBytes,
fileBase64: fileBase64,
};

try {
stompClient.publish({
destination: `/v1/pub/chat/${chatRoomId}/file`,
body: JSON.stringify(message),
});
resolve();
} catch (error) {
console.error('Failed to send file:', error);
reject(error);
}
} else {
console.log('STOMP 클라이언트를 먼저 연결해주세요');
reject('연결이 끊겼습니다. 새로고침 해주세요.');
}
};

reader.onerror = (error) => {
console.error('File reading error:', error);
reject(error);
};
});
}

// WebSocket 연결 해제 함수
export function disconnectWebSocket(): void {
export function disconnectWebSocket(stompClient: CompatClient) {
if (stompClient) {
// DISCONNECT
stompClient.deactivate();
stompClient = null;
// stompClient.disconnect();
}
}
40 changes: 40 additions & 0 deletions src/apis/chats/useGetChatRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { useSuspenseQuery } from '@tanstack/react-query';
import { isAxiosError } from 'axios';

import type { ChatRoom } from '@/types/chats';
import fetchInstance from '../fetchInstance';
import QUERY_KEYS from '../queryKeys';
import { BASE_URL } from './index';

type GetChatRoomProps = {
chatRoomId: number;
};

type GetChatRoomData = ChatRoom;

async function getChatRoom({ chatRoomId }: GetChatRoomProps): Promise<GetChatRoomData> {
try {
const response = await fetchInstance(BASE_URL).get(`/v1/chat/rooms/${chatRoomId}`);

return response.data;
} catch (error) {
if (isAxiosError(error)) {
if (error.response) {
throw new Error(error.response.data.message || '채팅방 정보 가져오기 실패');
} else {
throw new Error('네트워크 오류 또는 서버에 연결할 수 없습니다.');
}
} else {
throw new Error('알 수 없는 오류입니다.');
}
}
}

const useGetChatRoom = (chatRoomId: number) => {
return useSuspenseQuery<GetChatRoomData, Error>({
queryKey: [QUERY_KEYS.CHAT_ROOM, chatRoomId],
queryFn: () => getChatRoom({ chatRoomId }),
});
};

export default useGetChatRoom;
44 changes: 44 additions & 0 deletions src/apis/chats/usePostChatRoom.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { useMutation } from '@tanstack/react-query';
import { isAxiosError } from 'axios';

import type { ChatRoom } from '@/types/chats';
import { getQueryParams } from '@/utils/queryParams';
import fetchInstance from '../fetchInstance';
import { BASE_URL } from './index';

type PostChatRoomProps = {
userEmail1: string;
userEmail2: string;
};

type PostChatRoomData = ChatRoom;

async function postChatRoom({
userEmail1,
userEmail2,
}: PostChatRoomProps): Promise<PostChatRoomData> {
try {
const queryParams = getQueryParams({ userEmail1: userEmail1, userEmail2: userEmail2 });
const response = await fetchInstance(BASE_URL).post(`/v1/chat/rooms?${queryParams}`);

return response.data;
} catch (error) {
if (isAxiosError(error)) {
if (error.response) {
throw new Error(error.response.data.message || '채팅방 생성 실패');
} else {
throw new Error('네트워크 오류 또는 서버에 연결할 수 없습니다.');
}
} else {
throw new Error('알 수 없는 오류입니다.');
}
}
}

const usePostChatRoom = () => {
return useMutation<PostChatRoomData, Error, PostChatRoomProps>({
mutationFn: (props: PostChatRoomProps) => postChatRoom(props),
});
};

export default usePostChatRoom;
1 change: 1 addition & 0 deletions src/apis/queryKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ const QUERY_KEYS = {
USER_INFO: 'userInfo',
ARTIST_LIST: 'artistList',
PRODUCT_LIST: 'productList',
CHAT_ROOM: 'chatRoom',
};

export default QUERY_KEYS;
3 changes: 3 additions & 0 deletions src/assets/icons/image.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
43 changes: 42 additions & 1 deletion src/pages/ProductDetails/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,46 @@
import { useNavigate } from 'react-router-dom';

import usePostChatRoom from '@/apis/chats/usePostChatRoom';
import CTA, { CTAContainer } from '@/components/common/CTA';
import useUserStore from '@/store/useUserStore';
import { RouterPath } from '@/routes/path';

const USER_EMAIL_1 = '[email protected]';
const USER_EMAIL_2 = '[email protected]';

const ProductDetails = () => {
return <>ProductDetails</>;
const { email } = useUserStore();
const userEmail1 = email || USER_EMAIL_1; // 사용자 본인 이메일
const userEmail2 = USER_EMAIL_2; // 상대방 이메일
const navigate = useNavigate();

const { mutate: postChatRoom } = usePostChatRoom();

const handleClickChat = () => {
postChatRoom(
{
userEmail1,
userEmail2,
},
{
onSuccess: (data) => {
const chatRoomId = data.id;
navigate(`${RouterPath.chats}/${chatRoomId}`);
},
onError: (error) => {
alert(error);
},
},
);
};

return (
<>
<CTAContainer>
<CTA label="채팅하기" onClick={handleClickChat}></CTA>
</CTAContainer>
</>
);
};

export default ProductDetails;
Loading

0 comments on commit f9d1b30

Please sign in to comment.