diff --git a/app/javascript/widget/store/modules/message.js b/app/javascript/widget/store/modules/message.js deleted file mode 100644 index b11c5cdd4..000000000 --- a/app/javascript/widget/store/modules/message.js +++ /dev/null @@ -1,60 +0,0 @@ -import MessageAPI from '../../api/message'; -import { refreshActionCableConnector } from '../../helpers/actionCable'; - -const state = { - uiFlags: { - isUpdating: false, - }, -}; - -export const getters = { - getUIFlags: $state => $state.uiFlags, -}; - -export const actions = { - update: async ( - { commit, dispatch }, - { email, messageId, submittedValues } - ) => { - commit('toggleUpdateStatus', true); - try { - const { - data: { contact: { pubsub_token: pubsubToken } = {} }, - } = await MessageAPI.update({ - email, - messageId, - values: submittedValues, - }); - commit( - 'conversation/updateMessage', - { - id: messageId, - content_attributes: { - submitted_email: email, - submitted_values: email ? null : submittedValues, - }, - }, - { root: true } - ); - dispatch('contacts/get', {}, { root: true }); - refreshActionCableConnector(pubsubToken); - } catch (error) { - // Ignore error - } - commit('toggleUpdateStatus', false); - }, -}; - -export const mutations = { - toggleUpdateStatus($state, status) { - $state.uiFlags.isUpdating = status; - }, -}; - -export default { - namespaced: true, - state, - getters, - actions, - mutations, -}; diff --git a/app/javascript/widget/store/modules/message/actions.js b/app/javascript/widget/store/modules/message/actions.js new file mode 100644 index 000000000..d25eccc22 --- /dev/null +++ b/app/javascript/widget/store/modules/message/actions.js @@ -0,0 +1,157 @@ +import MessagePublicAPI from 'widget/api/messagePublic'; +import { refreshActionCableConnector } from 'widget/helpers/actionCable'; +import { + createTemporaryMessage, + createTemporaryAttachmentMessage, + createAttachmentParams, +} from './helpers'; + +export const actions = { + addOrUpdate: async ({ commit, getters }, message) => { + const { + conversation_id: conversationId, + id: messageId, + echo_id: echoId, + } = message; + + const messageIdInStore = echoId || messageId; + const doesMessageExist = getters.messageById(messageIdInStore); + + if (doesMessageExist) { + commit( + 'conversationV2/removeMessageIdFromConversation', + { conversationId, messageId: echoId }, + { root: true } + ); + commit('removeMessageEntry', echoId); + commit('removeMessageId', echoId); + } + const messages = [message]; + commit('addMessagesEntry', { conversationId, messages }); + commit('addMessageIds', { messages }); + commit( + 'conversationV2/appendMessageIdsToConversation', + { conversationId, messages }, + { root: true } + ); + }, + sendMessage: async ({ commit, dispatch }, params) => { + const { content, conversationId } = params; + try { + commit( + 'conversationV2/setConversationUIFlag', + { uiFlags: { isCreating: true }, conversationId }, + { root: true } + ); + + const message = createTemporaryMessage({ content }); + const { id: echoId } = message; + const messages = [message]; + commit('addMessagesEntry', { messages }); + commit('addMessageIds', { messages }); + commit( + 'conversationV2/appendMessageIdsToConversation', + { conversationId, messages }, + { root: true } + ); + const { data: newMessage } = await MessagePublicAPI.create( + conversationId, + content, + echoId + ); + + dispatch('addOrUpdate', { + ...newMessage, + echo_id: echoId, + }); + } catch (error) { + throw new Error(error); + } finally { + commit( + 'conversationV2/setConversationUIFlag', + { uiFlags: { isCreating: false }, conversationId }, + { root: true } + ); + } + }, + + sendAttachment: async ({ commit, dispatch }, params) => { + const { + attachment: { thumbUrl, fileType }, + conversationId, + } = params; + try { + commit( + 'conversationV2/setConversationUIFlag', + { uiFlags: { isCreating: true }, conversationId }, + { root: true } + ); + + const tempMessage = createTemporaryAttachmentMessage({ + thumbUrl, + fileType, + }); + + const { id: echoId } = tempMessage; + const messages = [tempMessage]; + const attachmentParams = createAttachmentParams(params); + + commit('addMessagesEntry', { conversationId, messages }); + commit('addMessageIds', { conversationId, messages }); + commit( + 'conversationV2/appendMessageIdsToConversation', + { conversationId, messages }, + { root: true } + ); + + const { data } = await MessagePublicAPI.createAttachment( + conversationId, + attachmentParams + ); + dispatch('addOrUpdate', { ...data, echo_id: echoId }); + } catch (error) { + throw new Error(error); + } finally { + commit( + 'conversationV2/setConversationUIFlag', + { uiFlags: { isCreating: false }, conversationId }, + { root: true } + ); + } + }, + + updateMessage: async ( + { commit, dispatch }, + { email, messageId, submittedValues } + ) => { + try { + commit('setMessageUIFlag', { + messageId, + uiFlags: { isUpdating: true }, + }); + const { + data: { contact: { pubsub_token: pubsubToken } = {} }, + } = await MessagePublicAPI.update({ + email, + messageId, + values: submittedValues, + }); + commit('updateMessageEntry', { + id: messageId, + content_attributes: { + submitted_email: email, + submitted_values: email ? null : submittedValues, + }, + }); + dispatch('contacts/get', {}, { root: true }); + refreshActionCableConnector(pubsubToken); + } catch (error) { + throw new Error(error); + } finally { + commit('setMessageUIFlag', { + messageId, + uiFlags: { isUpdating: false }, + }); + } + }, +}; diff --git a/app/javascript/widget/store/modules/message/getters.js b/app/javascript/widget/store/modules/message/getters.js new file mode 100644 index 000000000..5ef50d67d --- /dev/null +++ b/app/javascript/widget/store/modules/message/getters.js @@ -0,0 +1,7 @@ +export const getters = { + uIFlags: $state => $state.uiFlags, + messageById: _state => messageId => { + const message = _state.messages.byId[messageId]; + return message; + }, +}; diff --git a/app/javascript/widget/store/modules/message/helpers.js b/app/javascript/widget/store/modules/message/helpers.js new file mode 100644 index 000000000..4cb4dd97a --- /dev/null +++ b/app/javascript/widget/store/modules/message/helpers.js @@ -0,0 +1,44 @@ +import { MESSAGE_TYPE } from 'widget/helpers/constants'; +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; +}; + +export const createAttachmentParams = ({ attachment }) => { + const { referrerURL = '' } = window; + const timestamp = new Date().toString(); + const { file } = attachment; + + const formData = new FormData(); + formData.append('message[attachments][]', file, file.name); + formData.append('message[referer_url]', referrerURL); + formData.append('message[timestamp]', timestamp); + return formData; +}; diff --git a/app/javascript/widget/store/modules/message/index.js b/app/javascript/widget/store/modules/message/index.js new file mode 100644 index 000000000..8565e4093 --- /dev/null +++ b/app/javascript/widget/store/modules/message/index.js @@ -0,0 +1,23 @@ +import { getters } from './getters'; +import { actions } from './actions'; +import { mutations } from './mutations'; + +const state = { + messages: { + byId: {}, + allIds: [], + uiFlags: { + byId: { + // 1: { isCreating: false, isPending: false, isDeleting: false, isUpdating: false }, + }, + }, + }, +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations, +}; diff --git a/app/javascript/widget/store/modules/message/mutations.js b/app/javascript/widget/store/modules/message/mutations.js new file mode 100644 index 000000000..7b98e68ee --- /dev/null +++ b/app/javascript/widget/store/modules/message/mutations.js @@ -0,0 +1,51 @@ +import Vue from 'vue'; + +export const mutations = { + addMessagesEntry($state, { messages = [] }) { + messages.forEach(message => { + Vue.set($state.messages.byId, message.id, message); + }); + }, + + addMessageIds($state, { messages }) { + const messageIds = messages.map(message => message.id); + const allIds = $state.messages.allIds; + const newIds = [allIds, messageIds]; + const uniqIds = Array.from(new Set(newIds)); + + Vue.set($state.messages, 'allIds', uniqIds); + }, + + updateMessageEntry($state, message) { + const messageId = message.id; + if (!messageId) return; + + const messageById = $state.messages.byId[messageId]; + if (!messageById) return; + if (messageId !== message.id) return; + + Vue.set($state.messages.byId, messageId, { ...message }); + }, + + removeMessageEntry($state, messageId) { + if (!messageId) return; + + Vue.delete($state.messages.byId, messageId); + }, + + removeMessageId($state, messageId) { + if (!messageId) return; + + $state.messages.allIds = $state.messages.allIds.filter( + id => id !== messageId + ); + }, + + setMessageUIFlag($state, { messageId, uiFlags }) { + const flags = $state.messages.uiFlags.byId[messageId]; + $state.messages.uiFlags.byId[messageId] = { + ...flags, + ...uiFlags, + }; + }, +};