Adds actions to support multiple conversation
This commit is contained in:
parent
fa5d9e8cd7
commit
759d1ab1c6
6 changed files with 289 additions and 0 deletions
|
@ -29,6 +29,10 @@ const getConversationAPI = async () => {
|
||||||
return API.get(`/api/v1/widget/conversations${window.location.search}`);
|
return API.get(`/api/v1/widget/conversations${window.location.search}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getConversationsAPI = async () => {
|
||||||
|
return API.get(`/api/v1/widget/conversations`);
|
||||||
|
};
|
||||||
|
|
||||||
const toggleTyping = async ({ typingStatus }) => {
|
const toggleTyping = async ({ typingStatus }) => {
|
||||||
return API.post(
|
return API.post(
|
||||||
`/api/v1/widget/conversations/toggle_typing${window.location.search}`,
|
`/api/v1/widget/conversations/toggle_typing${window.location.search}`,
|
||||||
|
@ -58,4 +62,5 @@ export {
|
||||||
toggleTyping,
|
toggleTyping,
|
||||||
setUserLastSeenAt,
|
setUserLastSeenAt,
|
||||||
sendEmailTranscript,
|
sendEmailTranscript,
|
||||||
|
getConversationsAPI,
|
||||||
};
|
};
|
||||||
|
|
141
app/javascript/widget/store/modules/conversation_new/actions.js
Normal file
141
app/javascript/widget/store/modules/conversation_new/actions.js
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
import {
|
||||||
|
createConversationAPI,
|
||||||
|
sendMessageAPI,
|
||||||
|
sendAttachmentAPI,
|
||||||
|
// getMessagesAPI,
|
||||||
|
// toggleTyping,
|
||||||
|
// setUserLastSeenAt,
|
||||||
|
getConversationAPI,
|
||||||
|
getConversationsAPI,
|
||||||
|
} from 'widget/api/conversation';
|
||||||
|
import { refreshActionCableConnector } from '../../../helpers/actionCable';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createTemporaryMessage,
|
||||||
|
createTemporaryAttachmentMessage,
|
||||||
|
} from './helpers';
|
||||||
|
|
||||||
|
// Get activeConversation and pass it down to each action call, to
|
||||||
|
// target the right converdation
|
||||||
|
export const actions = {
|
||||||
|
fetchAllConversations: async ({ commit }) => {
|
||||||
|
try {
|
||||||
|
commit('setUIFlag', { isFetching: true });
|
||||||
|
const { data } = await getConversationsAPI();
|
||||||
|
data.forEach(conversation => {
|
||||||
|
const { id: conversationId, messages } = conversation;
|
||||||
|
commit('addConversationEntry', conversation);
|
||||||
|
commit('addConversationId', conversation.id);
|
||||||
|
commit('addMessagesEntry', { conversationId, messages });
|
||||||
|
commit('addMessageIds', { conversationId, messages });
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit('setUIFlag', { isFetching: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
fetchConversationById: async ({ commit }, params) => {
|
||||||
|
const { conversationId } = params;
|
||||||
|
try {
|
||||||
|
commit('setConversationUIFlag', { isFetching: true });
|
||||||
|
const { data } = await getConversationAPI(conversationId);
|
||||||
|
|
||||||
|
const { messages } = data;
|
||||||
|
commit('updateConversationEntry', data);
|
||||||
|
commit('addMessagesEntry', { conversationId, messages });
|
||||||
|
commit('addMessageIds', { conversationId, messages });
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit('setConversationUIFlag', { isFetching: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createConversation: async ({ commit }, params) => {
|
||||||
|
commit('setConversationUIFlag', { isCreating: true });
|
||||||
|
try {
|
||||||
|
const { data } = await createConversationAPI(params);
|
||||||
|
const { id: conversationId, messages } = data;
|
||||||
|
|
||||||
|
commit('addConversationEntry', data);
|
||||||
|
commit('addConversationId', conversationId);
|
||||||
|
commit('addMessagesEntry', { conversationId, messages });
|
||||||
|
commit('addMessageIds', { conversationId, messages });
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit('setConversationUIFlag', { isCreating: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sendMessage: async ({ commit }, params) => {
|
||||||
|
const { content, conversationId } = params;
|
||||||
|
const message = createTemporaryMessage({ content });
|
||||||
|
const messages = [message];
|
||||||
|
commit('addMessagesEntry', { conversationId, messages });
|
||||||
|
commit('addMessageIds', { conversationId, messages });
|
||||||
|
await sendMessageAPI(content, conversationId);
|
||||||
|
},
|
||||||
|
sendAttachment: async ({ commit }, params) => {
|
||||||
|
const {
|
||||||
|
attachment: { thumbUrl, fileType },
|
||||||
|
conversationId,
|
||||||
|
} = params;
|
||||||
|
const message = createTemporaryAttachmentMessage({ thumbUrl, fileType });
|
||||||
|
const messages = [message];
|
||||||
|
commit('addMessagesEntry', { conversationId, messages });
|
||||||
|
commit('addMessageIds', { conversationId, messages });
|
||||||
|
try {
|
||||||
|
const { data } = await sendAttachmentAPI(params);
|
||||||
|
commit('updateAttachmentMessageStatus', {
|
||||||
|
message: data,
|
||||||
|
tempId: tempMessage.id,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
// Show error
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// fetchOldConversations: async ({ commit }, { before } = {}) => {
|
||||||
|
// try {
|
||||||
|
// commit('setConversationListLoading', true);
|
||||||
|
// const { data } = await getMessagesAPI({ before });
|
||||||
|
// const formattedMessages = getNonDeletedMessages({ messages: data });
|
||||||
|
// commit('setMessagesInConversation', formattedMessages);
|
||||||
|
// commit('setConversationListLoading', false);
|
||||||
|
// } catch (error) {
|
||||||
|
// commit('setConversationListLoading', false);
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// clearConversations: ({ commit }) => {
|
||||||
|
// commit('clearConversations');
|
||||||
|
// },
|
||||||
|
// addOrUpdateMessage: async ({ commit }, data) => {
|
||||||
|
// const { id, content_attributes } = data;
|
||||||
|
// if (content_attributes && content_attributes.deleted) {
|
||||||
|
// commit('deleteMessage', id);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// commit('pushMessageToConversation', data);
|
||||||
|
// },
|
||||||
|
// toggleAgentTyping({ commit }, data) {
|
||||||
|
// commit('toggleAgentTypingStatus', data);
|
||||||
|
// },
|
||||||
|
// toggleUserTyping: async (_, data) => {
|
||||||
|
// try {
|
||||||
|
// await toggleTyping(data);
|
||||||
|
// } catch (error) {
|
||||||
|
// // IgnoreError
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// setUserLastSeen: async ({ commit, getters: appGetters }) => {
|
||||||
|
// if (!appGetters.getConversationSize) {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// const lastSeen = Date.now() / 1000;
|
||||||
|
// try {
|
||||||
|
// commit('setMetaUserLastSeenAt', lastSeen);
|
||||||
|
// await setUserLastSeenAt({ lastSeen });
|
||||||
|
// } catch (error) {
|
||||||
|
// // IgnoreError
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
};
|
|
@ -0,0 +1,56 @@
|
||||||
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
|
import groupBy from 'lodash.groupby';
|
||||||
|
import { groupConversationBySender } from './helpers';
|
||||||
|
import { formatUnixDate } from 'shared/helpers/DateHelper';
|
||||||
|
|
||||||
|
export const getters = {
|
||||||
|
getAllMessagesLoaded: _state => _state.uiFlags.allMessagesLoaded,
|
||||||
|
getIsCreating: _state => _state.uiFlags.isCreating,
|
||||||
|
getIsAgentTyping: _state => _state.uiFlags.isAgentTyping,
|
||||||
|
getConversation: _state => _state.conversations,
|
||||||
|
getConversationSize: _state => Object.keys(_state.conversations).length,
|
||||||
|
getEarliestMessage: _state => {
|
||||||
|
const conversation = Object.values(_state.conversations);
|
||||||
|
if (conversation.length) {
|
||||||
|
return conversation[0];
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
getGroupedConversation: _state => {
|
||||||
|
const conversationGroupedByDate = groupBy(
|
||||||
|
Object.values(_state.conversations),
|
||||||
|
message => formatUnixDate(message.created_at)
|
||||||
|
);
|
||||||
|
return Object.keys(conversationGroupedByDate).map(date => ({
|
||||||
|
date,
|
||||||
|
messages: groupConversationBySender(conversationGroupedByDate[date]),
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
getIsFetchingList: _state => _state.uiFlags.isFetchingList,
|
||||||
|
getMessageCount: _state => {
|
||||||
|
return Object.values(_state.conversations).length;
|
||||||
|
},
|
||||||
|
getUnreadMessageCount: _state => {
|
||||||
|
const { userLastSeenAt } = _state.meta;
|
||||||
|
const count = Object.values(_state.conversations).filter(chat => {
|
||||||
|
const { created_at: createdAt, message_type: messageType } = chat;
|
||||||
|
const isOutGoing = messageType === MESSAGE_TYPE.OUTGOING;
|
||||||
|
const hasNotSeen = userLastSeenAt
|
||||||
|
? createdAt * 1000 > userLastSeenAt * 1000
|
||||||
|
: true;
|
||||||
|
return hasNotSeen && isOutGoing;
|
||||||
|
}).length;
|
||||||
|
return count;
|
||||||
|
},
|
||||||
|
getUnreadTextMessages: (_state, _getters) => {
|
||||||
|
const unreadCount = _getters.getUnreadMessageCount;
|
||||||
|
const allMessages = [...Object.values(_state.conversations)];
|
||||||
|
const unreadAgentMessages = allMessages.filter(message => {
|
||||||
|
const { message_type: messageType } = message;
|
||||||
|
return messageType === MESSAGE_TYPE.OUTGOING;
|
||||||
|
});
|
||||||
|
const maxUnreadCount = Math.min(unreadCount, 3);
|
||||||
|
const allUnreadMessages = unreadAgentMessages.splice(-maxUnreadCount);
|
||||||
|
return allUnreadMessages;
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,71 @@
|
||||||
|
import { MESSAGE_TYPE } from 'widget/helpers/constants';
|
||||||
|
import { isASubmittedFormMessage } from 'shared/helpers/MessageTypeHelper';
|
||||||
|
|
||||||
|
import getUuid from '../../../helpers/uuid';
|
||||||
|
export const createTemporaryMessage = ({ attachments, content }) => {
|
||||||
|
const timestamp = new Date().getTime() / 1000;
|
||||||
|
return {
|
||||||
|
id: getUuid(),
|
||||||
|
content,
|
||||||
|
attachments,
|
||||||
|
status: 'in_progress',
|
||||||
|
created_at: timestamp,
|
||||||
|
message_type: MESSAGE_TYPE.INCOMING,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
export const createTemporaryAttachmentMessage = ({
|
||||||
|
thumbUrl,
|
||||||
|
fileType,
|
||||||
|
content,
|
||||||
|
}) => {
|
||||||
|
const attachment = {
|
||||||
|
thumb_url: thumbUrl,
|
||||||
|
data_url: thumbUrl,
|
||||||
|
file_type: fileType,
|
||||||
|
status: 'in_progress',
|
||||||
|
};
|
||||||
|
const message = createTemporaryMessage({
|
||||||
|
attachments: [attachment],
|
||||||
|
content,
|
||||||
|
});
|
||||||
|
return message;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getSenderName = message => (message.sender ? message.sender.name : '');
|
||||||
|
|
||||||
|
const shouldShowAvatar = (message, nextMessage) => {
|
||||||
|
const currentSender = getSenderName(message);
|
||||||
|
const nextSender = getSenderName(nextMessage);
|
||||||
|
|
||||||
|
return (
|
||||||
|
currentSender !== nextSender ||
|
||||||
|
message.message_type !== nextMessage.message_type ||
|
||||||
|
isASubmittedFormMessage(nextMessage)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const groupConversationBySender = conversationsForADate =>
|
||||||
|
conversationsForADate.map((message, index) => {
|
||||||
|
let showAvatar = false;
|
||||||
|
const isLastMessage = index === conversationsForADate.length - 1;
|
||||||
|
if (isASubmittedFormMessage(message)) {
|
||||||
|
showAvatar = false;
|
||||||
|
} else if (isLastMessage) {
|
||||||
|
showAvatar = true;
|
||||||
|
} else {
|
||||||
|
const nextMessage = conversationsForADate[index + 1];
|
||||||
|
showAvatar = shouldShowAvatar(message, nextMessage);
|
||||||
|
}
|
||||||
|
return { showAvatar, ...message };
|
||||||
|
});
|
||||||
|
|
||||||
|
export const findUndeliveredMessage = (messageInbox, { content }) =>
|
||||||
|
Object.values(messageInbox).filter(
|
||||||
|
message => message.content === content && message.status === 'in_progress'
|
||||||
|
);
|
||||||
|
|
||||||
|
export const getNonDeletedMessages = ({ messages }) => {
|
||||||
|
return messages.filter(
|
||||||
|
item => !(item.content_attributes && item.content_attributes.deleted)
|
||||||
|
);
|
||||||
|
};
|
|
@ -24,6 +24,7 @@ const state = {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
allConversationsLoaded: false,
|
allConversationsLoaded: false,
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
|
isCreating: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -48,6 +48,21 @@ export const mutations = {
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
addMessagesEntry($state, { conversationId, messages = [] }) {
|
||||||
|
if (!conversationId) return;
|
||||||
|
|
||||||
|
const allMessages = $state.messages;
|
||||||
|
const newMessages = messages.reduce(
|
||||||
|
(obj, message) => ({
|
||||||
|
...obj,
|
||||||
|
[message.id]: message,
|
||||||
|
}),
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
const updatedMessages = { ...allMessages, ...newMessages };
|
||||||
|
Vue.set($state.messages, 'byId', updatedMessages);
|
||||||
|
},
|
||||||
|
|
||||||
addMessageIds($state, { conversationId, messages }) {
|
addMessageIds($state, { conversationId, messages }) {
|
||||||
const conversationById = $state.conversations.byId[conversationId];
|
const conversationById = $state.conversations.byId[conversationId];
|
||||||
if (!conversationById) return;
|
if (!conversationById) return;
|
||||||
|
|
Loading…
Reference in a new issue