From 64f6bfc811bdfa6b3e35e32c1d6959d5cc464438 Mon Sep 17 00:00:00 2001 From: Sivin Varghese <64252451+iamsivin@users.noreply.github.com> Date: Tue, 14 Apr 2026 18:23:40 +0530 Subject: [PATCH] feat: Inline edit support for contact info (#13976) # Pull Request Template ## Description This PR adds inline editing support for contact name, phone number, email, and company fields in the conversation contact sidebar ## Type of change - [x] New feature (non-breaking change which adds functionality) ## How Has This Been Tested? **Screencast** https://github.com/user-attachments/assets/e9f8e37d-145b-4736-b27a-eb9ea66847bd ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [ ] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [ ] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules --------- Co-authored-by: Pranav Co-authored-by: Muhsin Keloth --- .../inline-input/InlineInput.vue | 13 ++- .../dashboard/i18n/locale/en/contact.json | 1 + .../conversation/contact/ContactInfo.vue | 92 ++++++++++++++++++- .../conversation/contact/ContactInfoRow.vue | 79 +++++++++++++++- .../store/modules/contacts/actions.js | 6 +- app/javascript/shared/helpers/CustomErrors.js | 11 ++- 6 files changed, 196 insertions(+), 6 deletions(-) diff --git a/app/javascript/dashboard/components-next/inline-input/InlineInput.vue b/app/javascript/dashboard/components-next/inline-input/InlineInput.vue index 81d7719cf..d65bb4257 100644 --- a/app/javascript/dashboard/components-next/inline-input/InlineInput.vue +++ b/app/javascript/dashboard/components-next/inline-input/InlineInput.vue @@ -36,7 +36,13 @@ const props = defineProps({ }, }); -const emit = defineEmits(['enterPress', 'input', 'blur', 'focus']); +const emit = defineEmits([ + 'enterPress', + 'escapePress', + 'input', + 'blur', + 'focus', +]); const modelValue = defineModel({ type: [String, Number], @@ -49,6 +55,10 @@ const onEnterPress = () => { emit('enterPress'); }; +const onEscapePress = () => { + emit('escapePress'); +}; + const handleInput = event => { emit('input', event.target.value); modelValue.value = event.target.value; @@ -102,6 +112,7 @@ defineExpose({ @focus="handleFocus" @blur="handleBlur" @keydown.enter.prevent="onEnterPress" + @keydown.escape.prevent="onEscapePress" /> diff --git a/app/javascript/dashboard/i18n/locale/en/contact.json b/app/javascript/dashboard/i18n/locale/en/contact.json index 803cd66cb..9234071d1 100644 --- a/app/javascript/dashboard/i18n/locale/en/contact.json +++ b/app/javascript/dashboard/i18n/locale/en/contact.json @@ -20,6 +20,7 @@ "CALL": "Call", "CALL_INITIATED": "Calling the contact…", "CALL_FAILED": "Unable to start the call. Please try again.", + "CLICK_TO_EDIT": "Click to edit", "VOICE_INBOX_PICKER": { "TITLE": "Choose a voice inbox" }, diff --git a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue index 3899d1ddf..2b6001729 100644 --- a/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue +++ b/app/javascript/dashboard/routes/dashboard/conversation/contact/ContactInfo.vue @@ -1,6 +1,10 @@ @@ -194,10 +254,26 @@ export default {
+

{{ contact.name }} +

{ + this.$refs.editInput?.focus(); + }); + }, + saveEdit() { + if (!this.isEditing) return; + this.isEditing = false; + const trimmed = this.editValue.trim(); + if (trimmed !== (this.value || '')) { + this.$emit('update', trimmed); + } + }, + cancelEdit() { + this.isEditing = false; + }, }, }; diff --git a/app/javascript/dashboard/store/modules/contacts/actions.js b/app/javascript/dashboard/store/modules/contacts/actions.js index d7f87b776..d0029207e 100644 --- a/app/javascript/dashboard/store/modules/contacts/actions.js +++ b/app/javascript/dashboard/store/modules/contacts/actions.js @@ -36,7 +36,11 @@ const buildContactFormData = contactParams => { export const handleContactOperationErrors = error => { if (error.response?.status === 422) { - throw new DuplicateContactException(error.response.data.attributes); + const exception = new DuplicateContactException( + error.response.data.attributes + ); + exception.message = error.response.data.message || exception.message; + throw exception; } else if (error.response?.data?.message) { throw new ExceptionWithMessage(error.response.data.message); } else { diff --git a/app/javascript/shared/helpers/CustomErrors.js b/app/javascript/shared/helpers/CustomErrors.js index 4f31eb291..ef5947189 100644 --- a/app/javascript/shared/helpers/CustomErrors.js +++ b/app/javascript/shared/helpers/CustomErrors.js @@ -1,10 +1,19 @@ /* eslint-disable max-classes-per-file */ export class DuplicateContactException extends Error { + static DEFAULT_MESSAGE = 'DUPLICATE_CONTACT'; + constructor(data) { - super('DUPLICATE_CONTACT'); + super(DuplicateContactException.DEFAULT_MESSAGE); this.data = data; this.name = 'DuplicateContactException'; } + + /** Server or client may assign `message` after construction; otherwise still DEFAULT_MESSAGE. */ + get contactErrorDetail() { + return this.message === DuplicateContactException.DEFAULT_MESSAGE + ? null + : this.message; + } } export class ExceptionWithMessage extends Error { constructor(data) {