Skip to content

Commit

Permalink
Merge pull request #11 from br-ndt/upload-images
Browse files Browse the repository at this point in the history
add upload-image feature to addNewAttraction form.
  • Loading branch information
ohad-porat committed May 12, 2022
2 parents 98e0a0a + 031e765 commit 1b22201
Show file tree
Hide file tree
Showing 13 changed files with 338 additions and 17 deletions.
1 change: 1 addition & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
"prop-types": "~15.7",
"react": "~16.13",
"react-dom": "~16.13",
"react-dropzone": "^11.3.1",
"react-hot-loader": "^4.12.21",
"react-router-dom": "5.2",
"redbox-react": "~1.6",
Expand Down
4 changes: 4 additions & 0 deletions client/src/components/AttractionShowPage.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const AttractionShowPage = (props) => {
const [attraction, setAttraction] = useState({
name: "",
description: "",
image: "",
reviews: [],
});
const [errors, setErrors] = useState({});
Expand Down Expand Up @@ -129,6 +130,8 @@ const AttractionShowPage = (props) => {

const attractionDescription = attraction.description ? <h2>{attraction.description}</h2> : null;

const attractionImage = attraction.image ? <img src={attraction.image} /> : null;

const reviewSection = reviewTiles.length ? (
<>
<h4>{attraction.name} Reviews:</h4>
Expand All @@ -146,6 +149,7 @@ const AttractionShowPage = (props) => {
<div className="callout">
{attractionName}
{attractionDescription}
{attractionImage}
{reviewForm}
{errorList}
{reviewSection}
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/AttractionsList.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const AttractionsList = (props) => {
}, []);

const attractionTileComponents = attractions.map((attractionObject) => {
return <AttractionTile key={attractionObject.id} {...attractionObject} />;
return <AttractionTile key={`attractionTile-${attractionObject.id}`} {...attractionObject} />;
});

const attractionForm = props.user ? (
Expand Down
9 changes: 7 additions & 2 deletions client/src/components/DropDownSelect.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@ const DropDownSelect = (props) => {
});

return (
<select value={props.value} name={props.listName} id={`dropdown-${props.listName}`} onChange={props.onChange}>
<option value={0}></option>
<select
value={props.value}
name={props.listName}
id={`dropdown-${props.listName}`}
onChange={props.onChange}
>
<option value={0}>(Enter Location)</option>
{options}
</select>
);
Expand Down
57 changes: 54 additions & 3 deletions client/src/components/NewAttractionForm.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,20 @@ import React, { useState, useEffect } from "react";
import translateServerErrors from "./../services/translateServerErrors.js";
import DropDownSelect from "./DropDownSelect.js";
import ErrorList from "./layout/ErrorList";
import Dropzone from "react-dropzone";

const NewAttractionForm = (props) => {
const [locations, setLocations] = useState([]);
const [newAttraction, setNewAttraction] = useState({
name: "",
description: "",
image: {},
locationId: 0,
});
const [errors, setErrors] = useState({});
const [uploadedImage, setUploadedImage] = useState({
preview: "",
});

useEffect(() => {
fetchLocations();
Expand Down Expand Up @@ -42,10 +47,15 @@ const NewAttractionForm = (props) => {
else setErrors({});

try {
const body = new FormData();
body.append("name", newAttraction.name);
body.append("description", newAttraction.description);
body.append("image", newAttraction.image);
body.append("locationId", newAttraction.locationId);
const response = await fetch(`/api/v1/locations/${newAttraction.locationId}/attractions`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newAttraction),
headers: { Accept: "image/jpeg" },
body: body,
});
if (!response.ok) {
if (response.status === 422) {
Expand Down Expand Up @@ -74,12 +84,27 @@ const NewAttractionForm = (props) => {
postAttraction();
};

const handleImageUpload = (acceptedImage) => {
setNewAttraction({
...newAttraction,
image: acceptedImage[0],
});

setUploadedImage({
preview: URL.createObjectURL(acceptedImage[0]),
});
};

const clearForm = () => {
setNewAttraction({
name: "",
description: "",
image: {},
locationId: 0,
});
setUploadedImage({
preview: "",
});
};

return (
Expand All @@ -100,7 +125,33 @@ const NewAttractionForm = (props) => {
onChange={handleInputChange}
value={newAttraction.description}
/>
<DropDownSelect listItems={locations} listName="locationId" onChange={handleInputChange} value={newAttraction.locationId}/>
<Dropzone onDrop={handleImageUpload}>
{({ getRootProps, getInputProps }) => (
<section>
<div {...getRootProps()}>
<input {...getInputProps()} />
<div className="button-group">
<input
className="button"
type="add"
onChange={handleInputChange}
value="Add Image"
/>
<div>
<ul>(Click to add, or drag and drop)</ul>
</div>
</div>
</div>
</section>
)}
</Dropzone>
<img src={uploadedImage.preview} />
<DropDownSelect
listItems={locations}
listName="locationId"
onChange={handleInputChange}
value={newAttraction.locationId}
/>
<input className="button" type="submit" />
</form>
</div>
Expand Down
3 changes: 3 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
@@ -1 +1,4 @@
SESSION_SECRET=""
AWS_ACCESS_KEY=""
AWS_SECRET_KEY=""
S3_BUCKET_DEVELOPMENT=""
4 changes: 4 additions & 0 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,17 @@
"webpack-hot-middleware": "^2.25.1"
},
"dependencies": {
"aws-sdk": "^2.842.0",
"bcrypt": "^5.0.1",
"body-parser": "^1.20.0",
"cookie-session": "^2.0.0",
"dropzone": "^6.0.0-beta.2",
"express": "^4.18.1",
"express-handlebars": "^5.2.0",
"knex": "^2.0.0",
"morgan": "^1.10.0",
"multer": "^1.4.2",
"multer-s3": "^2.9.0",
"objection": "^3.0.1",
"objection-unique": "^1.2.2",
"passport": "^0.5.2",
Expand Down
20 changes: 17 additions & 3 deletions server/src/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,23 @@ import "./boot.js";
import getNodeEnv from "./config/getNodeEnv.js";
import getDatabaseUrl from "./config/getDatabaseUrl.cjs";

export default {
const development = {
awsAccess: { key: process.env.AWS_ACCESS_KEY },
awsSecret: { key: process.env.AWS_SECRET_KEY },
s3Bucket: { name: process.env.S3_BUCKET_DEVELOPMENT },
databaseUrl: getDatabaseUrl(getNodeEnv()),
nodeEnv: getNodeEnv(),
session: { secret: process.env.SESSION_SECRET },
databaseUrl: getDatabaseUrl(getNodeEnv()),
web: { host: process.env.HOST || "0.0.0.0", port: process.env.PORT || 3000 }
};
}

const test = { ...development }

const production = {
...development,
s3Bucket: { name: process.env.S3_BUCKET_PRODUCTION }
}

const config = { development, test, production }

export default config[getNodeEnv()]
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ exports.up = async (knex) => {
table.bigIncrements("id");
table.string("name").notNullable();
table.text("description");
table.string("image")
table.timestamp("createdAt").notNullable().defaultTo(knex.fn.now());
table.timestamp("updatedAt").notNullable().defaultTo(knex.fn.now());
});
Expand Down
1 change: 1 addition & 0 deletions server/src/models/Attraction.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class Attraction extends Model {
properties: {
name: { type: "string", minLength: 1 },
description: { type: "string" },
image: { type: "string" },
locationId: { type: ["string", "integer"] },
},
};
Expand Down
7 changes: 4 additions & 3 deletions server/src/routes/api/v1/locationAttractionsRouter.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,18 @@ import express from "express";
import cleanUserInput from "../../../services/cleanUserInput.js";
import { ValidationError } from "objection";
import { Attraction } from "../../../models/index.js";
import uploadImage from "../../../services/uploadImage.js";

const locationAttractionsRouter = express.Router({ mergeParams: true });

locationAttractionsRouter.post("/", async (req, res) => {
locationAttractionsRouter.post("/", uploadImage.single("image"), async (req, res) => {
const { name, description } = cleanUserInput(req.body);
const { id } = req.params;
try {
const newAttraction = await Attraction.query().insertAndFetch({
name,
description,
locationId: id,
image: req.file.location,
locationId: req.params.id,
});
return res.status(201).json({ attraction: newAttraction });
} catch (error) {
Expand Down
26 changes: 26 additions & 0 deletions server/src/services/uploadImage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import AWS from "aws-sdk"
import multer from "multer"
import multerS3 from "multer-s3"

import config from "../config.js"

AWS.config.update({
accessKeyId: config.awsAccess.key,
secretAccessKey: config.awsSecret.key,
region: "us-east-1"
})

const s3 = new AWS.S3()

const uploadImage = multer({
storage: multerS3({
s3: s3,
bucket: "trip-production",
acl: "public-read",
key: function (req, file, cb) {
cb(null, Date.now().toString())
}
})
})

export default uploadImage

0 comments on commit 1b22201

Please sign in to comment.