diff --git a/.pnp.cjs b/.pnp.cjs index 1bb0503d1..f76603110 100644 --- a/.pnp.cjs +++ b/.pnp.cjs @@ -8314,7 +8314,7 @@ function $$SETUP_STATE(hydrateRuntimeState, basePath) { ]],\ ["@surma/rollup-plugin-off-main-thread", [\ ["npm:2.2.3", {\ - "packageLocation": "./.yarn/unplugged/@surma-rollup-plugin-off-main-thread-npm-2.2.3-1f57d3eded/node_modules/@surma/rollup-plugin-off-main-thread/",\ + "packageLocation": "./.yarn/cache/@surma-rollup-plugin-off-main-thread-npm-2.2.3-1f57d3eded-2c02134944.zip/node_modules/@surma/rollup-plugin-off-main-thread/",\ "packageDependencies": [\ ["@surma/rollup-plugin-off-main-thread", "npm:2.2.3"],\ ["ejs", "npm:3.1.10"],\ diff --git a/src/components/Contacts/ContactDetails/ContactDonationsTab/ContactDonationsTab.graphql b/src/components/Contacts/ContactDetails/ContactDonationsTab/ContactDonationsTab.graphql index 24c785a4d..0d69864f7 100644 --- a/src/components/Contacts/ContactDetails/ContactDonationsTab/ContactDonationsTab.graphql +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/ContactDonationsTab.graphql @@ -25,6 +25,7 @@ fragment ContactDonorAccounts on Contact { source likelyToGive name + relationshipCode primaryPerson { id } diff --git a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.graphql b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.graphql index 1eb40994c..61ab5d620 100644 --- a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.graphql +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.graphql @@ -36,6 +36,7 @@ mutation UpdateContactPartnership( } } likelyToGive + relationshipCode } } } 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 cc411a508..8e1572f4e 100644 --- a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.test.tsx +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.test.tsx @@ -18,6 +18,11 @@ import { ContactDonorAccountsFragment, ContactDonorAccountsFragmentDoc, } from '../../ContactDonationsTab.generated'; +import { UserOrganizationAccountsQuery } from '../PartnershipInfo.generated'; +import { + organizationAccountsMock, + organizationAccountsWithCruSwitzerlandMock, +} from '../PartnershipInfoMocks'; import { EditPartnershipInfoModal } from './EditPartnershipInfoModal'; jest.mock('notistack', () => ({ @@ -115,14 +120,27 @@ const newContactMock = gqlMock( interface ComponentsProps { isNewContact?: boolean; + includeCruSwitzerland?: boolean; } -const Components = ({ isNewContact = false }: ComponentsProps) => ( +const Components = ({ + isNewContact = false, + includeCruSwitzerland = false, +}: ComponentsProps) => ( - + + mocks={{ + UserOrganizationAccounts: includeCruSwitzerland + ? organizationAccountsWithCruSwitzerlandMock + : organizationAccountsMock, + }} + onCall={mutationSpy} + > { }), ); }); + + describe('Relationship code', () => { + it('should not render relationshipCode', async () => { + const { queryByRole } = render(); + + await waitFor(() => { + expect(mutationSpy).toHaveGraphqlOperation('UserOrganizationAccounts'); + }); + + expect( + queryByRole('textbox', { + name: 'Relationship Code', + }), + ).not.toBeInTheDocument(); + }); + + it('should render relationshipCode', async () => { + const { findByRole } = render(); + + expect( + await findByRole('textbox', { + name: 'Relationship Code', + }), + ).toBeInTheDocument(); + }); + + // Remove skip this when the UpdateContact mutation operation is updated to include the relationshipCode + it.skip('should update relationshipCode', async () => { + const { findByRole, getByText } = render( + , + ); + + const relationshipCode = await findByRole('textbox', { + name: 'Relationship Code', + }); + userEvent.clear(relationshipCode); + userEvent.type(relationshipCode, '1234'); + userEvent.click(getByText('Save')); + + await waitFor(() => + expect(mutationSpy).toHaveGraphqlOperation('UpdateContactPartnership', { + attributes: { + relationshipCode: '1234', + }, + }), + ); + }); + }); }); diff --git a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.tsx b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.tsx index 7b40dc5ab..f292dca91 100644 --- a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.tsx +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/EditPartnershipInfoModal/EditPartnershipInfoModal.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import InfoIcon from '@mui/icons-material/InfoOutlined'; import { Alert, @@ -46,6 +46,8 @@ import { useAccountListId } from '../../../../../../hooks/useAccountListId'; import { useApiConstants } from '../../../../../Constants/UseApiConstants'; import Modal from '../../../../../common/Modal/Modal'; import { ContactDonorAccountsFragment } from '../../ContactDonationsTab.generated'; +import { isApartOfSwitzerlandOrganization } from '../PartnershipInfo'; +import { useUserOrganizationAccountsQuery } from '../PartnershipInfo.generated'; import { useUpdateContactPartnershipMutation } from './EditPartnershipInfoModal.generated'; const ContactInputWrapper = styled(Box)(({ theme }) => ({ @@ -102,6 +104,7 @@ const contactPartnershipSchema = yup.object({ .nullable(), pledgeFrequency: yup.mixed().nullable(), likelyToGive: yup.mixed().nullable(), + relationshipCode: yup.string().nullable(), }); type Attributes = yup.InferType; @@ -118,6 +121,7 @@ export const EditPartnershipInfoModal: React.FC< const { appName } = useGetAppSettings(); const accountListId = useAccountListId(); const constants = useApiConstants(); + const { enqueueSnackbar } = useSnackbar(); const { getLocalizedContactStatus, getLocalizedPledgeFrequency } = useLocalizedConstants(); @@ -125,18 +129,26 @@ export const EditPartnershipInfoModal: React.FC< const [showRemoveCommitmentWarning, setShowRemoveCommitmentWarning] = useState(false); - const { enqueueSnackbar } = useSnackbar(); + const { data } = useUserOrganizationAccountsQuery(); + const userOrganizationAccounts = data?.userOrganizationAccounts; + + const showRelationshipCode = useMemo( + () => isApartOfSwitzerlandOrganization(userOrganizationAccounts), + [userOrganizationAccounts], + ); const [updateContactPartnership, { loading: updating }] = useUpdateContactPartnershipMutation(); const pledgeCurrencies = constants?.pledgeCurrency; const onSubmit = async (attributes: Attributes) => { + // Remove and just use "attributes" when the UpdateContact mutation operation is updated to include the relationshipCode + const { relationshipCode: _, ...newAttributes } = attributes; await updateContactPartnership({ variables: { accountListId: accountListId ?? '', attributes: { - ...attributes, + ...newAttributes, pledgeStartDate: attributes.pledgeStartDate?.toISODate() ?? null, nextAsk: attributes.nextAsk?.toISODate() ?? null, primaryPersonId: attributes.primaryPersonId, @@ -198,6 +210,7 @@ export const EditPartnershipInfoModal: React.FC< likelyToGive: contact.likelyToGive, name: contact.name, primaryPersonId: contact?.primaryPerson?.id ?? '', + relationshipCode: contact.relationshipCode ?? '', }} validationSchema={contactPartnershipSchema} onSubmit={onSubmit} @@ -216,6 +229,7 @@ export const EditPartnershipInfoModal: React.FC< likelyToGive, name, primaryPersonId, + relationshipCode, }, handleSubmit, handleChange, @@ -543,7 +557,23 @@ export const EditPartnershipInfoModal: React.FC< /> + {showRelationshipCode && ( + + + + + + )} + ( - ContactDonorAccountsFragmentDoc, - { - mocks: { - status: StatusEnum.PartnerFinancial, - nextAsk: DateTime.local().plus({ month: 3 }).toISO(), - pledgeCurrency: 'CAD', - pledgeStartDate: DateTime.local().toISO(), - pledgeFrequency: PledgeFrequencyEnum.Annual, - pledgeAmount: 55, - lastDonation: { - donationDate: DateTime.local().toISO(), - amount: { - currency: 'CAD', - }, - }, - }, - }, -); +const mutationSpy = jest.fn(); -const emptyMock = gqlMock( - ContactDonorAccountsFragmentDoc, - { - mocks: { - status: null, - }, - }, -); +interface ComponentsProps { + useEmptyMock?: boolean; + includeCruSwitzerland?: boolean; +} -jest.mock('next/router', () => ({ - useRouter: () => { - return { - query: { accountListId: 'abc' }, - isReady: true, - }; - }, -})); +const Components = ({ + useEmptyMock = false, + includeCruSwitzerland = false, +}: ComponentsProps) => ( + + + + + + mocks={{ + UserOrganizationAccounts: includeCruSwitzerland + ? organizationAccountsWithCruSwitzerlandMock + : organizationAccountsMock, + }} + onCall={mutationSpy} + > + + + + + + +); describe('PartnershipInfo', () => { - it('test renderer', async () => { - const { getByText, findByText } = render( - - - - - - - - - , - ); + it('test renderer', () => { + const { getByText } = render(); + + expect(getByText('CA$55 - Annual')).toBeInTheDocument(); + expect(getByText('Partner - Financial')).toBeInTheDocument(); + }); + + it('renders No Status', () => { + const { getByText } = render(); + + expect(getByText('No Status')).toBeInTheDocument(); + }); + + it('should not render relationship code', async () => { + const { queryByText } = render(); await waitFor(() => { - expect(getByText('CA$55 - Annual')).toBeInTheDocument(); + expect(mutationSpy).toHaveGraphqlOperation('UserOrganizationAccounts'); }); - expect(await findByText('Partner - Financial')).toBeInTheDocument(); + expect(queryByText('Relationship Code')).not.toBeInTheDocument(); }); - it('renders No Status', async () => { - const { findByText } = render( - - - - - - - - - , - ); + it('should render relationship code', async () => { + const { findByText } = render(); - expect(await findByText('No Status')).toBeInTheDocument(); + expect(await findByText('Relationship Code')).toBeInTheDocument(); }); it('should open edit partnership information modal', () => { - const { getByText, getByLabelText } = render( - - - - - - - - - , - ); + const { getByText, getByLabelText } = render(); userEvent.click(getByLabelText('Edit Icon')); expect(getByText('Edit Partnership')).toBeInTheDocument(); }); it('should close edit partnership information modal', async () => { - const { getByText, getByLabelText, queryByText } = render( - - - - - - - - - , - ); + const { getByText, getByLabelText, queryByText } = render(); userEvent.click(getByLabelText('Edit Icon')); expect(getByText('Edit Partnership')).toBeInTheDocument(); diff --git a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/PartnershipInfo.tsx b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/PartnershipInfo.tsx index 018b2506b..f1748974a 100644 --- a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/PartnershipInfo.tsx +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/PartnershipInfo.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useMemo, useState } from 'react'; import CheckCircleOutline from '@mui/icons-material/CheckCircleOutline'; import Clear from '@mui/icons-material/Clear'; import CreateIcon from '@mui/icons-material/Create'; @@ -16,6 +16,12 @@ import { currencyFormat } from '../../../../../lib/intlFormat'; import { HandshakeIcon } from '../../ContactDetailsHeader/ContactHeaderSection/HandshakeIcon'; import { ContactDonorAccountsFragment } from '../ContactDonationsTab.generated'; import { EditPartnershipInfoModal } from './EditPartnershipInfoModal/EditPartnershipInfoModal'; +import { + UserOrganizationAccountsQuery, + useUserOrganizationAccountsQuery, +} from './PartnershipInfo.generated'; + +export const SwitzerlandOrganizationName = 'Campus fuer Christus Switzerland'; const IconAndTextContainer = styled(Box)(({ theme }) => ({ margin: theme.spacing(0, 4), @@ -65,6 +71,17 @@ interface PartnershipInfoProp { contact: ContactDonorAccountsFragment | null; } +export const isApartOfSwitzerlandOrganization = ( + userOrganizationAccounts?: UserOrganizationAccountsQuery['userOrganizationAccounts'], +) => { + return ( + userOrganizationAccounts?.some( + (organizationAccount) => + organizationAccount.organization.name === SwitzerlandOrganizationName, + ) ?? false + ); +}; + export const PartnershipInfo: React.FC = ({ contact }) => { const { t } = useTranslation(); const locale = useLocale(); @@ -74,6 +91,14 @@ export const PartnershipInfo: React.FC = ({ contact }) => { const [editPartnershipModalOpen, setEditPartnershipModalOpen] = useState(false); + const { data } = useUserOrganizationAccountsQuery(); + const userOrganizationAccounts = data?.userOrganizationAccounts; + + const showRelationshipCode = useMemo( + () => isApartOfSwitzerlandOrganization(userOrganizationAccounts), + [userOrganizationAccounts], + ); + return ( @@ -196,6 +221,21 @@ export const PartnershipInfo: React.FC = ({ contact }) => { )} + + {showRelationshipCode && ( + + + + + + {t('Relationship Code')} + + + {contact?.relationshipCode} + + + )} + diff --git a/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/PartnershipInfoMocks.ts b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/PartnershipInfoMocks.ts new file mode 100644 index 000000000..0e13f773c --- /dev/null +++ b/src/components/Contacts/ContactDetails/ContactDonationsTab/PartnershipInfo/PartnershipInfoMocks.ts @@ -0,0 +1,65 @@ +import { DateTime } from 'luxon'; +import { gqlMock } from '__tests__/util/graphqlMocking'; +import { PledgeFrequencyEnum, StatusEnum } from 'src/graphql/types.generated'; +import { + ContactDonorAccountsFragment, + ContactDonorAccountsFragmentDoc, +} from '../ContactDonationsTab.generated'; +import { SwitzerlandOrganizationName } from './PartnershipInfo'; + +export const contactMock = gqlMock( + ContactDonorAccountsFragmentDoc, + { + mocks: { + status: StatusEnum.PartnerFinancial, + nextAsk: DateTime.local().plus({ month: 3 }).toISO(), + pledgeCurrency: 'CAD', + pledgeStartDate: DateTime.local().toISO(), + pledgeFrequency: PledgeFrequencyEnum.Annual, + pledgeAmount: 55, + lastDonation: { + donationDate: DateTime.local().toISO(), + amount: { + currency: 'CAD', + }, + }, + }, + }, +); + +export const contactEmptyMock = gqlMock( + ContactDonorAccountsFragmentDoc, + { + mocks: { + status: null, + }, + }, +); + +export const organizationAccountsMock = { + userOrganizationAccounts: [ + { + organization: { + name: 'Cru - New Staff', + }, + id: '123', + }, + ], +}; + +export const organizationAccountsWithCruSwitzerlandMock = { + userOrganizationAccounts: [ + { + organization: { + name: 'Cru - New Staff', + }, + id: '123', + }, + { + organization: { + name: SwitzerlandOrganizationName, + }, + id: '456', + }, + ], +};