Skip to content

Commit

Permalink
update (#118)
Browse files Browse the repository at this point in the history
* add speechgenarator

* css updated

* add validations
  • Loading branch information
dinidusachintha authored Oct 14, 2024
1 parent 0a396ae commit d647bf1
Show file tree
Hide file tree
Showing 12 changed files with 266 additions and 105 deletions.
1 change: 1 addition & 0 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"jspdf": "^2.5.2",
"jspdf-autotable": "^3.8.3",
"mongoose": "^8.4.3",
"morgan": "^1.10.0",
"multer": "^1.4.5-lts.1",
"nodemailer": "^6.9.14",
"pdfkit": "^0.15.0",
Expand Down
38 changes: 32 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,13 @@ 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
2 changes: 1 addition & 1 deletion frontend/src/Components/Home/BlogHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const BlogHeader = ({ loading }) => {
{/* Update the link to the shop */}
<button
type="button"
className="flex items-center bg-green-500 text-white px-4 py-2 rounded hover:bg-green-600"
className="flex items-center px-4 py-2 text-white rounded bg-lime-500 hover:bg-lime-600"
disabled={loading}
>
<FaCartShopping className="mr-2" />{' '}
Expand Down
131 changes: 88 additions & 43 deletions frontend/src/Pages/blog manage/AddBlog.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,34 +5,68 @@ 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 +75,62 @@ 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({})

// Basic validation
if (!title || !content || !author) {

const titleError = validateTitle()
const contentError = validateContent()
const authorError = validateAuthor()

if (titleError || contentError || authorError) {
setErrors({
title: !title ? 'Title is required' : '',
content: !content ? 'Content is required' : '',
author: !author ? 'Author is required' : '',
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,20 +143,29 @@ 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"

className="text-black dark:text-black"

htmlFor="title"
>
Title
Expand All @@ -146,7 +184,9 @@ function AddNews() {

<div>
<label
className="text-white dark:text-gray-200"

className="text-black dark:text-black"

htmlFor="author"
>
Author
Expand All @@ -165,7 +205,9 @@ function AddNews() {

<div className="col-span-2">
<label
className="text-white dark:text-gray-200"

className="text-black dark:text-black"

htmlFor="content"
>
Content
Expand All @@ -184,7 +226,9 @@ function AddNews() {

<div>
<label
className="block mb-2 font-medium text-white text-l dark:text-white"

className="block mb-2 font-medium text-black text-l dark:text-black"

htmlFor="newsImage"
>
Upload Image
Expand All @@ -198,10 +242,11 @@ function AddNews() {
/>
</div>

{/* Image Preview Section */}
{imagePreview && (
<div className="col-span-2 mt-4">
<h2 className="text-white dark:text-gray-200">

<h2 className="text-black dark:text-black">

Image Preview:
</h2>
<img
Expand All @@ -216,8 +261,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
Loading

0 comments on commit d647bf1

Please sign in to comment.