Skip to content

Commit

Permalink
Merge branch 'dinidusachintha-sachintha-update' into development
Browse files Browse the repository at this point in the history
  • Loading branch information
nmdra committed Oct 14, 2024
2 parents 0a396ae + f7d38a4 commit 76446a1
Show file tree
Hide file tree
Showing 10 changed files with 186 additions and 109 deletions.
36 changes: 30 additions & 6 deletions backend/routes/Blog.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,28 @@ import Blog from '../models/Blog.js' // Blog model

const router = Router()

// Function to validate required fields
// Function to validate required fields with enhanced rules
const validateBlogFields = (title, content, author) => {
const errors = {}
if (!title) errors.title = 'Title is required.'
if (!content) errors.content = 'Content is required.'
if (!author) errors.author = 'Author is required.'

if (!title) {
errors.title = 'Title is required.'
} else if (title.length < 5) {
errors.title = 'Title must be at least 5 characters long.'
}

if (!content) {
errors.content = 'Content is required.'
} else if (content.length < 10) {
errors.content = 'Content must be at least 10 characters long.'
}

if (!author) {
errors.author = 'Author is required.'
} else if (author.trim().length === 0) {
errors.author = 'Author name cannot be empty.'
}

return errors
}

Expand All @@ -20,7 +36,11 @@ router.post('/add', async (req, res) => {
// Validate required fields
const errors = validateBlogFields(title, content, author)
if (Object.keys(errors).length) {
return res.status(400).json({ errors })
// Return a structured response showing all validation errors
return res.status(400).json({
message: 'Validation failed. Please fix the errors below.',
errors
})
}

const newBlog = new Blog({
Expand Down Expand Up @@ -75,7 +95,11 @@ router.put('/update/:id', async (req, res) => {
// Validate required fields
const errors = validateBlogFields(title, content, author)
if (Object.keys(errors).length) {
return res.status(400).json({ errors })
// Return a structured response showing all validation errors
return res.status(400).json({
message: 'Validation failed. Please fix the errors below.',
errors
})
}

// Find the blog post by ID and update it
Expand Down
132 changes: 73 additions & 59 deletions frontend/src/Pages/blog manage/AddBlog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,67 @@ function AddNews() {
const [title, setTitle] = useState('')
const [content, setContent] = useState('')
const [author, setAuthor] = useState('')
const [selectedFile, setSelectedFile] = useState(null) // State for selected file
const [imagePreview, setImagePreview] = useState(null) // State for image preview
const [uploadMessage, setUploadMessage] = useState('') // Message for file upload status
const [selectedFile, setSelectedFile] = useState(null)
const [imagePreview, setImagePreview] = useState(null)
const [uploadMessage, setUploadMessage] = useState('')
const [successMessage, setSuccessMessage] = useState('')
const [loading, setLoading] = useState(false) // State to track loading
const [loading, setLoading] = useState(false)
const [errors, setErrors] = useState({})

const MAX_FILE_SIZE = 10 * 1024 * 1024 // 5MB limit
const ALLOWED_FILE_TYPES = ['image/jpeg', 'image/png']

useEffect(() => {
// Cleanup function to revoke object URL to avoid memory leaks
return () => {
if (imagePreview) {
URL.revokeObjectURL(imagePreview)
}
}
}, [imagePreview])

// Function to handle image upload
// Validate title length
const validateTitle = () => {
if (!title) return 'Title is required'
if (title.length > 100) return 'Title cannot exceed 100 characters'
return ''
}

// Validate content length
const validateContent = () => {
if (!content) return 'Content is required'
if (content.length < 20) return 'Content must be at least 20 characters'
return ''
}

// Validate author name (no numbers allowed)
const validateAuthor = () => {
if (!author) return 'Author is required'
const regex = /^[a-zA-Z\s]*$/
if (!regex.test(author)) return 'Author name should not contain numbers or special characters'
return ''
}

const handleUpload = async () => {
if (!selectedFile) {
setUploadMessage('No file selected')
return null
}

if (selectedFile.size > MAX_FILE_SIZE) {
setUploadMessage('File size exceeds the 5MB limit.')
return null
}

if (!ALLOWED_FILE_TYPES.includes(selectedFile.type)) {
setUploadMessage('Only JPG or PNG files are allowed.')
return null
}

const formData = new FormData()
formData.append('image', selectedFile)
formData.append('folder', 'avatars') // Adjust folder if needed
formData.append('folder', 'avatars')

setLoading(true) // Start loading
setLoading(true)

try {
const response = await axios.post('/api/images', formData, {
Expand All @@ -41,67 +74,56 @@ function AddNews() {
},
})

setUploadMessage('Image uploaded successfully!')
console.log(response.data)
return response.data.url // Return the uploaded image URL
return response.data.url
} catch (error) {
setUploadMessage('Upload failed: ' + error.message)
console.error('Upload error:', error) // Log the error for debugging
return null // Return null to signify failure
return null
} finally {
setLoading(false) // Stop loading
setLoading(false)
}
}

// Function to handle adding news
const addNews = async (e) => {
e.preventDefault()

// Clear previous errors
setErrors({})
const titleError = validateTitle()
const contentError = validateContent()
const authorError = validateAuthor()

// Basic validation
if (!title || !content || !author) {
setErrors({
title: !title ? 'Title is required' : '',
content: !content ? 'Content is required' : '',
author: !author ? 'Author is required' : '',
})
if (titleError || contentError || authorError) {
setErrors({ title: titleError, content: contentError, author: authorError })
return
}

// Upload the image first and get the URL
try {
const uploadedUrl = await handleUpload()

if (!uploadedUrl) return // Exit if image upload failed
if (!uploadedUrl) return

const news = {
title,
content,
author,
newsImage: uploadedUrl, // Use the uploaded image URL
newsImage: uploadedUrl,
}

await axios.post('/api/blog/add', news)
setSuccessMessage('✅ News added successfully!')
setSuccessMessage('✅ Blog added successfully!')
resetForm()
} catch (err) {
console.error('Error adding news:', err)
alert('Failed to add news: ' + err.message)
console.error('Error adding Blog:', err)
alert('Failed to add Blog: ' + err.message)
}
}

// Function to handle image file selection and preview
const handleImageChange = (e) => {
const file = e.target.files[0]
if (file) {
setSelectedFile(file)
setImagePreview(URL.createObjectURL(file)) // Create preview URL for the selected image
setImagePreview(URL.createObjectURL(file))
}
}

// Function to reset form fields
const resetForm = () => {
setTitle('')
setContent('')
Expand All @@ -114,22 +136,26 @@ function AddNews() {
return (
<>
{successMessage && (
<div className="p-4 mb-4 text-center text-white bg-blue-600 rounded-md">
<div className="p-4 mb-4 text-center text-white rounded-md bg-lime-600">
{successMessage}
</div>
)}
<section className="max-w-4xl p-6 mx-auto mt-20 bg-blue-700 rounded-md shadow-md dark:bg-gray-800">
<h1 className="text-xl font-bold text-white capitalize dark:text-white">
Add News

{uploadMessage && (
<div className="p-4 mb-4 text-center text-white bg-red-600 rounded-md">
{uploadMessage}
</div>
)}

<section className="max-w-4xl p-6 mx-auto mt-20 rounded-md shadow-md bg-lime-700 dark:bg-gray-200">
<h1 className="text-xl font-bold text-black capitalize dark:text-black">
Add Blog
</h1>

<form onSubmit={addNews}>
<div className="grid grid-cols-1 gap-6 mt-4 sm:grid-cols-2">
<div>
<label
className="text-white dark:text-green-200"
htmlFor="title"
>
<label className="text-black dark:text-black" htmlFor="title">
Title
</label>
<input
Expand All @@ -145,10 +171,7 @@ function AddNews() {
</div>

<div>
<label
className="text-white dark:text-gray-200"
htmlFor="author"
>
<label className="text-black dark:text-black" htmlFor="author">
Author
</label>
<input
Expand All @@ -164,10 +187,7 @@ function AddNews() {
</div>

<div className="col-span-2">
<label
className="text-white dark:text-gray-200"
htmlFor="content"
>
<label className="text-black dark:text-black" htmlFor="content">
Content
</label>
<textarea
Expand All @@ -183,10 +203,7 @@ function AddNews() {
</div>

<div>
<label
className="block mb-2 font-medium text-white text-l dark:text-white"
htmlFor="newsImage"
>
<label className="block mb-2 font-medium text-black text-l dark:text-black" htmlFor="newsImage">
Upload Image
</label>
<input
Expand All @@ -198,12 +215,9 @@ function AddNews() {
/>
</div>

{/* Image Preview Section */}
{imagePreview && (
<div className="col-span-2 mt-4">
<h2 className="text-white dark:text-gray-200">
Image Preview:
</h2>
<h2 className="text-black dark:text-black">Image Preview:</h2>
<img
src={imagePreview}
alt="Preview"
Expand All @@ -216,8 +230,8 @@ function AddNews() {
<div className="flex justify-end mt-6">
<button
type="submit"
className="px-6 py-2 leading-5 text-white bg-blue-500 rounded-md hover:bg-blue-900"
disabled={loading} // Disable button while loading
className="px-6 py-2 leading-5 text-white rounded-md bg-lime-500 hover:bg-lime-600"
disabled={loading}
>
{loading ? 'Uploading...' : 'Submit'}
</button>
Expand Down
14 changes: 7 additions & 7 deletions frontend/src/Pages/blog manage/BlogList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ function BlogList() {

return (
<>
<section className="p-6 mx-auto mt-20 bg-green-700 rounded-md shadow-md max-w-7xl dark:bg-green-800">
<section className="p-6 mx-auto mt-20 bg-white rounded-md shadow-md max-w-7xl dark:bg-gray-100">
<div className="flex items-center justify-between mb-4">
<h1 className="text-4xl font-bold text-white capitalize dark:text-white">
<h1 className="text-4xl font-bold text-black capitalize dark:text-black">
Blog List
</h1>
</div>
Expand All @@ -62,23 +62,23 @@ function BlogList() {
{blogs.map((blog) => (
<div
key={blog._id}
className="flex items-start p-4 bg-white rounded-md shadow-md dark:bg-green-800"
className="flex items-start p-4 bg-white rounded-md shadow-md dark:bg-white"
>
<div className="flex-shrink-0 mr-4">
<img
src={`${blog.newsImage}`}
alt={blog.title}
className="object-cover w-32 h-32 border border-green-300 rounded-md"
className="object-cover w-32 h-32 border rounded-md border-lime-500"
/>
</div>
<div className="flex-1">
<h2 className="text-2xl font-semibold text-gray-800 dark:text-white">
<h2 className="text-2xl font-semibold text-gray-800 dark:text-black">
{blog.title}
</h2>
<p className="text-gray-600 dark:text-green-300">
<p className="text-gray-600 dark:text-black">
Author: {blog.author}
</p>
<p className="mt-2 text-gray-600 dark:text-green-400 line-clamp-3">
<p className="mt-2 text-gray-600 dark:text-black line-clamp-3">
{blog.content.substring(0, 50)}...
</p>
</div>
Expand Down
10 changes: 5 additions & 5 deletions frontend/src/Pages/blog manage/CommentList.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ const CommentList = () => {
}

return (
<section className="p-6 mx-auto mt-20 bg-green-700 rounded-md shadow-md max-w-7xl dark:bg-green-800">
<section className="p-6 mx-auto mt-20 bg-gray-100 rounded-md shadow-md max-w-7xl dark:bg-gray-200">
<div className="flex items-center justify-between mb-4">
<h1 className="text-4xl font-bold text-white capitalize dark:text-white">
<h1 className="text-4xl font-bold text-black capitalize dark:text-black">
Comments List
</h1>
</div>
Expand All @@ -45,13 +45,13 @@ const CommentList = () => {
{comments.map((comment) => (
<div
key={comment._id}
className="flex items-start p-4 bg-white rounded-md shadow-md dark:bg-green-800"
className="flex items-start p-4 bg-white rounded-md shadow-md dark:bg-white"
>
<div className="flex-1">
<h2 className="text-xl font-semibold text-gray-800 dark:text-white">
<h2 className="text-xl font-semibold text-gray-800 dark:text-black">
Comment by: {comment.name}
</h2>
<p className="text-gray-600 dark:text-green-300">
<p className="text-gray-600 dark:text-black">
{comment.comment}
</p>
</div>
Expand Down
Loading

0 comments on commit 76446a1

Please sign in to comment.