UserForm.js 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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 [acl, setAcl] = useState([]);
  50. const navigate = useNavigate();
  51. useEffect(() => {
  52. console.log(promiseStatus);
  53. }, [promiseStatus]);
  54. const formik = useFormik({
  55. initialValues: {
  56. name: "",
  57. username: "",
  58. nick: "",
  59. password: "",
  60. },
  61. validationSchema: userSchema,
  62. validateOnChange: true,
  63. onSubmit: () => {
  64. let userToSave = {};
  65. userToSave = formik.values;
  66. user?._id && (userToSave._id = user._id);
  67. userToSave.acl = acl.map(({ value }) => value);
  68. avatar ? (userToSave.avatar = avatar) : delete userToSave.avatar;
  69. onSaveClick && onSaveClick();
  70. onSave(userToSave);
  71. setPromiseTimeOut(setTimeout(() => formik.setSubmitting(false), 3000));
  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. formik.validateForm();
  147. }, [user]);
  148. useEffect(() => {
  149. return () => {
  150. onClose && onClose();
  151. };
  152. }, []);
  153. return (
  154. <Box className="UserForm" component="form" onSubmit={formik.handleSubmit}>
  155. <Grid container spacing={2}>
  156. <Grid item xs={5}>
  157. <CProfileImageEditor avatar={avatar} />
  158. </Grid>
  159. <Grid item xs={7}>
  160. <TextField
  161. id="name"
  162. name="name"
  163. variant="outlined"
  164. label="Ім'я"
  165. size="small"
  166. error={formik.touched.name && Boolean(formik.errors.name)}
  167. value={formik.values.name}
  168. onBlur={formik.handleBlur}
  169. onChange={formik.handleChange}
  170. helperText={formik.touched.name && formik.errors.name}
  171. multiline
  172. fullWidth
  173. sx={{ mt: 2 }}
  174. />
  175. <TextField
  176. variant="outlined"
  177. id="username"
  178. name="username"
  179. label="Username"
  180. size="small"
  181. error={formik.touched.username && Boolean(formik.errors.username)}
  182. value={formik.values.username}
  183. onBlur={formik.handleBlur}
  184. onChange={formik.handleChange}
  185. helperText={formik.touched.username && formik.errors.username}
  186. multiline
  187. fullWidth
  188. sx={{ mt: 2 }}
  189. />
  190. <TextField
  191. variant="outlined"
  192. id="nick"
  193. name="nick"
  194. label="Nick"
  195. size="small"
  196. error={formik.touched.nick && Boolean(formik.errors.nick)}
  197. value={formik.values.nick}
  198. onBlur={formik.handleBlur}
  199. onChange={formik.handleChange}
  200. helperText={formik.touched.nick && formik.errors.nick}
  201. multiline
  202. fullWidth
  203. sx={{ mt: 2 }}
  204. />
  205. <TextField
  206. id="password"
  207. name="password"
  208. variant="outlined"
  209. size="small"
  210. label="Новий пароль"
  211. type={showPassword ? "text" : "password"}
  212. error={formik.touched.password && Boolean(formik.errors.password)}
  213. value={formik.values.password}
  214. onBlur={formik.handleBlur}
  215. onChange={formik.handleChange}
  216. helperText={formik.touched.password && formik.errors.password}
  217. InputProps={{
  218. endAdornment: (
  219. <IconButton onClick={() => setShowPassword((prev) => !prev)} edge="end">
  220. {showPassword ? <MdVisibilityOff /> : <MdVisibility />}
  221. </IconButton>
  222. ),
  223. }}
  224. fullWidth
  225. sx={{ mt: 2 }}
  226. />
  227. <Box sx={{ mt: 3 }}>
  228. <InputLabel>Permissions</InputLabel>
  229. <Select
  230. placeholder="Обрати категорії"
  231. value={acl}
  232. closeMenuOnSelect={false}
  233. onChange={onChange}
  234. options={aclList}
  235. isClearable={acl?.some((acl) => !acl.isFixed)}
  236. isMulti={true}
  237. styles={styles}
  238. />
  239. </Box>
  240. </Grid>
  241. </Grid>
  242. <Stack direction="row" sx={{ mt: 3 }} justifyContent="flex-end" spacing={1}>
  243. <Button variant="contained" disabled={!formik.isValid || formik.isSubmitting} type="submit">
  244. Зберегти
  245. </Button>
  246. </Stack>
  247. </Box>
  248. );
  249. };
  250. export const CUserForm = connect(
  251. (state) => ({
  252. promiseStatus: state.promise.userUpsert?.status || null,
  253. deletePromiseStatus: state.promise.userDelete?.status || null,
  254. user: state.promise?.adminUserById?.payload || {},
  255. avatar: state.promise?.uploadFile?.payload || state.promise?.adminUserById?.payload?.avatar || null,
  256. serverErrors: state.promise?.userUpsert?.error || [],
  257. }),
  258. {
  259. onSave: (user) => actionUserUpdate(user),
  260. onClose: () => actionPromisesClear(["userUpsert", "userDelete"]),
  261. }
  262. )(UserForm);