- remove useless optimization boilerplate
- use ref in useEffet for variables that are not denpencies
- add NexUserModal component
- disable submission while feedback
- reset form after validating feeback
- update i18n "accountStatus" key
pull/12/head
c-cal 6 years ago
parent c7127b14f9
commit 2b793b8b72
Signed by: watcha
GPG Key ID: 87DD78E7F7A1581D
  1. 9
      public/locales/en/usersTab.json
  2. 9
      public/locales/fr/usersTab.json
  3. 71
      src/NewUserForm.js
  4. 78
      src/NewUserModal.js
  5. 6
      src/TableTab.js
  6. 2
      src/UserRightPanel.js
  7. 169
      src/UsersTab.js

@ -26,15 +26,16 @@
"emailAddress": "Email address",
"lastSeen": "Last activity on",
"role": "Role",
"accountStatus": "Account status"
"status": "Account status"
},
"roles": {
"administrator": "Administrator",
"collaborator": "Collaborator",
"partner": "Partner"
},
"isActive": {
"1": "enabled",
"0": "disabled"
"status": {
"active": "enabled",
"inactive": "disabled",
"invited": "invitation sent"
}
}

@ -26,15 +26,16 @@
"emailAddress": "Adresse électronique",
"lastSeen": "Dernière activité le",
"role": "Rôle",
"accountStatus": "Statut du compte"
"status": "Statut du compte"
},
"roles": {
"administrator": "Administrateur",
"collaborator": "Collaborateur",
"partner": "Partenaire"
},
"isActive": {
"1": "activé",
"0": "désactivé"
"status": {
"active": "activé",
"inactive": "désactivé",
"invited": "invitation envoyée"
}
}

