Skip to content

Commit cb49b4b

Browse files
mutsinziisaacbahati10
authored andcommitted
Fix (#285):author will be able to update blogs (#294)
1 parent 3914d20 commit cb49b4b

File tree

6 files changed

+621
-143
lines changed

6 files changed

+621
-143
lines changed

src/components/form/Button.tsx

+6-2
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ const Button: React.FC<ButtonProps> = ({
2727
type={type}
2828
onClick={onClick}
2929
disabled={disabled}
30-
className={` w-[25vw] rounded-md px-2 py-3 text-white sm:text-[12px] my-2 focus:bg-[#56C870] bg-primary dark:bg-[#56C870] hover:bg-primary dark:hover:bg-[#80d293] cursor-pointer ${className}`}
30+
className={`w-[25vw] rounded-md px-2 py-3 text-white sm:text-[12px] my-2 focus:bg-[#56C870] bg-primary dark:bg-[#56C870] hover:bg-primary dark:hover:bg-[#80d293] cursor-pointer ${className}`}
3131
role={role}
3232
>
3333
{label} {children}
@@ -43,12 +43,16 @@ Button.propTypes = {
4343
className: PropTypes.string,
4444
role: PropTypes.string,
4545
label: PropTypes.string,
46-
children: PropTypes.node,
46+
children: PropTypes.oneOfType([
47+
PropTypes.node,
48+
PropTypes.arrayOf(PropTypes.node),
49+
]),
4750
};
4851

4952
Button.defaultProps = {
5053
label: "",
5154
onClick: () => {},
55+
type: "button",
5256
};
5357

5458
export default Button;

src/components/home/Blogs.tsx

+2-3
Original file line numberDiff line numberDiff line change
@@ -60,9 +60,8 @@ const BlogCard = ({ blog }: { blog: Blog }) => (
6060
</Link>
6161
<Link to={`/blogs/${blog.id}`}>
6262
<h3 className="text-xl font-semibold mb-2">{blog.title}</h3>
63-
<p className="mb-4 text-black-text dark:text-white overflow-hidden line-clamp-3">
64-
{blog.content}
65-
</p>
63+
<div className="mb-4 text-black-text dark:text-white overflow-hidden line-clamp-3" dangerouslySetInnerHTML={{ __html: `${blog.content.substring(0, 150)}...`}}>
64+
</div>
6665
</Link>
6766
</div>
6867
</div>

src/pages/Blogs/Blogs.tsx

+94-76
Original file line numberDiff line numberDiff line change
@@ -51,89 +51,107 @@ const Blogs: React.FC = () => {
5151
)}
5252
</div>
5353
{loading ? (
54-
<div className="flex justify-center items-center h-screen">
55-
<div className="spinner-border animate-spin inline-block w-8 h-8 border-4 rounded-full border-t-4 border-green-500" role="status">
56-
<span className="sr-only">Loading...</span>
57-
</div>
58-
</div>
54+
<div className="flex justify-center items-center h-screen">
55+
<div
56+
className="spinner-border animate-spin inline-block w-8 h-8 border-4 rounded-full border-t-4 border-green-500"
57+
role="status"
58+
>
59+
<span className="sr-only">Loading...</span>
60+
</div>
61+
</div>
5962
) : blogs.length > 0 ? (
6063
<>
61-
<div className="grid grid-cols-1 sm:grid-cols-3 md:grid-cols-1 gap-6 px-10">
64+
<div className="grid grid-cols-1 sm:grid-cols-3 md:grid-cols-1 gap-6 px-10">
6265
{blogs.map((blog) => (
63-
<div
64-
key={blog.id}
65-
className="bg-gray-200 dark:bg-dark-frame-bg dark:text-white text-primary shadow-lg rounded-lg cursor-pointer hover:shadow-xl transition duration-300"
66-
>
67-
<Link to={`/blogs/${blog.id}`}>
68-
<img
69-
src={blog.coverImage}
70-
alt={blog.title}
71-
className="w-full h-40 object-cover mb-4"
72-
/>
73-
</Link>
74-
<div className="p-3">
75-
<div className="flex justify-between text-sm text-primary dark:text-white mb-4 items-center">
76-
<div className="flex items-center gap-3">
77-
<Link to={`/blogs/${blog.id}`}>
78-
<div className="flex flex-wrap items-center gap-3">
79-
<span className="font-semibold flex flex-row gap-2 items-center">
80-
<FaUser size={20} className="dark:text-green" /> {blog.author.firstname}
81-
</span>
82-
<span className="ml-2 flex flex-row gap-2 items-center">
83-
<FaCalendar size={20} className="dark:text-green" />
84-
{blog.created_at
85-
? new Date(Number(blog.created_at)).toLocaleDateString()
86-
: "Unknown Date"}
87-
</span>
88-
<span className="flex flex-row gap-1 items-center">
89-
<CiHeart size={20} className="dark:text-green" /> {blog.reactions.length || 0}
90-
</span>
91-
<span className="flex flex-row gap-1 items-center">
92-
<FaCommentDots size={20} className="dark:text-green" /> {blog.comments.length || 0}
93-
</span>
94-
</div>
95-
</Link>
96-
</div>
97-
</div>
98-
<Link to={`/blogs/${blog.id}`}>
99-
<h3 className="text-xl font-semibold mb-2">{blog.title}</h3>
100-
<p className="mb-4 text-black-text dark:text-white overflow-hidden line-clamp-3">
101-
{blog.content.substring(0, 150)}...
102-
</p>
103-
</Link>
104-
<div className="flex flex-wrap gap-2 mt-3">
105-
{blog.tags.map((tag) => (
106-
<span
107-
key={tag}
108-
onClick={(e) => {
109-
setSelectedTag(tag);
110-
}}
111-
className="bg-green-100 text-green-700 text-xs px-2 py-1 rounded cursor-pointer hover:bg-green-200"
112-
>
113-
#{tag}
114-
</span>
115-
))}
116-
</div>
117-
</div>
118-
119-
</div>
120-
66+
<div
67+
key={blog.id}
68+
className="bg-gray-200 dark:bg-dark-frame-bg dark:text-white text-primary shadow-lg rounded-lg cursor-pointer hover:shadow-xl transition duration-300"
69+
>
70+
<Link to={`/blogs/${blog.id}`}>
71+
<img
72+
src={blog.coverImage}
73+
alt={blog.title}
74+
className="w-full h-40 object-cover mb-4"
75+
/>
76+
</Link>
77+
<div className="p-3">
78+
<div className="flex justify-between text-sm text-primary dark:text-white mb-4 items-center">
79+
<div className="flex items-center gap-3">
80+
<Link to={`/blogs/${blog.id}`}>
81+
<div className="flex flex-wrap items-center gap-3">
82+
<span className="font-semibold flex flex-row gap-2 items-center">
83+
<FaUser size={20} className="dark:text-green" />{" "}
84+
{blog.author.firstname}
85+
</span>
86+
<span className="ml-2 flex flex-row gap-2 items-center">
87+
<FaCalendar
88+
size={20}
89+
className="dark:text-green"
90+
/>
91+
{blog.created_at
92+
? new Date(
93+
Number(blog.created_at)
94+
).toLocaleDateString()
95+
: "Unknown Date"}
96+
</span>
97+
<span className="flex flex-row gap-1 items-center">
98+
<CiHeart size={20} className="dark:text-green" />{" "}
99+
{blog.reactions.length || 0}
100+
</span>
101+
<span className="flex flex-row gap-1 items-center">
102+
<FaCommentDots
103+
size={20}
104+
className="dark:text-green"
105+
/>{" "}
106+
{blog.comments.length || 0}
107+
</span>
108+
</div>
109+
</Link>
110+
</div>
111+
</div>
112+
<Link to={`/blogs/${blog.id}`}>
113+
<h3 className="text-xl font-semibold mb-2">
114+
{blog.title}
115+
</h3>
116+
<div
117+
className="mb-4 text-black-text dark:text-white overflow-hidden line-clamp-3"
118+
dangerouslySetInnerHTML={{
119+
__html: `${blog.content.substring(0, 150)}...`,
120+
}}
121+
></div>
122+
</Link>
123+
<div className="flex flex-wrap gap-2 mt-3">
124+
{blog.tags.map((tag) => (
125+
<span
126+
key={tag}
127+
onClick={(e) => {
128+
setSelectedTag(tag);
129+
}}
130+
className="bg-green-100 text-green-700 text-xs px-2 py-1 rounded cursor-pointer hover:bg-green-200"
131+
>
132+
#{tag}
133+
</span>
134+
))}
135+
</div>
136+
</div>
137+
</div>
121138
))}
122139
</div>
123-
124-
</>
140+
</>
125141
) : (
126142
<div className="flex flex-col justify-center items-center h-screen text-center dark:bg-dark-bg bg-white px-6">
127-
<svg
128-
xmlns="http://www.w3.org/2000/svg"
129-
viewBox="0 0 24 24"
130-
fill="currentColor"
131-
className="w-16 h-16 text-gray-500 mb-4"
132-
>
133-
<path d="M12 2a10 10 0 11-10 10A10 10 0 0112 2zm5 11H7a1 1 0 000 2h10a1 1 0 000-2zm-1-4a1 1 0 01-1 1H9a1 1 0 010-2h6a1 1 0 011 1z" />
134-
</svg>
135-
<p className="text-lg text-gray-500 dark:text-gray-300">No blogs available at the moment. Check back later!</p>
136-
</div>
143+
<svg
144+
xmlns="http://www.w3.org/2000/svg"
145+
viewBox="0 0 24 24"
146+
fill="currentColor"
147+
className="w-16 h-16 text-gray-500 mb-4"
148+
>
149+
<path d="M12 2a10 10 0 11-10 10A10 10 0 0112 2zm5 11H7a1 1 0 000 2h10a1 1 0 000-2zm-1-4a1 1 0 01-1 1H9a1 1 0 010-2h6a1 1 0 011 1z" />
150+
</svg>
151+
<p className="text-lg text-gray-500 dark:text-gray-300">
152+
No blogs available at the moment. Check back later!
153+
</p>
154+
</div>
137155
)}
138156
</div>
139157
<Footer />

src/pages/Blogs/allBlogs.tsx

+73-29
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import React, { useEffect, useState } from "react";
1+
import React, { useEffect, useState,ChangeEvent } from "react";
22
import { useNavigate } from "react-router-dom";
33
import { getAllBlogs, getBlogsByAuthor } from "../../redux/actions/blogActions";
44
import { createBlogAction } from "../../redux/actions/blogActions";
@@ -9,6 +9,9 @@ import { handleBlogImageUpload } from "../../utils/imageUploadUtil";
99
import { useAppDispatch, useAppSelector } from "../../hooks/hooks";
1010
import AllBlogsSkeleton from "../../skeletons/allBlogsSkeleton";
1111
import SingleBlogSkeleton from "skeletons/singleBlogSkeleton";
12+
import ReactQuill from 'react-quill';
13+
import 'react-quill/dist/quill.snow.css';
14+
import { AiOutlineClose } from 'react-icons/ai';
1215
import HideUnhideButton from "../../components/hideBlog"
1316

1417
interface Comment {
@@ -67,6 +70,7 @@ const AllBlogs = () => {
6770
const [addingBlog, setAddingBlog] = useState(false);
6871
const [manyImaages, setManyImages] = useState(false);
6972
const [tags, setTags] = useState('');
73+
const [content, setContent] = useState('');
7074
const [submitData, setSubmitData] = useState<SubmitData>({
7175
title: "",
7276
content: "",
@@ -226,20 +230,62 @@ const AllBlogs = () => {
226230
await dispatch(createBlogAction(obj));
227231
setAddingBlog(false);
228232
removeModal();
229-
dispatch(getBlogsByAuthor(String(userId)));
233+
234+
if (role == "applicant" || role == "trainee") {
235+
dispatch(getBlogsByAuthor(String(userId)));
236+
} else {
237+
dispatch(getAllBlogs());
238+
}
239+
230240
} catch (error) {
231241
console.log(error);
232242
} finally {
233243
setIsUploading(false);
234244
}
235245
};
246+
247+
const modules = {
248+
toolbar: [
249+
[{ 'header': [1, 2, 3, 4, 5, 6, false] }],
250+
['bold', 'italic', 'underline', 'strike'],
251+
[{ 'list': 'ordered'}, { 'list': 'bullet' }],
252+
['link'],
253+
[{ 'align': [] }],
254+
[{ 'color': [] }, { 'background': [] }],
255+
['clean']
256+
]
257+
};
258+
259+
const formats = [
260+
'header',
261+
'bold', 'italic', 'underline', 'strike',
262+
'list', 'bullet',
263+
'link', 'image',
264+
'align',
265+
'color', 'background'
266+
];
267+
268+
const handleContentChange = (value: string) => {
269+
setContent(value);
270+
271+
// Simulate an event object to match the expected input of handleInputChange
272+
const syntheticEvent = {
273+
target: {
274+
name: 'content',
275+
value: value
276+
}
277+
} as ChangeEvent<HTMLTextAreaElement>;
278+
279+
handleInputChange(syntheticEvent);
280+
};
281+
236282

237283

238284
return (
239285
<div className="min-h-screen bg-white w-full dark:bg-slate-900 dark:text-white p-6">
240286
{addNewBlogModal && (
241287
<div className="fixed inset-0 mt-16 p-0 flex items-center justify-center bg-black bg-opacity-20 dark:bg-opacity-40">
242-
<div className="bg-white dark:bg-dark-bg w-11/12 md:w-3/5 lg:w-2/5 rounded-lg p-6">
288+
<div className="bg-white dark:bg-dark-bg w-11/12 md:w-3/5 lg:w-2/5 rounded-lg p-6 max-h-[600px] overflow-y-scroll">
243289
<div className="w-full flex mb-2 items-center justify-between">
244290
<h3 className="font-bold text-m dark:text-white ">
245291
CREATE A NEW BLOG
@@ -267,13 +313,14 @@ const AllBlogs = () => {
267313
</div>
268314
<div className="flex flex-col">
269315
<label className="font-semibold text-sm">Blog Content</label>
270-
<textarea
271-
name="content"
272-
value={submitData.content}
273-
onChange={handleInputChange}
274-
className="border rounded focus:ring-2 focus:ring-white dark:bg-black px-4 py-2 h-24"
316+
<ReactQuill
317+
value={content}
318+
onChange={handleContentChange}
319+
modules={modules}
320+
formats={formats}
321+
theme="snow"
322+
className="border rounded dark:bg-black"
275323
placeholder="Enter Blog Content"
276-
maxLength={2000}
277324
/>
278325
{errors.content && (
279326
<span className="text-red-500 text-xs">{errors.content}</span>
@@ -361,19 +408,18 @@ const AllBlogs = () => {
361408
<div className="max-w-6xl mt-2 mx-auto">
362409
<div className="mb-6 w-full flex items-center justify-between">
363410
<h1 className="text-2xl font-semibold">All Blogs</h1>
364-
{userId && role && (role == "applicant" || role == "trainee") && (
365-
<div className="w-full sm:w-auto">
366-
<button
367-
disabled={isLoading}
368-
onClick={Open}
369-
className={`flex items-center justify-center w-full sm:w-auto ${
370-
isLoading ? "bg-emerald-300" : "bg-primary dark:bg-[#56C870]"
371-
} rounded-md py-2 px-4 text-white font-medium cursor-pointer hover:opacity-90 transition-opacity`}
372-
>
373-
<icons.AiOutlinePlus className="mr-2" /> Blog
374-
</button>
375-
</div>
376-
)}
411+
412+
<div className="w-full sm:w-auto">
413+
<button
414+
disabled={isLoading}
415+
onClick={Open}
416+
className={`flex items-center justify-center w-full sm:w-auto ${
417+
isLoading ? "bg-emerald-300" : "bg-primary dark:bg-[#56C870]"
418+
} rounded-md py-2 px-4 text-white font-medium cursor-pointer hover:opacity-90 transition-opacity`}
419+
>
420+
<icons.AiOutlinePlus className="mr-2" /> Blog
421+
</button>
422+
</div>
377423
</div>
378424

379425
<div className="space-y-4">
@@ -397,9 +443,10 @@ const AllBlogs = () => {
397443
<p className="text-lg font-medium break-words whitespace-normal overflow-wrap-break-word transition-colors">
398444
{blog.title}
399445
</p>
400-
<p className="dark:text-slate-400 text-slate-800 break break-words text-sm line-clamp-2">
401-
{blog.content}
402-
</p>
446+
<div
447+
className="dark:text-slate-400 text-slate-800 break break-words text-sm line-clamp-2"
448+
dangerouslySetInnerHTML={{ __html: blog.content }}
449+
></div>
403450
</div>
404451
<div className="w-1/6 flex flex-col items-end text-sm dar:text-slate-400">
405452
<span>{`${blog.author.firstname} ${blog.author.lastname}`}</span>
@@ -408,10 +455,7 @@ const AllBlogs = () => {
408455
</span>
409456
</div>
410457
{role === "admin" || role === "superAdmin" ? (
411-
<HideUnhideButton
412-
blogId={blog.id}
413-
isHidden={blog.isHidden}
414-
/>
458+
<HideUnhideButton blogId={blog.id} isHidden={blog.isHidden} />
415459
) : null}
416460
</div>
417461
))

0 commit comments

Comments
 (0)