Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace uses of alert with SnackBar(toast) #1928

Merged
merged 9 commits into from
Feb 28, 2023
Merged
32 changes: 31 additions & 1 deletion src/components/DataEntry/DataEntryTable/DataEntryTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -51,6 +52,8 @@ interface DataEntryTableState {
vernacularLang: WritingSystem;
defunctWordIds: string[];
isFetchingFrontier: boolean;
toastOpen: boolean;
toastMessage: string;
}

export function addSemanticDomainToSense(
Expand Down Expand Up @@ -107,6 +110,8 @@ export class DataEntryTable extends React.Component<
vernacularLang: newWritingSystem("qaa", "Unknown"),
defunctWordIds: [],
isFetchingFrontier: false,
toastOpen: false,
toastMessage: "",
};
this.refNewEntry = React.createRef<NewEntry>();
this.recorder = new Recorder();
Expand Down Expand Up @@ -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(
Expand All @@ -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}`
);
Expand Down Expand Up @@ -594,6 +611,18 @@ export class DataEntryTable extends React.Component<
this.setState({ defunctWordIds: [], recentlyAddedWords: [] });
}

handleToastDisplay(bool: boolean) {
if (bool)
return (
<PositionedSnackbar
open={this.state.toastOpen}
message={this.state.toastMessage}
vertical={"top"}
horizontal={"center"}
/>
);
}

render() {
return (
<form onSubmit={(e?: React.FormEvent<HTMLFormElement>) => this.submit(e)}>
Expand Down Expand Up @@ -721,6 +750,7 @@ export class DataEntryTable extends React.Component<
</Button>
</Grid>
</Grid>
{this.handleToastDisplay(this.state.toastOpen)}
</form>
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
56 changes: 43 additions & 13 deletions src/components/ProjectExport/ExportButton.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import { ButtonProps } from "@material-ui/core/Button";
import React, { useState } from "react";
import { useTranslation } from "react-i18next";
import { useDispatch, useSelector } from "react-redux";

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 {
Expand All @@ -17,13 +19,26 @@ interface ExportButtonProps {
export default function ExportButton(props: ExportButtonProps) {
const dispatch = useDispatch();
const { t } = useTranslation();
const [toastOpen, setToastOpen] = useState<boolean>(false);
const [toastMessage, setToastMessage] = useState<string>("");

//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"));
}
});
}
Expand All @@ -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 (
<PositionedSnackbar
open={toastOpen}
message={toastMessage}
vertical={"top"}
horizontal={"center"}
/>
);
}

return (
<LoadingButton
loading={loading}
disabled={loading}
buttonProps={{
...props.buttonProps,
onClick: exportProj,
color: "primary",
id: `project-${props.projectId}-export`,
}}
>
{t("buttons.export")}
</LoadingButton>
<React.Fragment>
<LoadingButton
loading={loading}
disabled={loading}
buttonProps={{
...props.buttonProps,
onClick: exportProj,
color: "primary",
id: `project-${props.projectId}-export`,
}}
>
{t("buttons.export")}
</LoadingButton>
{handleToastDisplay(toastOpen)}
</React.Fragment>
);
}
36 changes: 36 additions & 0 deletions src/components/SnackBar/SnackBar.tsx
Original file line number Diff line number Diff line change
@@ -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<boolean>(props.open);
const [message, setMessage] = useState<string>(props.message);
const [vertical, setVertical] = useState<SnackbarOrigin["vertical"]>(
props.vertical
);
const [horizontal, setHorizontal] = useState<SnackbarOrigin["horizontal"]>(
props.horizontal
);

const handleClose = () => {
setOpen(false);
};

return (
<div>
<Snackbar
anchorOrigin={{ vertical, horizontal }}
open={open}
autoHideDuration={3000}
onClose={handleClose}
message={message}
key={"Snack-Bar-key"}
/>
{open}
</div>
);
}
32 changes: 31 additions & 1 deletion src/components/UserSettings/UserSettings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -82,6 +83,8 @@ interface UserSettingsState {
emailTaken: boolean;
avatar: string;
avatarDialogOpen: boolean;
toastOpen: boolean;
toastMessage: string;
}

/**
Expand All @@ -100,6 +103,8 @@ class UserSettings extends React.Component<WithTranslation, UserSettingsState> {
emailTaken: false,
avatar: getAvatar(),
avatarDialogOpen: false,
toastOpen: false,
toastMessage: "",
};
}

Expand All @@ -126,6 +131,18 @@ class UserSettings extends React.Component<WithTranslation, UserSettingsState> {
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<HTMLFormElement>) {
e.preventDefault();
if (await this.isEmailOkay()) {
Expand All @@ -135,12 +152,24 @@ class UserSettings extends React.Component<WithTranslation, UserSettingsState> {
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 (
<PositionedSnackbar
open={this.state.toastOpen}
message={this.state.toastMessage}
vertical={"top"}
horizontal={"center"}
/>
);
}

render() {
return (
<React.Fragment>
Expand Down Expand Up @@ -251,6 +280,7 @@ class UserSettings extends React.Component<WithTranslation, UserSettingsState> {
this.setState({ avatar: getAvatar(), avatarDialogOpen: false });
}}
/>
{this.handleToastDisplay(this.state.toastOpen)}
</React.Fragment>
);
}
Expand Down
Loading