Skip to content

Commit

Permalink
feat: Allows multiple emails and phones in edit contact info (#32900)
Browse files Browse the repository at this point in the history
* feat: get contact data from the new endpoint

* feat: allow multiple emails and phone numbers

* chore: `ContactManagerInput` to ts

* chore: add fields validations
  • Loading branch information
dougfabris committed Oct 16, 2024
1 parent e87624b commit b5d9f84
Show file tree
Hide file tree
Showing 14 changed files with 270 additions and 203 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,12 @@ import React from 'react';
import AutoCompleteAgent from '../../components/AutoCompleteAgent';
import { useHasLicenseModule } from '../../hooks/useHasLicenseModule';

export const ContactManager = ({ value: userId, handler }) => {
type ContactManagerInputProps = {
value: string;
handler: (currentValue: string) => void;
};

export const ContactManagerInput = ({ value: userId, handler }: ContactManagerInputProps) => {
const t = useTranslation();
const hasLicense = useHasLicenseModule('livechat-enterprise');

Expand All @@ -23,4 +28,4 @@ export const ContactManager = ({ value: userId, handler }) => {
);
};

export default ContactManager;
export default ContactManagerInput;
4 changes: 2 additions & 2 deletions apps/meteor/client/views/omnichannel/additionalForms.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import BusinessHoursMultiple from '../../omnichannel/additionalForms/BusinessHoursMultiple';
import ContactManager from '../../omnichannel/additionalForms/ContactManager';
import ContactManagerInput from '../../omnichannel/additionalForms/ContactManagerInput';
import CurrentChatTags from '../../omnichannel/additionalForms/CurrentChatTags';
import CustomFieldsAdditionalForm from '../../omnichannel/additionalForms/CustomFieldsAdditionalForm';
import DepartmentBusinessHours from '../../omnichannel/additionalForms/DepartmentBusinessHours';
Expand All @@ -20,7 +20,7 @@ export {
EeTextAreaInput,
BusinessHoursMultiple,
EeTextInput,
ContactManager,
ContactManagerInput,
CurrentChatTags,
DepartmentBusinessHours,
DepartmentForwarding,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,23 @@
import type { ILivechatContact, Serialized } from '@rocket.chat/core-typings';
import { Box, IconButton, Tabs, TabsItem } from '@rocket.chat/fuselage';
import { UserAvatar } from '@rocket.chat/ui-avatar';
import type { RouteName } from '@rocket.chat/ui-contexts';
import { useTranslation, useEndpoint, usePermission, useRouter, useRouteParameter } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React from 'react';

import { ContextualbarHeader, ContextualbarIcon, ContextualbarTitle, ContextualbarClose } from '../../../components/Contextualbar';
import { useFormatDate } from '../../../hooks/useFormatDate';
import { FormSkeleton } from '../directory/components/FormSkeleton';
import { useContactRoute } from '../hooks/useContactRoute';
import ContactInfoChannels from './tabs/ContactInfoChannels';
import ContactInfoDetails from './tabs/ContactInfoDetails';
import ContactInfoHistory from './tabs/ContactInfoHistory';
import { ContextualbarHeader, ContextualbarIcon, ContextualbarTitle, ContextualbarClose } from '../../../../components/Contextualbar';
import { useFormatDate } from '../../../../hooks/useFormatDate';
import { useContactRoute } from '../../hooks/useContactRoute';
import ContactInfoChannels from '../tabs/ContactInfoChannels';
import ContactInfoDetails from '../tabs/ContactInfoDetails';
import ContactInfoHistory from '../tabs/ContactInfoHistory';

type ContactInfoProps = {
id: string;
contact: Serialized<ILivechatContact>;
onClose: () => void;
rid?: string;
route?: RouteName;
};

const ContactInfo = ({ id: contactId, onClose }: ContactInfoProps) => {
const ContactInfo = ({ contact, onClose }: ContactInfoProps) => {
const t = useTranslation();

const { getRouteName } = useRouter();
Expand All @@ -36,42 +33,18 @@ const ContactInfo = ({ id: contactId, onClose }: ContactInfoProps) => {
const getCustomFields = useEndpoint('GET', '/v1/livechat/custom-fields');
const { data: { customFields } = {} } = useQuery(['/v1/livechat/custom-fields'], () => getCustomFields());

const getContact = useEndpoint('GET', '/v1/omnichannel/contact');
const {
data: { contact } = {},
isInitialLoading,
isError,
} = useQuery(['/v1/omnichannel/contact', contactId], () => getContact({ contactId }), {
enabled: canViewCustomFields && !!contactId,
});

if (isInitialLoading) {
return (
<Box pi={24}>
<FormSkeleton />
</Box>
);
}

if (isError || !contact) {
return <Box mbs={16}>{t('Contact_not_found')}</Box>;
}

const { username, visitorEmails, phone, ts, livechatData, lastChat, contactManager } = contact;
const { name, emails, phones, createdAt, lastChat, contactManager, customFields: userCustomFields } = contact;

const showContactHistory = (currentRouteName === 'live' || currentRouteName === 'omnichannel-directory') && lastChat;

const [{ phoneNumber = '' }] = phone ?? [{}];
const [{ address: email = '' }] = visitorEmails ?? [{}];

const checkIsVisibleAndScopeVisitor = (key: string) => {
const field = customFields?.find(({ _id }) => _id === key);
return field?.visibility === 'visible' && field?.scope === 'visitor';
};

// Serialized does not like unknown :(
const customFieldEntries = canViewCustomFields
? Object.entries((livechatData ?? {}) as unknown as Record<string, string>).filter(
? Object.entries((userCustomFields ?? {}) as unknown as Record<string, string>).filter(
([key, value]) => checkIsVisibleAndScopeVisitor(key) && value,
)
: [];
Expand All @@ -84,12 +57,12 @@ const ContactInfo = ({ id: contactId, onClose }: ContactInfoProps) => {
<ContextualbarClose onClick={onClose} />
</ContextualbarHeader>
<Box display='flex' pi={24}>
{username && (
{name && (
<Box width='100%' pb={16} display='flex' alignItems='center' justifyContent='space-between'>
<Box display='flex'>
<UserAvatar size='x40' title={username} username={username} />
<UserAvatar size='x40' title={name} username={name} />
<Box mis={16} display='flex' flexDirection='column'>
<Box fontScale='h4'>{username}</Box>
<Box fontScale='h4'>{name}</Box>
{lastChat && <Box fontScale='c1'>{`${t('Last_Chat')}: ${formatDate(lastChat.ts)}`}</Box>}
</Box>
</Box>
Expand Down Expand Up @@ -118,10 +91,10 @@ const ContactInfo = ({ id: contactId, onClose }: ContactInfoProps) => {
</Tabs>
{context === 'details' && (
<ContactInfoDetails
ts={ts}
createdAt={createdAt}
contactManager={contactManager}
phoneNumber={phoneNumber}
email={email}
phones={phones?.map(({ phoneNumber }) => phoneNumber)}
emails={emails?.map(({ address }) => address)}
customFieldEntries={customFieldEntries}
/>
)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Box } from '@rocket.chat/fuselage';
import { useEndpoint, usePermission, useTranslation } from '@rocket.chat/ui-contexts';
import { useQuery } from '@tanstack/react-query';
import React from 'react';

import { ContextualbarSkeleton } from '../../../../components/Contextualbar';
import ContactInfo from './ContactInfo';

type ContactInfoWithDataProps = {
id: string;
onClose: () => void;
};

const ContactInfoWithData = ({ id: contactId, onClose }: ContactInfoWithDataProps) => {
const t = useTranslation();
const canViewCustomFields = usePermission('view-livechat-room-customfields');

const getContact = useEndpoint('GET', '/v1/omnichannel/contacts.get');
const { data, isInitialLoading, isError } = useQuery(['getContactById', contactId], () => getContact({ contactId }), {
enabled: canViewCustomFields && !!contactId,
});

if (isInitialLoading) {
return <ContextualbarSkeleton />;
}

if (isError || !data?.contact) {
return <Box mbs={16}>{t('Contact_not_found')}</Box>;
}

return <ContactInfo contact={data?.contact} onClose={onClose} />;
};

export default ContactInfoWithData;
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ContactInfoWithData';
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ const ContactInfoRouter = () => {
};

const {
v: { _id },
v: { contactId },
} = room;

if (context === 'edit') {
return <ContactEditWithData id={_id} onClose={closeTab} onCancel={handleCloseEdit} />;
return <ContactEditWithData id={contactId || ''} onClose={closeTab} onCancel={handleCloseEdit} />;
}

return <ContactInfo id={_id} onClose={closeTab} />;
return <ContactInfo id={contactId || ''} onClose={closeTab} />;
};

export default ContactInfoRouter;
Loading

0 comments on commit b5d9f84

Please sign in to comment.