diff --git a/client/src/common/constants/chat-type.ts b/client/src/common/constants/chat-type.ts index 6f86028..c9c4435 100644 --- a/client/src/common/constants/chat-type.ts +++ b/client/src/common/constants/chat-type.ts @@ -1,6 +1,7 @@ const ChatType = { - DM: 'DM', - Channel: 'Channel' + Message: 'Message', + ReplyTitle: 'ReplyTitle', + Reply: 'Reply' }; export { ChatType }; diff --git a/client/src/common/constants/chatroom-type.ts b/client/src/common/constants/chatroom-type.ts new file mode 100644 index 0000000..62bcac9 --- /dev/null +++ b/client/src/common/constants/chatroom-type.ts @@ -0,0 +1,6 @@ +const ChatroomType = { + DM: 'DM', + Channel: 'Channel' +}; + +export { ChatroomType }; diff --git a/client/src/common/constants/index.ts b/client/src/common/constants/index.ts index 411e63f..4ea73a3 100644 --- a/client/src/common/constants/index.ts +++ b/client/src/common/constants/index.ts @@ -1,7 +1,8 @@ import { DefaultSectionName } from './default-section-name'; import { KeyCode } from './key-code'; import { ScrollEventType } from './scroll-event-type'; +import { ChatroomType } from './chatroom-type'; import { ChatType } from './chat-type'; import { HttpStatusCode } from './http-status-code'; -export { DefaultSectionName, KeyCode, ScrollEventType, ChatType, HttpStatusCode }; +export { DefaultSectionName, KeyCode, ScrollEventType, ChatroomType, ChatType, HttpStatusCode }; diff --git a/client/src/common/socket/emits/reaction.ts b/client/src/common/socket/emits/reaction.ts index f1791ad..6f908f8 100644 --- a/client/src/common/socket/emits/reaction.ts +++ b/client/src/common/socket/emits/reaction.ts @@ -1,8 +1,12 @@ import { CREATE_MESSAGE_REACTION, DELETE_MESSAGE_REACTION, + CREATE_REPLY_REACTION, + DELETE_REPLY_REACTION, createMessageReactionState, - deleteMessageReactionState + deleteMessageReactionState, + createReplyReactionState, + deleteReplyReactionState } from '@socket/types/reaction-types'; import socket from '../socketIO'; @@ -13,3 +17,11 @@ export const createMessageReaction = (reaction: createMessageReactionState) => { export const deleteMessageReaction = (reaction: deleteMessageReactionState) => { socket.emit(DELETE_MESSAGE_REACTION, reaction); }; + +export const createReplyReaction = (reaction: createReplyReactionState) => { + socket.emit(CREATE_REPLY_REACTION, reaction); +}; + +export const deleteReplyReaction = (reaction: deleteReplyReactionState) => { + socket.emit(DELETE_REPLY_REACTION, reaction); +}; diff --git a/client/src/common/socket/types/reaction-types.ts b/client/src/common/socket/types/reaction-types.ts index 5fdc128..15660c8 100644 --- a/client/src/common/socket/types/reaction-types.ts +++ b/client/src/common/socket/types/reaction-types.ts @@ -1,5 +1,7 @@ export const CREATE_MESSAGE_REACTION = 'create reaction'; export const DELETE_MESSAGE_REACTION = 'delete reaction'; +export const CREATE_REPLY_REACTION = 'create reply reaction'; +export const DELETE_REPLY_REACTION = 'delete reply reaction'; interface userInfo { displayName: string; @@ -17,6 +19,17 @@ export interface deleteMessageReactionState { reactionId: number; } +export interface createReplyReactionState { + replyId: number; + title: string; + emoji: string; +} + +export interface deleteReplyReactionState { + replyId: number; + reactionId: number; +} + export interface socketMessageReactionState { authors: Array; chatroomId: number; @@ -25,3 +38,12 @@ export interface socketMessageReactionState { reactionId: number; title: string; } + +export interface socketReplyReactionState { + reactionId: number; + title: string; + emoji: string; + replyId: number; + authors: Array; + messageId: number; +} diff --git a/client/src/common/store/actions/thread-action.ts b/client/src/common/store/actions/thread-action.ts index edbe6d1..747266b 100644 --- a/client/src/common/store/actions/thread-action.ts +++ b/client/src/common/store/actions/thread-action.ts @@ -1,5 +1,15 @@ -import { LOAD_THREAD_ASYNC, INSERT_REPLY, LOAD_NEXT_REPLIES_ASYNC, replyState } from '@store/types/thread-types'; +import { socketReplyReactionState } from '@socket/types/reaction-types'; +import { + LOAD_THREAD_ASYNC, + INSERT_REPLY, + LOAD_NEXT_REPLIES_ASYNC, + ADD_REPLY_REACTION, + DELETE_REPLY_REACTION, + replyState +} from '@store/types/thread-types'; export const loadThread = (messageId: number) => ({ type: LOAD_THREAD_ASYNC, payload: { messageId } }); export const InsertReply = (payload: replyState) => ({ type: INSERT_REPLY, payload }); export const loadNextReplies = (payload: any) => ({ type: LOAD_NEXT_REPLIES_ASYNC, payload }); +export const createReplyReaction = (payload: socketReplyReactionState) => ({ type: ADD_REPLY_REACTION, payload }); +export const deleteReplyReaction = (payload: socketReplyReactionState) => ({ type: DELETE_REPLY_REACTION, payload }); diff --git a/client/src/common/store/reducers/chatroom-reducer.ts b/client/src/common/store/reducers/chatroom-reducer.ts index 07b2572..68cce84 100644 --- a/client/src/common/store/reducers/chatroom-reducer.ts +++ b/client/src/common/store/reducers/chatroom-reducer.ts @@ -4,7 +4,7 @@ import { uriParser } from '@utils/index'; import { joinChatroom, joinDM } from '@socket/emits/chatroom'; import { messageState } from '@store/types/message-types'; -import { messageReactionsState } from '@store/types/message-reactions-type'; +import { reactionsState } from '@store/types/reactions-type'; import { chatroomState, LOAD, @@ -155,7 +155,7 @@ const chatroomReducer = (state = initialState, action: ChatroomTypes) => { addReactionMessages.forEach((message: messageState) => { if (message.messageId === action.payload.messageId) { let isExistReaction = false; - message.messageReactions.forEach((reaction: messageReactionsState) => { + message.messageReactions.forEach((reaction: reactionsState) => { if (reaction.reactionId === action.payload.reactionId) { reaction.reactionCount += 1; reaction.reactionDisplayNames = action.payload.authors.reduce((acc: Array, val: any) => { @@ -183,7 +183,7 @@ const chatroomReducer = (state = initialState, action: ChatroomTypes) => { if (state.selectedChatroomId === action.payload.chatroomId) { deleteReactionMessages.forEach((message: messageState) => { if (message.messageId === action.payload.messageId) { - message.messageReactions.forEach((reaction: messageReactionsState) => { + message.messageReactions.forEach((reaction: reactionsState) => { if (reaction.reactionId === action.payload.reactionId) { reaction.reactionCount -= 1; reaction.reactionDisplayNames = action.payload.authors.reduce((acc: Array, val: any) => { diff --git a/client/src/common/store/reducers/modal-reducer.ts b/client/src/common/store/reducers/modal-reducer.ts index c6bd840..5ba6f15 100644 --- a/client/src/common/store/reducers/modal-reducer.ts +++ b/client/src/common/store/reducers/modal-reducer.ts @@ -12,13 +12,14 @@ import { EMOJI_PICKER_CLOSE, EMOJI_PICKER_OPEN } from '@store/types/modal-types'; +import { ChatType } from '@constants/index'; const initialState: ModalState = { createModal: { isOpen: false }, channelModal: { isOpen: false, x: 0, y: 0 }, userboxModal: { isOpen: false }, profileModal: { isOpen: false, x: 0, y: 0, userId: 0, profileUri: '', displayName: '' }, - emojiPicker: { isOpen: false, x: 0, y: 0, messageId: null } + emojiPicker: { isOpen: false, x: 0, y: 0, chatId: null, type: ChatType.Message } }; const ModalReducer = (state = initialState, action: ModalTypes) => { @@ -41,8 +42,8 @@ const ModalReducer = (state = initialState, action: ModalTypes) => { case PROFILE_MODAL_CLOSE: return { ...state, profileModal: { isOpen: false } }; case EMOJI_PICKER_OPEN: - const { x, y, messageId } = action.payload; - return { ...state, emojiPicker: { isOpen: true, x, y, messageId } }; + const { x, y, chatId, type } = action.payload; + return { ...state, emojiPicker: { isOpen: true, x, y, chatId, type } }; case EMOJI_PICKER_CLOSE: return { ...state, emojiPicker: { isOpen: false } }; default: diff --git a/client/src/common/store/reducers/thread-reducer.ts b/client/src/common/store/reducers/thread-reducer.ts index 2943705..f145d95 100644 --- a/client/src/common/store/reducers/thread-reducer.ts +++ b/client/src/common/store/reducers/thread-reducer.ts @@ -1,4 +1,16 @@ -import { LOAD_THREAD, threadState, INSERT_REPLY, ThreadTypes, LOAD_NEXT_REPLIES } from '@store/types/thread-types'; +/* eslint-disable no-param-reassign */ +import { reactionsState } from '@store/types/reactions-type'; +import { + LOAD_THREAD, + threadState, + INSERT_REPLY, + ThreadTypes, + LOAD_NEXT_REPLIES, + ADD_REPLY_REACTION, + DELETE_REPLY_REACTION, + replyState +} from '@store/types/thread-types'; +import { uriParser } from '@utils/index'; const initialState: threadState = { message: { @@ -15,7 +27,8 @@ const initialState: threadState = { chatroom: {}, messageReactions: [] }, - replies: [] + replies: [], + selectedThreadId: uriParser.getThreadId() }; export default function threadReducer(state = initialState, action: ThreadTypes) { @@ -24,7 +37,8 @@ export default function threadReducer(state = initialState, action: ThreadTypes) return { ...state, message: action.payload.message, - replies: action.payload.replies + replies: action.payload.replies, + selectedThreadId: uriParser.getThreadId() }; case INSERT_REPLY: const newReplies = state.replies; @@ -42,6 +56,58 @@ export default function threadReducer(state = initialState, action: ThreadTypes) ...state, replies: nextreplies }; + case ADD_REPLY_REACTION: { + const NewReplies = state.replies; + const { messageId, reactionId, replyId } = action.payload; + if (state.selectedThreadId === messageId) { + NewReplies.forEach((reply: replyState) => { + if (reply.replyId === replyId) { + let bExistReaction = false; + reply.replyReactions.forEach((reaction: any) => { + if (reaction.reactionId === reactionId) { + reaction.reactionCount += 1; + reaction.replyDisplayNames = action.payload.authors.reduce((acc: Array, val: any) => { + acc.push(val.displayName); + return acc; + }, []); + bExistReaction = true; + } + }); + if (!bExistReaction) { + reply.replyReactions.push({ + reactionCount: 1, + emoji: action.payload.emoji, + replyDisplayNames: [action.payload.authors[0].displayName], + reactionId: action.payload.reactionId, + title: action.payload.title + }); + } + } + }); + } + + return { ...state, replies: NewReplies }; + } + case DELETE_REPLY_REACTION: { + const NewReplies = state.replies; + const { messageId, reactionId, replyId } = action.payload; + if (state.selectedThreadId === messageId) { + NewReplies.forEach((reply: replyState) => { + if (reply.replyId === replyId) { + reply.replyReactions.forEach((reaction: any) => { + if (reaction.reactionId === action.payload.reactionId) { + reaction.reactionCount -= 1; + reaction.replyDisplayNames = action.payload.authors.reduce((acc: Array, val: any) => { + acc.push(val.displayName); + return acc; + }, []); + } + }); + } + }); + } + return { ...state, replies: NewReplies }; + } default: return state; } diff --git a/client/src/common/store/sagas/chatroom-saga.ts b/client/src/common/store/sagas/chatroom-saga.ts index d551ff5..979fbbe 100644 --- a/client/src/common/store/sagas/chatroom-saga.ts +++ b/client/src/common/store/sagas/chatroom-saga.ts @@ -1,6 +1,6 @@ import { call, put, takeEvery } from 'redux-saga/effects'; import API from '@utils/api'; -import { ChatType } from '@constants/index'; +import { ChatroomType } from '@constants/index'; import { LOAD, LOAD_ASYNC, @@ -59,7 +59,7 @@ function* pickChannelSaga(action: any) { function* addChannel(action: any) { try { const chatroomId = yield call(API.createChannel, action.payload.title, action.payload.description, action.payload.isPrivate); - const payload = { chatroomId, chatType: ChatType.Channel, isPrivate: action.payload.isPrivate, title: action.payload.title }; + const payload = { chatroomId, chatType: ChatroomType.Channel, isPrivate: action.payload.isPrivate, title: action.payload.title }; yield put({ type: ADD_CHANNEL, payload }); yield put({ type: PICK_CHANNEL_ASYNC, payload: { selectedChatroomId: chatroomId } }); } catch (e) { @@ -72,7 +72,7 @@ function* addDM(action: any) { const { invitedUserId } = action.payload; const chatroomId = yield call(API.createDM, invitedUserId); const { profileUri, displayName } = yield call(API.getUser, invitedUserId); - yield put({ type: ADD_DM, payload: { chatroomId, chatProfileImg: profileUri, chatType: ChatType.DM, title: displayName, invitedUserId } }); + yield put({ type: ADD_DM, payload: { chatroomId, chatProfileImg: profileUri, chatType: ChatroomType.DM, title: displayName, invitedUserId } }); yield put({ type: PICK_CHANNEL_ASYNC, payload: { selectedChatroomId: chatroomId } }); } catch (e) { console.log(e); diff --git a/client/src/common/store/types/message-types.ts b/client/src/common/store/types/message-types.ts index f7649fd..fa2a010 100644 --- a/client/src/common/store/types/message-types.ts +++ b/client/src/common/store/types/message-types.ts @@ -1,5 +1,5 @@ import { userState } from './user-types'; -import { messageReactionsState } from './message-reactions-type'; +import { reactionsState } from './reactions-type'; import { chatroomThreadState } from './chatroom-types'; export interface messageState { @@ -7,7 +7,7 @@ export interface messageState { createdAt: Date; updatedAt: Date; user: userState; - messageReactions: Array; + messageReactions: Array; thread: chatroomThreadState; } diff --git a/client/src/common/store/types/modal-types.ts b/client/src/common/store/types/modal-types.ts index 3687121..fb5e34c 100644 --- a/client/src/common/store/types/modal-types.ts +++ b/client/src/common/store/types/modal-types.ts @@ -35,7 +35,8 @@ export interface EmojiPickerState { isOpen: boolean; x: number; y: number; - messageId: number | null; + chatId: number | null; + type: string; } export interface ModalState { diff --git a/client/src/common/store/types/message-reactions-type.ts b/client/src/common/store/types/reactions-type.ts similarity index 74% rename from client/src/common/store/types/message-reactions-type.ts rename to client/src/common/store/types/reactions-type.ts index 5960b32..cc905a3 100644 --- a/client/src/common/store/types/message-reactions-type.ts +++ b/client/src/common/store/types/reactions-type.ts @@ -1,4 +1,4 @@ -export interface messageReactionsState { +export interface reactionsState { reactionId: number; title: string; reactionCount: number; diff --git a/client/src/common/store/types/thread-types.ts b/client/src/common/store/types/thread-types.ts index 832f683..01b7114 100644 --- a/client/src/common/store/types/thread-types.ts +++ b/client/src/common/store/types/thread-types.ts @@ -1,3 +1,4 @@ +import { socketReplyReactionState } from '@socket/types/reaction-types'; import { userState } from '@store/types/user-types'; export const LOAD_THREAD = 'LOAD_THREAD'; @@ -5,6 +6,8 @@ export const LOAD_THREAD_ASYNC = 'LOAD_THREAD_ASYNC'; export const INSERT_REPLY = 'INSERT_REPLY'; export const LOAD_NEXT_REPLIES = 'LOAD_NEXT_REPLIES'; export const LOAD_NEXT_REPLIES_ASYNC = 'LOAD_NEXT_REPLIES_ASYNC'; +export const ADD_REPLY_REACTION = 'ADD_MESSAGE_REACTION'; +export const DELETE_REPLY_REACTION = 'DELETE_MESSAGE_REACTION'; export interface threadMessageState { messageId: number; @@ -18,13 +21,13 @@ export interface threadMessageState { } export interface replyState { - messageId?: number; - replyId: number; + messageId: number; content: string; createdAt: Date; - updateAt: Date; + updatedAt: Date; + replyId: number; + replyReactions: Array; user: userState; - replyReactions: Array; } export interface repliesState { @@ -34,6 +37,7 @@ export interface repliesState { export interface threadState { message: threadMessageState; replies: Array; + selectedThreadId: number | null; } interface LoadThreadAction { @@ -51,4 +55,14 @@ interface LoadNextReplies { payload: repliesState; } -export type ThreadTypes = LoadThreadAction | InsertReply | LoadNextReplies; +interface AddReplyReaction { + type: typeof ADD_REPLY_REACTION; + payload: socketReplyReactionState; +} + +interface DeleteReplyReaction { + type: typeof DELETE_REPLY_REACTION; + payload: socketReplyReactionState; +} + +export type ThreadTypes = LoadThreadAction | InsertReply | LoadNextReplies | AddReplyReaction | DeleteReplyReaction; diff --git a/client/src/components/molecules/Actionbar/Actionbar.stories.tsx b/client/src/components/molecules/Actionbar/Actionbar.stories.tsx index 6774fef..03bbe3a 100644 --- a/client/src/components/molecules/Actionbar/Actionbar.stories.tsx +++ b/client/src/components/molecules/Actionbar/Actionbar.stories.tsx @@ -11,5 +11,5 @@ const Template: Story = (args) => ; export const BlackActionbar = Template.bind({}); BlackActionbar.args = { - messageId: 1 + chatId: 1 }; diff --git a/client/src/components/molecules/Actionbar/Actionbar.tsx b/client/src/components/molecules/Actionbar/Actionbar.tsx index 36a136c..2a586d1 100644 --- a/client/src/components/molecules/Actionbar/Actionbar.tsx +++ b/client/src/components/molecules/Actionbar/Actionbar.tsx @@ -1,6 +1,6 @@ import React from 'react'; import styled from 'styled-components'; -import { HoverIcon, EmojiPicker } from '@components/molecules'; +import { HoverIcon } from '@components/molecules'; import { color } from '@theme/index'; import EmojiIcon from '@imgs/emoji-icon.png'; import ThreadIcon from '@imgs/thread-icon.png'; @@ -9,9 +9,11 @@ import { useDispatch, useSelector } from 'react-redux'; import { useHistory } from 'react-router-dom'; import { loadThread } from '@store/actions/thread-action'; import { emojiPickerOpen } from '@store/actions/modal-action'; +import { ChatType } from '@constants/index'; interface ActionbarProps { - messageId: number; + chatId: number; + actionbarType?: string; } const ActionbarContainer = styled.div` @@ -24,24 +26,24 @@ const ActionbarContainer = styled.div` border-radius: 0.2rem; `; -const Actionbar: React.FC = ({ messageId, ...props }) => { +const Actionbar: React.FC = ({ actionbarType = ChatType.Message, chatId, ...props }) => { const history = useHistory(); const dispatch = useDispatch(); const chatroomId = useSelector((state: any) => state.chatroom.selectedChatroomId); const openThread = () => { - dispatch(loadThread(messageId)); - history.push(`/client/${chatroomId}/thread/${messageId}`); + dispatch(loadThread(chatId)); + history.push(`/client/${chatroomId}/thread/${chatId}`); }; const openEmojiPicker = (e: any) => { const x = window.pageXOffset + e.target.getBoundingClientRect().left; const y = window.pageYOffset + e.target.getBoundingClientRect().top; - dispatch(emojiPickerOpen({ x, y, messageId })); + dispatch(emojiPickerOpen({ x, y, chatId, type: actionbarType })); }; return ( - + {actionbarType === ChatType.Message && } ); diff --git a/client/src/components/molecules/EmojiBox/EmojiBox.tsx b/client/src/components/molecules/EmojiBox/EmojiBox.tsx index 99f4866..271ea51 100644 --- a/client/src/components/molecules/EmojiBox/EmojiBox.tsx +++ b/client/src/components/molecules/EmojiBox/EmojiBox.tsx @@ -1,17 +1,17 @@ +/* eslint-disable no-unused-expressions */ import React, { useState } from 'react'; import styled from 'styled-components'; import { Emoji } from '@components/atoms'; import { color } from '@theme/index'; -import { useDispatch } from 'react-redux'; -import { createMessageReaction, deleteMessageReaction } from '@socket/emits/reaction'; interface EmojiBoxProps { emoji: string; active?: boolean; number: number; - messageId: number; reactionId: number; title: string; + createReaction: any; + deleteReaction: any; } const EmojiBoxContainer = styled.div` @@ -39,15 +39,11 @@ const EmojiBoxText = styled.p` margin: 0; `; -const EmojiBox: React.FC = ({ messageId, reactionId, title, active = false, number, emoji, ...props }) => { +const EmojiBox: React.FC = ({ reactionId, title, createReaction, deleteReaction, active = false, number, emoji, ...props }) => { const [isActive, setActive] = useState(active); - const dispatch = useDispatch(); + const handlingClick = () => { - if (isActive) { - deleteMessageReaction({ messageId, reactionId }); - } else { - createMessageReaction({ messageId, title, emoji }); - } + isActive ? deleteReaction(reactionId) : createReaction(title, emoji); setActive(!isActive); }; diff --git a/client/src/components/molecules/EmojiPicker/EmojiPicker.tsx b/client/src/components/molecules/EmojiPicker/EmojiPicker.tsx index 7faca10..80e0ad7 100644 --- a/client/src/components/molecules/EmojiPicker/EmojiPicker.tsx +++ b/client/src/components/molecules/EmojiPicker/EmojiPicker.tsx @@ -3,16 +3,19 @@ import Picker from 'emoji-picker-react'; import { DropMenuBox } from '@components/atoms'; import { useDispatch, useSelector } from 'react-redux'; import { emojiPickerClose } from '@store/actions/modal-action'; -import { createMessageReaction } from '@socket/emits/reaction'; +import { createMessageReaction, createReplyReaction } from '@socket/emits/reaction'; +import { ChatType } from '@constants/index'; interface EmojiPickerProps {} const EmojiPicker: React.FC = () => { const dispatch = useDispatch(); - const { x, y, messageId, isOpen } = useSelector((store: any) => store.modal.emojiPicker); + const { x, y, chatId, isOpen, type } = useSelector((store: any) => store.modal.emojiPicker); + const onEmojiClick = (e: any, emojiObject: any) => { const { names, emoji } = emojiObject; - createMessageReaction({ messageId, title: names[0], emoji }); + if (type === ChatType.Reply) createReplyReaction({ replyId: chatId, title: names[0], emoji }); + else createMessageReaction({ messageId: chatId, title: names[0], emoji }); dispatch(emojiPickerClose()); }; diff --git a/client/src/components/molecules/Message/Message.tsx b/client/src/components/molecules/Message/Message.tsx index 2568322..01d9620 100644 --- a/client/src/components/molecules/Message/Message.tsx +++ b/client/src/components/molecules/Message/Message.tsx @@ -7,9 +7,10 @@ import { getTimeConversionValue } from '@utils/time'; import { useHistory } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import { loadThread } from '@store/actions/thread-action'; -import { messageReactionsState } from '@store/types/message-reactions-type'; +import { reactionsState } from '@store/types/reactions-type'; import { RootState } from '@store/reducers'; import { openProfileModal } from '@utils/modal'; +import { createMessageReaction, deleteMessageReaction } from '@socket/emits/reaction'; import { EmojiBox } from '../EmojiBox/EmojiBox'; interface MessageProps { @@ -20,7 +21,7 @@ interface MessageProps { createdAt: Date; thread: any; user: { userId: number; profileUri: string; displayName: string }; - messageReactions: Array; + messageReactions: Array; } const MessageContainer = styled.div` @@ -80,6 +81,12 @@ const Message: React.FC = ({ messageId, author, thread, content, s dispatch(loadThread(messageId)); history.push(`/client/${selectedChatroomId}/thread/${messageId}`); }; + const createReaction = (title: string, emoji: string) => { + createMessageReaction({ messageId, title, emoji }); + }; + const deleteReaction = (reactionId: number) => { + deleteMessageReaction({ messageId, reactionId }); + }; const createEmojiBox = () => { const EmojiBoxs = messageReactions.map((reaction) => { @@ -90,8 +97,9 @@ const Message: React.FC = ({ messageId, author, thread, content, s emoji={reaction.emoji} number={reaction.reactionCount} active={reaction.reactionDisplayNames.includes(userName)} - messageId={messageId} reactionId={reaction.reactionId} + createReaction={createReaction} + deleteReaction={deleteReaction} title={reaction.title} /> ) @@ -127,7 +135,7 @@ const Message: React.FC = ({ messageId, author, thread, content, s /> )} - {isHover && } + {isHover && } ); }; diff --git a/client/src/components/molecules/Reply/Reply.tsx b/client/src/components/molecules/Reply/Reply.tsx index e77f507..13ff502 100644 --- a/client/src/components/molecules/Reply/Reply.tsx +++ b/client/src/components/molecules/Reply/Reply.tsx @@ -1,11 +1,15 @@ -import React from 'react'; -import { useDispatch } from 'react-redux'; +import React, { useRef, useState } from 'react'; import styled from 'styled-components'; import { ProfileImg, Text } from '@components/atoms'; import { color } from '@theme/index'; import { getTimeConversionValue } from '@utils/time'; -import { profileModalOpen } from '@store/actions/modal-action'; import { openProfileModal } from '@utils/modal'; +import { ChatType } from '@constants/index'; +import { useSelector } from 'react-redux'; +import { RootState } from '@store/reducers'; +import { createReplyReaction, deleteReplyReaction } from '@socket/emits/reaction'; +import { Actionbar } from '../Actionbar/Actionbar'; +import { EmojiBox } from '../EmojiBox/EmojiBox'; interface ReplyProps { reply: any; @@ -46,9 +50,52 @@ const DateText = styled.p` font-size: 0.7rem; `; +const EmojiBoxWrap = styled.div` + display: flex; + margin-top: 0.3rem; +`; + const Reply: React.FC = ({ reply }) => { + const [isHover, setHover] = useState(false); + const { replyId, replyReactions } = reply; + const userName = useSelector((store: RootState) => store.user.displayName); + const replyContainterEl = useRef(); + const onMouseEnter = () => { + setHover(true); + }; + const onMouseLeave = () => { + setHover(false); + }; + const createReaction = (title: string, emoji: string) => { + createReplyReaction({ replyId, title, emoji }); + }; + const deleteReaction = (reactionId: number) => { + deleteReplyReaction({ replyId, reactionId }); + }; + + const createEmojiBox = () => { + if (replyReactions === undefined) return <>; + + const EmojiBoxs = replyReactions.map((reaction: any) => { + return ( + reaction.replyDisplayNames.length !== 0 && ( + + ) + ); + }); + return {EmojiBoxs}; + }; return ( - + @@ -64,7 +111,9 @@ const Reply: React.FC = ({ reply }) => { {reply.content} + {createEmojiBox()} + {isHover && } ); }; diff --git a/client/src/components/organisms/ThreadBody/ThreadBody.tsx b/client/src/components/organisms/ThreadBody/ThreadBody.tsx index f165df2..4b586e7 100644 --- a/client/src/components/organisms/ThreadBody/ThreadBody.tsx +++ b/client/src/components/organisms/ThreadBody/ThreadBody.tsx @@ -21,6 +21,10 @@ const InputBoxWrap = styled.div` margin: 1rem; `; +const ReplyTitle = styled.div` + margin-top: 1rem; +`; + const ThreadBody: React.FC = ({ messageId }) => { const ThreadBodyEl = useRef(); const [eventType, setEventType] = useState(ScrollEventType.COMMON); @@ -52,7 +56,9 @@ const ThreadBody: React.FC = ({ messageId }) => { return ( - + + + diff --git a/client/src/components/templates/Body.tsx b/client/src/components/templates/Body.tsx index 936b814..c8a0798 100644 --- a/client/src/components/templates/Body.tsx +++ b/client/src/components/templates/Body.tsx @@ -2,12 +2,27 @@ import React, { useEffect } from 'react'; import styled from 'styled-components'; import { channelModalClose, emojiPickerClose, profileModalClose } from '@store/actions/modal-action'; import { useDispatch } from 'react-redux'; -import { insertMessage, updateThread, createMessageReaction, deleteMessageReaction, joinDM, leaveChatroom } from '@store/actions/chatroom-action'; +import { + insertMessage, + updateThread, + createMessageReaction, + deleteMessageReaction, + joinDM, + leaveChatroom +} from '@store/actions/chatroom-action'; +import { createReplyReaction, deleteReplyReaction, InsertReply } from '@store/actions/thread-action'; import socket from '@socket/socketIO'; import { CREATE_MESSAGE } from '@socket/types/message-types'; import { CREATE_REPLY } from '@socket/types/thread-types'; -import { InsertReply } from '@store/actions/thread-action'; -import { socketMessageReactionState, CREATE_MESSAGE_REACTION, DELETE_MESSAGE_REACTION } from '@socket/types/reaction-types'; + +import { + socketMessageReactionState, + CREATE_MESSAGE_REACTION, + DELETE_MESSAGE_REACTION, + CREATE_REPLY_REACTION, + socketReplyReactionState, + DELETE_REPLY_REACTION +} from '@socket/types/reaction-types'; import { JOIN_DM, LEAVE_CHANNEL } from '@socket/types/chatroom-types'; import { leaveChannel } from '@store/actions/channel-action'; @@ -34,6 +49,12 @@ const Body: React.FC = ({ children }) => { socket.on(DELETE_MESSAGE_REACTION, (reaction: any) => { dispatch(deleteMessageReaction(reaction)); }); + socket.on(CREATE_REPLY_REACTION, (replyReaction: socketReplyReactionState) => { + dispatch(createReplyReaction(replyReaction)); + }); + socket.on(DELETE_REPLY_REACTION, (replyReaction: socketReplyReactionState) => { + dispatch(deleteReplyReaction(replyReaction)); + }); socket.on(JOIN_DM, (directMessage: any) => { dispatch(joinDM({ chatroomId: directMessage.chatroomId })); }); diff --git a/server/src/socket/handler/reply-reaction-handler.ts b/server/src/socket/handler/reply-reaction-handler.ts index c7fd98f..d5deaa0 100644 --- a/server/src/socket/handler/reply-reaction-handler.ts +++ b/server/src/socket/handler/reply-reaction-handler.ts @@ -1,4 +1,5 @@ import ReplyReactionService from '@service/reply-reaction-service'; +import MessageService from '@service/message-service'; import ReplyService from '@service/reply-service'; import eventName from '@constants/event-name'; @@ -6,20 +7,22 @@ const replyHandler = { async createReplyReaction(io, socket, messageReaction) { const req = socket.request; const { userId } = req.user; - const { chatroomId, replyId, title, emoji } = messageReaction; + const { replyId, title, emoji } = messageReaction; const newReplyReaction = await ReplyReactionService.getInstance().createReplyReaction(Number(userId), Number(replyId), title, emoji); const { messageId } = (await ReplyService.getInstance().getReplyInfo(Number(replyId))).message; - io.to(String(chatroomId)).emit(eventName.CREATE_REPLY_REACTION, { ...newReplyReaction, messageId, chatroomId }); + const { chatroomId } = (await MessageService.getInstance().getMessage(messageId)).chatroom; + io.to(String(chatroomId)).emit(eventName.CREATE_REPLY_REACTION, { ...newReplyReaction, messageId }); }, async deleteReplytReaction(io, socket, replyReaction) { const req = socket.request; const { userId } = req.user; - const { replyId, reactionId, chatroomId } = replyReaction; + const { replyId, reactionId } = replyReaction; await ReplyReactionService.getInstance().deleteReplyReaction(Number(userId), Number(replyId), Number(reactionId)); const newReplyReaction = await ReplyReactionService.getInstance().getReplyReaction(Number(replyId), Number(reactionId)); const { messageId } = (await ReplyService.getInstance().getReplyInfo(Number(replyId))).message; - io.to(String(chatroomId)).emit(eventName.DELETE_REPLY_REACTION, { ...newReplyReaction, messageId, chatroomId }); + const { chatroomId } = (await MessageService.getInstance().getMessage(messageId)).chatroom; + io.to(String(chatroomId)).emit(eventName.DELETE_REPLY_REACTION, { ...newReplyReaction, messageId }); } };