diff --git a/website/src/components/Edit/EditPage.tsx b/website/src/components/Edit/EditPage.tsx index d76c28fcd..66a677e1d 100644 --- a/website/src/components/Edit/EditPage.tsx +++ b/website/src/components/Edit/EditPage.tsx @@ -1,5 +1,6 @@ import { sentenceCase, snakeCase } from 'change-case'; import { type Dispatch, type FC, Fragment, type SetStateAction, useMemo, useRef, useState } from 'react'; +import { toast } from 'react-toastify'; import { EditableDataRow, ProcessedDataRow } from './DataRow.tsx'; import type { Row } from './InputField.tsx'; @@ -21,7 +22,6 @@ import { getAccessionVersionString } from '../../utils/extractAccessionVersion.t import { ConfirmationDialog } from '../DeprecatedConfirmationDialog.tsx'; import { BoxWithTabsBox, BoxWithTabsTab, BoxWithTabsTabBar } from '../common/BoxWithTabs.tsx'; import { FixedLengthTextViewer } from '../common/FixedLengthTextViewer.tsx'; -import { ManagedErrorFeedback, useErrorFeedbackState } from '../common/ManagedErrorFeedback.tsx'; import { withQueryProvider } from '../common/withQueryProvider.tsx'; type EditPageProps = { @@ -82,7 +82,6 @@ const InnerEditPage: FC = ({ const [editedSequences, setEditedSequences] = useState(mapSequencesToRow(dataToEdit)); const [processedSequenceTab, setProcessedSequenceTab] = useState(0); - const { errorMessage, isErrorOpen, openErrorFeedback, closeErrorFeedback } = useErrorFeedbackState(); const dialogRef = useRef(null); const isCreatingRevision = dataToEdit.status === approvedForReleaseStatus; @@ -92,7 +91,7 @@ const InnerEditPage: FC = ({ clientConfig, accessToken, dataToEdit, - openErrorFeedback, + (message) => toast.error(message, { position: 'top-center', autoClose: false }), ); const { mutate: submitEdit, isLoading: isEditLoading } = useSubmitEdit( @@ -100,7 +99,7 @@ const InnerEditPage: FC = ({ clientConfig, accessToken, dataToEdit, - openErrorFeedback, + (message) => toast.error(message, { position: 'top-center', autoClose: false }), ); const handleOpenConfirmationDialog = () => { @@ -136,7 +135,6 @@ const InnerEditPage: FC = ({ return ( <> -

