Skip to content

Commit

Permalink
Keep track of the sources to inform the user.
Browse files Browse the repository at this point in the history
Also allowing the user to delete the contact, and warning them
  • Loading branch information
dr-bizz committed Dec 9, 2024
1 parent c78f209 commit 0164a01
Show file tree
Hide file tree
Showing 2 changed files with 153 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -156,15 +156,13 @@ describe('DeleteContactModal', () => {
];

test.each(tests)('$testName', async ({ props }) => {
const { findByText, getByRole } = render(<TestComponent {...props} />);
const { findByText } = render(<TestComponent {...props} />);

expect(
await findByText(
/This contact cannot be deleted because part or all of the contact's data syncs with Donation Services/,
/its data may sync with Donation Services or other third-party systems/,
),
).toBeInTheDocument();

expect(getByRole('button', { name: 'delete contact' })).toBeDisabled();
});
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import React, { useMemo } from 'react';
import { Email, Person, Phone, Place } from '@mui/icons-material';
import {
CircularProgress,
DialogActions,
DialogContent,
DialogContentText,
List,
ListItem,
ListItemIcon,
ListItemText,
Typography,
} from '@mui/material';
import { styled } from '@mui/material/styles';
import { useTranslation } from 'react-i18next';
Expand All @@ -13,13 +18,22 @@ import {
} from 'src/components/common/Modal/ActionButtons/ActionButtons';
import Modal from 'src/components/common/Modal/Modal';
import { useAccountListId } from 'src/hooks/useAccountListId';
import { isEditableSource } from 'src/utils/sourceHelper';
import { isEditableSource, sourceToStr } from 'src/utils/sourceHelper';
import { useContactSourceQuery } from './ContactSource.generated';

const LoadingIndicator = styled(CircularProgress)(({ theme }) => ({
margin: theme.spacing(0, 1, 0, 0),
}));

interface DataInfo {
canDelete: boolean;
uniqueSources: {
contact: string[];
address: string[];
email: string[];
phone: string[];
};
}
interface DeleteContactModalProps {
open: boolean;
setOpen: (open: boolean) => void;
Expand All @@ -44,9 +58,17 @@ export const DeleteContactModal: React.FC<DeleteContactModalProps> = ({
});
const contactSources = data?.contact;

const canDelete = useMemo(() => {
const dataInfo: DataInfo = useMemo(() => {
if (!contactSources) {
return true;
return {
canDelete: true,
uniqueSources: {
contact: [],
address: [],
email: [],
phone: [],
},
};
}
// We ensure the contact was created on MPDX and that all the data is editable.
// If any data is not editable, this means it was created by a third party.
Expand All @@ -55,26 +77,67 @@ export const DeleteContactModal: React.FC<DeleteContactModalProps> = ({

const isContactNonEditable = !isEditableSource(contactSources.source ?? '');

const isAddressNonEditable = contactSources.addresses?.nodes.some(
(address) => !isEditableSource(address.source ?? ''),
const sources: Map<string, string[]> = new Map();
sources.set('contact', [contactSources.source ?? '']);

const isAddressNonEditable = contactSources.addresses?.nodes.reduce(
(acc, address) => {
sources.set('address', [
...(sources.get('address') ?? []),
address.source,
]);
return acc || !isEditableSource(address.source ?? '');
},
false,
);

const hasNonEditablePersonData = contactSources.people?.nodes?.some(
(people) => {
const foundNonEditableEmailAddress = people.emailAddresses.nodes.some(
(email) => !isEditableSource(email.source),
const hasNonEditablePersonData = contactSources.people?.nodes?.reduce(
(acc, person) => {
const foundNonEditableEmailAddress = person.emailAddresses.nodes.reduce(
(emailAcc, email) => {
sources.set('email', [
...(sources.get('email') ?? []),
email.source,
]);
return emailAcc || !isEditableSource(email.source);
},
false,
);
const foundNonEditablePhone = people.phoneNumbers.nodes.some(
(phone) => !isEditableSource(phone.source),
const foundNonEditablePhone = person.phoneNumbers.nodes.reduce(
(phoneAcc, phone) => {
sources.set('phone', [
...(sources.get('phone') ?? []),
phone.source,
]);
return phoneAcc || !isEditableSource(phone.source);
},
false,
);
return foundNonEditableEmailAddress || foundNonEditablePhone;
return acc || foundNonEditableEmailAddress || foundNonEditablePhone;
},
false,
);

const uniqueSources: DataInfo['uniqueSources'] = {
contact: [],
address: [],
email: [],
phone: [],
...Object.fromEntries(
Array.from(sources, ([dataType, ArrOfSources]) => [
dataType,
[...new Set(ArrOfSources)].map((source) => sourceToStr(t, source)),
]),
),
};

const contactIsNotEditable =
isContactNonEditable || isAddressNonEditable || hasNonEditablePersonData;

return !contactIsNotEditable;
return {
canDelete: !contactIsNotEditable,
uniqueSources,
};
}, [contactSources]);

return (
Expand All @@ -84,16 +147,80 @@ export const DeleteContactModal: React.FC<DeleteContactModalProps> = ({
handleClose={() => setOpen(false)}
>
<DialogContent dividers>
<DialogContentText>
{canDelete &&
t(
'Are you sure you want to permanently delete this contact? Doing so will permanently delete this contacts information, as well as task history. This cannot be undone. If you wish to keep this information, you can try hiding this contact instead.',
{dataInfo.canDelete ? (
<Typography>
{t(
`Are you sure you want to permanently delete this contact? Doing so will permanently delete this contacts information, as well as task history. This cannot be undone. If you wish to keep this information, you can try hiding this contact instead.`,
)}
{!canDelete &&
t(
"This contact cannot be deleted because part or all of the contact's data syncs with Donation Services. Please email Donation Services to request that this contact be deleted, or you can hide this contact instead.",
)}
</DialogContentText>
</Typography>
) : (
<>
<Typography>
{t(
`Be cautious when deleting this contact, as its data may sync with Donation Services or other third-party systems. Deleting the contact will not remove it from those systems; consider hiding the contact instead.`,
)}
</Typography>
<Typography fontWeight="bold">
{t(
`For contacts originating from Siebel or Donor Hub, email Donation Services to request deletion.`,
)}
</Typography>
<br />
<br />
<Typography variant="h6">{t('Data sources:')}</Typography>
<List dense={true}>
{!!dataInfo.uniqueSources.contact.length && (
<ListItem>
<ListItemIcon>
<Person />
</ListItemIcon>
<ListItemText
primary={`${t(
'Contact: ',
)} ${dataInfo.uniqueSources.contact.join(', ')}`}
/>
</ListItem>
)}

{!!dataInfo.uniqueSources.address.length && (
<ListItem>
<ListItemIcon>
<Place />
</ListItemIcon>
<ListItemText
primary={`${t(
'Address: ',
)} ${dataInfo.uniqueSources.address.join(', ')}`}
/>
</ListItem>
)}
{!!dataInfo.uniqueSources.email.length && (
<ListItem>
<ListItemIcon>
<Email />
</ListItemIcon>
<ListItemText
primary={`${t(
'Email: ',
)} ${dataInfo.uniqueSources.email.join(', ')}`}
/>
</ListItem>
)}
{!!dataInfo.uniqueSources.phone.length && (
<ListItem>
<ListItemIcon>
<Phone />
</ListItemIcon>
<ListItemText
primary={`${t(
'Phone: ',
)} ${dataInfo.uniqueSources.phone.join(', ')}`}
/>
</ListItem>
)}
</List>
</>
)}
</DialogContent>
<DialogActions>
<CancelButton
Expand All @@ -104,7 +231,7 @@ export const DeleteContactModal: React.FC<DeleteContactModalProps> = ({
<DeleteButton
size="large"
variant="contained"
disabled={deleting || !canDelete}
disabled={deleting}
onClick={deleteContact}
sx={{ marginRight: 0 }}
>
Expand Down

0 comments on commit 0164a01

Please sign in to comment.