Guest User

Untitled

a guest
Dec 18th, 2025
35
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.02 KB | None | 0 0
  1. import { useContext, useEffect, useState, useRef } from "react";
  2. import { AppContext } from "../../Context/AppContext";
  3. import { useNavigate, useParams, Link } from "react-router-dom";
  4. import { useForm, Controller } from "react-hook-form";
  5. import { MASTER_BOARD_NAME } from "../../constants.jsx";
  6. import AccessError from "../AccessError";
  7. import NotFound404 from "../NotFound404";
  8. import { getCookie } from "../../functions.jsx";
  9. import * as validationRules from "../../validationRulesRHF.jsx";
  10. import RichTextEditorRHF from "../../components/RichTextEditorRHF";
  11. import ErrorHead from "../../components/ErrorHead.jsx";
  12. import NavigationBar from "../../components/NavigationBar.jsx";
  13. import MegaModal from "../../components/MegaModal.jsx";
  14.  
  15. export default function AdminUserEdit() {
  16. const { id } = useParams();
  17. const navigate = useNavigate();
  18. const { user, loadingUser } = useContext(AppContext);
  19. const [showSuccessModal, setShowSuccessModal] = useState(false);
  20. const [errors, setErrors] = useState({});
  21. const [groups, setGroups] = useState([]);
  22. const signatureEditorRef = useRef(null);
  23. const [loadingContent, setLoadingContent] = useState(true);
  24.  
  25. // Data that is NOT being sent to the backend...
  26. const [tmpData, setTmpData] = useState({
  27. avatar: "",
  28. });
  29.  
  30. const {
  31. register,
  32. control,
  33. handleSubmit,
  34. reset,
  35. getValues, // Querying once
  36. setValue,
  37. trigger,
  38. watch, // Re-querying whenever the value changes
  39. formState: { errors: realtimeErrors },
  40. } = useForm({
  41. defaultValues: {
  42. id: "",
  43. name: "",
  44. new_password: "",
  45. email: "",
  46. group: "",
  47. job: "",
  48. hobbies: "",
  49. location: "",
  50. signature: "",
  51. delete_avatar: "",
  52. },
  53. //mode: "onSubmit",
  54. mode: "onChange", // First Validation If...
  55. reValidateMode: "onChange", // Every Other Validation If...
  56. });
  57.  
  58. //console.log(errors);
  59.  
  60. useEffect(() => {
  61. const interval = setInterval(() => {
  62. if (window.sceditor && signatureEditorRef.current) {
  63. const signatureEditor = window.sceditor.instance(signatureEditorRef.current);
  64. const updatedSignature = signatureEditor?.val?.() || "";
  65. console.log(watch("signature"));
  66.  
  67. //setValue("signature", updatedSignature);
  68. //trigger("signature");
  69. setValue("signature", updatedSignature, {
  70. shouldValidate: true,
  71. shouldDirty: true,
  72. shouldTouch: true,
  73. });
  74. }
  75. }, 100);
  76.  
  77. // Cleanup the interval when the component unmounts
  78. return () => {
  79. clearInterval(interval);
  80. };
  81. });
  82.  
  83. //formData = Field Values
  84. const onValid = async (formData, event) => {
  85. event.preventDefault();
  86.  
  87. // Manually grab latest value from the editor
  88. const signatureEditor = window.sceditor.instance(signatureEditorRef.current);
  89. const updatedSignature = signatureEditor?.val?.() || "";
  90.  
  91. const payload = {
  92. ...formData,
  93. signature: updatedSignature,
  94. };
  95.  
  96. const res = await fetch(`/web/admin/users/${id}`, {
  97. method: "put",
  98. credentials: "include",
  99. headers: {
  100. "X-XSRF-TOKEN": decodeURIComponent(getCookie("XSRF-TOKEN")), // This includes the CSRF Cookie/Token (Necessary for every Request Method except GET)
  101. },
  102. //body: JSON.stringify(formData),
  103. body: JSON.stringify(payload),
  104. });
  105.  
  106. const data = await res.json();
  107.  
  108. console.log(data);
  109.  
  110. if (data.errors) {
  111. window.scrollTo(0, 0);
  112. setErrors(data.errors);
  113. } else {
  114. setErrors([]);
  115. setShowSuccessModal(true);
  116. }
  117. };
  118.  
  119. // formData = errors
  120. const onInvalid = (formData, event) => {
  121. console.log("Form is invalid");
  122. };
  123.  
  124. useEffect(() => {
  125. if (user && user.group.is_admin === Number(1)) {
  126. document.title = `Edit User | ${MASTER_BOARD_NAME}`;
  127. }
  128. }, [user]);
  129.  
  130. async function getGroups() {
  131. const res = await fetch(`/web/admin/users/groups`, {
  132. method: "get",
  133. credentials: "include",
  134. });
  135. const data = await res.json();
  136.  
  137. if (res.ok) {
  138. setGroups(data.groups);
  139. }
  140. }
  141.  
  142. useEffect(() => {
  143. getGroups();
  144. }, []);
  145.  
  146. async function getUser() {
  147. const res = await fetch(`/web/admin/users/${id}`, {
  148. method: "get",
  149. credentials: "include",
  150. });
  151.  
  152. const data = await res.json();
  153.  
  154. if (res.ok) {
  155. reset({
  156. id: data.user.id,
  157. name: data.user.name,
  158. password: "",
  159. email: data.user.email,
  160. group: data.user.group.id,
  161. job: data.user.job,
  162. hobbies: data.user.hobbies,
  163. location: data.user.location,
  164. signature: data.user.signature,
  165. delete_avatar: 0,
  166. });
  167. setTmpData({
  168. avatar: data.user.avatar,
  169. });
  170. setLoadingContent(false);
  171. } else {
  172. setLoadingContent(false);
  173. }
  174. }
  175.  
  176. useEffect(() => {
  177. getUser();
  178. }, []);
  179.  
  180. if (!loadingUser && (!user || user.group.is_admin != Number(1))) {
  181. return <AccessError />;
  182. }
  183.  
  184. //if (!loadingContent && !formData.id) {
  185. if (!loadingContent && !watch("id")) {
  186. return <NotFound404 />;
  187. }
  188.  
  189. return (
  190. <>
  191. <div className="container py-4">
  192. {/* Breadcrumb Navigation */}
  193. <NavigationBar
  194. breadcrumbs={{
  195. items: [
  196. { item_name: "Home", item_url: "/", item_loaded: true },
  197. {
  198. item_name: "Admin Dashboard",
  199. item_url: `/admin/dashboard`,
  200. item_loaded: true,
  201. },
  202. {
  203. item_name: "Manage Users",
  204. item_url: `/admin/user/list`,
  205. item_loaded: true,
  206. },
  207. { item_name: "Edit User", item_loaded: true, item_active: true },
  208. ],
  209. }}
  210. />
  211.  
  212. {/* Error Box */}
  213. <ErrorHead errors={errors} />
  214.  
  215. {!loadingUser && !loadingContent ? (
  216. <>
  217. <div className="card shadow-sm">
  218. <div className="card-header bg-secondary bg-opacity-25 fw-bold">
  219. Edit User "{watch("name")}"
  220. </div>
  221. <div className="card-body">
  222. <form onSubmit={handleSubmit(onValid, onInvalid)}>
  223. {/* Required Fields Card */}
  224. <div className="card mb-3">
  225. <div className="card-header bg-light fw-semibold">Required Information</div>
  226. <div className="card-body">
  227. {/* Username */}
  228. <div className="mb-3">
  229. <label htmlFor="name" className="form-label">
  230. Username
  231. </label>
  232. <input
  233. className="form-control"
  234. type="text"
  235. placeholder="Enter username"
  236. id="name"
  237. {...register("name", validationRules.username)}
  238. />
  239. {realtimeErrors.name?.message ? (
  240. <p className="text-danger mt-2">{realtimeErrors.name.message}</p>
  241. ) : null}
  242. </div>
  243.  
  244. {/* New Password */}
  245. <div className="mb-3">
  246. <label htmlFor="new_password" className="form-label">
  247. Password
  248. </label>
  249. <input
  250. className="form-control"
  251. type="password"
  252. placeholder="Enter new password (Optional)"
  253. id="new_password"
  254. {...register("new_password", validationRules.new_password)}
  255. />
  256. {realtimeErrors.new_password?.message ? (
  257. <p className="text-danger mt-2">{realtimeErrors.new_password.message}</p>
  258. ) : null}
  259. </div>
  260.  
  261. {/* Email */}
  262. <div className="mb-3">
  263. <label htmlFor="email" className="form-label">
  264. Email
  265. </label>
  266. <input
  267. className="form-control"
  268. type="text"
  269. placeholder="Enter email"
  270. id="email"
  271. {...register("email", validationRules.email)}
  272. />
  273. {realtimeErrors.email?.message ? (
  274. <p className="text-danger mt-2">{realtimeErrors.email.message}</p>
  275. ) : null}
  276. </div>
  277.  
  278. {/* Usergroup */}
  279. <div className="mb-3">
  280. <label htmlFor="group" className="form-label">
  281. Usergroup
  282. </label>
  283. <select
  284. className="form-select"
  285. id="group"
  286. value={getValues("group") || ""}
  287. {...register("group", validationRules.usergroup)}
  288. >
  289. <option value="">Select a usergroup</option>
  290. {groups.map((group) => (
  291. <option key={group.id} value={group.id}>
  292. {group.name}
  293. </option>
  294. ))}
  295. </select>
  296. {realtimeErrors.group?.message ? (
  297. <p className="text-danger mt-2">{realtimeErrors.group.message}</p>
  298. ) : null}
  299. </div>
  300. </div>
  301. </div>
  302.  
  303. {/* Optional Fields Card */}
  304. <div className="card mb-3">
  305. <div className="card-header bg-light fw-semibold">Optional Information</div>
  306. <div className="card-body">
  307. {/* Job */}
  308. <div className="mb-3">
  309. <label htmlFor="job" className="form-label">
  310. Job
  311. </label>
  312. <input
  313. className="form-control"
  314. type="text"
  315. placeholder="Enter job title"
  316. id="job"
  317. {...register(
  318. "job",
  319. validationRules.genericOptionalMaxLengthField("Job", 100)
  320. )}
  321. />
  322. {realtimeErrors.job?.message ? (
  323. <p className="text-danger mt-2">{realtimeErrors.job.message}</p>
  324. ) : null}
  325. </div>
  326.  
  327. {/* Hobbies */}
  328. <div className="mb-3">
  329. <label htmlFor="hobbies" className="form-label">
  330. Hobbies
  331. </label>
  332. <input
  333. className="form-control"
  334. type="text"
  335. placeholder="Enter hobbies"
  336. id="hobbies"
  337. {...register(
  338. "hobbies",
  339. validationRules.genericOptionalMaxLengthField("Hobbies", 100)
  340. )}
  341. />
  342. {realtimeErrors.hobbies?.message ? (
  343. <p className="text-danger mt-2">{realtimeErrors.hobbies.message}</p>
  344. ) : null}
  345. </div>
  346.  
  347. {/* Location */}
  348. <div className="mb-3">
  349. <label htmlFor="location" className="form-label">
  350. Location
  351. </label>
  352. <input
  353. className="form-control"
  354. type="text"
  355. placeholder="Enter location"
  356. id="location"
  357. {...register(
  358. "location",
  359. validationRules.genericOptionalMaxLengthField("Location", 100)
  360. )}
  361. />
  362. {realtimeErrors.location?.message ? (
  363. <p className="text-danger mt-2">{realtimeErrors.location.message}</p>
  364. ) : null}
  365. </div>
  366.  
  367. {/* Signature */}
  368. {/*
  369. <div className="mb-3">
  370. <label htmlFor="signature" className="form-label">
  371. Signature
  372. </label>
  373. <RichTextEditorRHF ref={signatureEditorRef} value={formData.signature} />
  374. </div>
  375. */}
  376. <div className="mb-3">
  377. <label htmlFor="signature" className="form-label">
  378. Signature
  379. </label>
  380. <RichTextEditorRHF
  381. ref={signatureEditorRef}
  382. value={watch("signature")}
  383. rhfRegister={register}
  384. rhfFieldName="signature"
  385. rhfValidationRules={validationRules.email}
  386. />
  387. {realtimeErrors.signature?.message ? (
  388. <p className="text-danger mt-2">{realtimeErrors.signature.message}</p>
  389. ) : null}
  390. </div>
  391.  
  392. {/* Avatar */}
  393. {tmpData.avatar && (
  394. <div className="mb-3">
  395. <label htmlFor="signature" className="form-label">
  396. Avatar
  397. </label>
  398. <div>
  399. <img
  400. src={
  401. tmpData.avatar
  402. ? `${tmpData.avatar}?v=${Date.now()}`
  403. : "/no-avatar.png"
  404. }
  405. alt="avatar"
  406. className="rounded-circle mb-2"
  407. style={{ width: "100px", height: "100px" }}
  408. />
  409. </div>
  410. <div className="form-check">
  411. <input
  412. className="form-check-input"
  413. type="checkbox"
  414. id="delete_avatar"
  415. {...register("delete_avatar", {
  416. setValueAs: (value) => (value ? 1 : 0), // Convert true/false to 1/0
  417. })}
  418. />
  419.  
  420. <label className="form-check-label" htmlFor="delete_avatar">
  421. Delete Avatar?
  422. </label>
  423. </div>
  424. </div>
  425. )}
  426. </div>
  427. </div>
  428.  
  429. {/* Actions */}
  430. <div className="d-flex justify-content-between">
  431. <Link to="/admin/user/list" className="btn btn-secondary">
  432. Cancel
  433. </Link>
  434. <button type="submit" className="btn btn-success">
  435. Save
  436. </button>
  437. </div>
  438. </form>
  439. </div>
  440. </div>
  441. </>
  442. ) : (
  443. <>
  444. <div className="card p-3">Loading...</div>
  445. </>
  446. )}
  447. </div>
  448.  
  449. {/* Success Modal */}
  450. <MegaModal
  451. data={{
  452. modal_show: showSuccessModal,
  453. modal_type: "generic",
  454. modal_title: "Success",
  455. modal_title_classes: "bg-success text-white",
  456. modal_text: `User <strong>${watch("name")} </strong> has been successfully updated.`,
  457. modal_show_no_undo_warning: false,
  458. buttons: [
  459. {
  460. title: "Go to Userlist",
  461. classes: "btn btn-primary",
  462. onclick: () => {
  463. navigate("/admin/user/list");
  464. setShowSuccessModal(false);
  465. },
  466. },
  467. {
  468. title: "Keep Editing",
  469. classes: "btn btn-primary",
  470. onclick: () => {
  471. getUser();
  472. setShowSuccessModal(false);
  473. },
  474. },
  475. ],
  476. }}
  477. />
  478. </>
  479. );
  480. }
Advertisement
Add Comment
Please, Sign In to add comment