From c0148a536764a41e4063f8e5a06690748953815e Mon Sep 17 00:00:00 2001
From: Collin Pastika <collin.pastika@cru.org>
Date: Mon, 8 Jul 2024 16:23:57 -0400
Subject: [PATCH 01/22] added graphQL mutation

squash
---
 .../Tool/FixEmailAddresses/AddEmailAddress.graphql    | 11 +++++++++++
 1 file changed, 11 insertions(+)
 create mode 100644 src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql

diff --git a/src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql b/src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql
new file mode 100644
index 000000000..e6fabd887
--- /dev/null
+++ b/src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql
@@ -0,0 +1,11 @@
+mutation EmailAddresses($input: PersonUpdateMutationInput!) {
+  updatePerson(input: $input) {
+    person {
+      emailAddresses {
+        nodes {
+          email
+        }
+      }
+    }
+  }
+}

From 73a395cb881f2409967dcc84ac80fc2a5b786fcc Mon Sep 17 00:00:00 2001
From: Collin Pastika <collin.pastika@cru.org>
Date: Mon, 17 Jun 2024 14:33:20 -0400
Subject: [PATCH 02/22] fixed add email functionality

---
 .../FixEmailAddresses/EmailValidationForm.tsx |  74 ++++++-
 .../FixEmailAddressPerson.test.tsx            |  94 +++++++--
 .../FixEmailAddressPerson.tsx                 |   8 +-
 ...sses.graphql => FixEmailAddresses.graphql} |   0
 .../FixEmailAddresses.test.tsx                | 188 +++++++++++++++---
 .../FixEmailAddresses/FixEmailAddresses.tsx   |  17 +-
 .../FixEmailAddressesMocks.ts                 | 158 +++++++++++----
 7 files changed, 422 insertions(+), 117 deletions(-)
 rename src/components/Tool/FixEmailAddresses/{GetInvalidEmailAddresses.graphql => FixEmailAddresses.graphql} (100%)

diff --git a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
index 9c10ce480..2487ee12a 100644
--- a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
+++ b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
@@ -4,7 +4,13 @@ import { Form, Formik } from 'formik';
 import { useTranslation } from 'react-i18next';
 import * as Yup from 'yup';
 import { AddIcon } from 'src/components/Contacts/ContactDetails/ContactDetailsTab/StyledComponents';
+import { useAccountListId } from 'src/hooks/useAccountListId';
+import { useEmailAddressesMutation } from './AddEmailAddress.generated';
 import { RowWrapper } from './FixEmailAddressPerson';
