Reputation: 11
I am working on creating a admin dashboard where you can do CRUD operations on the products. When I am uploading the image, it gets uploaded to the AWS bucket, but it is not showing on the front end, It shows forbidden access, but i have given all permissions to the user. On second upload of image, the first one is visible, and this is my main problem. On saving the product details, all images are adding to the Mongo database.
Here is the code
ProductForm.js
"use client"
// import axios from 'axios';
import { useRouter } from 'next/navigation';
import React, { useEffect, useState } from 'react'
const ProductForm = ({
_id,
title: existingTitle,
description: existingDescription,
price: existingPrice,
images: existingImages
}) => {
const router = useRouter();
// console.log(existingTitle, existingDescription, existingPrice, _id);
const [title, setTitle] = useState(existingTitle || "");
const [description, setDescription] = useState(existingDescription || "");
const [price, setPrice] = useState(existingPrice || "");
const [images, setImages] = useState(existingImages || []);
// To load the existing product details on opening the page / update state for form fields when props change
useEffect(() => {
setTitle(existingTitle || "");
setDescription(existingDescription || "");
setPrice(existingPrice || "");
setImages(existingImages || []);
}, [existingTitle, existingDescription, existingPrice, existingImages])
// Form submission
const saveProduct = async (e) => {
e.preventDefault();
const productData = { title, description, price, _id, images };
console.log("productData", productData);
// Watch the video to send data using AXIOS
// UPDATE the product if ID exits
if (_id) {
await fetch("/api/products", {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(productData),
})
}
// POST (CREATE) the data if ID does not exist
else {
await fetch("/api/products", {
method: 'POST',
headers: {
'Content-Type': 'multipart',
},
body: JSON.stringify(productData),
})
}
// setgotoProducts(true); // might not need
router.push('/products'); // redirect to the products page
}
const [start, setstart] = useState(true)
useEffect(() => {
console.log("Useffect: ", images);
}, [start])
useEffect(() => {
console.log('Useffect Updated images:', images);
}, [images]);
const uploadImages = async (e) => {
const files = e.target?.files; // e.target?.files
// console.log(files);
if (!files || files.length == 0) {
console.error('No files selected');
return;
}
const data = new FormData();
// files are stored in a FileList (array like) object, convert it into an Array to use forEach
for (const file of files) {
data.append('files', file)
// console.log(file);
}
// or
// Array.from(files).forEach(file => data.append('files', file)) // Add each file to FormData
// Print Formdata object
for (let pair of data.entries()) { // let [key,value] of data.entries()
// console.log(pair);
}
const res = await fetch('/api/upload', {
method: 'POST', // headers are automatically added using FormData
body: data,
})
.then(res2 => res2.json())
.then(linksData => {
setImages(prevImages => [...prevImages, ...linksData.links]);
// console.log('Uploaded images:', ...linksData.links);
})
console.log('After upload:', images); // not getting updated
// const res = await fetch('/api/upload', {
// method: 'POST',
// body: data,
// });
// const linksData = await res.json();
// const updatedImages = [...images, ...linksData.links]; // Create a new array
// setImages(updatedImages); // Update state
// console.log('Updated images:', updatedImages); // Use the local variable to debug
}
return (
<form onSubmit={e => { saveProduct(e) }} className='max-w-[600px] ml-10'>
<div className='flex flex-col gap-1'>
<label>Name</label>
<input
type="text"
value={title}
onChange={e => setTitle(e.target.value)} />
</div>
<div className='flex flex-col gap-1'>
<label>Description</label>
<textarea name="description" id="" value={description} onChange={e => setDescription(e.target.value)} rows={5} />
</div>
<div className='flex flex-col gap-1'>
<label>Price (in CAD)</label>
<input name='price' type="text" value={price} onChange={e => setPrice(e.target.value)} />
</div>
<div className='flex flex-col gap-1'>
<label>Photos</label>
<div className="flex flex-wrap gap-3">
{!!images.length && images.map((link) => (
<div key={link} className="w-28 h-28">
<img className="rounded-lg w-full h-full object-fill" src={`${link}?t=${Date.now()}`} alt="productImage" />
</div>
))}
{/* */}
<label className='cursor-pointer w-28 h-24 flex gap-1 items-center rounded-lg bg-gray-200 text-gray-500'>
<img src="/upload.svg" alt="" />Upload
<input type="file" onChange={e => uploadImages(e)} className='hidden' name="" id="" multiple />
</label>
</div>
</div>
<button type='submit' className='btn-primary mt-5'>Save</button>
</form>
)
}
export default ProductForm
api/upload/route.js
import { NextResponse } from 'next/server';
// import { writeFile } from 'fs/promises'; // to save to localStorage
import { PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
import fs from 'fs';
const bucketName = "namjot-next-ecommerce";
export async function POST(req) {
// Get the files from the request
const data = await req.formData()
const files = data.getAll('files')
// console.log('data', data);
// console.log(files);
if (!files) return NextResponse.json({ success: false });
// const bytes = await files.arrayBuffer();
// const buffer = Buffer.from(bytes)
// const filepath = `${files.name}` //public/uploads/
// await writeFile(filepath, buffer)
// console.log(filepath);
// AWS S3 Bucket
const client = new S3Client({
region: 'us-east-1',
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY,
},
});
const links = [];
// Upload each file to AWS
for (const file of files) {
// Unique file name to avoid duplicate images in Bucket
const ext = file.name.split('.').pop() // pop() removes the last element from an array and returns that element
const newFilename = Date.now() + "." + ext;
// console.log({ ext, file });
// console.log(newFilename);
// Convert file to Buffer
const bytes = await file.arrayBuffer();
const buffer = Buffer.from(bytes)
// Upload to S3 bucket
client.send(new PutObjectCommand({
Bucket: bucketName,
Key: newFilename,
Body: buffer,
ACL: 'public-read',
ContentType: file.type,
}))
const link = `https://${bucketName}.s3.us-east-1.amazonaws.com/${newFilename}`;
links.push(link);
}
return NextResponse.json({ links });
}
// disable the default bodyparser that parses the FormData
export const config = {
api: {
bodyParser: false,
},
};
Upvotes: 1
Views: 74
Reputation: 31
I think you need to apply the Read & Write policies on your AWS s3 bucket. https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_s3_rw-bucket.html
Can you please try with page reload to see if your latest image is still showing or not? Sometime browser need time to load it.
But it's totally related to Read of objects policies. May be you have missed something while applying the policy rules on AWS S3.
Upvotes: 0