From f1e0113b06f320166b58a53885f85c51420b8fb4 Mon Sep 17 00:00:00 2001 From: Sibunnayak Date: Mon, 1 Apr 2024 16:23:50 +0530 Subject: [PATCH 1/4] Blog Create,Update,View page with api integration and delete api integration Done --- package.json | 1 + src/_nav.js | 2 +- src/routes.js | 12 + src/views/Blog/Blogs.jsx | 498 ++++++++-------------------------- src/views/Blog/CreateBlog.jsx | 264 ++++++++++-------- src/views/Blog/EditBlog.jsx | 419 ++++++++++++++++++++++++++++ src/views/Blog/ViewBlog.jsx | 135 +++++++++ src/views/Blog/test.jsx | 0 8 files changed, 830 insertions(+), 501 deletions(-) create mode 100644 src/views/Blog/EditBlog.jsx create mode 100644 src/views/Blog/ViewBlog.jsx delete mode 100644 src/views/Blog/test.jsx diff --git a/package.json b/package.json index cdf13a9..0cee480 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "country-state-city": "^3.2.1", "draft-js": "^0.11.7", "draft-js-export-html": "^1.4.1", + "draft-js-import-html": "^1.4.1", "md5": "^2.3.0", "moment": "^2.30.1", "prop-types": "^15.7.2", diff --git a/src/_nav.js b/src/_nav.js index 2360dcf..19e5933 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -253,7 +253,7 @@ const _nav = [ { component: CNavGroup, name: "Blog", - icon: , + icon: , items: [ { component: CNavItem, diff --git a/src/routes.js b/src/routes.js index ad812ad..e0d91d1 100644 --- a/src/routes.js +++ b/src/routes.js @@ -118,6 +118,8 @@ import EditTestimonial from "./views/Testimonials/EditTestimonial"; //Blogs import Blogs from "./views/Blog/Blogs"; import CreateBlog from "./views/Blog/CreateBlog"; +import UpdateBlog from "./views/Blog/EditBlog"; +import ViewBlog from "./views/Blog/ViewBlog"; const routes = [ { path: "/", exact: true, name: "Home" }, { @@ -492,6 +494,16 @@ const routes = [ name: "Blogs", element: CreateBlog, }, + { + path: "/blog/edit/:id", + name: "Blogs", + element: UpdateBlog, + }, + { + path: "/blog/view/:id", + name: "Blogs", + element: ViewBlog, + }, ]; export default routes; diff --git a/src/views/Blog/Blogs.jsx b/src/views/Blog/Blogs.jsx index 13547c1..13c7cc5 100644 --- a/src/views/Blog/Blogs.jsx +++ b/src/views/Blog/Blogs.jsx @@ -18,34 +18,33 @@ import SearchIcon from "@mui/icons-material/Search"; import Fuse from "fuse.js"; import { Typography } from "@material-ui/core"; import { AppBlockingSharp } from "@mui/icons-material"; + const Blogs = () => { const token = isAutheticated(); const [query, setQuery] = useState(""); const navigate = useNavigate(); const [loading, setLoading] = useState(true); const [success, setSuccess] = useState(true); - const [productsData, setProductsData] = useState([]); - const [filterData, setFilterData] = useState([]); - const [queryData, setQueryData] = useState([]); + const [BlogsData, setBlogsData] = useState([]); const [currentPage, setCurrentPage] = useState(1); const [itemPerPage, setItemPerPage] = useState(10); - const [showData, setShowData] = useState(productsData); + const [showData, setShowData] = useState(BlogsData); const handleShowEntries = (e) => { setCurrentPage(1); setItemPerPage(e.target.value); }; - const getProductsData = async () => { + const getBlogsData = async () => { axios - .get(`/api/product/getAll/`, { + .get(`/api/v1/blog/getallblog/`, { headers: { Authorization: `Bearer ${token}`, }, }) .then((res) => { - setProductsData(res.data?.product); + setBlogsData(res?.data?.BlogData); setLoading(false); }) .catch((error) => { @@ -61,17 +60,17 @@ const Blogs = () => { }; useEffect(() => { - getProductsData(); + getBlogsData(); }, [success]); useEffect(() => { const loadData = () => { const indexOfLastPost = currentPage * itemPerPage; const indexOfFirstPost = indexOfLastPost - itemPerPage; - setShowData(productsData.slice(indexOfFirstPost, indexOfLastPost)); + setShowData(BlogsData.slice(indexOfFirstPost, indexOfLastPost)); }; loadData(); - }, [currentPage, itemPerPage, productsData]); + }, [currentPage, itemPerPage, BlogsData]); const handleDelete = (id) => { swal({ @@ -84,7 +83,7 @@ const Blogs = () => { }).then((value) => { if (value === true) { axios - .delete(`/api/product/delete/${id}`, { + .delete(`/api/v1/blog/deleteblog/${id}`, { // Correct the API endpoint headers: { "Access-Control-Allow-Origin": "*", Authorization: `Bearer ${token}`, @@ -93,7 +92,7 @@ const Blogs = () => { .then((res) => { swal({ title: "Deleted", - text: "Product Deleted successfully!", + text: "Blog Deleted successfully!", icon: "success", button: "ok", }); @@ -111,54 +110,24 @@ const Blogs = () => { } }); }; - const [filterCategory, setFilterCategory] = useState(""); - - const handleSearchClick = (query) => { - const option = { - isCaseSensitive: true, - includeScore: false, - shouldSort: true, - includeMatches: false, - findAllMatches: false, - minMatchCharLength: 1, - location: 0, - threshold: 0.6, - distance: 100, - useExtendedSearch: true, - ignoreLocation: false, - ignoreFieldNorm: false, - fieldNormWeight: 1, - keys: ["name"], - }; - - const fuse = new Fuse(productsData, option); - const result = fuse.search(query); - - const searchedResult = result.map((result) => result.item); - console.log(searchedResult); - setQueryData(searchedResult); - }; - useEffect(() => { - if (query !== "") { - setFilterCategory(""); - } - setTimeout(() => handleSearchClick(query), 100); - }, [query]); useEffect(() => { setTimeout(() => { - if (filterCategory !== "") { - const filteredProducts = productsData.filter( - (product) => product.category?.categoryName === filterCategory + if (query !== "") { + let searchedResult = []; + searchedResult = BlogsData.filter((item) => + item.title.toString().includes(query) ); - setFilterData(filteredProducts); - } else { - // If no category is selected, show all products - setShowData(productsData); - // setFilterData(filteredProducts); + setShowData(searchedResult); + } + else{ + getBlogsData(); } }, 100); - }, [filterCategory, productsData]); + }, [query]); + + + return (
@@ -167,11 +136,11 @@ const Blogs = () => {
Blogs @@ -201,20 +170,20 @@ const Blogs = () => {
-
+
@@ -273,48 +317,42 @@ const CreateBlog = () => {

Upload jpg, jpeg and png only*

- - {productImages && - productImages.map((image, i) => ( - - BlogImage + BlogImage - handelDelete(image)} - fontSize="small" - sx={{ - color: "white", - position: "absolute", - cursor: "pointer", - padding: "0.2rem", - background: "black", - borderRadius: "50%", - }} - /> - {/* */} - - ))} - + marginBottom: "1rem", + }} + /> + {/* handelDelete(image)} + fontSize="small" + sx={{ + color: "white", + position: "absolute", + cursor: "pointer", + padding: "0.2rem", + background: "black", + borderRadius: "50%", + }} + /> */} + {/* */} + + )}
-
- {imagesPreview.map((image, index) => ( + {/*
Blog Image Preview - ))} -
+
*/}
diff --git a/src/views/Blog/EditBlog.jsx b/src/views/Blog/EditBlog.jsx new file mode 100644 index 0000000..b2f6ad7 --- /dev/null +++ b/src/views/Blog/EditBlog.jsx @@ -0,0 +1,419 @@ +import React, { useEffect, useState } from "react"; +import Button from "@material-ui/core/Button"; +import { Link, useNavigate, useParams } from "react-router-dom"; +import swal from "sweetalert"; +import axios from "axios"; +import { isAutheticated } from "src/auth"; +import CloudUploadIcon from "@mui/icons-material/CloudUpload"; +import DeleteSharpIcon from "@mui/icons-material/DeleteSharp"; +import { + Box, + FormControl, + IconButton, + MenuItem, + Select, + TextField, +} from "@mui/material"; +import { Editor } from "react-draft-wysiwyg"; +import { EditorState, convertFromHTML, ContentState } from "draft-js"; +import { stateToHTML } from "draft-js-export-html"; // This is for converting Draft.js content state to HTML +import { stateFromHTML } from "draft-js-import-html"; // This is for converting HTML to Draft.js content state + +const UpdateBlog = () => { + const token = isAutheticated(); + const navigate = useNavigate(); + + const [loading, setLoading] = useState(false); + + const [image, setimage] = useState(null); + const [title, setTitle] = useState(""); + const [tag, setTag] = useState(""); //tags array + const [blogContent, setBlogContent] = useState(EditorState.createEmpty()); + const [error, setError] = useState(""); + const [newUpdatedImages, setNewUpdatedImages] = useState(null); + const [Img, setImg] = useState(true); + const id = useParams()?.id; + //get Blogdata + const getBlog = async () => { + try { + const res = await axios.get(`/api/v1/blog/getoneblog/${id}`, { + headers: { + "Access-Control-Allow-Origin": "*", + Authorization: `Bearer ${token}`, + }, + }); + + // console.log(res?.data?.blog); + setTitle(res?.data?.blog?.title); + setimage(res?.data?.blog?.image); + // Convert HTML content to Draft.js EditorState + const contentState = stateFromHTML(res?.data?.blog?.blog_content); + const editorState = EditorState.createWithContent(contentState); + setBlogContent(editorState); + + setImg(false); + + // Joining the tags array into a string separated by commas + const tagsString = res?.data?.blog?.tags.join(","); + setTag(tagsString); + } catch (err) { + swal({ + title: "Error", + text: "Unable to fetch the blog", + icon: "error", + button: "Retry", + dangerMode: true, + }); + } + }; + + useEffect(() => { + getBlog(); + }, []); + + const handleFileChange = (e) => { + const files = e.target.files; + // Reset error state + setError(""); + + // Check if more than one image is selected + if (files.length > 1 || Img === false || newUpdatedImages !== null) { + setError("You can only upload one image."); + return; + } + + // Check file types and append to selectedFiles + const allowedTypes = ["image/jpeg", "image/png", "image/jpg"]; + const file = files[0]; // Only one file is selected, so we get the first one + + if (allowedTypes.includes(file.type)) { + setNewUpdatedImages(file); + } else { + setError("Please upload only PNG, JPEG, or JPG files."); + } + }; + + const handelDelete = async (public_id) => { + const ary = public_id.split("/"); + + const res = await axios.delete( + `/api/v1/blog/deleteImage/jatinMor/Blog/${ary[2]}`, + { + headers: { + "Access-Control-Allow-Origin": "*", + Authorization: `Bearer ${token}`, + }, + } + ); + if (res) { + setimage(null); + setImg(true); + } + }; + const handellocalDelete = () => { + setNewUpdatedImages(null); + }; + const handleSubmit = () => { + if (title === "" || blogContent === "" || tag === "") { + swal({ + title: "Warning", + text: "Fill all mandatory fields", + icon: "error", + button: "Close", + dangerMode: true, + }); + return; + } + setLoading(true); + const contentState = blogContent.getCurrentContent(); + const htmlData = stateToHTML(contentState); + + const formData = new FormData(); + formData.append("title", title); + formData.append("blog_content", htmlData); + formData.append("tags", tag); + + if (newUpdatedImages !== null) { + formData.append("image", newUpdatedImages); + } + + axios + .patch(`/api/v1/blog/updateblog/${id}`, formData, { + headers: { + Authorization: `Bearer ${token}`, + "Content-Type": "multipart/form-data", + "Access-Control-Allow-Origin": "*", + }, + }) + .then((res) => { + swal({ + title: "Updated", + text: "Blog Updated successfully!", + icon: "success", + button: "ok", + }); + setLoading(false); + navigate("/blogs", { replace: true }); + }) + .catch((err) => { + setLoading(false); + + const message = err.response?.data?.message + ? err.response?.data?.message + : "Something went wrong!"; + swal({ + title: "Warning", + text: message, + icon: "error", + button: "Retry", + dangerMode: true, + }); + }); + }; + return ( +
+
+
+
+
+ Update Blog +
+
+

+
+ +
+ + + + +
+
+
+
+
+
+
+
+
+ + setTitle(e.target.value)} + /> + {title ? ( + <> + + {25 - title.length} characters left + + + ) : ( + <> + )}{" "} +
+ +
+ + setTag(e.target.value)} + className="form-control" + id="tag" + placeHolder="enter Tags" + /> + + enter space or comma to add new tag + +
+
+ + + + + {error &&

{error}

} +
+ + *You cannot upload more than 1 image + +
+ + + {Img === false && image !== null ? ( + + profileImage + handelDelete(image.public_id)} + fontSize="small" + sx={{ + color: "white", + position: "absolute", + cursor: "pointer", + padding: "0.2rem", + background: "black", + borderRadius: "50%", + }} + /> + + ) : null} + + {newUpdatedImages !== null && Img && ( + + profileImage + handellocalDelete()} + fontSize="small" + sx={{ + color: "white", + position: "absolute", + cursor: "pointer", + padding: "0.2rem", + background: "black", + borderRadius: "50%", + }} + /> + {/* */} + + )} + +
+ + {/*
+ Blog Image Preview +
*/} +
+
+
+
+
+
+ + {/* Note : style at _custom.scss */} + { + setBlogContent(editorState); + }} + /> + {/* */} +
+
+
+
+
+ ); +}; + +export default UpdateBlog; diff --git a/src/views/Blog/ViewBlog.jsx b/src/views/Blog/ViewBlog.jsx new file mode 100644 index 0000000..5a2b6c7 --- /dev/null +++ b/src/views/Blog/ViewBlog.jsx @@ -0,0 +1,135 @@ +import React, { useEffect, useState } from "react"; +import Button from "@material-ui/core/Button"; +import { Link, useParams } from "react-router-dom"; +import swal from "sweetalert"; +import axios from "axios"; +import { Box } from "@mui/material"; + +const ViewBlog = () => { + const [image, setImage] = useState(null); + const [title, setTitle] = useState(""); + const [tag, setTag] = useState([]); + const [blogContent, setBlogContent] = useState(""); // Changed to string + + const { id } = useParams(); + + const getBlog = async () => { + try { + const res = await axios.get(`/api/v1/blog/getoneblog/${id}`); + + setTitle(res?.data?.blog?.title); + setImage(res?.data?.blog?.image); + setTag(res?.data?.blog?.tags); + // setBlogContent(res?.data?.blog?.blog_content); + setBlogContent(addStylesToHTML(res?.data?.blog?.blog_content)); + } catch (err) { + console.error(err); + swal({ + title: "Error", + text: "Unable to fetch the blog", + icon: "error", + button: "Retry", + dangerMode: true, + }); + } + }; + const addStylesToHTML = (content) => { + // Example: Add styles to

and

    elements + content = content.replace( + /

    /g, + '

    ' + ); + content = content.replace(/

      /g, '
        '); + return content; + }; + useEffect(() => { + getBlog(); + }, []); + + return ( +
        +
        +
        +
        +
        + View Blog +
        +
        +

        +
        + +
        + + + +
        +
        +
        +
        +
        +
        +
        +
        +
        + {image && ( + blog + )} +
        +

        + {title} +

        + + {tag.map((tag, index) => ( +
        + #{tag} +
        + ))} +
        +
        +
        +
        +
        +
        +
        + ); +}; + +export default ViewBlog; diff --git a/src/views/Blog/test.jsx b/src/views/Blog/test.jsx deleted file mode 100644 index e69de29..0000000 From df179be1fa5ab186785cedb0df18621a329cd72e Mon Sep 17 00:00:00 2001 From: Sibunnayak Date: Mon, 1 Apr 2024 17:10:24 +0530 Subject: [PATCH 2/4] Blog Update --- src/views/Blog/CreateBlog.jsx | 9 --------- src/views/Blog/EditBlog.jsx | 9 --------- 2 files changed, 18 deletions(-) diff --git a/src/views/Blog/CreateBlog.jsx b/src/views/Blog/CreateBlog.jsx index 03ebf00..7fb46b0 100644 --- a/src/views/Blog/CreateBlog.jsx +++ b/src/views/Blog/CreateBlog.jsx @@ -236,15 +236,6 @@ const CreateBlog = () => { value={title} onChange={(e) => setTitle(e.target.value)} /> - {title ? ( - <> - - {25 - title.length} characters left - - - ) : ( - <> - )}{" "}
        diff --git a/src/views/Blog/EditBlog.jsx b/src/views/Blog/EditBlog.jsx index b2f6ad7..fc8d850 100644 --- a/src/views/Blog/EditBlog.jsx +++ b/src/views/Blog/EditBlog.jsx @@ -237,15 +237,6 @@ const UpdateBlog = () => { value={title} onChange={(e) => setTitle(e.target.value)} /> - {title ? ( - <> - - {25 - title.length} characters left - - - ) : ( - <> - )}{" "}
        From 49794dfb7b1882c6a0f8e268ff421237fcf66ff0 Mon Sep 17 00:00:00 2001 From: Sibunnayak Date: Mon, 1 Apr 2024 17:58:23 +0530 Subject: [PATCH 3/4] search blog update --- src/views/Blog/Blogs.jsx | 31 ++++++++++++---------- src/views/Blog/CreateBlog.jsx | 49 ----------------------------------- 2 files changed, 17 insertions(+), 63 deletions(-) diff --git a/src/views/Blog/Blogs.jsx b/src/views/Blog/Blogs.jsx index 13c7cc5..da21bbb 100644 --- a/src/views/Blog/Blogs.jsx +++ b/src/views/Blog/Blogs.jsx @@ -83,7 +83,8 @@ const Blogs = () => { }).then((value) => { if (value === true) { axios - .delete(`/api/v1/blog/deleteblog/${id}`, { // Correct the API endpoint + .delete(`/api/v1/blog/deleteblog/${id}`, { + // Correct the API endpoint headers: { "Access-Control-Allow-Origin": "*", Authorization: `Bearer ${token}`, @@ -110,24 +111,23 @@ const Blogs = () => { } }); }; + useEffect(() => { setTimeout(() => { if (query !== "") { - let searchedResult = []; - searchedResult = BlogsData.filter((item) => - item.title.toString().includes(query) + const lowerCaseQuery = query.toLowerCase(); // Convert query to lowercase + + const searchedResult = BlogsData.filter((item) => + item.title.toString().toLowerCase().includes(lowerCaseQuery) ); setShowData(searchedResult); - } - else{ + } else { getBlogsData(); } }, 100); }, [query]); - - return (
        @@ -323,7 +323,10 @@ const Blogs = () => {
        diff --git a/src/views/Blog/CreateBlog.jsx b/src/views/Blog/CreateBlog.jsx index 7fb46b0..a467756 100644 --- a/src/views/Blog/CreateBlog.jsx +++ b/src/views/Blog/CreateBlog.jsx @@ -58,55 +58,6 @@ const CreateBlog = () => { setimage(null); }; - // const handleSubmit = () => { - // setLoading(true); - - // const contentState = blogContent.getCurrentContent(); - // const htmlData = stateToHTML(contentState); - // // console.log(title, typeof htmlData, image, tag); - - // const formData = new FormData(); - // formData.append("title", title); - // formData.append("blog_content", htmlData); - - // formData.append("image", image); - // formData.append("tags", tag); - // // for (let entry of formData.entries()) { - // // console.log(entry); - // // } - // axios - // .post(`/api/v1/blog/create`, formData, { - // headers: { - // Authorization: `Bearer ${token}`, - // "Content-Type": "multipart/form-data", - // "Access-Control-Allow-Origin": "*", - // }, - // }) - // .then((res) => { - // swal({ - // title: "Added", - // text: "Blog added successfully!", - // icon: "success", - // button: "ok", - // }); - // setLoading(false); - // navigate("/blogs"); - // }) - // .catch((err) => { - // console.log(err); - // setLoading(false); - // const message = err.response?.data?.message - // ? err.response?.data?.message - // : "Something went wrong!"; - // swal({ - // title: "Warning", - // text: message, - // icon: "error", - // button: "Retry", - // dangerMode: true, - // }); - // }); - // }; const handleSubmit = () => { // Check if any of the required fields are empty if (!title || !image || !tag || !blogContent) { From 6d3f6eb53b9470abc81ddbea91093a49fff5b062 Mon Sep 17 00:00:00 2001 From: Sibunnayak Date: Mon, 1 Apr 2024 18:30:41 +0530 Subject: [PATCH 4/4] nav bar blog icon change --- src/_nav.js | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/_nav.js b/src/_nav.js index 1fa51b2..ab19495 100644 --- a/src/_nav.js +++ b/src/_nav.js @@ -251,18 +251,25 @@ const _nav = [ }, //Blog start { - component: CNavGroup, + component: CNavItem, name: "Blog", icon: , - items: [ - { - component: CNavItem, - name: "Blog", - icon: , - to: "/blogs", - }, - ], + to: "/blogs", }, + + // { + // component: CNavGroup, + // name: "Blog", + // icon: , + // items: [ + // { + // component: CNavItem, + // name: "Blog", + // icon: , + // to: "/blogs", + // }, + // ], + // }, ]; export default _nav;