Server

// route
router.put("/post/:postId", requireSignin, isAdmin, editPost);
// controller
export const editPost = async (req, res) => {
  try {
    const { postId } = req.params;
    const { title, content, featuredImage, categories } = req.body;
    // get ids for catrgories
    let ids = [];
    for (let i = 0; i < categories.length; i++) {
      Category.findOne({ name: categories[i] }).exec((err, c) => {
        if (err) {
          console.log(err);
        }
        // console.log("c", c._id);
        ids.push(c._id);
      });
    }
    setTimeout(async () => {
      const post = await Post.findByIdAndUpdate(
        postId,
        {
          title,
          slug: slugify(title),
          content,
          categories: ids,
          featuredImage,
        },
        {
          new: true,
        }
      )
        .populate("postedBy", "_id name")
        .populate("categories", "_id name slug")
        .populate("featuredImage", "_id url");

      return res.json(post);
    }, 1000);
  } catch (err) {
    console.log(err);
  }
};

Client

// admin/posts/[slug].js
import { useState, useEffect, useContext } from "react";
import AdminLayout from "../../../components/layout/AdminLayout";
import { Row, Col, Button, Modal, Input, Upload, Image } from "antd";
import Editor from "rich-markdown-editor";
import { ThemeContext } from "../../../context/theme";
import axios from "axios";
import { Select, Typography } from "antd";
import { Loading3QuartersOutlined, UploadOutlined } from "@ant-design/icons";
import Resizer from "react-image-file-resizer";
import toast from "react-hot-toast";
import Media from "../../../components/media/Media";
import { MediaContext } from "../../../context/media";
import { useRouter } from "next/router";

const { Text } = Typography;

const { Option } = Select;

const EditPost = ({ post }) => {
  // hooks
  const [theme, setTheme] = useContext(ThemeContext);
  const [media, setMedia] = useContext(MediaContext);
  const router = useRouter();
  // state
  const [title, setTitle] = useState(post?.title);
  const [content, setContent] = useState(post?.content);
  const [categories, setCatgories] = useState([]); // useEffect populated the names
  const [featuredImage, setFeaturedImage] = useState(post?.featuredImage);
  const [visible, setVisible] = useState(false);
  const [loading, setLoading] = useState(false);
  const [loadedCatetories, setLoadedCatetories] = useState([]);

  useEffect(() => {
    loadCategories();
  }, []);

  useEffect(() => {
    // set post's existing categories
    let arr = [];
    post?.categories?.map((c) => arr.push(c.name));
    setCatgories(arr);
  }, []);

  const loadCategories = async () => {
    try {
      const { data } = await axios.get("/categories");
      setLoadedCatetories(data);
    } catch (err) {
      console.log(err);
    }
  };

  const resizeFile = (file) =>
    new Promise((resolve) => {
      Resizer.imageFileResizer(
        file,
        720,
        400,
        "JPEG",
        100,
        0,
        (uri) => {
          resolve(uri);
        },
        "base64"
      );
    });

  const uploadPostImage = async (file) => {
    try {
      const image = await resizeFile(file);
      const { data } = await axios.post("/upload-image", { image });
      // console.log("UPLOAD FILE RESULT => ", data);
      return data.url;
    } catch (err) {
      console.log(err);
    }
  };

  const handlePublish = async () => {
    try {
      setLoading(true);
      const { data } = await axios.put(`/post/${post._id}`, {
        title,
        content,
        categories,
        featuredImage: media?.selected?._id
          ? media.selected._id
          : featuredImage?._id
          ? featuredImage._id
          : "",
      });
      // console.log("POST UPDATED => ", data);
      if (data?.error) {
        toast.error(data.error);
        setLoading(false);
      } else {
        toast.success("Post updated successfully");
        setMedia({ ...media, selected: null });
        router.push("/admin/posts");
      }
    } catch (err) {
      console.log(err);
      toast.error("Post create failed. Try again.");
      setLoading(false);
    }
  };

  return (
    <AdminLayout>
      <Row>
        <Col sm={22} lg={14} offset={1}>
          <h4 style={{ marginBottom: "-10px" }}>Create Post</h4>
          {/* <pre>{JSON.stringify(media, null, 4)}</pre> */}
          <Input
            style={{ margin: "20px 0px 20px 0px" }}
            size="large"
            placeholder="Give it a title"
            value={title}
            onChange={(e) => setTitle(e.target.value)}
          />

          <Editor
            style={{ width: "100%" }}
            dark={theme === "light" ? false : true}
            defaultValue={content}
            // defaultValue="Write something..."
            uploadImage={uploadPostImage}
            onChange={(v) => setContent(v())}
          />
        </Col>
        <Col sm={22} lg={6} offset={1}>
          <Button
            type="default"
            htmlType="submit"
            style={{ margin: "10px 0px 10px 0" }}
            onClick={() => setVisible(true)}
          >
            Preview
          </Button>

          <h4>Categories</h4>
          <Select
            mode="multiple"
            allowClear
            style={{ width: "100%" }}
            placeholder="Please select"
            onChange={(v) => setCatgories(v)}
            value={[...categories]}
          >
            {loadedCatetories.map((item) => (
              <Option key={item.name}>{item.name}</Option>
            ))}
          </Select>

          {/* show selected/featured image */}
          {media.selected ? (
            <>
              <div style={{ marginBottom: 15 }}></div>

              <Image width="100%" src={media.selected.url} />
            </>
          ) : post?.featuredImage ? (
            <>
              <div style={{ marginBottom: 15 }}></div>

              <Image width="100%" src={post.featuredImage.url} />
            </>
          ) : (
            ""
          )}

          
          <Button
          
            onClick={() => setMedia({ ...media, showMediaModal: true })}
            style={{ marginTop: 10 }}
            icon={<UploadOutlined />}
            block
          >
            Featured Image
          </Button>
          {/* </Upload> */}

          <Button
            onClick={handlePublish}
            type="default"
            htmlType="submit"
            style={{ margin: "10px 0px 10px 0" }}
            loading={loading}
            block
          >
            Update
          </Button>
          {/* for post preview */}
          <Modal
            title="Preview"
            centered
            visible={visible}
            onOk={() => setVisible(false)}
            onCancel={() => setVisible(false)}
            width={720}
            footer={null}
          >
            <h1>{title}</h1>
            <img style={{ width: "100%" }} src={media.selected?.url} />
            <Editor
              dark={theme === "light" ? false : true}
              value={content}
              readOnly={true}
            />
          </Modal>
          {/* for media management */}
          <Modal
            title="Media"
            // centered
            style={{ top: 20 }}
            visible={media.showMediaModal}
            onOk={() => setMedia({ ...media, showMediaModal: false })}
            onCancel={() => setMedia({ ...media, showMediaModal: false })}
            width={720}
            footer={null}
          >
            <Media />
          </Modal>
        </Col>
      </Row>
    </AdminLayout>
  );
};

export async function getServerSideProps({ params }) {
  const { data } = await axios.get(`${process.env.API}/post/${params.slug}`);
  return {
    props: {
      post: data,
    },
  };
}

export default EditPost;