Skip to content

Commit

Permalink
Merge pull request YJU-OKURA#169 from Lainari/feat/class-board-api
Browse files Browse the repository at this point in the history
クラス参加とメンバーAPI接続、ライブチャット機能を追加
  • Loading branch information
Regulus0811 authored Jun 19, 2024
2 parents ea30c88 + b5a36dd commit db75e95
Show file tree
Hide file tree
Showing 31 changed files with 684 additions and 97 deletions.
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@
"dependencies": {
"@tanstack/react-query": "^5.17.10",
"@types/js-cookie": "^3.0.6",
"@types/event-source-polyfill": "^1.0.5",
"axios": "^1.6.5",
"event-source-polyfill": "^1.0.31",

"date-fns": "^3.6.0",
"ion-sdk-js": "^1.8.2",
"js-cookie": "^3.0.5",
Expand Down
3 changes: 3 additions & 0 deletions public/svgs/_class/copyButton.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
1 change: 1 addition & 0 deletions public/svgs/_class/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const icons = {
copy: '/svgs/_class/copyButton.svg',
favorite: '/svgs/_class/favorite.svg',
folder: '/svgs/_class/folder.svg',
noneFavorite: '/svgs/_class/noneFavorite.svg',
Expand Down
11 changes: 11 additions & 0 deletions public/svgs/_class/member/admin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions public/svgs/_class/member/assistant.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
6 changes: 6 additions & 0 deletions public/svgs/_class/member/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const icons = {
admin: '/svgs/_class/member/admin.svg',
assistant: '/svgs/_class/member/assistant.svg',
};

export default icons;
6 changes: 5 additions & 1 deletion src/api/_class/getClassesRole.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import req from '@/src/api/apiUtils';

const getClassesRole = async (uid: number, roleID: string) => {
const response = await req(`/cu/${uid}/classes?role=${roleID}`, 'get', 'gin');
const response = await req(
`/cu/${uid}/classes/by-role?role=${roleID}`,
'get',
'gin'
);

return response;
};
Expand Down
8 changes: 4 additions & 4 deletions src/api/_class/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import getClassInfo from './getClassInfo';
import getClasses from './getClasses';
import getClassesRole from './getClassesRole';
import getFavoriteClasses from './getFavoriteClasses';
// import patchClassRole from './patchClassRole';
// import patchToggleFavoriteClass from './patchToggleFavoriteClass';
import patchClassRole from './patchClassRole';
import patchToggleFavoriteClass from './patchToggleFavoriteClass';
import postCreateClass from './postCreateClass';

const classAPI = {
getClassInfo,
getClasses,
getClassesRole,
getFavoriteClasses,
// patchClassRole,
// patchToggleFavoriteClass,
patchClassRole,
patchToggleFavoriteClass,
postCreateClass,
};

Expand Down
5 changes: 5 additions & 0 deletions src/api/chatRoom/URLList.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const URLList = {
liveURL: 'http://43.203.66.25/api/gin/chat/stream/',
};

export default URLList;
30 changes: 30 additions & 0 deletions src/api/chatRoom/getLiveMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import URLList from './URLList';
import {NativeEventSource, EventSourcePolyfill} from 'event-source-polyfill';
import Cookies from 'js-cookie';

const getLiveMessages = (
scheduleId: number,
onMessage: (message: string) => void
) => {
const token = Cookies.get('access_token');
const url = `${URLList.liveURL}${scheduleId}`;

const EventSource = EventSourcePolyfill || NativeEventSource;
const eventSource = new EventSource(url, {
headers: {
Authorization: `Bearer ${token}`,
},
});

eventSource.onmessage = function (event) {
onMessage(event.data);
};

eventSource.onerror = function (event) {
console.error('EventSource failed:', event);
};

return eventSource;
};

export default getLiveMessages;
8 changes: 8 additions & 0 deletions src/api/chatRoom/getMessages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import req from '../apiUtils';

const getMessages = async (scheduleId: number) => {
const response = await req(`/chat/messages/${scheduleId}`, 'get', 'gin');
return response;
};

export default getMessages;
13 changes: 13 additions & 0 deletions src/api/chatRoom/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import getLiveMessages from './getLiveMessages';
import getMessages from './getMessages';
import postMessage from './postMessage';
import URLList from './URLList';

const chatRoomAPI = {
getMessages,
postMessage,
getLiveMessages,
URLList,
};

export default chatRoomAPI;
21 changes: 21 additions & 0 deletions src/api/chatRoom/postMessage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import req from '../apiUtils';

const postMessage = async (
scheduleId: number,
user: string,
message: string
) => {
const formData = new FormData();
formData.append('user', user);
formData.append('message', message);
const response = await req(
`/chat/room/${scheduleId}`,
'post',
'gin',
formData
);

return response.data;
};

export default postMessage;
4 changes: 3 additions & 1 deletion src/api/classCode/getVerifySecret.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ const getVerifySecret = async (
uid: number
) => {
const response = await req(
`/cc/VerifyClassCode?code=${classCode}secret=${secret}&uid=${uid}`,
`/cc/verifyAndRequestAccess?code=${classCode}${
secret ? `&secret=${secret}` : ''
}&uid=${uid}`,
'get',
'gin'
);
Expand Down
13 changes: 13 additions & 0 deletions src/api/classUser/getClassUsers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import req from '../apiUtils';

const getClassUsers = async (cid: number, role: string) => {
const response = await req(
`/cu/class/${cid}/members?role=${role}`,
'get',
'gin'
);

return response.data;
};

export default getClassUsers;
171 changes: 171 additions & 0 deletions src/app/classes/[cId]/[mId]/components/chatComponents/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
'use client';

import {useRef, useState, useEffect} from 'react';
import {useParams} from 'next/navigation';
import Image from 'next/image';
import {useRecoilValue} from 'recoil';
import userState from '@/src/recoil/atoms/userState';
import chatRoomAPI from '@/src/api/chatRoom';
import getUserInfo from '@/src/api/classUser/getUserInfo';
import ChatInput from '@/src/app/classes/[cId]/[mId]/components/subComponents/ChatInput';
import {User} from '@/src/interfaces/user';

interface UserInfo extends User {
id: number;
}

const ShowMain = () => {
const user = useRecoilValue(userState) as User;
const [message, setMessage] = useState('');
const [messages, setMessages] = useState<string[]>([]);
const [chatUsers, setChatUsers] = useState<{[key: string]: User}>({});
const params = useParams();
const getClassId = Number(params.cId);
const getScheduleId = 14;
const messagesEndRef = useRef<HTMLDivElement>(null);

const handleGetMessages = () => {
chatRoomAPI
.getMessages(getScheduleId)
.then(res => {
if (res && 'data' in res) {
const {data} = res;
setMessages(data || []);
} else {
console.error('응답에서 data를 찾을 수 없습니다.');
setMessages([]);
}
})
.catch(error => {
console.error('메시지를 가져오는 중 오류 발생:', error);
});
};

const handleGetLiveStart = () => {
const eventSource = chatRoomAPI.getLiveMessages(getScheduleId, message => {
setMessages(prevMessages => [...prevMessages, message]);
});

eventSource.onerror = function (event) {
console.error('EventSource failed:', event);
};

return eventSource;
};

const handleGetUserInfo = async (uid: number) => {
const userInfo = await getUserInfo(uid, getClassId);
return userInfo;
};

useEffect(() => {
if (message === '') return;
chatRoomAPI
.postMessage(getScheduleId, user.id.toString(), message)
.then(res => {
setMessage('');
console.log(res);
});
}, [message]);

useEffect(() => {
const fetchChatUsers = async () => {
const newChatUsers: {[id: string]: UserInfo} = {};
for (const msg of messages) {
const [id] = msg.split(': ');
if (!newChatUsers[id]) {
newChatUsers[id] = await handleGetUserInfo(Number(id));
}
}
setChatUsers(newChatUsers);
};

fetchChatUsers();
}, [messages]);

useEffect(() => {
if (messagesEndRef.current) {
messagesEndRef.current.scrollIntoView({behavior: 'smooth'});
}
}, [messages]);

useEffect(() => {
const eventSource = handleGetLiveStart();
return () => {
eventSource.close();
};
}, [getScheduleId]);

useEffect(() => {
handleGetMessages();
}, [getScheduleId]);

return (
<div className="flex justify-center mt-10">
<div className="w-full max-w-md">
<div className="w-full mt-2">
<div className="border-4 rounded-lg border-gray-500 p-2 h-[550px] overflow-auto">
<p className="text-center inline-block px-4 py-2 text-sm text-white bg-violet-300 rounded-lg w-full">
최상단 채팅 내용입니다
</p>
{messages.map((msg, index) => {
const [id, message] = msg.split(': ');
const chatUser = chatUsers[id];
if (!chatUser) {
return null;
}
return (
<div
key={index}
className={
Number(id) === user.id
? 'flex justify-end items-start'
: 'flex justify-start items-start'
}
>
{Number(id) !== user.id && (
<div className="flex items-center mt-2">
<div className="rounded-full w-8 h-8 overflow-hidden max-w-20">
<Image
src={chatUser.image || user.image}
alt={'userImage'}
width={40}
height={40}
/>
</div>
<div className="ml-2">
<p>{chatUser.nickname || user.name}</p>
<p
className={
'inline-block px-3 py-2 text-sm text-white bg-gray-500 rounded-lg max-w-72'
}
>
{message}
</p>
</div>
</div>
)}
{Number(id) === user.id && (
<div className="mt-2">
<p
className={
'inline-block px-4 py-2 text-sm text-white bg-blue-400 rounded-lg max-w-72'
}
>
{message}
</p>
</div>
)}
</div>
);
})}
<div ref={messagesEndRef} />
</div>
</div>
<ChatInput setMsg={setMessage} />
</div>
</div>
);
};

export default ShowMain;
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import React from 'react';
import React, {useState} from 'react';
import LiveClass from './LiveClass';
import {useParams} from 'next/navigation';
import {useRecoilValue} from 'recoil';
import {User} from '@/src/interfaces/user';
import userState from '@/src/recoil/atoms/userState';
import ShowMain from '../chatComponents/page';

const ManageSubContainer: React.FC = () => {
const [showMain, setShowMain] = useState(false);
const {cId} = useParams<{cId: string}>();
const classId = parseInt(cId, 10);
const user = useRecoilValue(userState) as User;
Expand All @@ -17,6 +19,17 @@ const ManageSubContainer: React.FC = () => {
return (
<div>
<LiveClass classId={classId} userId={user.id} />
<button
onClick={() => setShowMain(!showMain)}
className="fixed top-1 right-4 z-50 bg-primary text-primary-foreground rounded-full p-3 shadow-lg hover:bg-primary/90"
>
{showMain ? '💬' : '💬'}
</button>
{showMain && (
<div className="fixed top-0 right-0 w-96 h-full bg-white shadow-lg z-10">
<ShowMain />
</div>
)}
</div>
);
};
Expand Down
Loading

0 comments on commit db75e95

Please sign in to comment.