From f29240b0b2fe08e0a5361d59554c10e3d3560dd4 Mon Sep 17 00:00:00 2001 From: R Ranathunga Date: Wed, 2 Oct 2024 07:02:19 -0700 Subject: [PATCH] feat: add success and failure toasts --- .../AnalystDashboard/AssignmentEmailModal.tsx | 18 +++- app/components/LoadingSpinner.tsx | 6 +- app/components/Modal.tsx | 7 +- .../AssessmentAssignmentTable.test.tsx | 82 ++++++++++++++++++- 4 files changed, 103 insertions(+), 10 deletions(-) diff --git a/app/components/AnalystDashboard/AssignmentEmailModal.tsx b/app/components/AnalystDashboard/AssignmentEmailModal.tsx index a60b0e512..b53b3448e 100644 --- a/app/components/AnalystDashboard/AssignmentEmailModal.tsx +++ b/app/components/AnalystDashboard/AssignmentEmailModal.tsx @@ -1,6 +1,8 @@ import Modal from 'components/Modal'; import * as Sentry from '@sentry/nextjs'; import { useCreateEmailNotificationsMutation } from 'schema/mutations/application/createEmailNotifications'; +import { useToast } from 'components/AppProvider'; +import { useState } from 'react'; interface Props { isOpen: boolean; @@ -24,6 +26,8 @@ const AssignmentEmailModal: React.FC = ({ assignments = [], }) => { const [createEmailNotifications] = useCreateEmailNotificationsMutation(); + const [isLoading, setIsLoading] = useState(false); + const { showToast, hideToast } = useToast(); const analystsToNotify = Array.from( new Set(assignments.map((assignment) => assignment.assignedTo)) @@ -63,6 +67,8 @@ const AssignmentEmailModal: React.FC = ({ }; const notifyAnalysts = async () => { + setIsLoading(true); + hideToast(); try { const response = await fetch('/api/email/assessmentAssigneeChange', { method: 'POST', @@ -80,11 +86,20 @@ const AssignmentEmailModal: React.FC = ({ emailRecordResults, Object.values(details.assessmentsGrouped) ); + showToast('Email notification sent successfully', 'success', 5000); } catch (error) { Sentry.captureException({ name: 'Notify Analysts Error', message: error.message, }); + showToast( + 'Email notification did not work, please try again', + 'error', + 5000 + ); + } finally { + setIsLoading(false); + onSave(); } }; @@ -97,16 +112,17 @@ const AssignmentEmailModal: React.FC = ({ actions={[ { id: 'email-confirm-btn', + isLoading, label: saveLabel, onClick: async () => { notifyAnalysts(); - onSave(); }, }, { id: 'email-cancel-btn', label: cancelLabel, onClick: () => onCancel(), + disabled: isLoading, variant: 'secondary', }, ]} diff --git a/app/components/LoadingSpinner.tsx b/app/components/LoadingSpinner.tsx index 40c47abd8..8f8477cc0 100644 --- a/app/components/LoadingSpinner.tsx +++ b/app/components/LoadingSpinner.tsx @@ -1,7 +1,7 @@ -const LoadingSpinner = ({ color = '#fff' }) => ( +const LoadingSpinner = ({ color = '#fff', width = '24', height = '24' }) => ( = ({ @@ -67,9 +69,10 @@ const Modal: React.FC = ({ key={action.label} onClick={action.onClick} variant={action.variant || 'primary'} - disabled={action.disabled} + disabled={action.disabled || action.isLoading} > - {action.label} + {action.label}{' '} + {action.isLoading && } ))} diff --git a/app/tests/components/AnalystDashboard/AssessmentAssignmentTable.test.tsx b/app/tests/components/AnalystDashboard/AssessmentAssignmentTable.test.tsx index b8dbeb7f7..ece54ff8a 100644 --- a/app/tests/components/AnalystDashboard/AssessmentAssignmentTable.test.tsx +++ b/app/tests/components/AnalystDashboard/AssessmentAssignmentTable.test.tsx @@ -1,4 +1,4 @@ -import { act, fireEvent, screen } from '@testing-library/react'; +import { act, fireEvent, screen, waitFor } from '@testing-library/react'; import { graphql } from 'react-relay'; import ComponentTestingHelper from 'tests/utils/componentTestingHelper'; import AssessmentAssignmentTable, { @@ -552,10 +552,38 @@ describe('The AssessmentAssignmentTable component', () => { expect(notifyButton).toBeDisabled(); }); - it('should call correct endpoint when send email notifications confirmed', async () => { + it('should call the correct endpoint when email notifications are confirmed', async () => { global.fetch = jest.fn(() => Promise.resolve({ - json: () => Promise.resolve({}), + json: () => + Promise.resolve({ + success: true, + emailRecordResults: [ + { + messageId: '123', + to: ['tester@gov.bc.ca'], + cc: ['tester@gov.bc.ca'], + contexts: {}, + body: 'Test Analyst GIS has assigned you one or more assessment(s)', + subject: 'Assessment Assignee Change Notification', + }, + ], + details: { + assessmentsGrouped: { + 'analyst@gov.bc.ca': [ + { + ccbcNumber: 'CCBC-00001', + applicationId: 123, + notificationConnectionId: 'connectionID', + updatedBy: 1, + assignedTo: 'Analyst GIS', + assessmentType: 'technical', + }, + ], + }, + }, + }), + status: 200, }) ) as jest.Mock; @@ -580,10 +608,56 @@ describe('The AssessmentAssignmentTable component', () => { fireEvent.click(confirmBtn); }); - expect(fetch).toHaveBeenCalledWith( + expect(global.fetch).toHaveBeenCalledWith( '/api/email/assessmentAssigneeChange', expect.objectContaining({ method: 'POST' }) ); + + await waitFor(() => { + expect( + screen.getByText(/Email notification sent successfully/) + ).toBeInTheDocument(); + }); + }); + + it('should show the error toast when email notification to CHES failed', async () => { + global.fetch = jest.fn(() => + Promise.resolve({ + json: () => Promise.reject(new Error('oops')), + }) + ) as jest.Mock; + + componentTestingHelper.loadQuery(); + componentTestingHelper.renderComponent(); + + const notifyButton = screen.getByRole('button', { + name: 'Notify by email', + }); + + await act(async () => { + fireEvent.click(notifyButton); + }); + + const confirmBtn = screen.getByRole('button', { + name: 'Yes', + }); + + expect(confirmBtn).toBeInTheDocument(); + + await act(async () => { + fireEvent.click(confirmBtn); + }); + + expect(global.fetch).toHaveBeenCalledWith( + '/api/email/assessmentAssigneeChange', + expect.objectContaining({ method: 'POST' }) + ); + + await waitFor(() => { + expect( + screen.getByText(/Email notification did not work, please try again/) + ).toBeInTheDocument(); + }); }); });