From 55fcf57a930df447e0e4dd9e748f9f7f95f3537a Mon Sep 17 00:00:00 2001 From: Mykhailo Malykov Date: Sat, 10 Jul 2021 07:01:55 +0300 Subject: [PATCH] Add draft message feature --- src/chat/components/AddMessage/AddMessage.tsx | 21 ++++++++-- .../ConversationContainer.tsx | 13 ++++-- .../ConversationListContainer.tsx | 10 ++++- .../ConversationsList/ConversationsList.tsx | 5 ++- .../ConversationListItem.tsx | 31 +++++++++++--- src/chat/store/actions/chat-actions.ts | 6 ++- src/chat/store/reducers/chatReducer.ts | 41 ++++++++++++++----- src/chat/store/types/store.ts | 15 +++++++ src/testData/index.ts | 7 +++- 9 files changed, 120 insertions(+), 29 deletions(-) diff --git a/src/chat/components/AddMessage/AddMessage.tsx b/src/chat/components/AddMessage/AddMessage.tsx index 869c63e..9782420 100644 --- a/src/chat/components/AddMessage/AddMessage.tsx +++ b/src/chat/components/AddMessage/AddMessage.tsx @@ -1,16 +1,29 @@ -import React, {useState} from "react"; +import React, {useEffect, useState} from "react"; import {Fab, Grid, TextField} from "@material-ui/core"; import {Send} from '@material-ui/icons'; type Props = { + conversationId: string; + draftMessage: string; addMessage: (content: string) => void; + storeDraftMessage: (conversationId: string, content: string) => void; } -export const AddMessage: React.FC = ({addMessage}) => { - const [message, setMessage] = useState(''); +export const AddMessage: React.FC = ({conversationId, draftMessage, addMessage, storeDraftMessage}) => { + const [message, setMessage] = useState(draftMessage); + + useEffect(() => { + setMessage(draftMessage); + }, [conversationId, draftMessage]); + + const handleChange = (content: string) => { + setMessage(content); + storeDraftMessage(conversationId, content); + }; const handleClick = () => { addMessage(message); + storeDraftMessage(conversationId, ''); setMessage(''); }; @@ -23,7 +36,7 @@ export const AddMessage: React.FC = ({addMessage}) => { multiline maxRows={5} value={message} - onChange={e => setMessage(e.target.value)} + onChange={e => handleChange(e.target.value)} /> diff --git a/src/chat/components/ConversationContainer/ConversationContainer.tsx b/src/chat/components/ConversationContainer/ConversationContainer.tsx index da7cadf..bf61866 100644 --- a/src/chat/components/ConversationContainer/ConversationContainer.tsx +++ b/src/chat/components/ConversationContainer/ConversationContainer.tsx @@ -14,8 +14,11 @@ const useConversationContainerStyles = makeStyles(() => ({ export const ConversationContainer: React.FC = () => { const containerClasses = useConversationContainerStyles(); - const {selectedConversation} = useTypedSelector(state => state.chat); - const {addTextMessage} = useChatActions(); + const {selectedConversation, draftMessages} = useTypedSelector(state => state.chat); + const messageContent = selectedConversation ? + (draftMessages[selectedConversation.id] ?? '') : + ''; + const {addTextMessage, storeDraftTextMessage} = useChatActions(); if (!selectedConversation) { return ( @@ -32,7 +35,11 @@ export const ConversationContainer: React.FC = () => { return ( - + ); }; diff --git a/src/chat/components/ConversationListContainer/ConversationListContainer.tsx b/src/chat/components/ConversationListContainer/ConversationListContainer.tsx index 90607be..b9c426e 100644 --- a/src/chat/components/ConversationListContainer/ConversationListContainer.tsx +++ b/src/chat/components/ConversationListContainer/ConversationListContainer.tsx @@ -7,7 +7,13 @@ import {useChatActions} from "../../store/hooks/useChatActions"; import {SearchTextField} from "../SearchTextField/SearchTextField"; export const ConversationListContainer: React.FC = () => { - const {conversations, filteredConversations, conversationsLoadingError, isConversationsLoading} = useTypedSelector(state => state.chat); + const { + conversations, + filteredConversations, + conversationsLoadingError, + isConversationsLoading, + draftMessages, + } = useTypedSelector(state => state.chat); const {filterConversations} = useChatActions(); const filterConversationsDebounced = debounce(filterConversations, 500); @@ -27,7 +33,7 @@ export const ConversationListContainer: React.FC = () => { )} {conversations?.length > 0 && ( - + )} ); diff --git a/src/chat/components/ConversationsList/ConversationsList.tsx b/src/chat/components/ConversationsList/ConversationsList.tsx index 2ab817f..7228b59 100644 --- a/src/chat/components/ConversationsList/ConversationsList.tsx +++ b/src/chat/components/ConversationsList/ConversationsList.tsx @@ -3,12 +3,14 @@ import {Grid} from "@material-ui/core"; import {ConversationListItem} from "./ConversationsListItem/ConversationListItem"; import {Conversation} from "../../types/conversation"; import {useChatActions} from "../../store/hooks/useChatActions"; +import {DraftMessagesMap} from "../../store/types/store"; type Props = { conversations: Conversation[]; + draftMessages: DraftMessagesMap; } -export const ConversationsList: React.FC = ({conversations}) => { +export const ConversationsList: React.FC = ({conversations, draftMessages}) => { const {selectConversation} = useChatActions(); if (conversations.length === 0) { @@ -25,6 +27,7 @@ export const ConversationsList: React.FC = ({conversations}) => { ) } diff --git a/src/chat/components/ConversationsList/ConversationsListItem/ConversationListItem.tsx b/src/chat/components/ConversationsList/ConversationsListItem/ConversationListItem.tsx index aaeaef0..e177c58 100644 --- a/src/chat/components/ConversationsList/ConversationsListItem/ConversationListItem.tsx +++ b/src/chat/components/ConversationsList/ConversationsListItem/ConversationListItem.tsx @@ -1,23 +1,42 @@ import React from "react"; -import {Avatar, ListItem, ListItemIcon, ListItemText} from "@material-ui/core"; +import {Avatar, ListItem, ListItemIcon, ListItemText, Typography} from "@material-ui/core"; import {Conversation} from "../../../types/conversation"; type Props = { conversation: Conversation; selectConversation: Function; + draftMessage: string; }; -export const ConversationListItem: React.FC = ({conversation, selectConversation}) => { - const handleClick = () => selectConversation(conversation.id); +export const ConversationListItem: React.FC = ({conversation, selectConversation, draftMessage}) => { + const {id, lastMessage, secondUser} = conversation; + const handleClick = () => selectConversation(id); + const secondaryContent = draftMessage || lastMessage.content; + const secondary = draftMessage ? + ( + + + Draft: + + {secondaryContent} + + ) : + secondaryContent; return ( - + - + If no avatar provided - + ); }; diff --git a/src/chat/store/actions/chat-actions.ts b/src/chat/store/actions/chat-actions.ts index 92fc2fa..f905db3 100644 --- a/src/chat/store/actions/chat-actions.ts +++ b/src/chat/store/actions/chat-actions.ts @@ -6,7 +6,6 @@ import { SendMessageAction } from "../types/store"; import {MessageType} from "../../types/message"; -import {Conversation} from "../../types/conversation"; import {Dispatch} from "redux"; import {conversations} from "../../../testData"; @@ -37,6 +36,11 @@ export const addTextMessage = (content: string, conversationId: string, userId: } }); +export const storeDraftTextMessage = (conversationId: string, content: string) => ({ + type: ChatActionType.STORE_DRAFT_MESSAGE, + payload: {conversationId, content}, +}); + export const selectConversation = (conversationId: string): SelectConversationAction => ({ type: ChatActionType.SELECT_CONVERSATION, payload: conversationId diff --git a/src/chat/store/reducers/chatReducer.ts b/src/chat/store/reducers/chatReducer.ts index db45d55..14921ab 100644 --- a/src/chat/store/reducers/chatReducer.ts +++ b/src/chat/store/reducers/chatReducer.ts @@ -7,6 +7,7 @@ const initialState: ChatState = { selectedConversation: null, isConversationsLoading: false, conversationsLoadingError: '', + draftMessages: {}, }; export const chatReducer = (state = initialState, action: ChatAction): ChatState => { @@ -22,24 +23,32 @@ export const chatReducer = (state = initialState, action: ChatAction): ChatState }; case ChatActionType.FETCH_CONVERSATIONS_ERROR: return {...state, conversationsLoadingError: action.payload, isConversationsLoading: false}; - case ChatActionType.SEND_MESSAGE: + case ChatActionType.SEND_MESSAGE: { + const {conversations, filteredConversations, selectedConversation} = state; + const messages = [action.payload, ...selectedConversation?.messages ?? []]; + const [lastMessage] = messages; const updatedConversation = { ...state.selectedConversation, - messages: [action.payload, ...state.selectedConversation?.messages ?? []], + messages, + lastMessage, } as Conversation; - const conversations = [...state.conversations]; - conversations.splice( - state.conversations.findIndex(c => c.id === state.selectedConversation?.id), - 1, - updatedConversation - ); - return { ...state, - conversations, + conversations: replaceConversation(conversations, (selectedConversation as Conversation).id, updatedConversation), + filteredConversations: replaceConversation(filteredConversations, (selectedConversation as Conversation).id, updatedConversation), selectedConversation: updatedConversation }; + } + case ChatActionType.STORE_DRAFT_MESSAGE: + const {conversationId, content} = action.payload; + const {[conversationId]: previousContent, ...restDraftMessages} = state.draftMessages; + const draftMessages = { + ...restDraftMessages, + [conversationId]: content, + }; + + return {...state, draftMessages}; case ChatActionType.SELECT_CONVERSATION: const selectedConversation = state.conversations.find(c => c.id === action.payload); @@ -56,4 +65,16 @@ export const chatReducer = (state = initialState, action: ChatAction): ChatState } }; +function replaceConversation(conversations: Conversation[], conversationId: string, updatedConversation: Conversation): Conversation[] { + const updatedConversations = [...conversations]; + + updatedConversations.splice( + conversations.findIndex(c => c.id === conversationId), + 1, + updatedConversation + ); + + return updatedConversations; +} + export type RootState = ReturnType diff --git a/src/chat/store/types/store.ts b/src/chat/store/types/store.ts index 1eca9be..539d7ea 100644 --- a/src/chat/store/types/store.ts +++ b/src/chat/store/types/store.ts @@ -1,12 +1,17 @@ import {Conversation} from "../../types/conversation"; import {Message} from "../../types/message"; +export interface DraftMessagesMap { + [conversationId: string]: string; +} + export interface ChatState { conversations: Conversation[]; filteredConversations: Conversation[]; selectedConversation: Conversation | null; isConversationsLoading: boolean; conversationsLoadingError: string; + draftMessages: DraftMessagesMap; } export enum ChatActionType { @@ -14,6 +19,7 @@ export enum ChatActionType { FETCH_CONVERSATIONS_SUCCESSFUL = 'FETCH_CONVERSATIONS_SUCCESSFUL', FETCH_CONVERSATIONS_ERROR = 'FETCH_CONVERSATIONS_ERROR', SEND_MESSAGE = 'SEND_MESSAGE', + STORE_DRAFT_MESSAGE = 'STORE_DRAFT_MESSAGE', SELECT_CONVERSATION = 'SELECT_CONVERSATION', FILTER_CONVERSATIONS = 'FILTER_CONVERSATIONS' } @@ -37,6 +43,14 @@ export interface SendMessageAction { payload: Message; } +export interface StoreDraftMessageAction { + type: ChatActionType.STORE_DRAFT_MESSAGE; + payload: { + conversationId: string; + content: string; + }; +} + export interface SelectConversationAction { type: ChatActionType.SELECT_CONVERSATION; payload: string; @@ -52,5 +66,6 @@ export type ChatAction = FetchConversationsSuccessfulAction | FetchConversationsErrorAction | SendMessageAction | + StoreDraftMessageAction | SelectConversationAction | FilterConversationMessageAction; diff --git a/src/testData/index.ts b/src/testData/index.ts index 3f4e795..ac75837 100644 --- a/src/testData/index.ts +++ b/src/testData/index.ts @@ -48,12 +48,15 @@ export const messages: Message[] = [ {id: '33', conversationId: '2', content: 'At home', messageType: MessageType.TEXT, userId: '1'}, ].reverse(); +const [lastMessage1] = messages.filter(({conversationId}) => conversationId === '1'); +const [lastMessage2] = messages.filter(({conversationId}) => conversationId === '2'); + export const conversations: Conversation[] = [ { id: '1', userId: '1', secondUserId: '2', - lastMessage: messages.filter(({conversationId}) => conversationId === '2').pop() as Message, + lastMessage: lastMessage1, user: users[0], secondUser: users[1], messages: messages.filter(({conversationId}) => conversationId === '1') @@ -61,7 +64,7 @@ export const conversations: Conversation[] = [ id: '2', userId: '1', secondUserId: '3', - lastMessage: messages.filter(({conversationId}) => conversationId === '2').pop() as Message, + lastMessage: lastMessage2, user: users[0], secondUser: users[2], messages: messages.filter(({conversationId}) => conversationId === '2')