+import {
+  GetInvalidEmailAddressesDocument,
+  GetInvalidEmailAddressesQuery,
+} from './FixEmailAddresses.generated';
 
 const ContactInputField = styled(TextField, {
   shouldForwardProp: (prop) => prop !== 'destroyed',
@@ -24,16 +30,12 @@ interface EmailValidationFormEmail {
 interface EmailValidationFormProps {
   index: number;
   personId: string;
-  handleAdd?: (personId: string, email: string) => void;
 }
 
-//TODO: Implement during MPDX-7946
-const onSubmit = (values, actions) => {
-  actions.resetForm();
-};
-
 const EmailValidationForm = ({ personId }: EmailValidationFormProps) => {
   const { t } = useTranslation();
+  const accountListId = useAccountListId();
+  const [emailAddressesMutation] = useEmailAddressesMutation();
 
   const initialEmail = {
     email: '',
@@ -55,6 +57,66 @@ const EmailValidationForm = ({ personId }: EmailValidationFormProps) => {
     isValid: Yup.bool().default(false),
   });
 
+  const onSubmit = (values, actions) => {
+    emailAddressesMutation({
+      variables: {
+        input: {
+          attributes: {
+            id: personId,
+            emailAddresses: [
+              {
+                email: values.email,
+              },
+            ],
+          },
+          accountListId: accountListId || '',
+        },
+      },
+      update: (cache, { data: addEmailAddressData }) => {
+        actions.resetForm();
+        const query = {
+          query: GetInvalidEmailAddressesDocument,
+          variables: {
+            accountListId: accountListId,
+          },
+        };
+        const dataFromCache =
+          cache.readQuery<GetInvalidEmailAddressesQuery>(query);
+        if (dataFromCache) {
+          const peopleWithNewEmail = dataFromCache.people.nodes.map(
+            (person) => {
+              if (
+                person.id === personId &&
+                addEmailAddressData?.updatePerson?.person.emailAddresses.nodes
+              ) {
+                return {
+                  ...person,
+                  emailAddresses: {
+                    nodes:
+                      addEmailAddressData?.updatePerson?.person.emailAddresses
+                        .nodes,
+                  },
+                };
+              } else {
+                return person;
+              }
+            },
+          );
+
+          cache.writeQuery({
+            ...query,
+            data: {
+              people: {
+                ...dataFromCache.people,
+                nodes: peopleWithNewEmail,
+              },
+            },
+          });
+        }
+      },
+    });
+  };
+
   return (
     <Formik
       initialValues={initialEmail}
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
index 595b0ad5b..4ba32b081 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
@@ -1,15 +1,20 @@
 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 theme from '../../../theme';
+import { EmailAddressesMutation } from './AddEmailAddress.generated';
 import {
   FixEmailAddressPerson,
   FixEmailAddressPersonProps,
 } from './FixEmailAddressPerson';
 import { EmailAddressData } from './FixEmailAddresses';
+import { GetInvalidEmailAddressesQuery } from './FixEmailAddresses.generated';
+import { mockInvalidEmailAddressesResponse } from './FixEmailAddressesMocks';
 
 const testData = {
   name: 'Test Contact',
@@ -39,27 +44,32 @@ const testData = {
 
 const setContactFocus = jest.fn();
 
-const TestComponent: React.FC = () => {
+const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
   const handleChangeMock = jest.fn();
   const handleDeleteModalOpenMock = jest.fn();
-  const handleAddMock = jest.fn();
   const handleChangePrimaryMock = jest.fn();
   return (
     <ThemeProvider theme={theme}>
       <TestWrapper>
-        <FixEmailAddressPerson
-          toDelete={[]}
-          name={testData.name}
-          key={testData.name}
-          personId={testData.personId}
-          contactId={testData.contactId}
-          emailAddresses={testData.emailAddresses}
-          handleChange={handleChangeMock}
-          handleDelete={handleDeleteModalOpenMock}
-          handleAdd={handleAddMock}
-          handleChangePrimary={handleChangePrimaryMock}
-          setContactFocus={setContactFocus}
-        />
+        <GqlMockedProvider<{
+          GetInvalidEmailAddresses: GetInvalidEmailAddressesQuery;
+          EmailAddresses: EmailAddressesMutation;
+        }>
+          mocks={mocks}
+        >
+          <FixEmailAddressPerson
+            toDelete={[]}
+            name={testData.name}
+            key={testData.name}
+            personId={testData.personId}
+            contactId={testData.contactId}
+            emailAddresses={testData.emailAddresses}
+            handleChange={handleChangeMock}
+            handleDelete={handleDeleteModalOpenMock}
+            handleChangePrimary={handleChangePrimaryMock}
+            setContactFocus={setContactFocus}
+          />
+        </GqlMockedProvider>
       </TestWrapper>
     </ThemeProvider>
   );
@@ -68,7 +78,15 @@ const TestComponent: React.FC = () => {
 describe('FixEmailAddressPerson', () => {
   it('default', () => {
     const { getByText, getByTestId, getByDisplayValue } = render(
-      <TestComponent />,
+      <TestComponent
+        mocks={{
+          GetInvalidEmailAddresses: {
+            people: {
+              nodes: mockInvalidEmailAddressesResponse,
+            },
+          },
+        }}
+      />,
     );
 
     expect(getByText(testData.name)).toBeInTheDocument();
@@ -81,7 +99,17 @@ describe('FixEmailAddressPerson', () => {
   });
 
   it('input reset after adding an email address', async () => {
-    const { getByTestId, getByLabelText } = render(<TestComponent />);
+    const { getByTestId, getByLabelText } = render(
+      <TestComponent
+        mocks={{
+          GetInvalidEmailAddresses: {
+            people: {
+              nodes: mockInvalidEmailAddressesResponse,
+            },
+          },
+        }}
+      />,
+    );
 
     const addInput = getByLabelText('New Email Address');
     const addButton = getByTestId('addButton-testid');
@@ -99,7 +127,15 @@ describe('FixEmailAddressPerson', () => {
   describe('validation', () => {
     it('should show an error message if there is no email', async () => {
       const { getByLabelText, getByTestId, getByText } = render(
-        <TestComponent />,
+        <TestComponent
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: mockInvalidEmailAddressesResponse,
+              },
+            },
+          }}
+        />,
       );
 
       const addInput = getByLabelText('New Email Address');
@@ -115,7 +151,15 @@ describe('FixEmailAddressPerson', () => {
 
     it('should show an error message if there is an invalid email', async () => {
       const { getByLabelText, getByTestId, getByText } = render(
-        <TestComponent />,
+        <TestComponent
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: mockInvalidEmailAddressesResponse,
+              },
+            },
+          }}
+        />,
       );
 
       const addInput = getByLabelText('New Email Address');
@@ -130,7 +174,17 @@ describe('FixEmailAddressPerson', () => {
     });
 
     it('should not disable the add button', async () => {
-      const { getByLabelText, getByTestId } = render(<TestComponent />);
+      const { getByLabelText, getByTestId } = render(
+        <TestComponent
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: mockInvalidEmailAddressesResponse,
+              },
+            },
+          }}
+        />,
+      );
 
       const addInput = getByLabelText('New Email Address');
       userEvent.type(addInput, 'new@new.com');
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
index 6fbc00973..b2795932c 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
@@ -104,7 +104,6 @@ export interface FixEmailAddressPersonProps {
     event: React.ChangeEvent<HTMLInputElement>,
   ) => void;
   handleDelete: (personId: string, emailAddress: number) => void;
-  handleAdd: (personId: string, email: string) => void;
   handleChangePrimary: (personId: string, emailIndex: number) => void;
   setContactFocus: SetContactFocus;
 }
@@ -118,7 +117,6 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
   handleDelete,
   handleChangePrimary,
   setContactFocus,
-  handleAdd,
 }) => {
   const { t } = useTranslation();
   const locale = useLocale();
@@ -277,11 +275,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                       {
                         //TODO: index will need to be mapped to the correct personId
                       }
-                      <EmailValidationForm
-                        handleAdd={handleAdd}
-                        index={0}
-                        personId={personId}
-                      />
+                      <EmailValidationForm index={0} personId={personId} />
                     </BoxWithResponsiveBorder>
                   </RowWrapper>
                 </Grid>
diff --git a/src/components/Tool/FixEmailAddresses/GetInvalidEmailAddresses.graphql b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
similarity index 100%
rename from src/components/Tool/FixEmailAddresses/GetInvalidEmailAddresses.graphql
rename to src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 645e76eab..03b0903af 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -1,18 +1,25 @@
 import React from 'react';
+import { ApolloCache, InMemoryCache } from '@apollo/client';
 import { ThemeProvider } from '@mui/material/styles';
 import { render, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
-import { ErgonoMockShape } from 'graphql-ergonomock';
+import { ApolloErgonoMockMap, ErgonoMockShape } from 'graphql-ergonomock';
 import TestRouter from '__tests__/util/TestRouter';
 import TestWrapper from '__tests__/util/TestWrapper';
 import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
+import { GetInvalidEmailAddressesQuery } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
 import theme from '../../../theme';
+import { EmailAddressesMutation } from './AddEmailAddress.generated';
 import { FixEmailAddresses } from './FixEmailAddresses';
 import {
   contactId,
+  contactOneEmailAddressNodes,
+  contactTwoEmailAddressNodes,
+  mockCacheWriteData,
+  mockCacheWriteDataContactTwo,
   mockInvalidEmailAddressesResponse,
+  newEmail,
 } from './FixEmailAddressesMocks';
-import { GetInvalidEmailAddressesQuery } from './GetInvalidEmailAddresses.generated';
 
 const accountListId = 'test121';
 const router = {
@@ -23,23 +30,25 @@ const router = {
 const setContactFocus = jest.fn();
 
 const Components = ({
-  mockNodes = mockInvalidEmailAddressesResponse,
+  mocks = {
+    GetInvalidEmailAddresses: {
+      people: { nodes: mockInvalidEmailAddressesResponse },
+    },
+  },
+  cache,
 }: {
-  mockNodes?: ErgonoMockShape[];
+  mocks?: ApolloErgonoMockMap;
+  cache?: ApolloCache<object>;
 }) => (
   <ThemeProvider theme={theme}>
     <TestRouter router={router}>
       <TestWrapper>
         <GqlMockedProvider<{
           GetInvalidEmailAddresses: GetInvalidEmailAddressesQuery;
+          EmailAddresses: EmailAddressesMutation;
         }>
-          mocks={{
-            GetInvalidEmailAddresses: {
-              people: {
-                nodes: mockNodes,
-              },
-            },
-          }}
+          mocks={mocks}
+          cache={cache}
         >
           <FixEmailAddresses
             accountListId={accountListId}
@@ -115,28 +124,147 @@ describe('FixPhoneNumbers-Home', () => {
     expect(getByTestId('starIcon-testid2-0')).toBeInTheDocument();
   });
 
-  //TODO: Fix during MPDX-7946
-  it.skip('add an email address to second person', async () => {
-    const { getByTestId, getByDisplayValue } = render(<Components />);
-    await waitFor(() =>
-      expect(getByTestId('starIcon-testid2-0')).toBeInTheDocument(),
-    );
-    expect(getByTestId('textfield-testid2-0')).toBeInTheDocument();
-
-    const textfieldNew1 = getByTestId(
-      'addNewEmailInput-testid2',
-    ) as HTMLInputElement;
-    userEvent.type(textfieldNew1, 'email12345@gmail.com');
-    const addButton1 = getByTestId('addButton-testid2');
-    userEvent.click(addButton1);
-
-    expect(textfieldNew1.value).toBe('');
-    expect(getByTestId('textfield-testid2-2')).toBeInTheDocument();
-    expect(getByDisplayValue('email12345@gmail.com')).toBeInTheDocument();
+  describe('add email address', () => {
+    interface AddEmailAddressProps {
+      postSaveResponse: object;
+      emailAddressNodes: object[];
+      elementToWaitFor: string;
+      textFieldIndex: number;
+      addButtonId: string;
+      cache: InMemoryCache;
+    }
+
+    const addEmailAddress = async ({
+      postSaveResponse,
+      emailAddressNodes,
+      elementToWaitFor,
+      textFieldIndex,
+      addButtonId,
+      cache,
+    }: AddEmailAddressProps) => {
+      let cardinality = 0;
+      jest.spyOn(cache, 'readQuery').mockReturnValue(postSaveResponse);
+      jest.spyOn(cache, 'writeQuery');
+
+      const updatePerson = {
+        person: {
+          emailAddresses: {
+            nodes: [
+              ...emailAddressNodes,
+              {
+                email: newEmail.email,
+              },
+            ],
+          },
+        },
+      } as ErgonoMockShape;
+
+      const { getByTestId, getAllByLabelText } = render(
+        <Components
+          mocks={{
+            GetInvalidEmailAddresses: () => {
+              let queryResult;
+              if (cardinality === 0) {
+                queryResult = {
+                  people: {
+                    nodes: mockInvalidEmailAddressesResponse,
+                  },
+                };
+              } else {
+                queryResult = postSaveResponse;
+              }
+              cardinality++;
+              return queryResult;
+            },
+            EmailAddresses: { updatePerson },
+          }}
+          cache={cache}
+        />,
+      );
+      await waitFor(() => {
+        expect(getByTestId(elementToWaitFor)).toBeInTheDocument();
+      });
+
+      const textFieldNew =
+        getAllByLabelText('New Email Address')[textFieldIndex];
+      userEvent.type(textFieldNew, newEmail.email);
+      const addButton = getByTestId(addButtonId);
+      userEvent.click(addButton);
+    };
+
+    it('should add an email address to the first person', async () => {
+      const cache = new InMemoryCache();
+      const postSaveResponse = {
+        people: {
+          nodes: [
+            {
+              ...mockInvalidEmailAddressesResponse[0],
+              emailAddresses: {
+                nodes: [...contactOneEmailAddressNodes, newEmail],
+              },
+            },
+            { ...mockInvalidEmailAddressesResponse[1] },
+          ],
+        },
+      };
+      await addEmailAddress({
+        postSaveResponse,
+        emailAddressNodes: contactOneEmailAddressNodes,
+        elementToWaitFor: 'textfield-testid-0',
+        textFieldIndex: 0,
+        addButtonId: 'addButton-testid',
+        cache,
+      });
+
+      await waitFor(() => {
+        expect(cache.writeQuery).toHaveBeenLastCalledWith(
+          expect.objectContaining({ data: mockCacheWriteData }),
+        );
+      });
+    });
+
+    it('should add an email address to the second person', async () => {
+      const cache = new InMemoryCache();
+      const postSaveResponse = {
+        people: {
+          nodes: [
+            { ...mockInvalidEmailAddressesResponse[0] },
+            {
+              ...mockInvalidEmailAddressesResponse[1],
+              emailAddresses: {
+                nodes: [...contactTwoEmailAddressNodes, newEmail],
+              },
+            },
+          ],
+        },
+      };
+      await addEmailAddress({
+        postSaveResponse,
+        emailAddressNodes: contactTwoEmailAddressNodes,
+        elementToWaitFor: 'textfield-testid2-0',
+        textFieldIndex: 1,
+        addButtonId: 'addButton-testid2',
+        cache,
+      });
+
+      await waitFor(() => {
+        expect(cache.writeQuery).toHaveBeenLastCalledWith(
+          expect.objectContaining({ data: mockCacheWriteDataContactTwo }),
+        );
+      });
+    });
   });
 
   it('should render no contacts with no data', async () => {
-    const { getByText, getByTestId } = render(<Components mockNodes={[]} />);
+    const { getByText, getByTestId } = render(
+      <Components
+        mocks={{
+          GetInvalidEmailAddresses: {
+            people: { nodes: [] },
+          },
+        }}
+      />,
+    );
     await waitFor(() =>
       expect(getByTestId('fixEmailAddresses-null-state')).toBeInTheDocument(),
     );
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index c89c99262..00fb2288f 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -11,6 +11,7 @@ import {
 import { styled } from '@mui/material/styles';
 import { Trans, useTranslation } from 'react-i18next';
 import { SetContactFocus } from 'pages/accountLists/[accountListId]/tools/useToolsHelper';
+import { useGetInvalidEmailAddressesQuery } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
 import { PersonEmailAddressInput } from 'src/graphql/types.generated';
 import theme from '../../../theme';
 import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
@@ -18,7 +19,6 @@ import NoData from '../NoData';
 import { StyledInput } from '../StyledInput';
 import DeleteModal from './DeleteModal';
 import { FixEmailAddressPerson } from './FixEmailAddressPerson';
-import { useGetInvalidEmailAddressesQuery } from './GetInvalidEmailAddresses.generated';
 
 const Container = styled(Box)(() => ({
   padding: theme.spacing(3),
@@ -206,25 +206,13 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
     handleDeleteModalClose();
   };
 
-  // Add a new email address to the state
-  const handleAdd = (personId: string, email: string): void => {
-    const temp = { ...dataState };
-    temp[personId].emailAddresses.push({
-      updatedAt: new Date().toISOString(),
-      email: email,
-      primary: false,
-      source: 'MPDX',
-    });
-    setDataState(temp);
-  };
-
   // Change the primary address in the state
   const handleChangePrimary = (personId: string, emailIndex: number): void => {
     const temp = { ...dataState };
     temp[personId].emailAddresses = temp[personId].emailAddresses.map(
       (email, index) => ({
         ...email,
-        primary: index === emailIndex ? true : false,
+        primary: index === emailIndex,
       }),
     );
     setDataState(temp);
@@ -296,7 +284,6 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
                     contactId={person.contactId}
                     handleChange={handleChange}
                     handleDelete={handleDeleteModalOpen}
-                    handleAdd={handleAdd}
                     handleChangePrimary={handleChangePrimary}
                     setContactFocus={setContactFocus}
                   />
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts b/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts
index 744daf715..040722f63 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts
@@ -2,6 +2,61 @@ import { ErgonoMockShape } from 'graphql-ergonomock';
 
 export const contactId = 'contactId';
 
+export const newEmail = {
+  __typename: 'EmailAddress',
+  id: 'id6',
+  updatedAt: new Date('2021-06-22T03:40:05-06:00').toISOString(),
+  email: 'email12345@gmail.com',
+  primary: false,
+  source: 'MPDX',
+};
+
+export const contactOneEmailAddressNodes = [
+  {
+    __typename: 'EmailAddress',
+    id: 'id1',
+    updatedAt: new Date('2021-06-21T03:40:05-06:00').toISOString(),
+    email: 'email1@gmail.com',
+    primary: true,
+    source: 'MPDX',
+  },
+  {
+    __typename: 'EmailAddress',
+    id: 'id12',
+    updatedAt: new Date('2021-06-21T03:40:05-06:00').toISOString(),
+    email: 'email2@gmail.com',
+    primary: false,
+    source: 'MPDX',
+  },
+  {
+    __typename: 'EmailAddress',
+    id: 'id3',
+    updatedAt: new Date('2021-06-21T03:40:05-06:00').toISOString(),
+    email: 'email3@gmail.com',
+    primary: false,
+    source: 'MPDX',
+  },
+];
+
+export const contactTwoEmailAddressNodes = [
+  {
+    __typename: 'EmailAddress',
+    id: 'id4',
+    updatedAt: new Date('2021-06-21T03:40:05-06:00').toISOString(),
+    email: 'email4@gmail.com',
+    primary: true,
+    source: 'MPDX',
+  },
+  {
+    __typename: 'EmailAddress',
+    id: 'id5',
+    updatedAt: new Date('2021-06-22T03:40:05-06:00').toISOString(),
+    email: 'email5@gmail.com',
+    primary: false,
+    source: 'MPDX',
+  },
+];
+
 export const mockInvalidEmailAddressesResponse: ErgonoMockShape[] = [
   {
     id: 'testid',
@@ -9,29 +64,7 @@ export const mockInvalidEmailAddressesResponse: ErgonoMockShape[] = [
     lastName: 'Contact',
     contactId,
     emailAddresses: {
-      nodes: [
-        {
-          id: 'id1',
-          updatedAt: new Date('2021-06-21T03:40:05-06:00').toISOString(),
-          email: 'email1@gmail.com',
-          primary: true,
-          source: 'MPDX',
-        },
-        {
-          id: 'id12',
-          updatedAt: new Date('2021-06-21T03:40:05-06:00').toISOString(),
-          email: 'email2@gmail.com',
-          primary: false,
-          source: 'MPDX',
-        },
-        {
-          id: 'id3',
-          updatedAt: new Date('2021-06-21T03:40:05-06:00').toISOString(),
-          email: 'email3@gmail.com',
-          primary: false,
-          source: 'MPDX',
-        },
-      ],
+      nodes: contactOneEmailAddressNodes,
     },
   },
   {
@@ -40,22 +73,69 @@ export const mockInvalidEmailAddressesResponse: ErgonoMockShape[] = [
     lastName: 'Lion',
     contactId: 'contactId2',
     emailAddresses: {
-      nodes: [
-        {
-          id: 'id4',
-          updatedAt: new Date('2021-06-21T03:40:05-06:00').toISOString(),
-          number: 'email4@gmail.com',
-          primary: true,
-          source: 'MPDX',
-        },
-        {
-          id: 'id5',
-          updatedAt: new Date('2021-06-22T03:40:05-06:00').toISOString(),
-          number: 'email5@gmail.com',
-          primary: false,
-          source: 'MPDX',
-        },
-      ],
+      nodes: contactTwoEmailAddressNodes,
     },
   },
 ];
+
+export const mockCacheWriteData = {
+  people: {
+    nodes: [
+      {
+        ...mockInvalidEmailAddressesResponse[0],
+        emailAddresses: {
+          nodes: [
+            {
+              __typename: contactOneEmailAddressNodes[0].__typename,
+              email: contactOneEmailAddressNodes[0].email,
+            },
+            {
+              __typename: contactOneEmailAddressNodes[1].__typename,
+              email: contactOneEmailAddressNodes[1].email,
+            },
+            {
+              __typename: contactOneEmailAddressNodes[2].__typename,
+              email: contactOneEmailAddressNodes[2].email,
+            },
+            {
+              __typename: newEmail.__typename,
+              email: newEmail.email,
+            },
+          ],
+        },
+      },
+      {
+        ...mockInvalidEmailAddressesResponse[1],
+      },
+    ],
+  },
+};
+
+export const mockCacheWriteDataContactTwo = {
+  people: {
+    nodes: [
+      {
+        ...mockInvalidEmailAddressesResponse[0],
+      },
+      {
+        ...mockInvalidEmailAddressesResponse[1],
+        emailAddresses: {
+          nodes: [
+            {
+              __typename: contactTwoEmailAddressNodes[0].__typename,
+              email: contactTwoEmailAddressNodes[0].email,
+            },
+            {
+              __typename: contactTwoEmailAddressNodes[1].__typename,
+              email: contactTwoEmailAddressNodes[1].email,
+            },
+            {
+              __typename: newEmail.__typename,
+              email: newEmail.email,
+            },
+          ],
+        },
+      },
+    ],
+  },
+};

From 17b3df4b662f9979088c434c72da24ba64456294 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Fri, 19 Jul 2024 10:52:08 -0400
Subject: [PATCH 03/22] Fix inline email address editing

---
 .../FixEmailAddressPerson.test.tsx                 | 14 ++++++++++++--
 .../FixEmailAddresses/FixEmailAddressPerson.tsx    |  6 ++++--
 .../FixEmailAddresses/FixEmailAddresses.test.tsx   |  9 ++++-----
 .../Tool/FixEmailAddresses/FixEmailAddresses.tsx   |  3 ++-
 4 files changed, 22 insertions(+), 10 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
index 4ba32b081..bf7068d4c 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
@@ -6,13 +6,14 @@ 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 } from './FixEmailAddresses';
+import { EmailAddressData, PersonEmailAddresses } from './FixEmailAddresses';
 import { GetInvalidEmailAddressesQuery } from './FixEmailAddresses.generated';
 import { mockInvalidEmailAddressesResponse } from './FixEmailAddressesMocks';
 
@@ -48,6 +49,14 @@ const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
   const handleChangeMock = jest.fn();
   const handleDeleteModalOpenMock = jest.fn();
   const handleChangePrimaryMock = jest.fn();
+  const toDelete = [] as PersonEmailAddressInput[];
+  const dataState = {
+    id: {
+      emailAddresses: testData.emailAddresses as EmailAddressData[],
+      toDelete,
+    },
+  } as { [key: string]: PersonEmailAddresses };
+
   return (
     <ThemeProvider theme={theme}>
       <TestWrapper>
@@ -58,10 +67,11 @@ const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
           mocks={mocks}
         >
           <FixEmailAddressPerson
-            toDelete={[]}
+            toDelete={toDelete}
             name={testData.name}
             key={testData.name}
             personId={testData.personId}
+            dataState={dataState}
             contactId={testData.contactId}
             emailAddresses={testData.emailAddresses}
             handleChange={handleChangeMock}
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
index b2795932c..7cb3b27ce 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
@@ -23,7 +23,7 @@ import { dateFormatShort } from 'src/lib/intlFormat';
 import theme from '../../../theme';
 import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
 import EmailValidationForm from './EmailValidationForm';
-import { EmailAddressData } from './FixEmailAddresses';
+import { EmailAddressData, PersonEmailAddresses } from './FixEmailAddresses';
 
 const PersonCard = styled(Box)(({ theme }) => ({
   [theme.breakpoints.up('md')]: {
@@ -96,6 +96,7 @@ export interface FixEmailAddressPersonProps {
   name: string;
   emailAddresses?: EmailAddressData[];
   personId: string;
+  dataState: { [key: string]: PersonEmailAddresses };
   toDelete: PersonEmailAddressInput[];
   contactId: string;
   handleChange: (
@@ -112,6 +113,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
   name,
   emailAddresses,
   personId,
+  dataState,
   contactId,
   handleChange,
   handleDelete,
@@ -130,7 +132,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
         personId: personId,
         isPrimary: email.primary,
       })) || [],
-    [emailAddresses],
+    [emailAddresses, dataState],
   );
 
   const handleContactNameClick = () => {
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 03b0903af..63f4ab966 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -273,17 +273,16 @@ describe('FixPhoneNumbers-Home', () => {
     ).toBeInTheDocument();
   });
 
-  //TODO: Fix during MPDX-7946
-  it.skip('should modify first email of first contact', async () => {
+  it('should modify first email of first contact', async () => {
     const { getByTestId } = render(<Components />);
     await waitFor(() => {
       expect(getByTestId('textfield-testid-0')).toBeInTheDocument();
     });
-    const firstInput = getByTestId('textfield-testid-0') as HTMLInputElement;
+    const firstInput = getByTestId('textfield-testid-0');
 
-    expect(firstInput.value).toBe('email1@gmail.com');
+    expect(firstInput).toHaveValue('email1@gmail.com');
     userEvent.type(firstInput, '123');
-    expect(firstInput.value).toBe('email1@gmail.com123');
+    expect(firstInput).toHaveValue('email1@gmail.com123');
   });
 
   describe('setContactFocus()', () => {
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index 00fb2288f..34516aeb6 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -104,7 +104,7 @@ export interface EmailAddressData {
   destroy?: boolean;
 }
 
-interface PersonEmailAddresses {
+export interface PersonEmailAddresses {
   emailAddresses: EmailAddressData[];
   toDelete: PersonEmailAddressInput[];
 }
@@ -279,6 +279,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
                     name={`${person.firstName} ${person.lastName}`}
                     key={person.id}
                     personId={person.id}
+                    dataState={dataState}
                     emailAddresses={dataState[person.id]?.emailAddresses}
                     toDelete={dataState[person.id]?.toDelete}
                     contactId={person.contactId}

From cfdbd7cfa356f970ed5337f39f86e95efa9ead29 Mon Sep 17 00:00:00 2001
From: Collin Pastika <collin.pastika@cru.org>
Date: Tue, 9 Jul 2024 15:19:53 -0400
Subject: [PATCH 04/22] Fix part of the caching issue. The way the component is
 using state of the emails is causing the most issues why they aren't
 updating. The new email gets added but replaces the last email.

---
 .../FixEmailAddresses/AddEmailAddress.graphql |  2 +-
 .../FixEmailAddressPerson.test.tsx            | 83 +++++++++----------
 .../FixEmailAddressPerson.tsx                 | 54 ++++++------
 .../FixEmailAddresses.graphql                 | 14 ++++
 .../FixEmailAddresses.test.tsx                |  8 +-
 .../FixEmailAddresses/FixEmailAddresses.tsx   | 21 +++--
 .../FixEmailAddressesMocks.ts                 | 62 --------------
 7 files changed, 95 insertions(+), 149 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql b/src/components/Tool/FixEmailAddresses/AddEmailAddress.graphql
index e6fabd887..c0eedcfce 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/FixEmailAddressPerson.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
index bf7068d4c..c79b0dcb5 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
@@ -9,39 +9,38 @@ 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 { FixEmailAddressPerson } from './FixEmailAddressPerson';
 import { EmailAddressData, PersonEmailAddresses } from './FixEmailAddresses';
-import { GetInvalidEmailAddressesQuery } from './FixEmailAddresses.generated';
+import {
+  GetInvalidEmailAddressesQuery,
+  PersonInvalidEmailFragment,
+} from './FixEmailAddresses.generated';
 import { mockInvalidEmailAddressesResponse } from './FixEmailAddressesMocks';
 
-const testData = {
-  name: 'Test Contact',
-  personId: 'testid',
+const person: PersonInvalidEmailFragment = {
+  id: 'contactTestId',
+  firstName: 'Test',
+  lastName: 'Contact',
   contactId: 'contactTestId',
-  emailAddresses: [
-    {
-      source: 'DonorHub',
-      updatedAt: DateTime.fromISO('2021-06-21').toString(),
-      email: 'test1@test1.com',
-      primary: true,
-      isValid: false,
-      personId: 'testid',
-      isPrimary: true,
-    } as EmailAddressData,
-    {
-      source: 'MPDX',
-      updatedAt: DateTime.fromISO('2021-06-22').toString(),
-      email: 'test2@test1.com',
-      primary: false,
-      isValid: false,
-      personId: 'testid',
-      isPrimary: false,
-    } as EmailAddressData,
-  ],
-} as FixEmailAddressPersonProps;
+  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();
 
@@ -51,8 +50,8 @@ const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
   const handleChangePrimaryMock = jest.fn();
   const toDelete = [] as PersonEmailAddressInput[];
   const dataState = {
-    id: {
-      emailAddresses: testData.emailAddresses as EmailAddressData[],
+    contactTestId: {
+      emailAddresses: person.emailAddresses.nodes as EmailAddressData[],
       toDelete,
     },
   } as { [key: string]: PersonEmailAddresses };
@@ -67,13 +66,9 @@ const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
           mocks={mocks}
         >
           <FixEmailAddressPerson
+            person={person}
             toDelete={toDelete}
-            name={testData.name}
-            key={testData.name}
-            personId={testData.personId}
             dataState={dataState}
-            contactId={testData.contactId}
-            emailAddresses={testData.emailAddresses}
             handleChange={handleChangeMock}
             handleDelete={handleDeleteModalOpenMock}
             handleChangePrimary={handleChangePrimaryMock}
@@ -99,12 +94,14 @@ describe('FixEmailAddressPerson', () => {
       />,
     );
 
-    expect(getByText(testData.name)).toBeInTheDocument();
+    expect(
+      getByText(`${person.firstName} ${person.lastName}`),
+    ).toBeInTheDocument();
     expect(getByText('DonorHub (6/21/2021)')).toBeInTheDocument();
-    expect(getByTestId('textfield-testid-0')).toBeInTheDocument();
+    expect(getByTestId('textfield-contactTestId-0')).toBeInTheDocument();
     expect(getByDisplayValue('test1@test1.com')).toBeInTheDocument();
     expect(getByText('MPDX (6/22/2021)')).toBeInTheDocument();
-    expect(getByTestId('textfield-testid-1')).toBeInTheDocument();
+    expect(getByTestId('textfield-contactTestId-1')).toBeInTheDocument();
     expect(getByDisplayValue('test2@test1.com')).toBeInTheDocument();
   });
 
@@ -122,7 +119,7 @@ describe('FixEmailAddressPerson', () => {
     );
 
     const addInput = getByLabelText('New Email Address');
-    const addButton = getByTestId('addButton-testid');
+    const addButton = getByTestId('addButton-contactTestId');
 
     userEvent.type(addInput, 'new@new.com');
     await waitFor(() => {
@@ -152,7 +149,7 @@ describe('FixEmailAddressPerson', () => {
       userEvent.click(addInput);
       userEvent.tab();
 
-      const addButton = getByTestId('addButton-testid');
+      const addButton = getByTestId('addButton-contactTestId');
       await waitFor(() => {
         expect(addButton).toBeDisabled();
         expect(getByText('Please enter a valid email address')).toBeVisible();
@@ -176,7 +173,7 @@ describe('FixEmailAddressPerson', () => {
       userEvent.type(addInput, 'ab');
       userEvent.tab();
 
-      const addButton = getByTestId('addButton-testid');
+      const addButton = getByTestId('addButton-contactTestId');
       await waitFor(() => {
         expect(addButton).toBeDisabled();
         expect(getByText('Invalid Email Address Format')).toBeVisible();
@@ -200,7 +197,7 @@ describe('FixEmailAddressPerson', () => {
       userEvent.type(addInput, 'new@new.com');
       userEvent.tab();
 
-      const addButton = getByTestId('addButton-testid');
+      const addButton = getByTestId('addButton-contactTestId');
       await waitFor(() => {
         expect(addButton).not.toBeDisabled();
       });
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
index 7cb3b27ce..6587b954f 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
@@ -23,7 +23,8 @@ import { dateFormatShort } from 'src/lib/intlFormat';
 import theme from '../../../theme';
 import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
 import EmailValidationForm from './EmailValidationForm';
-import { EmailAddressData, PersonEmailAddresses } from './FixEmailAddresses';
+import { PersonEmailAddresses } from './FixEmailAddresses';
+import { PersonInvalidEmailFragment } from './FixEmailAddresses.generated';
 
 const PersonCard = styled(Box)(({ theme }) => ({
   [theme.breakpoints.up('md')]: {
@@ -93,12 +94,9 @@ const useStyles = makeStyles()((theme: Theme) => ({
 }));
 
 export interface FixEmailAddressPersonProps {
-  name: string;
-  emailAddresses?: EmailAddressData[];
-  personId: string;
-  dataState: { [key: string]: PersonEmailAddresses };
+  person: PersonInvalidEmailFragment;
   toDelete: PersonEmailAddressInput[];
-  contactId: string;
+  dataState: { [key: string]: PersonEmailAddresses };
   handleChange: (
     personId: string,
     numberIndex: number,
@@ -110,11 +108,8 @@ export interface FixEmailAddressPersonProps {
 }
 
 export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
-  name,
-  emailAddresses,
-  personId,
+  person,
   dataState,
-  contactId,
   handleChange,
   handleDelete,
   handleChangePrimary,
@@ -124,16 +119,23 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
   const locale = useLocale();
   const { classes } = useStyles();
 
-  const emails = useMemo(
-    () =>
-      emailAddresses?.map((email) => ({
+  const { id, contactId } = person;
+  const name = `${person.firstName} ${person.lastName}`;
+
+  const emails = useMemo(() => {
+    if (!dataState[id]?.emailAddresses.length) {
+      return [];
+    }
+
+    return (
+      dataState[id]?.emailAddresses.map((email) => ({
         ...email,
         isValid: false,
-        personId: personId,
+        personId: id,
         isPrimary: email.primary,
-      })) || [],
-    [emailAddresses, dataState],
-  );
+      })) || []
+    );
+  }, [person, dataState]);
 
   const handleContactNameClick = () => {
     setContactFocus(contactId);
@@ -203,15 +205,13 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                             </Typography>
                           </Box>
                           {email.isPrimary ? (
-                            <Box data-testid={`starIcon-${personId}-${index}`}>
+                            <Box data-testid={`starIcon-${id}-${index}`}>
                               <HoverableIcon path={mdiStar} size={1} />
                             </Box>
                           ) : (
                             <Box
-                              data-testid={`starOutlineIcon-${personId}-${index}`}
-                              onClick={() =>
-                                handleChangePrimary(personId, index)
-                              }
+                              data-testid={`starOutlineIcon-${id}-${index}`}
+                              onClick={() => handleChangePrimary(id, index)}
                             >
                               <HoverableIcon path={mdiStarOutline} size={1} />
                             </Box>
@@ -227,19 +227,19 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                           <TextField
                             style={{ width: '100%' }}
                             inputProps={{
-                              'data-testid': `textfield-${personId}-${index}`,
+                              'data-testid': `textfield-${id}-${index}`,
                             }}
                             onChange={(
                               event: React.ChangeEvent<HTMLInputElement>,
-                            ) => handleChange(personId, index, event)}
+                            ) => handleChange(id, index, event)}
                             value={email.email}
                             disabled={email.source !== 'MPDX'}
                           />
 
                           {email.source === 'MPDX' ? (
                             <Box
-                              data-testid={`delete-${personId}-${index}`}
-                              onClick={() => handleDelete(personId, index)}
+                              data-testid={`delete-${id}-${index}`}
+                              onClick={() => handleDelete(id, index)}
                             >
                               <HoverableIcon path={mdiDelete} size={1} />
                             </Box>
@@ -277,7 +277,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                       {
                         //TODO: index will need to be mapped to the correct personId
                       }
-                      <EmailValidationForm index={0} personId={personId} />
+                      <EmailValidationForm index={0} personId={id} />
                     </BoxWithResponsiveBorder>
                   </RowWrapper>
                 </Grid>
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
index f4534a40f..1929da204 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
@@ -30,3 +30,17 @@ fragment PersonEmailAddress on EmailAddress {
   updatedAt
   source
 }
+
+mutation UpdateEmailAddresses($input: PersonUpdateMutationInput!) {
+  updatePerson(input: $input) {
+    person {
+      emailAddresses {
+        nodes {
+          email
+          id
+          primary
+        }
+      }
+    }
+  }
+}
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 63f4ab966..907485397 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -15,8 +15,6 @@ import {
   contactId,
   contactOneEmailAddressNodes,
   contactTwoEmailAddressNodes,
-  mockCacheWriteData,
-  mockCacheWriteDataContactTwo,
   mockInvalidEmailAddressesResponse,
   newEmail,
 } from './FixEmailAddressesMocks';
@@ -152,7 +150,7 @@ describe('FixPhoneNumbers-Home', () => {
             nodes: [
               ...emailAddressNodes,
               {
-                email: newEmail.email,
+                ...newEmail,
               },
             ],
           },
@@ -218,7 +216,7 @@ describe('FixPhoneNumbers-Home', () => {
 
       await waitFor(() => {
         expect(cache.writeQuery).toHaveBeenLastCalledWith(
-          expect.objectContaining({ data: mockCacheWriteData }),
+          expect.objectContaining({ data: postSaveResponse }),
         );
       });
     });
@@ -249,7 +247,7 @@ describe('FixPhoneNumbers-Home', () => {
 
       await waitFor(() => {
         expect(cache.writeQuery).toHaveBeenLastCalledWith(
-          expect.objectContaining({ data: mockCacheWriteDataContactTwo }),
+          expect.objectContaining({ data: postSaveResponse }),
         );
       });
     });
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index 34516aeb6..9c35b9fe7 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -157,7 +157,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
             )
           : {},
       ),
-    [loading],
+    [loading, data],
   );
 
   const handleDeleteModalOpen = (
@@ -209,12 +209,14 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
   // Change the primary address in the state
   const handleChangePrimary = (personId: string, emailIndex: number): void => {
     const temp = { ...dataState };
-    temp[personId].emailAddresses = temp[personId].emailAddresses.map(
-      (email, index) => ({
-        ...email,
-        primary: index === emailIndex,
-      }),
-    );
+    if (temp[personId]) {
+      temp[personId].emailAddresses = temp[personId].emailAddresses.map(
+        (email, index) => ({
+          ...email,
+          primary: index === emailIndex,
+        }),
+      );
+    }
     setDataState(temp);
   };
 
@@ -276,13 +278,10 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
               <Grid item xs={12}>
                 {data?.people.nodes.map((person) => (
                   <FixEmailAddressPerson
-                    name={`${person.firstName} ${person.lastName}`}
+                    person={person}
                     key={person.id}
-                    personId={person.id}
                     dataState={dataState}
-                    emailAddresses={dataState[person.id]?.emailAddresses}
                     toDelete={dataState[person.id]?.toDelete}
-                    contactId={person.contactId}
                     handleChange={handleChange}
                     handleDelete={handleDeleteModalOpen}
                     handleChangePrimary={handleChangePrimary}
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts b/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts
index 040722f63..6ab77b75c 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts
@@ -77,65 +77,3 @@ export const mockInvalidEmailAddressesResponse: ErgonoMockShape[] = [
     },
   },
 ];
-
-export const mockCacheWriteData = {
-  people: {
-    nodes: [
-      {
-        ...mockInvalidEmailAddressesResponse[0],
-        emailAddresses: {
-          nodes: [
-            {
-              __typename: contactOneEmailAddressNodes[0].__typename,
-              email: contactOneEmailAddressNodes[0].email,
-            },
-            {
-              __typename: contactOneEmailAddressNodes[1].__typename,
-              email: contactOneEmailAddressNodes[1].email,
-            },
-            {
-              __typename: contactOneEmailAddressNodes[2].__typename,
-              email: contactOneEmailAddressNodes[2].email,
-            },
-            {
-              __typename: newEmail.__typename,
-              email: newEmail.email,
-            },
-          ],
-        },
-      },
-      {
-        ...mockInvalidEmailAddressesResponse[1],
-      },
-    ],
-  },
-};
-
-export const mockCacheWriteDataContactTwo = {
-  people: {
-    nodes: [
-      {
-        ...mockInvalidEmailAddressesResponse[0],
-      },
-      {
-        ...mockInvalidEmailAddressesResponse[1],
-        emailAddresses: {
-          nodes: [
-            {
-              __typename: contactTwoEmailAddressNodes[0].__typename,
-              email: contactTwoEmailAddressNodes[0].email,
-            },
-            {
-              __typename: contactTwoEmailAddressNodes[1].__typename,
-              email: contactTwoEmailAddressNodes[1].email,
-            },
-            {
-              __typename: newEmail.__typename,
-              email: newEmail.email,
-            },
-          ],
-        },
-      },
-    ],
-  },
-};

From 3d89e00976268f07c6a4fa24edd248a6d764681e Mon Sep 17 00:00:00 2001
From: Collin Pastika <collin.pastika@cru.org>
Date: Thu, 11 Jul 2024 14:56:59 -0400
Subject: [PATCH 05/22] fixed modal close button styling

---
 .../Tool/FixEmailAddresses/DeleteModal.tsx    | 20 +++++++++++++++----
 1 file changed, 16 insertions(+), 4 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/DeleteModal.tsx b/src/components/Tool/FixEmailAddresses/DeleteModal.tsx
index c07cf0977..bb689c5e3 100644
--- a/src/components/Tool/FixEmailAddresses/DeleteModal.tsx
+++ b/src/components/Tool/FixEmailAddresses/DeleteModal.tsx
@@ -74,13 +74,25 @@ const DeleteModal: React.FC<Props> = ({
               flexDirection="column"
               alignItems="center"
               className={classes.headerBox}
+              position="relative"
             >
-              <Typography variant="h5" style={{ marginTop: -theme.spacing(1) }}>
-                {t('Confirm')}
-              </Typography>
-              <IconButton onClick={handleClose} className={classes.iconButton}>
+              <IconButton
+                onClick={handleClose}
+                className={classes.iconButton}
+                style={{
+                  position: 'absolute',
+                  top: -4,
+                  right: 0,
+                }}
+              >
                 <Icon path={mdiCloseThick} size={1} />
               </IconButton>
+              <Typography
+                variant="h5"
+                style={{ marginTop: -theme.spacing(1), alignSelf: 'center' }}
+              >
+                {t('Confirm')}
+              </Typography>
             </Box>
           }
         />

From a8b6328ed62d913de37cd0680af52784e85ed198 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Fri, 19 Jul 2024 16:35:44 -0400
Subject: [PATCH 06/22] added delete functionality and fixed modal issues

---
 .../{ => DeleteModal}/DeleteModal.test.tsx    |   6 +-
 .../{ => DeleteModal}/DeleteModal.tsx         |  16 +--
 .../FixEmailAddresses/EmailValidationForm.tsx |   2 +-
 .../FixEmailAddressPerson.test.tsx            |  16 +--
 .../FixEmailAddressPerson.tsx                 |  18 +--
 .../FixEmailAddresses/FixEmailAddresses.tsx   | 117 ++++++++++--------
 6 files changed, 91 insertions(+), 84 deletions(-)
 rename src/components/Tool/FixEmailAddresses/{ => DeleteModal}/DeleteModal.test.tsx (92%)
 rename src/components/Tool/FixEmailAddresses/{ => DeleteModal}/DeleteModal.tsx (89%)
 rename src/components/Tool/FixEmailAddresses/{ => FixEmailAddressPerson}/FixEmailAddressPerson.test.tsx (92%)
 rename src/components/Tool/FixEmailAddresses/{ => FixEmailAddressPerson}/FixEmailAddressPerson.tsx (95%)

diff --git a/src/components/Tool/FixEmailAddresses/DeleteModal.test.tsx b/src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.test.tsx
similarity index 92%
rename from src/components/Tool/FixEmailAddresses/DeleteModal.test.tsx
rename to src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.test.tsx
index 9caf1caa8..8bd980207 100644
--- a/src/components/Tool/FixEmailAddresses/DeleteModal.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.test.tsx
@@ -2,14 +2,14 @@ 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 theme from 'src/theme';
 import DeleteModal from './DeleteModal';
 
 const testState = {
   open: true,
   personId: '',
-  emailIndex: 0,
-  emailAddress: 'test@test.com',
+  id: '',
+  email: 'test@test.com',
 };
 
 describe('FixEmailAddresses-DeleteModal', () => {
diff --git a/src/components/Tool/FixEmailAddresses/DeleteModal.tsx b/src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.tsx
similarity index 89%
rename from src/components/Tool/FixEmailAddresses/DeleteModal.tsx
rename to src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.tsx
index bb689c5e3..cafb47221 100644
--- a/src/components/Tool/FixEmailAddresses/DeleteModal.tsx
+++ b/src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.tsx
@@ -18,8 +18,8 @@ import {
   CancelButton,
   DeleteButton,
 } from 'src/components/common/Modal/ActionButtons/ActionButtons';
-import theme from '../../../theme';
-import { ModalState } from './FixEmailAddresses';
+import theme from 'src/theme';
+import { ModalState } from '../FixEmailAddresses';
 
 const useStyles = makeStyles()((theme: Theme) => ({
   modal: {
@@ -50,7 +50,7 @@ const useStyles = makeStyles()((theme: Theme) => ({
 interface Props {
   modalState: ModalState;
   handleClose: () => void;
-  handleDelete: () => void;
+  handleDelete: ({ personId, id, email }: ModalState) => void;
 }
 
 const DeleteModal: React.FC<Props> = ({
@@ -61,11 +61,7 @@ const DeleteModal: React.FC<Props> = ({
   const { classes } = useStyles();
   const { t } = useTranslation();
   return (
-    <Modal
-      open={modalState.open}
-      onClose={handleClose}
-      className={classes.modal}
-    >
+    <Modal open={true} onClose={handleClose} className={classes.modal}>
       <Card>
         <CardHeader
           title={
@@ -106,14 +102,14 @@ const DeleteModal: React.FC<Props> = ({
             <Typography>
               {t('Are you sure you wish to delete this email address:')}
             </Typography>
-            <Typography>{`"${modalState.emailAddress}"`}</Typography>
+            <Typography>{`"${modalState.email}"`}</Typography>
           </Box>
         </CardContent>
         <CardActions>
           <CancelButton onClick={handleClose} />
           <DeleteButton
             dataTestId="emailAddressDeleteButton"
-            onClick={handleDelete}
+            onClick={() => handleDelete(modalState)}
             sx={{ marginRight: 0 }}
           >
             {/*TODO: make "newAddress" field in address false so it says "edit" instead of "add" */}
diff --git a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
index 2487ee12a..8fed17857 100644
--- a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
+++ b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
@@ -6,7 +6,7 @@ import * as Yup from 'yup';
 import { AddIcon } from 'src/components/Contacts/ContactDetails/ContactDetailsTab/StyledComponents';
 import { useAccountListId } from 'src/hooks/useAccountListId';
 import { useEmailAddressesMutation } from './AddEmailAddress.generated';
-import { RowWrapper } from './FixEmailAddressPerson';
+import { RowWrapper } from './FixEmailAddressPerson/FixEmailAddressPerson';
 import {
   GetInvalidEmailAddressesDocument,
   GetInvalidEmailAddressesQuery,
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
similarity index 92%
rename from src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
rename to src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
index c79b0dcb5..a199cd7d3 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
@@ -6,16 +6,15 @@ 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 } from './FixEmailAddressPerson';
-import { EmailAddressData, PersonEmailAddresses } from './FixEmailAddresses';
+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';
+} from '../FixEmailAddresses.generated';
+import { mockInvalidEmailAddressesResponse } from '../FixEmailAddressesMocks';
+import { FixEmailAddressPerson } from './FixEmailAddressPerson';
 
 const person: PersonInvalidEmailFragment = {
   id: 'contactTestId',
@@ -48,11 +47,9 @@ const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
   const handleChangeMock = jest.fn();
   const handleDeleteModalOpenMock = jest.fn();
   const handleChangePrimaryMock = jest.fn();
-  const toDelete = [] as PersonEmailAddressInput[];
   const dataState = {
     contactTestId: {
       emailAddresses: person.emailAddresses.nodes as EmailAddressData[],
-      toDelete,
     },
   } as { [key: string]: PersonEmailAddresses };
 
@@ -67,7 +64,6 @@ const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
         >
           <FixEmailAddressPerson
             person={person}
-            toDelete={toDelete}
             dataState={dataState}
             handleChange={handleChangeMock}
             handleDelete={handleDeleteModalOpenMock}
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
similarity index 95%
rename from src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
rename to src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
index 6587b954f..c0a7d35e4 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
@@ -17,14 +17,13 @@ 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 { PersonEmailAddresses } from './FixEmailAddresses';
-import { PersonInvalidEmailFragment } from './FixEmailAddresses.generated';
+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')]: {
@@ -95,14 +94,13 @@ const useStyles = makeStyles()((theme: Theme) => ({
 
 export interface FixEmailAddressPersonProps {
   person: PersonInvalidEmailFragment;
-  toDelete: PersonEmailAddressInput[];
   dataState: { [key: string]: PersonEmailAddresses };
   handleChange: (
     personId: string,
     numberIndex: number,
     event: React.ChangeEvent<HTMLInputElement>,
   ) => void;
-  handleDelete: (personId: string, emailAddress: number) => void;
+  handleDelete: (personId: string, id: string, email: string) => void;
   handleChangePrimary: (personId: string, emailIndex: number) => void;
   setContactFocus: SetContactFocus;
 }
@@ -239,7 +237,9 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                           {email.source === 'MPDX' ? (
                             <Box
                               data-testid={`delete-${id}-${index}`}
-                              onClick={() => handleDelete(id, index)}
+                              onClick={() =>
+                                handleDelete(id, email.id, email.email)
+                              }
                             >
                               <HoverableIcon path={mdiDelete} size={1} />
                             </Box>
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index 9c35b9fe7..98dca54fc 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -9,16 +9,19 @@ import {
   Typography,
 } from '@mui/material';
 import { styled } from '@mui/material/styles';
+import { useSnackbar } from 'notistack';
 import { Trans, useTranslation } from 'react-i18next';
 import { SetContactFocus } from 'pages/accountLists/[accountListId]/tools/useToolsHelper';
-import { useGetInvalidEmailAddressesQuery } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
-import { PersonEmailAddressInput } from 'src/graphql/types.generated';
+import {
+  useGetInvalidEmailAddressesQuery,
+  useUpdateEmailAddressesMutation,
+} from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
 import theme from '../../../theme';
 import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
 import NoData from '../NoData';
 import { StyledInput } from '../StyledInput';
-import DeleteModal from './DeleteModal';
-import { FixEmailAddressPerson } from './FixEmailAddressPerson';
+import DeleteModal from './DeleteModal/DeleteModal';
+import { FixEmailAddressPerson } from './FixEmailAddressPerson/FixEmailAddressPerson';
 
 const Container = styled(Box)(() => ({
   padding: theme.spacing(3),
@@ -82,21 +85,12 @@ const DefaultSourceWrapper = styled(Box)(({ theme }) => ({
 }));
 
 export interface ModalState {
-  open: boolean;
   personId: string;
-  emailIndex: number;
-  emailAddress: string;
+  id: string;
+  email: string;
 }
-
-const defaultDeleteModalState = {
-  open: false,
-  personId: '',
-  emailIndex: 0,
-  emailAddress: '',
-};
-
 export interface EmailAddressData {
-  id?: string;
+  id: string;
   primary: boolean;
   updatedAt: string;
   source: string;
@@ -106,7 +100,6 @@ export interface EmailAddressData {
 
 export interface PersonEmailAddresses {
   emailAddresses: EmailAddressData[];
-  toDelete: PersonEmailAddressInput[];
 }
 
 interface FixEmailAddressesProps {
@@ -119,14 +112,16 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
   setContactFocus,
 }) => {
   const [defaultSource, setDefaultSource] = useState('MPDX');
-  const [deleteModalState, setDeleteModalState] = useState<ModalState>(
-    defaultDeleteModalState,
+  const [deleteModalState, setDeleteModalState] = useState<ModalState | null>(
+    null,
   );
   const { t } = useTranslation();
+  const { enqueueSnackbar } = useSnackbar();
 
   const { data, loading } = useGetInvalidEmailAddressesQuery({
     variables: { accountListId },
   });
+  const [updateEmailAddressesMutation] = useUpdateEmailAddressesMutation();
 
   const [dataState, setDataState] = useState<{
     [key: string]: PersonEmailAddresses;
@@ -150,7 +145,6 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
                       email: emailAddress.email,
                     }),
                   ),
-                  toDelete: [],
                 },
               }),
               {},
@@ -162,18 +156,57 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
 
   const handleDeleteModalOpen = (
     personId: string,
-    emailIndex: number,
+    id: string,
+    email: string,
   ): void => {
     setDeleteModalState({
-      open: true,
-      personId: personId,
-      emailIndex: emailIndex,
-      emailAddress: dataState[personId].emailAddresses[emailIndex].email,
+      personId,
+      id,
+      email,
     });
   };
 
   const handleDeleteModalClose = (): void => {
-    setDeleteModalState(defaultDeleteModalState);
+    setDeleteModalState(null);
+  };
+
+  // Delete function called after confirming with the delete modal
+  const handleDelete = async ({
+    personId,
+    id,
+    email,
+  }: ModalState): Promise<void> => {
+    await updateEmailAddressesMutation({
+      variables: {
+        input: {
+          accountListId,
+          attributes: {
+            id: personId,
+            emailAddresses: [
+              {
+                id: id,
+                destroy: true,
+              },
+            ],
+          },
+        },
+      },
+      update: (cache) => {
+        cache.evict({ id: `EmailAddress:${id}` });
+        cache.gc();
+      },
+      onCompleted: () => {
+        enqueueSnackbar(t(`Successfully deleted email address ${email}`), {
+          variant: 'success',
+        });
+        handleDeleteModalClose();
+      },
+      onError: () => {
+        enqueueSnackbar(t(`Error deleting email address ${email}`), {
+          variant: 'error',
+        });
+      },
+    });
   };
 
   // Update the state with the textfield's value
@@ -187,25 +220,6 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
     setDataState(temp);
   };
 
-  // Delete function called after confirming with the delete modal
-  const handleDelete = (): void => {
-    const temp = { ...dataState };
-    const deleting = temp[deleteModalState.personId].emailAddresses.splice(
-      deleteModalState.emailIndex,
-      1,
-    )[0];
-    deleting.destroy = true;
-    deleting.primary &&
-      (temp[deleteModalState.personId].emailAddresses[0].primary = true); // If the deleted email was primary, set the new first index to primary
-    deleting.id &&
-      temp[deleteModalState.personId].toDelete.push({
-        destroy: true,
-        id: deleting.id,
-      }); //Only destroy the email if it already exists (has an ID)
-    setDataState(temp);
-    handleDeleteModalClose();
-  };
-
   // Change the primary address in the state
   const handleChangePrimary = (personId: string, emailIndex: number): void => {
     const temp = { ...dataState };
@@ -281,7 +295,6 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
                     person={person}
                     key={person.id}
                     dataState={dataState}
-                    toDelete={dataState[person.id]?.toDelete}
                     handleChange={handleChange}
                     handleDelete={handleDeleteModalOpen}
                     handleChangePrimary={handleChangePrimary}
@@ -312,11 +325,13 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
           style={{ marginTop: theme.spacing(3) }}
         />
       )}
-      <DeleteModal
-        modalState={deleteModalState}
-        handleClose={handleDeleteModalClose}
-        handleDelete={handleDelete}
-      />
+      {deleteModalState && (
+        <DeleteModal
+          modalState={deleteModalState}
+          handleClose={handleDeleteModalClose}
+          handleDelete={handleDelete}
+        />
+      )}
     </Container>
   );
 };

From f93cc21c9404116540fc189158b96f0539fa2053 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Fri, 19 Jul 2024 17:08:32 -0400
Subject: [PATCH 07/22] Do a bit of cleanup of the code

---
 .../Tool/FixEmailAddresses/DeleteModal/DeleteModal.tsx     | 1 -
 .../Tool/FixEmailAddresses/EmailValidationForm.tsx         | 3 +--
 .../FixEmailAddressPerson/FixEmailAddressPerson.tsx        | 7 ++-----
 3 files changed, 3 insertions(+), 8 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.tsx b/src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.tsx
index cafb47221..8c3c8aa0e 100644
--- a/src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.tsx
+++ b/src/components/Tool/FixEmailAddresses/DeleteModal/DeleteModal.tsx
@@ -112,7 +112,6 @@ const DeleteModal: React.FC<Props> = ({
             onClick={() => handleDelete(modalState)}
             sx={{ marginRight: 0 }}
           >
-            {/*TODO: make "newAddress" field in address false so it says "edit" instead of "add" */}
             {t('Delete')}
           </DeleteButton>
         </CardActions>
diff --git a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
index 8fed17857..07d5e136c 100644
--- a/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
+++ b/src/components/Tool/FixEmailAddresses/EmailValidationForm.tsx
@@ -28,7 +28,6 @@ interface EmailValidationFormEmail {
 }
 
 interface EmailValidationFormProps {
-  index: number;
   personId: string;
 }
 
@@ -69,7 +68,7 @@ const EmailValidationForm = ({ personId }: EmailValidationFormProps) => {
               },
             ],
           },
-          accountListId: accountListId || '',
+          accountListId: accountListId ?? '',
         },
       },
       update: (cache, { data: addEmailAddressData }) => {
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
index c0a7d35e4..51ecca5f2 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
@@ -182,7 +182,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                     </ColumnHeaderWrapper>
                   </Hidden>
                   {emails.map((email, index) => (
-                    <Fragment key={index}>
+                    <Fragment key={email.id}>
                       <RowWrapper item xs={12} sm={6}>
                         <Box
                           display="flex"
@@ -274,10 +274,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                       justifyContent="flex-start"
                       px={2}
                     >
-                      {
-                        //TODO: index will need to be mapped to the correct personId
-                      }
-                      <EmailValidationForm index={0} personId={id} />
+                      <EmailValidationForm personId={id} />
                     </BoxWithResponsiveBorder>
                   </RowWrapper>
                 </Grid>

From 7e3678cb43a68cd171f797a45f5c97327f422075 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Fri, 19 Jul 2024 17:13:21 -0400
Subject: [PATCH 08/22] Fix up the tests now that delete is working

---
 .../FixEmailAddresses.test.tsx                | 26 ++++++++++++-------
 1 file changed, 16 insertions(+), 10 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 907485397..07bcfc0d5 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -90,21 +90,23 @@ describe('FixPhoneNumbers-Home', () => {
     expect(getByTestId('starOutlineIcon-testid-0')).toBeInTheDocument();
   });
 
-  //TODO: Fix during MPDX-7936
-  it.skip('delete third email from first person', async () => {
+  it('delete third email from first person', async () => {
     const { getByTestId, queryByTestId } = render(<Components />);
 
     const delete02 = await waitFor(() => getByTestId('delete-testid-2'));
     userEvent.click(delete02);
 
-    const deleteButton = getByTestId('modal-delete-button');
+    const deleteButton = await waitFor(() =>
+      getByTestId('modal-delete-button'),
+    );
     userEvent.click(deleteButton);
 
-    expect(queryByTestId('textfield-testid-2')).not.toBeInTheDocument();
+    await waitFor(() => {
+      expect(queryByTestId('textfield-testid-2')).not.toBeInTheDocument();
+    });
   });
 
-  //TODO: Fix during MPDX-7936
-  it.skip('change second email for second person to primary then delete it', async () => {
+  it('change second email for second person to primary then delete it', async () => {
     const { getByTestId, queryByTestId } = render(<Components />);
 
     const star11 = await waitFor(() =>
@@ -112,14 +114,18 @@ describe('FixPhoneNumbers-Home', () => {
     );
     userEvent.click(star11);
 
-    const delete11 = getByTestId('delete-testid2-1');
+    const delete11 = await waitFor(() => getByTestId('delete-testid2-1'));
     userEvent.click(delete11);
 
-    const deleteButton = getByTestId('modal-delete-button');
+    const deleteButton = await waitFor(() =>
+      getByTestId('modal-delete-button'),
+    );
     userEvent.click(deleteButton);
 
-    expect(queryByTestId('starIcon-testid2-1')).not.toBeInTheDocument();
-    expect(getByTestId('starIcon-testid2-0')).toBeInTheDocument();
+    await waitFor(() => {
+      expect(queryByTestId('starIcon-testid2-1')).not.toBeInTheDocument();
+      expect(getByTestId('starIcon-testid2-0')).toBeInTheDocument();
+    });
   });
 
   describe('add email address', () => {

From 122cd9bf81453c622525d9ba3ca6772e9758334e Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Wed, 24 Jul 2024 08:55:38 -0400
Subject: [PATCH 09/22] Implement single confirm button

---
 .../FixEmailAddressPerson.test.tsx            |   2 +
 .../FixEmailAddressPerson.tsx                 |  19 ++-
 .../FixEmailAddresses.test.tsx                | 144 +++++++++++++++---
 .../FixEmailAddresses/FixEmailAddresses.tsx   |  48 ++++++
 4 files changed, 192 insertions(+), 21 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
index a199cd7d3..3b64149a7 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
@@ -42,6 +42,7 @@ const person: PersonInvalidEmailFragment = {
 };
 
 const setContactFocus = jest.fn();
+const handleSingleConfirm = jest.fn();
 
 const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
   const handleChangeMock = jest.fn();
@@ -68,6 +69,7 @@ const TestComponent = ({ mocks }: { mocks: ApolloErgonoMockMap }) => {
             handleChange={handleChangeMock}
             handleDelete={handleDeleteModalOpenMock}
             handleChangePrimary={handleChangePrimaryMock}
+            handleSingleConfirm={handleSingleConfirm}
             setContactFocus={setContactFocus}
           />
         </GqlMockedProvider>
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
index 51ecca5f2..3c134a68a 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
@@ -22,7 +22,7 @@ import { dateFormatShort } from 'src/lib/intlFormat';
 import theme from 'src/theme';
 import { ConfirmButtonIcon } from '../../ConfirmButtonIcon';
 import EmailValidationForm from '../EmailValidationForm';
-import { PersonEmailAddresses } from '../FixEmailAddresses';
+import { EmailAddressData, PersonEmailAddresses } from '../FixEmailAddresses';
 import { PersonInvalidEmailFragment } from '../FixEmailAddresses.generated';
 
 const PersonCard = styled(Box)(({ theme }) => ({
@@ -102,6 +102,10 @@ export interface FixEmailAddressPersonProps {
   ) => void;
   handleDelete: (personId: string, id: string, email: string) => void;
   handleChangePrimary: (personId: string, emailIndex: number) => void;
+  handleSingleConfirm: (
+    person: PersonInvalidEmailFragment,
+    emails: EmailAddressData[],
+  ) => void;
   setContactFocus: SetContactFocus;
 }
 
@@ -111,6 +115,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
   handleChange,
   handleDelete,
   handleChangePrimary,
+  handleSingleConfirm,
   setContactFocus,
 }) => {
   const { t } = useTranslation();
@@ -130,7 +135,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
         ...email,
         isValid: false,
         personId: id,
-        isPrimary: email.primary,
+        primary: email.primary,
       })) || []
     );
   }, [person, dataState]);
@@ -202,7 +207,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                               )})`}
                             </Typography>
                           </Box>
-                          {email.isPrimary ? (
+                          {email.primary ? (
                             <Box data-testid={`starIcon-${id}-${index}`}>
                               <HoverableIcon path={mdiStar} size={1} />
                             </Box>
@@ -289,7 +294,13 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
             style={{ paddingLeft: theme.spacing(1) }}
           >
             <ConfirmButtonWrapper>
-              <Button variant="contained" style={{ width: '100%' }}>
+              <Button
+                variant="contained"
+                style={{ width: '100%' }}
+                onClick={() =>
+                  handleSingleConfirm(person, emails as EmailAddressData[])
+                }
+              >
                 <ConfirmButtonIcon />
                 {t('Confirm')}
               </Button>
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 07bcfc0d5..f975b9c47 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -4,10 +4,14 @@ import { ThemeProvider } from '@mui/material/styles';
 import { render, waitFor } from '@testing-library/react';
 import userEvent from '@testing-library/user-event';
 import { ApolloErgonoMockMap, ErgonoMockShape } from 'graphql-ergonomock';
+import { SnackbarProvider } from 'notistack';
 import TestRouter from '__tests__/util/TestRouter';
 import TestWrapper from '__tests__/util/TestWrapper';
 import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
-import { GetInvalidEmailAddressesQuery } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
+import {
+  GetInvalidEmailAddressesQuery,
+  UpdateEmailAddressesMutation,
+} from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
 import theme from '../../../theme';
 import { EmailAddressesMutation } from './AddEmailAddress.generated';
 import { FixEmailAddresses } from './FixEmailAddresses';
@@ -27,6 +31,18 @@ const router = {
 
 const setContactFocus = 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 Components = ({
   mocks = {
     GetInvalidEmailAddresses: {
@@ -39,22 +55,25 @@ const Components = ({
   cache?: ApolloCache<object>;
 }) => (
   <ThemeProvider theme={theme}>
-    <TestRouter router={router}>
-      <TestWrapper>
-        <GqlMockedProvider<{
-          GetInvalidEmailAddresses: GetInvalidEmailAddressesQuery;
-          EmailAddresses: EmailAddressesMutation;
-        }>
-          mocks={mocks}
-          cache={cache}
-        >
-          <FixEmailAddresses
-            accountListId={accountListId}
-            setContactFocus={setContactFocus}
-          />
-        </GqlMockedProvider>
-      </TestWrapper>
-    </TestRouter>
+    <SnackbarProvider>
+      <TestRouter router={router}>
+        <TestWrapper>
+          <GqlMockedProvider<{
+            GetInvalidEmailAddresses: GetInvalidEmailAddressesQuery;
+            EmailAddresses: EmailAddressesMutation;
+            UpdateEmailAddresses: UpdateEmailAddressesMutation;
+          }>
+            mocks={mocks}
+            cache={cache}
+          >
+            <FixEmailAddresses
+              accountListId={accountListId}
+              setContactFocus={setContactFocus}
+            />
+          </GqlMockedProvider>
+        </TestWrapper>
+      </TestRouter>
+    </SnackbarProvider>
   </ThemeProvider>
 );
 
@@ -304,4 +323,95 @@ describe('FixPhoneNumbers-Home', () => {
       expect(setContactFocus).toHaveBeenCalledWith(contactId);
     });
   });
+
+  describe('handleSingleConfirm', () => {
+    it('should successfully submit changes to multiple emails', async () => {
+      const cache = new InMemoryCache();
+      const personName = 'Test Contact';
+
+      const updatePerson = {
+        person: {
+          id: mockInvalidEmailAddressesResponse[0].id,
+          emailAddresses: {
+            nodes: [
+              {
+                ...contactOneEmailAddressNodes[0],
+                email: 'different@email.com',
+              },
+              {
+                ...contactOneEmailAddressNodes[1],
+                email: 'different2@email.com',
+              },
+              {
+                ...contactOneEmailAddressNodes[2],
+              },
+            ],
+          },
+        },
+      } as ErgonoMockShape;
+
+      const { getAllByRole, queryByTestId, queryByText } = render(
+        <Components
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: mockInvalidEmailAddressesResponse,
+              },
+            },
+            UpdateEmailAddresses: { updatePerson },
+          }}
+          cache={cache}
+        />,
+      );
+
+      await waitFor(() =>
+        expect(queryByTestId('loading')).not.toBeInTheDocument(),
+      );
+
+      const confirmButton = getAllByRole('button', { name: 'Confirm' })[0];
+      userEvent.click(confirmButton);
+
+      await waitFor(() => {
+        expect(mockEnqueue).toHaveBeenCalledWith(
+          `Successfully updated email addresses for ${personName}`,
+          { variant: 'success' },
+        );
+        expect(queryByText(personName)).not.toBeInTheDocument();
+      });
+    });
+
+    it('should handle an error', async () => {
+      const cache = new InMemoryCache();
+
+      const { getAllByRole, queryByTestId } = render(
+        <Components
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: mockInvalidEmailAddressesResponse,
+              },
+            },
+            UpdateEmailAddresses: () => {
+              throw new Error('Server Error');
+            },
+          }}
+          cache={cache}
+        />,
+      );
+
+      await waitFor(() =>
+        expect(queryByTestId('loading')).not.toBeInTheDocument(),
+      );
+
+      const confirmButton = getAllByRole('button', { name: 'Confirm' })[0];
+      userEvent.click(confirmButton);
+
+      await waitFor(() => {
+        expect(mockEnqueue).toHaveBeenCalledWith(
+          'Error updating email addresses for Test Contact',
+          { variant: 'error', autoHideDuration: 7000 },
+        );
+      });
+    });
+  });
 });
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index 98dca54fc..888902eb3 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -13,9 +13,11 @@ import { useSnackbar } from 'notistack';
 import { Trans, useTranslation } from 'react-i18next';
 import { SetContactFocus } from 'pages/accountLists/[accountListId]/tools/useToolsHelper';
 import {
+  PersonInvalidEmailFragment,
   useGetInvalidEmailAddressesQuery,
   useUpdateEmailAddressesMutation,
 } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
+import { PersonEmailAddressInput } from 'src/graphql/types.generated';
 import theme from '../../../theme';
 import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
 import NoData from '../NoData';
@@ -240,6 +242,51 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
     setDefaultSource(event.target.value);
   };
 
+  const handleSingleConfirm = async (
+    person: PersonInvalidEmailFragment,
+    emails: EmailAddressData[],
+  ) => {
+    const personName = `${person.firstName} ${person.lastName}`;
+    const emailAddresses = [] as PersonEmailAddressInput[];
+    emails.map((emailAddress) => {
+      emailAddresses.push({
+        email: emailAddress.email,
+        id: emailAddress.id,
+        primary: emailAddress.primary,
+        validValues: true,
+      });
+    });
+
+    await updateEmailAddressesMutation({
+      variables: {
+        input: {
+          accountListId,
+          attributes: {
+            id: person.id,
+            emailAddresses,
+          },
+        },
+      },
+      update: (cache) => {
+        cache.evict({ id: `Person:${person.id}` });
+      },
+      onCompleted: () => {
+        enqueueSnackbar(
+          t(`Successfully updated email addresses for ${personName}`),
+          {
+            variant: 'success',
+          },
+        );
+      },
+      onError: () => {
+        enqueueSnackbar(t(`Error updating email addresses for ${personName}`), {
+          variant: 'error',
+          autoHideDuration: 7000,
+        });
+      },
+    });
+  };
+
   return (
     <Container>
       {!loading && data && dataState ? (
@@ -298,6 +345,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
                     handleChange={handleChange}
                     handleDelete={handleDeleteModalOpen}
                     handleChangePrimary={handleChangePrimary}
+                    handleSingleConfirm={handleSingleConfirm}
                     setContactFocus={setContactFocus}
                   />
                 ))}

From ad84715448e974aadd2fe61c64cf89e7dd0342b2 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Wed, 24 Jul 2024 09:18:28 -0400
Subject: [PATCH 10/22] Allow for selection of primary when all email addresses
 are primary

---
 .../FixEmailAddressPerson.tsx                 |  5 +-
 .../FixEmailAddresses.test.tsx                | 75 +++++++++++++++++--
 2 files changed, 72 insertions(+), 8 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
index 3c134a68a..5b9a9e5e8 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
@@ -208,7 +208,10 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                             </Typography>
                           </Box>
                           {email.primary ? (
-                            <Box data-testid={`starIcon-${id}-${index}`}>
+                            <Box
+                              data-testid={`starIcon-${id}-${index}`}
+                              onClick={() => handleChangePrimary(id, index)}
+                            >
                               <HoverableIcon path={mdiStar} size={1} />
                             </Box>
                           ) : (
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index f975b9c47..084c4a0a9 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -98,15 +98,76 @@ describe('FixPhoneNumbers-Home', () => {
     expect(queryByTestId('no-data')).not.toBeInTheDocument();
   });
 
-  it('change primary of first email', async () => {
-    const { getByTestId, queryByTestId } = render(<Components />);
+  describe('handleChangePrimary()', () => {
+    it('changes primary of first email', async () => {
+      const { getByTestId, queryByTestId } = render(<Components />);
+
+      const star1 = await waitFor(() =>
+        getByTestId('starOutlineIcon-testid-1'),
+      );
+      userEvent.click(star1);
+
+      expect(queryByTestId('starIcon-testid-0')).not.toBeInTheDocument();
+      expect(getByTestId('starIcon-testid-1')).toBeInTheDocument();
+      expect(getByTestId('starOutlineIcon-testid-0')).toBeInTheDocument();
+    });
+
+    it('should choose primary and deselect primary from others', async () => {
+      const { getByTestId, queryByTestId } = render(
+        <Components
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: [
+                  {
+                    ...mockInvalidEmailAddressesResponse[0],
+                    emailAddresses: {
+                      nodes: [
+                        {
+                          ...contactOneEmailAddressNodes[0],
+                          primary: true,
+                        },
+                        {
+                          ...contactOneEmailAddressNodes[1],
+                          primary: true,
+                        },
+                        {
+                          ...contactOneEmailAddressNodes[2],
+                          primary: true,
+                        },
+                      ],
+                    },
+                  },
+                  {
+                    ...mockInvalidEmailAddressesResponse[1],
+                  },
+                ],
+              },
+            },
+          }}
+        />,
+      );
 
-    const star1 = await waitFor(() => getByTestId('starOutlineIcon-testid-1'));
-    userEvent.click(star1);
+      let newPrimary;
+      await waitFor(() => {
+        expect(getByTestId('starIcon-testid-0')).toBeInTheDocument();
+        expect(getByTestId('starIcon-testid-1')).toBeInTheDocument();
+        newPrimary = getByTestId('starIcon-testid-2');
+        expect(newPrimary).toBeInTheDocument();
+      });
+      userEvent.click(newPrimary);
 
-    expect(queryByTestId('starIcon-testid-0')).not.toBeInTheDocument();
-    expect(getByTestId('starIcon-testid-1')).toBeInTheDocument();
-    expect(getByTestId('starOutlineIcon-testid-0')).toBeInTheDocument();
+      await waitFor(() => {
+        expect(queryByTestId('starIcon-testid-0')).not.toBeInTheDocument();
+        expect(queryByTestId('starIcon-testid-1')).not.toBeInTheDocument();
+        expect(getByTestId('starIcon-testid-2')).toBeInTheDocument();
+        expect(getByTestId('starOutlineIcon-testid-0')).toBeInTheDocument();
+        expect(getByTestId('starOutlineIcon-testid-1')).toBeInTheDocument();
+        expect(
+          queryByTestId('starOutlineIcon-testid-2'),
+        ).not.toBeInTheDocument();
+      });
+    });
   });
 
   it('delete third email from first person', async () => {

From aa2b9f948ac8e36c7620f4e46635906c07a3784d Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Wed, 24 Jul 2024 17:15:15 -0400
Subject: [PATCH 11/22] Implement bulk confirm button

---
 .../FixEmailAddresses.graphql                 | 16 ++++
 .../FixEmailAddresses.test.tsx                | 79 +++++++++++++++++++
 .../FixEmailAddresses/FixEmailAddresses.tsx   | 42 +++++++++-
 3 files changed, 136 insertions(+), 1 deletion(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
index 1929da204..0e02d45af 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.graphql
@@ -44,3 +44,19 @@ mutation UpdateEmailAddresses($input: PersonUpdateMutationInput!) {
     }
   }
 }
+
+mutation UpdatePeople($input: PeopleUpdateMutationInput!) {
+  updatePeople(input: $input) {
+    people {
+      emailAddresses {
+        nodes {
+          email
+          id
+          primary
+          source
+          validValues
+        }
+      }
+    }
+  }
+}
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 084c4a0a9..b402802d4 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -11,6 +11,7 @@ import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
 import {
   GetInvalidEmailAddressesQuery,
   UpdateEmailAddressesMutation,
+  UpdatePeopleMutation,
 } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
 import theme from '../../../theme';
 import { EmailAddressesMutation } from './AddEmailAddress.generated';
@@ -62,6 +63,7 @@ const Components = ({
             GetInvalidEmailAddresses: GetInvalidEmailAddressesQuery;
             EmailAddresses: EmailAddressesMutation;
             UpdateEmailAddresses: UpdateEmailAddressesMutation;
+            UpdatePeople: UpdatePeopleMutation;
           }>
             mocks={mocks}
             cache={cache}
@@ -475,4 +477,81 @@ describe('FixPhoneNumbers-Home', () => {
       });
     });
   });
+
+  describe('handleBulkConfirm', () => {
+    it('should save all the email changes for all people', async () => {
+      const cache = new InMemoryCache();
+      const noPeopleMessage = 'No people with email addresses need attention';
+
+      const { getByRole, getByText, queryByTestId } = render(
+        <Components
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: mockInvalidEmailAddressesResponse,
+              },
+            },
+          }}
+          cache={cache}
+        />,
+      );
+
+      await waitFor(() =>
+        expect(queryByTestId('loading')).not.toBeInTheDocument(),
+      );
+
+      const bulkConfirmButton = getByRole('button', {
+        name: 'Confirm 2 as MPDX',
+      });
+      userEvent.click(bulkConfirmButton);
+
+      await waitFor(() => {
+        expect(mockEnqueue).toHaveBeenCalledWith(
+          `Successfully updated email addresses`,
+          { variant: 'success' },
+        );
+        expect(getByText(noPeopleMessage)).toBeVisible();
+      });
+    });
+
+    it('should handle errors', async () => {
+      const cache = new InMemoryCache();
+      const personName1 = 'Test Contact';
+      const personName2 = 'Simba Lion';
+
+      const { getByRole, getByText, queryByTestId } = render(
+        <Components
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: mockInvalidEmailAddressesResponse,
+              },
+            },
+            UpdatePeople: () => {
+              throw new Error('Server error');
+            },
+          }}
+          cache={cache}
+        />,
+      );
+
+      await waitFor(() =>
+        expect(queryByTestId('loading')).not.toBeInTheDocument(),
+      );
+
+      const bulkConfirmButton = getByRole('button', {
+        name: 'Confirm 2 as MPDX',
+      });
+      userEvent.click(bulkConfirmButton);
+
+      await waitFor(() => {
+        expect(mockEnqueue).toHaveBeenCalledWith(
+          `Error updating email addresses`,
+          { variant: 'error', autoHideDuration: 7000 },
+        );
+        expect(getByText(personName1)).toBeVisible();
+        expect(getByText(personName2)).toBeVisible();
+      });
+    });
+  });
 });
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index 888902eb3..857eaf78d 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -16,6 +16,7 @@ import {
   PersonInvalidEmailFragment,
   useGetInvalidEmailAddressesQuery,
   useUpdateEmailAddressesMutation,
+  useUpdatePeopleMutation,
 } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
 import { PersonEmailAddressInput } from 'src/graphql/types.generated';
 import theme from '../../../theme';
@@ -124,6 +125,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
     variables: { accountListId },
   });
   const [updateEmailAddressesMutation] = useUpdateEmailAddressesMutation();
+  const [updatePeople] = useUpdatePeopleMutation();
 
   const [dataState, setDataState] = useState<{
     [key: string]: PersonEmailAddresses;
@@ -287,6 +289,44 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
     });
   };
 
+  const handleBulkConfirm = async () => {
+    await updatePeople({
+      variables: {
+        input: {
+          accountListId,
+          attributes: Object.entries(dataState).map((value) => ({
+            id: value[0],
+            emailAddresses: value[1].emailAddresses.map(
+              (emailAddress) =>
+                ({
+                  email: emailAddress.email,
+                  id: emailAddress.id,
+                  primary: emailAddress.primary,
+                  validValues: true,
+                } as PersonEmailAddressInput),
+            ),
+          })),
+        },
+      },
+      update: (cache) => {
+        data?.people.nodes.forEach((person) => {
+          cache.evict({ id: `Person:${person.id}` });
+        });
+      },
+      onCompleted: () => {
+        enqueueSnackbar(t(`Successfully updated email addresses`), {
+          variant: 'success',
+        });
+      },
+      onError: () => {
+        enqueueSnackbar(t(`Error updating email addresses`), {
+          variant: 'error',
+          autoHideDuration: 7000,
+        });
+      },
+    });
+  };
+
   return (
     <Container>
       {!loading && data && dataState ? (
@@ -322,7 +362,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
                       <option value="MPDX">MPDX</option>
                       <option value="DataServer">DataServer</option>
                     </SourceSelect>
-                    <ConfirmButton>
+                    <ConfirmButton onClick={handleBulkConfirm}>
                       <ConfirmButtonIcon />
                       {t('Confirm {{amount}} as {{source}}', {
                         amount: data.people.nodes.length,

From 893af94b6d269cdf847b326a0eea409eca715660 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Thu, 25 Jul 2024 09:16:56 -0400
Subject: [PATCH 12/22] Add a confirmation modal for bulk save

---
 .../FixEmailAddresses.test.tsx                | 37 +++++++++++++++++++
 .../FixEmailAddresses/FixEmailAddresses.tsx   | 15 +++++++-
 2 files changed, 51 insertions(+), 1 deletion(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index b402802d4..f9aeba636 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -504,6 +504,7 @@ describe('FixPhoneNumbers-Home', () => {
         name: 'Confirm 2 as MPDX',
       });
       userEvent.click(bulkConfirmButton);
+      userEvent.click(getByRole('button', { name: 'Yes' }));
 
       await waitFor(() => {
         expect(mockEnqueue).toHaveBeenCalledWith(
@@ -543,6 +544,7 @@ describe('FixPhoneNumbers-Home', () => {
         name: 'Confirm 2 as MPDX',
       });
       userEvent.click(bulkConfirmButton);
+      userEvent.click(getByRole('button', { name: 'Yes' }));
 
       await waitFor(() => {
         expect(mockEnqueue).toHaveBeenCalledWith(
@@ -553,5 +555,40 @@ describe('FixPhoneNumbers-Home', () => {
         expect(getByText(personName2)).toBeVisible();
       });
     });
+
+    it('should cancel the bulk confirmation', async () => {
+      const cache = new InMemoryCache();
+      const personName1 = 'Test Contact';
+      const personName2 = 'Simba Lion';
+
+      const { getByRole, getByText, queryByTestId } = render(
+        <Components
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: mockInvalidEmailAddressesResponse,
+              },
+            },
+          }}
+          cache={cache}
+        />,
+      );
+
+      await waitFor(() =>
+        expect(queryByTestId('loading')).not.toBeInTheDocument(),
+      );
+
+      const bulkConfirmButton = getByRole('button', {
+        name: 'Confirm 2 as MPDX',
+      });
+      userEvent.click(bulkConfirmButton);
+      userEvent.click(getByRole('button', { name: 'No' }));
+
+      await waitFor(() => {
+        expect(mockEnqueue).not.toHaveBeenCalled();
+        expect(getByText(personName1)).toBeVisible();
+        expect(getByText(personName2)).toBeVisible();
+      });
+    });
   });
 });
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index 857eaf78d..983751b74 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -18,6 +18,7 @@ import {
   useUpdateEmailAddressesMutation,
   useUpdatePeopleMutation,
 } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
+import { Confirmation } from 'src/components/common/Modal/Confirmation/Confirmation';
 import { PersonEmailAddressInput } from 'src/graphql/types.generated';
 import theme from '../../../theme';
 import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
@@ -118,6 +119,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
   const [deleteModalState, setDeleteModalState] = useState<ModalState | null>(
     null,
   );
+  const [showBulkConfirmModal, setShowBulkConfirmModal] = useState(false);
   const { t } = useTranslation();
   const { enqueueSnackbar } = useSnackbar();
 
@@ -362,7 +364,9 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
                       <option value="MPDX">MPDX</option>
                       <option value="DataServer">DataServer</option>
                     </SourceSelect>
-                    <ConfirmButton onClick={handleBulkConfirm}>
+                    <ConfirmButton
+                      onClick={() => setShowBulkConfirmModal(true)}
+                    >
                       <ConfirmButtonIcon />
                       {t('Confirm {{amount}} as {{source}}', {
                         amount: data.people.nodes.length,
@@ -420,6 +424,15 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
           handleDelete={handleDelete}
         />
       )}
+      <Confirmation
+        isOpen={showBulkConfirmModal}
+        handleClose={() => setShowBulkConfirmModal(false)}
+        mutation={handleBulkConfirm}
+        title={t('Confirm')}
+        message={t(`You are updating all contacts visible on this page, setting the first ${defaultSource} email address as the
+          primary email address. If no such email address exists the contact will not be updated.
+          Are you sure you want to do this?`)}
+      />
     </Container>
   );
 };

From 2df85fbebd10c56c3293683198721bcaad10f69e Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Thu, 25 Jul 2024 14:25:35 -0400
Subject: [PATCH 13/22] Use the app name instead of hard-coding MPDX for source

---
 .../FixEmailAddressPerson.tsx                 |  6 +-
 .../FixEmailAddresses.test.tsx                | 56 +++++++++++++------
 .../FixEmailAddresses/FixEmailAddresses.tsx   |  6 +-
 3 files changed, 47 insertions(+), 21 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
index 5b9a9e5e8..73cc24c6f 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
@@ -17,6 +17,7 @@ import { DateTime } from 'luxon';
 import { useTranslation } from 'react-i18next';
 import { makeStyles } from 'tss-react/mui';
 import { SetContactFocus } from 'pages/accountLists/[accountListId]/tools/useToolsHelper';
+import useGetAppSettings from 'src/hooks/useGetAppSettings';
 import { useLocale } from 'src/hooks/useLocale';
 import { dateFormatShort } from 'src/lib/intlFormat';
 import theme from 'src/theme';
@@ -118,6 +119,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
   handleSingleConfirm,
   setContactFocus,
 }) => {
+  const { appName } = useGetAppSettings();
   const { t } = useTranslation();
   const locale = useLocale();
   const { classes } = useStyles();
@@ -239,10 +241,10 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                               event: React.ChangeEvent<HTMLInputElement>,
                             ) => handleChange(id, index, event)}
                             value={email.email}
-                            disabled={email.source !== 'MPDX'}
+                            disabled={email.source !== appName}
                           />
 
-                          {email.source === 'MPDX' ? (
+                          {email.source === appName ? (
                             <Box
                               data-testid={`delete-${id}-${index}`}
                               onClick={() =>
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index f9aeba636..2ecb1ed7a 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -13,6 +13,7 @@ import {
   UpdateEmailAddressesMutation,
   UpdatePeopleMutation,
 } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
+import useGetAppSettings from 'src/hooks/useGetAppSettings';
 import theme from '../../../theme';
 import { EmailAddressesMutation } from './AddEmailAddress.generated';
 import { FixEmailAddresses } from './FixEmailAddresses';
@@ -44,6 +45,8 @@ jest.mock('notistack', () => ({
   },
 }));
 
+jest.mock('src/hooks/useGetAppSettings');
+
 const Components = ({
   mocks = {
     GetInvalidEmailAddresses: {
@@ -80,24 +83,43 @@ const Components = ({
 );
 
 describe('FixPhoneNumbers-Home', () => {
-  it('default with test data', async () => {
-    const { getByText, getByTestId, queryByTestId } = render(<Components />);
+  beforeEach(() => {
+    (useGetAppSettings as jest.Mock).mockReturnValue({ appName: 'MPDX' });
+  });
 
-    await waitFor(() =>
-      expect(getByText('Fix Email Addresses')).toBeInTheDocument(),
-    );
-    await waitFor(() =>
-      expect(getByTestId('starOutlineIcon-testid-1')).toBeInTheDocument(),
-    );
-    await expect(
-      getByText('You have 2 email addresses to confirm.'),
-    ).toBeInTheDocument();
-    expect(getByText('Confirm 2 as MPDX')).toBeInTheDocument();
-    expect(getByText('Test Contact')).toBeInTheDocument();
-    expect(getByText('Simba Lion')).toBeInTheDocument();
-    expect(getByTestId('textfield-testid-0')).toBeInTheDocument();
-    expect(getByTestId('starIcon-testid-0')).toBeInTheDocument();
-    expect(queryByTestId('no-data')).not.toBeInTheDocument();
+  describe('render', () => {
+    it('default with test data', async () => {
+      const { getByText, getByTestId, queryByTestId } = render(<Components />);
+
+      await waitFor(() =>
+        expect(getByText('Fix Email Addresses')).toBeInTheDocument(),
+      );
+      await waitFor(() =>
+        expect(getByTestId('starOutlineIcon-testid-1')).toBeInTheDocument(),
+      );
+      await expect(
+        getByText('You have 2 email addresses to confirm.'),
+      ).toBeInTheDocument();
+      expect(getByText('Confirm 2 as MPDX')).toBeInTheDocument();
+      expect(getByText('Test Contact')).toBeInTheDocument();
+      expect(getByText('Simba Lion')).toBeInTheDocument();
+      expect(getByTestId('textfield-testid-0')).toBeInTheDocument();
+      expect(getByTestId('starIcon-testid-0')).toBeInTheDocument();
+      expect(queryByTestId('no-data')).not.toBeInTheDocument();
+    });
+
+    it('should show the app name as a source value', async () => {
+      (useGetAppSettings as jest.Mock).mockReturnValue({
+        appName: 'OtherThing',
+      });
+
+      const { getByRole, getByText } = render(<Components />);
+      await waitFor(() => {
+        expect(getByText('Fix Email Addresses')).toBeInTheDocument();
+        expect(getByText('Confirm 2 as OtherThing')).toBeInTheDocument();
+        expect(getByRole('combobox')).toHaveDisplayValue('OtherThing');
+      });
+    });
   });
 
   describe('handleChangePrimary()', () => {
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index 983751b74..da04ffbab 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -20,6 +20,7 @@ import {
 } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
 import { Confirmation } from 'src/components/common/Modal/Confirmation/Confirmation';
 import { PersonEmailAddressInput } from 'src/graphql/types.generated';
+import useGetAppSettings from 'src/hooks/useGetAppSettings';
 import theme from '../../../theme';
 import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
 import NoData from '../NoData';
@@ -115,7 +116,8 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
   accountListId,
   setContactFocus,
 }) => {
-  const [defaultSource, setDefaultSource] = useState('MPDX');
+  const { appName } = useGetAppSettings();
+  const [defaultSource, setDefaultSource] = useState(appName);
   const [deleteModalState, setDeleteModalState] = useState<ModalState | null>(
     null,
   );
@@ -361,7 +363,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
                         handleSourceChange(event)
                       }
                     >
-                      <option value="MPDX">MPDX</option>
+                      <option value={appName}>{appName}</option>
                       <option value="DataServer">DataServer</option>
                     </SourceSelect>
                     <ConfirmButton

From 06f2494cb46e4a3165f3fb1729cfd4e1f7e340de Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Fri, 26 Jul 2024 11:26:47 -0400
Subject: [PATCH 14/22] Bring over the logic from Angular surrounding bulkSave
 and the source chosen at the top of the page

---
 .../FixEmailAddresses.test.tsx                | 105 +++++++++++++++---
 .../FixEmailAddresses/FixEmailAddresses.tsx   | 102 +++++++++++------
 2 files changed, 158 insertions(+), 49 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 2ecb1ed7a..a36db5012 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -16,7 +16,11 @@ import {
 import useGetAppSettings from 'src/hooks/useGetAppSettings';
 import theme from '../../../theme';
 import { EmailAddressesMutation } from './AddEmailAddress.generated';
-import { FixEmailAddresses } from './FixEmailAddresses';
+import {
+  FixEmailAddresses,
+  PersonEmailAddresses,
+  determineBulkDataToSend,
+} from './FixEmailAddresses';
 import {
   contactId,
   contactOneEmailAddressNodes,
@@ -505,22 +509,15 @@ describe('FixPhoneNumbers-Home', () => {
       const cache = new InMemoryCache();
       const noPeopleMessage = 'No people with email addresses need attention';
 
-      const { getByRole, getByText, queryByTestId } = render(
-        <Components
-          mocks={{
-            GetInvalidEmailAddresses: {
-              people: {
-                nodes: mockInvalidEmailAddressesResponse,
-              },
-            },
-          }}
-          cache={cache}
-        />,
+      const { getByRole, getByText, getByTestId, queryByTestId } = render(
+        <Components cache={cache} />,
       );
 
-      await waitFor(() =>
-        expect(queryByTestId('loading')).not.toBeInTheDocument(),
-      );
+      await waitFor(() => {
+        expect(queryByTestId('loading')).not.toBeInTheDocument();
+        expect(getByTestId('starOutlineIcon-testid-1')).toBeInTheDocument();
+      });
+      userEvent.click(getByTestId('starOutlineIcon-testid-1'));
 
       const bulkConfirmButton = getByRole('button', {
         name: 'Confirm 2 as MPDX',
@@ -612,5 +609,83 @@ describe('FixPhoneNumbers-Home', () => {
         expect(getByText(personName2)).toBeVisible();
       });
     });
+
+    it('should not update if there is no email for the default source', async () => {
+      const cache = new InMemoryCache();
+      const noPrimaryEmailMessage =
+        'No DataServer primary email address exists to update';
+
+      const { getByRole, queryByTestId } = render(<Components cache={cache} />);
+
+      await waitFor(() => {
+        expect(queryByTestId('loading')).not.toBeInTheDocument();
+      });
+      userEvent.selectOptions(getByRole('combobox'), 'DataServer');
+
+      const bulkConfirmButton = getByRole('button', {
+        name: 'Confirm 2 as DataServer',
+      });
+      userEvent.click(bulkConfirmButton);
+      userEvent.click(getByRole('button', { name: 'Yes' }));
+
+      await waitFor(() => {
+        expect(mockEnqueue).toHaveBeenCalledWith(noPrimaryEmailMessage, {
+          variant: 'warning',
+          autoHideDuration: 7000,
+        });
+        expect(bulkConfirmButton).toBeVisible();
+      });
+    });
+  });
+
+  describe('determineBulkDataToSend', () => {
+    it('should set the first email of the given source to primary', () => {
+      const dataState = {
+        testid: {
+          emailAddresses: [
+            {
+              ...contactOneEmailAddressNodes[0],
+              primary: false,
+            },
+            {
+              ...contactOneEmailAddressNodes[1],
+            },
+            {
+              ...contactOneEmailAddressNodes[2],
+              primary: true,
+            },
+          ],
+        },
+      } as { [key: string]: PersonEmailAddresses };
+      const defaultSource = 'MPDX';
+
+      const dataToSend = determineBulkDataToSend(dataState, defaultSource);
+
+      const emails = dataToSend[0].emailAddresses ?? [];
+      expect(emails[0].primary).toEqual(true);
+      expect(emails[2].primary).toEqual(false);
+    });
+
+    it('should be empty if there is no email of the given source', () => {
+      const dataState = {
+        testid: {
+          emailAddresses: [
+            {
+              ...contactOneEmailAddressNodes[0],
+            },
+            {
+              ...contactOneEmailAddressNodes[1],
+            },
+            {
+              ...contactOneEmailAddressNodes[2],
+            },
+          ],
+        },
+      } as { [key: string]: PersonEmailAddresses };
+      const defaultSource = 'DataServer';
+
+      const dataToSend = determineBulkDataToSend(dataState, defaultSource);
+      expect(dataToSend.length).toEqual(0);
+    });
   });
 });
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index da04ffbab..e83999565 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -19,7 +19,10 @@ import {
   useUpdatePeopleMutation,
 } from 'src/components/Tool/FixEmailAddresses/FixEmailAddresses.generated';
 import { Confirmation } from 'src/components/common/Modal/Confirmation/Confirmation';
-import { PersonEmailAddressInput } from 'src/graphql/types.generated';
+import {
+  PersonEmailAddressInput,
+  PersonUpdateInput,
+} from 'src/graphql/types.generated';
 import useGetAppSettings from 'src/hooks/useGetAppSettings';
 import theme from '../../../theme';
 import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
@@ -112,6 +115,36 @@ interface FixEmailAddressesProps {
   setContactFocus: SetContactFocus;
 }
 
+export const determineBulkDataToSend = (
+  dataState: {
+    [key: string]: PersonEmailAddresses;
+  },
+  defaultSource: string,
+): PersonUpdateInput[] => {
+  const dataToSend = [] as PersonUpdateInput[];
+
+  Object.entries(dataState).forEach((value) => {
+    const primaryEmailAddress = value[1].emailAddresses.find(
+      (email) => email.source === defaultSource,
+    );
+    if (primaryEmailAddress) {
+      dataToSend.push({
+        id: value[0],
+        emailAddresses: value[1].emailAddresses.map(
+          (emailAddress) =>
+            ({
+              email: emailAddress.email,
+              id: emailAddress.id,
+              primary: emailAddress.id === primaryEmailAddress.id,
+              validValues: true,
+            } as PersonEmailAddressInput),
+        ),
+      });
+    }
+  });
+  return dataToSend;
+};
+
 export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
   accountListId,
   setContactFocus,
@@ -294,41 +327,42 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
   };
 
   const handleBulkConfirm = async () => {
-    await updatePeople({
-      variables: {
-        input: {
-          accountListId,
-          attributes: Object.entries(dataState).map((value) => ({
-            id: value[0],
-            emailAddresses: value[1].emailAddresses.map(
-              (emailAddress) =>
-                ({
-                  email: emailAddress.email,
-                  id: emailAddress.id,
-                  primary: emailAddress.primary,
-                  validValues: true,
-                } as PersonEmailAddressInput),
-            ),
-          })),
+    const dataToSend = determineBulkDataToSend(dataState, defaultSource ?? '');
+
+    if (dataToSend.length) {
+      await updatePeople({
+        variables: {
+          input: {
+            accountListId,
+            attributes: dataToSend,
+          },
         },
-      },
-      update: (cache) => {
-        data?.people.nodes.forEach((person) => {
-          cache.evict({ id: `Person:${person.id}` });
-        });
-      },
-      onCompleted: () => {
-        enqueueSnackbar(t(`Successfully updated email addresses`), {
-          variant: 'success',
-        });
-      },
-      onError: () => {
-        enqueueSnackbar(t(`Error updating email addresses`), {
-          variant: 'error',
+        update: (cache) => {
+          data?.people.nodes.forEach((person) => {
+            cache.evict({ id: `Person:${person.id}` });
+          });
+        },
+        onCompleted: () => {
+          enqueueSnackbar(t(`Successfully updated email addresses`), {
+            variant: 'success',
+          });
+        },
+        onError: () => {
+          enqueueSnackbar(t(`Error updating email addresses`), {
+            variant: 'error',
+            autoHideDuration: 7000,
+          });
+        },
+      });
+    } else {
+      enqueueSnackbar(
+        t(`No ${defaultSource} primary email address exists to update`),
+        {
+          variant: 'warning',
           autoHideDuration: 7000,
-        });
-      },
-    });
+        },
+      );
+    }
   };
 
   return (

From 20e3f4737b4787775622d8da28cfea9bfc0caf87 Mon Sep 17 00:00:00 2001
From: Daniel Bisgrove <daniel@bizz-websites.com>
Date: Thu, 8 Aug 2024 10:45:10 -0400
Subject: [PATCH 15/22] Fixing tests to check for appName and ensuring all
 instances of "MPDX" are replaced with appName

---
 .../FixEmailAddressPerson/FixEmailAddressPerson.tsx |  2 +-
 .../FixEmailAddresses/FixEmailAddresses.test.tsx    | 13 +++++++------
 2 files changed, 8 insertions(+), 7 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
index d29f40c5b..2d02bb4a5 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.tsx
@@ -362,7 +362,7 @@ export const FixEmailAddressPerson: React.FC<FixEmailAddressPersonProps> = ({
                               <strong>{t('Source')}: </strong>
                             </Typography>
                           </Hidden>
-                          <Typography display="inline">MPDX</Typography>
+                          <Typography display="inline">{appName}</Typography>
                         </Box>
                       </Box>
                     </RowWrapper>
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 620a9b34d..4206e017c 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -85,6 +85,11 @@ const Components = ({ mocks = defaultGraphQLMock }: ComponentsProps) => (
 );
 
 describe('FixEmailAddresses-Home', () => {
+  beforeEach(() => {
+    (useGetAppSettings as jest.Mock).mockReturnValue({
+      appName: 'MPDX',
+    });
+  });
   it('default with test data', async () => {
     const { getByText, getByTestId, queryByTestId } = render(<Components />);
 
@@ -106,15 +111,11 @@ describe('FixEmailAddresses-Home', () => {
   });
 
   it('should show the app name as a source value', async () => {
-    (useGetAppSettings as jest.Mock).mockReturnValue({
-      appName: 'OtherThing',
-    });
-
     const { getByRole, getByText } = render(<Components />);
     await waitFor(() => {
       expect(getByText('Fix Email Addresses')).toBeInTheDocument();
-      expect(getByText('Confirm 2 as OtherThing')).toBeInTheDocument();
-      expect(getByRole('combobox')).toHaveDisplayValue('OtherThing');
+      expect(getByText('Confirm 2 as MPDX')).toBeInTheDocument();
+      expect(getByRole('combobox')).toHaveDisplayValue('MPDX');
     });
   });
 

From 13bac5f89ebeffc67b485bd5b6ca46fd5f046c83 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Thu, 8 Aug 2024 17:23:58 -0400
Subject: [PATCH 16/22] Fix test

---
 .../FixEmailAddressPerson/FixEmailAddressPerson.test.tsx  | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
index 7b6634135..d7b4a9963 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressPerson/FixEmailAddressPerson.test.tsx
@@ -7,6 +7,7 @@ import { SnackbarProvider } from 'notistack';
 import TestWrapper from '__tests__/util/TestWrapper';
 import { GqlMockedProvider } from '__tests__/util/graphqlMocking';
 import { render, waitFor } from '__tests__/util/testingLibraryReactMock';
+import useGetAppSettings from 'src/hooks/useGetAppSettings';
 import theme from 'src/theme';
 import { EmailAddressesMutation } from '../AddEmailAddress.generated';
 import { EmailAddressData, PersonEmailAddresses } from '../FixEmailAddresses';
@@ -48,6 +49,7 @@ const mutationSpy = jest.fn();
 const handleSingleConfirm = jest.fn();
 const mockEnqueue = jest.fn();
 
+jest.mock('src/hooks/useGetAppSettings');
 jest.mock('notistack', () => ({
   // eslint-disable-next-line @typescript-eslint/ban-ts-comment
   // @ts-ignore
@@ -105,6 +107,12 @@ const TestComponent = ({
 };
 
 describe('FixEmailAddressPerson', () => {
+  beforeEach(() => {
+    (useGetAppSettings as jest.Mock).mockReturnValue({
+      appName: 'MPDX',
+    });
+  });
+
   it('default', () => {
     const { getByText, getByTestId, getByDisplayValue } = render(
       <TestComponent

From 5ee124264bd2a1a928a7cec4d52181d037004686 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Fri, 9 Aug 2024 09:38:13 -0400
Subject: [PATCH 17/22] Garbage collect the cache after evicting

---
 src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index 61da26171..f88eb4a01 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -278,6 +278,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
           data?.people.nodes.forEach((person) => {
             cache.evict({ id: `Person:${person.id}` });
           });
+          cache.gc();
         },
         onCompleted: () => {
           enqueueSnackbar(t(`Successfully updated email addresses`), {

From cd0f05d230628e973ea68471adcab8cc9460590f Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Fri, 9 Aug 2024 09:55:09 -0400
Subject: [PATCH 18/22] Add TODO for future improvements

---
 src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index f88eb4a01..86f22c061 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -109,6 +109,7 @@ interface FixEmailAddressesProps {
   setContactFocus: SetContactFocus;
 }
 
+//TODO: Try to make bulk confirm logic more similar across tools, perhaps we can factor out a common function
 export const determineBulkDataToSend = (
   dataState: {
     [key: string]: PersonEmailAddresses;

From 3717840098553a94ed65b7bfe26aeef036b039ec Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Fri, 9 Aug 2024 14:13:54 -0400
Subject: [PATCH 19/22] Make the source dropdown dynamic and styled the same as
 fix mailing addresses

---
 .../FixEmailAddresses.test.tsx                | 54 ++++++++++--
 .../FixEmailAddresses/FixEmailAddresses.tsx   | 82 ++++++++++---------
 .../FixEmailAddressesMocks.ts                 |  2 +-
 3 files changed, 92 insertions(+), 46 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 4206e017c..7f4456284 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -115,7 +115,7 @@ describe('FixEmailAddresses-Home', () => {
     await waitFor(() => {
       expect(getByText('Fix Email Addresses')).toBeInTheDocument();
       expect(getByText('Confirm 2 as MPDX')).toBeInTheDocument();
-      expect(getByRole('combobox')).toHaveDisplayValue('MPDX');
+      expect(getByRole('combobox')).toHaveTextContent('MPDX');
     });
   });
 
@@ -508,17 +508,59 @@ describe('FixEmailAddresses-Home', () => {
 
     it('should not update if there is no email for the default source', async () => {
       const noPrimaryEmailMessage =
-        'No DataServer primary email address exists to update';
+        'No MPDX primary email address exists to update';
 
-      const { getByRole, queryByTestId } = render(<Components />);
+      const { getByRole, queryByTestId } = render(
+        <Components
+          mocks={{
+            GetInvalidEmailAddresses: {
+              people: {
+                nodes: [
+                  {
+                    ...mockInvalidEmailAddressesResponse[0],
+                    emailAddresses: {
+                      nodes: [
+                        {
+                          ...contactOneEmailAddressNodes[0],
+                          source: 'DataServer',
+                        },
+                        {
+                          ...contactOneEmailAddressNodes[1],
+                          source: 'DonorHub',
+                        },
+                      ],
+                    },
+                  },
+                  {
+                    ...mockInvalidEmailAddressesResponse[1],
+                    emailAddresses: {
+                      nodes: [
+                        {
+                          ...contactOneEmailAddressNodes[0],
+                          source: 'DataServer',
+                        },
+                        {
+                          ...contactOneEmailAddressNodes[1],
+                          source: 'DonorHub',
+                        },
+                      ],
+                    },
+                  },
+                ],
+              },
+            },
+          }}
+        />,
+      );
 
       await waitFor(() => {
         expect(queryByTestId('loading')).not.toBeInTheDocument();
       });
-      userEvent.selectOptions(getByRole('combobox'), 'DataServer');
+      userEvent.click(getByRole('combobox'));
+      userEvent.click(getByRole('option', { name: 'MPDX' }));
 
       const bulkConfirmButton = getByRole('button', {
-        name: 'Confirm 2 as DataServer',
+        name: 'Confirm 2 as MPDX',
       });
       userEvent.click(bulkConfirmButton);
       userEvent.click(getByRole('button', { name: 'Yes' }));
@@ -577,7 +619,7 @@ describe('FixEmailAddresses-Home', () => {
           ],
         },
       } as { [key: string]: PersonEmailAddresses };
-      const defaultSource = 'DataServer';
+      const defaultSource = 'DonorHub';
 
       const dataToSend = determineBulkDataToSend(dataState, defaultSource);
       expect(dataToSend.length).toEqual(0);
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index 86f22c061..ad32f5267 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -5,7 +5,9 @@ import {
   CircularProgress,
   Divider,
   Grid,
-  NativeSelect,
+  MenuItem,
+  Select,
+  SelectChangeEvent,
   Typography,
 } from '@mui/material';
 import { styled } from '@mui/material/styles';
@@ -23,11 +25,9 @@ import {
   PersonEmailAddressInput,
   PersonUpdateInput,
 } from 'src/graphql/types.generated';
-import useGetAppSettings from 'src/hooks/useGetAppSettings';
 import theme from '../../../theme';
 import { ConfirmButtonIcon } from '../ConfirmButtonIcon';
 import NoData from '../NoData';
-import { StyledInput } from '../StyledInput';
 import { FixEmailAddressPerson } from './FixEmailAddressPerson/FixEmailAddressPerson';
 
 const Container = styled(Box)(() => ({
@@ -48,7 +48,7 @@ const FixEmailAddressesWrapper = styled(Grid)(() => ({
   },
 }));
 
-const SourceSelect = styled(NativeSelect)(() => ({
+const SourceSelect = styled(Select)(() => ({
   minWidth: theme.spacing(20),
   width: '10%',
   marginLeft: theme.spacing(2),
@@ -144,7 +144,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
   accountListId,
   setContactFocus,
 }) => {
-  const { appName } = useGetAppSettings();
+  const appName = process.env.APP_NAME ?? 'MPDX';
   const [defaultSource, setDefaultSource] = useState(appName);
   const [showBulkConfirmModal, setShowBulkConfirmModal] = useState(false);
   const { t } = useTranslation();
@@ -159,33 +159,38 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
   const [dataState, setDataState] = useState<{
     [key: string]: PersonEmailAddresses;
   }>({});
+  const [sourceOptions, setSourceOptions] = useState<string[]>([appName]);
 
   // Create a mutable copy of the query data and store in the state
-  useEffect(
-    () =>
-      setDataState(
-        data
-          ? data.people.nodes?.reduce<{ [key: string]: PersonEmailAddresses }>(
-              (map, person) => ({
-                ...map,
-                [person.id]: {
-                  emailAddresses: person.emailAddresses.nodes.map(
-                    (emailAddress) => ({
-                      id: emailAddress.id,
-                      primary: emailAddress.primary,
-                      updatedAt: emailAddress.updatedAt,
-                      source: emailAddress.source,
-                      email: emailAddress.email,
-                    }),
-                  ),
+  useEffect(() => {
+    const existingSources = new Set<string>();
+    existingSources.add(appName);
+
+    const newDataState = data
+      ? data.people.nodes?.reduce<{ [key: string]: PersonEmailAddresses }>(
+          (map, person) => ({
+            ...map,
+            [person.id]: {
+              emailAddresses: person.emailAddresses.nodes.map(
+                (emailAddress) => {
+                  existingSources.add(emailAddress.source);
+                  return {
+                    id: emailAddress.id,
+                    primary: emailAddress.primary,
+                    updatedAt: emailAddress.updatedAt,
+                    source: emailAddress.source,
+                    email: emailAddress.email,
+                  };
                 },
-              }),
-              {},
-            )
-          : {},
-      ),
-    [loading, data],
-  );
+              ),
+            },
+          }),
+          {},
+        )
+      : {};
+    setDataState(newDataState);
+    setSourceOptions([...existingSources]);
+  }, [loading, data]);
 
   // Update the state with the textfield's value
   const handleChange = (
@@ -212,10 +217,8 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
     setDataState(temp);
   };
 
-  const handleSourceChange = (
-    event: React.ChangeEvent<HTMLSelectElement>,
-  ): void => {
-    setDefaultSource(event.target.value);
+  const handleSourceChange = (event: SelectChangeEvent<unknown>): void => {
+    setDefaultSource(event.target.value as string);
   };
 
   const handleSingleConfirm = async (
@@ -330,14 +333,15 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
                     <Typography>{t('Default Primary Source:')}</Typography>
 
                     <SourceSelect
-                      input={<StyledInput />}
                       value={defaultSource}
-                      onChange={(event: React.ChangeEvent<HTMLSelectElement>) =>
-                        handleSourceChange(event)
-                      }
+                      onChange={handleSourceChange}
+                      size="small"
                     >
-                      <option value={appName}>{appName}</option>
-                      <option value="DataServer">DataServer</option>
+                      {sourceOptions.map((source) => (
+                        <MenuItem key={source} value={source}>
+                          {source}
+                        </MenuItem>
+                      ))}
                     </SourceSelect>
                     <ConfirmButton
                       onClick={() => setShowBulkConfirmModal(true)}
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts b/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts
index 6ab77b75c..5cca43e5b 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddressesMocks.ts
@@ -26,7 +26,7 @@ export const contactOneEmailAddressNodes = [
     updatedAt: new Date('2021-06-21T03:40:05-06:00').toISOString(),
     email: 'email2@gmail.com',
     primary: false,
-    source: 'MPDX',
+    source: 'DataServer',
   },
   {
     __typename: 'EmailAddress',

From 41b5ab9bb119b7e130aaec0a11a5639df0ae81c4 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Fri, 9 Aug 2024 14:28:44 -0400
Subject: [PATCH 20/22] Add dynamic source loading for mailing addresses as
 well

---
 .../FixMailingAddresses/FixMailingAddresses.tsx | 17 ++++++++++++++---
 1 file changed, 14 insertions(+), 3 deletions(-)

diff --git a/src/components/Tool/FixMailingAddresses/FixMailingAddresses.tsx b/src/components/Tool/FixMailingAddresses/FixMailingAddresses.tsx
index 1efb9afb5..8878dc6c1 100644
--- a/src/components/Tool/FixMailingAddresses/FixMailingAddresses.tsx
+++ b/src/components/Tool/FixMailingAddresses/FixMailingAddresses.tsx
@@ -1,4 +1,4 @@
-import React, { useCallback, useState } from 'react';
+import React, { useCallback, useEffect, useState } from 'react';
 import { ApolloCache } from '@apollo/client';
 import { mdiCheckboxMarkedCircle } from '@mdi/js';
 import { Icon } from '@mdi/react';
@@ -128,8 +128,6 @@ enum ModalEnum {
   Edit = 'Edit',
 }
 
-const sourceOptions = [appName, 'DataServer'];
-
 const FixMailingAddresses: React.FC<Props> = ({
   accountListId,
   setContactFocus,
@@ -142,6 +140,7 @@ const FixMailingAddresses: React.FC<Props> = ({
   const [selectedContactId, setSelectedContactId] = useState('');
   const [defaultSource, setDefaultSource] = useState(appName);
   const [openBulkConfirmModal, setOpenBulkConfirmModal] = useState(false);
+  const [sourceOptions, setSourceOptions] = useState<string[]>([appName]);
 
   const { data, loading } = useInvalidAddressesQuery({
     variables: { accountListId },
@@ -149,6 +148,18 @@ const FixMailingAddresses: React.FC<Props> = ({
   const [updateAddress] = useUpdateContactAddressMutation();
   const { enqueueSnackbar } = useSnackbar();
 
+  useEffect(() => {
+    const existingSources = new Set<string>();
+    existingSources.add(appName);
+
+    data?.contacts.nodes.forEach((contact) => {
+      contact.addresses.nodes.forEach((address) => {
+        existingSources.add(address.source);
+      });
+    });
+    setSourceOptions([...existingSources]);
+  }, [loading, data]);
+
   const handleSingleConfirm = async ({
     addresses,
     id,

From c6843ce0ae1d48b31d45c887fdb85639326b3e80 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Mon, 12 Aug 2024 10:19:54 -0400
Subject: [PATCH 21/22] Add logic to capture bad data, following the example of
 fix mailing address

---
 .../FixEmailAddresses/FixEmailAddresses.test.tsx     | 12 ++++++++++--
 .../Tool/FixEmailAddresses/FixEmailAddresses.tsx     | 11 +++++++++--
 2 files changed, 19 insertions(+), 4 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
index 7f4456284..2a8187c05 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.test.tsx
@@ -596,7 +596,11 @@ describe('FixEmailAddresses-Home', () => {
       } as { [key: string]: PersonEmailAddresses };
       const defaultSource = 'MPDX';
 
-      const dataToSend = determineBulkDataToSend(dataState, defaultSource);
+      const dataToSend = determineBulkDataToSend(
+        dataState,
+        defaultSource,
+        'MPDX',
+      );
 
       const emails = dataToSend[0].emailAddresses ?? [];
       expect(emails[0].primary).toEqual(true);
@@ -621,7 +625,11 @@ describe('FixEmailAddresses-Home', () => {
       } as { [key: string]: PersonEmailAddresses };
       const defaultSource = 'DonorHub';
 
-      const dataToSend = determineBulkDataToSend(dataState, defaultSource);
+      const dataToSend = determineBulkDataToSend(
+        dataState,
+        defaultSource,
+        'MPDX',
+      );
       expect(dataToSend.length).toEqual(0);
     });
   });
diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index ad32f5267..ab06459ba 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -115,12 +115,15 @@ export const determineBulkDataToSend = (
     [key: string]: PersonEmailAddresses;
   },
   defaultSource: string,
+  appName: string,
 ): PersonUpdateInput[] => {
   const dataToSend = [] as PersonUpdateInput[];
 
   Object.entries(dataState).forEach((value) => {
     const primaryEmailAddress = value[1].emailAddresses.find(
-      (email) => email.source === defaultSource,
+      (email) =>
+        email.source === defaultSource ||
+        (defaultSource === appName && email.source === 'MPDX'),
     );
     if (primaryEmailAddress) {
       dataToSend.push({
@@ -268,7 +271,11 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
   };
 
   const handleBulkConfirm = async () => {
-    const dataToSend = determineBulkDataToSend(dataState, defaultSource ?? '');
+    const dataToSend = determineBulkDataToSend(
+      dataState,
+      defaultSource ?? '',
+      appName,
+    );
 
     if (dataToSend.length) {
       await updatePeople({

From 56503c7359dbf1bce6e170b71abfce4cca8669e4 Mon Sep 17 00:00:00 2001
From: Bill Randall <william.randall@cru.org>
Date: Mon, 12 Aug 2024 11:08:00 -0400
Subject: [PATCH 22/22] Simplify

---
 .../Tool/FixEmailAddresses/FixEmailAddresses.tsx          | 8 +-------
 1 file changed, 1 insertion(+), 7 deletions(-)

diff --git a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
index ab06459ba..22b16e6f8 100644
--- a/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
+++ b/src/components/Tool/FixEmailAddresses/FixEmailAddresses.tsx
@@ -177,13 +177,7 @@ export const FixEmailAddresses: React.FC<FixEmailAddressesProps> = ({
               emailAddresses: person.emailAddresses.nodes.map(
                 (emailAddress) => {
                   existingSources.add(emailAddress.source);
-                  return {
-                    id: emailAddress.id,
-                    primary: emailAddress.primary,
-                    updatedAt: emailAddress.updatedAt,
-                    source: emailAddress.source,
-                    email: emailAddress.email,
-                  };
+                  return { ...emailAddress };
                 },
               ),
             },