From e584ed25922e8752ff6fde21bef119634916814e Mon Sep 17 00:00:00 2001 From: Caleb Alldrin Date: Thu, 19 Sep 2024 14:34:24 -0700 Subject: [PATCH] MPDX-8226 Edit Partnership Info & Contact Header (#1081) * Contact Header improve styles * Contact Details Tab improve styles and organization * Edit Contact Other Modal add new fields * Edit Partnership Info Modal add new fields * Prevent contact address from showing null * Unstar title and syntax changes --- .../CelebrationIcons/CelebrationIcons.tsx | 4 +- .../Celebrationicons.test.tsx | 4 +- .../ContactDetailsHeader.test.tsx | 158 ++-- .../ContactDetailsHeader.tsx | 57 +- .../ContactDetailsMoreActions.tsx | 6 +- .../ContactHeaderAddressSection.tsx | 19 +- .../ContactHeaderEmailSection.tsx | 4 +- .../ContactHeaderPhoneSection.tsx | 4 +- .../ContactHeaderStatusSection.tsx | 35 +- .../ContactHeaderSection/HandshakeIcon.tsx | 14 +- .../ContactDetailsTab.test.tsx | 14 +- .../ContactDetailsTab/ContactDetailsTab.tsx | 15 +- .../Mailing/ContactDetailsTabMailing.test.tsx | 4 +- .../Mailing/ContactDetailsTabMailing.tsx | 189 ++--- .../Other/ContactDetailsOther.stories.tsx | 6 +- .../Other/ContactDetailsOther.tsx | 39 +- .../Other/ContactOther.graphql | 2 + .../EditContactOther.graphql | 2 + .../EditContactOtherModal.test.tsx | 131 +++- .../EditContactOtherModal.tsx | 358 +++++---- .../People/ContactDetailsTabPeople.test.tsx | 21 + .../People/ContactDetailsTabPeople.tsx | 49 +- .../PersonModal/PersonName/PersonName.tsx | 22 +- .../Tags/ContactTags.test.tsx | 2 +- .../ContactDetailsTab/Tags/ContactTags.tsx | 3 +- .../ContactDonationsTab.graphql | 9 + .../EditPartnershipInfoModal.graphql | 11 + .../EditPartnershipInfoModal.test.tsx | 162 ++-- .../EditPartnershipInfoModal.tsx | 732 ++++++++---------- .../ContactTaskRow/ContactTaskRow.test.tsx | 6 +- .../TaskCommentsButton/TaskCommentsButton.tsx | 4 +- .../TaskCompleteButton.test.tsx | 4 +- .../TaskCompleteButton/TaskCompleteButton.tsx | 4 +- .../ContactTasksTab/ContactTasksTab.tsx | 4 +- .../StarTaskIconButton.test.tsx | 24 +- .../ContactFlowRow/ContactFlowRow.test.tsx | 2 +- .../Contacts/ContactRow/ContactRow.test.tsx | 6 +- .../Contacts/ContactRow/ContactRow.tsx | 6 +- .../StarContactIconButton.test.tsx | 34 +- .../Primary/NavBar/NavItem/NavItem.tsx | 6 +- .../Primary/TopBar/Items/AddMenu/AddMenu.tsx | 3 +- .../Layout/Header/Actions/Actions.tsx | 8 +- src/components/Task/Status/Status.tsx | 2 +- src/components/Task/TaskRow/TaskRow.test.tsx | 4 +- .../ContactFlowRow/ContactFlowRow.test.tsx | 4 +- .../DeleteAppealContactModalMocks.ts | 6 + .../common/DeleteItemIcon/DeleteItemIcon.tsx | 4 +- .../StarredItemIcon/StarredItemIcon.test.tsx | 24 +- .../StarredItemIcon/StarredItemIcon.tsx | 6 +- src/lib/data/languages.ts | 11 +- 50 files changed, 1140 insertions(+), 1108 deletions(-) diff --git a/src/components/Contacts/CelebrationIcons/CelebrationIcons.tsx b/src/components/Contacts/CelebrationIcons/CelebrationIcons.tsx index c76927488..0610268b5 100644 --- a/src/components/Contacts/CelebrationIcons/CelebrationIcons.tsx +++ b/src/components/Contacts/CelebrationIcons/CelebrationIcons.tsx @@ -51,12 +51,12 @@ export const CelebrationIcons: React.FC = ({ contact }) => { <> {contactHasAnniversary() ? ( - + ) : null} {contactHasBirthday() ? ( - + ) : null} diff --git a/src/components/Contacts/CelebrationIcons/Celebrationicons.test.tsx b/src/components/Contacts/CelebrationIcons/Celebrationicons.test.tsx index 6e9e8c482..0b6313af9 100644 --- a/src/components/Contacts/CelebrationIcons/Celebrationicons.test.tsx +++ b/src/components/Contacts/CelebrationIcons/Celebrationicons.test.tsx @@ -28,6 +28,6 @@ it('should display ring', () => { }); const { getByRole } = render(); - expect(getByRole('img', { hidden: true, name: 'Cake' })).toBeVisible(); - expect(getByRole('img', { hidden: true, name: 'Ring' })).toBeVisible(); + expect(getByRole('img', { hidden: true, name: 'Birthday' })).toBeVisible(); + expect(getByRole('img', { hidden: true, name: 'Anniversary' })).toBeVisible(); }); diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.test.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.test.tsx index 55282fd23..3606c1776 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.test.tsx @@ -1,5 +1,7 @@ import React from 'react'; import { ThemeProvider } from '@mui/material/styles'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon'; import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SnackbarProvider } from 'notistack'; @@ -129,111 +131,117 @@ describe('ContactDetails', () => { expect(queryByTestId('Skeleton')).toBeNull(); }); - it('should open edit contact details modal', async () => { + it('should open Edit Partnership modal', async () => { const { queryByText, getAllByLabelText } = render( - - - - mocks={mocks} - > - - - {}} - setContactDetailsLoaded={() => {}} - contactDetailsLoaded={false} - /> - - - - - + + + + + mocks={mocks} + > + + + {}} + setContactDetailsLoaded={() => {}} + contactDetailsLoaded={false} + /> + + + + + + , ); await waitFor(() => - expect(getAllByLabelText('Edit Icon')[0]).toBeInTheDocument(), + expect(getAllByLabelText('Edit Partnership Info')[0]).toBeInTheDocument(), ); - userEvent.click(getAllByLabelText('Edit Icon')[0]); + userEvent.click(getAllByLabelText('Edit Partnership Info')[0]); await waitFor(() => - expect(queryByText('Edit Contact Details')).toBeInTheDocument(), + expect(queryByText('Edit Partnership')).toBeInTheDocument(), ); }); - it('should close edit contact address modal', async () => { + it('should close Edit Partnership modal', async () => { const { queryByText, getAllByLabelText, getByLabelText } = render( - - - - mocks={mocks} - > - - - {}} - setContactDetailsLoaded={() => {}} - contactDetailsLoaded={false} - /> - - - - - + + + + + mocks={mocks} + > + + + {}} + setContactDetailsLoaded={() => {}} + contactDetailsLoaded={false} + /> + + + + + + , ); await waitFor(() => - expect(getAllByLabelText('Edit Icon')[0]).toBeInTheDocument(), + expect(getAllByLabelText('Edit Partnership Info')[0]).toBeInTheDocument(), ); - userEvent.click(getAllByLabelText('Edit Icon')[0]); + userEvent.click(getAllByLabelText('Edit Partnership Info')[0]); await waitFor(() => - expect(queryByText('Edit Contact Details')).toBeInTheDocument(), + expect(queryByText('Edit Partnership')).toBeInTheDocument(), ); userEvent.click(getByLabelText('Close')); await waitFor(() => - expect(queryByText('Edit Contact Details')).not.toBeInTheDocument(), + expect(queryByText('Edit Partnership')).not.toBeInTheDocument(), ); }); it('should render avatar', async () => { const { queryByText, getAllByLabelText } = render( - - - - mocks={mocks} - > - - - {}} - setContactDetailsLoaded={() => {}} - contactDetailsLoaded={false} - /> - - - - - + + + + + mocks={mocks} + > + + + {}} + setContactDetailsLoaded={() => {}} + contactDetailsLoaded={false} + /> + + + + + + , ); await waitFor(() => - expect(getAllByLabelText('Edit Icon')[0]).toBeInTheDocument(), + expect(getAllByLabelText('Edit Partnership Info')[0]).toBeInTheDocument(), ); - userEvent.click(getAllByLabelText('Edit Icon')[0]); + userEvent.click(getAllByLabelText('Edit Partnership Info')[0]); await waitFor(() => - expect(queryByText('Edit Contact Details')).toBeInTheDocument(), + expect(queryByText('Edit Partnership Info')).toBeInTheDocument(), ); const avatarImage = document.querySelector('img') as HTMLImageElement; diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx index fccd6bf72..b9fcd23d3 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsHeader.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import Close from '@mui/icons-material/Close'; import { Avatar, Box, IconButton, Skeleton, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; @@ -7,18 +7,13 @@ import { StatusEnum } from 'src/graphql/types.generated'; import { ContactContextTypesEnum } from 'src/lib/contactContextTypes'; import theme from '../../../../theme'; import { StarContactIconButton } from '../../StarContactIconButton/StarContactIconButton'; -import { - ContactDetailContext, - ContactDetailsType, -} from '../ContactDetailContext'; -import { EditContactDetailsModal } from '../ContactDetailsTab/People/Items/EditContactDetailsModal/EditContactDetailsModal'; import { EditIcon } from '../ContactDetailsTab/StyledComponents'; +import { EditPartnershipInfoModal } from '../ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal'; import { useGetContactDetailsHeaderQuery } from './ContactDetailsHeader.generated'; import { ContactDetailsMoreAcitions } from './ContactDetailsMoreActions/ContactDetailsMoreActions'; import { ContactHeaderAddressSection } from './ContactHeaderSection/ContactHeaderAddressSection'; import { ContactHeaderEmailSection } from './ContactHeaderSection/ContactHeaderEmailSection'; import { ContactHeaderNewsletterSection } from './ContactHeaderSection/ContactHeaderNewsletterSection'; -import { ContactHeaderPartnerSection } from './ContactHeaderSection/ContactHeaderPartnerSection'; import { ContactHeaderPhoneSection } from './ContactHeaderSection/ContactHeaderPhoneSection'; import { ContactHeaderStatusSection } from './ContactHeaderSection/ContactHeaderStatusSection'; @@ -46,9 +41,9 @@ const HeaderBarButtonsWrap = styled(Box)(({}) => ({ })); const ContactAvatar = styled(Avatar)(({}) => ({ backgroundColor: theme.palette.secondary.dark, - height: 64, - width: 64, - borderRadius: 32, + height: 50, + width: 50, + borderRadius: 25, })); const PrimaryContactName = styled(Typography)(({}) => ({ display: 'inline', @@ -56,8 +51,8 @@ const PrimaryContactName = styled(Typography)(({}) => ({ fontWeight: 'bold', })); const CloseButtonIcon = styled(Close)(({}) => ({ - width: 14, - height: 14, + width: 24, + height: 24, color: theme.palette.text.primary, })); const HeaderSectionWrap = styled(Box)(({}) => ({ @@ -78,9 +73,8 @@ export const ContactDetailsHeader: React.FC = ({ const loading = !data; const { t } = useTranslation(); - const { editModalOpen, setEditModalOpen } = React.useContext( - ContactDetailContext, - ) as ContactDetailsType; + const [editPartnershipModalOpen, setEditPartnershipModalOpen] = + useState(false); useEffect(() => { if (!loading && !contactDetailsLoaded) { @@ -90,11 +84,11 @@ export const ContactDetailsHeader: React.FC = ({ }, [loading]); return ( - + - {!data ? ( + {!data?.contact ? ( = ({ }} /> - ) : data.contact ? ( + ) : ( <> - {data.contact.name} + {data?.contact.name} setEditModalOpen(true)} - aria-label={t('Edit Icon')} + onClick={() => setEditPartnershipModalOpen(true)} + aria-label={t('Edit Partnership Info')} > - + - ) : null} + )} = ({ /> - = ({ /> - {data && ( - setEditModalOpen(false)} + {data?.contact && editPartnershipModalOpen && ( + setEditPartnershipModalOpen(false)} /> )} diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsMoreActions/ContactDetailsMoreActions.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsMoreActions/ContactDetailsMoreActions.tsx index d8cfb207e..b8aaa9045 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsMoreActions/ContactDetailsMoreActions.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactDetailsMoreActions/ContactDetailsMoreActions.tsx @@ -52,8 +52,8 @@ type AddMenuItem = { }; const MoreButtonIcon = styled(MoreVert)(({ theme }) => ({ - width: 16, - height: 16, + width: 24, + height: 24, color: theme.palette.text.primary, })); @@ -265,7 +265,7 @@ export const ContactDetailsMoreAcitions: React.FC< aria-haspopup="true" onClick={(event) => setAnchorEl(event.currentTarget)} > - + ({ fontSize: 16, })); +const StyledAddressTypography = styled(Typography)(() => ({ + lineHeight: '1.50', +})); + export const ContactHeaderAddressSection = ({ loading, contact, @@ -51,17 +55,24 @@ export const ContactHeaderAddressSection = ({ return ( }> - {envelope} - {street} + + {envelope} + + + {street} + - {`${city}, ${state ?? ''} ${postalCode}`}{' '} + >{`${city}, ${ + state ?? '' + } ${postalCode}`}{' '} diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/ContactHeaderEmailSection.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/ContactHeaderEmailSection.tsx index bbfe17fe7..e369f44c7 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/ContactHeaderEmailSection.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/ContactHeaderEmailSection.tsx @@ -40,7 +40,9 @@ export const ContactHeaderEmailSection = ({ return ( }> - {email} + + {email} + ); diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/ContactHeaderPhoneSection.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/ContactHeaderPhoneSection.tsx index c3d4935c3..24aa95680 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/ContactHeaderPhoneSection.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/ContactHeaderPhoneSection.tsx @@ -42,7 +42,9 @@ export const ContactHeaderPhoneSection = ({ }> - {number} + + {number} + = ({ const statusText = status && contactPartnershipStatus[status]; return ( <> - {status ? ( - } - rightIcon={ - setEditPartnershipModalOpen(true)}> - - - } - > + {status && ( + }> = ({ > {statusText} - {status === StatusEnum.PartnerFinancial ? ( + {status === StatusEnum.PartnerFinancial && ( <> - {contact?.pledgeAmount && contact?.pledgeFrequency ? ( + {contact?.pledgeAmount && contact?.pledgeFrequency && ( {`${ contact.pledgeAmount && contact?.pledgeCurrency @@ -102,7 +94,7 @@ export const ContactHeaderStatusSection: React.FC = ({ contact.pledgeFrequency, )}`}`} - ) : null} + )} {lateStatusEnum !== undefined && ( @@ -113,26 +105,17 @@ export const ContactHeaderStatusSection: React.FC = ({ )} - ) : null} + )} - ) : ( - - setEditPartnershipModalOpen(true)} - > - - - )} - {contact && editPartnershipModalOpen ? ( + {contact && editPartnershipModalOpen && ( setEditPartnershipModalOpen(false)} /> - ) : null} + )} ); } diff --git a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/HandshakeIcon.tsx b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/HandshakeIcon.tsx index 4fbcfcec4..2c04545f0 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/HandshakeIcon.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsHeader/ContactHeaderSection/HandshakeIcon.tsx @@ -1,6 +1,7 @@ import React, { ReactElement } from 'react'; import { SvgIcon } from '@mui/material'; import { styled } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; import theme from '../../../../../theme'; const SwapIconSvg = styled(SvgIcon)(({}) => ({ @@ -10,8 +11,11 @@ const SwapIconSvg = styled(SvgIcon)(({}) => ({ color: theme.palette.text.secondary, })); -export const HandshakeIcon = (): ReactElement => ( - - - -); +export const HandshakeIcon = (): ReactElement => { + const { t } = useTranslation(); + return ( + + + + ); +}; diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/ContactDetailsTab.test.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/ContactDetailsTab.test.tsx index 730e8c5d5..522d773b5 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/ContactDetailsTab.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/ContactDetailsTab.test.tsx @@ -307,14 +307,16 @@ describe('ContactDetailTab', () => { await waitFor(() => expect(queryByText('Loading')).not.toBeInTheDocument()); }); - it('should open edit contact other details modal', async () => { - const { queryByText } = render(); + it('should open and close Edit Contact Other Details modal', async () => { + const { queryByText, getByTestId, getByText, getByLabelText } = render( + , + ); await waitFor(() => expect(queryByText('Loading')).not.toBeInTheDocument()); - }); + userEvent.click(getByTestId('Edit Other')); + expect(getByText('Edit Contact Other Details')).toBeInTheDocument(); + + userEvent.click(getByLabelText('Close')); - it('should close edit contact other details modal', async () => { - const { queryByText } = render(); - await waitFor(() => expect(queryByText('Loading')).not.toBeInTheDocument()); await waitFor(() => expect(queryByText('Edit Contact Other Details')).not.toBeInTheDocument(), ); diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/ContactDetailsTab.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/ContactDetailsTab.tsx index 1bdc88f98..e7ddc310b 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/ContactDetailsTab.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/ContactDetailsTab.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import { Box, Divider, Skeleton, Typography } from '@mui/material'; +import { Box, Divider, IconButton, Skeleton, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import { useTranslation } from 'react-i18next'; import { @@ -12,6 +12,7 @@ import { ContactDetailsOther } from './Other/ContactDetailsOther'; import { EditContactOtherModal } from './Other/EditContactOtherModal/EditContactOtherModal'; import { ContactDetailsPartnerAccounts } from './PartnerAccounts/ContactDetailsPartnerAccounts'; import { ContactDetailsTabPeople } from './People/ContactDetailsTabPeople'; +import { EditIcon } from './StyledComponents'; import { ContactTags } from './Tags/ContactTags'; const ContactDetailsTabContainer = styled(Box)(({ theme }) => ({ @@ -99,11 +100,11 @@ export const ContactDetailsTab: React.FC = ({ )} - {/* Mailing Section */} + {/* Addresses Section */} - {t('Mailing')} + {t('Addresses')} {!data ? ( @@ -125,6 +126,13 @@ export const ContactDetailsTab: React.FC = ({ {t('Other')} + setEditOtherModalOpen(true)} + aria-label={t('Edit')} + style={{ marginLeft: 5 }} + > + + {!data ? ( @@ -137,7 +145,6 @@ export const ContactDetailsTab: React.FC = ({ )} diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/Mailing/ContactDetailsTabMailing.test.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/Mailing/ContactDetailsTabMailing.test.tsx index 554d238aa..b6e47f1ae 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/Mailing/ContactDetailsTabMailing.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/Mailing/ContactDetailsTabMailing.test.tsx @@ -99,7 +99,7 @@ describe('ContactDetailsTabMailing', () => { expect(getByText('Orlando, 32832')).toBeInTheDocument(); - userEvent.click(getByText('Show More')); + userEvent.click(getByText('Show 1 More')); const { getByText: getByTextInTestId } = within( getByTestId('NonPrimaryAddresses'), ); @@ -129,7 +129,7 @@ describe('ContactDetailsTabMailing', () => { expect(getByText('Orlando, FL 32832')).toBeInTheDocument(); - userEvent.click(getByText('Show More')); + userEvent.click(getByText('Show 1 More')); const { getByText: getByTextInTestId } = within( getByTestId('NonPrimaryAddresses'), ); diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/Mailing/ContactDetailsTabMailing.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/Mailing/ContactDetailsTabMailing.tsx index 36804fb31..1966dbc60 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/Mailing/ContactDetailsTabMailing.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/Mailing/ContactDetailsTabMailing.tsx @@ -1,19 +1,18 @@ import React, { useState } from 'react'; import CreateIcon from '@mui/icons-material/Create'; import LocationOn from '@mui/icons-material/LocationOn'; -import { Box, Grid, IconButton, Link, Typography } from '@mui/material'; +import { Box, IconButton, Link, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import { DateTime } from 'luxon'; import { useTranslation } from 'react-i18next'; import { useLocale } from 'src/hooks/useLocale'; import { dateFormat } from 'src/lib/intlFormat'; -import { getLocalizedSendNewsletter } from 'src/utils/functions/getLocalizedSendNewsletter'; import { sourceToStr } from 'src/utils/sourceToStr'; import { ContactDetailContext, ContactDetailsType, } from '../../ContactDetailContext'; -import { AddButton, AddIcon, AddText, EditIcon } from '../StyledComponents'; +import { AddButton, AddIcon, AddText } from '../StyledComponents'; import { AddAddressModal } from './AddAddressModal/AddAddressModal'; import { ContactMailingFragment } from './ContactMailing.generated'; import { EditContactAddressModal } from './EditContactAddressModal/EditContactAddressModal'; @@ -22,6 +21,7 @@ import { EditMailingInfoModal } from './EditMailingInfoModal/EditMailingInfoModa const ContactDetailsMailingMainContainer = styled(Box)(({ theme }) => ({ display: 'flex', marginTop: theme.spacing(1), + marginBottom: theme.spacing(2), })); const ContactDetailsMailingIcon = styled(LocationOn)(({ theme }) => ({ @@ -38,9 +38,8 @@ const ContactDetailsMailingLabelTextContainer = styled(Box)(({}) => ({ marginTop: '10px', })); -const ContactDetailsMailingLabel = styled(Typography)(({ theme }) => ({ - color: theme.palette.text.secondary, - marginRight: '5px', +const StyledAddressTypography = styled(Typography)(() => ({ + lineHeight: '1.25', })); const ContactMailingShowMoreLabel = styled(Typography)(({ theme }) => ({ @@ -55,6 +54,7 @@ const ContactAddressPrimaryText = styled(Typography)(({ theme }) => ({ const ContactAddressRowContainer = styled(Box)(() => ({ display: 'flex', alignItems: 'center', + marginTop: 10, })); const AddressEditIcon = styled(CreateIcon)(({ theme }) => ({ @@ -79,7 +79,7 @@ export const ContactDetailsTabMailing: React.FC = ({ }) => { const { t } = useTranslation(); const locale = useLocale(); - const { addresses, greeting, envelopeGreeting, sendNewsletter, id } = data; + const { addresses, id } = data; const { editingAddressId, setEditingAddressId, @@ -103,6 +103,14 @@ export const ContactDetailsTabMailing: React.FC = ({ useState(false); return ( <> + + + setAddAddressModalOpen(true)}> + + {t('Add Address')} + + + @@ -127,40 +135,73 @@ export const ContactDetailsTabMailing: React.FC = ({ - - - {`${primaryAddress.city}, ${primaryAddress.state ?? ''} ${ - primaryAddress.postalCode - }`} - - - - + + {`${primaryAddress.city}${primaryAddress.city && ','} ${ + primaryAddress.state ?? '' + } ${primaryAddress.postalCode}`} + + + {primaryAddress.country} - - - - - {t('Source:')} {sourceToStr(t, primaryAddress.source)} ( - {dateFormat( - DateTime.fromISO(primaryAddress.createdAt), - locale, - )} - ) - - + + + + {t('Source:')} {sourceToStr(t, primaryAddress.source)} ( + {dateFormat( + DateTime.fromISO(primaryAddress.createdAt), + locale, + )} + ) + )} {/* Show More Section */} - {nonPrimaryAddresses.length > 0 ? ( + {showContactDetailTabMoreOpen && + nonPrimaryAddresses.map((address) => ( + + + + {address.street ?? ''} + + { + setEditingAddressId(address.id); + }} + aria-label={t('Edit Icon')} + > + + + + + {`${address.city ?? ''}${address.city && ','} ${ + address.state ?? '' + } ${address.postalCode ?? ''}`} + + + {address.country ?? ''} + + + {t('Source:')} {sourceToStr(t, address.source)} ( + {dateFormat(DateTime.fromISO(address.createdAt), locale)}) + + + ))} + {nonPrimaryAddresses.length > 0 && ( - + @@ -171,103 +212,31 @@ export const ContactDetailsTabMailing: React.FC = ({ > {showContactDetailTabMoreOpen ? t('Show Less') - : t('Show More')} + : t('Show {{amount}} More', { + amount: nonPrimaryAddresses.length, + })} - ) : null} - {showContactDetailTabMoreOpen - ? nonPrimaryAddresses.map((address) => ( - - - - {address.street} - - { - setEditingAddressId(address.id); - }} - aria-label={t('Edit Icon')} - > - - - - - {`${address.city}, ${address.state ?? ''} ${ - address.postalCode - }`} - - - {address.country} - - - {t('Source:')} {sourceToStr(t, address.source)} ( - {dateFormat(DateTime.fromISO(address.createdAt), locale)}) - - - )) - : null} - {/* Greeting Section */} - - - {t('Greeting')} - - {greeting} - setEditMailingModalOpen(true)} - aria-label={t('Edit Mailing')} - > - - - - {/* Envelope Name Section */} - - - {t('Envelope Name')} - - {envelopeGreeting} - - {/* Newsletter Section */} - - - {t('Newsletter')} - - - {getLocalizedSendNewsletter(t, sendNewsletter)} - - + )} - - setAddAddressModalOpen(true)}> - - {t('Add Address')} - - - {selectedAddress ? ( + {selectedAddress && ( setEditingAddressId(undefined)} /> - ) : null} - {addAddressModalOpen ? ( + )} + {addAddressModalOpen && ( setAddAddressModalOpen(false)} /> - ) : null} + )} {editMailingModalOpen && ( { return ( - {}} - handleOpen={() => {}} - /> + {}} /> ); diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/ContactDetailsOther.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/ContactDetailsOther.tsx index 1019f20eb..24e83f567 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/ContactDetailsOther.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/ContactDetailsOther.tsx @@ -1,10 +1,11 @@ import React from 'react'; -import { Box, IconButton, Typography } from '@mui/material'; +import { Box, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import { useTranslation } from 'react-i18next'; +import { useApiConstants } from 'src/components/Constants/UseApiConstants'; import { PreferredContactMethodEnum } from 'src/graphql/types.generated'; +import { formatLanguage } from 'src/lib/data/languages'; import i18n from 'src/lib/i18n'; -import { EditIcon } from '../StyledComponents'; import { ContactOtherFragment } from './ContactOther.generated'; const ContactOtherContainer = styled(Box)(({ theme }) => ({ @@ -37,7 +38,6 @@ interface ContactDetailsOtherProp { openDetails?: boolean, flows?: boolean, ) => void; - handleOpen: (open: boolean) => void; } export const localizedContactMethod = (method?: string | null): string => { @@ -64,9 +64,10 @@ export const localizedContactMethod = (method?: string | null): string => { export const ContactDetailsOther: React.FC = ({ contact, onContactSelected, - handleOpen, }) => { const { t } = useTranslation(); + const constants = useApiConstants(); + const languages = constants?.languages || undefined; const { user, preferredContactMethod, @@ -75,6 +76,8 @@ export const ContactDetailsOther: React.FC = ({ churchName, website, contactReferralsToMe, + greeting, + envelopeGreeting, } = contact; const referredBy = contactReferralsToMe?.nodes[0]?.referredBy; @@ -82,6 +85,20 @@ export const ContactDetailsOther: React.FC = ({ return ( + {/* Greeting Section */} + + + {t('Greeting')} + + {greeting} + + {/* Envelope Name Section */} + + + {t('Envelope Name')} + + {envelopeGreeting} + {t('Assignee')} @@ -89,13 +106,6 @@ export const ContactDetailsOther: React.FC = ({ {user ? `${user.firstName} ${user.lastName}` : t('None')} - handleOpen(true)} - aria-label={t('Edit Other Icon')} - style={{ marginLeft: 16 }} - > - - @@ -124,9 +134,14 @@ export const ContactDetailsOther: React.FC = ({ {t('Language')} - {locale} + + {formatLanguage(locale, languages)} + + + {t('Time Zone')} + {timezone} diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/ContactOther.graphql b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/ContactOther.graphql index 83a9b3ee9..083a97095 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/ContactOther.graphql +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/ContactOther.graphql @@ -5,6 +5,8 @@ fragment ContactOther on Contact { timezone churchName website + greeting + envelopeGreeting user { id firstName diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOther.graphql b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOther.graphql index 6d0fd7c3d..1a9f2bcf5 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOther.graphql +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOther.graphql @@ -35,6 +35,8 @@ mutation UpdateContactOther( preferredContactMethod timezone website + greeting + envelopeGreeting contactReferralsToMe(first: 10) { nodes { id diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOtherModal.test.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOtherModal.test.tsx index 151c5640c..0faa585c1 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOtherModal.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOtherModal.test.tsx @@ -1,5 +1,7 @@ import React from 'react'; import { ThemeProvider } from '@mui/material/styles'; +import { LocalizationProvider } from '@mui/x-date-pickers'; +import { AdapterLuxon } from '@mui/x-date-pickers/AdapterLuxon'; import { act, render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { SnackbarProvider } from 'notistack'; @@ -54,6 +56,8 @@ const mockContact: ContactOtherFragment = { churchName: mock.churchName, website: mock.website, user: { id: 'user-1' }, + greeting: mock.greeting, + envelopeGreeting: mock.envelopeGreeting, contactReferralsToMe: mock.contactReferralsToMe, }; @@ -276,6 +280,94 @@ describe('EditContactOtherModal', () => { }); }); + it('should handle editing the referred by | Create', async () => { + const { getByLabelText, getByText } = render( + + + + + mocks={{ + ContactOptions: { + contacts: { + nodes: [ + { + id: 'contact-1', + name: 'Person, Cool', + }, + { + id: 'contact-2', + name: 'Guy, Great', + }, + ], + }, + }, + }} + > + + + + + , + ); + + const referredByInput = getByLabelText('Referred By'); + await waitFor(() => expect(referredByInput).toBeInTheDocument()); + userEvent.click(referredByInput); + userEvent.type(referredByInput, 'G'); + await waitFor(() => expect(getByText('Guy, Great')).toBeInTheDocument()); + userEvent.click(getByText('Guy, Great')); + userEvent.click(getByText('Save')); + await waitFor(() => + expect(mockEnqueue).toHaveBeenCalledWith('Contact updated successfully', { + variant: 'success', + }), + ); + expect(handleClose).toHaveBeenCalled(); + }); + + it('should handle editing the referred by | No Contacts or Referrals', async () => { + const { getByLabelText, getByText } = render( + + + + + mocks={{ + ContactOptions: { + contacts: { + nodes: [], + }, + }, + }} + > + + + + + , + ); + + const referredByInput = getByLabelText('Referred By'); + await waitFor(() => expect(referredByInput).toBeInTheDocument()); + userEvent.click(referredByInput); + expect(getByText('No options')).toBeInTheDocument(); + }); + it('should handle empty contact method', async () => { const mutationSpy = jest.fn(); const { getByText, getByLabelText } = render( @@ -323,6 +415,8 @@ describe('EditContactOtherModal', () => { const mutationSpy = jest.fn(); const newChurchName = 'Great Cool Church II'; const newWebsite = 'coolwebsite2.com'; + const newGreeting = 'New Name'; + const newEnvelopeGreeting = 'New Full Name'; const { getByText, getByLabelText, getByRole, findByRole } = render( @@ -408,6 +502,17 @@ describe('EditContactOtherModal', () => { userEvent.clear(church); userEvent.type(church, newChurchName); userEvent.type(getByLabelText('Website'), newWebsite); + + const greetingInput = getByLabelText('Greeting'); + expect(greetingInput).toHaveValue(mockContact.greeting); + userEvent.clear(greetingInput); + userEvent.type(greetingInput, newGreeting); + + const envelopeGreetingInput = getByLabelText('Envelope Name Line'); + expect(envelopeGreetingInput).toHaveValue(mockContact.envelopeGreeting); + userEvent.clear(envelopeGreetingInput); + userEvent.type(envelopeGreetingInput, newEnvelopeGreeting); + userEvent.click(getByText('Save')); await waitFor(() => expect(mockEnqueue).toHaveBeenCalledWith('Contact updated successfully', { @@ -415,20 +520,18 @@ describe('EditContactOtherModal', () => { }), ); - const { operation } = mutationSpy.mock.calls[6][0]; - expect(operation).toMatchObject({ - operationName: 'UpdateContactOther', - variables: { - accountListId, - attributes: { - preferredContactMethod: PreferredContactMethodEnum.WhatsApp, - locale: 'Australian English', - timezone: '(GMT-09:00) Alaska', - churchName: newChurchName, - website: newWebsite, - userId: 'user-2', - contactReferralsToMe: [{}], - }, + expect(mutationSpy).toHaveGraphqlOperation('UpdateContactOther', { + accountListId, + attributes: { + preferredContactMethod: PreferredContactMethodEnum.WhatsApp, + locale: 'en-AU', + timezone: '(GMT-09:00) Alaska', + churchName: newChurchName, + website: newWebsite, + userId: 'user-2', + greeting: newGreeting, + envelopeGreeting: newEnvelopeGreeting, + contactReferralsToMe: [{}], }, }); }); diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOtherModal.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOtherModal.tsx index 3df961517..ed210be29 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOtherModal.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/Other/EditContactOtherModal/EditContactOtherModal.tsx @@ -30,6 +30,7 @@ import { PreferredContactMethodEnum, UserScopedToAccountList, } from 'src/graphql/types.generated'; +import { useLocale } from 'src/hooks/useLocale'; import { useGetTimezones } from '../../../../../../hooks/useGetTimezones'; import { useApiConstants } from '../../../../../Constants/UseApiConstants'; import Modal from '../../../../../common/Modal/Modal'; @@ -49,12 +50,12 @@ const ContactEditContainer = styled(Box)(({ theme }) => ({ display: 'flex', width: '100%', flexDirection: 'column', - margin: theme.spacing(4, 0), + margin: theme.spacing(1, 0), })); const ContactInputWrapper = styled(Box)(({ theme }) => ({ position: 'relative', - padding: theme.spacing(0, 6), + padding: theme.spacing(0, 1), margin: theme.spacing(2, 0), })); @@ -96,10 +97,13 @@ export const EditContactOtherModal: React.FC = ({ const { enqueueSnackbar } = useSnackbar(); const [updateContactOther, { loading: updating }] = useUpdateContactOtherMutation(); + const userLocale = useLocale(); const isMobile = useMediaQuery((theme: Theme) => theme.breakpoints.down('sm'), ); + // Add additional language locales here, for multiline TextField support + const multilineLocales = ['en', 'de']; const constants = useApiConstants(); const languages = constants?.languages ?? []; @@ -179,6 +183,8 @@ export const EditContactOtherModal: React.FC = ({ | 'timezone' | 'userId' | 'website' + | 'greeting' + | 'envelopeGreeting' > & { referredById: string | null | undefined } > = yup.object({ id: yup.string().required(), @@ -191,6 +197,8 @@ export const EditContactOtherModal: React.FC = ({ locale: yup.string().nullable(), timezone: yup.string().nullable(), website: yup.string().nullable(), + greeting: yup.string().nullable(), + envelopeGreeting: yup.string().nullable(), referredById: yup.string().nullable(), }); @@ -226,6 +234,8 @@ export const EditContactOtherModal: React.FC = ({ locale: attributes.locale, timezone: attributes.timezone, website: attributes.website, + greeting: attributes.greeting, + envelopeGreeting: attributes.envelopeGreeting, contactReferralsToMe: referralsInput, }, }, @@ -275,6 +285,8 @@ export const EditContactOtherModal: React.FC = ({ locale: contact.locale, timezone: contact.timezone, website: contact.website, + greeting: contact.greeting, + envelopeGreeting: contact.envelopeGreeting, referredById: contact.contactReferralsToMe?.nodes[0]?.referredBy.id || '', }} @@ -289,6 +301,8 @@ export const EditContactOtherModal: React.FC = ({ locale, timezone, website, + greeting, + envelopeGreeting, referredById, }, setFieldValue, @@ -300,111 +314,95 @@ export const EditContactOtherModal: React.FC = ({
- - id)} - getOptionLabel={(userId) => { - const user = users.find(({ id }) => id === userId); - return user ? userName(user) : ''; - }} - renderInput={(params) => ( - - )} - value={userId ?? null} - onChange={(_, userId) => { - setFieldValue('userId', userId); - }} - /> - - - - - a.name.localeCompare(b.name), - ) - )?.map(({ id }) => id) || [] - } - getOptionLabel={(contactId) => - mergedContacts.find(({ id }) => id === contactId) - ?.name ?? '' - } - renderInput={(params): ReactElement => ( - - {(loadingFilteredById || - loadingFilteredByName) && ( - - )} - {params.InputProps.endAdornment} - - ), - }} - /> - )} - value={referredById || null} - onChange={(_, referredBy): void => { - setFieldValue('referredById', referredBy); - setSelectedId(referredBy || ''); - }} - isOptionEqualToValue={(option, value): boolean => - option === value - } - /> + + + + + - - - - - {t('Preferred Contact Method')} - - - setFieldValue('preferredContactMethod', e.target.value) - } - fullWidth={true} - > - {Object.values(PreferredContactMethodEnum).map( - (value) => { - const contactMethod = localizedContactMethod(value); - return ( - - {contactMethod} - - ); - }, - )} - - - - - - - + + + + + + + + id)} + getOptionLabel={(userId) => { + const user = users.find(({ id }) => id === userId); + return user ? userName(user) : ''; + }} + renderInput={(params) => ( + + )} + value={userId ?? null} + onChange={(_, userId) => { + setFieldValue('userId', userId); + }} + /> + + + + + + + {t('Preferred Contact Method')} + + + setFieldValue( + 'preferredContactMethod', + e.target.value, + ) + } + fullWidth={true} + > + {Object.values(PreferredContactMethodEnum).map( + (value) => { + const contactMethod = + localizedContactMethod(value); + return ( + + {contactMethod} + + ); + }, + )} + + + + + + + + {t('Language')} @@ -435,17 +433,20 @@ export const EditContactOtherModal: React.FC = ({ > {languages.map( (value) => - value?.value && ( - + value.id && + value.value && ( + {t(value.value)} ), )} - - - + + + + + {t('Timezone')} @@ -481,48 +482,107 @@ export const EditContactOtherModal: React.FC = ({ ))} - + - - - ( + + + + ( + + {loadingChurchOptions && ( + + )} + {params.InputProps.endAdornment} + + ), + }} + /> + )} + value={churchName || ''} + onChange={(_, churchName): void => { + setFieldValue('churchName', churchName); + }} + /> + + + + - {loadingChurchOptions && ( - - )} - {params.InputProps.endAdornment} - - ), + label={t('Website')} + value={website} + onChange={handleChange('website')} + inputProps={{ 'aria-label': t('Website') }} + fullWidth + /> + + + + + + a.name.localeCompare(b.name), + ) + )?.map(({ id }) => id) || [] + } + getOptionLabel={(contactId) => + mergedContacts.find(({ id }) => id === contactId) + ?.name ?? '' + } + renderInput={(params): ReactElement => ( + + {(loadingFilteredById || + loadingFilteredByName) && ( + + )} + {params.InputProps.endAdornment} + + ), + }} + /> + )} + value={referredById || null} + onChange={(_, referredBy): void => { + setFieldValue('referredById', referredBy); + setSelectedId(referredBy || ''); }} + isOptionEqualToValue={(option, value): boolean => + option === value + } /> - )} - value={churchName || ''} - onChange={(_, churchName): void => { - setFieldValue('churchName', churchName); - }} - /> - - - - + + + diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/ContactDetailsTabPeople.test.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/ContactDetailsTabPeople.test.tsx index cd3e5f022..c1e145838 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/ContactDetailsTabPeople.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/ContactDetailsTabPeople.test.tsx @@ -207,6 +207,27 @@ describe('ContactDetailsTabPeople', () => { }); }); + it('should render Add Person button with no people', async () => { + const { getByText } = render( + + + + + + + + + + + , + ); + expect(getByText('People')).toBeInTheDocument(); + expect(getByText('Add Person')).toBeInTheDocument(); + }); + describe('No primary phone number', () => { const personMocks = { firstName: 'Test', diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/ContactDetailsTabPeople.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/ContactDetailsTabPeople.tsx index 661084114..7fa95177a 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/ContactDetailsTabPeople.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/ContactDetailsTabPeople.tsx @@ -58,14 +58,14 @@ const ContactPersonNameText = styled(Typography)(() => ({ })); const ContactPersonPrimaryText = styled(Typography)(({ theme }) => ({ - margin: theme.spacing(0, 1), + marginLeft: theme.spacing(1), color: theme.palette.text.secondary, })); const ContactPersonIconContainer = styled(Box)(() => ({ width: '18px', height: '18px', - marginRight: '35px', + marginRight: '25px', })); const MergePeopleIcon = styled(MergeIcon)(({ theme }) => ({ @@ -152,7 +152,7 @@ export const ContactDetailsTabPeople: React.FC = ({ onClick={() => toggleSelectPerson(person.id)} > @@ -165,7 +165,7 @@ export const ContactDetailsTabPeople: React.FC = ({ textDecoration: person.deceased ? 'line-through' : '', }} > - {`${person.firstName} ${person.lastName}`} + {`${person.firstName || ''} ${person.lastName || ''}`} {primaryPerson?.id === person.id ? ( @@ -187,7 +187,10 @@ export const ContactDetailsTabPeople: React.FC = ({ {person.primaryPhoneNumber?.number !== null ? ( - + {person.primaryPhoneNumber?.number} ) : ( @@ -213,7 +216,10 @@ export const ContactDetailsTabPeople: React.FC = ({ - + {person.primaryEmailAddress?.email} @@ -260,18 +266,25 @@ export const ContactDetailsTabPeople: React.FC = ({ return ( <> + {!selecting && ( + + {!people.nodes.length && ( + + {t('People')} + + )} + + setCreatePersonModalOpen(true)}> + + {t('Add Person')} + + + + )} {primaryPerson ? personView(primaryPerson) : null} {people.nodes.map((person) => person.id !== primaryPerson?.id ? personView(person) : null, )} - {!selecting && ( - setCreatePersonModalOpen(true)}> - - - {t('Add Person')} - - - )} {people.nodes.length > 1 && (selecting ? ( <> @@ -302,7 +315,7 @@ export const ContactDetailsTabPeople: React.FC = ({ ))} - {createPersonModalOpen ? ( + {createPersonModalOpen && ( = ({ handleClose={() => setCreatePersonModalOpen(false)} contactData={data} /> - ) : null} - {mergePeopleModalOpen ? ( + )} + {mergePeopleModalOpen && ( @@ -322,7 +335,7 @@ export const ContactDetailsTabPeople: React.FC = ({ setMergePeopleModalOpen(false); }} /> - ) : null} + )} ); }; diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonName/PersonName.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonName/PersonName.tsx index 84bf49adf..09aa9dfe5 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonName/PersonName.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/People/Items/PersonModal/PersonName/PersonName.tsx @@ -1,13 +1,6 @@ import React, { useRef } from 'react'; import UploadIcon from '@mui/icons-material/Upload'; -import { - Avatar, - Box, - Grid, - IconButton, - TextField, - Typography, -} from '@mui/material'; +import { Avatar, Grid, IconButton, TextField, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; import { FormikProps } from 'formik'; import { useTranslation } from 'react-i18next'; @@ -82,7 +75,7 @@ export const PersonName: React.FC = ({ return ( <> - {person ? ( + {person && ( @@ -90,7 +83,7 @@ export const PersonName: React.FC = ({ @@ -102,14 +95,11 @@ export const PersonName: React.FC = ({ ref={fileRef} onChange={handleFileChange} /> - - {`${person.firstName} ${person.lastName}`} + + {`${person.firstName || ''} ${person.lastName || ''}`} - ) : null} + )} diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/Tags/ContactTags.test.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/Tags/ContactTags.test.tsx index b780f4b32..f038cea55 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/Tags/ContactTags.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/Tags/ContactTags.test.tsx @@ -156,7 +156,7 @@ describe('ContactTags', () => { , ); - const tag1DeleteIcon = getAllByTitle('Delete Icon')[0].querySelector( + const tag1DeleteIcon = getAllByTitle('Remove')[0].querySelector( '.MuiChip-deleteIcon', ); diff --git a/src/components/Contacts/ContactDetails/ContactDetailsTab/Tags/ContactTags.tsx b/src/components/Contacts/ContactDetails/ContactDetailsTab/Tags/ContactTags.tsx index 20a7e614f..bfced5220 100644 --- a/src/components/Contacts/ContactDetails/ContactDetailsTab/Tags/ContactTags.tsx +++ b/src/components/Contacts/ContactDetails/ContactDetailsTab/Tags/ContactTags.tsx @@ -146,7 +146,7 @@ export const ContactTags: React.FC = ({ label={tag} disabled={updating} onDelete={() => handleTagDelete(tag)} - title={t('Delete Icon')} + title={t('Remove')} /> ))} = ({ {...params} placeholder={t('add tag')} disabled={isSubmitting || updating} + size="small" /> )} onChange={(_, tagList): void => diff --git a/src/components/Contacts/ContactDetails/ContactDonationsTab/ContactDonationsTab.graphql b/src/components/Contacts/ContactDetails/ContactDonationsTab/ContactDonationsTab.graphql index ebcd88bcf..ad74cd2d3 100644 --- a/src/components/Contacts/ContactDetails/ContactDonationsTab/ContactDonationsTab.graphql +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/ContactDonationsTab.graphql @@ -24,6 +24,15 @@ fragment ContactDonorAccounts on Contact { status source likelyToGive + name + primaryPerson { + id + } + people(first: 25) { + nodes { + ...ContactPerson + } + } lastDonation { ...ContactDonation } diff --git a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.graphql b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.graphql index f7e566fa7..1eb40994c 100644 --- a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.graphql +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.graphql @@ -7,6 +7,17 @@ mutation UpdateContactPartnership( ) { contact { id + name + primaryPerson { + id + firstName + lastName + } + people(first: 25) { + nodes { + ...ContactPerson + } + } status pledgeAmount pledgeFrequency diff --git a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.test.tsx b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.test.tsx index b4403125b..466d5ab8b 100644 --- a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.test.tsx @@ -40,14 +40,17 @@ const contactMock = gqlMock( currency: 'CAD', }, }, - contactReferralsToMe: { + people: { nodes: [ { - id: 'referred-by-id-1', - referredBy: { - id: 'referred-by-contact-id', - name: 'Person, Cool', - }, + firstName: 'Will', + lastName: 'Turner', + id: '2', + }, + { + firstName: 'test', + lastName: 'guy', + id: '3', }, ], }, @@ -74,14 +77,30 @@ const newContactMock = gqlMock( nodes: [], }, likelyToGive: null, + name: 'Test', + primaryPerson: { + id: '1', + firstName: 'test', + lastName: null, + }, + people: { + nodes: [ + { + firstName: 'Will', + lastName: 'Turner', + id: '2', + }, + { + firstName: 'test', + lastName: 'guy', + id: '3', + }, + ], + }, }, }, ); -const contactMockNoReferrals: ContactDonorAccountsFragment = { - ...contactMock, - contactReferralsToMe: { nodes: [] }, -}; jest.mock('next/router', () => ({ useRouter: () => { return { @@ -640,12 +659,15 @@ describe('EditPartnershipInfoModal', () => { ]); }); - it('should handle editing the referred by | Delete', async () => { - const { getByLabelText, getByText, getByRole, queryByText } = render( + it('should handle editing contact name and primary contact', async () => { + const mutationSpy = jest.fn(); + const newContactName = 'Guy, Cool and Neat'; + const newPrimaryContactName = `${newContactMock.people.nodes[1].firstName} ${newContactMock.people.nodes[1].lastName}`; + const { getByRole } = render( - + { , ); + mutationSpy.mockClear(); - const referredByInput = getByLabelText('Referred By'); - await waitFor(() => expect(referredByInput).toBeInTheDocument()); - userEvent.click(referredByInput); - expect(getByText('Person, Cool')).toBeInTheDocument(); - const deleteIcon = getByRole('button', { - name: 'Person, Cool', - }).querySelector('.MuiChip-deleteIcon'); - - expect(deleteIcon).toBeInTheDocument(); - deleteIcon && userEvent.click(deleteIcon); - expect(queryByText('Person, Cool')).not.toBeInTheDocument(); - userEvent.click(getByText('Save')); - await waitFor(() => - expect(mockEnqueue).toHaveBeenCalledWith( - 'Partnership information updated successfully.', - { - variant: 'success', - }, - ), + const contactTextBox = getByRole('textbox', { + hidden: true, + name: 'Contact', + }); + userEvent.clear(contactTextBox); + userEvent.type(contactTextBox, newContactName); + userEvent.click( + getByRole('combobox', { hidden: true, name: 'Primary Person' }), ); - expect(handleClose).toHaveBeenCalled(); - }); - - it('should handle editing the referred by | Create', async () => { - const { getByLabelText, getByText } = render( - - - - - mocks={{ - GetDataForPartnershipInfoModal: { - contacts: { - nodes: [ - { - id: 'contact-1', - name: 'Person, Cool', - }, - { - id: 'contact-2', - name: 'Guy, Great', - }, - ], - }, - }, - }} - > - - - - - , + userEvent.click( + getByRole('option', { hidden: true, name: newPrimaryContactName }), ); + userEvent.click(getByRole('button', { name: 'Save' })); - const referredByInput = getByLabelText('Referred By'); - await waitFor(() => expect(referredByInput).toBeInTheDocument()); - userEvent.click(referredByInput); - userEvent.type(referredByInput, 'G'); - await waitFor(() => expect(getByText('Guy, Great')).toBeInTheDocument()); - userEvent.click(getByText('Guy, Great')); - expect(getByText('Guy, Great')).toBeInTheDocument(); - userEvent.click(getByText('Save')); await waitFor(() => expect(mockEnqueue).toHaveBeenCalledWith( 'Partnership information updated successfully.', @@ -730,39 +701,12 @@ describe('EditPartnershipInfoModal', () => { }, ), ); - expect(handleClose).toHaveBeenCalled(); - }); - - it('should handle editing the referred by | No Contacts or Referrals', async () => { - const { getByLabelText, getByText } = render( - - - - - mocks={{ - GetDataForPartnershipInfoModal: { - contacts: { - nodes: [], - }, - }, - }} - > - - - - - , - ); - - const referredByInput = getByLabelText('Referred By'); - await waitFor(() => expect(referredByInput).toBeInTheDocument()); - userEvent.click(referredByInput); - expect(getByText('No options')).toBeInTheDocument(); + expect(mutationSpy).toHaveGraphqlOperation('UpdateContactPartnership', { + attributes: { + name: newContactName, + primaryPersonId: newContactMock.people.nodes[1].id, + }, + }); }); it('should handle editing next ask date', async () => { diff --git a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.tsx b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.tsx index 3f43ec2ea..6aff9ea04 100644 --- a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.tsx +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.tsx @@ -1,8 +1,7 @@ -import React, { ReactElement, useState } from 'react'; +import React, { useState } from 'react'; import InfoIcon from '@mui/icons-material/InfoOutlined'; import { Alert, - Autocomplete, Box, Button, Checkbox, @@ -11,6 +10,7 @@ import { DialogContent, FormControl, FormControlLabel, + Grid, InputLabel, MenuItem, Select, @@ -30,7 +30,6 @@ import { SubmitButton, } from 'src/components/common/Modal/ActionButtons/ActionButtons'; import { - ContactReferralToMeInput, LikelyToGiveEnum, PledgeFrequencyEnum, SendNewsletterEnum, @@ -47,14 +46,11 @@ import { useAccountListId } from '../../../../../../hooks/useAccountListId'; import { useApiConstants } from '../../../../../Constants/UseApiConstants'; import Modal from '../../../../../common/Modal/Modal'; import { ContactDonorAccountsFragment } from '../../ContactDonationsTab.generated'; -import { - useGetDataForPartnershipInfoModalQuery, - useUpdateContactPartnershipMutation, -} from './EditPartnershipInfoModal.generated'; +import { useUpdateContactPartnershipMutation } from './EditPartnershipInfoModal.generated'; const ContactInputWrapper = styled(Box)(({ theme }) => ({ position: 'relative', - padding: theme.spacing(0, 6), + padding: theme.spacing(0, 1), margin: theme.spacing(2, 0), })); @@ -101,21 +97,13 @@ const contactPartnershipSchema = yup.object({ pledgeCurrency: yup.string().nullable(), nextAsk: nullableDateTime(), noAppeals: yup.boolean().default(false).nullable(), + name: yup.string().required(), + primaryPersonId: yup.string(), sendNewsletter: yup .mixed() .oneOf(Object.values(SendNewsletterEnum)) .nullable(), pledgeFrequency: yup.mixed().nullable(), - contactReferralsToMe: yup - .array() - .of( - yup.object({ - destroy: yup.boolean().default(false), - referredById: yup.string().nullable(), - id: yup.string().nullable(), - }), - ) - .default([]), likelyToGive: yup.mixed().nullable(), }); @@ -133,41 +121,16 @@ export const EditPartnershipInfoModal: React.FC< const { appName } = useGetAppSettings(); const accountListId = useAccountListId(); const constants = useApiConstants(); - const [referredByName, setReferredByName] = useState(''); const [showRemoveCommitmentWarning, setShowRemoveCommitmentWarning] = useState(false); - const referredContactIds = contact.contactReferralsToMe.nodes.map( - (referral) => referral.referredBy.id, - ); - const [currentReferredContactIds, setCurrentReferredContactIds] = - useState(referredContactIds); const { enqueueSnackbar } = useSnackbar(); - const { data, loading, refetch } = useGetDataForPartnershipInfoModalQuery({ - variables: { - accountListId: accountListId ?? '', - contactsFilter: { - nameLike: referredByName, - }, - }, - }); const [updateContactPartnership, { loading: updating }] = useUpdateContactPartnershipMutation(); const pledgeCurrencies = constants?.pledgeCurrency; const onSubmit = async (attributes: Attributes) => { - const removedReferrals = contact.contactReferralsToMe.nodes - .filter( - (referral) => - currentReferredContactIds.indexOf(referral.referredBy.id) === -1, - ) - .map((referral) => ({ - id: referral.id, - referredById: referral.referredBy.id, - destroy: true, - })); - await updateContactPartnership({ variables: { accountListId: accountListId ?? '', @@ -175,10 +138,7 @@ export const EditPartnershipInfoModal: React.FC< ...attributes, pledgeStartDate: attributes.pledgeStartDate?.toISODate(), nextAsk: attributes.nextAsk?.toISODate(), - contactReferralsToMe: [ - ...(attributes.contactReferralsToMe || []), - ...removedReferrals, - ], + primaryPersonId: attributes.primaryPersonId, }, }, }); @@ -189,89 +149,6 @@ export const EditPartnershipInfoModal: React.FC< handleClose(); }; - const handleUpdateReferredBySearch = (search: string) => { - setReferredByName(search); - refetch({ - accountListId, - contactsFilter: { - nameLike: referredByName, - }, - }); - }; - - const filteredContacts = data?.contacts.nodes.filter( - ({ id }) => id !== contact.id, - ); - - const contactReferralData = contact.contactReferralsToMe.nodes.map( - (referral) => ({ - name: referral.referredBy.name, - id: referral.referredBy.id, - }), - ); - - const contactReferrals = contact.contactReferralsToMe.nodes.map( - (referral) => ({ - destroy: false, - referredById: referral.referredBy.id, - id: referral.id, - }), - ); - - const updateCurrentContacts = (ids: string[]) => { - // Use the current list of contacts from the data and the current selected ids to determine if there are any new contacts - const newCurrentContacts = - data?.contacts.nodes - .filter( - (contact) => - ids.indexOf(contact.id) !== -1 && - currentReferredContactIds.indexOf(contact.id) === -1, - ) - .map(({ name, id }) => ({ name, id })) || []; - - setCurrentContacts([ - // filter out any current contacts that are no longer selected - ...currentContacts.filter((contact) => ids.indexOf(contact.id) !== -1), - ...newCurrentContacts, - ]); - }; - - // Value used to persist currently selected contact data, even as the user searches for new contacts - const [currentContacts, setCurrentContacts] = useState(contactReferralData); - - const updateReferredBy = ( - ids: string[], - setFieldValue: (name: string, value: ContactReferralToMeInput[]) => void, - ) => { - // Set the ids currently selected - setCurrentReferredContactIds(ids); - // Update array of current contacts based on currently selected ids - updateCurrentContacts(ids); - - // Map through current referrals and filter out ones that are not currently selected - const referralsToMe = contact.contactReferralsToMe.nodes - .filter((referral) => ids.indexOf(referral.referredBy.id) !== -1) - .map((referral) => { - return { - id: referral.id, - referredById: referral.referredBy.id, - destroy: false, - }; - }); - // Map through currently selected ids, and filter out any that are already existing referral contact ids - const newReferralsToMe = ids - .filter((referredId) => referredContactIds.indexOf(referredId) === -1) - .map((referredById) => ({ - destroy: false, - referredById, - })); - - setFieldValue('contactReferralsToMe', [ - ...referralsToMe, - ...newReferralsToMe, - ]); - }; - const updateStatus = ( newStatus: StatusEnum, setFieldValue: (name: string, value: StatusEnum | number | null) => void, @@ -317,8 +194,9 @@ export const EditPartnershipInfoModal: React.FC< nextAsk: contact.nextAsk ? DateTime.fromISO(contact.nextAsk) : null, noAppeals: Boolean(contact.noAppeals), sendNewsletter: contact.sendNewsletter ?? SendNewsletterEnum.None, - contactReferralsToMe: contactReferrals, likelyToGive: contact.likelyToGive, + name: contact.name, + primaryPersonId: contact?.primaryPerson?.id ?? '', }} validationSchema={contactPartnershipSchema} onSubmit={onSubmit} @@ -334,310 +212,332 @@ export const EditPartnershipInfoModal: React.FC< nextAsk, noAppeals, sendNewsletter, - contactReferralsToMe, likelyToGive, + name, + primaryPersonId, }, handleSubmit, handleChange, setFieldValue, isSubmitting, isValid, + touched, errors, + handleBlur, }) => ( {errors.pledgeFrequency} - - - - {t('Status')} - - - - - {showRemoveCommitmentWarning && ( - - - - {t( - '{{appName}} uses your contact status, commitment amount, and frequency together to calculate many things, including your progress towards your goal and notification alerts.', - { appName }, - )} - - - {t( - 'If you are switching this contact away from Partner - Financial status, their commitment amount and frequency will no longer be included in calculations. Would you like to remove their commitment amount and frequency, as well?', - )} - - - + + + + + )} + + + + {t( + 'Commitments can only be set if status is Partner - Financial', + )} + + } + > + + + ), }} - > - - {getPledgeCurrencyOptions(pledgeCurrencies)} - - )} - - - - - - {t('Likely To Give')} - - - - - - - - {t('Frequency')} - - - setFieldValue('pledgeFrequency', e.target.value) - } - IconComponent={ - status !== StatusEnum.PartnerFinancial - ? () => ( - - {t( - 'Commitments can only be set if status is Partner - Financial', - )} - - } - > - - - ) - : undefined - } - > - - {Object.values(PledgeFrequencyEnum).map((value) => ( - - {getLocalizedPledgeFrequency(t, value)} - - ))} - - - - - setFieldValue('pledgeStartDate', date)} - /> - - - - - {t('Newsletter')} - - - - - - a.name.localeCompare(b.name)) - .map(({ id }) => id)) || - [] - } - getOptionLabel={(contactId) => - (filteredContacts && - [...currentContacts, ...filteredContacts].find( - ({ id }) => id === contactId, - )?.name) ?? - '' - } - loading={loading} - inputValue={referredByName} - renderInput={(params): ReactElement => ( - - handleUpdateReferredBySearch(e.target.value) + fullWidth + /> + + + + + + + {t('Frequency')} + + + setFieldValue('pledgeFrequency', e.target.value) + } + IconComponent={ + status !== StatusEnum.PartnerFinancial + ? () => ( + + {t( + 'Commitments can only be set if status is Partner - Financial', + )} + + } + > + + + ) + : undefined + } + > + + {Object.values(PledgeFrequencyEnum).map((value) => ( + + {getLocalizedPledgeFrequency(t, value)} + + ))} + + + + + + + + + {t('Currency')} + + {pledgeCurrencies && ( + + )} + + + + + + + setFieldValue('pledgeStartDate', date) } /> - )} - value={ - contactReferralsToMe.map( - (referral) => referral.referredById, - ) ?? undefined - } - onChange={(_, contactIds): void => - updateReferredBy(contactIds, setFieldValue) - } - isOptionEqualToValue={(option, value): boolean => - option === value - } - /> - - - setFieldValue('nextAsk', nextAsk)} - /> - + + + + + + + {t('Likely To Give')} + + + + + + + + setFieldValue('nextAsk', nextAsk)} + /> + + + { ); expect(await findByText(task.subject)).toBeVisible(); - userEvent.click(getByRole('img', { hidden: true, name: 'Check Icon' })); + userEvent.click(getByRole('img', { hidden: true, name: 'Check' })); expect(openTaskModal).toHaveBeenCalledWith({ view: TaskModalEnum.Complete, taskId: task.id, @@ -197,7 +197,7 @@ describe('ContactTaskRow', () => { ); expect(await findByText(task.subject)).toBeVisible(); - userEvent.click(getByRole('img', { hidden: true, name: 'Comment Icon' })); + userEvent.click(getByRole('img', { hidden: true, name: 'Comment' })); expect(openTaskModal).toHaveBeenCalledWith({ taskId: task.id, view: TaskModalEnum.Comments, @@ -226,7 +226,7 @@ describe('ContactTaskRow', () => { ); expect(await findByText(task.subject)).toBeVisible(); - userEvent.click(getByRole('img', { name: 'Outlined Delete Icon' })); + userEvent.click(getByRole('img', { name: 'Delete' })); expect( await findByText('Are you sure you wish to delete the selected task?'), ).toBeVisible(); diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCommentsButton/TaskCommentsButton.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCommentsButton/TaskCommentsButton.tsx index b6efcc7a1..88e762db1 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCommentsButton/TaskCommentsButton.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCommentsButton/TaskCommentsButton.tsx @@ -2,6 +2,7 @@ import React from 'react'; import ChatBubbleOutline from '@mui/icons-material/ChatBubbleOutline'; import { Button, ButtonProps, Typography } from '@mui/material'; import { styled } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; import theme from 'src/theme'; const TaskRowWrap = styled(Button, { @@ -48,13 +49,14 @@ export const TaskCommentsButton: React.FC = ({ detailsPage, ...props }) => { + const { t } = useTranslation(); return ( - + {numberOfComments} diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCompleteButton/TaskCompleteButton.test.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCompleteButton/TaskCompleteButton.test.tsx index c66be3f1e..87c5fbf69 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCompleteButton/TaskCompleteButton.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCompleteButton/TaskCompleteButton.test.tsx @@ -16,7 +16,7 @@ describe('TaskCompleteButton', () => { ); const completeButton = getByRole('button'); - const checkIcon = getByRole('img', { hidden: true, name: 'Check Icon' }); + const checkIcon = getByRole('img', { hidden: true, name: 'Check' }); expect(completeButton).toBeInTheDocument(); expect(checkIcon).toBeInTheDocument(); @@ -42,7 +42,7 @@ describe('TaskCompleteButton', () => { ); const completeButton = getByRole('button'); - const checkIcon = getByRole('img', { hidden: true, name: 'Check Icon' }); + const checkIcon = getByRole('img', { hidden: true, name: 'Check' }); expect(completeButton).toBeInTheDocument(); expect(checkIcon).toBeInTheDocument(); diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCompleteButton/TaskCompleteButton.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCompleteButton/TaskCompleteButton.tsx index 7ea00352a..028630cb4 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCompleteButton/TaskCompleteButton.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTaskRow/TaskCompleteButton/TaskCompleteButton.tsx @@ -2,6 +2,7 @@ import React from 'react'; import Check from '@mui/icons-material/Check'; import { Button, ButtonProps } from '@mui/material'; import { styled } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; import theme from 'src/theme'; const ButtonWrap = styled(Button, { @@ -31,9 +32,10 @@ export const TaskCompleteButton: React.FC = ({ isComplete, ...props }) => { + const { t } = useTranslation(); return ( - + ); }; diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTasksTab.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTasksTab.tsx index 8a54c0f4c..f30858d8a 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTasksTab.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/ContactTasksTab.tsx @@ -200,7 +200,7 @@ export const ContactTasksTab: React.FC = ({ /> - + = ({ }) } style={{ - height: `Max(calc(100vh - ${infiniteListRectTop}px), 260px)`, + height: `max(100vh - ${infiniteListRectTop}px, 500px)`, }} data-testid="virtuoso-item-list" /> diff --git a/src/components/Contacts/ContactDetails/ContactTasksTab/StarTaskIconButton/StarTaskIconButton.test.tsx b/src/components/Contacts/ContactDetails/ContactTasksTab/StarTaskIconButton/StarTaskIconButton.test.tsx index 43529364a..fc60d4175 100644 --- a/src/components/Contacts/ContactDetails/ContactTasksTab/StarTaskIconButton/StarTaskIconButton.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactTasksTab/StarTaskIconButton/StarTaskIconButton.test.tsx @@ -10,7 +10,7 @@ const taskId = '1'; describe('StarTaskIconButton', () => { it('renders not starred', async () => { - const { queryByRole } = render( + const { queryByTestId } = render( { , ); - const starFilledIcon = queryByRole('img', { - hidden: true, - name: 'Filled Star Icon', - }); - const starOutlineIcon = queryByRole('img', { - hidden: true, - name: 'Outline Star Icon', - }); + const starFilledIcon = queryByTestId('Filled Star Icon'); + const starOutlineIcon = queryByTestId('Outline Star Icon'); expect(starFilledIcon).not.toBeInTheDocument(); expect(starOutlineIcon).toBeInTheDocument(); }); it('renders starred', async () => { - const { queryByRole } = render( + const { queryByTestId } = render( { , ); - const starFilledIcon = queryByRole('img', { - hidden: true, - name: 'Filled Star Icon', - }); - const starOutlineIcon = queryByRole('img', { - hidden: true, - name: 'Outline Star Icon', - }); + const starFilledIcon = queryByTestId('Filled Star Icon'); + const starOutlineIcon = queryByTestId('Outline Star Icon'); expect(starFilledIcon).toBeInTheDocument(); expect(starOutlineIcon).not.toBeInTheDocument(); diff --git a/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.test.tsx b/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.test.tsx index 0994c0646..b3d75af76 100644 --- a/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.test.tsx +++ b/src/components/Contacts/ContactFlow/ContactFlowRow/ContactFlowRow.test.tsx @@ -45,7 +45,7 @@ describe('ContactFlowRow', () => { , ); expect(getByText('Test Name')).toBeInTheDocument(); - expect(getByTitle('Filled Star Icon')).toBeInTheDocument(); + expect(getByTitle('Unstar')).toBeInTheDocument(); }); it('should call contact selected function', () => { diff --git a/src/components/Contacts/ContactRow/ContactRow.test.tsx b/src/components/Contacts/ContactRow/ContactRow.test.tsx index b8ec1fd67..32e236f0b 100644 --- a/src/components/Contacts/ContactRow/ContactRow.test.tsx +++ b/src/components/Contacts/ContactRow/ContactRow.test.tsx @@ -106,10 +106,12 @@ describe('ContactsRow', () => { getByText( [ contact.primaryAddress?.street, - contact.primaryAddress?.city, + `${contact.primaryAddress?.city}${ + contact.primaryAddress?.city && ',' + }`, contact.primaryAddress?.state, contact.primaryAddress?.postalCode, - ].join(', '), + ].join(' '), ), ).toBeInTheDocument(); }); diff --git a/src/components/Contacts/ContactRow/ContactRow.tsx b/src/components/Contacts/ContactRow/ContactRow.tsx index e79ba64bf..1c6109fd2 100644 --- a/src/components/Contacts/ContactRow/ContactRow.tsx +++ b/src/components/Contacts/ContactRow/ContactRow.tsx @@ -120,10 +120,12 @@ export const ContactRow: React.FC = ({ contact, useTopMargin }) => { {[ primaryAddress.street, - primaryAddress.city, + `${primaryAddress.city}${primaryAddress.city && ','}`, primaryAddress.state, primaryAddress.postalCode, - ].join(', ')} + ] + .filter(Boolean) + .join(' ')} ) diff --git a/src/components/Contacts/StarContactIconButton/StarContactIconButton.test.tsx b/src/components/Contacts/StarContactIconButton/StarContactIconButton.test.tsx index 05129ac39..8c74ba582 100644 --- a/src/components/Contacts/StarContactIconButton/StarContactIconButton.test.tsx +++ b/src/components/Contacts/StarContactIconButton/StarContactIconButton.test.tsx @@ -12,7 +12,7 @@ const contactId = '1'; describe('StarTaskIconButton', () => { it('renders not starred', async () => { - const { queryByRole } = render( + const { queryByTestId } = render( { , ); - const starFilledIcon = queryByRole('img', { - hidden: true, - name: 'Filled Star Icon', - }); - const starOutlineIcon = queryByRole('img', { - hidden: true, - name: 'Outline Star Icon', - }); + const starFilledIcon = queryByTestId('Filled Star Icon'); + const starOutlineIcon = queryByTestId('Outline Star Icon'); expect(starFilledIcon).not.toBeInTheDocument(); expect(starOutlineIcon).toBeInTheDocument(); }); it('renders starred', async () => { - const { queryByRole } = render( + const { queryByTestId } = render( { , ); - const starFilledIcon = queryByRole('img', { - hidden: true, - name: 'Filled Star Icon', - }); - const starOutlineIcon = queryByRole('img', { - hidden: true, - name: 'Outline Star Icon', - }); + const starFilledIcon = queryByTestId('Filled Star Icon'); + const starOutlineIcon = queryByTestId('Outline Star Icon'); expect(starFilledIcon).toBeInTheDocument(); expect(starOutlineIcon).not.toBeInTheDocument(); @@ -66,7 +54,7 @@ describe('StarTaskIconButton', () => { it('should toggle starred state', async () => { const mutationSpy = jest.fn(); - const { getByRole } = render( + const { getByTestId } = render( { , ); + const starOutlineIcon = getByTestId('Outline Star Icon'); - userEvent.click( - getByRole('img', { - hidden: true, - name: 'Outline Star Icon', - }), - ); + userEvent.click(starOutlineIcon); await waitFor(() => expect(mutationSpy).toHaveBeenCalledWith({ diff --git a/src/components/Layouts/Primary/NavBar/NavItem/NavItem.tsx b/src/components/Layouts/Primary/NavBar/NavItem/NavItem.tsx index 3ba1d2ac5..cb87c501a 100644 --- a/src/components/Layouts/Primary/NavBar/NavItem/NavItem.tsx +++ b/src/components/Layouts/Primary/NavBar/NavItem/NavItem.tsx @@ -5,6 +5,7 @@ import ChevronRightIcon from '@mui/icons-material/ChevronRight'; import ExpandLessIcon from '@mui/icons-material/ExpandLess'; import { Button, Collapse, ListItemButton, MenuItem } from '@mui/material'; import { styled, useTheme } from '@mui/material/styles'; +import { useTranslation } from 'react-i18next'; import HandoffLink from 'src/components/HandoffLink'; import { LeafListItem, Title } from '../StyledComponents'; @@ -54,6 +55,7 @@ export const NavItem: FC = ({ }) => { const [open, setOpen] = useState(openProp ?? false); const theme = useTheme(); + const { t } = useTranslation(); const handleToggle = (): void => { setOpen((prevOpen) => !prevOpen); @@ -80,13 +82,13 @@ export const NavItem: FC = ({ ) : ( )} diff --git a/src/components/Layouts/Primary/TopBar/Items/AddMenu/AddMenu.tsx b/src/components/Layouts/Primary/TopBar/Items/AddMenu/AddMenu.tsx index 8c454de29..9af66d0ce 100644 --- a/src/components/Layouts/Primary/TopBar/Items/AddMenu/AddMenu.tsx +++ b/src/components/Layouts/Primary/TopBar/Items/AddMenu/AddMenu.tsx @@ -189,6 +189,7 @@ const AddMenu = ({ isInDrawer = false }: AddMenuProps): ReactElement => { const [selectedMenuItem, changeSelectedMenuItem] = useState(null); const [dialogOpen, changeDialogOpen] = useState(false); + const { t } = useTranslation(); const addMenuContent = [ { @@ -254,7 +255,7 @@ const AddMenu = ({ isInDrawer = false }: AddMenuProps): ReactElement => { aria-expanded={Boolean(anchorEl)} onClick={(event) => setAnchorEl(event.currentTarget)} > - + {isExpanded ? ( - + ) : ( - + )} } @@ -60,7 +60,7 @@ export const FourteenMonthReportActions: React.FC<