Skip to content
This repository has been archived by the owner on Oct 29, 2024. It is now read-only.

Commit

Permalink
Merge pull request #74 from s-vamshi/simplify-image-upload-for-admin-…
Browse files Browse the repository at this point in the history
…in-products

Simplify image upload for admin in products by integrating Cloudinary
  • Loading branch information
roopeshsn authored Oct 8, 2022
2 parents a61c420 + c6b006b commit 819541d
Show file tree
Hide file tree
Showing 15 changed files with 1,381 additions and 151 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,9 @@ EMAIL_USERNAME =
EMAIL_PASSWORD =
EMAIL_HOST =
EMAIL_PORT =
CLOUDINARY_CLOUD_NAME =
CLOUDINARY_API_KEY =
CLOUDINARY_API_SECRET =
```

### Add data in .env file
Expand Down
14 changes: 13 additions & 1 deletion backend/config/db.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const mongoose = require('mongoose')
const cloudinary = require('cloudinary').v2

const connectDB = async () => {
try {
Expand All @@ -13,4 +14,15 @@ const connectDB = async () => {
}
}

module.exports = connectDB
//cloudinary config details which will be used further to upload images
cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
secure: true,
})

module.exports = {
connectDB,
cloudinary,
}
16 changes: 13 additions & 3 deletions backend/controllers/productController.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@ const createProduct = asyncHandler(async (req, res) => {
category,
countInStock,
} = req.body
console.log(req.user)
const product = new Product({
name,
price,
Expand All @@ -78,8 +77,19 @@ const createProduct = asyncHandler(async (req, res) => {
description,
})

const createdProduct = await product.save()
res.status(201).json(createdProduct)
try {
const createdProduct = await product.save()

res.status(201).json(createdProduct)
} catch (error) {
if (error.name === 'ValidationError') {
let errors = ''
Object.keys(error.errors).forEach((key) => {
errors += error.errors[key].message + '.\n'
})
res.status(500).json(errors)
}
}
})

// @desc Update a product
Expand Down
16 changes: 8 additions & 8 deletions backend/models/productModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,37 +9,37 @@ const productSchema = mongoose.Schema(
},
name: {
type: String,
required: true,
required: [true, 'Please Enter Name of the Product'],
},
imageSrc: {
type: String,
required: true,
required: [true, 'Please Upload an Image File'],
},
imageAlt: {
type: String,
required: true,
required: [true, 'Please Enter Image Alt'],
},
category: {
type: String,
required: true,
required: [true, 'Please Enter Category of the Product'],
},
description: {
type: String,
required: true,
required: [true, 'Please Enter Description of the Product'],
},
price: {
type: Number,
required: true,
required: [true, 'Please Enter Price of Product'],
default: 0,
},
mrp: {
type: Number,
required: true,
required: [true, 'Please Enter MRP of the Product'],
default: 0,
},
countInStock: {
type: Number,
required: true,
required: [true, 'Please Enter Stock of the Product'],
default: 0,
},
},
Expand Down
19 changes: 8 additions & 11 deletions backend/routes/uploadRoutes.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,15 @@
const path = require('path')
const express = require('express')
const multer = require('multer')
const { cloudinary } = require('../config/db')
const { CloudinaryStorage } = require('multer-storage-cloudinary')

const router = express.Router()

const storage = multer.diskStorage({
destination(req, file, cb) {
cb(null, 'uploads/')
},
filename(req, file, cb) {
cb(
null,
`${file.fieldname}-${Date.now()}${path.extname(file.originalname)}`,
)
const storage = new CloudinaryStorage({
cloudinary,
params: {
folder: 'Products',
},
})

Expand All @@ -24,7 +21,7 @@ function checkFileType(file, cb) {
if (extname && mimetype) {
return cb(null, true)
} else {
cb('Images only!')
return cb(new Error('Upload Images(.jpg, .jpeg, .png) only!'))
}
}

Expand All @@ -36,7 +33,7 @@ const upload = multer({
})

router.post('/', upload.single('image'), (req, res) => {
res.send(`/${req.file.path}`)
res.send(`${req.file.path}`)
})

module.exports = router
6 changes: 3 additions & 3 deletions backend/server.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
const path = require('path')
const express = require('express')
const connectDB = require('./config/db')
require('dotenv').config()
const { connectDB } = require('./config/db')
const productRoutes = require('./routes/productRoutes')
const userRoutes = require('./routes/userRoutes')
const orderRoutes = require('./routes/orderRoutes')
Expand All @@ -9,7 +10,6 @@ const categoryRoutes = require('./routes/categoryRoutes')
const carouselRoutes = require('./routes/carouselRoutes')
const { notFound, errorHandler } = require('./middleware/errorMiddleware')
const compression = require('compression')
require('dotenv').config()

const app = express()
const PORT = process.env.PORT || 5000
Expand All @@ -24,7 +24,7 @@ app.use('/api/categories', categoryRoutes)
app.use('/api/products', productRoutes)
app.use('/api/users', userRoutes)
app.use('/api/orders', orderRoutes)
app.use('/api/update', uploadRoutes)
app.use('/api/upload', uploadRoutes)

if (process.env.NODE_ENV === 'production') {
app.use(express.static(path.join(__dirname, '../frontend/build')))
Expand Down
4 changes: 2 additions & 2 deletions frontend/src/actions/productActions.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,8 @@ export const createProduct = (product) => async (dispatch, getState) => {
})
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
error.response && error.response.data
? error.response.data
: error.message
if (message === 'Not authorized, token failed') {
dispatch(logout())
Expand Down
6 changes: 5 additions & 1 deletion frontend/src/components/Message.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { Alert } from 'react-bootstrap'

const Message = ({ className, variant, children }) => {
return (
<Alert className={className} variant={variant}>
<Alert
className={className}
style={{ whiteSpace: 'pre-wrap' }}
variant={variant}
>
{children}
</Alert>
)
Expand Down
103 changes: 59 additions & 44 deletions frontend/src/screens/CreateProductScreen.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import React, { useState, useEffect } from 'react'
import axios from 'axios'
import { Link } from 'react-router-dom'
import { Form, Button } from 'react-bootstrap'
import { Row, Col, Card, Form, Button } from 'react-bootstrap'
import { useDispatch, useSelector } from 'react-redux'
import Message from '../components/Message'
import Loader from '../components/Loader'
import FormContainer from '../components/FormContainer'
// import { PRODUCT_UPDATE_RESET } from '../constants/productConstants'
import { PRODUCT_CREATE_FAIL } from '../constants/productConstants'
import { createProduct } from '../actions/productActions'

const CreateProductScreen = ({ history }) => {
Expand All @@ -17,7 +18,7 @@ const CreateProductScreen = ({ history }) => {
const [category, setCategory] = useState('')
const [countInStock, setCountInStock] = useState(0)
const [description, setDescription] = useState('')
// const [uploading, setUploading] = useState(false)
const [uploading, setUploading] = useState(false)

const dispatch = useDispatch()

Expand All @@ -44,28 +45,36 @@ const CreateProductScreen = ({ history }) => {
}
}, [success, history])

// const uploadFileHandler = async (e) => {
// const file = e.target.files[0]
// const formData = new FormData()
// formData.append('image', file)
// setUploading(true)

// try {
// const config = {
// headers: {
// 'Content-Type': 'multipart/form-data',
// },
// }

// const { data } = await axios.post('/api/upload', formData, config)

// setImageSrc(data)
// setUploading(false)
// } catch (error) {
// console.error(error)
// setUploading(false)
// }
// }
const uploadFileHandler = async (e) => {
const file = e.target.files[0]
const formData = new FormData()
formData.append('image', file)
setUploading(true)

try {
const config = {
headers: {
'Content-Type': 'multipart/form-data',
},
}

const { data } = await axios.post('/api/upload', formData, config)

setImageSrc(data)
setUploading(false)
} catch (error) {
const message =
error.response && error.response.data.message
? error.response.data.message
: error.message
dispatch({
type: PRODUCT_CREATE_FAIL,
payload: message,
})
setImageSrc('')
setUploading(false)
}
}

const submitHandler = (e) => {
e.preventDefault()
Expand Down Expand Up @@ -121,27 +130,33 @@ const CreateProductScreen = ({ history }) => {
></Form.Control>
</Form.Group>

<Form.Group controlId="image">
<Form.Label>Image Src</Form.Label>
<Form.Control
type="text"
placeholder="Enter Image URL"
value={imageSrc}
onChange={(e) => setImageSrc(e.target.value)}
></Form.Control>
<Form.Group controlId="image-file">
<Row>
<Col>
<Form.Label>Product Image</Form.Label>
<Form.File
id="image-file"
size="sm"
custom
variant="secondary"
onChange={uploadFileHandler}
></Form.File>
</Col>
<Col>
{uploading && <Loader />}
{!uploading && imageSrc && (
<img
src={imageSrc}
className={'m-2'}
width={100}
height={100}
alt="product"
/>
)}
</Col>
</Row>
</Form.Group>

{/* <Form.Group className='mt-2' controlId='image-file'>
<Form.File
id='image-file'
size='sm'
custom
variant='secondary'
onChange={uploadFileHandler}
></Form.File>
{uploading && <Loader />}
</Form.Group> */}

<Form.Group className="my-3" controlId="brand">
<Form.Label>Image Alt</Form.Label>
<Form.Control
Expand Down
Loading

0 comments on commit 819541d

Please sign in to comment.