diff --git a/src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql b/src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql
index e6fabd887c..c0eedcfce0 100644
--- a/src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql
+++ b/src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql
@@ -3,7 +3,7 @@ mutation EmailAddresses($input: PersonUpdateMutationInput!) {
person {
emailAddresses {
nodes {
- email
+ ...PersonEmailAddress
}
}
}
diff --git a/src/components/Tool/FixEmailAddresses/DeleteModal.test.tsx b/src/components/Tool/FixEmailAddresses/DeleteModal.test.tsx
deleted file mode 100644
index 9caf1caa82..0000000000
--- a/src/components/Tool/FixEmailAddresses/DeleteModal.test.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import React from 'react';
-import { ThemeProvider } from '@mui/material/styles';
-import TestWrapper from '__tests__/util/TestWrapper';
-import { render } from '__tests__/util/testingLibraryReactMock';
-import theme from '../../../theme';
-import DeleteModal from './DeleteModal';
-
-const testState = {
- open: true,
- personId: '',
- emailIndex: 0,
- emailAddress: 'test@test.com',
-};
-
-describe('FixEmailAddresses-DeleteModal', () => {
- it('default', () => {
- const handleClose = jest.fn();
- const handleDelete = jest.fn();
-
- const { getByText } = render(
-
-
-
-
- ,
- );
-
- expect(getByText('Confirm')).toBeInTheDocument();
- expect(
- getByText('Are you sure you wish to delete this email address:'),
- ).toBeInTheDocument();
- expect(getByText('"test@test.com"')).toBeInTheDocument();
- expect(getByText('Cancel')).toBeInTheDocument();
- expect(getByText('Delete')).toBeInTheDocument();
- });
-});
diff --git a/src/components/Tool/FixEmailAddresses/DeleteModal.tsx b/src/components/Tool/FixEmailAddresses/DeleteModal.tsx
deleted file mode 100644
index c07cf09779..0000000000
--- a/src/components/Tool/FixEmailAddresses/DeleteModal.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import React from 'react';
-import { mdiCloseThick } from '@mdi/js';
-import Icon from '@mdi/react';
-import {
- Box,
- Card,
- CardActions,
- CardContent,
- CardHeader,
- IconButton,
- Modal,
- Theme,
- Typography,
-} from '@mui/material';
-import { useTranslation } from 'react-i18next';
-import { makeStyles } from 'tss-react/mui';
-import {
- CancelButton,
- DeleteButton,
-} from 'src/components/common/Modal/ActionButtons/ActionButtons';
-import theme from '../../../theme';
-import { ModalState } from './FixEmailAddresses';
-
-const useStyles = makeStyles()((theme: Theme) => ({
- modal: {
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- overflow: 'auto',
- },
- blue: {
- color: 'white',
- backgroundColor: theme.palette.mpdxBlue.main,
- },
- iconButton: {
- position: 'absolute',
- top: -theme.spacing(2),
- right: -theme.spacing(2),
- '&:hover': {
- color: 'red',
- },
- },
- headerBox: {
- padding: 0,
- marginBottom: -theme.spacing(1),
- position: 'relative',
- },
-}));
-
-interface Props {
- modalState: ModalState;
- handleClose: () => void;
- handleDelete: () => void;
-}
-
-const DeleteModal: React.FC = ({
- modalState,
- handleClose,
- handleDelete,
-}) => {
- const { classes } = useStyles();
- const { t } = useTranslation();
- return (
-
-
-
-
- {t('Confirm')}
-
-
-
-
-
- }
- />
-
-
-
- {t('Are you sure you wish to delete this email address:')}
-
- {`"${modalState.emailAddress}"`}
-
-
-
-
-
- {/*TODO: make "newAddress" field in address false so it says "edit" instead of "add" */}
- {t('Delete')}
-
-
-
-
- );
-};
-
-export default DeleteModal;
diff --git a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
index 98636c499c..0ef12d7970 100644
--- a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
+++ b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
@@ -8,7 +8,7 @@ import { AddIcon } from 'src/components/Contacts/ContactDetails/ContactDetailsTa
import { useAccountListId } from 'src/hooks/useAccountListId';
import i18n from 'src/lib/i18n';
import { useEmailAddressesMutation } from './AddEmailAddress.generated';
-import { RowWrapper } from './FixEmailAddressPerson';
+import { RowWrapper } from './FixEmailAddressPerson/FixEmailAddressPerson';
import {
GetInvalidEmailAddressesDocument,
GetInvalidEmailAddressesQuery,
@@ -30,7 +30,6 @@ interface EmailValidationFormEmail {
}
interface EmailValidationFormProps {
- index: number;
personId: string;
}
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
deleted file mode 100644
index e76b9c00aa..0000000000
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
+++ /dev/null
@@ -1,208 +0,0 @@
-import React from 'react';
-import { ThemeProvider } from '@mui/material/styles';
-import userEvent from '@testing-library/user-event';
-import { ApolloErgonoMockMap } from 'graphql-ergonomock';
-import { DateTime } from 'luxon';
-import TestWrapper from '__tests__/util/TestWrapper';
-import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
-import { render, waitFor } from '__tests__/util/testingLibraryReactMock';
-import { PersonEmailAddressInput } from 'src/graphql/types.generated';
-import theme from '../../../theme';
-import { EmailAddressesMutation } from './AddEmailAddress.generated';
-import {
- FixEmailAddressPerson,
- FixEmailAddressPersonProps,
-} from './FixEmailAddressPerson';
-import { EmailAddressData, PersonEmailAddresses } from './FixEmailAddresses';
-import { GetInvalidEmailAddressesQuery } from './FixEmailAddresses.generated';
-import { mockInvalidEmailAddressesResponse } from './FixEmailAddressesMocks';
-
-const testData = {
- name: 'Test Contact',
- personId: 'testid',
- contactId: 'contactTestId',
- emailAddresses: [
- {
- source: 'DonorHub',
- updatedAt: DateTime.fromISO('2021-06-21').toString(),
- email: 'test1@test1.com',
- primary: true,
- isValid: false,
- personId: 'testid',
- } as EmailAddressData,
- {
- source: 'MPDX',
- updatedAt: DateTime.fromISO('2021-06-22').toString(),
- email: 'test2@test1.com',
- primary: false,
- isValid: false,
- personId: 'testid',
- } as EmailAddressData,
- ],
-} as FixEmailAddressPersonProps;
-
-const setContactFocus = jest.fn();
-const handleChangeMock = jest.fn();
-const handleDeleteModalOpenMock = jest.fn();
-const handleChangePrimaryMock = jest.fn();
-
-const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
- const toDelete = [] as PersonEmailAddressInput[];
- const dataState = {
- id: {
- emailAddresses: testData.emailAddresses as EmailAddressData[],
- toDelete,
- },
- } as { [key: string]: PersonEmailAddresses };
-
- return (
-
-
-
- mocks={mocks}
- >
-
-
-
-
- );
-};
-
-describe('FixEmailAddressPerson', () => {
- it('default', () => {
- const { getByText, getByTestId, getByDisplayValue } = render(
- ,
- );
-
- expect(getByText(testData.name)).toBeInTheDocument();
- expect(getByText('DonorHub (6/21/2021)')).toBeInTheDocument();
- expect(getByTestId('textfield-testid-0')).toBeInTheDocument();
- expect(getByDisplayValue('test1@test1.com')).toBeInTheDocument();
- expect(getByText('MPDX (6/22/2021)')).toBeInTheDocument();
- expect(getByTestId('textfield-testid-1')).toBeInTheDocument();
- expect(getByDisplayValue('test2@test1.com')).toBeInTheDocument();
- expect(getByTestId('starIcon-testid-0')).toBeInTheDocument();
- });
-
- it('input reset after adding an email address', async () => {
- const { getByTestId, getByLabelText } = render(
- ,
- );
-
- const addInput = getByLabelText('New Email Address');
- const addButton = getByTestId('addButton-testid');
-
- userEvent.type(addInput, 'new@new.com');
- await waitFor(() => {
- expect(addInput).toHaveValue('new@new.com');
- });
- userEvent.click(addButton);
- await waitFor(() => {
- expect(addInput).toHaveValue('');
- });
- });
-
- describe('validation', () => {
- it('should show an error message if there is no email', async () => {
- const { getByLabelText, getByTestId, getByText } = render(
- ,
- );
-
- const addInput = getByLabelText('New Email Address');
- userEvent.click(addInput);
- userEvent.tab();
-
- const addButton = getByTestId('addButton-testid');
- await waitFor(() => {
- expect(addButton).toBeDisabled();
- expect(getByText('Please enter a valid email address')).toBeVisible();
- });
- });
-
- it('should show an error message if there is an invalid email', async () => {
- const { getByLabelText, getByTestId, getByText } = render(
- ,
- );
-
- const addInput = getByLabelText('New Email Address');
- userEvent.type(addInput, 'ab');
- userEvent.tab();
-
- const addButton = getByTestId('addButton-testid');
- await waitFor(() => {
- expect(addButton).toBeDisabled();
- expect(getByText('Invalid Email Address Format')).toBeVisible();
- });
- });
-
- it('should not disable the add button', async () => {
- const { getByLabelText, getByTestId } = render(
- ,
- );
-
- const addInput = getByLabelText('New Email Address');
- userEvent.type(addInput, 'new@new.com');
- userEvent.tab();
-
- const addButton = getByTestId('addButton-testid');
- await waitFor(() => {
- expect(addButton).not.toBeDisabled();
- });
- });
- });
-});
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
deleted file mode 100644
index 7cb3b27ce7..0000000000
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
+++ /dev/null
@@ -1,305 +0,0 @@
-import React, { Fragment, useMemo } from 'react';
-import { mdiDelete, mdiLock, mdiStar, mdiStarOutline } from '@mdi/js';
-import { Icon } from '@mdi/react';
-import {
- Avatar,
- Box,
- Button,
- Grid,
- Hidden,
- Link,
- TextField,
- Theme,
- Typography,
-} from '@mui/material';
-import { styled } from '@mui/material/styles';
-import { DateTime } from 'luxon';
-import { useTranslation } from 'react-i18next';
-import { makeStyles } from 'tss-react/mui';
-import { SetContactFocus } from 'pages/accountLists/[accountListId]/tools/useToolsHelper';
-import { PersonEmailAddressInput } from 'src/graphql/types.generated';
-import { useLocale } from 'src/hooks/useLocale';
-import { dateFormatShort } from 'src/lib/intlFormat';
-import theme from '../../../theme';
-import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
-import EmailValidationForm from './EmailValidationForm';
-import { EmailAddressData, PersonEmailAddresses } from './FixEmailAddresses';
-
-const PersonCard = styled(Box)(({ theme }) => ({
- [theme.breakpoints.up('md')]: {
- border: `1px solid ${theme.palette.cruGrayMedium.main}`,
- },
-}));
-
-const Container = styled(Grid)(({ theme }) => ({
- display: 'flex',
- flexDirection: 'row',
- justifyContent: 'flex-end',
- [theme.breakpoints.down('sm')]: {
- border: `1px solid ${theme.palette.cruGrayMedium.main}`,
- },
-}));
-
-const EmailAddressListWrapper = styled(Grid)(({ theme }) => ({
- backgroundColor: theme.palette.cruGrayLight.main,
- width: '100%',
- [theme.breakpoints.down('xs')]: {
- paddingTop: theme.spacing(2),
- },
-}));
-
-const ConfirmButtonWrapper = styled(Box)(({ theme }) => ({
- marginLeft: theme.spacing(2),
- [theme.breakpoints.down('sm')]: {
- marginLeft: theme.spacing(1),
- marginRight: theme.spacing(2),
- marginTop: theme.spacing(2),
- marginBottom: theme.spacing(2),
- },
- '& .MuiButton-root': {
- backgroundColor: theme.palette.mpdxBlue.main,
- color: 'white',
- },
-}));
-
-const BoxWithResponsiveBorder = styled(Box)(({ theme }) => ({
- [theme.breakpoints.down('xs')]: {
- paddingBottom: theme.spacing(2),
- borderBottom: `1px solid ${theme.palette.cruGrayMedium.main}`,
- },
-}));
-
-const ColumnHeaderWrapper = styled(Grid)(({ theme }) => ({
- paddingTop: theme.spacing(2),
- paddingBottom: theme.spacing(2),
-}));
-
-export const RowWrapper = styled(Grid)(({ theme }) => ({
- paddingBottom: theme.spacing(2),
-}));
-
-const HoverableIcon = styled(Icon)(({ theme }) => ({
- '&:hover': {
- color: theme.palette.mpdxBlue.main,
- cursor: 'pointer',
- },
-}));
-
-const useStyles = makeStyles()((theme: Theme) => ({
- avatar: {
- width: theme.spacing(7),
- height: theme.spacing(7),
- },
-}));
-
-export interface FixEmailAddressPersonProps {
- name: string;
- emailAddresses?: EmailAddressData[];
- personId: string;
- dataState: { [key: string]: PersonEmailAddresses };
- toDelete: PersonEmailAddressInput[];
- contactId: string;
- handleChange: (
- personId: string,
- numberIndex: number,
- event: React.ChangeEvent,
- ) => void;
- handleDelete: (personId: string, emailAddress: number) => void;
- handleChangePrimary: (personId: string, emailIndex: number) => void;
- setContactFocus: SetContactFocus;
-}
-
-export const FixEmailAddressPerson: React.FC = ({
- name,
- emailAddresses,
- personId,
- dataState,
- contactId,
- handleChange,
- handleDelete,
- handleChangePrimary,
- setContactFocus,
-}) => {
- const { t } = useTranslation();
- const locale = useLocale();
- const { classes } = useStyles();
-
- const emails = useMemo(
- () =>
- emailAddresses?.map((email) => ({
- ...email,
- isValid: false,
- personId: personId,
- isPrimary: email.primary,
- })) || [],
- [emailAddresses, dataState],
- );
-
- const handleContactNameClick = () => {
- setContactFocus(contactId);
- };
-
- return (
-
-
-
-
-
-
-
-
-
-
- {name}
-
-
-
-
-
-
-
-
-
-
- {t('Source')}
-
-
- {t('Primary')}
-
-
-
-
-
-
- {t('Address')}
-
-
-
-
- {emails.map((email, index) => (
-
-
-
-
-
-
- {t('Source')}:
-
-
-
- {`${email.source} (${dateFormatShort(
- DateTime.fromISO(email.updatedAt),
- locale,
- )})`}
-
-
- {email.isPrimary ? (
-
-
-
- ) : (
-
- handleChangePrimary(personId, index)
- }
- >
-
-
- )}
-
-
-
-
- ,
- ) => handleChange(personId, index, event)}
- value={email.email}
- disabled={email.source !== 'MPDX'}
- />
-
- {email.source === 'MPDX' ? (
- handleDelete(personId, index)}
- >
-
-
- ) : (
-
- )}
-
-
-
- ))}
-
-
-
-
-
- {t('Source')}:
-
-
- MPDX
-
-
-
-
-
- {
- //TODO: index will need to be mapped to the correct personId
- }
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- );
-};
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
new file mode 100644
index 0000000000..74316430cf
--- /dev/null
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
@@ -0,0 +1,276 @@
+import React from 'react';
+import { ThemeProvider } from '@mui/material/styles';
+import userEvent from '@testing-library/user-event';
+import { ApolloErgonoMockMap } from 'graphql-ergonomock';
+import { DateTime } from 'luxon';
+import { SnackbarProvider } from 'notistack';
+import TestWrapper from '__tests__/util/TestWrapper';
+import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
+import {
+ render,
+ screen,
+ waitFor,
+} from '__tests__/util/testingLibraryReactMock';
+import theme from 'src/theme';
+import { EmailAddressesMutation } from '../AddEmailAddress.generated';
+import { EmailAddressData, PersonEmailAddresses } from '../FixEmailAddresses';
+import {
+ GetInvalidEmailAddressesQuery,
+ PersonInvalidEmailFragment,
+} from '../FixEmailAddresses.generated';
+import { mockInvalidEmailAddressesResponse } from '../FixEmailAddressesMocks';
+import { FixEmailAddressPerson } from './FixEmailAddressPerson';
+
+const accountListId = 'accountListId';
+const person: PersonInvalidEmailFragment = {
+ id: 'contactTestId',
+ firstName: 'Test',
+ lastName: 'Contact',
+ contactId: 'contactTestId',
+ emailAddresses: {
+ nodes: [
+ {
+ id: 'email1',
+ source: 'DonorHub',
+ updatedAt: DateTime.fromISO('2021-06-21').toString(),
+ email: 'test1@test1.com',
+ primary: true,
+ },
+ {
+ id: 'email2',
+ source: 'MPDX',
+ updatedAt: DateTime.fromISO('2021-06-22').toString(),
+ email: 'test2@test1.com',
+ primary: false,
+ },
+ ],
+ },
+};
+
+const setContactFocus = jest.fn();
+const mutationSpy = jest.fn();
+const mockEnqueue = jest.fn();
+
+jest.mock('notistack', () => ({
+ // eslint-disable-next-line @typescript-eslint/ban-ts-comment
+ // @ts-ignore
+ ...jest.requireActual('notistack'),
+ useSnackbar: () => {
+ return {
+ enqueueSnackbar: mockEnqueue,
+ };
+ },
+}));
+
+const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
+ const handleChangeMock = jest.fn();
+ const handleChangePrimaryMock = jest.fn();
+ const dataState = {
+ contactTestId: {
+ emailAddresses: person.emailAddresses.nodes as EmailAddressData[],
+ },
+ } as { [key: string]: PersonEmailAddresses };
+
+ return (
+
+
+
+
+ mocks={mocks}
+ onCall={mutationSpy}
+ >
+
+
+
+
+
+ );
+};
+
+describe('FixEmailAddressPerson', () => {
+ it('default', () => {
+ const { getByText, getByTestId, getByDisplayValue } = render(
+ ,
+ );
+
+ expect(
+ getByText(`${person.firstName} ${person.lastName}`),
+ ).toBeInTheDocument();
+ expect(getByText('DonorHub (6/21/2021)')).toBeInTheDocument();
+ expect(getByTestId('textfield-contactTestId-0')).toBeInTheDocument();
+ expect(getByDisplayValue('test1@test1.com')).toBeInTheDocument();
+ expect(getByText('MPDX (6/22/2021)')).toBeInTheDocument();
+ expect(getByTestId('textfield-contactTestId-1')).toBeInTheDocument();
+ expect(getByDisplayValue('test2@test1.com')).toBeInTheDocument();
+ });
+
+ it('input reset after adding an email address', async () => {
+ const { getByTestId, getByLabelText } = render(
+ ,
+ );
+
+ const addInput = getByLabelText('New Email Address');
+ const addButton = getByTestId('addButton-contactTestId');
+
+ userEvent.type(addInput, 'new@new.com');
+ await waitFor(() => {
+ expect(addInput).toHaveValue('new@new.com');
+ });
+ userEvent.click(addButton);
+ await waitFor(() => {
+ expect(addInput).toHaveValue('');
+ });
+ });
+
+ describe('validation', () => {
+ it('should show an error message if there is no email', async () => {
+ const { getByLabelText, getByTestId, getByText } = render(
+ ,
+ );
+
+ const addInput = getByLabelText('New Email Address');
+ userEvent.click(addInput);
+ userEvent.tab();
+
+ const addButton = getByTestId('addButton-contactTestId');
+ await waitFor(() => {
+ expect(addButton).toBeDisabled();
+ expect(getByText('Please enter a valid email address')).toBeVisible();
+ });
+ });
+
+ it('should show an error message if there is an invalid email', async () => {
+ const { getByLabelText, getByTestId, getByText } = render(
+ ,
+ );
+
+ const addInput = getByLabelText('New Email Address');
+ userEvent.type(addInput, 'ab');
+ userEvent.tab();
+
+ const addButton = getByTestId('addButton-contactTestId');
+ await waitFor(() => {
+ expect(addButton).toBeDisabled();
+ expect(getByText('Invalid Email Address Format')).toBeVisible();
+ });
+ });
+
+ it('should not disable the add button', async () => {
+ const { getByLabelText, getByTestId } = render(
+ ,
+ );
+
+ const addInput = getByLabelText('New Email Address');
+ userEvent.type(addInput, 'new@new.com');
+ userEvent.tab();
+
+ const addButton = getByTestId('addButton-contactTestId');
+ await waitFor(() => {
+ expect(addButton).not.toBeDisabled();
+ });
+ });
+
+ it('should show delete confirmation', async () => {
+ const { getByTestId, getByRole } = render(
+ ,
+ );
+ await waitFor(() => getByTestId('delete-contactTestId-1'));
+
+ userEvent.click(getByTestId('delete-contactTestId-1'));
+ screen.logTestingPlaygroundURL();
+ await waitFor(() => {
+ expect(getByRole('heading', { name: 'Confirm' })).toBeInTheDocument();
+ });
+ userEvent.click(getByRole('button', { name: 'Yes' }));
+
+ const { id, email } = person.emailAddresses.nodes[1];
+
+ await waitFor(() => {
+ expect(mutationSpy.mock.lastCall[0].operation.operationName).toEqual(
+ 'UpdateEmailAddresses',
+ );
+ expect(mutationSpy.mock.lastCall[0].operation.variables).toEqual({
+ input: {
+ accountListId,
+ attributes: {
+ id: person.id,
+ emailAddresses: [
+ {
+ id,
+ destroy: true,
+ },
+ ],
+ },
+ },
+ });
+ });
+
+ await waitFor(() =>
+ expect(mockEnqueue).toHaveBeenCalledWith(
+ `Successfully deleted email address ${email}`,
+ {
+ variant: 'success',
+ },
+ ),
+ );
+ });
+ });
+});
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
new file mode 100644
index 0000000000..e791e5efd8
--- /dev/null
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
@@ -0,0 +1,402 @@
+import React, { Fragment, useMemo } from 'react';
+import { mdiDelete, mdiLock, mdiStar, mdiStarOutline } from '@mdi/js';
+import { Icon } from '@mdi/react';
+import {
+ Avatar,
+ Box,
+ Button,
+ Grid,
+ Hidden,
+ Link,
+ TextField,
+ Theme,
+ Typography,
+} from '@mui/material';
+import { styled } from '@mui/material/styles';
+import { DateTime } from 'luxon';
+import { useSnackbar } from 'notistack';
+import { useTranslation } from 'react-i18next';
+import { makeStyles } from 'tss-react/mui';
+import { SetContactFocus } from 'pages/accountLists/[accountListId]/tools/useToolsHelper';
+import { useUpdateEmailAddressesMutation } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
+import { Confirmation } from 'src/components/common/Modal/Confirmation/Confirmation';
+import { useLocale } from 'src/hooks/useLocale';
+import { dateFormatShort } from 'src/lib/intlFormat';
+import theme from 'src/theme';
+import { ConfirmButtonIcon } from '../../ConfirmButtonIcon';
+import EmailValidationForm from '../EmailValidationForm';
+import { PersonEmailAddresses } from '../FixEmailAddresses';
+import { PersonInvalidEmailFragment } from '../FixEmailAddresses.generated';
+
+const PersonCard = styled(Box)(({ theme }) => ({
+ [theme.breakpoints.up('md')]: {
+ border: `1px solid ${theme.palette.cruGrayMedium.main}`,
+ },
+}));
+
+const Container = styled(Grid)(({ theme }) => ({
+ display: 'flex',
+ flexDirection: 'row',
+ justifyContent: 'flex-end',
+ [theme.breakpoints.down('sm')]: {
+ border: `1px solid ${theme.palette.cruGrayMedium.main}`,
+ },
+}));
+
+const EmailAddressListWrapper = styled(Grid)(({ theme }) => ({
+ backgroundColor: theme.palette.cruGrayLight.main,
+ width: '100%',
+ [theme.breakpoints.down('xs')]: {
+ paddingTop: theme.spacing(2),
+ },
+}));
+
+const ConfirmButtonWrapper = styled(Box)(({ theme }) => ({
+ marginLeft: theme.spacing(2),
+ [theme.breakpoints.down('sm')]: {
+ marginLeft: theme.spacing(1),
+ marginRight: theme.spacing(2),
+ marginTop: theme.spacing(2),
+ marginBottom: theme.spacing(2),
+ },
+ '& .MuiButton-root': {
+ backgroundColor: theme.palette.mpdxBlue.main,
+ color: 'white',
+ },
+}));
+
+const BoxWithResponsiveBorder = styled(Box)(({ theme }) => ({
+ [theme.breakpoints.down('xs')]: {
+ paddingBottom: theme.spacing(2),
+ borderBottom: `1px solid ${theme.palette.cruGrayMedium.main}`,
+ },
+}));
+
+const ColumnHeaderWrapper = styled(Grid)(({ theme }) => ({
+ paddingTop: theme.spacing(2),
+ paddingBottom: theme.spacing(2),
+}));
+
+export const RowWrapper = styled(Grid)(({ theme }) => ({
+ paddingBottom: theme.spacing(2),
+}));
+
+const HoverableIcon = styled(Icon)(({ theme }) => ({
+ '&:hover': {
+ color: theme.palette.mpdxBlue.main,
+ cursor: 'pointer',
+ },
+}));
+
+const useStyles = makeStyles()((theme: Theme) => ({
+ avatar: {
+ width: theme.spacing(7),
+ height: theme.spacing(7),
+ },
+}));
+
+export interface FixEmailAddressPersonProps {
+ person: PersonInvalidEmailFragment;
+ dataState: { [key: string]: PersonEmailAddresses };
+ accountListId: string;
+ handleChange: (
+ personId: string,
+ numberIndex: number,
+ event: React.ChangeEvent,
+ ) => void;
+ handleChangePrimary: (personId: string, emailIndex: number) => void;
+ setContactFocus: SetContactFocus;
+}
+
+interface Email {
+ isValid: boolean;
+ personId: string;
+ isPrimary: boolean;
+ id: string;
+ primary: boolean;
+ updatedAt: string;
+ source: string;
+ email: string;
+ destroy?: boolean | undefined;
+}
+
+interface EmailToDelete {
+ id: string;
+ email: Email;
+}
+
+export const FixEmailAddressPerson: React.FC = ({
+ person,
+ dataState,
+ accountListId,
+ handleChange,
+ handleChangePrimary,
+ setContactFocus,
+}) => {
+ const { t } = useTranslation();
+ const locale = useLocale();
+ const { classes } = useStyles();
+ const { enqueueSnackbar } = useSnackbar();
+ const [updateEmailAddressesMutation] = useUpdateEmailAddressesMutation();
+ const [deleteModalOpen, setDeleteModalOpen] = React.useState(false);
+ const [emailToDelete, setEmailToDelete] =
+ React.useState(null);
+
+ const { id, contactId } = person;
+ const name = `${person.firstName} ${person.lastName}`;
+
+ const emails: Email[] = useMemo(() => {
+ if (!dataState[id]?.emailAddresses.length) {
+ return [];
+ }
+
+ return (
+ dataState[id]?.emailAddresses.map((email) => ({
+ ...email,
+ isValid: false,
+ personId: id,
+ isPrimary: email.primary,
+ })) || []
+ );
+ }, [person, dataState]);
+
+ const handleDelete = async (): Promise => {
+ if (!emailToDelete) {
+ return;
+ }
+ const { id: personId, email } = emailToDelete;
+ await updateEmailAddressesMutation({
+ variables: {
+ input: {
+ accountListId,
+ attributes: {
+ id: personId,
+ emailAddresses: [
+ {
+ id: email.id,
+ destroy: true,
+ },
+ ],
+ },
+ },
+ },
+ update: (cache) => {
+ cache.evict({ id: `EmailAddress:${email.id}` });
+ cache.gc();
+ },
+ onCompleted: () => {
+ enqueueSnackbar(
+ t(`Successfully deleted email address ${email.email}`),
+ {
+ variant: 'success',
+ },
+ );
+ handleDeleteEmailModalClose();
+ },
+ onError: () => {
+ enqueueSnackbar(t(`Error deleting email address ${email.email}`), {
+ variant: 'error',
+ });
+ },
+ });
+ };
+
+ const handleContactNameClick = () => {
+ setContactFocus(contactId);
+ };
+
+ const handleDeleteEmailOpen = ({ id, email }: EmailToDelete) => {
+ setDeleteModalOpen(true);
+ setEmailToDelete({ id, email });
+ };
+ const handleDeleteEmailModalClose = (): void => {
+ setDeleteModalOpen(false);
+ setEmailToDelete(null);
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ {name}
+
+
+
+
+
+
+
+
+
+
+ {t('Source')}
+
+
+ {t('Primary')}
+
+
+
+
+
+
+ {t('Address')}
+
+
+
+
+ {emails.map((email, index) => (
+
+
+
+
+
+
+ {t('Source')}:
+
+
+
+ {`${email.source} (${dateFormatShort(
+ DateTime.fromISO(email.updatedAt),
+ locale,
+ )})`}
+
+
+ {email.isPrimary ? (
+
+
+
+ ) : (
+ handleChangePrimary(id, index)}
+ >
+
+
+ )}
+
+
+
+
+ ,
+ ) => handleChange(id, index, event)}
+ value={email.email}
+ disabled={email.source !== 'MPDX'}
+ />
+
+ {email.source === 'MPDX' ? (
+
+ handleDeleteEmailOpen({ id, email })
+ }
+ >
+
+
+ ) : (
+
+ )}
+
+
+
+ ))}
+
+
+
+
+
+ {t('Source')}:
+
+
+ MPDX
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {deleteModalOpen && emailToDelete && (
+
+ {t('Are you sure you wish to delete this email address:')}{' '}
+ {emailToDelete?.email.email}
+
+ }
+ mutation={handleDelete}
+ handleClose={handleDeleteEmailModalClose}
+ />
+ )}
+ >
+ );
+};
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
index f4534a40f1..4282ab56ef 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
@@ -30,3 +30,15 @@ fragment PersonEmailAddress on EmailAddress {
updatedAt
source
}
+
+mutation UpdateEmailAddresses($input: PersonUpdateMutationInput!) {
+ updatePerson(input: $input) {
+ person {
+ emailAddresses {
+ nodes {
+ ...PersonEmailAddress
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 33668ed05e..c1d08e0e36 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -1,9 +1,8 @@
import React from 'react';
-import { ApolloCache, InMemoryCache } from '@apollo/client';
import { ThemeProvider } from '@mui/material/styles';
-import { render, waitFor } from '@testing-library/react';
+import { render, screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
-import { ApolloErgonoMockMap, ErgonoMockShape } from 'graphql-ergonomock';
+import { ApolloErgonoMockMap } from 'graphql-ergonomock';
import { SnackbarProvider } from 'notistack';
import TestRouter from '__tests__/util/TestRouter';
import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
@@ -13,10 +12,6 @@ import { EmailAddressesMutation } from './AddEmailAddress.generated';
import { FixEmailAddresses } from './FixEmailAddresses';
import {
contactId,
- contactOneEmailAddressNodes,
- contactTwoEmailAddressNodes,
- mockCacheWriteData,
- mockCacheWriteDataContactTwo,
mockInvalidEmailAddressesResponse,
newEmail,
} from './FixEmailAddressesMocks';
@@ -50,10 +45,9 @@ const defaultGraphQLMock = {
interface ComponentsProps {
mocks?: ApolloErgonoMockMap;
- cache?: ApolloCache