feat: add contact card

This commit is contained in:
Shivam Mishra
2024-12-06 14:36:24 +05:30
parent 9c6b78700c
commit e7471b8e74
4 changed files with 193 additions and 9 deletions
@@ -21,10 +21,11 @@ import VideoBubble from './bubbles/Video.vue';
import InstagramStoryBubble from './bubbles/InstagramStory.vue';
import AttachmentsBubble from './bubbles/Attachments.vue';
import EmailBubble from './bubbles/Email/Index.vue';
import UnsupportedBubble from './bubbles/Unsupported.vue';
import ContactBubble from './bubbles/Contact.vue';
const LocationBubble = defineAsyncComponent(
() => import('./bubbles/Location.vue')
);
import UnsupportedBubble from './bubbles/Unsupported.vue';
import MessageError from './MessageError.vue';
import MessageMeta from './MessageMeta.vue';
@@ -253,14 +254,19 @@ const componentToRender = computed(() => {
return InstagramStoryBubble;
}
if (props.attachments.length === 1 && !props.content) {
if (props.attachments.length === 1) {
const fileType = props.attachments[0].fileType;
if (fileType === ATTACHMENT_TYPES.IMAGE) return ImageBubble;
if (fileType === ATTACHMENT_TYPES.FILE) return FileBubble;
if (fileType === ATTACHMENT_TYPES.AUDIO) return AudioBubble;
if (fileType === ATTACHMENT_TYPES.VIDEO) return VideoBubble;
if (fileType === ATTACHMENT_TYPES.IG_REEL) return VideoBubble;
if (fileType === ATTACHMENT_TYPES.LOCATION) return LocationBubble;
if (!props.content) {
if (fileType === ATTACHMENT_TYPES.IMAGE) return ImageBubble;
if (fileType === ATTACHMENT_TYPES.FILE) return FileBubble;
if (fileType === ATTACHMENT_TYPES.AUDIO) return AudioBubble;
if (fileType === ATTACHMENT_TYPES.VIDEO) return VideoBubble;
if (fileType === ATTACHMENT_TYPES.IG_REEL) return VideoBubble;
if (fileType === ATTACHMENT_TYPES.LOCATION) return LocationBubble;
}
// Attachment content is the name of the contact
if (fileType === ATTACHMENT_TYPES.CONTACT) return ContactBubble;
}
if (props.attachments.length > 1 && !props.content) {
@@ -0,0 +1,135 @@
<script setup>
import { computed } from 'vue';
import { useAlert } from 'dashboard/composables';
import { useStore } from 'dashboard/composables/store';
import { useI18n } from 'vue-i18n';
import BaseBubble from './Base.vue';
import Icon from 'next/icon/Icon.vue';
import Button from 'next/button/Button.vue';
import {
DuplicateContactException,
ExceptionWithMessage,
} from 'shared/helpers/CustomErrors';
/**
* @typedef {Object} Attachment
* @property {number} id - Unique identifier for the attachment
* @property {number} messageId - ID of the associated message
* @property {'image'|'audio'|'video'|'file'|'location'|'fallback'|'share'|'story_mention'|'contact'|'ig_reel'} fileType - Type of the attachment (file or image)
* @property {number} accountId - ID of the associated account
* @property {string|null} extension - File extension
* @property {string} dataUrl - URL to access the full attachment data
* @property {string} thumbUrl - URL to access the thumbnail version
* @property {number} fileSize - Size of the file in bytes
* @property {number|null} width - Width of the image if applicable
* @property {number|null} height - Height of the image if applicable
*/
/**
* @typedef {Object} Props
* @property {Attachment[]} [attachments=[]] - The attachments associated with the message
*/
const props = defineProps({
content: {
type: String,
required: true,
},
attachments: {
type: Array,
required: true,
},
});
const $store = useStore();
const { t } = useI18n();
const attachment = computed(() => {
return props.attachments[0];
});
const phoneNumber = computed(() => {
return attachment.value.fallbackTitle;
});
const formattedPhoneNumber = computed(() => {
return phoneNumber.value.replace(/\s|-|[A-Za-z]/g, '');
});
const rawPhoneNumber = computed(() => {
return phoneNumber.value.replace(/\D/g, '');
});
const name = computed(() => {
return props.content;
});
function getContactObject() {
const contactItem = {
name: name.value,
phone_number: `+${rawPhoneNumber.value}`,
};
return contactItem;
}
async function filterContactByNumber(searchCandidate) {
const query = {
attribute_key: 'phone_number',
filter_operator: 'equal_to',
values: [searchCandidate],
attribute_model: 'standard',
custom_attribute_type: '',
};
const queryPayload = { payload: [query] };
const contacts = await $store.dispatch('contacts/filter', {
queryPayload,
resetState: false,
});
return contacts.shift();
}
function openContactNewTab(contactId) {
const accountId = window.location.pathname.split('/')[3];
const url = `/app/accounts/${accountId}/contacts/${contactId}`;
window.open(url, '_blank');
}
async function addContact() {
try {
let contact = await filterContactByNumber(rawPhoneNumber);
if (!contact) {
contact = await $store.dispatch('contacts/create', getContactObject());
useAlert(t('CONTACT_FORM.SUCCESS_MESSAGE'));
}
openContactNewTab(contact.id);
} catch (error) {
if (error instanceof DuplicateContactException) {
if (error.data.includes('phone_number')) {
useAlert(t('CONTACT_FORM.FORM.PHONE_NUMBER.DUPLICATE'));
}
} else if (error instanceof ExceptionWithMessage) {
useAlert(error.data);
} else {
useAlert(t('CONTACT_FORM.ERROR_MESSAGE'));
}
}
}
</script>
<template>
<BaseBubble class="p-3 min-w-64">
<div class="grid gap-0.5 mb-2">
<span class="truncate">{{ name }}</span>
<span class="truncate text-sm text-n-slate-11">
{{ phoneNumber }}
</span>
</div>
<div v-if="formattedPhoneNumber" class="link-wrap">
<Button slate solid sm class="w-full" @click.prevent="addContact">
{{ t('CONVERSATION.SAVE_CONTACT') }}
</Button>
</div>
</BaseBubble>
</template>
@@ -73,6 +73,49 @@ export default camelcaseKeys(
},
],
},
{
id: 5296,
content: 'Shivam Mishra',
inbox_id: 486,
conversation_id: 1016,
message_type: 0,
content_type: 'text',
status: 'sent',
content_attributes: {
in_reply_to: null,
},
created_at: 1732710571,
private: false,
source_id: null,
sender: {
additional_attributes: {
created_at_ip: '::1',
},
custom_attributes: {},
email: 'anthony@example.com',
id: 599,
identifier: null,
name: 'anthony',
phone_number: null,
thumbnail: '',
type: 'contact',
},
attachments: [
{
id: 378,
message_id: 5314,
file_type: 'contact',
account_id: 2,
coordinates_lat: 37.7937545,
coordinates_long: -122.3997472,
fallback_title: '+919999999999',
extension: null,
file_size: 287949,
width: null,
height: null,
},
],
},
{
id: 5297,
content: 'Give the team a way to reach you.',
@@ -38,7 +38,7 @@
"REMOVE_SELECTION": "Remove Selection",
"DOWNLOAD": "Download",
"UNKNOWN_FILE_TYPE": "Unknown File",
"SAVE_CONTACT": "Save",
"SAVE_CONTACT": "Save Contact",
"UPLOADING_ATTACHMENTS": "Uploading attachments...",
"REPLIED_TO_STORY": "Replied to your story",
"UNSUPPORTED_MESSAGE": "This message is unsupported. You can view this message on the Facebook / Instagram app.",