Skip to content
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'] },
Comment thread
renatobecker marked this conversation as 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