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>,