From cd3778d0a645e1de1b735982d1368f40f047f132 Mon Sep 17 00:00:00 2001 From: Diego Sampaio Date: Thu, 4 Aug 2016 10:21:58 -0300 Subject: [PATCH] Livechat CRM integration improvements (#3912) - Add ability to livechat customers to close the chat session - Send data to CRM on any livechat edit action - Add new livechat callbacks: `livechat.saveGuest`, `livechat.saveRoom` and `livechat.saveInfo` - Rename livechat callbacks: - `closeLivechat` -> `livechat.closeRoom` - `sendOfflineLivechatMessage` - `livechat.offlineMessage` - Rename method `livechat:saveLivechatInfo` to `livechat:saveInfo` - Save CRM returned data - Add option to override the input box template --- client/startup/defaultRoomTypes.coffee | 2 + .../client/lib/roomTypes.coffee | 39 +++++ packages/rocketchat-lib/i18n/en.i18n.json | 2 + packages/rocketchat-lib/i18n/pt.i18n.json | 4 +- .../app/client/components/modal.html | 8 ++ .../app/client/stylesheets/main.less | 86 ++++++++++- .../app/client/views/messages.html | 3 + .../app/client/views/messages.js | 36 ++++- .../app/client/views/options.html | 7 + .../app/client/views/options.js | 29 ++++ .../rocketchat-livechat/app/i18n/en.i18n.json | 10 +- .../rocketchat-livechat/app/i18n/pt.i18n.json | 10 +- packages/rocketchat-livechat/app/run.sh | 2 +- packages/rocketchat-livechat/client/ui.js | 11 +- .../views/app/livechatNotSubscribed.html | 3 + .../client/views/app/tabbar/visitorEdit.js | 2 +- .../client/views/app/tabbar/visitorInfo.html | 2 +- packages/rocketchat-livechat/package.js | 10 +- .../externalMessage.js} | 0 .../server/hooks/offlineMessage.js | 17 +++ .../server/hooks/sendToCRM.js | 46 ++++++ .../server/lib/Livechat.js | 91 +++++++++++- .../server/methods/closeByVisitor.js | 23 +++ .../{saveLivechatInfo.js => saveInfo.js} | 12 +- .../server/methods/sendOfflineMessage.js | 2 +- .../server/models/Rooms.js | 22 +++ .../server/setupWebhook.js | 133 ------------------ .../message/messageBox.coffee | 10 +- .../message/messageBox.html | 16 ++- 29 files changed, 467 insertions(+), 171 deletions(-) create mode 100644 packages/rocketchat-livechat/app/client/components/modal.html create mode 100644 packages/rocketchat-livechat/app/client/views/options.html create mode 100644 packages/rocketchat-livechat/app/client/views/options.js create mode 100644 packages/rocketchat-livechat/client/views/app/livechatNotSubscribed.html rename packages/rocketchat-livechat/server/{externalMessageHook.js => hooks/externalMessage.js} (100%) create mode 100644 packages/rocketchat-livechat/server/hooks/offlineMessage.js create mode 100644 packages/rocketchat-livechat/server/hooks/sendToCRM.js create mode 100644 packages/rocketchat-livechat/server/methods/closeByVisitor.js rename packages/rocketchat-livechat/server/methods/{saveLivechatInfo.js => saveInfo.js} (62%) delete mode 100644 packages/rocketchat-livechat/server/setupWebhook.js diff --git a/client/startup/defaultRoomTypes.coffee b/client/startup/defaultRoomTypes.coffee index 88fb8b1958fb..eda7e31aca67 100644 --- a/client/startup/defaultRoomTypes.coffee +++ b/client/startup/defaultRoomTypes.coffee @@ -22,6 +22,8 @@ RocketChat.roomTypes.add 'c', 10, return roomData.name condition: -> return RocketChat.authz.hasAtLeastOnePermission ['view-c-room', 'view-joined-room'] + showJoinLink: (roomId) -> + return !! ChatRoom.findOne { _id: roomId, t: 'c' } RocketChat.roomTypes.add 'd', 20, template: 'directMessages' diff --git a/packages/rocketchat-lib/client/lib/roomTypes.coffee b/packages/rocketchat-lib/client/lib/roomTypes.coffee index dd76247c66c9..96a2bbde7b46 100644 --- a/packages/rocketchat-lib/client/lib/roomTypes.coffee +++ b/packages/rocketchat-lib/client/lib/roomTypes.coffee @@ -71,6 +71,41 @@ RocketChat.roomTypes = new class findRoom = (roomType, identifier, user) -> return roomTypes[roomType]?.findRoom identifier, user + canSendMessage = (roomId) -> + return ChatSubscription.find({ rid: roomId }).count() > 0 + + verifyCanSendMessage = (roomId) -> + room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } }) + return if not room?.t? + + roomType = room.t + + return roomTypes[roomType]?.canSendMessage roomId if roomTypes[roomType]?.canSendMessage? + + return canSendMessage roomId + + verifyShowJoinLink = (roomId) -> + room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } }) + return if not room?.t? + + roomType = room.t + + if not roomTypes[roomType]?.showJoinLink? + return false + + return roomTypes[roomType].showJoinLink roomId + + getNotSubscribedTpl = (roomId) -> + room = ChatRoom.findOne({ _id: roomId }, { fields: { t: 1 } }) + return if not room?.t? + + roomType = room.t + + if not roomTypes[roomType]?.notSubscribedTpl? + return false + + return roomTypes[roomType].notSubscribedTpl + # addType: addType getTypes: getAllTypes getIdentifiers: getIdentifiers @@ -86,4 +121,8 @@ RocketChat.roomTypes = new class checkCondition: checkCondition + verifyCanSendMessage: verifyCanSendMessage + verifyShowJoinLink: verifyShowJoinLink + getNotSubscribedTpl: getNotSubscribedTpl + add: add diff --git a/packages/rocketchat-lib/i18n/en.i18n.json b/packages/rocketchat-lib/i18n/en.i18n.json index e212e0e69799..bfb220dd2012 100644 --- a/packages/rocketchat-lib/i18n/en.i18n.json +++ b/packages/rocketchat-lib/i18n/en.i18n.json @@ -243,6 +243,7 @@ "close" : "close", "Close" : "Close", "Closed" : "Closed", + "Closed_by_visitor" : "Closed by visitor", "Closing_chat" : "Closing chat", "Collapse_Embedded_Media_By_Default" : "Collapse embedded media by default", "Color" : "Color", @@ -1128,6 +1129,7 @@ "There_are_no_agents_added_to_this_department_yet" : "There are no agents added to this department yet.", "There_are_no_integrations" : "There are no integrations", "There_are_no_users_in_this_role" : "There are no users in this role.", + "This_conversation_is_already_closed" : "This conversation is already closed.", "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password" : "This email has already been used and has not been verified. Please change your password.", "This_is_a_desktop_notification" : "This is a desktop notification", "This_is_a_push_test_messsage" : "This is a push test messsage", diff --git a/packages/rocketchat-lib/i18n/pt.i18n.json b/packages/rocketchat-lib/i18n/pt.i18n.json index f398ba0aa191..255c29a4d057 100644 --- a/packages/rocketchat-lib/i18n/pt.i18n.json +++ b/packages/rocketchat-lib/i18n/pt.i18n.json @@ -240,6 +240,7 @@ "close" : "fechar", "Close" : "Fechar", "Closed" : "Fechado", + "Closed_by_visitor" : "Encerrado pelo visitante", "Closing_chat" : "Encerrando chat", "Collapse_Embedded_Media_By_Default" : "Esconder mídia por padrão", "Color" : "Cor", @@ -1095,6 +1096,7 @@ "There_are_no_agents_added_to_this_department_yet" : "Não há agentes adicionados a este departamento ainda.", "There_are_no_integrations" : "Não há integrações", "There_are_no_users_in_this_role" : "Não há usuários neste papel.", + "This_conversation_is_already_closed" : "Esta conversa já está fechada.", "This_email_has_already_been_used_and_has_not_been_verified__Please_change_your_password" : "Este e-mail já foi utilizado e não foi verificado. Por favor, altere sua senha.", "This_is_a_desktop_notification" : "Esta é uma notificação de desktop", "This_is_a_push_test_messsage" : "Este é uma mensagem de teste de push notification", @@ -1260,4 +1262,4 @@ "Your_mail_was_sent_to_s" : "O seu e-mail foi enviado para %s", "Your_password_is_wrong" : "Sua senha está errada!", "Your_push_was_sent_to_s_devices" : "Sua natificação foi enviada para %s dispositivos" -} \ No newline at end of file +} diff --git a/packages/rocketchat-livechat/app/client/components/modal.html b/packages/rocketchat-livechat/app/client/components/modal.html new file mode 100644 index 000000000000..e5356e817baf --- /dev/null +++ b/packages/rocketchat-livechat/app/client/components/modal.html @@ -0,0 +1,8 @@ + diff --git a/packages/rocketchat-livechat/app/client/stylesheets/main.less b/packages/rocketchat-livechat/app/client/stylesheets/main.less index 6cf5b8cb3938..8390f37fc3b0 100644 --- a/packages/rocketchat-livechat/app/client/stylesheets/main.less +++ b/packages/rocketchat-livechat/app/client/stylesheets/main.less @@ -24,10 +24,12 @@ body { overflow: hidden; position: relative; // background-color: @primary-background-color; + + border-top-right-radius: 5px; + border-top-left-radius: 5px; } input, -button, select, textarea { font-family: inherit; @@ -40,6 +42,17 @@ textarea { outline: none; } +button { + background: none; + border: none; + padding: 0px; + text-align: left; + cursor: pointer; + text-transform: inherit; + color: inherit; + font-style: inherit; +} + input:focus { outline: none; box-shadow: 0 0 0; @@ -114,13 +127,16 @@ input:focus { flex-direction: column; height: 100%; + border-top-right-radius: inherit; + border-top-left-radius: inherit; + .title { flex: 1 0 @header-min-height; line-height: @header-min-height; - border-top-right-radius: 5px; - border-top-left-radius: 5px; + border-top-right-radius: inherit; + border-top-left-radius: inherit; color: #FFF; z-index: 10; cursor: pointer; @@ -398,6 +414,56 @@ input:focus { color: @primary-font-color; } } + .toggle-options { + clear: both; + color: @secondary-font-color; + margin-left: 6px; + outline: none; + margin-top: 5px; + font-size: 0.65rem; + } + + .options-menu { + min-width: 100px; + // min-height: 40px; + bottom: 21px; + left: 6px; + border-radius: 2px; + padding: 6px 0; + background-color: #fff; + color: @secondary-font-color; + + box-shadow: 0px 1px 1px 0 rgba(0,0,0,0.2), 0 2px 10px 0 rgba(0,0,0,.16); + position: absolute; + z-index: 200; + + .transition(transform 0.15s ease, visibility 0.15s ease, opacity 0.15s ease); + + .transform(translateY(30px)); + opacity: 0; + visibility: hidden; + + &.show { + .transform(translateY(0px)); + opacity: 1; + display: block; + visibility: visible; + } + + ul { + li { + padding: 0 13px 0 8px; + &:hover { + background-color: #EEE; + } + button { + display: block; + padding: 4px 2px; + outline: none; + } + } + } + } } .offline { flex: 1 1 100%; @@ -475,8 +541,13 @@ input:focus { } } -#survey { +.modal { + border-top-right-radius: inherit; + border-top-left-radius: inherit; + .overlay { + border-top-right-radius: inherit; + border-top-left-radius: inherit; background-color: rgba(0,0,0,0.5); position: fixed; height: 100%; @@ -510,7 +581,7 @@ input:focus { } .content { - overflow-y: scroll; + overflow-y: auto; padding: 10px; flex: 1 1 100%; @@ -572,6 +643,11 @@ input:focus { } } +.sweet-overlay { + border-top-right-radius: inherit; + border-top-left-radius: inherit; +} + @media all and(max-height: 200px) { .livechat-room { .title { diff --git a/packages/rocketchat-livechat/app/client/views/messages.html b/packages/rocketchat-livechat/app/client/views/messages.html index 3a6da5fb8559..9728c7ed9846 100644 --- a/packages/rocketchat-livechat/app/client/views/messages.html +++ b/packages/rocketchat-livechat/app/client/views/messages.html @@ -20,5 +20,8 @@ + + {{> options show=showOptions}} + diff --git a/packages/rocketchat-livechat/app/client/views/messages.js b/packages/rocketchat-livechat/app/client/views/messages.js index e9d0312cc02c..12924694f765 100644 --- a/packages/rocketchat-livechat/app/client/views/messages.js +++ b/packages/rocketchat-livechat/app/client/views/messages.js @@ -1,5 +1,5 @@ Template.messages.helpers({ - messages: function() { + messages() { return ChatMessage.find({ rid: visitor.getRoom(), t: { @@ -10,6 +10,20 @@ Template.messages.helpers({ ts: 1 } }); + }, + showOptions() { + if (Template.instance().showOptions.get()) { + return 'show'; + } else { + return ''; + } + }, + optionsLink() { + if (Template.instance().showOptions.get()) { + return t('Close_menu'); + } else { + return t('Options'); + } } }); @@ -35,16 +49,18 @@ Template.messages.events({ }, 'click .error': function(event) { return $(event.currentTarget).removeClass('show'); + }, + 'click .toggle-options': function(event, instance) { + instance.showOptions.set(!instance.showOptions.get()); } }); Template.messages.onCreated(function() { - var self; - self = this; + this.atBottom = true; - self.atBottom = true; + this.showOptions = new ReactiveVar(false); - self.updateMessageInputHeight = function(input) { + this.updateMessageInputHeight = function(input) { // Inital height is 28. If the scrollHeight is greater than that( we have more text than area ), // increase the size of the textarea. The max-height is set at 200 // even if the scrollHeight become bigger than that it should never exceed that. @@ -56,6 +72,16 @@ Template.messages.onCreated(function() { return $(input).height($(input).val() === '' ? '15px' : (inputScrollHeight >= 200 ? inputScrollHeight - 50 : inputScrollHeight - 20)); } }; + + $(document).click((/*event*/) => { + if (!this.showOptions.get()) { + return; + } + let target = $(event.target); + if (!target.closest('.options-menu').length && !target.is('.options-menu') && !target.closest('.toggle-options').length && !target.is('.toggle-options')) { + this.showOptions.set(false); + } + }); }); Template.messages.onRendered(function() { diff --git a/packages/rocketchat-livechat/app/client/views/options.html b/packages/rocketchat-livechat/app/client/views/options.html new file mode 100644 index 000000000000..8c57e9f6379d --- /dev/null +++ b/packages/rocketchat-livechat/app/client/views/options.html @@ -0,0 +1,7 @@ + diff --git a/packages/rocketchat-livechat/app/client/views/options.js b/packages/rocketchat-livechat/app/client/views/options.js new file mode 100644 index 000000000000..79ba0e0ea146 --- /dev/null +++ b/packages/rocketchat-livechat/app/client/views/options.js @@ -0,0 +1,29 @@ +Template.options.events({ + 'click .end-chat'() { + swal({ + text: t('Are_you_sure_do_you_want_end_this_chat'), + title: '', + type: 'warning', + showCancelButton: true, + confirmButtonColor: '#DD6B55', + confirmButtonText: t('Yes'), + cancelButtonText: t('No'), + closeOnConfirm: false, + html: false + }, () => { + Meteor.call('livechat:closeByVisitor', (error) => { + if (error) { + return console.log('Error ->', error); + } + + swal({ + title: '', + text: t('Chat_ended'), + type: 'success', + timer: 1500, + showConfirmButton: false + }); + }); + }); + } +}); diff --git a/packages/rocketchat-livechat/app/i18n/en.i18n.json b/packages/rocketchat-livechat/app/i18n/en.i18n.json index 6eba5f5861b4..51c20d5f2066 100644 --- a/packages/rocketchat-livechat/app/i18n/en.i18n.json +++ b/packages/rocketchat-livechat/app/i18n/en.i18n.json @@ -1,13 +1,20 @@ { "Additional_Feedback" : "Additional Feedback", "Appearance" : "Appearance", + "Are_you_sure_do_you_want_end_this_chat" : "Are you sure do you want end this chat?", + "Cancel" : "Cancel", + "Chat_ended" : "Chat ended!", + "Close_menu" : "Close menu", "Conversation_finished" : "Conversation finished", + "End_chat" : "End chat", "How_friendly_was_the_chat_agent" : "How friendly was the chat agent?", "How_knowledgeable_was_the_chat_agent" : "How knowledgeable was the chat agent?", "How_responsive_was_the_chat_agent" : "How responsive was the chat agent?", "How_satisfied_were_you_with_this_chat" : "How satisfied were you with this chat?", "Installation" : "Installation", "New_messages" : "New messages", + "No" : "No", + "Options" : "Options", "Please_answer_survey" : "Please take a moment to answer a quick survey about this chat", "Please_fill_name_and_email" : "Please fill name and email", "Powered_by" : "Powered by", @@ -25,5 +32,6 @@ "User_joined" : "User joined", "User_left" : "User left", "We_are_offline_Sorry_for_the_inconvenience" : "We are offline. Sorry for the inconvenience.", + "Yes" : "Yes", "You_must_complete_all_fields" : "You must complete all fields" -} \ No newline at end of file +} diff --git a/packages/rocketchat-livechat/app/i18n/pt.i18n.json b/packages/rocketchat-livechat/app/i18n/pt.i18n.json index 0580e35cfced..259b0dc7a721 100644 --- a/packages/rocketchat-livechat/app/i18n/pt.i18n.json +++ b/packages/rocketchat-livechat/app/i18n/pt.i18n.json @@ -1,13 +1,20 @@ { "Additional_Feedback" : "Feedback Adicional", "Appearance" : "Aparência", + "Are_you_sure_do_you_want_end_this_chat" : "Você tem certeza que deseja encerrar?", + "Cancel" : "Cancelar", + "Chat_ended" : "Chat encerrado!", + "Close_menu" : "Fechar menu", "Conversation_finished" : "Chat encerrado", + "End_chat" : "Encerrar chat", "How_friendly_was_the_chat_agent" : "Quão amigável foi o agente de bate-papo?", "How_knowledgeable_was_the_chat_agent" : "Quão conhecedor foi o agente bate-papo?", "How_responsive_was_the_chat_agent" : "Quão responsiva foi o agente de bate-papo?", "How_satisfied_were_you_with_this_chat" : "Você ficou satisfeito com este bate-papo?", "Installation" : "Instalação", "New_messages" : "Novas mensagens", + "No" : "Não", + "Options" : "Opções", "Please_answer_survey" : "Por favor nos dê um momento para responder uma rápida pesquisa sobre este chat", "Please_fill_name_and_email" : "Por favor preencha nome e email", "Powered_by" : "Distribuído por", @@ -25,5 +32,6 @@ "User_joined" : "Usuário entrou", "User_left" : "Usuário saiu", "We_are_offline_Sorry_for_the_inconvenience" : "Nós estamos offline. Desculpe pelo inconveniente.", + "Yes" : "Sim", "You_must_complete_all_fields" : "Você deve preencher todos os campos" -} \ No newline at end of file +} diff --git a/packages/rocketchat-livechat/app/run.sh b/packages/rocketchat-livechat/app/run.sh index e05687e2cc12..39a51b3dc45f 100755 --- a/packages/rocketchat-livechat/app/run.sh +++ b/packages/rocketchat-livechat/app/run.sh @@ -1,5 +1,5 @@ #!/bin/sh export DDP_DEFAULT_CONNECTION_URL=http://localhost:3000 -export MONGO_URL=mongodb://localhost:3001 +export MONGO_URL=mongodb://localhost:27017 meteor -p 5000 diff --git a/packages/rocketchat-livechat/client/ui.js b/packages/rocketchat-livechat/client/ui.js index 26ce50710b85..c0b335c8c34c 100644 --- a/packages/rocketchat-livechat/client/ui.js +++ b/packages/rocketchat-livechat/client/ui.js @@ -29,8 +29,17 @@ RocketChat.roomTypes.add('l', 5, { } }, - condition: () => { + condition() { return RocketChat.settings.get('Livechat_enabled') && RocketChat.authz.hasAllPermission('view-l-room'); + }, + + canSendMessage(roomId) { + let room = ChatRoom.findOne({ _id: roomId }, { fields: { open: 1 } }); + return room && room.open === true; + }, + + notSubscribedTpl: { + template: 'livechatNotSubscribed' } }); diff --git a/packages/rocketchat-livechat/client/views/app/livechatNotSubscribed.html b/packages/rocketchat-livechat/client/views/app/livechatNotSubscribed.html new file mode 100644 index 000000000000..97e7c7e9f308 --- /dev/null +++ b/packages/rocketchat-livechat/client/views/app/livechatNotSubscribed.html @@ -0,0 +1,3 @@ + diff --git a/packages/rocketchat-livechat/client/views/app/tabbar/visitorEdit.js b/packages/rocketchat-livechat/client/views/app/tabbar/visitorEdit.js index 0038cb99fe23..37b64c95433f 100644 --- a/packages/rocketchat-livechat/client/views/app/tabbar/visitorEdit.js +++ b/packages/rocketchat-livechat/client/views/app/tabbar/visitorEdit.js @@ -53,7 +53,7 @@ Template.visitorEdit.events({ roomData.topic = event.currentTarget.elements['topic'].value; roomData.tags = event.currentTarget.elements['tags'].value; - Meteor.call('livechat:saveLivechatInfo', userData, roomData, (err) => { + Meteor.call('livechat:saveInfo', userData, roomData, (err) => { if (err) { toastr.error(t(err.error)); } else { diff --git a/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html b/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html index fe44653583f6..bd7ee4afc746 100644 --- a/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html +++ b/packages/rocketchat-livechat/client/views/app/tabbar/visitorInfo.html @@ -45,8 +45,8 @@

{{username}}

{{#if canSeeButtons}}