feat: Create a new conversation from the contact panel (#2019)
* Chore: Improve button component styles
This commit is contained in:
parent
c287ad08fb
commit
864471a21e
16 changed files with 469 additions and 9 deletions
|
@ -14,6 +14,10 @@ class ContactAPI extends ApiClient {
|
||||||
return axios.get(`${this.url}/${contactId}/conversations`);
|
return axios.get(`${this.url}/${contactId}/conversations`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getContactableInboxes(contactId) {
|
||||||
|
return axios.get(`${this.url}/${contactId}/contactable_inboxes`);
|
||||||
|
}
|
||||||
|
|
||||||
search(search = '', page = 1) {
|
search(search = '', page = 1) {
|
||||||
return axios.get(`${this.url}/search?q=${search}&page=${page}`);
|
return axios.get(`${this.url}/search?q=${search}&page=${page}`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@
|
||||||
>
|
>
|
||||||
<spinner v-if="isLoading" size="small" />
|
<spinner v-if="isLoading" size="small" />
|
||||||
<i v-else-if="icon" class="icon" :class="icon"></i>
|
<i v-else-if="icon" class="icon" :class="icon"></i>
|
||||||
<span v-if="$slots.default" class="content"><slot></slot></span>
|
<span v-if="$slots.default" class="button__content"><slot></slot></span>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
|
@ -63,11 +63,16 @@ export default {
|
||||||
.button {
|
.button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
|
&.link {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.spinner {
|
.spinner {
|
||||||
padding: 0 var(--space-small);
|
padding: 0 var(--space-small);
|
||||||
}
|
}
|
||||||
.icon + .content {
|
.icon + .button__content {
|
||||||
padding-left: var(--space-small);
|
padding-left: var(--space-small);
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
"INITIATED_FROM": "Initiated from",
|
"INITIATED_FROM": "Initiated from",
|
||||||
"INITIATED_AT": "Initiated at",
|
"INITIATED_AT": "Initiated at",
|
||||||
"IP_ADDRESS": "IP Address",
|
"IP_ADDRESS": "IP Address",
|
||||||
|
"NEW_MESSAGE": "New message",
|
||||||
"CONVERSATIONS": {
|
"CONVERSATIONS": {
|
||||||
"NO_RECORDS_FOUND": "There are no previous conversations associated to this contact.",
|
"NO_RECORDS_FOUND": "There are no previous conversations associated to this contact.",
|
||||||
"TITLE": "Previous Conversations"
|
"TITLE": "Previous Conversations"
|
||||||
|
@ -106,6 +107,30 @@
|
||||||
"CONTACT_ALREADY_EXIST": "This email address is in use for another contact.",
|
"CONTACT_ALREADY_EXIST": "This email address is in use for another contact.",
|
||||||
"ERROR_MESSAGE": "There was an error, please try again"
|
"ERROR_MESSAGE": "There was an error, please try again"
|
||||||
},
|
},
|
||||||
|
"NEW_CONVERSATION": {
|
||||||
|
"BUTTON_LABEL": "Start conversation",
|
||||||
|
"TITLE": "New conversation",
|
||||||
|
"DESC": "Start a new conversation by sending a new message.",
|
||||||
|
"NO_INBOX": "Couldn't find an inbox to initiate a new conversation with this contact.",
|
||||||
|
"FORM": {
|
||||||
|
"TO": {
|
||||||
|
"LABEL": "To"
|
||||||
|
},
|
||||||
|
"INBOX": {
|
||||||
|
"LABEL": "Inbox",
|
||||||
|
"ERROR": "Select an inbox"
|
||||||
|
},
|
||||||
|
"MESSAGE": {
|
||||||
|
"LABEL": "Message",
|
||||||
|
"PLACEHOLDER": "Write your message here",
|
||||||
|
"ERROR": "Message can't be empty"
|
||||||
|
},
|
||||||
|
"SUBMIT": "Send message",
|
||||||
|
"CANCEL": "Cancel",
|
||||||
|
"SUCCESS_MESSAGE": "Message sent!",
|
||||||
|
"ERROR_MESSAGE": "Couldn't send! try again"
|
||||||
|
}
|
||||||
|
},
|
||||||
"CONTACTS_PAGE": {
|
"CONTACTS_PAGE": {
|
||||||
"HEADER": "Contacts",
|
"HEADER": "Contacts",
|
||||||
"SEARCH_BUTTON": "Search",
|
"SEARCH_BUTTON": "Search",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<span class="close-button" @click="onClose">
|
<span class="close-button" @click="onClose">
|
||||||
<i class="ion-android-close close-icon" />
|
<i class="ion-android-close close-icon" />
|
||||||
</span>
|
</span>
|
||||||
<contact-info :contact="contact" />
|
<contact-info show-new-message :contact="contact" />
|
||||||
<contact-custom-attributes
|
<contact-custom-attributes
|
||||||
v-if="hasContactAttributes"
|
v-if="hasContactAttributes"
|
||||||
:custom-attributes="contact.custom_attributes"
|
:custom-attributes="contact.custom_attributes"
|
||||||
|
|
|
@ -50,19 +50,41 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<woot-button
|
<woot-button
|
||||||
|
v-if="!showNewMessage"
|
||||||
class="edit-contact"
|
class="edit-contact"
|
||||||
variant="clear"
|
variant="clear link"
|
||||||
size="small"
|
size="small"
|
||||||
@click="toggleEditModal"
|
@click="toggleEditModal"
|
||||||
>
|
>
|
||||||
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
|
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
|
||||||
</woot-button>
|
</woot-button>
|
||||||
|
<div v-else class="contact-actions">
|
||||||
|
<woot-button
|
||||||
|
class="new-message"
|
||||||
|
size="small expanded"
|
||||||
|
@click="toggleConversationModal"
|
||||||
|
>
|
||||||
|
{{ $t('CONTACT_PANEL.NEW_MESSAGE') }}
|
||||||
|
</woot-button>
|
||||||
|
<woot-button
|
||||||
|
variant="hollow"
|
||||||
|
size="small expanded"
|
||||||
|
@click="toggleEditModal"
|
||||||
|
>
|
||||||
|
{{ $t('EDIT_CONTACT.BUTTON_LABEL') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
<edit-contact
|
<edit-contact
|
||||||
v-if="showEditModal"
|
v-if="showEditModal"
|
||||||
:show="showEditModal"
|
:show="showEditModal"
|
||||||
:contact="contact"
|
:contact="contact"
|
||||||
@cancel="toggleEditModal"
|
@cancel="toggleEditModal"
|
||||||
/>
|
/>
|
||||||
|
<new-conversation
|
||||||
|
:show="showConversationModal"
|
||||||
|
:contact="contact"
|
||||||
|
@cancel="toggleConversationModal"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -71,6 +93,7 @@ import ContactInfoRow from './ContactInfoRow';
|
||||||
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail.vue';
|
||||||
import SocialIcons from './SocialIcons';
|
import SocialIcons from './SocialIcons';
|
||||||
import EditContact from './EditContact';
|
import EditContact from './EditContact';
|
||||||
|
import NewConversation from './NewConversation';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
|
@ -78,6 +101,7 @@ export default {
|
||||||
EditContact,
|
EditContact,
|
||||||
Thumbnail,
|
Thumbnail,
|
||||||
SocialIcons,
|
SocialIcons,
|
||||||
|
NewConversation,
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
contact: {
|
contact: {
|
||||||
|
@ -88,10 +112,15 @@ export default {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
showNewMessage: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
showEditModal: false,
|
showEditModal: false,
|
||||||
|
showConversationModal: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
@ -111,6 +140,9 @@ export default {
|
||||||
toggleEditModal() {
|
toggleEditModal() {
|
||||||
this.showEditModal = !this.showEditModal;
|
this.showEditModal = !this.showEditModal;
|
||||||
},
|
},
|
||||||
|
toggleConversationModal() {
|
||||||
|
this.showConversationModal = !this.showConversationModal;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
@ -120,7 +152,7 @@ export default {
|
||||||
@import '~dashboard/assets/scss/mixins';
|
@import '~dashboard/assets/scss/mixins';
|
||||||
.contact--profile {
|
.contact--profile {
|
||||||
align-items: flex-start;
|
align-items: flex-start;
|
||||||
padding: var(--space-normal) var(--space-normal) var(--space-large);
|
padding: var(--space-normal) var(--space-normal);
|
||||||
|
|
||||||
.user-thumbnail-box {
|
.user-thumbnail-box {
|
||||||
margin-right: $space-normal;
|
margin-right: $space-normal;
|
||||||
|
@ -164,8 +196,21 @@ export default {
|
||||||
font-size: $font-weight-normal;
|
font-size: $font-weight-normal;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.contact-actions {
|
||||||
|
margin: var(--space-small) 0;
|
||||||
|
}
|
||||||
|
.button.edit-contact {
|
||||||
|
margin-left: var(--space-two);
|
||||||
|
padding-left: var(--space-micro);
|
||||||
|
}
|
||||||
|
|
||||||
.edit-contact {
|
.button.new-message {
|
||||||
margin-left: var(--space-slab);
|
margin-right: var(--space-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1,210 @@
|
||||||
|
<template>
|
||||||
|
<form class="conversation--form" @submit.prevent="handleSubmit">
|
||||||
|
<div v-if="showNoInboxAlert" class="callout warning">
|
||||||
|
<p>
|
||||||
|
{{ $t('NEW_CONVERSATION.NO_INBOX') }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<div class="row">
|
||||||
|
<div class="columns">
|
||||||
|
<label :class="{ error: $v.targetInbox.$error }">
|
||||||
|
{{ $t('NEW_CONVERSATION.FORM.INBOX.LABEL') }}
|
||||||
|
<select v-model="targetInbox">
|
||||||
|
<option
|
||||||
|
v-for="contactableInbox in inboxes"
|
||||||
|
:key="contactableInbox.inbox.id"
|
||||||
|
:value="contactableInbox"
|
||||||
|
>
|
||||||
|
{{ contactableInbox.inbox.name }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<span v-if="$v.targetInbox.$error" class="message">
|
||||||
|
{{ $t('NEW_CONVERSATION.FORM.INBOX.ERROR') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="columns">
|
||||||
|
<label>
|
||||||
|
{{ $t('NEW_CONVERSATION.FORM.TO.LABEL') }}
|
||||||
|
<div class="contact-input">
|
||||||
|
<thumbnail
|
||||||
|
:src="contact.thumbnail"
|
||||||
|
size="24px"
|
||||||
|
:username="contact.name"
|
||||||
|
:status="contact.availability_status"
|
||||||
|
/>
|
||||||
|
<h4 class="text-block-title contact-name">
|
||||||
|
{{ contact.name }}
|
||||||
|
</h4>
|
||||||
|
</div>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="columns">
|
||||||
|
<label :class="{ error: $v.message.$error }">
|
||||||
|
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.LABEL') }}
|
||||||
|
<textarea
|
||||||
|
v-model="message"
|
||||||
|
class="message-input"
|
||||||
|
type="text"
|
||||||
|
:placeholder="$t('NEW_CONVERSATION.FORM.MESSAGE.PLACEHOLDER')"
|
||||||
|
@input="$v.message.$touch"
|
||||||
|
/>
|
||||||
|
<span v-if="$v.message.$error" class="message">
|
||||||
|
{{ $t('NEW_CONVERSATION.FORM.MESSAGE.ERROR') }}
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button class="button clear" @click.prevent="onCancel">
|
||||||
|
{{ $t('NEW_CONVERSATION.FORM.CANCEL') }}
|
||||||
|
</button>
|
||||||
|
<woot-button type="submit" :is-loading="conversationsUiFlags.isCreating">
|
||||||
|
{{ $t('NEW_CONVERSATION.FORM.SUBMIT') }}
|
||||||
|
</woot-button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import { mapGetters } from 'vuex';
|
||||||
|
import Thumbnail from 'dashboard/components/widgets/Thumbnail';
|
||||||
|
|
||||||
|
import alertMixin from 'shared/mixins/alertMixin';
|
||||||
|
import { ExceptionWithMessage } from 'shared/helpers/CustomErrors';
|
||||||
|
import { required } from 'vuelidate/lib/validators';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
Thumbnail,
|
||||||
|
},
|
||||||
|
mixins: [alertMixin],
|
||||||
|
props: {
|
||||||
|
contact: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
onSubmit: {
|
||||||
|
type: Function,
|
||||||
|
default: () => {},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
message: '',
|
||||||
|
selectedInbox: '',
|
||||||
|
};
|
||||||
|
},
|
||||||
|
validations: {
|
||||||
|
message: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
|
targetInbox: {
|
||||||
|
required,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapGetters({
|
||||||
|
uiFlags: 'contacts/getUIFlags',
|
||||||
|
conversationsUiFlags: 'contactConversations/getUIFlags',
|
||||||
|
}),
|
||||||
|
getNewConversation() {
|
||||||
|
return {
|
||||||
|
inboxId: this.targetInbox.inbox.id,
|
||||||
|
sourceId: this.targetInbox.source_id,
|
||||||
|
contactId: this.contact.id,
|
||||||
|
message: { content: this.message },
|
||||||
|
};
|
||||||
|
},
|
||||||
|
targetInbox: {
|
||||||
|
get() {
|
||||||
|
return this.selectedInbox || '';
|
||||||
|
},
|
||||||
|
set(value) {
|
||||||
|
this.selectedInbox = value;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
showNoInboxAlert() {
|
||||||
|
if (!this.contact.contactableInboxes) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.inboxes.length === 0 && !this.uiFlags.isFetchingInboxes;
|
||||||
|
},
|
||||||
|
inboxes() {
|
||||||
|
return this.contact.contactableInboxes || [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onCancel() {
|
||||||
|
this.$emit('cancel');
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
this.$emit('success');
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleSubmit() {
|
||||||
|
this.$v.$touch();
|
||||||
|
if (this.$v.$invalid) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const payload = this.getNewConversation;
|
||||||
|
|
||||||
|
await this.onSubmit(payload);
|
||||||
|
this.onSuccess();
|
||||||
|
this.showAlert(this.$t('NEW_CONVERSATION.FORM.SUCCESS_MESSAGE'));
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof ExceptionWithMessage) {
|
||||||
|
this.showAlert(error.data);
|
||||||
|
} else {
|
||||||
|
this.showAlert(this.$t('NEW_CONVERSATION.FORM.ERROR_MESSAGE'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.conversation--form {
|
||||||
|
padding: var(--space-normal) var(--space-large) var(--space-large);
|
||||||
|
|
||||||
|
.columns {
|
||||||
|
padding: 0 var(--space-smaller);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-group-label {
|
||||||
|
font-size: var(--font-size-small);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-input {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
height: 3.9rem;
|
||||||
|
background: var(--color-background-light);
|
||||||
|
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
padding: var(--space-smaller) var(--space-small);
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
|
||||||
|
.contact-name {
|
||||||
|
margin: 0;
|
||||||
|
margin-left: var(--space-small);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.message-input {
|
||||||
|
min-height: 8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-footer {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,51 @@
|
||||||
|
<template>
|
||||||
|
<woot-modal :show.sync="show" :on-close="onCancel">
|
||||||
|
<div class="column content-box">
|
||||||
|
<woot-modal-header
|
||||||
|
:header-title="$t('NEW_CONVERSATION.TITLE')"
|
||||||
|
:header-content="$t('NEW_CONVERSATION.DESC')"
|
||||||
|
/>
|
||||||
|
<conversation-form
|
||||||
|
:contact="contact"
|
||||||
|
:on-submit="onSubmit"
|
||||||
|
@success="onSuccess"
|
||||||
|
@cancel="onCancel"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</woot-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import ConversationForm from './ConversationForm';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
components: {
|
||||||
|
ConversationForm,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
show: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
contact: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const { id } = this.contact;
|
||||||
|
this.$store.dispatch('contacts/fetchContactableInbox', id);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
onCancel() {
|
||||||
|
this.$emit('cancel');
|
||||||
|
},
|
||||||
|
onSuccess() {
|
||||||
|
this.$emit('cancel');
|
||||||
|
},
|
||||||
|
async onSubmit(contactItem) {
|
||||||
|
await this.$store.dispatch('contactConversations/create', contactItem);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
</script>
|
|
@ -1,6 +1,7 @@
|
||||||
import Vue from 'vue';
|
import Vue from 'vue';
|
||||||
import * as types from '../mutation-types';
|
import * as types from '../mutation-types';
|
||||||
import ContactAPI from '../../api/contacts';
|
import ContactAPI from '../../api/contacts';
|
||||||
|
import ConversationApi from '../../api/conversations';
|
||||||
|
|
||||||
const state = {
|
const state = {
|
||||||
records: {},
|
records: {},
|
||||||
|
@ -19,6 +20,30 @@ export const getters = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actions = {
|
export const actions = {
|
||||||
|
create: async ({ commit }, params) => {
|
||||||
|
commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, {
|
||||||
|
isCreating: true,
|
||||||
|
});
|
||||||
|
const { inboxId, message, contactId, sourceId } = params;
|
||||||
|
try {
|
||||||
|
const { data } = await ConversationApi.create({
|
||||||
|
inbox_id: inboxId,
|
||||||
|
contact_id: contactId,
|
||||||
|
source_id: sourceId,
|
||||||
|
message,
|
||||||
|
});
|
||||||
|
commit(types.default.ADD_CONTACT_CONVERSATION, {
|
||||||
|
id: contactId,
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(error);
|
||||||
|
} finally {
|
||||||
|
commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, {
|
||||||
|
isCreating: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
get: async ({ commit }, contactId) => {
|
get: async ({ commit }, contactId) => {
|
||||||
commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, {
|
commit(types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, {
|
||||||
isFetching: true,
|
isFetching: true,
|
||||||
|
@ -53,6 +78,10 @@ export const mutations = {
|
||||||
[types.default.SET_CONTACT_CONVERSATIONS]: ($state, { id, data }) => {
|
[types.default.SET_CONTACT_CONVERSATIONS]: ($state, { id, data }) => {
|
||||||
Vue.set($state.records, id, data);
|
Vue.set($state.records, id, data);
|
||||||
},
|
},
|
||||||
|
[types.default.ADD_CONTACT_CONVERSATION]: ($state, { id, data }) => {
|
||||||
|
const conversations = $state.records[id] || [];
|
||||||
|
Vue.set($state.records, id, [...conversations, data]);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
|
|
@ -83,6 +83,26 @@ export const actions = {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
fetchContactableInbox: async ({ commit }, id) => {
|
||||||
|
commit(types.SET_CONTACT_UI_FLAG, { isFetchingInboxes: true });
|
||||||
|
try {
|
||||||
|
const response = await ContactAPI.getContactableInboxes(id);
|
||||||
|
const contact = {
|
||||||
|
id,
|
||||||
|
contactableInboxes: response.data.payload,
|
||||||
|
};
|
||||||
|
commit(types.SET_CONTACT_ITEM, contact);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response?.data?.message) {
|
||||||
|
throw new ExceptionWithMessage(error.response.data.message);
|
||||||
|
} else {
|
||||||
|
throw new Error(error);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
commit(types.SET_CONTACT_UI_FLAG, { isFetchingInboxes: false });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
updatePresence: ({ commit }, data) => {
|
updatePresence: ({ commit }, data) => {
|
||||||
commit(types.UPDATE_CONTACTS_PRESENCE, data);
|
commit(types.UPDATE_CONTACTS_PRESENCE, data);
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,6 +11,7 @@ const state = {
|
||||||
uiFlags: {
|
uiFlags: {
|
||||||
isFetching: false,
|
isFetching: false,
|
||||||
isFetchingItem: false,
|
isFetchingItem: false,
|
||||||
|
isFetchingInboxes: false,
|
||||||
isUpdating: false,
|
isUpdating: false,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
import * as MutationHelpers from 'shared/helpers/vuex/mutationHelpers';
|
||||||
import * as types from '../mutation-types';
|
import * as types from '../mutation-types';
|
||||||
|
import { INBOX_TYPES } from 'shared/mixins/inboxMixin';
|
||||||
import InboxesAPI from '../../api/inboxes';
|
import InboxesAPI from '../../api/inboxes';
|
||||||
import WebChannel from '../../api/channel/webChannel';
|
import WebChannel from '../../api/channel/webChannel';
|
||||||
import FBChannel from '../../api/channel/fbChannel';
|
import FBChannel from '../../api/channel/fbChannel';
|
||||||
|
@ -41,6 +42,20 @@ export const getters = {
|
||||||
getInboxes($state) {
|
getInboxes($state) {
|
||||||
return $state.records;
|
return $state.records;
|
||||||
},
|
},
|
||||||
|
getNewConversationInboxes($state) {
|
||||||
|
return $state.records.filter(inbox => {
|
||||||
|
const {
|
||||||
|
channel_type: channelType,
|
||||||
|
phone_number: phoneNumber = '',
|
||||||
|
} = inbox;
|
||||||
|
|
||||||
|
const isEmailChannel = channelType === INBOX_TYPES.EMAIL;
|
||||||
|
const isSmsChannel =
|
||||||
|
channelType === INBOX_TYPES.TWILIO &&
|
||||||
|
phoneNumber.startsWith('whatsapp');
|
||||||
|
return isEmailChannel || isSmsChannel;
|
||||||
|
});
|
||||||
|
},
|
||||||
getInbox: $state => inboxId => {
|
getInbox: $state => inboxId => {
|
||||||
const [inbox] = $state.records.filter(
|
const [inbox] = $state.records.filter(
|
||||||
record => record.id === Number(inboxId)
|
record => record.id === Number(inboxId)
|
||||||
|
|
|
@ -38,4 +38,43 @@ describe('#actions', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#create', () => {
|
||||||
|
it('sends correct actions if API is success', async () => {
|
||||||
|
axios.post.mockResolvedValue({ data: conversationList[0] });
|
||||||
|
await actions.create(
|
||||||
|
{ commit },
|
||||||
|
{ inboxId: 1, message: { content: 'hi' }, contactId: 4, sourceId: 5 }
|
||||||
|
);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true }],
|
||||||
|
|
||||||
|
[
|
||||||
|
types.default.ADD_CONTACT_CONVERSATION,
|
||||||
|
{ id: 4, data: conversationList[0] },
|
||||||
|
],
|
||||||
|
[
|
||||||
|
types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG,
|
||||||
|
{ isCreating: false },
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
it('sends correct actions if API is error', async () => {
|
||||||
|
axios.post.mockRejectedValue({ message: 'Incorrect header' });
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
actions.create(
|
||||||
|
{ commit },
|
||||||
|
{ inboxId: 1, message: { content: 'hi' }, contactId: 4, sourceId: 5 }
|
||||||
|
)
|
||||||
|
).rejects.toThrow(Error);
|
||||||
|
expect(commit.mock.calls).toEqual([
|
||||||
|
[types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG, { isCreating: true }],
|
||||||
|
[
|
||||||
|
types.default.SET_CONTACT_CONVERSATIONS_UI_FLAG,
|
||||||
|
{ isCreating: false },
|
||||||
|
],
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -26,4 +26,17 @@ describe('#mutations', () => {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('#ADD_CONTACT_CONVERSATION', () => {
|
||||||
|
it('Adds new contact conversation to records', () => {
|
||||||
|
const state = { records: {} };
|
||||||
|
mutations[types.default.ADD_CONTACT_CONVERSATION](state, {
|
||||||
|
id: 1,
|
||||||
|
data: { id: 1, contact_id: 1, message: 'hello' },
|
||||||
|
});
|
||||||
|
expect(state.records).toEqual({
|
||||||
|
1: [{ id: 1, contact_id: 1, message: 'hello' }],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -114,6 +114,7 @@ export default {
|
||||||
// Contact Conversation
|
// Contact Conversation
|
||||||
SET_CONTACT_CONVERSATIONS_UI_FLAG: 'SET_CONTACT_CONVERSATIONS_UI_FLAG',
|
SET_CONTACT_CONVERSATIONS_UI_FLAG: 'SET_CONTACT_CONVERSATIONS_UI_FLAG',
|
||||||
SET_CONTACT_CONVERSATIONS: 'SET_CONTACT_CONVERSATIONS',
|
SET_CONTACT_CONVERSATIONS: 'SET_CONTACT_CONVERSATIONS',
|
||||||
|
ADD_CONTACT_CONVERSATION: 'ADD_CONTACT_CONVERSATION',
|
||||||
|
|
||||||
// Conversation Label
|
// Conversation Label
|
||||||
SET_CONVERSATION_LABELS_UI_FLAG: 'SET_CONVERSATION_LABELS_UI_FLAG',
|
SET_CONVERSATION_LABELS_UI_FLAG: 'SET_CONVERSATION_LABELS_UI_FLAG',
|
||||||
|
|
|
@ -40,7 +40,7 @@ class Contacts::ContactableInboxesService
|
||||||
end
|
end
|
||||||
|
|
||||||
def twilio_contactable_inbox(inbox)
|
def twilio_contactable_inbox(inbox)
|
||||||
return unless @contact.phone_number
|
return if @contact.phone_number.blank?
|
||||||
|
|
||||||
case inbox.channel.medium
|
case inbox.channel.medium
|
||||||
when 'sms'
|
when 'sms'
|
||||||
|
|
|
@ -1,2 +1,4 @@
|
||||||
json.source_id resource.source_id
|
json.source_id resource.source_id
|
||||||
json.inbox resource.inbox
|
json.inbox do
|
||||||
|
json.partial! 'api/v1/models/inbox.json.jbuilder', resource: resource.inbox
|
||||||
|
end
|
||||||
|
|
Loading…
Reference in a new issue