@ -1,4 +1,4 @@
import React, { useMemo } from "react";
import React, { useEffect, useRef } from "react";
import { Formik } from "formik";
import { useTranslation } from "react-i18next";
import * as yup from "yup";
@ -6,44 +6,50 @@ import Button from "react-bootstrap/Button";
import Form from "react-bootstrap/Form";
import InputGroup from "react-bootstrap/InputGroup";
const namePattern = "[a-z\u00C0-\u017F]{2,}";
const NAME_PATTERN = "[a-z\u00C0-\u017F]{2,}";
export default ({ userList, onSubmit, bindSubmitForm, feedback }) => {
const { t } = useTranslation("usersTab");
const schema = useMemo(
() =>
yup.object({
fullName: yup
.string()
.required(t("requiered.fullName"))
.matches(
new RegExp(`^${namePattern} ${namePattern}$`, "i"),
t("invalid", { field: "$t(invalidField.fullName)" })
),
emailAddress: yup
.string()
.required(t("requiered.emailAddress"))
.email(
t("invalid", { field: "$t(invalidField.emailAddress)" })
)
.test(
"is-available",
t("unavailableEmailAddress"),
value =>
!value ||
!userList.some(user => user.emailAddress === value)
),
isSynapseAdministrator: yup.bool(),
}),
[userList, t]
);
const resetFormRef = useRef();
const bindResetForm = resetForm => {
resetFormRef.current = resetForm;
};
useEffect(() => {
if (!feedback) {
resetFormRef.current();
}
}, [feedback]);
const schema = yup.object({
fullName: yup
.string()
.required(t("requiered.fullName"))
.matches(
new RegExp(`^${NAME_PATTERN} ${NAME_PATTERN}$`, "i"),
t("invalid", { field: "$t(invalidField.fullName)" })
),
emailAddress: yup
.string()
.required(t("requiered.emailAddress"))
.email(t("invalid", { field: "$t(invalidField.emailAddress)" }))
.test(
"is-available",
t("unavailableEmailAddress"),
value =>
!value ||
!userList.some(user => user.emailAddress === value)
),
isSynapseAdministrator: yup.bool(),
});
return (
<Formik
initialValues={{
fullName: "",
emailAddress: "",
isSynapseAdministrator: false,
}}
validationSchema={schema}
{...{ onSubmit }}
@ -52,11 +58,13 @@ export default ({ userList, onSubmit, bindSubmitForm, feedback }) => {
handleChange,
handleSubmit,
submitForm,
resetForm,
values,
touched,
errors,
}) => {
bindSubmitForm(submitForm);
bindResetForm(resetForm);
return (
<Form noValidate onSubmit={handleSubmit}>
<Form.Group>
@ -122,13 +130,16 @@ export default ({ userList, onSubmit, bindSubmitForm, feedback }) => {
id="isSynapseAdministrator"
name="isSynapseAdministrator"
label={t("synapseAdminCheckbox")}
value={values.isSynapseAdministrator}
checked={values.isSynapseAdministrator}
onChange={handleChange}
disabled={feedback}
/>
{/* This button is only required to submit the form from a field by pressing the enter key.
It is therefore hidden. The button actually used is rendered in the parent component. */}
<Button
type="submit"
disabled={feedback}
style={{ display: "none" }}
></Button>
</Form>

@ -0,0 +1,78 @@
import React, { useRef, useState } from "react";
import { useMutate } from "restful-react";
import { useTranslation } from "react-i18next";
import NewItemModal from "./NewItemModal";
import NewUserForm from "./NewUserForm";
export default ({ modalShow, setModalShow, userList, setUserList }) => {
const { t } = useTranslation("usersTab");
const [feedback, setFeedback] = useState(null);
const { mutate: post, loading } = useMutate({
verb: "POST",
path: "watcha_register",
});
const onSubmit = data => {
const payload = makePayload(data);
post(payload)
.then(response => {
const user = makeUser(data);
setUserList([...userList, user]);
setFeedback({ variant: "success", message: t("success") });
})
.catch(error =>
setFeedback({ variant: "danger", message: t("danger") })
);
};
const onHide = () => {
setModalShow(false);
setFeedback(null);
};
const submitFormRef = useRef();
const bindSubmitForm = submitForm => {
submitFormRef.current = submitForm;
};
return (
<NewItemModal
show={modalShow}
title={t("button")}
onSave={() => submitFormRef.current()}
onClick={() => setFeedback(null)}
{...{ feedback, loading, onHide }}
>
<NewUserForm
{...{ userList, onSubmit, bindSubmitForm, feedback }}
/>
</NewItemModal>
);
};
const makePayload = data => ({
admin: data.isSynapseAdministrator ? "admin" : false,
email: data.emailAddress,
full_name: data.fullName,
user: computeUserIdFromEmailAddress(data.emailAddress),
});
const makeUser = data => ({
userId: computeUserIdFromEmailAddress(data.emailAddress),
displayName: data.fullName,
emailAddress: data.emailAddress,
lastSeen: null,
role: data.isSynapseAdministrator ? "administrator" : "collaborator",
status: "active",
});
const computeUserIdFromEmailAddress = emailAddress =>
emailAddress
.replace("@", "/")
.normalize("NFKD")
.replace(/[\u0300-\u036F]/g, "")
.toLowerCase()
.replace(/[^\w=\-./]/g, "");

@ -11,14 +11,14 @@ import SearchBox from "./SearchBox";
import Table from "./Table";
export default ({
ns,
data,
columns,
initialState,
button,
rightPanel,
newItemModal,
editUser,
newItemModal
rightPanel,
ns,
}) => {
const globalFilter = useMemo(() => fuzzyTextFilterFn, []);

@ -371,7 +371,7 @@ class UserRightPanel extends Component {
render() {
const ISPARTNER = this.props.user.role === "partner";
const { t } = this.props;
const ISDEACTIVATE = this.props.user.accountStatus === 0;
const ISDEACTIVATE = this.props.user.status === "inactive";
let editEmail;
const bottomButtons = [];
let title = t(`usersTab:roles.${this.props.user.role}`);

@ -5,15 +5,14 @@ import React, {
useRef,
useState,
} from "react";
import { useGet, useMutate } from "restful-react";
import { useGet } from "restful-react";
import { useTranslation } from "react-i18next";
import { useDispatchContext, useUserIdContext } from "./contexts";
import Button from "./NewItemButton";
import Date from "./Date";
import DelayedSpinner from "./DelayedSpinner";
import NewItemModal from "./NewItemModal";
import NewUserForm from "./NewUserForm";
import NewUserModal from "./NewUserModal";
import TableTab, { compareLowerCase } from "./TableTab";
import UserRightPanel from "./UserRightPanel";
@ -26,10 +25,8 @@ export default () => {
const dispatch = useDispatchContext();
const [userList, setUserList] = useState(null);
const [intervalId, setIntervalId] = useState(null);
const [rightPanel, setRightPanel] = useState(null);
const [modalShow, setModalShow] = useState(false);
const [feedback, setFeedback] = useState(null);
const [rightPanel, setRightPanel] = useState(null);
const { data, refetch } = useGet({
path: "watcha_user_list",
@ -37,6 +34,49 @@ export default () => {
resolve,
});
const refetchRef = useRef();
refetchRef.current = refetch;
const intervalIdRef = useRef();
useEffect(() => {
refetchRef.current();
}, []);
useEffect(() => {
setUserList(data);
if (intervalIdRef.current) {
clearInterval(intervalIdRef.current);
}
intervalIdRef.current = setInterval(() => refetchRef.current(), 10000);
}, [data]);
const onClose = () => setRightPanel();
useEffect(() => {
if (userList && userId) {
for (const user of userList) {
if (user.userId === userId) {
setRightPanel(<UserRightPanel {...{ user, onClose }} />);
dispatch({ userId: null });
return;
}
}
}
}, [userList, userId, dispatch]);
const button = <Button onClick={() => setModalShow(true)} {...{ ns }} />;
const newItemModal = (
<NewUserModal {...{ modalShow, setModalShow, userList, setUserList }} />
);
const editUser = useCallback(
user =>
setRightPanel(user && <UserRightPanel {...{ user, onClose }} />),
[]
);
const columns = useMemo(
() => [
{
@ -62,13 +102,11 @@ export default () => {
Cell: ({ value }) => t(`roles.${value}`),
},
{
Header: t("headers.accountStatus"),
accessor: "accountStatus",
Header: t("headers.status"),
accessor: "status",
disableGlobalFilter: true,
Cell: ({ value }) => (
<span className={value === 1 ? "active" : "inactive"}>
{t(`isActive.${value}`)}
</span>
<span className={value}>{t(`status.${value}`)}</span>
),
},
],
@ -80,118 +118,17 @@ export default () => {
[]
);
const button = useMemo(
() => <Button onClick={() => setModalShow(true)} {...{ ns }} />,
[]
);
const submitFormRef = useRef();
const bindSubmitForm = submitForm => {
submitFormRef.current = submitForm;
};
const { mutate: post, loading } = useMutate({
verb: "POST",
path: "watcha_register",
});
const newItemModal = useMemo(() => {
const onSubmit = data => {
const userId = data.emailAddress
.replace("@", "/")
.normalize("NFKD")
.replace(/[\u0300-\u036F]/g, "")
.toLowerCase()
.replace(/[^\W=\-./]/g, "");
post({
admin: data.isSynapseAdministrator ? "admin" : false,
email: data.emailAddress,
full_name: data.fullName,
user: userId,
})
.then(response => {
setUserList([
...userList,
{
userId,
displayName: data.fullName,
emailAddress: data.emailAddress,
lastSeen: null,
role: data.isSynapseAdministrator
? "administrator"
: "collaborator",
accountStatus: 1,
},
]);
setFeedback({ variant: "success", message: t("success") });
})
.catch(error =>
setFeedback({ variant: "danger", message: t("danger") })
);
};
const onHide = () => {
setModalShow(false);
setFeedback(null);
};
return (
<NewItemModal
show={modalShow}
title={t("usersTab:button")}
onSave={() => submitFormRef.current()}
onClick={() => setFeedback(null)}
{...{ feedback, loading, onHide }}
>
<NewUserForm
{...{ userList, onSubmit, bindSubmitForm, feedback }}
/>
</NewItemModal>
);
}, [modalShow, feedback, setFeedback, loading, userList, post, t]);
const onClose = useCallback(() => setRightPanel(), []);
const editUser = useCallback(
user =>
setRightPanel(user && <UserRightPanel {...{ user, onClose }} />),
[onClose]
);
useEffect(() => {
refetch();
}, []);
useEffect(() => {
setUserList(data);
const _intervalId = setInterval(() => refetch(), 10000);
setIntervalId(prevIntervalId => {
prevIntervalId && clearInterval(prevIntervalId);
return _intervalId;
});
}, [data]);
useEffect(() => {
if (userList && userId) {
for (const user of userList) {
if (user.userId === userId) {
setRightPanel(<UserRightPanel {...{ user, onClose }} />);
dispatch({ userId: null });
return;
}
}
}
}, [userList, userId, dispatch, onClose]);
return userList ? (
<TableTab
data={userList}
{...{
ns,
columns,
initialState,
button,
rightPanel,
editUser,
newItemModal,
editUser,
rightPanel,
ns,
}}
/>
) : (
@ -211,5 +148,5 @@ const resolve = data =>
: item.is_partner === 1
? "partner"
: "collaborator",
accountStatus: item.is_active,
status: item.is_active === 1 ? "active" : "inactive",
}));

Loading…
Cancel
Save