diff --git a/README.md b/README.md index 80249ae..46cf987 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,30 @@ -To start the server, run `nodemon server.js` +**Marketplace - Web App – team project** +--- +- **Front-end :** JavaScript, React, Redux, CSS +- **Back-end :** Node.js, Express, MongoDB +- **Other tools:** Git, Github, Postman -Modify the `server.js` file to add endpoints +**Alibay is an online marketplace that makes available the following functionalities:** -The frontend code goes in the `/src` directory \ No newline at end of file +- signup / login +- selling new item form +- browsing items / filtering by cost and stock availability +- purchasing items / processing payments through Stripe + + *Project video available:* https://bit.ly/2kCBtFN + + --- + - signup / login + + + - selling new item form + + + - browsing items / filtering by cost and stock availability + + + + - purchasing items / processing payments through Stripe + + + diff --git a/backend_frontend_contract.md b/backend_frontend_contract.md new file mode 100644 index 0000000..9565060 --- /dev/null +++ b/backend_frontend_contract.md @@ -0,0 +1,252 @@ +#Users + +- User object + +``` +{ + id: integer, + username: string, + password: string +} +``` + + + +## **GET /users/:id/orders** + +Returns all Orders associated with the specified user. + +- **Headers** + cookie: sessionId + **Content:** + +``` +{ + orders: [ + {}, + {}, + {} + ] +} +``` + +## **POST /signup** + +Creates a new User and returns the new object. + +- **Data Params** + +``` + { + id: integer, + username: string, + password: string + } +``` + +## **POST /login** + + + + + +#items + +- item object + +``` +{ + id: integer + name: string + cost: float(2) + available_quantity: integer + description: string + filePath: string +} +``` + +## **GET /items** + +Returns all items in the system. +**Content:** + +``` +{ + items: [ + {}, + {}, + {} + ] +} +``` + +## **GET /items/:id** + +Returns the specified item. + +- **Headers** + cookie: sessionId + **Content:** `{ }` + +## **GET /items/:id/orders** + +Returns all Orders associated with the specified item. + +- **Headers** + cookie: sessionId + **Content:** + +``` +{ + orders: [ + {}, + {}, + {} + ] +} +``` + +## **POST /items** + +Creates a new item and returns the new object. + +- **Data Params** + +``` + { + id: integer + name: string + cost: float(2) + available_quantity: integer + description: string + filePath: string + } +``` + +**Content:** `{ }` + +#Orders + +- Order object + +``` +{ + id: integer + user_id: + total: float(2) + items: [ + { + item: , + quantity: integer + }, + { + item: , + quantity: integer + }, + { + item: , + quantity: integer + }, + ] + created_at: datetime(iso 8601) + updated_at: datetime(iso 8601) +} +``` + +## **GET /orders** + +Returns all orders in the system. + +**Content:** + +``` +{ + orders: [ + {}, + {}, + {} + ] +} +``` + +## **GET /orders/:id** + +Returns the specified order. + +- **URL Params** + _Required:_ `id=[integer]` +- **Data Params** + None +- **Headers** + cookie: sessionId + **Content:** `{ }` + +## **GET /orders/:id/items** + +Returns all items associated with the specified order. + +- **Headers** + cookie: sessionId + **Content:** + +``` +{ + items: [ + {}, + {}, + {} + ] +} +``` + +## **GET /orders/:id/user** + +Returns all Users associated with the specified order. + +- **Headers** + cookie: sessionId +- **Success Response:** `{ }` + +## **POST /orders** + +Creates a new Order and returns the new object. + +- **Data Params** + +``` + { + user_id: + item: , + quantity: integer + } +``` + +- **Headers** + Content-Type: application/json +- **Success Response:** + **Content:** `{ }` diff --git a/images/1-skirt.jpg b/images/1-skirt.jpg new file mode 100644 index 0000000..67b296c Binary files /dev/null and b/images/1-skirt.jpg differ diff --git a/images/1179117_in_pp.jpg b/images/1179117_in_pp.jpg new file mode 100644 index 0000000..c7c1891 Binary files /dev/null and b/images/1179117_in_pp.jpg differ diff --git a/images/20-karat-gold-earrings.jpg b/images/20-karat-gold-earrings.jpg new file mode 100644 index 0000000..d3476f9 Binary files /dev/null and b/images/20-karat-gold-earrings.jpg differ diff --git a/images/Almeida-dress.jpg b/images/Almeida-dress.jpg new file mode 100644 index 0000000..60d53de Binary files /dev/null and b/images/Almeida-dress.jpg differ diff --git a/images/aaaaa.jpg b/images/aaaaa.jpg new file mode 100644 index 0000000..67b296c Binary files /dev/null and b/images/aaaaa.jpg differ diff --git a/images/alibay-browsing-items.png b/images/alibay-browsing-items.png new file mode 100644 index 0000000..eae6d7e Binary files /dev/null and b/images/alibay-browsing-items.png differ diff --git a/images/alibay-cart1.png b/images/alibay-cart1.png new file mode 100644 index 0000000..eec4aff Binary files /dev/null and b/images/alibay-cart1.png differ diff --git a/images/alibay-cart2.png b/images/alibay-cart2.png new file mode 100644 index 0000000..fab76cf Binary files /dev/null and b/images/alibay-cart2.png differ diff --git a/images/alibay-item-details.png b/images/alibay-item-details.png new file mode 100644 index 0000000..230c336 Binary files /dev/null and b/images/alibay-item-details.png differ diff --git a/images/alibay-payment.png b/images/alibay-payment.png new file mode 100644 index 0000000..c3feb2f Binary files /dev/null and b/images/alibay-payment.png differ diff --git a/images/alibay-selling-new-item.png b/images/alibay-selling-new-item.png new file mode 100644 index 0000000..1997f23 Binary files /dev/null and b/images/alibay-selling-new-item.png differ diff --git a/images/alibay-shipping-details.png b/images/alibay-shipping-details.png new file mode 100644 index 0000000..7985d7b Binary files /dev/null and b/images/alibay-shipping-details.png differ diff --git a/images/alibay-signup-login.png b/images/alibay-signup-login.png new file mode 100644 index 0000000..9b0234d Binary files /dev/null and b/images/alibay-signup-login.png differ diff --git a/images/ast_days_of_summer.mp4 b/images/ast_days_of_summer.mp4 new file mode 100644 index 0000000..d09a4ca Binary files /dev/null and b/images/ast_days_of_summer.mp4 differ diff --git a/images/beige dress.jpg b/images/beige dress.jpg new file mode 100644 index 0000000..b568699 Binary files /dev/null and b/images/beige dress.jpg differ diff --git a/images/beigedress2.jpg b/images/beigedress2.jpg new file mode 100644 index 0000000..840599a Binary files /dev/null and b/images/beigedress2.jpg differ diff --git a/images/blackshoes.jpg b/images/blackshoes.jpg new file mode 100644 index 0000000..c2dce11 Binary files /dev/null and b/images/blackshoes.jpg differ diff --git a/images/blazer.jpg b/images/blazer.jpg new file mode 100644 index 0000000..4967a22 Binary files /dev/null and b/images/blazer.jpg differ diff --git a/images/bluedress.png b/images/bluedress.png new file mode 100644 index 0000000..42cfaed Binary files /dev/null and b/images/bluedress.png differ diff --git a/images/bluedress.webp b/images/bluedress.webp new file mode 100644 index 0000000..1e402ce Binary files /dev/null and b/images/bluedress.webp differ diff --git a/images/brownblazer.jpg b/images/brownblazer.jpg new file mode 100644 index 0000000..39d71ee Binary files /dev/null and b/images/brownblazer.jpg differ diff --git a/images/butterflyshoes.jpg b/images/butterflyshoes.jpg new file mode 100644 index 0000000..b10d16e Binary files /dev/null and b/images/butterflyshoes.jpg differ diff --git a/images/dodo bar dress.jpg b/images/dodo bar dress.jpg new file mode 100644 index 0000000..02e0884 Binary files /dev/null and b/images/dodo bar dress.jpg differ diff --git a/images/dress.jpg b/images/dress.jpg new file mode 100644 index 0000000..938049c Binary files /dev/null and b/images/dress.jpg differ diff --git a/images/ethnicshoes.jpg b/images/ethnicshoes.jpg new file mode 100644 index 0000000..a748663 Binary files /dev/null and b/images/ethnicshoes.jpg differ diff --git a/images/farmer.mov b/images/farmer.mov new file mode 100644 index 0000000..13ef905 Binary files /dev/null and b/images/farmer.mov differ diff --git a/images/glitterdress.jpg b/images/glitterdress.jpg new file mode 100644 index 0000000..ce47a3f Binary files /dev/null and b/images/glitterdress.jpg differ diff --git a/images/glitterdress2.jpg b/images/glitterdress2.jpg new file mode 100644 index 0000000..cbdcde2 Binary files /dev/null and b/images/glitterdress2.jpg differ diff --git a/images/heart.png b/images/heart.png new file mode 100644 index 0000000..fb33bf1 Binary files /dev/null and b/images/heart.png differ diff --git a/images/lantern dress.jpg b/images/lantern dress.jpg new file mode 100644 index 0000000..7cf4038 Binary files /dev/null and b/images/lantern dress.jpg differ diff --git a/images/leathersandals.jpg b/images/leathersandals.jpg new file mode 100644 index 0000000..5391e8e Binary files /dev/null and b/images/leathersandals.jpg differ diff --git a/images/metallicMididress.jpg b/images/metallicMididress.jpg new file mode 100644 index 0000000..b07f513 Binary files /dev/null and b/images/metallicMididress.jpg differ diff --git a/images/midi dress.jpg b/images/midi dress.jpg new file mode 100644 index 0000000..77830d8 Binary files /dev/null and b/images/midi dress.jpg differ diff --git a/images/militaryjumpsuit.png b/images/militaryjumpsuit.png new file mode 100644 index 0000000..3ba9a8f Binary files /dev/null and b/images/militaryjumpsuit.png differ diff --git a/images/militarypant1.jpg b/images/militarypant1.jpg new file mode 100644 index 0000000..fbc51d0 Binary files /dev/null and b/images/militarypant1.jpg differ diff --git a/images/militarypant2.jpg b/images/militarypant2.jpg new file mode 100644 index 0000000..245f8eb Binary files /dev/null and b/images/militarypant2.jpg differ diff --git a/images/niceshoe.jpg b/images/niceshoe.jpg new file mode 100644 index 0000000..59a64d9 Binary files /dev/null and b/images/niceshoe.jpg differ diff --git a/images/one-shoulder-dress.jpg b/images/one-shoulder-dress.jpg new file mode 100644 index 0000000..cad35dc Binary files /dev/null and b/images/one-shoulder-dress.jpg differ diff --git a/images/one-shoulder.jpg b/images/one-shoulder.jpg new file mode 100644 index 0000000..7491146 Binary files /dev/null and b/images/one-shoulder.jpg differ diff --git a/images/patent-leather-shoes.jpg b/images/patent-leather-shoes.jpg new file mode 100644 index 0000000..c77a15b Binary files /dev/null and b/images/patent-leather-shoes.jpg differ diff --git a/images/patterndress.png b/images/patterndress.png new file mode 100644 index 0000000..711b181 Binary files /dev/null and b/images/patterndress.png differ diff --git a/images/printed-denim-acket.jpg b/images/printed-denim-acket.jpg new file mode 100644 index 0000000..787ba4e Binary files /dev/null and b/images/printed-denim-acket.jpg differ diff --git a/images/printed-jacket.jpg b/images/printed-jacket.jpg new file mode 100644 index 0000000..25bff77 Binary files /dev/null and b/images/printed-jacket.jpg differ diff --git a/images/printed-shorts.jpg b/images/printed-shorts.jpg new file mode 100644 index 0000000..c853a1e Binary files /dev/null and b/images/printed-shorts.jpg differ diff --git a/images/self-portrait.jpg b/images/self-portrait.jpg new file mode 100644 index 0000000..843fa2a Binary files /dev/null and b/images/self-portrait.jpg differ diff --git a/images/self-portrait2.jpg b/images/self-portrait2.jpg new file mode 100644 index 0000000..b75d67b Binary files /dev/null and b/images/self-portrait2.jpg differ diff --git a/images/sequineddress.jpg b/images/sequineddress.jpg new file mode 100644 index 0000000..8a35bd6 Binary files /dev/null and b/images/sequineddress.jpg differ diff --git a/images/store.jpg b/images/store.jpg new file mode 100644 index 0000000..a0b0037 Binary files /dev/null and b/images/store.jpg differ diff --git a/images/stores.jpg b/images/stores.jpg new file mode 100644 index 0000000..1fa6f24 Binary files /dev/null and b/images/stores.jpg differ diff --git a/images/suede-mini-skirt.jpg b/images/suede-mini-skirt.jpg new file mode 100644 index 0000000..d5d8ffa Binary files /dev/null and b/images/suede-mini-skirt.jpg differ diff --git a/images/yellowflower .png b/images/yellowflower .png new file mode 100644 index 0000000..6aac830 Binary files /dev/null and b/images/yellowflower .png differ diff --git a/package.json b/package.json index 70de175..d395832 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,8 @@ "css-loader": "^2.1.0", "html-loader": "^0.5.5", "html-webpack-plugin": "^3.2.0", - "react": "16.6.0", - "react-dom": "16.6.0", + "react": "^16.6.0", + "react-dom": "^16.6.0", "style-loader": "^0.23.1", "webpack": "^4.29.0", "webpack-cli": "^3.2.1", @@ -26,11 +26,27 @@ }, "dependencies": { "@babel/plugin-proposal-class-properties": "^7.3.4", + "auth0": "^2.19.0", "chokidar": "^3.0.0", - "redux": "^4.0.4", - "react-redux": "6.0.1", + "cookie-parser": "^1.4.4", "express": "^4.16.4", + "file-system": "^2.2.2", + "jquery": "^3.4.1", + "mongodb": "^3.3.0-beta2", + "multer": "^1.4.2", + "prop-types": "^15.7.2", + "react-icons": "^3.7.0", + "react-paginate": "^6.3.0", + "react-pagination": "^1.0.0", + "react-redux": "6.0.1", + "react-router-dom": "^5.0.1", + "react-router-redux": "^4.0.8", + "react-stripe-checkout": "^2.6.3", + "redux": "^4.0.4", + "sha1": "^1.1.1", "shelljs": "^0.8.3", + "stripe": "^7.7.0", + "video-react": "^0.14.1", "webpack-inject-plugin": "^1.5.0", "ws": "^7.0.0" } diff --git a/public/index.html b/public/index.html index 715b087..e3abecb 100644 --- a/public/index.html +++ b/public/index.html @@ -1,14 +1,11 @@ - - - + + Fullstack dev! - - - -
-
- + + +
+ diff --git a/server.js b/server.js index 0cd13bf..69da125 100644 --- a/server.js +++ b/server.js @@ -1,19 +1,454 @@ -let express = require('express') -let app = express() -let reloadMagic = require('./reload-magic.js') +let express = require("express"); +let app = express(); +let reloadMagic = require("./reload-magic.js"); -reloadMagic(app) +// database MongoDB +let MongoClient = require("mongodb").MongoClient; +let ObjectID = require("mongodb").ObjectID; +let dbo = undefined; +let url = + "mongodb+srv://decode:decode@cluster0-8wcxc.mongodb.net/test?retryWrites=true&w=majority"; +MongoClient.connect(url, { useNewUrlParser: true }, (err, db) => { + dbo = db.db("media-board"); +}); -app.use('/', express.static('build')); // Needed for the HTML and JS files -app.use('/', express.static('public')); // Needed for local assets +// hash passwords using sha1 +let sha1 = require("sha1"); + +// stripe +// I may add stuff in here +app.post("/save-stripe-token", (req, res) => { + res.send(JSON.stringify({ success: true })); +}); + +// multer +let multer = require("multer"); +let upload = multer({ dest: __dirname + "/uploads/" }); +reloadMagic(app); + +//cookie +let cookieParser = require("cookie-parser"); +app.use(cookieParser()); + +// variables +// sessions = {sessionId: emailId} +let sessions = {}; + +app.use("/", express.static("build")); // Needed for the HTML and JS files + +app.use("/uploads", express.static("uploads")); // Needed for local assets +app.use("/images", express.static("images")); // Your endpoints go after this line +//app.post create/update a resource and send it back after +// loveit +app.post("/add-a-loveIt", upload.none(), (req, res) => { + console.log("req.body", req.body); -// Your endpoints go before this line + let numberOfLoveIt = parseInt(req.body.loveItNumber); + + // dbo.collection("items").findOne({}); + dbo.collection("items").updateOne( + { _id: ObjectID(req.body.itemId) }, + { + $inc: { + loveIt: numberOfLoveIt + } + }, + (err, result) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + res.send(JSON.stringify({ success: true })); + return; + } + ); +}); + +// search item +app.post("/search-item", (req, res) => { + console.log("item = req.body.name", req.body.name); + dbo + .collection("items") + .findOne({ name: req.body.name }) + .toArray((err, item) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + console.log("item", item); + res.send(JSON.stringify(item)); + }); +}); + +// signup +app.post("/signup", upload.none(), (req, res) => { + console.log("**** I'm in the signup endpoint"); + console.log("this is the body", req.body); + + let email = req.body.email; + let username = req.body.username; + let password = req.body.password; + + dbo.collection("users").findOne({ email: email }, (err, expectedemail) => { + console.log("expectedemail", expectedemail); + if (err) { + res.send(JSON.stringify({ success: false })); + return; + } + if (expectedemail === undefined || expectedemail === null) { + dbo.collection("users").insertOne({ + email: email, + username: username, + password: sha1(password) + }); + res.send(JSON.stringify({ success: true })); + return; + } + if (expectedemail !== undefined) { + res.send(JSON.stringify({ success: false })); + return; + } + res.send(JSON.stringify({ success: false })); + }); +}); + +// generate cookie +let generateId = () => { + return "" + Math.floor(Math.random() * 100000000); +}; + +// login +app.post("/login", upload.none(), (req, res) => { + console.log("**** I'm in the login endpoint"); + console.log("this is the parsed body", req.body); + + let email = req.body.email; + let enteredPassword = req.body.password; + + dbo.collection("users").findOne({ email: email }, (err, user) => { + console.log("email", email); + console.log("enteredPassword", req.body.password); + console.log("users"); + if (err) { + console.log("error", err); + res.send(JSON.stringify({ success: false })); + return; + } + if (user === null || user === undefined) { + console.log("user === null"); + res.send(JSON.stringify({ success: false })); + return; + } + if (user.password === sha1(enteredPassword)) { + console.log("correct password"); + let sessionId = generateId(); + console.log("generated id", sessionId); + res.cookie("sid", sessionId); + + sessions[sessionId] = user.email; + console.log("sessions", sessions); + res.send( + JSON.stringify({ + success: true + }) + ); + return; + } + res.send(JSON.stringify({ success: false })); + }); +}); -app.all('/*', (req, res, next) => { // needed for react router - res.sendFile(__dirname + '/build/index.html'); -}) +// new-item +app.post("/new-item", upload.single("itemImage"), (req, res) => { + console.log("request to /new-item body", req.body); + console.log(req.file); + let filePath; + let name = req.body.name; + let cost = parseInt(req.body.cost); + let description = req.body.description; + let review = ""; + let available_quantity = parseInt(req.body.available_quantity); + + if (req.file !== undefined) { + filePath = "/uploads/" + req.file.filename; + } + + dbo.collection("items").insertOne({ + filePath: filePath, + name: name, + cost: cost, + description: description, + available_quantity: available_quantity, + review: [] + }); + res.send( + JSON.stringify({ + success: true + }) + ); +}); + +// logout +app.post("/logout", upload.none(), (req, res) => { + console.log("logout"); + console.log("sessions", sessions); + let sessionId = req.cookies.sid; + console.log("sessionId", sessionId); + let currentUser = sessions[sessionId]; + // delete cart + dbo.collection("cart").deleteMany( + { + email: currentUser + }, + (err, result) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + res.send( + JSON.stringify({ + success: true + }) + ); + return; + } + ); + + // delete cookie + delete sessions[sessionId]; + console.log("sessions", sessions); +}); + +// app.get retrieves information and send it back after +// items - populate the database +app.get("/items", (req, res) => { + // console.log("request to all items"); + dbo + .collection("items") + .find({}) + .toArray((err, items) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + // console.log("items", items); + res.send(JSON.stringify(items)); + }); +}); + +// retrieve all items in cart +app.get("/cart-items", (req, res) => { + let sessionId = req.cookies.sid; + let currentUser = sessions[sessionId]; + dbo + .collection("cart") + .find({ email: currentUser }) + .toArray((err, cartItems) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + res.send(JSON.stringify(cartItems)); + }); +}); + +// cart +app.post("/update-quantity-cart", upload.none(), (req, res) => { + let sessionId = req.cookies.sid; + let user = sessions[sessionId]; + console.log("req.body.quantity", req.body.quantity); + let quantityToRestore = parseInt(req.body.quantity); + console.log("quantityToRestore", quantityToRestore); + dbo.collection("cart").updateOne( + { itemId: ObjectID(req.body.itemId), email: user }, + { + $inc: { + quantity: quantityToRestore + } + } + ); +}); + +app.post("/remove-from-cart", upload.none(), (req, res) => { + let sessionId = req.cookies.sid; + let user = sessions[sessionId]; + dbo + .collection("cart") + .deleteOne( + { itemId: ObjectID(req.body.itemId), email: user }, + (err, result) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + res.send( + JSON.stringify({ + success: true + }) + ); + return; + } + ); +}); +app.post("/add-to-cart", upload.none(), (req, res) => { + let sessionId = req.cookies.sid; + let currentUser = sessions[sessionId]; + let quantity = parseInt(req.body.quantity); + dbo + .collection("items") + .findOne({ _id: ObjectID(req.body.itemId) }, (err, item) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + // update quantity in items collection + let quantityToRestore = quantity; + console.log("quantityToRestore", quantityToRestore); + dbo.collection("items").updateOne( + { _id: ObjectID(req.body.itemId) }, + { + $inc: { + available_quantity: -quantityToRestore + } + } + ); + + dbo + .collection("cart") + .findOne( + { itemId: ObjectID(req.body.itemId), email: currentUser }, + (err, cartItem) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + console.log("item._id", item._id); + if (cartItem === null) { + dbo.collection("cart").insertOne({ + // itemId: req.body.itemId, + itemId: item._id, + email: currentUser, + name: item.name, + quantity: quantity + }); + res.send( + JSON.stringify({ + success: true + }) + ); + return; + } + if (cartItem !== null) { + dbo.collection("cart").updateOne( + { _id: ObjectID(cartItem._id) }, + { + $inc: { + quantity: quantity + } + } + ); + res.send( + JSON.stringify({ + success: true + }) + ); + return; + } + } + ); + }); +}); + +// delete cart and create history cart +app.post("/checkout", upload.none(), (req, res) => { + let sessionId = req.cookies.sid; + let currentUser = sessions[sessionId]; + + dbo + .collection("cart") + .find({ email: currentUser }) + .toArray((err, cartItems) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + console.log({ cartItems }); + + // update quantity in items collection + cartItems.map(cartItem => { + let quantityToRestore = cartItem.quantity; + console.log("cartItem.quantity", cartItem.quantity); + dbo.collection("items").updateOne( + { _id: ObjectID(cartItems.itemId) }, + { + $inc: { + quantity: -quantityToRestore + } + } + ); + }); + }); + + // delete cart + dbo.collection("cart").deleteMany({ email: currentUser }, (err, result) => { + if (err) { + console.log("error", err); + res.send("fail"); + return; + } + res.send( + JSON.stringify({ + success: true + }) + ); + return; + }); +}); + +// reviews +app.post("/reviews", upload.none(), (req, res) => { + console.log("our review: ", req.body.review); + let review = req.body.review; + let sessionId = req.cookies.sid; + let currentUser = sessions[sessionId]; + + dbo.collection("items").updateOne( + { _id: ObjectID(req.body.itemId) }, + { + $push: { + review: { + username: currentUser, + message: review + } + } + } + ); + res.send( + JSON.stringify({ + success: true + }) + ); + return; +}); + +// Your endpoints go before this line +app.all("/*", (req, res, next) => { + // needed for react router + res.sendFile(__dirname + "/build/index.html"); +}); -app.listen(4000, '0.0.0.0', () => { console.log("Server running on port 4000") }) +app.listen(4000, "0.0.0.0", () => { + console.log("Server running on port 4000"); +}); diff --git a/src/App.css b/src/App.css new file mode 100644 index 0000000..97bb96d --- /dev/null +++ b/src/App.css @@ -0,0 +1,142 @@ +.App { + height: 100vh; + display: flex; + color: white; +} + +.App__Aside { + width: 50%; + background-color: white; + background: url("/images/stores.jpg") no-repeat center center; + z-index: 105; + background-size: cover; +} + +.App__Form { + width: 50%; + text-align-last: center; + height: 100%; + background-color: #101010; + padding: 25px 40px; + overflow: auto; + z-index: 105; +} +.FormCenter { + margin-bottom: 100px; +} +.or { + color: white; +} + +.FormTitle { + padding-top: 20%; + color: black; + font-weight: 300; + margin-bottom: 50px; +} + +.FormTitle__Link { + color: white; + text-decoration: none; + display: inline-block; + font-size: 1.7em; + margin: 0 10px; + padding-bottom: 5px; +} + +.FormTitle__Link:first-child { + margin-left: 0; +} + +.FormTitle__Link--Active { + color: white; + border-bottom: 1px solid white; + margin-bottom: 40px; +} + +.FormField { + margin-bottom: 40px; +} + +.FormField__Label { + display: block; + text-transform: uppercase; + font-size: 0.9em; + color: white; +} + +.FormField__Input:-webkit-autofill { + -webkit-text-fill-color: black !important; + -webkit-box-shadow: 0 0 0px 1000px white inset; + box-shadow: 0 0 0px 1000px white inset; + width: 50%; + background-color: white; + font-size: 0.9em; + border: none; + outline: none; + font-size: 1em; + font-weight: 300; + padding-bottom: 10px; + margin-top: 10px; +} +.FormField__Input { + text-align: left; + padding-top: 10px; + width: 50%; + background-color: white; + color: black; + outline: none; + font-size: 1em; + font-weight: 300; + padding-bottom: 10px; + padding-left: 15px; + margin-top: 10px; + font-family: "roboto", sans-serif; +} + +.FormField__Input::placeholder { + color: #616e7f; +} + +.FormField__Button { + background-color: #101010; + color: white; + border: none; + outline: none; + padding-right: 25px; + padding-left: 25px; + padding-top: 15px; + padding-bottom: 15px; + font-size: 22px; + font-weight: 400; + min-inline-size: -webkit-fill-available; +} +.FormField__Button:hover { + cursor: pointer; +} +.FormField__Link { + color: #66707d; + text-decoration: none; + display: inline-block; + border-bottom: 1.5px solid #225e62; + padding-bottom: 5px; +} + +.FormField__CheckboxLabel { + color: #646f7d; + font-size: 0.9em; +} + +.FormField__Checkbox { + position: relative; + top: 1.5px; +} + +.FormField__TermsLink { + color: white; + border-bottom: 1px solid #199087; + text-decoration: none; + display: inline-block; + padding-bottom: 2px; + margin-left: 5px; +} diff --git a/src/App.jsx b/src/App.jsx index b3b06a2..ce2bd72 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,10 +1,176 @@ -import React, { Component } from 'react' +import React, { Component } from "react"; +import { + BrowserRouter as Router, + Route, + NavLink, + withRouter, + Link, + Redirect +} from "react-router-dom"; +import { browserHistory } from "react-router"; +import { + syncHistoryWithStore, + routerReducer, + routerMiddleware, + push +} from "react-router-redux"; +import { connect } from "react-redux"; -class App extends Component { - render = () => { - return "Hello world!" +// components +import Cart from "./Cart.jsx"; +import NewItem from "./NewItem.jsx"; +import Items from "./Items.jsx"; +import Signup from "./pages/Signup.jsx"; +import Login from "./pages/Login.jsx"; +import ItemDetails from "./itemDetails.jsx"; +import Checkout from "./Checkout.jsx"; + +import "./Item.css"; +import { FiShoppingCart } from "react-icons/fi"; +import { FaHome } from "react-icons/fa"; + +// testing cronjob +class UnconnectedNavigation extends Component { + constructor(props) { + super(props); + this.state = { + email: "", + color: "#101010" + }; + } + listenScrollEvent = e => { + if (window.scrollY > 400) { + this.setState({ color: "#101010" }); + } else { + this.setState({ color: "#101010" }); + } + }; + + componentDidMount() { + window.addEventListener("scroll", this.listenScrollEvent); + } + componentWillUnmount() { + window.removeEventListener("scroll", this.listenScrollEvent); + } + + logout = async () => { + console.log("clicked logout"); + let response = await (await fetch("/logout", { method: "POST" })).text(); + let body = JSON.parse(response); + if (body.success) { + this.props.history.push("/"); } + }; + render = () => { + console.log("this.state.email", this.state.email); + return ( + + ); + }; +} + +let renderItemDetails = routerData => { + return ( +
+ +
+ ); +}; + +class UnconnectedApp extends Component { + render = () => { + return ( +
+
+ + + + + {this.props.loggedIn ? ( + + ) : ( + + )} + {this.props.loggedIn ? ( + + ) : ( + + )} + {this.props.loggedIn ? ( + + ) : ( + + )} + {this.props.loggedIn ? ( + + ) : ( + + )} + {/* */} + {/* */} + {/* */} + {/* */} + + + +
+
+ ); + }; } +let mapStateToProps = state => { + return { + email: state.email, + loggedIn: state.loggedIn + }; +}; -export default App +let Navigation = withRouter(UnconnectedNavigation); +let App = connect(mapStateToProps)(UnconnectedApp); +export default App; diff --git a/src/Cart.css b/src/Cart.css new file mode 100644 index 0000000..9264156 --- /dev/null +++ b/src/Cart.css @@ -0,0 +1,205 @@ +@import url("https://fonts.googleapis.com/css?family=Roboto:300,400,500,700"); +.Cart { + position: relative; + justify-items: center; + box-sizing: inherit; + text-align: left; +} +.NavBlock { + z-index: 101; + position: absolute; + width: 100%; + background: white; + padding: 500px; + height: 80%; + margin-top: 150px; +} +.ShoppingBag { + z-index: 200; + position: absolute; + top: 70px; + margin-left: 150px; + width: fit-content; + height: fit-content; + margin: 80px auto; + background: white; + box-shadow: 1px 2px 3px 0px rgba(0, 0, 0, 0.1); + border-radius: 6px; + display: flex; + flex-direction: column; +} +.ShoppingBagText { + height: 60px; + border-bottom: 1px solid black; + padding: 0px 30px; + color: black; + font-size: 32px; + font-weight: 500; +} +.ItemFrame { + border-bottom: 1px solid; + text-align: left; + padding: 20px 10px; + height: fit-content; + display: flex; +} + +.ItemsDetailsUl { + display: flex; + flex-direction: column; + box-sizing: border-box; + list-style-type: none; + text-align: left; +} + +.Checkout { + z-index: 140; + position: fixed; + left: 803px; + top: 241px; + padding: 20px; + margin-left: 60px; +} +.CheckoutText { + font-weight: 500; + text-align: center; +} +.ItemsPictureFrame { + margin: 10px; + padding: 10px; +} +.ItemPictureCart { + border: transparent 10px; +} +.ItemsDetailsDiv { + margin-top: 20px; + margin-right: 20px; + width: 250px; +} +.ItemsDetailsDiv span:first-child { + margin-bottom: 10px; +} +.ItemsDetailsDiv span:last-child { + font-weight: 300; + margin-top: 8px; + color: black; +} +.ItemsQuantity { + padding-bottom: 10px; + margin-top: 30px; + text-align: center; + font-size: 20px; + font-weight: 200; +} +.ItemsQuantity input { + -webkit-appearance: none; + border: none; + text-align: center; + width: 32px; + font-size: 16px; + color: black; + font-weight: 300; +} +#buttonCart { + width: 30px; + height: 30px; + background-color: white; + border-radius: 6px; + border: none; + cursor: pointer; +} + +#buttonCart:focus, +input:focus { + outline: 0; +} +.RemoveItemButton { + background-color: white; + color: black; + border: 1px solid; + cursor: pointer; + margin-top: 45px; + font-size: 12px; + padding-left: 12px; + padding-right: 12px; +} +.RemoveItemButton:hover { + box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), + 0 17px 50px 0 rgba(0, 0, 0, 0.19); +} +.ItemPriceDiv { + margin-top: 20px; + margin-right: 60px; +} +.RemoveOneItemButton { + font-size: 24px; + font-weight: 300; + margin-right: 10px; +} +.AddOneItemButton { + font-size: 20px; + font-weight: 300; +} + +.ItemsName { + display: block; + font-size: 22px; + color: Black; + font-weight: 500; +} +.ItemsDescription { + font-size: 16px; + font-weight: 100; + margin-bottom: 15px; + display: block; +} +.ItemsCost { + font-size: 22px; + font-weight: bolder; +} +.QuantityButtons { + text-align: center; +} +.SubTotalDiv { + margin-top: 25px; + text-align: center; +} +.SubTotalText { + font-size: 18px; + font-weight: 300; +} +.SubTotalNumber { + font-size: 18px; + font-weight: 400; +} +.StripeButtonText { + position: absolute; + top: 450px; + background-color: black !important; + background-image: none; + color: white; + border: none; + font-family: "Roboto", sans-serif; + font-size: 28px; + padding-left: 27px; + padding-right: 27px; + cursor: pointer; +} +.StripeButtonText:hover { + box-shadow: 0 12px 16px 0 rgba(0, 0, 0, 0.24), + 0 17px 50px 0 rgba(0, 0, 0, 0.19); +} +.StripeButton { + background-color: transparent; +} +.TotalText { + text-align: center; + font-weight: 300; +} +.TotalNumber { + font-weight: 500; +} +.ItemsAction { + padding-right: 20px; + align-content: center; +} diff --git a/src/Cart.jsx b/src/Cart.jsx new file mode 100644 index 0000000..de795d3 --- /dev/null +++ b/src/Cart.jsx @@ -0,0 +1,257 @@ +import React, { Component } from "react"; +// import StripeCheckout from "react-stripe-checkout"; +import { connect } from "react-redux"; +import ItemDetails from "./itemDetails.jsx"; +import "./cart.css"; + +class UnconnectedCart extends Component { + constructor(props) { + super(props); + this.state = { + cart: this.props.cart, + items: this.props.items, + itemId: this.props.itemId, + quantity: 0, + loggedIn: this.props.loggedIn, + email: this.props.email + }; + } + + componentDidMount = () => { + let updateCartItems = async () => { + // get all cart items from the server + let response = await fetch("/cart-items"); + let responseBody = await response.text(); + // console.log("responseBody", responseBody); + let parsed = JSON.parse(responseBody); + // console.log("parsed", parsed); + this.props.dispatch({ type: "set-cart", cart: parsed }); + }; + setInterval(updateCartItems, 500); + }; + + // componentDidMount = () => { + // this.reload(); + // }; + // componentDidUpdate = (prevProps, prevState) => { + // if (prevState.cart !== this.state.cart) { + // this.reload(); + // } + // }; + + // reload = async () => { + // let response = await fetch("/cart-items"); + // let responseBody = await response.text(); + // let parsed = JSON.parse(responseBody); + // this.props.dispatch({ type: "set-cart", cart: parsed }); + // }; + + // onToken = token => { + // fetch("/save-stripe-token", { + // method: "POST", + // body: JSON.stringify(token) + // }).then(response => { + // response.json().then(data => { + // // alert(`Thank you for your purchase! ${data.email}`); + // // add username/email below + // alert(`Thank you for your purchase ${this.props.email}! `); + // }); + // }); + // }; + + removeOneFromCart = async itemId => { + this.setState( + { + ...this.state, + itemId: itemId, + quantity: (this.state.quantity -= 1) + }, + async () => { + console.log("this.state.itemId", itemId); + console.log("this.props.itemId", this.props.itemId); + + let data = new FormData(); + data.append("itemId", itemId); + data.append("quantity", -1); + let response = await (await fetch("/update-quantity-cart", { + method: "POST", + body: data + })).text(); + let body = JSON.parse(response); + } + ); + }; + + addOneToCart = async itemId => { + this.setState( + { + ...this.state, + itemId: itemId, + quantity: (this.state.quantity += 1) + }, + async () => { + let data = new FormData(); + data.append("itemId", itemId); + + data.append("quantity", +1); + let response = await (await fetch("/update-quantity-cart", { + method: "POST", + body: data + })).text(); + let body = JSON.parse(response); + } + ); + }; + removeFromCart = async itemId => { + this.setState( + { + ...this.state, + itemId: itemId + // itemId: this.props.itemId + }, + async () => { + let data = new FormData(); + // data.append("itemId", this.state.itemId); + data.append("itemId", itemId); + let response = await (await fetch("/remove-from-cart", { + method: "POST", + body: data + })).text(); + let body = JSON.parse(response); + } + ); + }; + + clearCart = async () => { + let response = await (await fetch("/checkout", { + method: "POST" + })).text(); + let body = JSON.parse(response); + }; + + handleCheckout = () => { + this.props.history.push("/checkout"); + return; + }; + + render = () => { + let subTotal = 0; + return ( +
+
+
+
Shopping Bag
+ {this.props.cart.map(cartItem => { + let itemDetails = this.props.items.filter(item => { + return item._id === cartItem.itemId; + })[0]; + + return ( +
+ + + +
+ {itemDetails.name} + + {itemDetails.description} + + {itemDetails.cost + "$ "} +
+
+
+ + Sub total:⠀ + + {cartItem.quantity * itemDetails.cost}$ + +
+ {(subTotal += cartItem.quantity * itemDetails.cost)} +
+ {console.log("subTotal", subTotal)} +
+
+
+
+ quantity: {cartItem.quantity} +
+ + + +
+ + +
+
+ ); + })} +
+
+

