mirror of
https://github.com/cheveguerra/whaticket-community.git
synced 2026-04-20 12:49:32 +00:00
feat: start internatinalization
This commit is contained in:
3
frontend/.gitignore
vendored
3
frontend/.gitignore
vendored
@@ -5,6 +5,7 @@
|
|||||||
/.pnp
|
/.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
|
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
/coverage
|
/coverage
|
||||||
|
|
||||||
@@ -22,6 +23,6 @@
|
|||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
"date-fns": "^2.14.0",
|
"date-fns": "^2.14.0",
|
||||||
"emoji-mart": "^3.0.0",
|
"emoji-mart": "^3.0.0",
|
||||||
"formik": "^2.1.5",
|
"formik": "^2.1.5",
|
||||||
|
"i18next": "^19.6.3",
|
||||||
|
"i18next-browser-languagedetector": "^5.0.1",
|
||||||
"mic-recorder-to-mp3": "^2.2.1",
|
"mic-recorder-to-mp3": "^2.2.1",
|
||||||
"qrcode.react": "^1.0.0",
|
"qrcode.react": "^1.0.0",
|
||||||
"react": "^16.13.1",
|
"react": "^16.13.1",
|
||||||
|
|||||||
@@ -2,10 +2,11 @@ import React, { useState, useEffect } from "react";
|
|||||||
|
|
||||||
import { Formik, FieldArray } from "formik";
|
import { Formik, FieldArray } from "formik";
|
||||||
|
|
||||||
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
import { green } from "@material-ui/core/colors";
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
import Dialog from "@material-ui/core/Dialog";
|
import Dialog from "@material-ui/core/Dialog";
|
||||||
|
|
||||||
import DialogActions from "@material-ui/core/DialogActions";
|
import DialogActions from "@material-ui/core/DialogActions";
|
||||||
import DialogContent from "@material-ui/core/DialogContent";
|
import DialogContent from "@material-ui/core/DialogContent";
|
||||||
import DialogTitle from "@material-ui/core/DialogTitle";
|
import DialogTitle from "@material-ui/core/DialogTitle";
|
||||||
@@ -13,9 +14,8 @@ import Typography from "@material-ui/core/Typography";
|
|||||||
import IconButton from "@material-ui/core/IconButton";
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
|
import DeleteOutlineIcon from "@material-ui/icons/DeleteOutline";
|
||||||
import CircularProgress from "@material-ui/core/CircularProgress";
|
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 api from "../../services/api";
|
||||||
|
|
||||||
@@ -121,14 +121,16 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
|||||||
}) => (
|
}) => (
|
||||||
<form onSubmit={handleSubmit}>
|
<form onSubmit={handleSubmit}>
|
||||||
<DialogTitle id="form-dialog-title">
|
<DialogTitle id="form-dialog-title">
|
||||||
{contactId ? "Editar contato" : "Adicionar contato"}
|
{contactId
|
||||||
|
? `${i18n.t("contactModal.title.edit")}`
|
||||||
|
: `${i18n.t("contactModal.title.add")}`}
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<Typography variant="subtitle1" gutterBottom>
|
<Typography variant="subtitle1" gutterBottom>
|
||||||
Dados do contato
|
{i18n.t("contactModal.form.mainInfo")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<TextField
|
<TextField
|
||||||
label="Nome"
|
label={i18n.t("contactModal.form.name")}
|
||||||
name="name"
|
name="name"
|
||||||
value={values.name || ""}
|
value={values.name || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -138,18 +140,18 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
|||||||
className={classes.textField}
|
className={classes.textField}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Número do Whatsapp"
|
label={i18n.t("contactModal.form.number")}
|
||||||
name="number"
|
name="number"
|
||||||
value={values.number || ""}
|
value={values.number || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
placeholder="Ex: 5513912344321"
|
placeholder="5513912344321"
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
margin="dense"
|
margin="dense"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label="Email"
|
label={i18n.t("contactModal.form.email")}
|
||||||
name="email"
|
name="email"
|
||||||
value={values.email || ""}
|
value={values.email || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -163,7 +165,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
|||||||
style={{ marginBottom: 8, marginTop: 12 }}
|
style={{ marginBottom: 8, marginTop: 12 }}
|
||||||
variant="subtitle1"
|
variant="subtitle1"
|
||||||
>
|
>
|
||||||
Informações adicionais
|
{i18n.t("contactModal.form.extraInfo")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<FieldArray name="extraInfo">
|
<FieldArray name="extraInfo">
|
||||||
@@ -177,7 +179,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
|||||||
key={`${index}-info`}
|
key={`${index}-info`}
|
||||||
>
|
>
|
||||||
<TextField
|
<TextField
|
||||||
label="Nome do campo"
|
label={i18n.t("contactModal.form.extraName")}
|
||||||
name={`extraInfo[${index}].name`}
|
name={`extraInfo[${index}].name`}
|
||||||
value={info.name || ""}
|
value={info.name || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -187,7 +189,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
|||||||
className={classes.textField}
|
className={classes.textField}
|
||||||
/>
|
/>
|
||||||
<TextField
|
<TextField
|
||||||
label="Valor"
|
label={i18n.t("contactModal.form.extraValue")}
|
||||||
name={`extraInfo[${index}].value`}
|
name={`extraInfo[${index}].value`}
|
||||||
value={info.value || ""}
|
value={info.value || ""}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
@@ -211,7 +213,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={() => push({ name: "", value: "" })}
|
onClick={() => push({ name: "", value: "" })}
|
||||||
>
|
>
|
||||||
+ Adicionar atributo
|
{`+ ${i18n.t("contactModal.buttons.addExtraInfo")}`}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
@@ -225,7 +227,7 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
|||||||
disabled={isSubmitting}
|
disabled={isSubmitting}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
>
|
>
|
||||||
Cancelar
|
{i18n.t("contactModal.buttons.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -234,7 +236,9 @@ const ContactModal = ({ open, onClose, contactId }) => {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
className={classes.btnWrapper}
|
className={classes.btnWrapper}
|
||||||
>
|
>
|
||||||
{contactId ? "Salvar" : "Adicionar"}
|
{contactId
|
||||||
|
? `${i18n.t("contactModal.buttons.okEdit")}`
|
||||||
|
: `${i18n.t("contactModal.buttons.okAdd")}`}
|
||||||
{isSubmitting && (
|
{isSubmitting && (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={24}
|
size={24}
|
||||||
|
|||||||
@@ -1,28 +1,16 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { Link as RouterLink } from "react-router-dom";
|
import { Link as RouterLink } from "react-router-dom";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
|
||||||
|
|
||||||
import List from "@material-ui/core/List";
|
|
||||||
import ListItem from "@material-ui/core/ListItem";
|
import ListItem from "@material-ui/core/ListItem";
|
||||||
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
import ListItemIcon from "@material-ui/core/ListItemIcon";
|
||||||
import ListItemText from "@material-ui/core/ListItemText";
|
import ListItemText from "@material-ui/core/ListItemText";
|
||||||
// import ListSubheader from "@material-ui/core/ListSubheader";
|
|
||||||
import Collapse from "@material-ui/core/Collapse";
|
|
||||||
|
|
||||||
import DashboardIcon from "@material-ui/icons/Dashboard";
|
import DashboardIcon from "@material-ui/icons/Dashboard";
|
||||||
import WhatsAppIcon from "@material-ui/icons/WhatsApp";
|
import WhatsAppIcon from "@material-ui/icons/WhatsApp";
|
||||||
import SyncAltIcon from "@material-ui/icons/SyncAlt";
|
import SyncAltIcon from "@material-ui/icons/SyncAlt";
|
||||||
import ChatIcon from "@material-ui/icons/Chat";
|
|
||||||
import LayersIcon from "@material-ui/icons/Layers";
|
|
||||||
import ExpandLess from "@material-ui/icons/ExpandLess";
|
|
||||||
import ExpandMore from "@material-ui/icons/ExpandMore";
|
|
||||||
import ContactPhoneIcon from "@material-ui/icons/ContactPhone";
|
import ContactPhoneIcon from "@material-ui/icons/ContactPhone";
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
import { i18n } from "../../translate/i18n";
|
||||||
nested: {
|
|
||||||
paddingLeft: theme.spacing(4),
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
function ListItemLink(props) {
|
function ListItemLink(props) {
|
||||||
const { icon, primary, to, className } = props;
|
const { icon, primary, to, className } = props;
|
||||||
@@ -46,76 +34,27 @@ function ListItemLink(props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const MainListItems = () => {
|
const MainListItems = () => {
|
||||||
const classes = useStyles();
|
|
||||||
const [open, setOpen] = React.useState(false);
|
|
||||||
|
|
||||||
const handleClick = () => {
|
|
||||||
setOpen(!open);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<ListItemLink to="/" primary="Dashboard" icon={<DashboardIcon />} />
|
<ListItemLink to="/" primary="Dashboard" icon={<DashboardIcon />} />
|
||||||
|
|
||||||
<ListItem button onClick={handleClick}>
|
|
||||||
<ListItemIcon>
|
|
||||||
<WhatsAppIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary="WhatsApp" />
|
|
||||||
{open ? <ExpandLess /> : <ExpandMore />}
|
|
||||||
</ListItem>
|
|
||||||
<Collapse in={open} timeout="auto" unmountOnExit>
|
|
||||||
<List component="div" disablePadding>
|
|
||||||
<ListItemLink
|
<ListItemLink
|
||||||
className={classes.nested}
|
|
||||||
to="/whats-auth"
|
to="/whats-auth"
|
||||||
primary="Conexão"
|
primary={i18n.t("mainDrawer.listItems.connection")}
|
||||||
icon={<SyncAltIcon />}
|
icon={<SyncAltIcon />}
|
||||||
/>
|
/>
|
||||||
<ListItemLink
|
<ListItemLink
|
||||||
className={classes.nested}
|
|
||||||
to="/chat"
|
to="/chat"
|
||||||
primary="Chat"
|
primary={i18n.t("mainDrawer.listItems.tickets")}
|
||||||
icon={<ChatIcon />}
|
icon={<WhatsAppIcon />}
|
||||||
/>
|
/>
|
||||||
</List>
|
|
||||||
</Collapse>
|
|
||||||
<ListItemLink
|
<ListItemLink
|
||||||
to="/contacts"
|
to="/contacts"
|
||||||
primary="Contatos"
|
primary={i18n.t("mainDrawer.listItems.contacts")}
|
||||||
icon={<ContactPhoneIcon />}
|
icon={<ContactPhoneIcon />}
|
||||||
/>
|
/>
|
||||||
<ListItem button disabled>
|
|
||||||
<ListItemIcon>
|
|
||||||
<LayersIcon />
|
|
||||||
</ListItemIcon>
|
|
||||||
<ListItemText primary="Integrações" />
|
|
||||||
</ListItem>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
// export const secondaryListItems = (
|
|
||||||
// <div>
|
|
||||||
// <ListSubheader inset>Saved reports</ListSubheader>
|
|
||||||
// <ListItem button>
|
|
||||||
// <ListItemIcon>
|
|
||||||
// <AssignmentIcon />
|
|
||||||
// </ListItemIcon>
|
|
||||||
// <ListItemText primary="Current month" />
|
|
||||||
// </ListItem>
|
|
||||||
// <ListItem button>
|
|
||||||
// <ListItemIcon>
|
|
||||||
// <AssignmentIcon />
|
|
||||||
// </ListItemIcon>
|
|
||||||
// <ListItemText primary="Last quarter" />
|
|
||||||
// </ListItem>
|
|
||||||
// <ListItem button>
|
|
||||||
// <ListItemIcon>
|
|
||||||
// <AssignmentIcon />
|
|
||||||
// </ListItemIcon>
|
|
||||||
// <ListItemText primary="Year-end sale" />
|
|
||||||
// </ListItem>
|
|
||||||
// </div>
|
|
||||||
// );
|
|
||||||
|
|
||||||
export default MainListItems;
|
export default MainListItems;
|
||||||
|
|||||||
@@ -221,8 +221,6 @@ const useStyles = makeStyles(theme => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let socket;
|
|
||||||
|
|
||||||
const MessagesList = () => {
|
const MessagesList = () => {
|
||||||
const { ticketId } = useParams();
|
const { ticketId } = useParams();
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@@ -272,17 +270,9 @@ const MessagesList = () => {
|
|||||||
}, [pageNumber, ticketId, history]);
|
}, [pageNumber, ticketId, history]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||||
socket.emit("joinChatBox", ticketId, () => {});
|
socket.emit("joinChatBox", ticketId, () => {});
|
||||||
|
|
||||||
return () => {
|
|
||||||
socket.disconnect();
|
|
||||||
setPageNumber(1);
|
|
||||||
setMessagesList([]);
|
|
||||||
};
|
|
||||||
}, [ticketId]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
socket.on("appMessage", data => {
|
socket.on("appMessage", data => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
|
|
||||||
@@ -300,7 +290,13 @@ const MessagesList = () => {
|
|||||||
setContact(data.contact);
|
setContact(data.contact);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}, [loading]);
|
|
||||||
|
return () => {
|
||||||
|
socket.disconnect();
|
||||||
|
setPageNumber(1);
|
||||||
|
setMessagesList([]);
|
||||||
|
};
|
||||||
|
}, [ticketId, loading]);
|
||||||
|
|
||||||
const loadMore = () => {
|
const loadMore = () => {
|
||||||
setPageNumber(prevPageNumber => prevPageNumber + 1);
|
setPageNumber(prevPageNumber => prevPageNumber + 1);
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import { green } from "@material-ui/core/colors";
|
|||||||
|
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
import api from "../../services/api";
|
import api from "../../services/api";
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
@@ -101,7 +102,9 @@ const NewTicketModal = ({ modalOpen, onClose, contactId }) => {
|
|||||||
scroll="paper"
|
scroll="paper"
|
||||||
>
|
>
|
||||||
<form onSubmit={handleSaveTicket}>
|
<form onSubmit={handleSaveTicket}>
|
||||||
<DialogTitle id="form-dialog-title">Criar Ticket</DialogTitle>
|
<DialogTitle id="form-dialog-title">
|
||||||
|
{i18n.t("newTicketModal.title")}
|
||||||
|
</DialogTitle>
|
||||||
<DialogContent dividers>
|
<DialogContent dividers>
|
||||||
<Autocomplete
|
<Autocomplete
|
||||||
id="asynchronous-demo"
|
id="asynchronous-demo"
|
||||||
@@ -115,7 +118,7 @@ const NewTicketModal = ({ modalOpen, onClose, contactId }) => {
|
|||||||
renderInput={params => (
|
renderInput={params => (
|
||||||
<TextField
|
<TextField
|
||||||
{...params}
|
{...params}
|
||||||
label="Digite para pesquisar o contato"
|
label={i18n.t("newTicketModal.fieldLabel")}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -143,7 +146,7 @@ const NewTicketModal = ({ modalOpen, onClose, contactId }) => {
|
|||||||
disabled={loading}
|
disabled={loading}
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
>
|
>
|
||||||
Cancelar
|
{i18n.t("newTicketModal.buttons.cancel")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
@@ -152,7 +155,7 @@ const NewTicketModal = ({ modalOpen, onClose, contactId }) => {
|
|||||||
variant="contained"
|
variant="contained"
|
||||||
className={classes.btnWrapper}
|
className={classes.btnWrapper}
|
||||||
>
|
>
|
||||||
Salvar
|
{i18n.t("newTicketModal.buttons.ok")}
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
size={24}
|
size={24}
|
||||||
|
|||||||
@@ -2,11 +2,13 @@ import React from "react";
|
|||||||
import QRCode from "qrcode.react";
|
import QRCode from "qrcode.react";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
|
|
||||||
const Qrcode = ({ qrCode }) => {
|
const Qrcode = ({ qrCode }) => {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Typography color="primary" gutterBottom>
|
<Typography color="primary" gutterBottom>
|
||||||
Leia o QrCode para iniciar a sessão
|
{i18n.t("qrCode.message")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<QRCode value={qrCode} size={256} />
|
<QRCode value={qrCode} size={256} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
|
|
||||||
const SessionInfo = ({ session }) => {
|
const SessionInfo = ({ session }) => {
|
||||||
console.log(session);
|
console.log(session);
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
<Typography component="h2" variant="h6" color="primary" gutterBottom>
|
||||||
{`Status: ${session.status}`}
|
{`${i18n.t("sessionInfo.status")}${session.status}`}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography component="p" variant="h6">
|
<Typography component="p" variant="h6">
|
||||||
{`Bateria: ${session.battery}%`}
|
{`${i18n.t("sessionInfo.battery")}${session.battery}%`}
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography color="textSecondary">
|
<Typography color="textSecondary">
|
||||||
{`Carregando: ${session.plugged} `}
|
{`${i18n.t("sessionInfo.charging")}${session.plugged} `}
|
||||||
</Typography>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -23,10 +23,9 @@ import NewTicketModal from "../NewTicketModal";
|
|||||||
import TicketsList from "../TicketsList";
|
import TicketsList from "../TicketsList";
|
||||||
import TabPanel from "../TabPanel";
|
import TabPanel from "../TabPanel";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
import api from "../../services/api";
|
import api from "../../services/api";
|
||||||
|
|
||||||
let socket;
|
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
ticketsWrapper: {
|
ticketsWrapper: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
@@ -209,15 +208,9 @@ const Tickets = () => {
|
|||||||
}, [searchParam, pageNumber, token, tab]);
|
}, [searchParam, pageNumber, token, tab]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||||
socket.emit("joinNotification");
|
socket.emit("joinNotification");
|
||||||
|
|
||||||
return () => {
|
|
||||||
socket.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
socket.on("ticket", data => {
|
socket.on("ticket", data => {
|
||||||
if (loading) return;
|
if (loading) return;
|
||||||
|
|
||||||
@@ -232,7 +225,7 @@ const Tickets = () => {
|
|||||||
if (data.action === "delete") {
|
if (data.action === "delete") {
|
||||||
deleteTicket(data);
|
deleteTicket(data);
|
||||||
if (ticketId && data.ticketId === +ticketId) {
|
if (ticketId && data.ticketId === +ticketId) {
|
||||||
toast.warn("O ticket que você estava foi deletado.");
|
toast.warn(i18n.t("tickets.toasts.deleted"));
|
||||||
history.push("/chat");
|
history.push("/chat");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -253,6 +246,10 @@ const Tickets = () => {
|
|||||||
showDesktopNotification(data);
|
showDesktopNotification(data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.disconnect();
|
||||||
|
};
|
||||||
}, [history, ticketId, userId, loading]);
|
}, [history, ticketId, userId, loading]);
|
||||||
|
|
||||||
const loadMore = () => {
|
const loadMore = () => {
|
||||||
@@ -262,8 +259,6 @@ const Tickets = () => {
|
|||||||
const updateTickets = ({ ticket }) => {
|
const updateTickets = ({ ticket }) => {
|
||||||
setTickets(prevState => {
|
setTickets(prevState => {
|
||||||
const ticketIndex = prevState.findIndex(t => t.id === ticket.id);
|
const ticketIndex = prevState.findIndex(t => t.id === ticket.id);
|
||||||
|
|
||||||
if (prevState.length >= 20) {
|
|
||||||
if (ticketIndex !== -1) {
|
if (ticketIndex !== -1) {
|
||||||
let aux = [...prevState];
|
let aux = [...prevState];
|
||||||
aux[ticketIndex] = ticket;
|
aux[ticketIndex] = ticket;
|
||||||
@@ -272,9 +267,6 @@ const Tickets = () => {
|
|||||||
} else {
|
} else {
|
||||||
return [ticket, ...prevState];
|
return [ticket, ...prevState];
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
return [ticket, ...prevState];
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -298,7 +290,10 @@ const Tickets = () => {
|
|||||||
icon: contact.profilePicUrl,
|
icon: contact.profilePicUrl,
|
||||||
tag: ticket.id,
|
tag: ticket.id,
|
||||||
};
|
};
|
||||||
let notification = new Notification(`Mensagem de ${contact.name}`, options);
|
let notification = new Notification(
|
||||||
|
`${i18n.t("tickets.notification.message")} ${contact.name}`,
|
||||||
|
options
|
||||||
|
);
|
||||||
|
|
||||||
notification.onclick = function (event) {
|
notification.onclick = function (event) {
|
||||||
event.preventDefault(); //
|
event.preventDefault(); //
|
||||||
@@ -396,19 +391,19 @@ const Tickets = () => {
|
|||||||
<Tab
|
<Tab
|
||||||
value={"open"}
|
value={"open"}
|
||||||
icon={<MoveToInboxIcon />}
|
icon={<MoveToInboxIcon />}
|
||||||
label="Inbox"
|
label={i18n.t("tickets.tabs.open.title")}
|
||||||
classes={{ root: classes.tab }}
|
classes={{ root: classes.tab }}
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
value={"closed"}
|
value={"closed"}
|
||||||
icon={<CheckBoxIcon />}
|
icon={<CheckBoxIcon />}
|
||||||
label="Resolvidos"
|
label={i18n.t("tickets.tabs.closed.title")}
|
||||||
classes={{ root: classes.tab }}
|
classes={{ root: classes.tab }}
|
||||||
/>
|
/>
|
||||||
<Tab
|
<Tab
|
||||||
value={"search"}
|
value={"search"}
|
||||||
icon={<SearchIcon />}
|
icon={<SearchIcon />}
|
||||||
label="Busca"
|
label={i18n.t("tickets.tabs.search.title")}
|
||||||
classes={{ root: classes.tab }}
|
classes={{ root: classes.tab }}
|
||||||
/>
|
/>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
@@ -418,7 +413,7 @@ const Tickets = () => {
|
|||||||
<SearchIcon className={classes.searchIcon} />
|
<SearchIcon className={classes.searchIcon} />
|
||||||
<InputBase
|
<InputBase
|
||||||
className={classes.contactsSearchInput}
|
className={classes.contactsSearchInput}
|
||||||
placeholder="Pesquisar tickets e mensagens"
|
placeholder={i18n.t("tickets.search.placeholder")}
|
||||||
type="search"
|
type="search"
|
||||||
onChange={handleSearchContact}
|
onChange={handleSearchContact}
|
||||||
/>
|
/>
|
||||||
@@ -432,19 +427,19 @@ const Tickets = () => {
|
|||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
<div className={classes.ticketsListHeader}>
|
<div className={classes.ticketsListHeader}>
|
||||||
Atendendo
|
{i18n.t("tickets.tabs.open.assignedHeader")}
|
||||||
<span className={classes.ticketsCount}>
|
<span className={classes.ticketsCount}>
|
||||||
{countTickets("open", userId)}
|
{countTickets("open", userId)}
|
||||||
</span>
|
</span>
|
||||||
<div className={classes.ticketsListActions}>
|
<div className={classes.ticketsListActions}>
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
label="Todos"
|
label={i18n.t("tickets.buttons.showAll")}
|
||||||
labelPlacement="start"
|
labelPlacement="start"
|
||||||
control={
|
control={
|
||||||
<Switch
|
<Switch
|
||||||
size="small"
|
size="small"
|
||||||
checked={showAllTickets}
|
checked={showAllTickets}
|
||||||
onChange={e => setShowAllTickets(prevState => !prevState)}
|
onChange={() => setShowAllTickets(prevState => !prevState)}
|
||||||
name="showAllTickets"
|
name="showAllTickets"
|
||||||
color="primary"
|
color="primary"
|
||||||
/>
|
/>
|
||||||
@@ -467,8 +462,10 @@ const Tickets = () => {
|
|||||||
showAllTickets={showAllTickets}
|
showAllTickets={showAllTickets}
|
||||||
ticketId={ticketId}
|
ticketId={ticketId}
|
||||||
handleAcepptTicket={handleAcepptTicket}
|
handleAcepptTicket={handleAcepptTicket}
|
||||||
noTicketsTitle="Pronto pra mais?"
|
noTicketsTitle={i18n.t("tickets.tabs.open.openNoTicketsTitle")}
|
||||||
noTicketsMessage="Aceite um ticket da fila para começar."
|
noTicketsMessage={i18n.t(
|
||||||
|
"tickets.tabs.open.openNoTicketsMessage"
|
||||||
|
)}
|
||||||
status="open"
|
status="open"
|
||||||
userId={userId}
|
userId={userId}
|
||||||
/>
|
/>
|
||||||
@@ -482,7 +479,7 @@ const Tickets = () => {
|
|||||||
onScroll={handleScroll}
|
onScroll={handleScroll}
|
||||||
>
|
>
|
||||||
<div className={classes.ticketsListHeader}>
|
<div className={classes.ticketsListHeader}>
|
||||||
Aguardando
|
{i18n.t("tickets.tabs.open.pendingHeader")}
|
||||||
<span className={classes.ticketsCount}>
|
<span className={classes.ticketsCount}>
|
||||||
{countTickets("pending", null)}
|
{countTickets("pending", null)}
|
||||||
</span>
|
</span>
|
||||||
@@ -495,8 +492,10 @@ const Tickets = () => {
|
|||||||
showAllTickets={showAllTickets}
|
showAllTickets={showAllTickets}
|
||||||
ticketId={ticketId}
|
ticketId={ticketId}
|
||||||
handleAcepptTicket={handleAcepptTicket}
|
handleAcepptTicket={handleAcepptTicket}
|
||||||
noTicketsTitle="Tudo resolvido!"
|
noTicketsTitle={i18n.t("tickets.tabs.open.pendingNoTicketsTitle")}
|
||||||
noTicketsMessage="Nenhum ticket pendente."
|
noTicketsMessage={i18n.t(
|
||||||
|
"tickets.tabs.open.pendingNoTicketsMessage"
|
||||||
|
)}
|
||||||
status="pending"
|
status="pending"
|
||||||
userId={null}
|
userId={null}
|
||||||
/>
|
/>
|
||||||
@@ -541,8 +540,8 @@ const Tickets = () => {
|
|||||||
showAllTickets={showAllTickets}
|
showAllTickets={showAllTickets}
|
||||||
ticketId={ticketId}
|
ticketId={ticketId}
|
||||||
handleAcepptTicket={handleAcepptTicket}
|
handleAcepptTicket={handleAcepptTicket}
|
||||||
noTicketsTitle="Nada encontrado!"
|
noTicketsTitle={i18n.t("tickets.tabs.search.noTicketsTitle")}
|
||||||
noTicketsMessage="Tente buscar por outro termo."
|
noTicketsMessage={i18n.t("tickets.tabs.search.noTicketsMessage")}
|
||||||
status="all"
|
status="all"
|
||||||
/>
|
/>
|
||||||
{loading && <TicketsSkeleton />}
|
{loading && <TicketsSkeleton />}
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ import Divider from "@material-ui/core/Divider";
|
|||||||
import Badge from "@material-ui/core/Badge";
|
import Badge from "@material-ui/core/Badge";
|
||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
ticket: {
|
ticket: {
|
||||||
position: "relative",
|
position: "relative",
|
||||||
@@ -185,7 +187,7 @@ const TicketsList = ({
|
|||||||
className="hidden-button"
|
className="hidden-button"
|
||||||
onClick={e => handleAcepptTicket(ticket.id)}
|
onClick={e => handleAcepptTicket(ticket.id)}
|
||||||
>
|
>
|
||||||
Aceitar
|
{i18n.t("ticketsList.buttons.accept")}
|
||||||
</Button>
|
</Button>
|
||||||
) : null}
|
) : null}
|
||||||
</ListItem>
|
</ListItem>
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useHistory } from "react-router-dom";
|
|||||||
|
|
||||||
import { toast } from "react-toastify";
|
import { toast } from "react-toastify";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
import api from "../../services/api";
|
import api from "../../services/api";
|
||||||
|
|
||||||
const useAuth = () => {
|
const useAuth = () => {
|
||||||
@@ -23,7 +24,6 @@ const useAuth = () => {
|
|||||||
history.location.pathname === "/signup"
|
history.location.pathname === "/signup"
|
||||||
) {
|
) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -35,7 +35,7 @@ const useAuth = () => {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setIsAuth(false);
|
setIsAuth(false);
|
||||||
toast.error("Erro de autenticação. Por favor, faça login novamente");
|
toast.error(i18n.t("auth.toasts.fail"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
checkAuth();
|
checkAuth();
|
||||||
@@ -50,9 +50,10 @@ const useAuth = () => {
|
|||||||
localStorage.setItem("userId", res.data.userId);
|
localStorage.setItem("userId", res.data.userId);
|
||||||
api.defaults.headers.Authorization = `Bearer ${res.data.token}`;
|
api.defaults.headers.Authorization = `Bearer ${res.data.token}`;
|
||||||
setIsAuth(true);
|
setIsAuth(true);
|
||||||
|
toast.success(i18n.t("auth.toasts.success"));
|
||||||
history.push("/chat");
|
history.push("/chat");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error("Erro de autenticação. Verifique os dados de login");
|
toast.error(i18n.t("auth.toasts.fail"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,8 @@ import { makeStyles } from "@material-ui/core/styles";
|
|||||||
import Tickets from "../../components/Tickets/";
|
import Tickets from "../../components/Tickets/";
|
||||||
import MessagesList from "../../components/MessagesList/";
|
import MessagesList from "../../components/MessagesList/";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
chatContainer: {
|
chatContainer: {
|
||||||
flex: 1,
|
flex: 1,
|
||||||
@@ -61,7 +63,7 @@ const Chat = () => {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<Paper square variant="outlined" className={classes.welcomeMsg}>
|
<Paper square variant="outlined" className={classes.welcomeMsg}>
|
||||||
<span>Selecione um contato para começar a conversar</span>
|
<span>{i18n.t("chat.noTicketMessage")}</span>
|
||||||
</Paper>
|
</Paper>
|
||||||
)}
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import ContactsSekeleton from "../../components/ContactsSekeleton";
|
|||||||
import ContactModal from "../../components/ContactModal";
|
import ContactModal from "../../components/ContactModal";
|
||||||
import ConfirmationModal from "../../components/ConfirmationModal/";
|
import ConfirmationModal from "../../components/ConfirmationModal/";
|
||||||
|
|
||||||
let socket;
|
import { i18n } from "../../translate/i18n";
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
mainContainer: {
|
mainContainer: {
|
||||||
@@ -105,13 +105,7 @@ const Contacts = () => {
|
|||||||
}, [searchParam, page, rowsPerPage]);
|
}, [searchParam, page, rowsPerPage]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||||
return () => {
|
|
||||||
socket.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
socket.on("contact", data => {
|
socket.on("contact", data => {
|
||||||
if ((data.action === "update" || data.action === "create") && !loading) {
|
if ((data.action === "update" || data.action === "create") && !loading) {
|
||||||
updateContacts(data.contact);
|
updateContacts(data.contact);
|
||||||
@@ -121,6 +115,10 @@ const Contacts = () => {
|
|||||||
deleteContact(data.contactId);
|
deleteContact(data.contactId);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.disconnect();
|
||||||
|
};
|
||||||
}, [loading]);
|
}, [loading]);
|
||||||
|
|
||||||
const updateContacts = contact => {
|
const updateContacts = contact => {
|
||||||
@@ -206,8 +204,10 @@ const Contacts = () => {
|
|||||||
<ConfirmationModal
|
<ConfirmationModal
|
||||||
title={
|
title={
|
||||||
deletingContact
|
deletingContact
|
||||||
? `Deletar ${deletingContact.name}?`
|
? `${i18n.t("contacts.confirmationModal.deleteTitle")} ${
|
||||||
: `Importar contatos`
|
deletingContact.name
|
||||||
|
}?`
|
||||||
|
: `${i18n.t("contacts.confirmationModal.importTitlte")}`
|
||||||
}
|
}
|
||||||
open={confirmOpen}
|
open={confirmOpen}
|
||||||
setOpen={setConfirmOpen}
|
setOpen={setConfirmOpen}
|
||||||
@@ -218,17 +218,17 @@ const Contacts = () => {
|
|||||||
}
|
}
|
||||||
>
|
>
|
||||||
{deletingContact
|
{deletingContact
|
||||||
? "Tem certeza que deseja deletar este contato? Todos os tickets relacionados serão perdidos."
|
? `${i18n.t("contacts.confirmationModal.deleteMessage")}`
|
||||||
: "Deseja importas todos os contatos do telefone? Essa função é experimental, você terá que recarregar a página após a importação "}
|
: `${i18n.t("contacts.confirmationModal.importMessage")}`}
|
||||||
</ConfirmationModal>
|
</ConfirmationModal>
|
||||||
<div className={classes.contactsHeader}>
|
<div className={classes.contactsHeader}>
|
||||||
<Typography variant="h5" gutterBottom>
|
<Typography variant="h5" gutterBottom>
|
||||||
Contatos
|
{i18n.t("contacts.title")}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
|
||||||
<div className={classes.actionButtons}>
|
<div className={classes.actionButtons}>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="Pesquisar..."
|
placeholder={i18n.t("contacts.searchPlaceholder")}
|
||||||
type="search"
|
type="search"
|
||||||
value={searchParam}
|
value={searchParam}
|
||||||
onChange={handleSearch}
|
onChange={handleSearch}
|
||||||
@@ -245,14 +245,14 @@ const Contacts = () => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
onClick={e => setConfirmOpen(true)}
|
onClick={e => setConfirmOpen(true)}
|
||||||
>
|
>
|
||||||
Importar contatos
|
{i18n.t("contacts.buttons.import")}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
color="primary"
|
color="primary"
|
||||||
onClick={handleOpenContactModal}
|
onClick={handleOpenContactModal}
|
||||||
>
|
>
|
||||||
Adicionar contato
|
{i18n.t("contacts.buttons.add")}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -261,10 +261,12 @@ const Contacts = () => {
|
|||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell padding="checkbox" />
|
<TableCell padding="checkbox" />
|
||||||
<TableCell>Nome</TableCell>
|
<TableCell>{i18n.t("contacts.table.name")}</TableCell>
|
||||||
<TableCell>Whatsapp</TableCell>
|
<TableCell>{i18n.t("contacts.table.whatsapp")}</TableCell>
|
||||||
<TableCell>Email</TableCell>
|
<TableCell>{i18n.t("contacts.table.email")}</TableCell>
|
||||||
<TableCell align="right">Ações</TableCell>
|
<TableCell align="right">
|
||||||
|
{i18n.t("contacts.table.actions")}
|
||||||
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
|
|||||||
6
frontend/src/pages/Dashboard/Chart.js
vendored
6
frontend/src/pages/Dashboard/Chart.js
vendored
@@ -11,6 +11,8 @@ import {
|
|||||||
} from "recharts";
|
} from "recharts";
|
||||||
import { startOfHour, parseISO, format } from "date-fns";
|
import { startOfHour, parseISO, format } from "date-fns";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
|
|
||||||
import Title from "./Title";
|
import Title from "./Title";
|
||||||
import api from "../../services/api";
|
import api from "../../services/api";
|
||||||
|
|
||||||
@@ -69,7 +71,9 @@ const Chart = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<Title>{`Tickets hoje: ${tickets.length}`}</Title>
|
<Title>{`${i18n.t("dashboard.charts.perDay.title")}${
|
||||||
|
tickets.length
|
||||||
|
}`}</Title>
|
||||||
<ResponsiveContainer>
|
<ResponsiveContainer>
|
||||||
<BarChart
|
<BarChart
|
||||||
data={chartData}
|
data={chartData}
|
||||||
|
|||||||
@@ -5,8 +5,6 @@ import Avatar from "@material-ui/core/Avatar";
|
|||||||
import Button from "@material-ui/core/Button";
|
import Button from "@material-ui/core/Button";
|
||||||
import CssBaseline from "@material-ui/core/CssBaseline";
|
import CssBaseline from "@material-ui/core/CssBaseline";
|
||||||
import TextField from "@material-ui/core/TextField";
|
import TextField from "@material-ui/core/TextField";
|
||||||
// import FormControlLabel from "@material-ui/core/FormControlLabel";
|
|
||||||
// import Checkbox from "@material-ui/core/Checkbox";
|
|
||||||
import Link from "@material-ui/core/Link";
|
import Link from "@material-ui/core/Link";
|
||||||
import Grid from "@material-ui/core/Grid";
|
import Grid from "@material-ui/core/Grid";
|
||||||
import Box from "@material-ui/core/Box";
|
import Box from "@material-ui/core/Box";
|
||||||
@@ -15,13 +13,15 @@ import Typography from "@material-ui/core/Typography";
|
|||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import Container from "@material-ui/core/Container";
|
import Container from "@material-ui/core/Container";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
|
|
||||||
import { AuthContext } from "../../context/Auth/AuthContext";
|
import { AuthContext } from "../../context/Auth/AuthContext";
|
||||||
|
|
||||||
const Copyright = () => {
|
const Copyright = () => {
|
||||||
return (
|
return (
|
||||||
<Typography variant="body2" color="textSecondary" align="center">
|
<Typography variant="body2" color="textSecondary" align="center">
|
||||||
{"Copyright © "}
|
{"Copyright © "}
|
||||||
<Link color="inherit" href="https://material-ui.com/">
|
<Link color="inherit" href="https://economicros.com.br/">
|
||||||
Canove
|
Canove
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
{new Date().getFullYear()}
|
{new Date().getFullYear()}
|
||||||
@@ -50,7 +50,7 @@ const useStyles = makeStyles(theme => ({
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const Login = ({ showToast }) => {
|
const Login = () => {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
|
|
||||||
const [user, setUser] = useState({ email: "", password: "" });
|
const [user, setUser] = useState({ email: "", password: "" });
|
||||||
@@ -69,7 +69,7 @@ const Login = ({ showToast }) => {
|
|||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
Login
|
{i18n.t("login.title")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form
|
<form
|
||||||
className={classes.form}
|
className={classes.form}
|
||||||
@@ -82,7 +82,7 @@ const Login = ({ showToast }) => {
|
|||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
id="email"
|
id="email"
|
||||||
label="Email"
|
label={i18n.t("login.form.email")}
|
||||||
name="email"
|
name="email"
|
||||||
value={user.email}
|
value={user.email}
|
||||||
onChange={handleChangeInput}
|
onChange={handleChangeInput}
|
||||||
@@ -95,17 +95,13 @@ const Login = ({ showToast }) => {
|
|||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
name="password"
|
name="password"
|
||||||
label="Senha"
|
label={i18n.t("login.form.password")}
|
||||||
type="password"
|
type="password"
|
||||||
id="password"
|
id="password"
|
||||||
value={user.password}
|
value={user.password}
|
||||||
onChange={handleChangeInput}
|
onChange={handleChangeInput}
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
/>
|
/>
|
||||||
{/* <FormControlLabel
|
|
||||||
control={<Checkbox value="remember" color="primary" />}
|
|
||||||
label="Lembrar"
|
|
||||||
/> */}
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
type="submit"
|
||||||
fullWidth
|
fullWidth
|
||||||
@@ -113,14 +109,9 @@ const Login = ({ showToast }) => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
className={classes.submit}
|
className={classes.submit}
|
||||||
>
|
>
|
||||||
Entrar
|
{i18n.t("login.buttons.submit")}
|
||||||
</Button>
|
</Button>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
{/* <Grid item xs>
|
|
||||||
<Link href="#" variant="body2">
|
|
||||||
Forgot password?
|
|
||||||
</Link>
|
|
||||||
</Grid> */}
|
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Link
|
<Link
|
||||||
href="#"
|
href="#"
|
||||||
@@ -128,7 +119,7 @@ const Login = ({ showToast }) => {
|
|||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
to="/signup"
|
to="/signup"
|
||||||
>
|
>
|
||||||
{"Não tem uma conta? Cadastre-se!"}
|
{i18n.t("login.buttons.register")}
|
||||||
</Link>
|
</Link>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ import Typography from "@material-ui/core/Typography";
|
|||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import Container from "@material-ui/core/Container";
|
import Container from "@material-ui/core/Container";
|
||||||
|
|
||||||
|
import { i18n } from "../../translate/i18n";
|
||||||
|
|
||||||
import api from "../../services/api";
|
import api from "../../services/api";
|
||||||
|
|
||||||
const Copyright = () => {
|
const Copyright = () => {
|
||||||
@@ -65,10 +67,10 @@ const SignUp = () => {
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
try {
|
try {
|
||||||
await api.post("/auth/signup", user);
|
await api.post("/auth/signup", user);
|
||||||
toast.success("Usuário criado com sucesso! Faça seu login.");
|
toast.success(i18n.t("signup.toast.success"));
|
||||||
history.push("/login");
|
history.push("/login");
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error("Erro ao criar usuário. Verifique os dados informados.");
|
toast.error(i18n.t("signup.toast.fail"));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -80,7 +82,7 @@ const SignUp = () => {
|
|||||||
<LockOutlinedIcon />
|
<LockOutlinedIcon />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<Typography component="h1" variant="h5">
|
<Typography component="h1" variant="h5">
|
||||||
Cadastre-se
|
{i18n.t("signup.title")}
|
||||||
</Typography>
|
</Typography>
|
||||||
<form className={classes.form} noValidate onSubmit={handleSignUp}>
|
<form className={classes.form} noValidate onSubmit={handleSignUp}>
|
||||||
<Grid container spacing={2}>
|
<Grid container spacing={2}>
|
||||||
@@ -92,7 +94,7 @@ const SignUp = () => {
|
|||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
id="name"
|
id="name"
|
||||||
label="Nome"
|
label={i18n.t("signup.form.name")}
|
||||||
value={user.name}
|
value={user.name}
|
||||||
onChange={handleChangeInput}
|
onChange={handleChangeInput}
|
||||||
autoFocus
|
autoFocus
|
||||||
@@ -105,7 +107,7 @@ const SignUp = () => {
|
|||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
id="email"
|
id="email"
|
||||||
label="Email"
|
label={i18n.t("signup.form.email")}
|
||||||
name="email"
|
name="email"
|
||||||
autoComplete="email"
|
autoComplete="email"
|
||||||
value={user.email}
|
value={user.email}
|
||||||
@@ -118,7 +120,7 @@ const SignUp = () => {
|
|||||||
required
|
required
|
||||||
fullWidth
|
fullWidth
|
||||||
name="password"
|
name="password"
|
||||||
label="Senha"
|
label={i18n.t("signup.form.password")}
|
||||||
type="password"
|
type="password"
|
||||||
id="password"
|
id="password"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
@@ -134,12 +136,12 @@ const SignUp = () => {
|
|||||||
color="primary"
|
color="primary"
|
||||||
className={classes.submit}
|
className={classes.submit}
|
||||||
>
|
>
|
||||||
Cadastrar
|
{i18n.t("signup.buttons.submit")}
|
||||||
</Button>
|
</Button>
|
||||||
<Grid container justify="flex-end">
|
<Grid container justify="flex-end">
|
||||||
<Grid item>
|
<Grid item>
|
||||||
<Link href="#" variant="body2" component={RouterLink} to="/login">
|
<Link href="#" variant="body2" component={RouterLink} to="/login">
|
||||||
Já tem uma conta? Entre!
|
{i18n.t("signup.buttons.login")}
|
||||||
</Link>
|
</Link>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ import Paper from "@material-ui/core/Paper";
|
|||||||
import SessionInfo from "../../components/SessionInfo";
|
import SessionInfo from "../../components/SessionInfo";
|
||||||
import Qrcode from "../../components/Qrcode";
|
import Qrcode from "../../components/Qrcode";
|
||||||
|
|
||||||
let socket;
|
|
||||||
|
|
||||||
const useStyles = makeStyles(theme => ({
|
const useStyles = makeStyles(theme => ({
|
||||||
root: {
|
root: {
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -55,14 +53,8 @@ const WhatsAuth = () => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
const socket = openSocket(process.env.REACT_APP_BACKEND_URL);
|
||||||
|
|
||||||
return () => {
|
|
||||||
socket.disconnect();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
socket.on("qrcode", data => {
|
socket.on("qrcode", data => {
|
||||||
if (data.action === "update") {
|
if (data.action === "update") {
|
||||||
setQrCode(data.qr);
|
setQrCode(data.qr);
|
||||||
@@ -76,6 +68,10 @@ const WhatsAuth = () => {
|
|||||||
history.push("/chat");
|
history.push("/chat");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
socket.disconnect();
|
||||||
|
};
|
||||||
}, [history]);
|
}, [history]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
14
frontend/src/translate/i18n.js
Normal file
14
frontend/src/translate/i18n.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
import i18n from "i18next";
|
||||||
|
import LanguageDetector from "i18next-browser-languagedetector";
|
||||||
|
|
||||||
|
import { messages } from "./languages";
|
||||||
|
|
||||||
|
i18n.use(LanguageDetector).init({
|
||||||
|
debug: false,
|
||||||
|
defaultNS: ["translations"],
|
||||||
|
fallbackLng: "pt",
|
||||||
|
ns: ["translations"],
|
||||||
|
resources: messages,
|
||||||
|
});
|
||||||
|
|
||||||
|
export { i18n };
|
||||||
24
frontend/src/translate/languages/en.js
Normal file
24
frontend/src/translate/languages/en.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const messages = {
|
||||||
|
en: {
|
||||||
|
translations: {
|
||||||
|
signup: {
|
||||||
|
title: "Signup",
|
||||||
|
toasts: {
|
||||||
|
success: "User sucessfully registered. Login!",
|
||||||
|
fail: "Error creating user account. Verify your data.",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
name: "Name",
|
||||||
|
email: "Email",
|
||||||
|
password: "Password",
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
submit: "SIGNUP",
|
||||||
|
login: "Already registred? Go to login!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { messages };
|
||||||
9
frontend/src/translate/languages/index.js
Normal file
9
frontend/src/translate/languages/index.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { messages as portugueseMessages } from "./pt";
|
||||||
|
import { messages as englishMessages } from "./en";
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
...portugueseMessages,
|
||||||
|
...englishMessages,
|
||||||
|
};
|
||||||
|
|
||||||
|
export { messages };
|
||||||
154
frontend/src/translate/languages/pt.js
Normal file
154
frontend/src/translate/languages/pt.js
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
const messages = {
|
||||||
|
pt: {
|
||||||
|
translations: {
|
||||||
|
signup: {
|
||||||
|
title: "Cadastre-se",
|
||||||
|
toasts: {
|
||||||
|
success: "Usuário criado com sucesso! Faça seu login!!!.",
|
||||||
|
fail: "Erro ao criar usuário. Verifique os dados informados.",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
name: "Nome",
|
||||||
|
email: "Email",
|
||||||
|
password: "Senha",
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
submit: "Cadastrar",
|
||||||
|
login: "Já tem uma conta? Entre!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
login: {
|
||||||
|
title: "Login",
|
||||||
|
form: {
|
||||||
|
email: "Email",
|
||||||
|
password: "Senha",
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
submit: "Entrar",
|
||||||
|
register: "Não tem um conta? Cadastre-se!",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
toasts: {
|
||||||
|
success: "Login efetuado com sucesso!",
|
||||||
|
fail: "Erro de autenticação. Por favor, faça login novamente",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
dashboard: {
|
||||||
|
charts: {
|
||||||
|
perDay: {
|
||||||
|
title: "Tickets hoje: ",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
sessionInfo: {
|
||||||
|
status: "Status: ",
|
||||||
|
battery: "Bateria: ",
|
||||||
|
charging: "Carregando: ",
|
||||||
|
},
|
||||||
|
qrCode: {
|
||||||
|
message: "Leia o QrCode para iniciar a sessão",
|
||||||
|
},
|
||||||
|
contacts: {
|
||||||
|
title: "Contatos",
|
||||||
|
searchPlaceholder: "Pesquisar...",
|
||||||
|
confirmationModal: {
|
||||||
|
deleteTitle: "Deletar ",
|
||||||
|
importTitlte: "Importar contatos",
|
||||||
|
deleteMessage:
|
||||||
|
"Tem certeza que deseja deletar este contato? Todos os tickets relacionados serão perdidos.",
|
||||||
|
importMessage:
|
||||||
|
"Deseja importas todos os contatos do telefone? Essa função é experimental, você terá que recarregar a página após a importação.",
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
import: "Importar Contatos",
|
||||||
|
add: "Adicionar Contato",
|
||||||
|
},
|
||||||
|
table: {
|
||||||
|
name: "Nome",
|
||||||
|
whatsapp: "WhatsApp",
|
||||||
|
email: "Email",
|
||||||
|
actions: "Ações",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
contactModal: {
|
||||||
|
title: {
|
||||||
|
add: "Adicionar contato",
|
||||||
|
edit: "Editar contato",
|
||||||
|
},
|
||||||
|
form: {
|
||||||
|
mainInfo: "Dados do contato",
|
||||||
|
extraInfo: "Informações adicionais",
|
||||||
|
name: "Nome",
|
||||||
|
number: "Número do Whatsapp",
|
||||||
|
email: "Email",
|
||||||
|
extraName: "Nome do campo",
|
||||||
|
extraValue: "Valor",
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
addExtraInfo: "Adicionar informação",
|
||||||
|
okAdd: "Adicionar",
|
||||||
|
okEdit: "Salvar",
|
||||||
|
cancel: "Cancelar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
chat: {
|
||||||
|
noTicketMessage: "Selecione um ticket para começar a conversar.",
|
||||||
|
},
|
||||||
|
tickets: {
|
||||||
|
toasts: {
|
||||||
|
deleted: "O ticket que você estava foi deletado.",
|
||||||
|
},
|
||||||
|
notification: {
|
||||||
|
message: "Mensagem de",
|
||||||
|
},
|
||||||
|
tabs: {
|
||||||
|
open: {
|
||||||
|
title: "Inbox",
|
||||||
|
assignedHeader: "Atendendo",
|
||||||
|
pendingHeader: "Aguardando",
|
||||||
|
openNoTicketsTitle: "Pronto pra mais?",
|
||||||
|
openNoTicketsMessage: "Aceite um ticket da fila para começar.",
|
||||||
|
pendingNoTicketsTitle: "Tudo resolvido!",
|
||||||
|
pendingNoTicketsMessage: "Nenhum ticket pendente.",
|
||||||
|
},
|
||||||
|
closed: { title: "Resolvidos" },
|
||||||
|
search: {
|
||||||
|
title: "Busca",
|
||||||
|
noTicketsTitle: "Nada encontrado!",
|
||||||
|
noTicketsMessage: "Tente pesquisar por outro termo.",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
search: {
|
||||||
|
placeholder: "Pesquisar tickets e mensagens.",
|
||||||
|
},
|
||||||
|
buttons: {
|
||||||
|
showAll: "Todos",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ticketsList: {
|
||||||
|
buttons: {
|
||||||
|
accept: "Aceitar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
newTicketModal: {
|
||||||
|
title: "Criar Ticket",
|
||||||
|
fieldLabel: "Digite para pesquisar o contato",
|
||||||
|
buttons: {
|
||||||
|
ok: "Salvar",
|
||||||
|
cancel: "Cancelar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
mainDrawer: {
|
||||||
|
listItems: {
|
||||||
|
dashboard: "Dashboard",
|
||||||
|
connection: "Conexão",
|
||||||
|
tickets: "Tickets",
|
||||||
|
contacts: "Contatos",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export { messages };
|
||||||
11768
frontend/yarn.lock
11768
frontend/yarn.lock
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user