From 6810d5fcc75ffb063eb481c88b244f59558063e1 Mon Sep 17 00:00:00 2001 From: fuencui Date: Fri, 24 Feb 2023 08:44:11 -0800 Subject: [PATCH 1/7] Replace uses of alert with SnackBar(toast) --- .../DataEntryTable/DataEntryTable.tsx | 32 ++++- .../tests/DataEntryTable.test.tsx | 1 + src/components/ProjectExport/ExportButton.tsx | 56 +++++++-- src/components/SnackBar/SnackBar.tsx | 36 ++++++ src/components/UserSettings/UserSettings.tsx | 32 ++++- .../ReviewEntriesTable.tsx | 112 +++++++++++------- 6 files changed, 214 insertions(+), 55 deletions(-) create mode 100644 src/components/SnackBar/SnackBar.tsx diff --git a/src/components/DataEntry/DataEntryTable/DataEntryTable.tsx b/src/components/DataEntry/DataEntryTable/DataEntryTable.tsx index df58c665d8..4b8aa35419 100644 --- a/src/components/DataEntry/DataEntryTable/DataEntryTable.tsx +++ b/src/components/DataEntry/DataEntryTable/DataEntryTable.tsx @@ -20,6 +20,7 @@ import NewEntry, { import RecentEntry from "components/DataEntry/DataEntryTable/RecentEntry/RecentEntry"; import { getFileNameForWord } from "components/Pronunciations/AudioRecorder"; import Recorder from "components/Pronunciations/Recorder"; +import PositionedSnackbar from "components/SnackBar/SnackBar"; import theme from "types/theme"; import { newSense, simpleWord } from "types/word"; import { firstGlossText } from "types/wordUtilities"; @@ -51,6 +52,8 @@ interface DataEntryTableState { vernacularLang: WritingSystem; defunctWordIds: string[]; isFetchingFrontier: boolean; + toastOpen: boolean; + toastMessage: string; } export function addSemanticDomainToSense( @@ -107,6 +110,8 @@ export class DataEntryTable extends React.Component< vernacularLang: newWritingSystem("qaa", "Unknown"), defunctWordIds: [], isFetchingFrontier: false, + toastOpen: false, + toastMessage: "", }; this.refNewEntry = React.createRef(); this.recorder = new Recorder(); @@ -226,6 +231,18 @@ export class DataEntryTable extends React.Component< this.replaceInDisplay(wordToUpdate.id, updatedWord); } + //Update the alert message and display it for 3 seconds + handleToastUpdate(message: string) { + this.setState({ + toastMessage: message, + toastOpen: true, + }); + setTimeout(() => { + this.setState({ toastMessage: "", toastOpen: false }); + }, 3000); + return; + } + // Checks if sense already exists with this gloss and semantic domain // returns false if encounters duplicate async updateWordWithNewGloss( @@ -252,7 +269,7 @@ export class DataEntryTable extends React.Component< .includes(this.props.semanticDomain.id) ) { // User is trying to add a sense that already exists - alert( + this.handleToastUpdate( this.props.t("addWords.senseInWord") + `: ${existingWord.vernacular}, ${gloss}` ); @@ -594,6 +611,18 @@ export class DataEntryTable extends React.Component< this.setState({ defunctWordIds: [], recentlyAddedWords: [] }); } + handleToastDisplay(bool: boolean) { + if (bool) + return ( + + ); + } + render() { return (
) => this.submit(e)}> @@ -721,6 +750,7 @@ export class DataEntryTable extends React.Component< + {this.handleToastDisplay(this.state.toastOpen)}
); } diff --git a/src/components/DataEntry/DataEntryTable/tests/DataEntryTable.test.tsx b/src/components/DataEntry/DataEntryTable/tests/DataEntryTable.test.tsx index e91e032aa4..f2c60dc66a 100644 --- a/src/components/DataEntry/DataEntryTable/tests/DataEntryTable.test.tsx +++ b/src/components/DataEntry/DataEntryTable/tests/DataEntryTable.test.tsx @@ -32,6 +32,7 @@ jest.mock("backend", () => ({ jest.mock("components/DataEntry/DataEntryTable/RecentEntry/RecentEntry"); jest.mock("components/Pronunciations/PronunciationsComponent", () => "div"); jest.mock("components/Pronunciations/Recorder"); +jest.mock("components/SnackBar/SnackBar", () => "div"); jest.spyOn(window, "alert").mockImplementation(() => {}); let testRenderer: ReactTestRenderer; diff --git a/src/components/ProjectExport/ExportButton.tsx b/src/components/ProjectExport/ExportButton.tsx index 46bdcd462b..cf911aa940 100644 --- a/src/components/ProjectExport/ExportButton.tsx +++ b/src/components/ProjectExport/ExportButton.tsx @@ -1,4 +1,5 @@ import { ButtonProps } from "@material-ui/core/Button"; +import React, { useState } from "react"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; @@ -6,6 +7,7 @@ import { isFrontierNonempty } from "backend"; import LoadingButton from "components/Buttons/LoadingButton"; import { asyncExportProject } from "components/ProjectExport/Redux/ExportProjectActions"; import { ExportStatus } from "components/ProjectExport/Redux/ExportProjectReduxTypes"; +import PositionedSnackbar from "components/SnackBar/SnackBar"; import { StoreState } from "types"; interface ExportButtonProps { @@ -17,13 +19,26 @@ interface ExportButtonProps { export default function ExportButton(props: ExportButtonProps) { const dispatch = useDispatch(); const { t } = useTranslation(); + const [toastOpen, setToastOpen] = useState(false); + const [toastMessage, setToastMessage] = useState(""); + + //Update the alert message and display it for 3 seconds + function handleToastUpdate(message: string) { + setToastMessage(message); + setToastOpen(true); + setTimeout(() => { + setToastMessage(""); + setToastOpen(false); + }, 3000); + return; + } function exportProj() { isFrontierNonempty(props.projectId).then((isNonempty) => { if (isNonempty) { dispatch(asyncExportProject(props.projectId)); } else { - alert(t("projectExport.cannotExportEmpty")); + handleToastUpdate(t("projectExport.cannotExportEmpty")); } }); } @@ -36,18 +51,33 @@ export default function ExportButton(props: ExportButtonProps) { exportResult.status === ExportStatus.Success || exportResult.status === ExportStatus.Downloading; + function handleToastDisplay(bool: boolean) { + if (bool) + return ( + + ); + } + return ( - - {t("buttons.export")} - + + + {t("buttons.export")} + + {handleToastDisplay(toastOpen)} + ); } diff --git a/src/components/SnackBar/SnackBar.tsx b/src/components/SnackBar/SnackBar.tsx new file mode 100644 index 0000000000..078121673f --- /dev/null +++ b/src/components/SnackBar/SnackBar.tsx @@ -0,0 +1,36 @@ +import Snackbar, { SnackbarOrigin } from "@mui/material/Snackbar"; +import { useEffect, useState } from "react"; + +interface PositionedSnackbarProps extends SnackbarOrigin { + open: boolean; + message: string; +} + +export default function PositionedSnackbar(props: PositionedSnackbarProps) { + const [open, setOpen] = useState(props.open); + const [message, setMessage] = useState(props.message); + const [vertical, setVertical] = useState( + props.vertical + ); + const [horizontal, setHorizontal] = useState( + props.horizontal + ); + + const handleClose = () => { + setOpen(false); + }; + + return ( +
+ + {open} +
+ ); +} diff --git a/src/components/UserSettings/UserSettings.tsx b/src/components/UserSettings/UserSettings.tsx index 2430a4ad22..3410cc36b6 100644 --- a/src/components/UserSettings/UserSettings.tsx +++ b/src/components/UserSettings/UserSettings.tsx @@ -18,6 +18,7 @@ import { withTranslation, WithTranslation } from "react-i18next"; import { User } from "api/models"; import { isEmailTaken, updateUser } from "backend"; import { getAvatar, getCurrentUser } from "backend/localStorage"; +import PositionedSnackbar from "components/SnackBar/SnackBar"; import AvatarUpload from "components/UserSettings/AvatarUpload"; import theme from "types/theme"; import { newUser } from "types/user"; @@ -82,6 +83,8 @@ interface UserSettingsState { emailTaken: boolean; avatar: string; avatarDialogOpen: boolean; + toastOpen: boolean; + toastMessage: string; } /** @@ -100,6 +103,8 @@ class UserSettings extends React.Component { emailTaken: false, avatar: getAvatar(), avatarDialogOpen: false, + toastOpen: false, + toastMessage: "", }; } @@ -126,6 +131,18 @@ class UserSettings extends React.Component { return !(await isEmailTaken(this.state.email)); } + //Update the alert message and display it for 3 seconds + handleToastUpdate(message: string) { + this.setState({ + toastMessage: message, + toastOpen: true, + }); + setTimeout(() => { + this.setState({ toastMessage: "", toastOpen: false }); + }, 3000); + return; + } + async onSubmit(e: React.FormEvent) { e.preventDefault(); if (await this.isEmailOkay()) { @@ -135,12 +152,24 @@ class UserSettings extends React.Component { phone: this.state.phone, email: this.state.email, }); - alert(this.props.t("userSettings.updateSuccess")); + this.handleToastUpdate(this.props.t("userSettings.updateSuccess")); } else { this.setState({ emailTaken: true }); } } + handleToastDisplay(bool: boolean) { + if (bool) + return ( + + ); + } + render() { return ( @@ -251,6 +280,7 @@ class UserSettings extends React.Component { this.setState({ avatar: getAvatar(), avatarDialogOpen: false }); }} /> + {this.handleToastDisplay(this.state.toastOpen)} ); } diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx index ffad35b255..cafc715295 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx @@ -1,9 +1,10 @@ import MaterialTable from "@material-table/core"; import { Typography } from "@material-ui/core"; -import { ReactElement } from "react"; +import React, { ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; +import PositionedSnackbar from "components/SnackBar/SnackBar"; import columns, { ColumnTitle, } from "goals/ReviewEntries/ReviewEntriesComponent/CellColumns"; @@ -36,47 +37,78 @@ export default function ReviewEntriesTable( const showDefinitions = useSelector( (state: StoreState) => state.currentProjectState.project.definitionsEnabled ); + const [toastOpen, setToastOpen] = useState(false); + const [toastMessage, setToastMessage] = useState(""); const { t } = useTranslation(); + //Update the alert message and display it for 3 seconds + function handleToastUpdate(message: string) { + setToastMessage(message); + setToastOpen(true); + setTimeout(() => { + setToastMessage(""); + setToastOpen(false); + }, 3000); + return; + } + + function handleToastDisplay(bool: boolean) { + if (bool) + return ( + + ); + } + return ( - - icons={tableIcons} - title={ - - {t("reviewEntries.title")} - - } - columns={ - showDefinitions - ? columns - : columns.filter((c) => c.title !== ColumnTitle.Definitions) - } - data={words} - editable={{ - onRowUpdate: (newData: ReviewEntriesWord, oldData: ReviewEntriesWord) => - new Promise(async (resolve, reject) => { - await props - .onRowUpdate(newData, oldData) - .then(resolve) - .catch((reason) => { - alert(t(reason)); - reject(reason); - }); - }), - }} - options={{ - draggable: false, - filtering: true, - pageSize: - words.length > 0 - ? Math.min(words.length, ROWS_PER_PAGE[0]) - : ROWS_PER_PAGE[0], - pageSizeOptions: removeDuplicates([ - Math.min(words.length, ROWS_PER_PAGE[0]), - Math.min(words.length, ROWS_PER_PAGE[1]), - Math.min(words.length, ROWS_PER_PAGE[2]), - ]), - }} - /> + + + icons={tableIcons} + title={ + + {t("reviewEntries.title")} + + } + columns={ + showDefinitions + ? columns + : columns.filter((c) => c.title !== ColumnTitle.Definitions) + } + data={words} + editable={{ + onRowUpdate: ( + newData: ReviewEntriesWord, + oldData: ReviewEntriesWord + ) => + new Promise(async (resolve, reject) => { + await props + .onRowUpdate(newData, oldData) + .then(resolve) + .catch((reason) => { + handleToastUpdate(t(reason)); + reject(reason); + }); + }), + }} + options={{ + draggable: false, + filtering: true, + pageSize: + words.length > 0 + ? Math.min(words.length, ROWS_PER_PAGE[0]) + : ROWS_PER_PAGE[0], + pageSizeOptions: removeDuplicates([ + Math.min(words.length, ROWS_PER_PAGE[0]), + Math.min(words.length, ROWS_PER_PAGE[1]), + Math.min(words.length, ROWS_PER_PAGE[2]), + ]), + }} + /> + {handleToastDisplay(toastOpen)} + ); } From c7750d7286596694017bae916b19948ddaea6e4a Mon Sep 17 00:00:00 2001 From: fuencui Date: Mon, 27 Feb 2023 08:44:49 -0800 Subject: [PATCH 2/7] Use Notistack Toast to replace JS alert --- .vscode/settings.json | 3 +- package-lock.json | 38 +++++++++++++++++++ package.json | 9 +++-- .../DataEntryTable/DataEntryTable.tsx | 32 +--------------- src/components/ProjectExport/ExportButton.tsx | 31 ++------------- src/components/SnackBar/SnackBar.tsx | 36 ------------------ src/components/UserSettings/UserSettings.tsx | 31 --------------- .../ReviewEntriesTable.tsx | 31 ++------------- src/index.tsx | 17 +++++---- 9 files changed, 63 insertions(+), 165 deletions(-) delete mode 100644 src/components/SnackBar/SnackBar.tsx diff --git a/.vscode/settings.json b/.vscode/settings.json index 54baa0c737..cbe227279c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,5 +30,6 @@ "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "files.insertFinalNewline": true + "files.insertFinalNewline": true, + "cSpell.words": ["notistack"] } diff --git a/package-lock.json b/package-lock.json index 565acd4d36..a498edbc3f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,6 +28,7 @@ "make-dir": "^3.1.0", "motion": "^10.15.5", "mui-language-picker": "^1.1.15", + "notistack": "^2.0.8", "nspell": "^2.1.5", "react": "^17.0.2", "react-beautiful-dnd": "^13.0.0", @@ -16707,6 +16708,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", @@ -34419,6 +34448,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 f7d28cd91c..e49758ace7 100644 --- a/package.json +++ b/package.json @@ -45,6 +45,7 @@ "@microsoft/signalr": "^6.0.7", "@segment/analytics-next": "^1.40.0", "axios": "^1.3.3", + "crypto-js": "^4.1.1", "http-status-codes": "^2.1.4", "i18next": "^21.6.16", "i18next-browser-languagedetector": "^6.1.4", @@ -53,6 +54,7 @@ "make-dir": "^3.1.0", "motion": "^10.15.5", "mui-language-picker": "^1.1.15", + "notistack": "^2.0.8", "nspell": "^2.1.5", "react": "^17.0.2", "react-beautiful-dnd": "^13.0.0", @@ -67,7 +69,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", @@ -78,6 +79,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", @@ -90,7 +92,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", @@ -180,7 +181,9 @@ } }, "jest": { - "transformIgnorePatterns": ["/node_modules/(!${axios})"] + "transformIgnorePatterns": [ + "/node_modules/(!${axios})" + ] }, "prettier": { "overrides": [ diff --git a/src/components/DataEntry/DataEntryTable/DataEntryTable.tsx b/src/components/DataEntry/DataEntryTable/DataEntryTable.tsx index 4b8aa35419..df58c665d8 100644 --- a/src/components/DataEntry/DataEntryTable/DataEntryTable.tsx +++ b/src/components/DataEntry/DataEntryTable/DataEntryTable.tsx @@ -20,7 +20,6 @@ import NewEntry, { import RecentEntry from "components/DataEntry/DataEntryTable/RecentEntry/RecentEntry"; import { getFileNameForWord } from "components/Pronunciations/AudioRecorder"; import Recorder from "components/Pronunciations/Recorder"; -import PositionedSnackbar from "components/SnackBar/SnackBar"; import theme from "types/theme"; import { newSense, simpleWord } from "types/word"; import { firstGlossText } from "types/wordUtilities"; @@ -52,8 +51,6 @@ interface DataEntryTableState { vernacularLang: WritingSystem; defunctWordIds: string[]; isFetchingFrontier: boolean; - toastOpen: boolean; - toastMessage: string; } export function addSemanticDomainToSense( @@ -110,8 +107,6 @@ export class DataEntryTable extends React.Component< vernacularLang: newWritingSystem("qaa", "Unknown"), defunctWordIds: [], isFetchingFrontier: false, - toastOpen: false, - toastMessage: "", }; this.refNewEntry = React.createRef(); this.recorder = new Recorder(); @@ -231,18 +226,6 @@ export class DataEntryTable extends React.Component< this.replaceInDisplay(wordToUpdate.id, updatedWord); } - //Update the alert message and display it for 3 seconds - handleToastUpdate(message: string) { - this.setState({ - toastMessage: message, - toastOpen: true, - }); - setTimeout(() => { - this.setState({ toastMessage: "", toastOpen: false }); - }, 3000); - return; - } - // Checks if sense already exists with this gloss and semantic domain // returns false if encounters duplicate async updateWordWithNewGloss( @@ -269,7 +252,7 @@ export class DataEntryTable extends React.Component< .includes(this.props.semanticDomain.id) ) { // User is trying to add a sense that already exists - this.handleToastUpdate( + alert( this.props.t("addWords.senseInWord") + `: ${existingWord.vernacular}, ${gloss}` ); @@ -611,18 +594,6 @@ export class DataEntryTable extends React.Component< this.setState({ defunctWordIds: [], recentlyAddedWords: [] }); } - handleToastDisplay(bool: boolean) { - if (bool) - return ( - - ); - } - render() { return (
) => this.submit(e)}> @@ -750,7 +721,6 @@ export class DataEntryTable extends React.Component< - {this.handleToastDisplay(this.state.toastOpen)}
); } diff --git a/src/components/ProjectExport/ExportButton.tsx b/src/components/ProjectExport/ExportButton.tsx index cf911aa940..6e7695314a 100644 --- a/src/components/ProjectExport/ExportButton.tsx +++ b/src/components/ProjectExport/ExportButton.tsx @@ -7,8 +7,8 @@ import { isFrontierNonempty } from "backend"; import LoadingButton from "components/Buttons/LoadingButton"; import { asyncExportProject } from "components/ProjectExport/Redux/ExportProjectActions"; import { ExportStatus } from "components/ProjectExport/Redux/ExportProjectReduxTypes"; -import PositionedSnackbar from "components/SnackBar/SnackBar"; import { StoreState } from "types"; +import { useSnackbar } from "notistack"; interface ExportButtonProps { projectId: string; @@ -19,26 +19,14 @@ interface ExportButtonProps { export default function ExportButton(props: ExportButtonProps) { const dispatch = useDispatch(); const { t } = useTranslation(); - const [toastOpen, setToastOpen] = useState(false); - const [toastMessage, setToastMessage] = useState(""); - - //Update the alert message and display it for 3 seconds - function handleToastUpdate(message: string) { - setToastMessage(message); - setToastOpen(true); - setTimeout(() => { - setToastMessage(""); - setToastOpen(false); - }, 3000); - return; - } + const { enqueueSnackbar } = useSnackbar(); function exportProj() { isFrontierNonempty(props.projectId).then((isNonempty) => { if (isNonempty) { dispatch(asyncExportProject(props.projectId)); } else { - handleToastUpdate(t("projectExport.cannotExportEmpty")); + enqueueSnackbar(t("projectExport.cannotExportEmpty")); } }); } @@ -51,18 +39,6 @@ export default function ExportButton(props: ExportButtonProps) { exportResult.status === ExportStatus.Success || exportResult.status === ExportStatus.Downloading; - function handleToastDisplay(bool: boolean) { - if (bool) - return ( - - ); - } - return ( {t("buttons.export")} - {handleToastDisplay(toastOpen)} ); } diff --git a/src/components/SnackBar/SnackBar.tsx b/src/components/SnackBar/SnackBar.tsx deleted file mode 100644 index 078121673f..0000000000 --- a/src/components/SnackBar/SnackBar.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import Snackbar, { SnackbarOrigin } from "@mui/material/Snackbar"; -import { useEffect, useState } from "react"; - -interface PositionedSnackbarProps extends SnackbarOrigin { - open: boolean; - message: string; -} - -export default function PositionedSnackbar(props: PositionedSnackbarProps) { - const [open, setOpen] = useState(props.open); - const [message, setMessage] = useState(props.message); - const [vertical, setVertical] = useState( - props.vertical - ); - const [horizontal, setHorizontal] = useState( - props.horizontal - ); - - const handleClose = () => { - setOpen(false); - }; - - return ( -
- - {open} -
- ); -} diff --git a/src/components/UserSettings/UserSettings.tsx b/src/components/UserSettings/UserSettings.tsx index 3410cc36b6..01f47ad19d 100644 --- a/src/components/UserSettings/UserSettings.tsx +++ b/src/components/UserSettings/UserSettings.tsx @@ -18,7 +18,6 @@ import { withTranslation, WithTranslation } from "react-i18next"; import { User } from "api/models"; import { isEmailTaken, updateUser } from "backend"; import { getAvatar, getCurrentUser } from "backend/localStorage"; -import PositionedSnackbar from "components/SnackBar/SnackBar"; import AvatarUpload from "components/UserSettings/AvatarUpload"; import theme from "types/theme"; import { newUser } from "types/user"; @@ -83,8 +82,6 @@ interface UserSettingsState { emailTaken: boolean; avatar: string; avatarDialogOpen: boolean; - toastOpen: boolean; - toastMessage: string; } /** @@ -103,8 +100,6 @@ class UserSettings extends React.Component { emailTaken: false, avatar: getAvatar(), avatarDialogOpen: false, - toastOpen: false, - toastMessage: "", }; } @@ -131,18 +126,6 @@ class UserSettings extends React.Component { return !(await isEmailTaken(this.state.email)); } - //Update the alert message and display it for 3 seconds - handleToastUpdate(message: string) { - this.setState({ - toastMessage: message, - toastOpen: true, - }); - setTimeout(() => { - this.setState({ toastMessage: "", toastOpen: false }); - }, 3000); - return; - } - async onSubmit(e: React.FormEvent) { e.preventDefault(); if (await this.isEmailOkay()) { @@ -152,24 +135,11 @@ class UserSettings extends React.Component { phone: this.state.phone, email: this.state.email, }); - this.handleToastUpdate(this.props.t("userSettings.updateSuccess")); } else { this.setState({ emailTaken: true }); } } - handleToastDisplay(bool: boolean) { - if (bool) - return ( - - ); - } - render() { return ( @@ -280,7 +250,6 @@ class UserSettings extends React.Component { this.setState({ avatar: getAvatar(), avatarDialogOpen: false }); }} /> - {this.handleToastDisplay(this.state.toastOpen)} ); } diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx index cafc715295..f47270a6b8 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx @@ -4,13 +4,13 @@ import React, { ReactElement, useState } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; -import PositionedSnackbar from "components/SnackBar/SnackBar"; import columns, { ColumnTitle, } from "goals/ReviewEntries/ReviewEntriesComponent/CellColumns"; import { ReviewEntriesWord } from "goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTypes"; import tableIcons from "goals/ReviewEntries/ReviewEntriesComponent/icons"; import { StoreState } from "types"; +import { useSnackbar } from "notistack"; interface ReviewEntriesTableProps { onRowUpdate: ( @@ -37,33 +37,9 @@ export default function ReviewEntriesTable( const showDefinitions = useSelector( (state: StoreState) => state.currentProjectState.project.definitionsEnabled ); - const [toastOpen, setToastOpen] = useState(false); - const [toastMessage, setToastMessage] = useState(""); + const { enqueueSnackbar } = useSnackbar(); const { t } = useTranslation(); - //Update the alert message and display it for 3 seconds - function handleToastUpdate(message: string) { - setToastMessage(message); - setToastOpen(true); - setTimeout(() => { - setToastMessage(""); - setToastOpen(false); - }, 3000); - return; - } - - function handleToastDisplay(bool: boolean) { - if (bool) - return ( - - ); - } - return ( @@ -89,7 +65,7 @@ export default function ReviewEntriesTable( .onRowUpdate(newData, oldData) .then(resolve) .catch((reason) => { - handleToastUpdate(t(reason)); + enqueueSnackbar(t(reason)); reject(reason); }); }), @@ -108,7 +84,6 @@ export default function ReviewEntriesTable( ]), }} /> - {handleToastDisplay(toastOpen)} ); } diff --git a/src/index.tsx b/src/index.tsx index 03ff4b6c96..27412c095d 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -9,17 +9,20 @@ import App from "components/App/component"; import "i18n"; import { persistor, store } from "store"; import theme from "types/theme"; +import { SnackbarProvider } from "notistack"; //Provider connects store to component containers ReactDOM.render( - - - - - - - + + + + + + + + + , document.getElementById("root") ); From cbc85d5b42649a2bdd25f074bfd9869c781f0e5d Mon Sep 17 00:00:00 2001 From: fuencui Date: Mon, 27 Feb 2023 09:14:48 -0800 Subject: [PATCH 3/7] Fix lint test and front-end test --- .../DataEntryTable/tests/DataEntryTable.test.tsx | 1 - src/components/ProjectExport/ExportButton.tsx | 4 ++-- .../ReviewEntriesComponent/ReviewEntriesTable.tsx | 6 +++--- .../tests/ReviewEntriesComponent.test.tsx | 10 ++++++++++ src/index.tsx | 2 +- 5 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/components/DataEntry/DataEntryTable/tests/DataEntryTable.test.tsx b/src/components/DataEntry/DataEntryTable/tests/DataEntryTable.test.tsx index f2c60dc66a..e91e032aa4 100644 --- a/src/components/DataEntry/DataEntryTable/tests/DataEntryTable.test.tsx +++ b/src/components/DataEntry/DataEntryTable/tests/DataEntryTable.test.tsx @@ -32,7 +32,6 @@ jest.mock("backend", () => ({ jest.mock("components/DataEntry/DataEntryTable/RecentEntry/RecentEntry"); jest.mock("components/Pronunciations/PronunciationsComponent", () => "div"); jest.mock("components/Pronunciations/Recorder"); -jest.mock("components/SnackBar/SnackBar", () => "div"); jest.spyOn(window, "alert").mockImplementation(() => {}); let testRenderer: ReactTestRenderer; diff --git a/src/components/ProjectExport/ExportButton.tsx b/src/components/ProjectExport/ExportButton.tsx index 6e7695314a..431e39b31a 100644 --- a/src/components/ProjectExport/ExportButton.tsx +++ b/src/components/ProjectExport/ExportButton.tsx @@ -1,5 +1,6 @@ import { ButtonProps } from "@material-ui/core/Button"; -import React, { useState } from "react"; +import { useSnackbar } from "notistack"; +import React from "react"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; @@ -8,7 +9,6 @@ import LoadingButton from "components/Buttons/LoadingButton"; import { asyncExportProject } from "components/ProjectExport/Redux/ExportProjectActions"; import { ExportStatus } from "components/ProjectExport/Redux/ExportProjectReduxTypes"; import { StoreState } from "types"; -import { useSnackbar } from "notistack"; interface ExportButtonProps { projectId: string; diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx index f47270a6b8..5e5b475733 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx @@ -1,6 +1,7 @@ import MaterialTable from "@material-table/core"; import { Typography } from "@material-ui/core"; -import React, { ReactElement, useState } from "react"; +import { useSnackbar } from "notistack"; +import React, { ReactElement } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; @@ -10,7 +11,6 @@ import columns, { import { ReviewEntriesWord } from "goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTypes"; import tableIcons from "goals/ReviewEntries/ReviewEntriesComponent/icons"; import { StoreState } from "types"; -import { useSnackbar } from "notistack"; interface ReviewEntriesTableProps { onRowUpdate: ( @@ -37,8 +37,8 @@ export default function ReviewEntriesTable( const showDefinitions = useSelector( (state: StoreState) => state.currentProjectState.project.definitionsEnabled ); - const { enqueueSnackbar } = useSnackbar(); const { t } = useTranslation(); + const { enqueueSnackbar } = useSnackbar(); return ( diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/tests/ReviewEntriesComponent.test.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/tests/ReviewEntriesComponent.test.tsx index 0eb8bd01a3..ad9fb33bb8 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("@material-ui/core", () => { 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 27412c095d..f345ea3d9a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,4 +1,5 @@ import ThemeProvider from "@material-ui/styles/ThemeProvider"; +import { SnackbarProvider } from "notistack"; import ReactDOM from "react-dom"; import { Provider } from "react-redux"; import { Router } from "react-router-dom"; @@ -9,7 +10,6 @@ import App from "components/App/component"; import "i18n"; import { persistor, store } from "store"; import theme from "types/theme"; -import { SnackbarProvider } from "notistack"; //Provider connects store to component containers ReactDOM.render( From dab29da71bca9cfe5c461efc3638bda468195619 Mon Sep 17 00:00:00 2001 From: fuencui Date: Mon, 27 Feb 2023 09:25:07 -0800 Subject: [PATCH 4/7] axios: ^0.27.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index e49758ace7..aba7e8d2fc 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "@matt-block/react-recaptcha-v2": "^1.0.6", "@microsoft/signalr": "^6.0.7", "@segment/analytics-next": "^1.40.0", - "axios": "^1.3.3", + "axios": "^0.27.2", "crypto-js": "^4.1.1", "http-status-codes": "^2.1.4", "i18next": "^21.6.16", From df41cf9edc0655c5cc1bf35d4763f3b10da9caad Mon Sep 17 00:00:00 2001 From: fuencui Date: Tue, 28 Feb 2023 08:51:21 -0800 Subject: [PATCH 5/7] clean up unnecessary code --- .vscode/settings.json | 3 +- src/components/ProjectExport/ExportButton.tsx | 26 +++--- src/components/UserSettings/UserSettings.tsx | 1 + .../ReviewEntriesTable.tsx | 83 +++++++++---------- 4 files changed, 53 insertions(+), 60 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index cbe227279c..54baa0c737 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,6 +30,5 @@ "[yaml]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, - "files.insertFinalNewline": true, - "cSpell.words": ["notistack"] + "files.insertFinalNewline": true } diff --git a/src/components/ProjectExport/ExportButton.tsx b/src/components/ProjectExport/ExportButton.tsx index 431e39b31a..b0f00af25e 100644 --- a/src/components/ProjectExport/ExportButton.tsx +++ b/src/components/ProjectExport/ExportButton.tsx @@ -40,19 +40,17 @@ export default function ExportButton(props: ExportButtonProps) { exportResult.status === ExportStatus.Downloading; return ( - - - {t("buttons.export")} - - + + {t("buttons.export")} + ); } diff --git a/src/components/UserSettings/UserSettings.tsx b/src/components/UserSettings/UserSettings.tsx index 01f47ad19d..2430a4ad22 100644 --- a/src/components/UserSettings/UserSettings.tsx +++ b/src/components/UserSettings/UserSettings.tsx @@ -135,6 +135,7 @@ class UserSettings extends React.Component { phone: this.state.phone, email: this.state.email, }); + alert(this.props.t("userSettings.updateSuccess")); } else { this.setState({ emailTaken: true }); } diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx index 5e5b475733..0b1d858c8b 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx @@ -41,49 +41,44 @@ export default function ReviewEntriesTable( const { enqueueSnackbar } = useSnackbar(); return ( - - - icons={tableIcons} - title={ - - {t("reviewEntries.title")} - - } - columns={ - showDefinitions - ? columns - : columns.filter((c) => c.title !== ColumnTitle.Definitions) - } - data={words} - editable={{ - onRowUpdate: ( - newData: ReviewEntriesWord, - oldData: ReviewEntriesWord - ) => - new Promise(async (resolve, reject) => { - await props - .onRowUpdate(newData, oldData) - .then(resolve) - .catch((reason) => { - enqueueSnackbar(t(reason)); - reject(reason); - }); - }), - }} - options={{ - draggable: false, - filtering: true, - pageSize: - words.length > 0 - ? Math.min(words.length, ROWS_PER_PAGE[0]) - : ROWS_PER_PAGE[0], - pageSizeOptions: removeDuplicates([ - Math.min(words.length, ROWS_PER_PAGE[0]), - Math.min(words.length, ROWS_PER_PAGE[1]), - Math.min(words.length, ROWS_PER_PAGE[2]), - ]), - }} - /> - + + icons={tableIcons} + title={ + + {t("reviewEntries.title")} + + } + columns={ + showDefinitions + ? columns + : columns.filter((c) => c.title !== ColumnTitle.Definitions) + } + data={words} + editable={{ + onRowUpdate: (newData: ReviewEntriesWord, oldData: ReviewEntriesWord) => + new Promise(async (resolve, reject) => { + await props + .onRowUpdate(newData, oldData) + .then(resolve) + .catch((reason) => { + enqueueSnackbar(t(reason)); + reject(reason); + }); + }), + }} + options={{ + draggable: false, + filtering: true, + pageSize: + words.length > 0 + ? Math.min(words.length, ROWS_PER_PAGE[0]) + : ROWS_PER_PAGE[0], + pageSizeOptions: removeDuplicates([ + Math.min(words.length, ROWS_PER_PAGE[0]), + Math.min(words.length, ROWS_PER_PAGE[1]), + Math.min(words.length, ROWS_PER_PAGE[2]), + ]), + }} + /> ); } From 713a41d9c9b886cbb1ee3067d44f20ffa8224dad Mon Sep 17 00:00:00 2001 From: fuencui Date: Tue, 28 Feb 2023 09:00:49 -0800 Subject: [PATCH 6/7] clean up unnecessary code --- src/components/ProjectExport/ExportButton.tsx | 1 - .../ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/ProjectExport/ExportButton.tsx b/src/components/ProjectExport/ExportButton.tsx index b0f00af25e..a7f2d6b1a7 100644 --- a/src/components/ProjectExport/ExportButton.tsx +++ b/src/components/ProjectExport/ExportButton.tsx @@ -1,6 +1,5 @@ import { ButtonProps } from "@material-ui/core/Button"; import { useSnackbar } from "notistack"; -import React from "react"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx index 0b1d858c8b..c1afb53044 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx @@ -1,7 +1,7 @@ import MaterialTable from "@material-table/core"; import { Typography } from "@material-ui/core"; import { useSnackbar } from "notistack"; -import React, { ReactElement } from "react"; +import { ReactElement } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; From e819900e2489f2a370667ec4feb21371881c7206 Mon Sep 17 00:00:00 2001 From: fuencui Date: Tue, 28 Feb 2023 15:01:27 -0800 Subject: [PATCH 7/7] resolve merge conflict update to MUI5 --- src/components/ProjectExport/ExportButton.tsx | 2 +- .../ReviewEntriesComponent/ReviewEntriesTable.tsx | 4 ++-- src/index.tsx | 1 - 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/components/ProjectExport/ExportButton.tsx b/src/components/ProjectExport/ExportButton.tsx index a7f2d6b1a7..0a240e5041 100644 --- a/src/components/ProjectExport/ExportButton.tsx +++ b/src/components/ProjectExport/ExportButton.tsx @@ -1,4 +1,4 @@ -import { ButtonProps } from "@material-ui/core/Button"; +import { ButtonProps } from "@mui/material/Button"; import { useSnackbar } from "notistack"; import { useTranslation } from "react-i18next"; import { useDispatch, useSelector } from "react-redux"; diff --git a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx index c4924a601e..9145b903ea 100644 --- a/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx +++ b/src/goals/ReviewEntries/ReviewEntriesComponent/ReviewEntriesTable.tsx @@ -1,6 +1,6 @@ import MaterialTable from "@material-table/core"; -import { Typography } from "@material-ui/core"; -import { useSnackbar } from "@notistack"; +import { Typography } from "@mui/material"; +import { useSnackbar } from "notistack"; import { ReactElement } from "react"; import { useTranslation } from "react-i18next"; import { useSelector } from "react-redux"; diff --git a/src/index.tsx b/src/index.tsx index 246bfcf3a0..4ca836a657 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +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";