Update auth context
// add profile property
const [auth, setAuth] = useState({
user: null,
token: "",
profile: {
name: "",
email: "",
website: "",
password: "",
role: "",
},
});
To populate and make changes to the updating user data, we will use context state so that it's easy to re-use the component in many pages (for example admin can use it to update other users profile while each user can use the same component to update their profile)
// admin/users/[id].js
useEffect(() => {
if (auth?.token) loadUser();
}, [id, auth?.token]);
const loadUser = async () => {
try {
const { data } = await axios.get(`/user/${id}`);
setAuth({ ...auth, profile: data });
} catch (err) {
console.log(err);
}
};
Server
We need 2 different endpoints to update user. One for all users including admin to update their own profile. The other is for admins to update other users profile (including their role)
// route
router.put("/update-user", requireSignin, updateUser);
router.put("/update-user-by-admin", requireSignin, isAdmin, updateUserByAdmin);
// controller
export const updateUser = async (req, res) => {
try {
const { id, name, email, password, website } = req.body;
const userFromDb = await User.findById(id);
if (userFromDb._id.toString() !== req.user._id.toString()) {
return res.json({ error: "Unauthorized" });
}
// check valid email
if (!emailValidator.validate(email)) {
return res.json({ error: "Invalid email" });
}
// check if email is taken
const exist = await User.findOne({ email });
if (exist && exist._id.toString() !== userFromDb._id.toString()) {
return res.json({ error: "Email is taken" });
}
// check password length
if (password && password.length < 6) {
return res.json({
error: "Password is required and should be 6 characters long",
});
}
const hashedPassword = password ? await hashPassword(password) : undefined;
const updated = await User.findByIdAndUpdate(
id,
{
name: name || userFromDb.name,
email: email || userFromDb.email,
password: hashedPassword || userFromDb.password,
website: website || userFromDb.website,
},
{ new: true }
);
// console.log("updated user", updated);
res.json(updated);
} catch (err) {
res.sendStatus(400);
console.log(err);
}
};
export const updateUserByAdmin = async (req, res) => {
try {
const { id, name, email, password, website, role } = req.body;
const userFromDb = await User.findById(id);
// check valid email
if (!emailValidator.validate(email)) {
return res.json({ error: "Invalid email" });
}
// check if email is taken
const exist = await User.findOne({ email });
if (exist && exist._id.toString() !== userFromDb._id.toString()) {
return res.json({ error: "Email is taken" });
}
// check password length
if (password && password.length < 6) {
return res.json({
error: "Password is required and should be 6 characters long",
});
}
const hashedPassword = password ? await hashPassword(password) : undefined;
const updated = await User.findByIdAndUpdate(
id,
{
name: name || userFromDb.name,
email: email || userFromDb.email,
password: hashedPassword || userFromDb.password,
website: website || userFromDb.website,
role: role || userFromDb.role,
},
{ new: true }
);
// console.log("updated user", updated);
res.json(updated);
} catch (err) {
res.sendStatus(400);
console.log(err);
}
};
Client
Pass props title
from /admin/users/[id].js
to ProfileUpdate
component
<ProfileUpdate title="Edit profile" user={user} />
// components/user/ProfileUpdate
import { useState, useEffect, useContext } from "react";
import { Row, Col, Button, Modal, Input, Upload, Image, Checkbox } from "antd";
import { AuthContext } from "../../context/auth";
import axios from "axios";
import { Select, Typography } from "antd";
import toast from "react-hot-toast";
import { useRouter } from "next/router";
import generator from "generate-password";
const { Option } = Select;
// admin-updating-themself
// admin-updating-users
// users-updating-themself
const ProfileUpdate = ({ title }) => {
// context
const [auth, setAuth] = useContext(AuthContext);
// hooks
const router = useRouter();
// console.log("ROUTER QUERY =>", router);
// state
const [id, setId] = useState("");
const [name, setName] = useState("");
const [email, setEmail] = useState("");
const [website, setWebsite] = useState("");
const [password, setPassword] = useState("");
const [role, setRole] = useState("");
const [loading, setLoading] = useState(false);
// to show/hide roles
const [showRoles, setShowRoles] = useState(false);
useEffect(() => {
setName(auth.profile?.name);
setEmail(auth.profile?.email);
setWebsite(auth.profile?.website);
setRole(auth.profile?.role);
setId(auth.profile?._id);
}, [auth]);
useEffect(() => {
if (router.query?.routename === "update-user-by-admin") {
setShowRoles(true);
}
}, [router.query?.routename]);
const handleSubmit = async () => {
try {
setLoading(true);
const { data } = await axios.put(`/${router.query.routename}`, {
id,
name,
email,
website,
password,
role,
});
// console.log("PROFILE UPDATED => ", data);
if (data?.error) {
toast.error(data.error);
setLoading(false);
} else {
console.log("data => ", data);
setName(data.name);
setEmail(data.email);
setWebsite(data.website);
setRole(data.role);
setId(data._id);
// update context and local storage
setAuth({ ...auth, user: data });
let fromLocalStorage = JSON.parse(localStorage.getItem("auth"));
fromLocalStorage.user = data.user;
localStorage.setItem("auth", JSON.stringify(fromLocalStorage));
toast.success("Profile updated successfully");
setLoading(false);
}
} catch (err) {
console.log(err);
toast.error("Profile update failed. Try again.");
setLoading(false);
}
};
return (
<Row>
<Col span={12} offset={6}>
<h4 style={{ marginBottom: "-10px" }}>{title}</h4>
<Input
style={{ margin: "20px 0px 10px 0px" }}
size="large"
placeholder="Full name"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<Input
style={{ margin: "10px 0px 10px 0px" }}
size="large"
placeholder="Email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<Input
style={{ margin: "10px 0px 10px 0px" }}
size="large"
placeholder="Website"
value={website}
onChange={(e) => setWebsite(e.target.value)}
/>
<div style={{ display: "flex" }}>
<Input.Password
style={{ margin: "10px 0px 10px 0px" }}
size="large"
placeholder="Password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</div>
{showRoles && (
<Select
value={role}
style={{ width: "100%", margin: "10px 0px 10px 0px" }}
onChange={(v) => setRole(v)}
>
<Option value="Subscriber">Subscriber</Option>
<Option value="Author">Author</Option>
<Option value="Admin">Admin</Option>
</Select>
)}
<Button
onClick={handleSubmit}
type="default"
htmlType="submit"
style={{ margin: "10px 0px 10px 0" }}
loading={loading}
block
>
Submit
</Button>
</Col>
</Row>
);
};
export default ProfileUpdate;
Pass the endpoint using route query so that if admin is trying to update other user's profile it will make to reach correct endpoint in the backend
// admin/users/index.js
const dynamicRoutename = (user) =>
user._id === auth.user._id ? "update-user" : "update-user-by-admin";
<Link
href={{
pathname: `/admin/users/${item._id}`,
query: { routename: dynamicRoutename(item) },
}}
>
<a>edit</a>
</Link>,