Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

クラス参加とメンバーAPI接続、ライブチャット機能を追加 #169

Merged
merged 3 commits into from
Jun 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading