diff --git a/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMerge.vue b/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMerge.vue index 65cbfafe2..2a0e79931 100644 --- a/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMerge.vue +++ b/app/javascript/dashboard/components-next/Contacts/ContactsSidebar/ContactMerge.vue @@ -20,7 +20,7 @@ const props = defineProps({ }, }); -const emit = defineEmits(['goToContactsList']); +const emit = defineEmits(['goToContactsList', 'resetTab']); const { t } = useI18n(); const store = useStore(); @@ -74,6 +74,9 @@ const onContactSearch = debounce( ); const resetState = () => { + if (state.primaryContactId === null) { + emit('resetTab'); + } state.primaryContactId = null; searchResults.value = []; isSearching.value = false; diff --git a/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue b/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue index 3f92df408..d46ffd850 100644 --- a/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue +++ b/app/javascript/dashboard/components-next/Contacts/Pages/ContactDetails.vue @@ -130,15 +130,28 @@ const handleAvatarDelete = async () => {

{{ selectedContact?.name }}

- - {{ $t('CONTACTS_LAYOUT.DETAILS.CREATED_AT', { date: createdAt }) }} - • - {{ - $t('CONTACTS_LAYOUT.DETAILS.LAST_ACTIVITY', { - date: lastActivityAt, - }) - }} - +
+ + + {{ selectedContact?.identifier }} + + + + {{ $t('CONTACTS_LAYOUT.DETAILS.CREATED_AT', { date: createdAt }) }} + • + {{ + $t('CONTACTS_LAYOUT.DETAILS.LAST_ACTIVITY', { + date: lastActivityAt, + }) + }} + +
diff --git a/app/javascript/dashboard/components-next/NewConversation/helpers/composeConversationHelper.js b/app/javascript/dashboard/components-next/NewConversation/helpers/composeConversationHelper.js index 119552f61..ef3d0e88d 100644 --- a/app/javascript/dashboard/components-next/NewConversation/helpers/composeConversationHelper.js +++ b/app/javascript/dashboard/components-next/NewConversation/helpers/composeConversationHelper.js @@ -1,12 +1,8 @@ import { INBOX_TYPES } from 'dashboard/helper/inbox'; +import { getInboxIconByType } from 'dashboard/helper/inbox'; import camelcaseKeys from 'camelcase-keys'; import ContactAPI from 'dashboard/api/contacts'; -export const convertChannelTypeToLabel = channelType => { - const [, type] = channelType.split('::'); - return type ? type.charAt(0).toUpperCase() + type.slice(1) : channelType; -}; - export const generateLabelForContactableInboxesList = ({ name, email, @@ -22,7 +18,7 @@ export const generateLabelForContactableInboxesList = ({ ) { return `${name} (${phoneNumber})`; } - return `${name} (${convertChannelTypeToLabel(channelType)})`; + return name; }; export const buildContactableInboxesList = contactInboxes => { @@ -30,6 +26,7 @@ export const buildContactableInboxesList = contactInboxes => { return contactInboxes.map( ({ name, id, email, channelType, phoneNumber, ...rest }) => ({ id, + icon: getInboxIconByType(channelType, phoneNumber, 'line'), label: generateLabelForContactableInboxesList({ name, email, diff --git a/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js b/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js index dcfa3b051..ca30235d1 100644 --- a/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js +++ b/app/javascript/dashboard/components-next/NewConversation/helpers/specs/composeConversationHelper.spec.js @@ -6,19 +6,6 @@ import * as helpers from '../composeConversationHelper'; vi.mock('dashboard/api/contacts'); describe('composeConversationHelper', () => { - describe('convertChannelTypeToLabel', () => { - it('converts channel type with namespace to capitalized label', () => { - expect(helpers.convertChannelTypeToLabel('Channel::Email')).toBe('Email'); - expect(helpers.convertChannelTypeToLabel('Channel::Whatsapp')).toBe( - 'Whatsapp' - ); - }); - - it('returns original value if no namespace found', () => { - expect(helpers.convertChannelTypeToLabel('email')).toBe('email'); - }); - }); - describe('generateLabelForContactableInboxesList', () => { const contact = { name: 'John Doe', @@ -59,7 +46,7 @@ describe('composeConversationHelper', () => { ...contact, channelType: 'Channel::Api', }) - ).toBe('John Doe (Api)'); + ).toBe('John Doe'); }); }); @@ -83,6 +70,7 @@ describe('composeConversationHelper', () => { const result = helpers.buildContactableInboxesList(inboxes); expect(result[0]).toMatchObject({ id: 1, + icon: 'i-ri-mail-line', label: 'Email Inbox (support@example.com)', action: 'inbox', value: 1, diff --git a/app/javascript/dashboard/components/layout/config/sidebarItems/contacts.js b/app/javascript/dashboard/components/layout/config/sidebarItems/contacts.js index 3cef2237b..e4150d47d 100644 --- a/app/javascript/dashboard/components/layout/config/sidebarItems/contacts.js +++ b/app/javascript/dashboard/components/layout/config/sidebarItems/contacts.js @@ -15,7 +15,7 @@ const contacts = accountId => ({ icon: 'contact-card-group', label: 'ALL_CONTACTS', hasSubMenu: false, - toState: frontendURL(`accounts/${accountId}/contacts`), + toState: frontendURL(`accounts/${accountId}/contacts?page=1`), toStateName: 'contacts_dashboard_index', }, ], diff --git a/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue b/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue index efe89d4c6..c36b6f611 100644 --- a/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue +++ b/app/javascript/dashboard/components/layout/sidebarComponents/Secondary.vue @@ -134,7 +134,7 @@ export default { icon: 'number-symbol', label: 'TAGGED_WITH', hasSubMenu: true, - key: 'label', + key: 'labels', newLink: this.showNewLink(FEATURE_FLAGS.TEAM_MANAGEMENT), newLinkTag: 'NEW_LABEL', toState: frontendURL(`accounts/${this.accountId}/settings/labels`), @@ -147,7 +147,7 @@ export default { color: label.color, truncateLabel: true, toState: frontendURL( - `accounts/${this.accountId}/labels/${label.title}/contacts` + `accounts/${this.accountId}/contacts/labels/${label.title}` ), })), }; @@ -194,7 +194,7 @@ export default { icon: 'folder', label: 'CUSTOM_VIEWS_SEGMENTS', hasSubMenu: true, - key: 'custom_view', + key: 'segments', children: this.customViews .filter(view => view.filter_type === 'contact') .map(view => ({ @@ -202,7 +202,7 @@ export default { label: view.name, truncateLabel: true, toState: frontendURL( - `accounts/${this.accountId}/contacts/custom_view/${view.id}` + `accounts/${this.accountId}/contacts/segments/${view.id}` ), })), }; @@ -247,7 +247,7 @@ export default { { switch (type) { @@ -110,15 +110,16 @@ export const getInboxClassByType = (type, phoneNumber) => { } }; -export const getInboxIconByType = (type, phoneNumber) => { +export const getInboxIconByType = (type, phoneNumber, variant = 'fill') => { // Special case for Twilio (whatsapp and sms) if (type === INBOX_TYPES.TWILIO) { return phoneNumber?.startsWith('whatsapp') - ? 'i-ri-whatsapp-fill' - : 'i-ri-chat-1-fill'; + ? `i-ri-whatsapp-${variant}` + : `i-ri-chat-1-${variant}`; } - return INBOX_ICON_MAP[type] ?? DEFAULT_ICON; + const baseIcon = INBOX_ICON_MAP[type] ?? DEFAULT_ICON; + return `${baseIcon}-${variant}`; }; export const getInboxWarningIconClass = (type, reauthorizationRequired) => { diff --git a/app/javascript/dashboard/helper/specs/inbox.spec.js b/app/javascript/dashboard/helper/specs/inbox.spec.js index 621c131f2..0d5625603 100644 --- a/app/javascript/dashboard/helper/specs/inbox.spec.js +++ b/app/javascript/dashboard/helper/specs/inbox.spec.js @@ -41,70 +41,112 @@ describe('#Inbox Helpers', () => { }); describe('getInboxIconByType', () => { - it('returns correct icon for web widget', () => { - expect(getInboxIconByType(INBOX_TYPES.WEB)).toBe('i-ri-global-fill'); + describe('fill variant (default)', () => { + it('returns correct icon for web widget', () => { + expect(getInboxIconByType(INBOX_TYPES.WEB)).toBe('i-ri-global-fill'); + }); + + it('returns correct icon for Facebook', () => { + expect(getInboxIconByType(INBOX_TYPES.FB)).toBe('i-ri-messenger-fill'); + }); + + it('returns correct icon for Twitter', () => { + expect(getInboxIconByType(INBOX_TYPES.TWITTER)).toBe( + 'i-ri-twitter-x-fill' + ); + }); + + it('returns correct icon for WhatsApp', () => { + expect(getInboxIconByType(INBOX_TYPES.WHATSAPP)).toBe( + 'i-ri-whatsapp-fill' + ); + }); + + it('returns correct icon for API', () => { + expect(getInboxIconByType(INBOX_TYPES.API)).toBe('i-ri-cloudy-fill'); + }); + + it('returns correct icon for Email', () => { + expect(getInboxIconByType(INBOX_TYPES.EMAIL)).toBe('i-ri-mail-fill'); + }); + + it('returns correct icon for Telegram', () => { + expect(getInboxIconByType(INBOX_TYPES.TELEGRAM)).toBe( + 'i-ri-telegram-fill' + ); + }); + + it('returns correct icon for Line', () => { + expect(getInboxIconByType(INBOX_TYPES.LINE)).toBe('i-ri-line-fill'); + }); + + it('returns default icon for unknown type', () => { + expect(getInboxIconByType('UNKNOWN_TYPE')).toBe('i-ri-chat-1-fill'); + }); + + it('returns default icon for undefined type', () => { + expect(getInboxIconByType(undefined)).toBe('i-ri-chat-1-fill'); + }); }); - it('returns correct icon for Facebook', () => { - expect(getInboxIconByType(INBOX_TYPES.FB)).toBe('i-ri-messenger-fill'); - }); + describe('line variant', () => { + it('returns correct line icon for web widget', () => { + expect(getInboxIconByType(INBOX_TYPES.WEB, null, 'line')).toBe( + 'i-ri-global-line' + ); + }); - it('returns correct icon for Twitter', () => { - expect(getInboxIconByType(INBOX_TYPES.TWITTER)).toBe( - 'i-ri-twitter-x-fill' - ); + it('returns correct line icon for Facebook', () => { + expect(getInboxIconByType(INBOX_TYPES.FB, null, 'line')).toBe( + 'i-ri-messenger-line' + ); + }); + + it('returns correct line icon for unknown type', () => { + expect(getInboxIconByType('UNKNOWN_TYPE', null, 'line')).toBe( + 'i-ri-chat-1-line' + ); + }); }); describe('Twilio cases', () => { - it('returns WhatsApp icon for Twilio WhatsApp number', () => { - expect( - getInboxIconByType(INBOX_TYPES.TWILIO, 'whatsapp:+1234567890') - ).toBe('i-ri-whatsapp-fill'); + describe('fill variant', () => { + it('returns WhatsApp icon for Twilio WhatsApp number', () => { + expect( + getInboxIconByType(INBOX_TYPES.TWILIO, 'whatsapp:+1234567890') + ).toBe('i-ri-whatsapp-fill'); + }); + + it('returns SMS icon for regular Twilio number', () => { + expect(getInboxIconByType(INBOX_TYPES.TWILIO, '+1234567890')).toBe( + 'i-ri-chat-1-fill' + ); + }); + + it('returns SMS icon when phone number is undefined', () => { + expect(getInboxIconByType(INBOX_TYPES.TWILIO, undefined)).toBe( + 'i-ri-chat-1-fill' + ); + }); }); - it('returns SMS icon for regular Twilio number', () => { - expect(getInboxIconByType(INBOX_TYPES.TWILIO, '+1234567890')).toBe( - 'i-ri-chat-1-fill' - ); + describe('line variant', () => { + it('returns WhatsApp line icon for Twilio WhatsApp number', () => { + expect( + getInboxIconByType( + INBOX_TYPES.TWILIO, + 'whatsapp:+1234567890', + 'line' + ) + ).toBe('i-ri-whatsapp-line'); + }); + + it('returns SMS line icon for regular Twilio number', () => { + expect( + getInboxIconByType(INBOX_TYPES.TWILIO, '+1234567890', 'line') + ).toBe('i-ri-chat-1-line'); + }); }); - - it('returns SMS icon when phone number is undefined', () => { - expect(getInboxIconByType(INBOX_TYPES.TWILIO, undefined)).toBe( - 'i-ri-chat-1-fill' - ); - }); - }); - - it('returns correct icon for WhatsApp', () => { - expect(getInboxIconByType(INBOX_TYPES.WHATSAPP)).toBe( - 'i-ri-whatsapp-fill' - ); - }); - - it('returns correct icon for API', () => { - expect(getInboxIconByType(INBOX_TYPES.API)).toBe('i-ri-cloudy-fill'); - }); - - it('returns correct icon for Email', () => { - expect(getInboxIconByType(INBOX_TYPES.EMAIL)).toBe('i-ri-mail-fill'); - }); - - it('returns correct icon for Telegram', () => { - expect(getInboxIconByType(INBOX_TYPES.TELEGRAM)).toBe( - 'i-ri-telegram-fill' - ); - }); - - it('returns correct icon for Line', () => { - expect(getInboxIconByType(INBOX_TYPES.LINE)).toBe('i-ri-line-fill'); - }); - - it('returns default icon for unknown type', () => { - expect(getInboxIconByType('UNKNOWN_TYPE')).toBe('i-ri-chat-1-fill'); - }); - - it('returns default icon for undefined type', () => { - expect(getInboxIconByType(undefined)).toBe('i-ri-chat-1-fill'); }); }); diff --git a/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue b/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue index d22048a37..8b07646e0 100644 --- a/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue +++ b/app/javascript/dashboard/routes/dashboard/contacts/pages/ContactManageView.vue @@ -145,6 +145,7 @@ onMounted(() => { ref="contactMergeRef" :selected-contact="selectedContact" @go-to-contacts-list="goToContactsList" + @reset-tab="handleTabChange(CONTACT_TABS_OPTIONS[0])" />