From c17380d48a8c8f6e974b6414ca40f61fd2509219 Mon Sep 17 00:00:00 2001 From: Nithin David Thomas Date: Fri, 19 Feb 2021 20:22:58 +0530 Subject: [PATCH] Feat: Create contact from contacts page (#1806) * Add contact create modal to contacts page * Test cases * Review fixes --- .../dashboard/i18n/locale/en/contact.json | 9 ++- .../contacts/components/ContactsView.vue | 9 ++- .../dashboard/contacts/components/Header.vue | 30 ++++++++-- .../conversation/contact/ContactForm.vue | 15 ++++- .../conversation/contact/CreateContact.vue | 55 +++++++++++++++++++ .../conversation/contact/EditContact.vue | 5 ++ .../store/modules/contacts/actions.js | 21 ++++++- .../modules/specs/contacts/actions.spec.js | 46 +++++++++++++++- app/javascript/shared/helpers/CustomErrors.js | 8 +++ 9 files changed, 187 insertions(+), 11 deletions(-) create mode 100644 app/javascript/dashboard/routes/dashboard/conversation/contact/CreateContact.vue 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'; + } +}