Checkout

+

+ Total: + {subTotal}$ +

+ {/*

+
+ } + ComponentClass="div" + token={this.onToken} + stripeKey="pk_test_O9HT5wBse32v6Ev3y8xDbYnQ00SpdfFqSl" + > + + + + +
+

*/} + +
+
+ ); + }; +} + +let mapStateToProps = state => { + return { + email: state.email, + cart: state.cart, + items: state.items, + itemId: state.itemId, + loggedIn: state.loggedIn + }; +}; +let Cart = connect(mapStateToProps)(UnconnectedCart); +export default Cart; diff --git a/src/Checkout.jsx b/src/Checkout.jsx new file mode 100644 index 0000000..24a7d9b --- /dev/null +++ b/src/Checkout.jsx @@ -0,0 +1,99 @@ +import React, { Component } from "react"; +import { connect } from "react-redux"; +import StripeCheckout from "react-stripe-checkout"; +import "./main.css"; + +class UnconnectedCheckout extends Component { + constructor(props) { + super(props); + this.state = { + email: this.props.email + }; + } + onToken = token => { + fetch("/save-stripe-token", { + method: "POST", + body: JSON.stringify(token) + }).then(response => { + response.json().then(data => { + // alert(`Thank you for your purchase! ${data.email}`); + // add username/email below + alert(`Thank you for your purchase ${this.props.email}! `); + }); + }); + }; + + render = () => { + return ( +
+

Checkout

+
SHIPPING ADDRESS
+
+
+ First name + +
+
+ Last name + +
+
+ Street address + +
+
+ City + +
+
+ ZIP or postal code + +
+
+ Country + +
+
+ State or province + +
+
+ Phone + +
+
+ +

+
+ } + ComponentClass="div" + token={this.onToken} + stripeKey="pk_test_O9HT5wBse32v6Ev3y8xDbYnQ00SpdfFqSl" + > + + + +
+

+
+ ); + }; +} + +let mapStateToProps = state => { + return { email: state.email }; +}; + +let Checkout = connect(mapStateToProps)(UnconnectedCheckout); + +export default Checkout; diff --git a/src/Item.css b/src/Item.css new file mode 100644 index 0000000..bf88e33 --- /dev/null +++ b/src/Item.css @@ -0,0 +1,375 @@ +@import url("https://fonts.googleapis.com/css?family=Crimson+Text|Libre+Caslon+Display|Quicksand&display=swap"); +@import url("https://fonts.googleapis.com/css?family=EB+Garamond|Cardo:400italic"); +.Wrapper { + z-index: 100; + text-align: center; + position: relative; + top: 85px; + display: flex; + justify-content: center; + background-color: white; +} +.HeaderNavBar { + text-align: center; + height: auto; + justify-content: center; + display: flex; + left: 30%; +} + +.secondWrapper { + justify-content: center; + position: relative; + text-align: center; +} + +video { + background-color: #101010; + z-index: 104; +} +@media all and (min-width: 600px) { + video { + width: 100%; + height: 100%; + background: contain; + background-color: #101010; + } +} +.myNavbar { + background: black; +} + +.secondPart { + position: absolute; + right: 5%; + top: 6%; + font-size: 20px; + display: flex; +} + +.entryNav { + position: absolute; + left: 5%; + top: 6%; + font-size: 20px; +} + +.stylelogo { + color: #fff; + font-family: "Cardo", serif; + + font-weight: normal; + font-style: italic; + letter-spacing: 0.1em; + line-height: 4px; + margin: auto; + text-align: center; + position: relative; +} + +em { + font-family: "EB Garamond", serif; + font-size: 38px; + text-transform: uppercase; + letter-spacing: 0.1em; + display: block; + font-style: normal; + + text-shadow: 0.07em 0.07em 0 rgba(0, 0, 0, 0.1); +} + +nav { + position: absolute; + top: 5px; + left: 0; + background: #101010; + z-index: 99; + width: 100%; + display: flex; + justify-content: center; + /* text-align: center; */ +} + +.hvr-bounce-to-right { + font-family: "Libre Caslon Display", serif; + color: #eee; + text-decoration: none; + padding: 20px; + flex-grow: 1; + background: #101010; + font-size: 26px; +} +button.hvr-bounce-to-right { + border: none; + font-family: "Libre Caslon Display", serif; + background: transparent; + text-decoration: none; + padding: 20px; + flex-grow: 1; + + font-size: 26px; +} + +.hvr-bounce-to-right { + display: inline-block; + vertical-align: middle; + -webkit-transform: perspective(1px) translateZ(0); + transform: perspective(1px) translateZ(0); + /* box-shadow: 0 0 1px rgba(0, 0, 0, 0); */ + position: relative; + -webkit-transition-property: color; + transition-property: color; + -webkit-transition-duration: 0.5s; + transition-duration: 0.5s; +} +.hvr-bounce-to-right:before { + content: ""; + position: absolute; + z-index: -1; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: gray; + + -webkit-transform: scaleX(0); + transform: scaleX(0); + -webkit-transform-origin: 0 50%; + transform-origin: 0 50%; + -webkit-transition-property: transform; + transition-property: transform; + -webkit-transition-duration: 0.5s; + transition-duration: 0.5s; + -webkit-transition-timing-function: ease-out; + transition-timing-function: ease-out; +} + +.hvr-bounce-to-right:hover, +.hvr-bounce-to-right:focus, +.hvr-bounce-to-right:active { + color: white; +} +.hvr-bounce-to-right:hover:before, +.hvr-bounce-to-right:focus:before, +.hvr-bounce-to-right:active:before { + -webkit-transform: scaleX(1); + transform: scaleX(1); + -webkit-transition-timing-function: cubic-bezier(0.52, 1.64, 0.37, 0.66); + transition-timing-function: cubic-bezier(0.52, 1.64, 0.37, 0.66); +} +.FiltersArea { + font-weight: 500; + display: inline-flex; + text-align-last: center; +} +.filterArea { + display: flex; + justify-content: center; + align-items: flex-start; + top: 65px; +} +.UserDisplay { + top: -40px; + border: none; + justify-content: center; + position: relative; + color: black; + padding: 5px; +} +.FilterButton { + border: none; + background: black; + justify-content: center; + position: relative; + color: white; + padding: 5px; +} +.SearchBar { + position: relative; + justify-content: center; + margin-right: 40px; +} + +.InputSearchBar { + display: inline-block; + position: relative; + height: 35px; + width: 35px; + box-sizing: border-box; + margin: 0px 8px 7px 0px; + padding: 7px 9px 0px 9px; + border: 3px solid #ffffff; + border-radius: 25px; + transition: all 200ms ease; + cursor: text; +} + +.InputSearchBar:after { + content: ""; + position: absolute; + width: 3px; + height: 20px; + right: -5px; + top: 21px; + background: #ffffff; + border-radius: 3px; + transform: rotate(-45deg); + transition: all 200ms ease; +} + +.InputSearchBar.active, +.InputSearchBar:hover { + width: 200px; + margin-right: 0px; +} + +.InputSearchBar:after { + height: 0px; +} + +input { + width: 100%; + border: none; + box-sizing: border-box; + font-family: Helvetica; + font-size: 15px; + color: inherit; + background: transparent; + outline-width: 0px; +} + +.NavUl { + list-style-type: none; + margin: 0; + padding: 0; + overflow: hidden; + background-color: white; +} + +.NavLi { + float: left; + display: block; + color: white; + text-align: center; + padding: 14px 10px; + text-decoration: none; +} +.ItemsField { + align-items: center; + position: relative; + height: fit-content; + list-style-type: none; + text-align: center; + display: flex; + flex-direction: row; + flex-wrap: wrap; + min-height: -webkit-fill-available; + grid-row-gap: 70px; + width: fit-content; + background-color: white; + inline-size: -webkit-fill-available; + align-self: center; + z-index: 104; + + height: auto; + top: 200px; +} +.ItemsFields { + position: relative; + padding: 20px; + font-size: 20px; + text-align: center; + min-width: 33%; + z-index: 104; + width: fit-content; + display: flex; + flex-direction: column; +} +.ItemName { + font-style: italic; + font-weight: bold; + font-family: "Nunito", sans-serif; +} +.ItemDescription { + font-size: 25px; +} +.ItemQuantities { + color: red; + font-size: 28px; + transform: rotate(20deg); + display: block; + position: absolute; + right: 35%; + top: 26%; + font-size: 25px; + font-weight: bolder; +} +.itemPrice { + font-weight: 100; +} +.ItemDescriptionLink { + background-color: Transparent; + background-repeat: no-repeat; + border: none; + + cursor: pointer; + overflow: hidden; +} + +.ItemDescriptionLink a { + color: white; +} +.PaginationDiv { + inline-size: -webkit-fill-available; + display: flex; + justify-content: center; +} +.PagePrevious { + padding-bottom: 10px; + background: transparent; + background-color: transparent; + border: none; + font-size: 20px; + margin-right: 10px; + margin-top: 15px; +} +.PageNumber { + padding-bottom: 10px; + background: transparent; + background-color: transparent; + border: none; + font-size: 20px; + margin-right: 10px; + margin-top: 15px; +} +.PageNext { + padding-bottom: 10px; + background: transparent; + background-color: transparent; + border: none; + font-size: 20px; + margin-right: 10px; + margin-top: 15px; +} + + +@media screen and (max-width: 480px) { + .ItemsFields { + width: 50%; + } +} +@media screen and (max-width: 568px) { + .ItemsFields { + width: 33.3%; + } +} +@media screen and (max-width: 768px) { + .ItemsFields { + width: 33.3%; + } +} +@media screen and (max-width: 1024px) { + .ItemsFields { + width: 25%; + } +} diff --git a/src/ItemDetails.css b/src/ItemDetails.css new file mode 100644 index 0000000..a206578 --- /dev/null +++ b/src/ItemDetails.css @@ -0,0 +1,52 @@ +.ItemPicture { + position: absolute; + top: 25%; + left: 10%; +} +.itemInfo { + position: absolute; + top: 50%; + left: 60%; +} +.formInput { + border: 1px solid black; + height: 50px; + padding: 15px; +} +.divTitle { + padding-bottom: 15px; +} + +.reviewBox { + position: absolute; + height: auto; + padding: 10px; + /* border: 1px solid; */ + top: 700px; + left: 60%; +} +.newReview { + margin-bottom: 25px; +} +.addButton { + margin-top: 10px; + font-size: 20px; + height: 45px; + padding-bottom: 25px; + border: none; +} +.newButton { + background-color: #181818; + color: white; + height: 30px; + border: none; +} + +.reviewForm { + position: absolute; + height: auto; + padding: 10px; + /* border: 1px solid; */ + top: 650px; + left: 10%; +} diff --git a/src/Items.jsx b/src/Items.jsx new file mode 100644 index 0000000..845c668 --- /dev/null +++ b/src/Items.jsx @@ -0,0 +1,301 @@ +import React, { Component } from "react"; +import "./Item.css"; +import { connect } from "react-redux"; +import { Link } from "react-router-dom"; +import ItemDetails from "./itemDetails.jsx"; +import { IoMdSearch, IoIosThumbsDown } from "react-icons/io"; +import { IconContext } from "react-icons"; +class UnconnectedItems extends Component { + constructor(props) { + super(props); + this.state = { + searchItem: "", + items: this.props.items, + displayFilters: false, + filterCost: 0, + filterMaxCost: 10000, + filterInStock: false, + itemName: "", + email: "", + itemFound: "", + quantity: 0, + item: {}, + loveIt: 0, + page: 0 + }; + } + componentDidMount = () => { + let updateItems = async () => { + // get all items from the server + let response = await fetch("/items"); + let responseBody = await response.text(); + // console.log("responseBody", responseBody); + let parsed = JSON.parse(responseBody); + console.log("parsed", parsed); + this.props.dispatch({ type: "set-items", items: parsed }); + }; + setInterval(updateItems, 500); + }; + // componentDidMount = () => { + // this.reload(); + // }; + // componentDidUpdate = (prevProps, prevState) => { + // if (prevState.items !== this.state.items) { + // this.reload(); + // } + // }; + // reload = async () => { + // let response = await fetch("/items"); + // let responseBody = await response.text(); + // let parsed = JSON.parse(responseBody); + // this.props.dispatch({ type: "set-items", items: parsed }); + // }; + gotoCart = () => { + this.props.history.push("/cart"); + }; + displayFilters = () => { + this.setState({ + ...this.state, + displayFilters: !this.state.displayFilters + }); + console.log("this.state.displayFilters", this.state.displayFilters); + }; + handleOnChangeSearch = event => { + event.preventDefault(); + console.log("searched item", event.target.value); + this.setState({ ...this.state, itemFound: event.target.value }); + }; + submitFilters = event => { + // fetch this '/filter-items' + }; + maxCostOnChange = event => { + event.preventDefault(); + this.setState({ filterMaxCost: event.target.value }); + }; + minCostOnChange = event => { + event.preventDefault(); + if (event.target.value === "") { + this.setState({ filterCost: 0 }); + } + let cost = parseInt(event.target.value); + if (isNaN(cost)) return 0; + this.setState({ filterCost: cost }); + }; + inStockOnChange = event => { + // event.preventDefault(); + console.log("event.target.checked", event.target.checked); + this.setState({ ...this.state, filterInStock: event.target.checked }); + }; + goBackToPreviousPage = () => { + this.setState({ ...this.state, page: this.state.page - 1 }); + }; + goToNextPage = () => { + this.setState({ ...this.state, page: this.state.page + 1 }); + }; + goToPage = i => { + this.setState({ ...this.state, page: i }); + }; + render = () => { + console.log("rendering items"); + let displayedItems = this.props.items; + console.log("this.props.items", this.props.items); + console.log("this.state.itemFound", this.state.itemFound); + if (this.state.itemFound !== "") { + console.log("I am filtering"); + displayedItems = displayedItems.filter(item => { + return item.name.includes(this.state.itemFound); + }); + } + let maxCost = parseInt(this.state.filterMaxCost); + displayedItems = displayedItems.filter(item => { + return item.cost > this.state.filterCost && item.cost < maxCost; + }); + if (this.state.filterInStock) { + displayedItems = displayedItems + .slice(this.state.page * 6, this.state.page * 6 + 6) + .filter(item => { + return ( + item.cost > this.state.filterCost && + item.cost < maxCost && + item.available_quantity > 0 + ); + }); + } else { + displayedItems = displayedItems + .slice(this.state.page * 6, this.state.page * 6 + 6) + .filter(item => { + return item.cost > this.state.filterCost && item.cost < maxCost; + }); + } + + // displayedItems = displayedItems + // .slice(this.state.page * 6, this.state.page * 6 + 6) + // .filter(item => { + // return item.cost > this.state.filterCost && item.cost < maxCost; + // }); + let indexPage = []; + for (let index = 1; index <= this.props.items.length / 6; index += 6) { + console.log(index); + indexPage.push(index); + } + console.log("displayedItems", displayedItems); + return ( +
+ +