diff --git a/staff/marti-herms/project/G-HUB/api/index.js b/staff/marti-herms/project/G-HUB/api/index.js
index 9cdaee267..6dcec472b 100644
--- a/staff/marti-herms/project/G-HUB/api/index.js
+++ b/staff/marti-herms/project/G-HUB/api/index.js
@@ -60,7 +60,7 @@ mongoose.connect(process.env.MONGODB_URI)
api.delete('/reviews/:reviewId', jwtVerifier, handle.deleteReview)
- api.get('/chat/:targeUserId', jwtVerifier, handle.openChat)
+ api.get('/chat/:targetUserId', jwtVerifier, handle.openChat)
api.post('/chat/:chatId/messages', jwtVerifier, jsonBodyParser, handle.sendMessage)
diff --git a/staff/marti-herms/project/G-HUB/app/logic/getChatMessages.js b/staff/marti-herms/project/G-HUB/app/logic/getChatMessages.js
new file mode 100644
index 000000000..3ce8a3fc6
--- /dev/null
+++ b/staff/marti-herms/project/G-HUB/app/logic/getChatMessages.js
@@ -0,0 +1,29 @@
+import { validate, errors } from 'com'
+
+const { SystemError } = errors
+
+export default (chatId) => {
+ validate.string(chatId, 'chatId')
+
+ return fetch(`${import.meta.env.VITE_API_URL}/chat/${chatId}/messages`, {
+ headers: { Authorization: `Bearer ${sessionStorage.token}` }
+ })
+ .catch(error => { throw new SystemError(error.message) })
+ .then(response => {
+ const { status } = response
+
+ if (status === 200) {
+ return response.json()
+ .then(messages => messages)
+ }
+
+ return response.json()
+ .then(body => {
+ const { error, message } = body
+
+ const constructor = errors[error]
+
+ throw new constructor(message)
+ })
+ })
+}
\ No newline at end of file
diff --git a/staff/marti-herms/project/G-HUB/app/logic/index.js b/staff/marti-herms/project/G-HUB/app/logic/index.js
index 9311eb1c4..06a9b5c2e 100644
--- a/staff/marti-herms/project/G-HUB/app/logic/index.js
+++ b/staff/marti-herms/project/G-HUB/app/logic/index.js
@@ -22,11 +22,15 @@ import getUserAvatar from './getUserAvatar.js'
import getUser from './getUser.js'
import editUserAvatar from './editUserAvatar.js'
import editUserUsername from './editUserUsername.js'
+import openChat from './openChat.js'
+import sendMessage from './sendMessage.js'
+import getChatMessages from './getChatMessages.js'
const logic = {
deleteReview,
editUserAvatar,
editUserUsername,
+ getChatMessages,
getDevUserGames,
getGameById,
getGameReviews,
@@ -41,10 +45,12 @@ const logic = {
loginUser,
logoutUser,
makeReview,
+ openChat,
registerGame,
registerUser,
searchGame,
searchUser,
+ sendMessage,
toggleAddGame,
toggleFavGame,
toggleFollowUser,
diff --git a/staff/marti-herms/project/G-HUB/app/logic/openChat.js b/staff/marti-herms/project/G-HUB/app/logic/openChat.js
new file mode 100644
index 000000000..d2882e8c7
--- /dev/null
+++ b/staff/marti-herms/project/G-HUB/app/logic/openChat.js
@@ -0,0 +1,29 @@
+import { validate, errors } from 'com'
+
+const { SystemError } = errors
+
+export default (targetUserId) => {
+ validate.string(targetUserId, 'targetUserId')
+
+ return fetch(`${import.meta.env.VITE_API_URL}/chat/${targetUserId}`, {
+ headers: { Authorization: `Bearer ${sessionStorage.token}` }
+ })
+ .catch(error => { throw new SystemError(error.message) })
+ .then(response => {
+ const { status } = response
+
+ if (status === 200) {
+ return response.json()
+ .then(chatId => chatId)
+ }
+
+ return response.json()
+ .then(body => {
+ const { error, message } = body
+
+ const constructor = errors[error]
+
+ throw new constructor(message)
+ })
+ })
+}
\ No newline at end of file
diff --git a/staff/marti-herms/project/G-HUB/app/logic/sendMessage.js b/staff/marti-herms/project/G-HUB/app/logic/sendMessage.js
new file mode 100644
index 000000000..347b951d0
--- /dev/null
+++ b/staff/marti-herms/project/G-HUB/app/logic/sendMessage.js
@@ -0,0 +1,31 @@
+import { validate, errors } from 'com'
+
+const { SystemError } = errors
+
+export default (chatId, content) => {
+ validate.string(chatId, 'chatId')
+ validate.string(content, 'content')
+
+ if (content.trim().length === 0) throw new Error('empty content')
+
+ return fetch(`${import.meta.env.VITE_API_URL}/chat/${chatId}/messages`, {
+ method: 'POST',
+ headers: { Authorization: `Bearer ${sessionStorage.token}` },
+ body: JSON.stringify({ content })
+ })
+ .catch(error => { throw new SystemError(error.message) })
+ .then(response => {
+ const { status } = response
+
+ if (status === 200) return
+
+ return response.json()
+ .then(body => {
+ const { error, message } = body
+
+ const constructor = errors[error]
+
+ throw new constructor(message)
+ })
+ })
+}
\ No newline at end of file
diff --git a/staff/marti-herms/project/G-HUB/app/src/home/AddGame.jsx b/staff/marti-herms/project/G-HUB/app/src/home/AddGame.jsx
index a4976f2c4..c6833394d 100644
--- a/staff/marti-herms/project/G-HUB/app/src/home/AddGame.jsx
+++ b/staff/marti-herms/project/G-HUB/app/src/home/AddGame.jsx
@@ -55,7 +55,7 @@ export default function AddGame({ onAddGame }) {
{image && }
-
+
diff --git a/staff/marti-herms/project/G-HUB/app/src/home/Chat.jsx b/staff/marti-herms/project/G-HUB/app/src/home/Chat.jsx
new file mode 100644
index 000000000..140cd15c7
--- /dev/null
+++ b/staff/marti-herms/project/G-HUB/app/src/home/Chat.jsx
@@ -0,0 +1,141 @@
+import { useEffect, useState } from 'react'
+import { useParams } from 'react-router-dom'
+import { IoIosSend as SendIcon } from 'react-icons/io'
+
+import Container from '../library/Container'
+import Form from '../library/Form'
+import Input from '../library/Input'
+import Button from '../library/Button'
+import Avatar from '../library/Avatar'
+import Paragraph from '../library/Paragraph'
+
+import Message from './Message'
+
+import useContext from '../context'
+import extractPayloadFromToken from '../../util/extractPayloadFromToken'
+
+import defaultAvatar from '../../images/defaultAvatar.svg'
+
+import logic from '../../logic'
+
+export default function Chat({ onOpenChat }) {
+ const [users, setUsers] = useState([])
+ const [chat, setChat] = useState(null)
+ const [messages, setMessages] = useState([])
+ const { alert } = useContext()
+
+ const { sub: loggedInUser } = extractPayloadFromToken(sessionStorage.token)
+
+ const { userId } = useParams()
+
+ useEffect(() => {
+ try {
+ if (userId === loggedInUser) {
+ logic.getUserFollowing(userId)
+ .then(users => setUsers(users))
+ .catch(error => {
+ console.error(error)
+
+ alert(error.message)
+ })
+ } else {
+ logic.openChat(userId)
+ .then(chatId => setChat(chatId))
+ .catch(error => {
+ console.error(error)
+
+ alert(error.message)
+ })
+ }
+ } catch (error) {
+ console.error(error)
+
+ alert(error.message)
+ }
+ }, [userId])
+
+ useEffect(() => {
+ let intervalId
+ if (chat) {
+ loadMessages()
+ intervalId = setInterval(() => {
+ loadMessages()
+ }, 1000)
+ }
+ return () => {
+ if (intervalId) {
+ clearInterval(intervalId)
+ setChat(null)
+ }
+ }
+ }, [chat])
+
+ const handleSendMessage = (event) => {
+ event.preventDefault()
+
+ const form = event.target
+
+ const messageInput = form['message-input']
+
+ const message = messageInput.value
+
+ try {
+ logic.sendMessage(chat, message)
+ .then(() => {
+ loadMessages()
+ })
+ .catch(error => {
+ console.error(error)
+
+ alert(error)
+ })
+ } catch (error) {
+ console.error(error)
+
+ alert(error.message)
+ }
+ }
+
+ const loadMessages = () => {
+ try {
+ logic.getChatMessages(chat)
+ .then(messages => setMessages(messages.reverse()))
+ .catch(error => {
+ console.error(error)
+
+ alert(error.message)
+ })
+ } catch (error) {
+ console.error(error)
+
+ alert(error.message)
+ }
+ }
+
+ const handleOpenChat = (userId) => {
+ onOpenChat(userId)
+ }
+
+ return
+ {userId !== loggedInUser ? <>
+
+ {messages.map(message => )}
+
+
+ > :
+
+ {users.map(user =>
+
+
+
+ {user.username}
+
+
+
+ )}
+ }
+
+}
\ No newline at end of file
diff --git a/staff/marti-herms/project/G-HUB/app/src/home/Footer.jsx b/staff/marti-herms/project/G-HUB/app/src/home/Footer.jsx
index 0ab5381bf..93a2419c4 100644
--- a/staff/marti-herms/project/G-HUB/app/src/home/Footer.jsx
+++ b/staff/marti-herms/project/G-HUB/app/src/home/Footer.jsx
@@ -1,16 +1,25 @@
-import { Route, Routes } from 'react-router-dom'
+import { Route, Routes, useLocation } from 'react-router-dom'
import { MdScreenSearchDesktop as SearchIcon, MdAddComment as ReviewIcon, MdAddBox as AddGameIcon, MdCancelPresentation as CancelIcon } from 'react-icons/md'
import { GoHome as HomeIcon } from 'react-icons/go'
-
+import { BsChatRightText as ChatIcon } from 'react-icons/bs'
import Button from '../library/Button'
-import NavigationButton from '../library/NavigationButton'
import extractPayloadFromToken from '../../util/extractPayloadFromToken.js'
import paths from '../../util/paths.js'
-export default function Footer({ makeReviewVisibility, onSearchGame, onAddGame, onHome, onAddReview, onCancel }) {
+export default function Footer({ makeReviewVisibility, onSearchGame, onAddGame, onHome, onAddReview, onCancel, onChat }) {
const { role } = extractPayloadFromToken(sessionStorage.token)
+ const location = useLocation()
+
+ let userId = location.pathname.slice(location.pathname.indexOf('/', 1) + 1)
+ if (userId.includes('/')) {
+ userId = userId.slice(0, userId.indexOf('/'))
+ }
+
+ const handleChatClick = () => {
+ onChat(userId)
+ }
return
+ onCancel={handleCancelReview}
+ onChat={handleChat} >
>
}
\ No newline at end of file
diff --git a/staff/marti-herms/project/G-HUB/app/src/library/Input.jsx b/staff/marti-herms/project/G-HUB/app/src/library/Input.jsx
index c14ff3d02..28c632f4d 100644
--- a/staff/marti-herms/project/G-HUB/app/src/library/Input.jsx
+++ b/staff/marti-herms/project/G-HUB/app/src/library/Input.jsx
@@ -1,3 +1,3 @@
export default function Input({ className = '', ...nextProps }) {
- return
+ return
}
\ No newline at end of file
diff --git a/staff/marti-herms/project/G-HUB/app/util/paths.js b/staff/marti-herms/project/G-HUB/app/util/paths.js
index 4c6a9e952..9f720829c 100644
--- a/staff/marti-herms/project/G-HUB/app/util/paths.js
+++ b/staff/marti-herms/project/G-HUB/app/util/paths.js
@@ -7,7 +7,8 @@ const paths = {
game: '/games/',
profile: '/profile/',
following: '/following/',
- followers: '/followers/'
+ followers: '/followers/',
+ chat: '/chat/'
}
export default paths
\ No newline at end of file