{isCreatingRevision ? 'Create new revision from' : 'Edit'} {dataToEdit.accession}. diff --git a/website/src/components/ReviewPage/ReviewPage.tsx b/website/src/components/ReviewPage/ReviewPage.tsx index 35accf991..5e5d0ad25 100644 --- a/website/src/components/ReviewPage/ReviewPage.tsx +++ b/website/src/components/ReviewPage/ReviewPage.tsx @@ -1,6 +1,7 @@ import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'; import Pagination from '@mui/material/Pagination'; import { type ChangeEvent, type FC, useState } from 'react'; +import { toast } from 'react-toastify'; import { ReviewCard } from './ReviewCard.tsx'; import { useSubmissionOperations } from '../../hooks/useSubmissionOperations.ts'; @@ -21,7 +22,6 @@ import { import { type ClientConfig } from '../../types/runtimeConfig.ts'; import { displayConfirmationDialog } from '../ConfirmationDialog.tsx'; import { getLastApprovalTimeKey } from '../SearchPage/RecentSequencesBanner.tsx'; -import { ManagedErrorFeedback, useErrorFeedbackState } from '../common/ManagedErrorFeedback.tsx'; import { withQueryProvider } from '../common/withQueryProvider.tsx'; import BiTrash from '~icons/bi/trash'; import IwwaArrowDown from '~icons/iwwa/arrow-down'; @@ -70,11 +70,9 @@ const NumberAndVisibility = ({ }; const InnerReviewPage: FC = ({ clientConfig, organism, group, accessToken }) => { - const { errorMessage, isErrorOpen, openErrorFeedback, closeErrorFeedback } = useErrorFeedbackState(); - const [pageQuery, setPageQuery] = useState({ page: 1, size: pageSizeOptions[2] }); - const hooks = useSubmissionOperations(organism, group, clientConfig, accessToken, openErrorFeedback, pageQuery); + const hooks = useSubmissionOperations(organism, group, clientConfig, accessToken, toast.error, pageQuery); const showErrors = hooks.includedStatuses.includes(hasErrorsStatus); const showUnprocessed = @@ -333,25 +331,22 @@ const InnerReviewPage: FC = ({ clientConfig, organism, group, a ); return ( - <> - -
-
-
- {controlPanel} - {bulkActionButtons} -
-
+
+
+
+ {controlPanel} + {bulkActionButtons}
- {reviewCards} - {pagination} +
- + {reviewCards} + {pagination} +
); }; diff --git a/website/src/components/SeqSetCitations/SeqSetForm.tsx b/website/src/components/SeqSetCitations/SeqSetForm.tsx index 11acf18fd..00b2384a8 100644 --- a/website/src/components/SeqSetCitations/SeqSetForm.tsx +++ b/website/src/components/SeqSetCitations/SeqSetForm.tsx @@ -1,6 +1,7 @@ import { AxiosError } from 'axios'; import { capitalCase } from 'change-case'; import { type FC, type FormEvent, useState, useEffect } from 'react'; +import { toast } from 'react-toastify'; import { getClientLogger } from '../../clientLogger'; import { routes } from '../../routes/routes.ts'; @@ -9,7 +10,6 @@ import type { ClientConfig } from '../../types/runtimeConfig'; import { type SeqSet, type SeqSetRecord } from '../../types/seqSetCitation'; import { createAuthorizationHeader } from '../../utils/createAuthorizationHeader'; import { deserializeAccessionInput, serializeSeqSetRecords } from '../../utils/parseAccessionInput'; -import { ManagedErrorFeedback, useErrorFeedbackState } from '../common/ManagedErrorFeedback'; const logger = getClientLogger('SeqSetForm'); @@ -30,17 +30,10 @@ export const SeqSetForm: FC = ({ clientConfig, accessToken, edi ); const [seqSetRecordValidation, setSeqSetRecordValidation] = useState(''); - const { - errorMessage: serverErrorMessage, - isErrorOpen, - openErrorFeedback, - closeErrorFeedback, - } = useErrorFeedbackState(); - const { createSeqSet, updateSeqSet, validateSeqSetRecords, isLoading } = useActionHooks( clientConfig, accessToken, - openErrorFeedback, + (message) => toast.error(message, { position: 'top-center', autoClose: false }), setSeqSetRecordValidation, ); @@ -143,7 +136,6 @@ export const SeqSetForm: FC = ({ clientConfig, accessToken, edi return (
-

{`${editSeqSet ? 'Edit' : 'Create a'} SeqSet`}

diff --git a/website/src/components/SeqSetCitations/SeqSetItem.tsx b/website/src/components/SeqSetCitations/SeqSetItem.tsx index 16dd7fb30..0a6ce0265 100644 --- a/website/src/components/SeqSetCitations/SeqSetItem.tsx +++ b/website/src/components/SeqSetCitations/SeqSetItem.tsx @@ -1,6 +1,7 @@ import MUIPagination from '@mui/material/Pagination'; import { AxiosError } from 'axios'; import { type FC, useState } from 'react'; +import { toast } from 'react-toastify'; import { CitationPlot } from './CitationPlot'; import { getClientLogger } from '../../clientLogger'; @@ -9,7 +10,6 @@ import type { ClientConfig } from '../../types/runtimeConfig'; import { type SeqSetRecord, type SeqSet, type CitedByResult, SeqSetRecordType } from '../../types/seqSetCitation'; import { createAuthorizationHeader } from '../../utils/createAuthorizationHeader'; import { displayConfirmationDialog } from '../ConfirmationDialog.tsx'; -import { ManagedErrorFeedback, useErrorFeedbackState } from '../common/ManagedErrorFeedback'; import { withQueryProvider } from '../common/withQueryProvider.tsx'; const logger = getClientLogger('SeqSetItem'); @@ -77,7 +77,6 @@ const SeqSetItemInner: FC = ({ citedByData, isAdminView = false, }) => { - const { errorMessage, isErrorOpen, openErrorFeedback, closeErrorFeedback } = useErrorFeedbackState(); const [page, setPage] = useState(1); const sequencesPerPage = 10; @@ -86,7 +85,7 @@ const SeqSetItemInner: FC = ({ accessToken, seqSet.seqSetId, seqSet.seqSetVersion, - openErrorFeedback, + (message) => toast.error(message, { position: 'top-center', autoClose: false }), ); const handleCreateDOI = async () => { @@ -139,7 +138,6 @@ const SeqSetItemInner: FC = ({ return (
-

{seqSet.name}

diff --git a/website/src/components/SeqSetCitations/SeqSetItemActions.tsx b/website/src/components/SeqSetCitations/SeqSetItemActions.tsx index adeefa691..fd087e233 100644 --- a/website/src/components/SeqSetCitations/SeqSetItemActions.tsx +++ b/website/src/components/SeqSetCitations/SeqSetItemActions.tsx @@ -1,4 +1,5 @@ import { type FC, useState } from 'react'; +import { toast } from 'react-toastify'; import { ExportSeqSet } from './ExportSeqSet'; import { SeqSetForm } from './SeqSetForm'; @@ -9,7 +10,6 @@ import type { ClientConfig } from '../../types/runtimeConfig'; import type { SeqSetRecord, SeqSet } from '../../types/seqSetCitation'; import { createAuthorizationHeader } from '../../utils/createAuthorizationHeader'; import { displayConfirmationDialog } from '../ConfirmationDialog.tsx'; -import { ManagedErrorFeedback, useErrorFeedbackState } from '../common/ManagedErrorFeedback'; import Modal from '../common/Modal'; import { withQueryProvider } from '../common/withQueryProvider.tsx'; @@ -32,7 +32,6 @@ const SeqSetItemActionsInner: FC = ({ }) => { const [editModalVisible, setEditModalVisible] = useState(false); const [exportModalVisible, setExportModalVisible] = useState(false); - const { errorMessage, isErrorOpen, openErrorFeedback, closeErrorFeedback } = useErrorFeedbackState(); const isClient = useClientFlag(); const { mutate: deleteSeqSet } = useDeleteSeqSetAction( @@ -40,7 +39,7 @@ const SeqSetItemActionsInner: FC = ({ accessToken, seqSet.seqSetId, seqSet.seqSetVersion, - openErrorFeedback, + (message) => toast.error(message, { position: 'top-center', autoClose: false }), ); const handleDeleteSeqSet = async () => { @@ -49,7 +48,6 @@ const SeqSetItemActionsInner: FC = ({ return (
-
diff --git a/website/src/components/Submission/DataUploadForm.tsx b/website/src/components/Submission/DataUploadForm.tsx index 186fdbeea..471e4faa0 100644 --- a/website/src/components/Submission/DataUploadForm.tsx +++ b/website/src/components/Submission/DataUploadForm.tsx @@ -307,7 +307,7 @@ const InnerDataUploadForm = ({ event.preventDefault(); if (!agreedToINSDCUploadTerms) { - onError('Please tick the box agree that you will not independently submit these sequences to INSDC'); + onError('Please tick the box to agree that you will not independently submit these sequences to INSDC'); return; } diff --git a/website/src/components/Submission/RevisionForm.tsx b/website/src/components/Submission/RevisionForm.tsx index 8ed956d3d..27dc7277a 100644 --- a/website/src/components/Submission/RevisionForm.tsx +++ b/website/src/components/Submission/RevisionForm.tsx @@ -1,11 +1,11 @@ import { type FC } from 'react'; +import { toast } from 'react-toastify'; import { DataUploadForm } from './DataUploadForm.tsx'; import { routes } from '../../routes/routes.ts'; import { type Group } from '../../types/backend.ts'; import type { ReferenceGenomesSequenceNames } from '../../types/referencesGenomes'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; -import { ManagedErrorFeedback, useErrorFeedbackState } from '../common/ManagedErrorFeedback'; type RevisionFormProps = { accessToken: string; @@ -22,18 +22,15 @@ export const RevisionForm: FC = ({ group, referenceGenomeSequenceNames, }) => { - const { errorMessage, isErrorOpen, openErrorFeedback, closeErrorFeedback } = useErrorFeedbackState(); - return (
- toast.error(message, { position: 'top-center', autoClose: false })} group={group} onSuccess={() => { window.location.href = routes.userSequenceReviewPage(organism, group.groupId); diff --git a/website/src/components/Submission/SubmissionForm.spec.tsx b/website/src/components/Submission/SubmissionForm.spec.tsx index 46efcb77c..ff6b041bc 100644 --- a/website/src/components/Submission/SubmissionForm.spec.tsx +++ b/website/src/components/Submission/SubmissionForm.spec.tsx @@ -1,5 +1,6 @@ import { render, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { toast } from 'react-toastify'; import { describe, expect, test, vi } from 'vitest'; import { SubmissionForm } from './SubmissionForm'; @@ -15,6 +16,12 @@ vi.mock('../../api', () => ({ }), })); +vi.mock('react-toastify', () => ({ + toast: { + error: vi.fn(), + }, +})); + const group: Group = { groupId: 1, groupName: testGroups[0].groupName, @@ -93,7 +100,10 @@ describe('SubmitForm', () => { await userEvent.click(submitButton); await waitFor(() => { - expect(getByText((text) => text.includes('Please select a sequences file'))).toBeInTheDocument(); + expect(toast.error).toHaveBeenCalledWith(expect.stringContaining('Please select a sequences file'), { + position: 'top-center', + autoClose: false, + }); }); }); @@ -135,13 +145,12 @@ describe('SubmitForm', () => { const submitButton = getByText('Submit sequences'); await userEvent.click(submitButton); await waitFor(() => { - expect( - getByText((text) => - text.includes( - 'Please tick the box agree that you will not independently submit these sequences to INSDC', - ), + expect(toast.error).toHaveBeenCalledWith( + expect.stringContaining( + 'Please tick the box to agree that you will not independently submit these sequences to INSDC', ), - ).toBeInTheDocument(); + { position: 'top-center', autoClose: false }, + ); }); await userEvent.click( @@ -165,7 +174,10 @@ describe('SubmitForm', () => { await userEvent.click(submitButton); await waitFor(() => { - expect(getByText((text) => text.includes(receivedUnexpectedMessageFromBackend))).toBeInTheDocument(); + expect(toast.error).toHaveBeenCalledWith(expect.stringContaining(receivedUnexpectedMessageFromBackend), { + position: 'top-center', + autoClose: false, + }); }); } }); diff --git a/website/src/components/Submission/SubmissionForm.tsx b/website/src/components/Submission/SubmissionForm.tsx index 069e886e0..bc10c357b 100644 --- a/website/src/components/Submission/SubmissionForm.tsx +++ b/website/src/components/Submission/SubmissionForm.tsx @@ -1,11 +1,11 @@ import { type FC } from 'react'; +import { toast } from 'react-toastify'; import { DataUploadForm } from './DataUploadForm.tsx'; import { routes } from '../../routes/routes.ts'; import { type Group } from '../../types/backend.ts'; import type { ReferenceGenomesSequenceNames } from '../../types/referencesGenomes'; import type { ClientConfig } from '../../types/runtimeConfig.ts'; -import { ManagedErrorFeedback, useErrorFeedbackState } from '../common/ManagedErrorFeedback'; type SubmissionFormProps = { accessToken: string; @@ -22,18 +22,15 @@ export const SubmissionForm: FC = ({ group, referenceGenomeSequenceNames, }) => { - const { errorMessage, isErrorOpen, openErrorFeedback, closeErrorFeedback } = useErrorFeedbackState(); - return (
- toast.error(message, { position: 'top-center', autoClose: false })} group={group} onSuccess={() => { window.location.href = routes.userSequenceReviewPage(organism, group.groupId); diff --git a/website/src/components/common/ManagedErrorFeedback.tsx b/website/src/components/common/ManagedErrorFeedback.tsx deleted file mode 100644 index 0b014131e..000000000 --- a/website/src/components/common/ManagedErrorFeedback.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import Button from '@mui/material/Button'; -import Portal from '@mui/material/Portal'; -import Snackbar from '@mui/material/Snackbar'; -import { type FC, useState } from 'react'; - -type ErrorFeedbackProps = { - message: string; - open: boolean; - onClose: () => void; -}; - -export const ManagedErrorFeedback: FC = ({ message, open, onClose }) => { - const action = ( - - ); - return ( - - - - ); -}; - -export function useErrorFeedbackState() { - const [isErrorOpen, setIsErrorOpen] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); - - const openErrorFeedback = (message: string) => { - setErrorMessage(message); - setIsErrorOpen(true); - }; - - const closeErrorFeedback = () => { - setErrorMessage(''); - setIsErrorOpen(false); - }; - - return { errorMessage, isErrorOpen, openErrorFeedback, closeErrorFeedback }; -}