improvement: better user feedback on clicking buttons

This commit is contained in:
canove
2020-10-08 14:10:34 -03:00
parent 31bf85635d
commit c170c0c8ae
6 changed files with 145 additions and 102 deletions

View File

@@ -0,0 +1,35 @@
import React from "react";
import { makeStyles } from "@material-ui/core/styles";
import { green } from "@material-ui/core/colors";
import { CircularProgress, Button } from "@material-ui/core";
const useStyles = makeStyles(theme => ({
button: {
position: "relative",
},
buttonProgress: {
color: green[500],
position: "absolute",
top: "50%",
left: "50%",
marginTop: -12,
marginLeft: -12,
},
}));
const ButtonWithSpinner = ({ loading, children, ...rest }) => {
const classes = useStyles();
return (
<Button className={classes.button} disabled={loading} {...rest}>
{children}
{loading && (
<CircularProgress size={24} className={classes.buttonProgress} />
)}
</Button>
);
};
export default ButtonWithSpinner;

View File

@@ -195,55 +195,54 @@ const MessageInput = ({ ticketStatus }) => {
setLoading(false);
};
const handleStartRecording = () => {
navigator.getUserMedia(
{ audio: true },
() => {
Mp3Recorder.start()
.then(() => {
setRecording(true);
})
.catch(e => console.error(e));
},
() => {
console.log("Permission Denied");
}
);
};
const handleUploadAudio = () => {
const handleStartRecording = async () => {
setLoading(true);
Mp3Recorder.stop()
.getMp3()
.then(async ([buffer, blob]) => {
if (blob.size < 10000) {
setLoading(false);
setRecording(false);
return;
}
const formData = new FormData();
const filename = `${new Date().getTime()}.mp3`;
formData.append("media", blob, filename);
formData.append("body", filename);
formData.append("fromMe", true);
try {
await api.post(`/messages/${ticketId}`, formData);
} catch (err) {
console.log(err);
if (err.response && err.response.data && err.response.data.error) {
toast.error(err.response.data.error);
}
}
setRecording(false);
setLoading(false);
})
.catch(err => console.log(err));
try {
await navigator.mediaDevices.getUserMedia({ audio: true });
await Mp3Recorder.start();
setRecording(true);
setLoading(false);
} catch (err) {
console.log(err);
setLoading(false);
}
};
const handleCancelAudio = () => {
Mp3Recorder.stop()
.getMp3()
.then(() => setRecording(false));
const handleUploadAudio = async () => {
setLoading(true);
try {
const [, blob] = await Mp3Recorder.stop().getMp3();
if (blob.size < 10000) {
setLoading(false);
setRecording(false);
return;
}
const formData = new FormData();
const filename = `${new Date().getTime()}.mp3`;
formData.append("media", blob, filename);
formData.append("body", filename);
formData.append("fromMe", true);
await api.post(`/messages/${ticketId}`, formData);
setRecording(false);
setLoading(false);
} catch (err) {
console.log(err);
if (err.response && err.response.data && err.response.data.error) {
toast.error(err.response.data.error);
}
}
};
const handleCancelAudio = async () => {
try {
await Mp3Recorder.stop().getMp3();
setRecording(false);
} catch (err) {
console.log(err);
}
};
if (media.preview)

View File

@@ -13,12 +13,12 @@ import Autocomplete, {
createFilterOptions,
} from "@material-ui/lab/Autocomplete";
import CircularProgress from "@material-ui/core/CircularProgress";
import { green } from "@material-ui/core/colors";
import { makeStyles } from "@material-ui/core/styles";
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import ButtonWithSpinner from "../ButtonWithSpinner";
const useStyles = makeStyles(theme => ({
root: {
@@ -26,19 +26,11 @@ const useStyles = makeStyles(theme => ({
flexWrap: "wrap",
},
btnWrapper: {
// margin: theme.spacing(1),
position: "relative",
},
buttonProgress: {
color: green[500],
position: "absolute",
top: "50%",
left: "50%",
marginTop: -12,
marginLeft: -12,
},
// btnWrapper: {
// // margin: theme.spacing(1),
// // position: "relative",
// display: "flex",
// },
}));
const filterOptions = createFilterOptions({
@@ -163,21 +155,14 @@ const NewTicketModal = ({ modalOpen, onClose }) => {
>
{i18n.t("newTicketModal.buttons.cancel")}
</Button>
<Button
<ButtonWithSpinner
variant="contained"
type="submit"
color="primary"
disabled={loading}
variant="contained"
className={classes.btnWrapper}
loading={loading}
>
{i18n.t("newTicketModal.buttons.ok")}
{loading && (
<CircularProgress
size={24}
className={classes.buttonProgress}
/>
)}
</Button>
</ButtonWithSpinner>
</DialogActions>
</form>
</Dialog>

View File

@@ -3,12 +3,13 @@ import { useHistory } from "react-router-dom";
import { toast } from "react-toastify";
import { makeStyles } from "@material-ui/core/styles";
import { Button, IconButton } from "@material-ui/core";
import { IconButton } from "@material-ui/core";
import { MoreVert, Replay } from "@material-ui/icons";
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import TicketOptionsMenu from "../TicketOptionsMenu";
import ButtonWithSpinner from "../ButtonWithSpinner";
const useStyles = makeStyles(theme => ({
actionButtons: {
@@ -27,6 +28,7 @@ const TicketActionButtons = ({ ticket }) => {
const history = useHistory();
const userId = +localStorage.getItem("userId");
const [anchorEl, setAnchorEl] = useState(null);
const [loading, setLoading] = useState(false);
const ticketOptionsMenuOpen = Boolean(anchorEl);
const handleOpenTicketOptionsMenu = e => {
@@ -38,18 +40,21 @@ const TicketActionButtons = ({ ticket }) => {
};
const handleUpdateTicketStatus = async (e, status, userId) => {
setLoading(true);
try {
await api.put(`/tickets/${ticket.id}`, {
status: status,
userId: userId || null,
});
setLoading(false);
if (status === "open") {
history.push(`/tickets/${ticket.id}`);
} else {
history.push("/tickets");
}
} catch (err) {
setLoading(false);
console.log(err);
if (err.response && err.response.data && err.response.data.error) {
toast.error(err.response.data.error);
@@ -60,31 +65,34 @@ const TicketActionButtons = ({ ticket }) => {
return (
<div className={classes.actionButtons}>
{ticket.status === "closed" && (
<Button
<ButtonWithSpinner
loading={loading}
startIcon={<Replay />}
size="small"
onClick={e => handleUpdateTicketStatus(e, "open", userId)}
>
{i18n.t("messagesList.header.buttons.reopen")}
</Button>
</ButtonWithSpinner>
)}
{ticket.status === "open" && (
<>
<Button
<ButtonWithSpinner
loading={loading}
startIcon={<Replay />}
size="small"
onClick={e => handleUpdateTicketStatus(e, "pending", null)}
>
{i18n.t("messagesList.header.buttons.return")}
</Button>
<Button
</ButtonWithSpinner>
<ButtonWithSpinner
loading={loading}
size="small"
variant="contained"
color="primary"
onClick={e => handleUpdateTicketStatus(e, "closed", userId)}
>
{i18n.t("messagesList.header.buttons.resolve")}
</Button>
</ButtonWithSpinner>
<IconButton onClick={handleOpenTicketOptionsMenu}>
<MoreVert />
</IconButton>
@@ -97,14 +105,15 @@ const TicketActionButtons = ({ ticket }) => {
</>
)}
{ticket.status === "pending" && (
<Button
<ButtonWithSpinner
loading={loading}
size="small"
variant="contained"
color="primary"
onClick={e => handleUpdateTicketStatus(e, "open", userId)}
>
{i18n.t("messagesList.header.buttons.accept")}
</Button>
</ButtonWithSpinner>
)}
</div>
);

View File

@@ -16,12 +16,16 @@ const useStyles = makeStyles(theme => ({
const TicketHeader = ({ loading, children }) => {
const classes = useStyles();
if (loading) return <TicketHeaderSkeleton />;
return (
<Card square className={classes.ticketHeader}>
{children}
</Card>
<>
{loading ? (
<TicketHeaderSkeleton />
) : (
<Card square className={classes.ticketHeader}>
{children}
</Card>
)}
</>
);
};

View File

@@ -1,4 +1,4 @@
import React from "react";
import React, { useState, useEffect, useRef } from "react";
import { useHistory, useParams } from "react-router-dom";
import { parseISO, format, isSameDay } from "date-fns";
@@ -12,23 +12,15 @@ import Typography from "@material-ui/core/Typography";
import Avatar from "@material-ui/core/Avatar";
import Divider from "@material-ui/core/Divider";
import Badge from "@material-ui/core/Badge";
import Button from "@material-ui/core/Button";
import { i18n } from "../../translate/i18n";
import api from "../../services/api";
import ButtonWithSpinner from "../ButtonWithSpinner";
const useStyles = makeStyles(theme => ({
ticket: {
position: "relative",
"& .hidden-button": {
display: "none",
},
"&:hover .hidden-button": {
display: "flex",
position: "absolute",
left: "50%",
},
},
noTicketsDiv: {
display: "flex",
@@ -83,23 +75,41 @@ const useStyles = makeStyles(theme => ({
color: "white",
backgroundColor: green[500],
},
acceptButton: {
position: "absolute",
left: "50%",
},
}));
const TicketListItem = ({ ticket }) => {
const classes = useStyles();
const history = useHistory();
const userId = +localStorage.getItem("userId");
const [loading, setLoading] = useState(false);
const { ticketId } = useParams();
const isMounted = useRef(true);
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
const handleAcepptTicket = async ticketId => {
setLoading(true);
try {
await api.put(`/tickets/${ticketId}`, {
status: "open",
userId: userId,
});
} catch (err) {
setLoading(false);
alert(err);
}
if (isMounted.current) {
setLoading(false);
}
history.push(`/tickets/${ticketId}`);
};
@@ -180,17 +190,18 @@ const TicketListItem = ({ ticket }) => {
</span>
}
/>
{ticket.status === "pending" ? (
<Button
variant="contained"
size="small"
{ticket.status === "pending" && (
<ButtonWithSpinner
color="primary"
className="hidden-button"
variant="contained"
className={classes.acceptButton}
size="small"
loading={loading}
onClick={e => handleAcepptTicket(ticket.id)}
>
{i18n.t("ticketsList.buttons.accept")}
</Button>
) : null}
</ButtonWithSpinner>
)}
</ListItem>
<Divider variant="inset" component="li" />
</React.Fragment>