Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NEW] Allows agents to send chat transcript to omnichannel end-users #17774

Merged
merged 6 commits into from
Jun 20, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
1 change: 1 addition & 0 deletions app/authorization/server/startup.js
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Meteor.startup(function() {
{ _id: 'view-livechat-room-closed-by-another-agent', roles: ['livechat-manager', 'admin'] },
{ _id: 'view-livechat-room-customfields', roles: ['livechat-manager', 'livechat-agent', 'admin'] },
{ _id: 'edit-livechat-room-customfields', roles: ['livechat-manager', 'livechat-agent', 'admin'] },
{ _id: 'send-omnichannel-chat-transcript', roles: ['livechat-manager', 'admin'] },
renatobecker marked this conversation as resolved.
Show resolved Hide resolved
];

for (const permission of permissions) {
Expand Down
29 changes: 23 additions & 6 deletions app/livechat/client/views/app/tabbar/visitorInfo.html
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
<template name="visitorInfo">
{{#if editing}}
{{> visitorEdit (editDetails)}}
{{> visitorEdit (roomInfoData)}}
{{/if}}
{{#if forwarding}}
{{> visitorForward (forwardDetails)}}
{{> visitorForward (roomInfoData)}}
{{/if}}
{{#if sendingTranscript}}
{{> visitorTranscript (roomInfoData)}}
{{/if}}
<div class="user-view {{showDetail}}">
<div class="about clearfix">
Expand Down Expand Up @@ -51,25 +54,39 @@ <h3>{{_ "Conversation"}}</h3>
{{/if}}
</ul>
</div>

{{#with transcriptRequest}}
<div class="info">
<h3>{{_ "Transcript_Request"}}</h3>
<ul>
{{#if requestedAt}}<li><strong>{{_ "Requested_At"}}</strong>: {{transcriptRequestedDateTime}}</li>{{/if}}
{{#if requestedBy}}<li><strong>{{_ "Requested_By"}}</strong>: {{requestedBy.username}}</li>{{/if}}
</ul>
</div>
{{/with}}
</div>

{{#if canSeeButtons}}
<nav class="centered-buttons">
{{#if canEditRoom}}
<button class='button button-block edit-livechat'><span><i class='icon-edit'></i> {{_ "Edit"}}</span></button>
<button class='button rc-button rc-button--secondary button-block edit-livechat'><span><i class='icon-edit'></i> {{_ "Edit"}}</span></button>
{{/if}}

{{#if roomOpen}}
{{#if canCloseRoom}}
<button class='button button-block close-livechat'><span><i class='icon-download'></i> {{_ "Close"}}</span></button>
<button class='button rc-button rc-button--secondary button-block close-livechat'><span><i class='icon-download'></i> {{_ "Close"}}</span></button>
{{/if}}
{{#if canForwardGuest}}
<button class="rc-button rc-button--secondary button-block forward-livechat"><span><i class="icon-forward"></i> {{_ "Forward"}}</span></button>
<button class="button rc-button rc-button--secondary button-block forward-livechat"><span><i class="icon-forward"></i> {{_ "Forward"}}</span></button>
{{/if}}
{{#if canReturnQueue}}
<button class="rc-button rc-button--secondary button-block return-inquiry"><span><i class="icon-ccw"></i> {{_ "Return"}}</span></button>
<button class="button rc-button rc-button--secondary button-block return-inquiry"><span><i class="icon-ccw"></i> {{_ "Return"}}</span></button>
{{/if}}
{{/if}}

{{#if canSendTranscript}}
<button class="button rc-button rc-button--secondary button-block send-transcript"><span><i class="icon-mail"></i> {{_ "Transcript"}}</span></button>
{{/if}}
</nav>
{{/if}}

Expand Down
37 changes: 23 additions & 14 deletions app/livechat/client/views/app/tabbar/visitorInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,22 +136,11 @@ Template.visitorInfo.helpers({
return Template.instance().action.get() === 'forward';
},

editDetails() {
const instance = Template.instance();
const user = instance.user.get();
return {
visitorId: user ? user._id : null,
roomId: this.rid,
save() {
instance.action.set();
},
cancel() {
instance.action.set();
},
};
sendingTranscript() {
return Template.instance().action.get() === 'transcript';
},

forwardDetails() {
roomInfoData() {
const instance = Template.instance();
const user = instance.user.get();
return {
Expand Down Expand Up @@ -211,6 +200,10 @@ Template.visitorInfo.helpers({
return hasPermission('transfer-livechat-guest');
},

canSendTranscript() {
return hasPermission('send-omnichannel-chat-transcript');
},

roomClosedDateTime() {
const { closedAt } = this;
return DateFormat.formatDateAndTime(closedAt);
Expand Down Expand Up @@ -241,6 +234,16 @@ Template.visitorInfo.helpers({
// because only the dynamic template data will be reloaded
return Template.instance().room;
},

transcriptRequest() {
const room = Template.instance().room.get();
return room?.transcriptRequest;
},

transcriptRequestedDateTime() {
const { requestedAt } = this;
return DateFormat.formatDateAndTime(requestedAt);
},
});

Template.visitorInfo.events({
Expand Down Expand Up @@ -309,6 +312,12 @@ Template.visitorInfo.events({

instance.action.set('forward');
},

'click .send-transcript'(event, instance) {
event.preventDefault();

instance.action.set('transcript');
},
});

Template.visitorInfo.onCreated(function() {
Expand Down
56 changes: 56 additions & 0 deletions app/livechat/client/views/app/tabbar/visitorTranscript.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<template name="visitorTranscript">
<div class="content">
<form autocomplete="off">
<fieldset>
<div class="rc-input rc-form-group rc-form-group--small">
<label class="rc-input__label">
<div class="rc-input__title">{{_ "Visitor_Email"}}</div>
<div class="rc-input__wrapper">
<div class="rc-input__icon">
{{> icon icon='mail' }}
</div>
<input class="rc-input__element" type="text" name="email" autocomplete="off" value="{{email}}" disabled>
</div>
</label>
</div>
<div class="rc-input rc-form-group rc-form-group--small">
<label class="rc-input__label">
<div class="rc-input__title">{{_ "Subject"}}</div>
<div class="rc-input__wrapper">
<div class="rc-input__icon">
{{> icon block="rc-input__icon-svg rc-icon--default-size" icon="edit"}}
</div>
<input class="rc-input__element" type="text" name="subject" autocomplete="off" value="{{subject}}" disabled="{{transcriptRequested}}"/>
</div>
</label>
</div>
</fieldset>
</form>
{{#if errorMessage}}
<div class="mail-messages__instructions mail-messages__instructions--warning">
<div class="mail-messages__instructions-wrapper">
<div class="mail-messages__instructions-text">{{errorMessage}}</div>
</div>
</div>
{{/if}}
{{#if infoMessage}}
<div class="mail-messages__instructions mail-messages__instructions--selected">
<div class="mail-messages__instructions-wrapper">
<div class="mail-messages__instructions-text">{{infoMessage}}</div>
</div>
</div>
{{/if}}
<div class="rc-user-info__flex rc-user-info__row">
<button class='rc-button cancel'><span>{{_ "Cancel"}}</span></button>
{{#if roomOpen}}
{{#if transcriptRequested}}
<button class='rc-button rc-button--cancel discard' type="button"><span>{{_ "Discard"}}</span></button>
{{else}}
<button class='rc-button rc-button--primary request'><span>{{_ "Request"}}</span></button>
{{/if}}
{{else}}
<button class='rc-button rc-button--primary send'><span>{{_ "Send"}}</span></button>
{{/if}}
</div>
</div>
</template>
170 changes: 170 additions & 0 deletions app/livechat/client/views/app/tabbar/visitorTranscript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { Meteor } from 'meteor/meteor';
import { ReactiveVar } from 'meteor/reactive-var';
import { Template } from 'meteor/templating';
import toastr from 'toastr';

import { t, isEmail, handleError, roomTypes } from '../../../../../utils';
import { APIClient } from '../../../../../utils/client';
import './visitorTranscript.html';

const validateTranscriptData = (instance) => {
const subject = instance.$('[name="subject"]').val();
const email = instance.$('[name="email"]').val();

if (email === '') {
instance.errorMessage.set(t('Mail_Message_Missing_to'));
return false;
}

if (!isEmail(email)) {
instance.errorMessage.set(t('Mail_Message_Invalid_emails', email));
return false;
}

if (subject === '') {
instance.errorMessage.set(t('Mail_Message_Missing_subject'));
return false;
}

const visitor = instance.visitor.get();
const { visitorEmails: { 0: visitorEmail } } = visitor;

if (email !== visitorEmail.address) {
instance.errorMessage.set(t('Livechat_visitor_email_and_transcript_email_do_not_match'));
return false;
}

return true;
};

Template.visitorTranscript.helpers({
roomOpen() {
const room = Template.instance().room.get();
return room && room.open === true;
},
email() {
const room = Template.instance().room.get();
if (room?.transcriptRequest) {
return room.transcriptRequest.email;
}

const visitor = Template.instance().visitor.get();
if (visitor?.visitorEmails && visitor.visitorEmails.length > 0) {
return visitor.visitorEmails[0].address;
}
},
subject() {
const room = Template.instance().room.get();
if (room?.transcriptRequest) {
return room.transcriptRequest.subject;
}

return t('Transcript_of_your_livechat_conversation') || (room && roomTypes.getRoomName(room.t, room));
},
errorEmail() {
const instance = Template.instance();
return instance && instance.erroredEmails.get().join(', ');
},
errorMessage() {
return Template.instance().errorMessage.get();
},
infoMessage() {
return Template.instance().infoMessage.get();
},
transcriptRequested() {
const room = Template.instance().room.get();
return room?.hasOwnProperty('transcriptRequest');
},
});

Template.visitorTranscript.events({
'click .send'(e, instance) {
event.preventDefault();

if (!validateTranscriptData(instance)) {
return;
}

const subject = instance.$('[name="subject"]').val();
const email = instance.$('[name="email"]').val();

const room = instance.room.get();
const { _id: rid } = room;

const visitor = instance.visitor.get();
const { token } = visitor;

Meteor.call('livechat:sendTranscript', token, rid, email, subject, (err) => {
if (err != null) {
return handleError(err);
}

toastr.success(t('Your_email_has_been_queued_for_sending'));
this.save();
});
},

'click .request'(e, instance) {
event.preventDefault();

if (!validateTranscriptData(instance)) {
return;
}

const subject = instance.$('[name="subject"]').val();
const email = instance.$('[name="email"]').val();

const room = instance.room.get();
const { _id: rid } = room;

Meteor.call('livechat:requestTranscript', rid, email, subject, (err) => {
if (err != null) {
return handleError(err);
}

toastr.success(t('Livechat_transcript_has_been_requested'));
this.save();
});
},

'click .discard'(e, instance) {
event.preventDefault();

const room = instance.room.get();
const { _id: rid } = room;

Meteor.call('livechat:discardTranscript', rid, (err) => {
if (err != null) {
return handleError(err);
}

toastr.success(t('Livechat_transcript_request_has_been_canceled'));
this.save();
});
},

'click .cancel'() {
this.cancel();
},
});

Template.visitorTranscript.onCreated(async function() {
this.room = new ReactiveVar();
this.visitor = new ReactiveVar();
this.errorMessage = new ReactiveVar('');
this.infoMessage = new ReactiveVar('');

this.autorun(async () => {
const { visitor } = await APIClient.v1.get(`livechat/visitors.info?visitorId=${ Template.currentData().visitorId }`);
this.visitor.set(visitor);
});

this.autorun(async () => {
const { room } = await APIClient.v1.get(`rooms.info?roomId=${ Template.currentData().roomId }`);
this.room.set(room);

if (room?.transcriptRequest) {
this.infoMessage.set(t('Livechat_transcript_already_requested_warning'));
}
});
});
1 change: 1 addition & 0 deletions app/livechat/client/views/regular.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ import './app/tabbar/visitorForward';
import './app/tabbar/visitorHistory';
import './app/tabbar/visitorInfo';
import './app/tabbar/visitorNavigation';
import './app/tabbar/visitorTranscript';
26 changes: 26 additions & 0 deletions app/livechat/lib/messageTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,32 @@ MessageTypes.registerType({
},
});

MessageTypes.registerType({
id: 'livechat_transcript_history',
system: true,
message: 'Livechat_chat_transcript_sent',
data(message) {
if (!message.requestData) {
return;
}

const { requestData: { type, visitor = {}, user = {} } = {} } = message;
const requestTypes = {
visitor: () => TAPi18n.__('Livechat_visitor_transcript_request', {
guest: visitor.name || visitor.username,
}),
user: () => TAPi18n.__('Livechat_user_sent_chat_transcript_to_visitor', {
agent: user.name || user.username,
guest: visitor.name || visitor.username,
}),
};

return {
transcript: requestTypes[type](),
};
},
});

MessageTypes.registerType({
id: 'livechat_video_call',
system: true,
Expand Down
Loading