diff --git a/app/javascript/dashboard/i18n/locale/en/contact.json b/app/javascript/dashboard/i18n/locale/en/contact.json
index cc4e20bb5..f5cabeac7 100644
--- a/app/javascript/dashboard/i18n/locale/en/contact.json
+++ b/app/javascript/dashboard/i18n/locale/en/contact.json
@@ -45,6 +45,11 @@
"TITLE": "Edit contact",
"DESC": "Edit contact details"
},
+ "CREATE_CONTACT": {
+ "BUTTON_LABEL": "New Contact",
+ "TITLE": "Create new contact",
+ "DESC": "Add basic information details about the contact."
+ },
"CONTACT_FORM": {
"FORM": {
"SUBMIT": "Submit",
@@ -95,9 +100,9 @@
}
}
},
- "SUCCESS_MESSAGE": "Updated contact successfully",
+ "SUCCESS_MESSAGE": "Contact saved successfully",
"CONTACT_ALREADY_EXIST": "This email address is in use for another contact.",
- "ERROR_MESSAGE": "There was an error updating the contact, please try again"
+ "ERROR_MESSAGE": "There was an error, please try again"
},
"CONTACTS_PAGE": {
"HEADER": "Contacts",
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsView.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsView.vue
index b55efcf31..f483523eb 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsView.vue
+++ b/app/javascript/dashboard/routes/dashboard/contacts/components/ContactsView.vue
@@ -6,6 +6,7 @@
:on-search-submit="onSearchSubmit"
this-selected-contact-id=""
:on-input-search="onInputSearch"
+ :on-toggle-create="onToggleCreate"
/>
+
@@ -34,6 +36,7 @@ import { mapGetters } from 'vuex';
import ContactsHeader from './Header';
import ContactsTable from './ContactsTable';
import ContactInfoPanel from './ContactInfoPanel';
+import CreateContact from 'dashboard/routes/dashboard/conversation/contact/CreateContact';
import TableFooter from 'dashboard/components/widgets/TableFooter';
export default {
@@ -42,11 +45,12 @@ export default {
ContactsTable,
TableFooter,
ContactInfoPanel,
+ CreateContact,
},
data() {
return {
searchQuery: '',
- showEditModal: false,
+ showCreateModal: false,
selectedContactId: '',
};
},
@@ -123,6 +127,9 @@ export default {
this.selectedContactId = '';
this.showContactInfoPanelPane = false;
},
+ onToggleCreate() {
+ this.showCreateModal = !this.showCreateModal;
+ },
},
};
diff --git a/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue b/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue
index b8ca4c64a..8e774a4ba 100644
--- a/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue
+++ b/app/javascript/dashboard/routes/dashboard/contacts/components/Header.vue
@@ -24,6 +24,11 @@
@click="onSearchSubmit"
/>
+
+
@@ -45,6 +50,15 @@ export default {
type: Function,
default: () => {},
},
+ onToggleCreate: {
+ type: Function,
+ default: () => {},
+ },
+ },
+ data() {
+ return {
+ showCreateModal: false,
+ };
},
computed: {
searchButtonClass() {
@@ -69,35 +83,41 @@ export default {
width: 100%;
margin-bottom: var(--space-slab);
}
+
+.right-aligned-wrap {
+ display: flex;
+}
+
.search-wrap {
width: 400px;
- height: 3.6rem;
+ height: 3.8rem;
display: flex;
align-items: center;
position: relative;
+ margin-right: var(--space-small);
.search-icon {
position: absolute;
top: 1px;
left: var(--space-one);
- height: 3.6rem;
+ height: 3.8rem;
line-height: 3.6rem;
font-size: var(--font-size-medium);
color: var(--b-700);
}
.contact-search {
margin: 0;
- height: 3.6rem;
+ height: 3.8rem;
width: 100%;
padding-left: var(--space-large);
padding-right: 6rem;
+ border-color: var(--s-100);
}
.button {
margin-left: var(--space-small);
height: 3.2rem;
- top: var(--space-micro);
- right: var(--space-micro);
+ right: var(--space-smaller);
position: absolute;
padding: 0 var(--space-small);
transition: transform 100ms linear;
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue
index 66f0e399f..dfa7b205f 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactForm.vue
@@ -81,7 +81,10 @@
diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/EditContact.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/EditContact.vue
index 9d9569f62..b6bf2c8bd 100644
--- a/app/javascript/dashboard/routes/dashboard/conversation/contact/EditContact.vue
+++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/EditContact.vue
@@ -11,6 +11,8 @@
:contact="contact"
:in-progress="uiFlags.isUpdating"
:on-submit="onSubmit"
+ @success="onSuccess"
+ @cancel="onCancel"
/>
@@ -45,6 +47,9 @@ export default {
onCancel() {
this.$emit('cancel');
},
+ onSuccess() {
+ this.$emit('cancel');
+ },
async onSubmit(contactItem) {
await this.$store.dispatch('contacts/update', contactItem);
},
diff --git a/app/javascript/dashboard/store/modules/contacts/actions.js b/app/javascript/dashboard/store/modules/contacts/actions.js
index fd16a8de6..039b69d2c 100644
--- a/app/javascript/dashboard/store/modules/contacts/actions.js
+++ b/app/javascript/dashboard/store/modules/contacts/actions.js
@@ -1,4 +1,7 @@
-import { DuplicateContactException } from 'shared/helpers/CustomErrors';
+import {
+ DuplicateContactException,
+ ExceptionWithMessage,
+} from 'shared/helpers/CustomErrors';
import types from '../../mutation-types';
import ContactAPI from '../../../api/contacts';
@@ -64,6 +67,22 @@ export const actions = {
}
},
+ create: async ({ commit }, userObject) => {
+ commit(types.SET_CONTACT_UI_FLAG, { isCreating: true });
+ try {
+ const response = await ContactAPI.create(userObject);
+ commit(types.SET_CONTACT_ITEM, response.data.payload.contact);
+ commit(types.SET_CONTACT_UI_FLAG, { isCreating: false });
+ } catch (error) {
+ commit(types.SET_CONTACT_UI_FLAG, { isCreating: false });
+ if (error.response?.data?.message) {
+ throw new ExceptionWithMessage(error.response.data.message);
+ } else {
+ throw new Error(error);
+ }
+ }
+ },
+
updatePresence: ({ commit }, data) => {
commit(types.UPDATE_CONTACTS_PRESENCE, data);
},
diff --git a/app/javascript/dashboard/store/modules/specs/contacts/actions.spec.js b/app/javascript/dashboard/store/modules/specs/contacts/actions.spec.js
index 70e937b8b..4bc8b6723 100644
--- a/app/javascript/dashboard/store/modules/specs/contacts/actions.spec.js
+++ b/app/javascript/dashboard/store/modules/specs/contacts/actions.spec.js
@@ -2,7 +2,10 @@ import axios from 'axios';
import Contacts from '../../contacts';
import types from '../../../mutation-types';
import contactList from './fixtures';
-import { DuplicateContactException } from '../../../../../shared/helpers/CustomErrors';
+import {
+ DuplicateContactException,
+ ExceptionWithMessage,
+} from '../../../../../shared/helpers/CustomErrors';
const { actions } = Contacts;
@@ -95,6 +98,47 @@ describe('#actions', () => {
});
});
+ describe('#create', () => {
+ it('sends correct mutations if API is success', async () => {
+ axios.post.mockResolvedValue({
+ data: { payload: { contact: contactList[0] } },
+ });
+ await actions.create({ commit }, contactList[0]);
+ expect(commit.mock.calls).toEqual([
+ [types.SET_CONTACT_UI_FLAG, { isCreating: true }],
+ [types.SET_CONTACT_ITEM, contactList[0]],
+ [types.SET_CONTACT_UI_FLAG, { isCreating: false }],
+ ]);
+ });
+ it('sends correct actions if API is error', async () => {
+ axios.post.mockRejectedValue({ message: 'Incorrect header' });
+ await expect(actions.create({ commit }, contactList[0])).rejects.toThrow(
+ Error
+ );
+ expect(commit.mock.calls).toEqual([
+ [types.SET_CONTACT_UI_FLAG, { isCreating: true }],
+ [types.SET_CONTACT_UI_FLAG, { isCreating: false }],
+ ]);
+ });
+
+ it('sends correct actions if email is already present', async () => {
+ axios.post.mockRejectedValue({
+ response: {
+ data: {
+ message: 'Email exists already',
+ },
+ },
+ });
+ await expect(actions.create({ commit }, contactList[0])).rejects.toThrow(
+ ExceptionWithMessage
+ );
+ expect(commit.mock.calls).toEqual([
+ [types.SET_CONTACT_UI_FLAG, { isCreating: true }],
+ [types.SET_CONTACT_UI_FLAG, { isCreating: false }],
+ ]);
+ });
+ });
+
describe('#setContact', () => {
it('returns correct mutations', () => {
const data = { id: 1, name: 'john doe', availability_status: 'online' };
diff --git a/app/javascript/shared/helpers/CustomErrors.js b/app/javascript/shared/helpers/CustomErrors.js
index b9e74e917..4f31eb291 100644
--- a/app/javascript/shared/helpers/CustomErrors.js
+++ b/app/javascript/shared/helpers/CustomErrors.js
@@ -1,3 +1,4 @@
+/* eslint-disable max-classes-per-file */
export class DuplicateContactException extends Error {
constructor(data) {
super('DUPLICATE_CONTACT');
@@ -5,3 +6,10 @@ export class DuplicateContactException extends Error {
this.name = 'DuplicateContactException';
}
}
+export class ExceptionWithMessage extends Error {
+ constructor(data) {
+ super('ERROR_WITH_MESSAGE');
+ this.data = data;
+ this.name = 'ExceptionWithMessage';
+ }
+}