Skip to content
Open
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
21 changes: 17 additions & 4 deletions src/chat/components/AddMessage/AddMessage.tsx
Original file line number Diff line number Diff line change
@@ -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<Props> = ({addMessage}) => {
const [message, setMessage] = useState('');
export const AddMessage: React.FC<Props> = ({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('');
};

Expand All @@ -23,7 +36,7 @@ export const AddMessage: React.FC<Props> = ({addMessage}) => {
multiline
maxRows={5}
value={message}
onChange={e => setMessage(e.target.value)}
onChange={e => handleChange(e.target.value)}
/>
</Grid>
<Grid item xs={1}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -32,7 +35,11 @@ export const ConversationContainer: React.FC = () => {
return (
<Grid container direction="column" item xs={8} className={containerClasses.root}>
<MessagesList selectedConversation={selectedConversation}/>
<AddMessage addMessage={addMessageHandler}/>
<AddMessage
conversationId={selectedConversation.id}
draftMessage={messageContent}
addMessage={addMessageHandler}
storeDraftMessage={storeDraftTextMessage}/>
</Grid>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -27,7 +33,7 @@ export const ConversationListContainer: React.FC = () => {
</Grid>
)}
{conversations?.length > 0 && (
<ConversationsList conversations={filteredConversations}/>
<ConversationsList conversations={filteredConversations} draftMessages={draftMessages}/>
)}
</Grid>
);
Expand Down
5 changes: 4 additions & 1 deletion src/chat/components/ConversationsList/ConversationsList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<Props> = ({conversations}) => {
export const ConversationsList: React.FC<Props> = ({conversations, draftMessages}) => {
const {selectConversation} = useChatActions();

if (conversations.length === 0) {
Expand All @@ -25,6 +27,7 @@ export const ConversationsList: React.FC<Props> = ({conversations}) => {
<ConversationListItem
key={conversation.id}
conversation={conversation}
draftMessage={draftMessages[conversation.id]}
selectConversation={selectConversation}/>)
}
</Grid>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Props> = ({conversation, selectConversation}) => {
const handleClick = () => selectConversation(conversation.id);
export const ConversationListItem: React.FC<Props> = ({conversation, selectConversation, draftMessage}) => {
const {id, lastMessage, secondUser} = conversation;
const handleClick = () => selectConversation(id);
const secondaryContent = draftMessage || lastMessage.content;
const secondary = draftMessage ?
(
<React.Fragment>
<Typography
component="span"
color="error"
>
Draft:
</Typography>
{secondaryContent}
</React.Fragment>
) :
secondaryContent;

return (
<ListItem button key={conversation.id} onClick={handleClick}>
<ListItem button key={id} onClick={handleClick}>
<ListItemIcon>
<Avatar alt={conversation.secondUser.name} src="https://material-ui.com/static/images/avatar/1.jpg">
<Avatar alt={secondUser.name} src="https://material-ui.com/static/images/avatar/1.jpg">
If no avatar provided
</Avatar>
</ListItemIcon>
<ListItemText primary={conversation.secondUser.name} secondary={conversation.lastMessage.content}/>
<ListItemText
primary={secondUser.name}
secondary={secondary}
secondaryTypographyProps={{noWrap: true}}/>
</ListItem>
);
};
6 changes: 5 additions & 1 deletion src/chat/store/actions/chat-actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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
Expand Down
41 changes: 31 additions & 10 deletions src/chat/store/reducers/chatReducer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const initialState: ChatState = {
selectedConversation: null,
isConversationsLoading: false,
conversationsLoadingError: '',
draftMessages: {},
};

export const chatReducer = (state = initialState, action: ChatAction): ChatState => {
Expand All @@ -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);

Expand All @@ -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<typeof chatReducer>
15 changes: 15 additions & 0 deletions src/chat/store/types/store.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
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 {
FETCH_CONVERSATIONS = 'FETCH_CONVERSATIONS',
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'
}
Expand All @@ -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;
Expand All @@ -52,5 +66,6 @@ export type ChatAction =
FetchConversationsSuccessfulAction |
FetchConversationsErrorAction |
SendMessageAction |
StoreDraftMessageAction |
SelectConversationAction |
FilterConversationMessageAction;
7 changes: 5 additions & 2 deletions src/testData/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,20 +48,23 @@ 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')
},{
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')
Expand Down