diff --git a/package-lock.json b/package-lock.json index 2235093873..9745d14f38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -30,6 +30,7 @@ "make-dir": "^3.1.0", "motion": "^10.15.5", "mui-language-picker": "^1.2.6", + "notistack": "^2.0.8", "nspell": "^2.1.5", "react": "^17.0.2", "react-beautiful-dnd": "^13.0.0", @@ -16644,6 +16645,34 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/notistack": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-2.0.8.tgz", + "integrity": "sha512-/IY14wkFp5qjPgKNvAdfL5Jp6q90+MjgKTPh4c81r/lW70KeuX6b9pE/4f8L4FG31cNudbN9siiFS5ql1aSLRw==", + "dependencies": { + "clsx": "^1.1.0", + "hoist-non-react-statics": "^3.3.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/notistack" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "@mui/material": "^5.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, "node_modules/npm-normalize-package-bin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", @@ -34319,6 +34348,15 @@ "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", "dev": true }, + "notistack": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/notistack/-/notistack-2.0.8.tgz", + "integrity": "sha512-/IY14wkFp5qjPgKNvAdfL5Jp6q90+MjgKTPh4c81r/lW70KeuX6b9pE/4f8L4FG31cNudbN9siiFS5ql1aSLRw==", + "requires": { + "clsx": "^1.1.0", + "hoist-non-react-statics": "^3.3.0" + } + }, "npm-normalize-package-bin": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", diff --git a/package.json b/package.json index 7cca203833..018937cec7 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "@mui/styles": "^5.11.7", "@segment/analytics-next": "^1.40.0", "axios": "^0.27.2", + "crypto-js": "^4.1.1", "http-status-codes": "^2.1.4", "i18next": "^21.6.16", "i18next-browser-languagedetector": "^6.1.4", @@ -55,6 +56,7 @@ "make-dir": "^3.1.0", "motion": "^10.15.5", "mui-language-picker": "^1.2.6", + "notistack": "^2.0.8", "nspell": "^2.1.5", "react": "^17.0.2", "react-beautiful-dnd": "^13.0.0", @@ -69,7 +71,6 @@ "redux-devtools-extension": "^2.13.8", "redux-persist": "^6.0.0", "redux-thunk": "^2.4.0", - "crypto-js": "^4.1.1", "sweetalert2": "^11.7.1", "ts-key-enum": "^2.0.12", "uuid": "^8.3.2", @@ -80,6 +81,7 @@ "@testing-library/react": "^12.1.2", "@testing-library/react-hooks": "^8.0.0", "@testing-library/user-event": "^14.1.1", + "@types/crypto-js": "^4.1.1", "@types/jest": "^29.4.0", "@types/loadable__component": "^5.13.4", "@types/nspell": "^2.1.1", @@ -92,7 +94,6 @@ "@types/react-test-renderer": "^17.0.0", "@types/redux-mock-store": "^1.0.3", "@types/segment-analytics": "^0.0.34", - "@types/crypto-js": "^4.1.1", "@types/uuid": "^8.3.1", "@types/validator": "^13.7.11", "@typescript-eslint/eslint-plugin": "^5.51.0", diff --git a/src/components/ProjectExport/ExportButton.tsx b/src/components/ProjectExport/ExportButton.tsx index 92806e74f2..0a240e5041 100644 --- a/src/components/ProjectExport/ExportButton.tsx +++ b/src/components/ProjectExport/ExportButton.tsx @@ -1,4 +1,5 @@ import { ButtonProps } from "@mui/material/Button"; +import { useSnackbar } from "notistack"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; @@ -17,13 +18,14 @@ interface ExportButtonProps { export default function ExportButton(props: ExportButtonProps) { const dispatch = useDispatch(); const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); function exportProj() { isFrontierNonempty(props.projectId).then((isNonempty) => { if (isNonempty) { dispatch(asyncExportProject(props.projectId)); } else { - alert(t("projectExport.cannotExportEmpty")); + enqueueSnackbar(t("projectExport.cannotExportEmpty")); } }); } diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx index e26b4d7336..9145b903ea 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx @@ -1,5 +1,6 @@ import MaterialTable from "@material-table/core"; import { Typography } from "@mui/material"; +import { useSnackbar } from "notistack"; import { ReactElement } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; @@ -37,6 +38,7 @@ export default function ReviewEntriesTable( (state: StoreState) => state.currentProjectState.project.definitionsEnabled ); const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); return ( @@ -59,7 +61,7 @@ export default function ReviewEntriesTable( .onRowUpdate(newData, oldData) .then(resolve) .catch((reason) => { - alert(t(reason)); + enqueueSnackbar(t(reason)); reject(reason); }); }), diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/tests/ReviewEntriesComponent.test.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/tests/ReviewEntriesComponent.test.tsx index 286ba255f4..56d5e1066d 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/tests/ReviewEntriesComponent.test.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/tests/ReviewEntriesComponent.test.tsx @@ -14,6 +14,7 @@ const mockGetFrontierWords = jest.fn(); const mockMaterialTable = jest.fn(); const mockUpdateAllWords = jest.fn(); const mockUuid = jest.fn(); +const mockEnqueue = jest.fn(); // To deal with the table not wanting to behave in testing. jest.mock("@material-table/core", () => ({ @@ -28,6 +29,15 @@ jest.mock("@mui/material", () => { Dialog: material.Container, }; }); + +jest.mock("notistack", () => ({ + ...jest.requireActual("notistack"), + useSnackbar: () => { + return { + enqueueSnackbar: mockEnqueue, + }; + }, +})); jest.mock("uuid", () => ({ v4: () => mockUuid() })); jest.mock("backend", () => ({ getFrontierWords: () => mockGetFrontierWords(), diff --git a/src/index.tsx b/src/index.tsx index d8ff397b25..4ca836a657 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,5 @@ import { StyledEngineProvider, ThemeProvider } from "@mui/material/styles"; +import { SnackbarProvider } from "notistack"; import ReactDOM from "react-dom"; import { Provider } from "react-redux"; import { Router } from "react-router-dom"; @@ -14,13 +15,15 @@ import theme from "types/theme"; ReactDOM.render( - - - - - - - + + + + + + + + + , document.getElementById("root")