UserForm.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. import { connect } from "react-redux";
  2. import { useState, useEffect, useContext } from "react";
  3. import { actionUserUpdate } from "../../../actions/actionUserUpdate";
  4. import { UIContext } from "../../UIContext";
  5. import Select from "react-select";
  6. import { Box, Button, Grid, IconButton, InputLabel, Stack, TextField } from "@mui/material";
  7. import { useFormik } from "formik";
  8. import * as Yup from "yup";
  9. import { useNavigate } from "react-router-dom";
  10. import { MdVisibility, MdVisibilityOff } from "react-icons/md";
  11. import { aclList } from "../../../helpers";
  12. import { actionUploadFile } from "../../../actions/actionUploadFile";
  13. import { ProfileImageEditor } from "../../common/ProfileImageEditor";
  14. import { actionPromisesClear } from "../../../actions/actionPromisesClear";
  15. const styles = {
  16. multiValue: (base, state) => {
  17. return state.data.isFixed ? { ...base, backgroundColor: "gray" } : base;
  18. },
  19. multiValueLabel: (base, state) => {
  20. return state.data.isFixed ? { ...base, fontWeight: "bold", color: "white", paddingRight: 6 } : base;
  21. },
  22. multiValueRemove: (base, state) => {
  23. return state.data.isFixed ? { ...base, display: "none" } : base;
  24. },
  25. };
  26. const CProfileImageEditor = connect(null, {
  27. onFileDrop: (acceptedFiles) => actionUploadFile(acceptedFiles[0]),
  28. })(ProfileImageEditor);
  29. const userSchema = Yup.object().shape({
  30. name: Yup.string(),
  31. username: Yup.string().min(3, "Too Short!").max(15, "Too Long!").required("Required"),
  32. password: Yup.string().min(3, "Too Short!").max(15, "Too Long!"),
  33. nick: Yup.string(),
  34. });
  35. export const UserForm = ({
  36. serverErrors = [],
  37. onSaveClick,
  38. onSave,
  39. onClose,
  40. onUnmount,
  41. promiseStatus,
  42. deletePromiseStatus,
  43. avatar = null,
  44. user = {},
  45. } = {}) => {
  46. const { setAlert } = useContext(UIContext);
  47. const [promiseTimeOut, setPromiseTimeOut] = useState(null);
  48. const [showPassword, setShowPassword] = useState(false);
  49. const [isNew, setIsNew] = useState(false);
  50. const [acl, setAcl] = useState([]);
  51. const navigate = useNavigate();
  52. const formik = useFormik({
  53. initialValues: {
  54. name: "",
  55. username: "",
  56. nick: "",
  57. password: "",
  58. },
  59. validationSchema: userSchema,
  60. validateOnChange: true,
  61. validateOnMount: true,
  62. onSubmit: () => {
  63. let userToSave = {};
  64. userToSave = formik.values;
  65. !isNew && user?._id && (userToSave._id = user._id);
  66. userToSave.acl = acl.map(({ value }) => value);
  67. avatar ? (userToSave.avatar = avatar) : delete userToSave.avatar;
  68. onSaveClick && onSaveClick();
  69. onSave(userToSave);
  70. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  71. setIsNew(false);
  72. },
  73. });
  74. const orderOptions = (values) => {
  75. return values.filter((v) => v.isFixed).concat(values.filter((v) => !v.isFixed));
  76. };
  77. const onChange = (values, actionMeta) => {
  78. switch (actionMeta.action) {
  79. case "remove-value":
  80. case "pop-value":
  81. if (actionMeta.removedValue.isFixed) {
  82. return;
  83. }
  84. break;
  85. case "clear":
  86. values = aclList.filter((acl) => acl.isFixed);
  87. break;
  88. }
  89. values = orderOptions(values);
  90. setAcl(values);
  91. };
  92. useEffect(() => {
  93. return () => {
  94. promiseTimeOut && clearTimeout(promiseTimeOut);
  95. setPromiseTimeOut(null);
  96. };
  97. }, []);
  98. useEffect(() => {
  99. if (promiseStatus === "FULFILLED") {
  100. formik.setSubmitting(false);
  101. promiseTimeOut && clearTimeout(promiseTimeOut);
  102. setPromiseTimeOut(null);
  103. setAlert({
  104. show: true,
  105. severity: "success",
  106. message: "Готово",
  107. });
  108. }
  109. if (promiseStatus === "REJECTED") {
  110. const errorMessage = (serverErrors ? [].concat(serverErrors) : []).reduce((prev, curr) => prev + "\n" + curr.message, "");
  111. formik.setSubmitting(false);
  112. promiseTimeOut && clearTimeout(promiseTimeOut);
  113. setPromiseTimeOut(null);
  114. setAlert({
  115. show: true,
  116. severity: "error",
  117. message: errorMessage,
  118. });
  119. }
  120. }, [promiseStatus]);
  121. useEffect(() => {
  122. if (deletePromiseStatus === "FULFILLED") {
  123. promiseTimeOut && clearTimeout(promiseTimeOut);
  124. setPromiseTimeOut(null);
  125. navigate("/admin/users/");
  126. }
  127. if (deletePromiseStatus === "REJECTED") {
  128. promiseTimeOut && clearTimeout(promiseTimeOut);
  129. setPromiseTimeOut(null);
  130. setAlert({
  131. show: true,
  132. severity: "error",
  133. message: "Помилка",
  134. });
  135. }
  136. return () => {
  137. onUnmount && onUnmount();
  138. };
  139. }, [deletePromiseStatus]);
  140. useEffect(() => {
  141. setAcl(orderOptions(aclList.filter((item) => user?.acl?.includes(item.value)) || []));
  142. formik.setFieldValue("name", user.name || "");
  143. formik.setFieldValue("username", user.username || "");
  144. formik.setFieldValue("nick", user.nick || "");
  145. formik.setFieldValue("password", user.password || "");
  146. }, [user]);
  147. useEffect(() => {
  148. return () => {
  149. onClose && onClose();
  150. };
  151. }, []);
  152. return (
  153. <Box className="UserForm" component="form" onSubmit={formik.handleSubmit}>
  154. <Grid container spacing={2}>
  155. <Grid item xs={5}>
  156. <CProfileImageEditor avatar={avatar} />
  157. </Grid>
  158. <Grid item xs={7}>
  159. <TextField
  160. id="name"
  161. name="name"
  162. variant="outlined"
  163. label="Ім'я"
  164. size="small"
  165. error={formik.touched.name && Boolean(formik.errors.name)}
  166. value={formik.values.name}
  167. onBlur={formik.handleBlur}
  168. onChange={formik.handleChange}
  169. helperText={formik.touched.name && formik.errors.name}
  170. multiline
  171. fullWidth
  172. sx={{ mt: 2 }}
  173. />
  174. <TextField
  175. variant="outlined"
  176. id="username"
  177. name="username"
  178. label="Username"
  179. size="small"
  180. error={formik.touched.username && Boolean(formik.errors.username)}
  181. value={formik.values.username}
  182. onBlur={formik.handleBlur}
  183. onChange={formik.handleChange}
  184. helperText={formik.touched.username && formik.errors.username}
  185. multiline
  186. fullWidth
  187. sx={{ mt: 2 }}
  188. />
  189. <TextField
  190. variant="outlined"
  191. id="nick"
  192. name="nick"
  193. label="Nick"
  194. size="small"
  195. error={formik.touched.nick && Boolean(formik.errors.nick)}
  196. value={formik.values.nick}
  197. onBlur={formik.handleBlur}
  198. onChange={formik.handleChange}
  199. helperText={formik.touched.nick && formik.errors.nick}
  200. multiline
  201. fullWidth
  202. sx={{ mt: 2 }}
  203. />
  204. <TextField
  205. id="password"
  206. name="password"
  207. variant="outlined"
  208. size="small"
  209. label="Новий пароль"
  210. type={showPassword ? "text" : "password"}
  211. error={formik.touched.password && Boolean(formik.errors.password)}
  212. value={formik.values.password}
  213. onBlur={formik.handleBlur}
  214. onChange={formik.handleChange}
  215. helperText={formik.touched.password && formik.errors.password}
  216. InputProps={{
  217. endAdornment: (
  218. <IconButton onClick={() => setShowPassword((prev) => !prev)} edge="end">
  219. {showPassword ? <MdVisibilityOff /> : <MdVisibility />}
  220. </IconButton>
  221. ),
  222. }}
  223. fullWidth
  224. sx={{ mt: 2 }}
  225. />
  226. <Box sx={{ mt: 3 }}>
  227. <InputLabel>Permissions</InputLabel>
  228. <Select
  229. placeholder="Обрати категорії"
  230. value={acl}
  231. closeMenuOnSelect={false}
  232. onChange={onChange}
  233. options={aclList}
  234. isClearable={acl?.some((acl) => !acl.isFixed)}
  235. isMulti={true}
  236. styles={styles}
  237. />
  238. </Box>
  239. </Grid>
  240. </Grid>
  241. <Stack direction="row" sx={{ mt: 3 }} justifyContent="flex-end" spacing={1}>
  242. {!!user._id && (
  243. <Button variant="contained" onClick={() => setIsNew(true)} disabled={formik.isSubmitting} type="submit">
  244. Зберегти як новий
  245. </Button>
  246. )}
  247. <Button variant="contained" onClick={() => setIsNew(false)} disabled={formik.isSubmitting} type="submit">
  248. Зберегти
  249. </Button>
  250. </Stack>
  251. </Box>
  252. );
  253. };
  254. export const CUserForm = connect(
  255. (state) => ({
  256. promiseStatus: state.promise.userUpsert?.status || null,
  257. deletePromiseStatus: state.promise.userDelete?.status || null,
  258. user: state.promise?.adminUserById?.payload || {},
  259. avatar: state.promise?.uploadFile?.payload || state.promise?.adminUserById?.payload?.avatar || null,
  260. serverErrors: state.promise?.userUpsert?.error || [],
  261. }),
  262. {
  263. onSave: (user) => actionUserUpdate(user),
  264. onClose: () => actionPromisesClear(["userUpsert", "userDelete"]),
  265. }
  266. )(UserForm);