From 50882f2186b3558c7fe8105548732b2ffbc661fe Mon Sep 17 00:00:00 2001 From: Son Nguyen Date: Wed, 4 Sep 2024 18:34:15 -0400 Subject: [PATCH] Update: Added nodemon and SwaggerJS --- .prettierrc | 24 ++ backend/config/db.js | 21 +- backend/index.js | 14 +- backend/routes/checkout.js | 3 +- backend/routes/products.js | 9 +- backend/routes/search.js | 7 +- backend/seed/productSeeds.js | 421 ++++++++++++++------------- package-lock.json | 29 +- package.json | 4 +- setupProxy.js | 16 +- src/App.css | 82 +++--- src/App.jsx | 118 ++++---- src/components/CheckoutForm.jsx | 349 ++++++++++------------ src/components/NavigationBar.jsx | 317 ++++++++++---------- src/components/OrderConfirmation.jsx | 36 +-- src/components/ProductCard.jsx | 84 +++--- src/components/ProductListing.jsx | 16 +- src/components/SearchResults.jsx | 118 ++++---- src/components/ShoppingCart.jsx | 111 +++---- src/dev/index.js | 11 +- src/dev/palette.jsx | 37 +-- src/dev/previews.jsx | 13 +- src/dev/useInitial.js | 14 +- src/index.js | 16 +- src/pages/Cart.jsx | 114 ++++---- src/pages/Checkout.jsx | 113 ++++--- src/pages/Home.jsx | 240 ++++++++------- src/pages/OrderSuccess.jsx | 20 +- src/pages/ProductDetails.jsx | 116 ++++---- src/pages/Shop.jsx | 131 ++++----- 30 files changed, 1254 insertions(+), 1350 deletions(-) create mode 100644 .prettierrc diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..7f18b59 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,24 @@ +{ + "printWidth": 160, + "tabWidth": 2, + "useTabs": false, + "semi": true, + "singleQuote": true, + "trailingComma": "es5", + "bracketSpacing": true, + "arrowParens": "avoid", + "overrides": [ + { + "files": "*.html", + "options": { + "parser": "html" + } + }, + { + "files": "*.js", + "options": { + "parser": "babel" + } + } + ] +} diff --git a/backend/config/db.js b/backend/config/db.js index 9cbdc2f..6b26fd8 100644 --- a/backend/config/db.js +++ b/backend/config/db.js @@ -2,17 +2,16 @@ const mongoose = require('mongoose'); require('dotenv').config(); const connectDB = async () => { - try { - await mongoose.connect(process.env.MONGO_URI, { - useNewUrlParser: true, - useUnifiedTopology: true, - }); - console.log('MongoDB connected...'); - } - catch (err) { - console.error(err.message); - process.exit(1); - } + try { + await mongoose.connect(process.env.MONGO_URI, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + console.log('MongoDB connected...'); + } catch (err) { + console.error(err.message); + process.exit(1); + } }; module.exports = connectDB; diff --git a/backend/index.js b/backend/index.js index 89a9090..9b5ef19 100644 --- a/backend/index.js +++ b/backend/index.js @@ -15,9 +15,9 @@ const PORT = process.env.PORT || 5000; // Database Connection mongoose - .connect(process.env.MONGO_URI, { }) - .then(() => console.log('MongoDB Connected')) - .catch((err) => console.log(err)); + .connect(process.env.MONGO_URI, {}) + .then(() => console.log('MongoDB Connected')) + .catch(err => console.log(err)); // Middleware app.use(cors()); @@ -32,8 +32,8 @@ app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerSpec)); // Seed database on startup seedDB().then(() => { - // Start Server after seeding - app.listen(PORT, () => { - console.log(`Server listening on port ${PORT}`); - }); + // Start Server after seeding + app.listen(PORT, () => { + console.log(`Server listening on port ${PORT}`); + }); }); diff --git a/backend/routes/checkout.js b/backend/routes/checkout.js index 83e294e..0f89178 100644 --- a/backend/routes/checkout.js +++ b/backend/routes/checkout.js @@ -109,8 +109,7 @@ router.post('/create-order', async (req, res) => { await new Promise(resolve => setTimeout(resolve, 3000)); res.status(201).json({ message: 'Order created successfully!' }); - } - catch (error) { + } catch (error) { console.error('Error creating order:', error); res.status(500).json({ error: 'Failed to create order' }); } diff --git a/backend/routes/products.js b/backend/routes/products.js index 684240d..1b40175 100644 --- a/backend/routes/products.js +++ b/backend/routes/products.js @@ -54,8 +54,7 @@ router.get('/', async (req, res) => { })); res.json(formattedProducts); - } - catch (err) { + } catch (err) { res.status(500).send('Server error'); } }); @@ -90,8 +89,7 @@ router.get('/:id', async (req, res) => { return res.status(404).send('Product not found'); } res.json(product); - } - catch (err) { + } catch (err) { res.status(500).send('Server error'); } }); @@ -123,8 +121,7 @@ router.get('/category/:category', async (req, res) => { try { const products = await Product.find({ category: req.params.category }); res.json(products); - } - catch (err) { + } catch (err) { res.status(500).send('Server error'); } }); diff --git a/backend/routes/search.js b/backend/routes/search.js index 0692185..c012ea8 100644 --- a/backend/routes/search.js +++ b/backend/routes/search.js @@ -67,15 +67,12 @@ router.get('/', async (req, res) => { const query = req.query.q; const products = await Product.find({ - $or: [ - { name: { $regex: query, $options: 'i' } }, - { description: { $regex: query, $options: 'i' } } - ] + $or: [{ name: { $regex: query, $options: 'i' } }, { description: { $regex: query, $options: 'i' } }], }); res.json(products); } catch (error) { - console.error("Error searching products:", error); + console.error('Error searching products:', error); res.status(500).json({ error: 'An error occurred during the search.' }); } }); diff --git a/backend/seed/productSeeds.js b/backend/seed/productSeeds.js index 93207b9..84c42ba 100644 --- a/backend/seed/productSeeds.js +++ b/backend/seed/productSeeds.js @@ -5,221 +5,222 @@ const mongoose = require('mongoose'); // Sample product data -- to be replaced with actual products const productSeeds = [ - { - name: 'iPhone 15 Pro Max', - description: 'Apple\'s latest flagship smartphone.', - price: 1099, - category: 'electronics', - image: 'https://cdn.tgdd.vn/Products/Images/42/305658/iphone-15-pro-max-blue-thumbnew-600x600.jpg', - brand: 'Apple', - stock: 100, - }, - { - name: 'MacBook Air M2', - description: 'Powerful and lightweight laptop from Apple.', - price: 1199, - category: 'computers', - image: 'https://cdn8.web4s.vn/media/products/mac-air-m2/macbookairm2-midnight%201.jpg', - brand: 'Apple', - stock: 50, - }, - { - name: 'Sony WH-1000XM5 Headphones', - description: 'Industry-leading noise-canceling headphones.', - price: 399, - category: 'electronics', - image: 'https://bizweb.dktcdn.net/100/340/129/products/wh1000xm5-midnightblue-2-cuongphanvn.jpg?v=1714306049613', - brand: 'Sony', - stock: 200, - }, - { - name: 'Samsung 65" QLED TV', - description: 'Immersive 4K TV experience with QLED technology.', - price: 1499, - category: 'electronics', - image: 'https://cdn.mediamart.vn/images/product/qled-tivi-4k-samsung-65-inch-65q80c-smart-tv_5304e716.png', // Replace with actual image URL - brand: 'Samsung', - stock: 10, - }, - { - name: 'Canon EOS R5', - description: 'High-performance mirrorless camera from Canon.', - price: 3799, - category: 'electronics', - image: 'https://i1.adis.ws/i/canon/eos-r5_front_rf24-105mmf4lisusm_square_32c26ad194234d42b3cd9e582a21c99b', - brand: 'Canon', - stock: 5, - }, - { - name: 'Apple Watch Series 7', - description: 'Stay connected and healthy with the latest Apple Watch.', - price: 399, - category: 'electronics', - image: 'https://akbroshop.com/wp-content/uploads/2022/08/hinh-aw-s7-xanh.jpg', - brand: 'Apple', - stock: 100, - }, - { - name: 'Dell XPS 15', - description: 'Powerful laptop with stunning 4K display.', - price: 1799, - category: 'computers', - image: 'https://minhvu.vn/thumb/dellxps/dellxps159530/dellxps159530cbfbjco_480_360.jpg', - brand: 'Dell', - stock: 20, - }, - { - name: 'Samsung Galaxy Tab S7+', - description: 'Premium Android tablet with stunning AMOLED display.', - price: 849, - category: 'electronics', - image: 'https://hanoicomputercdn.com/media/product/60370_may_tinh_bang_samsung_galaxy_tab_s7_plus_128gb_den.png', - brand: 'Samsung', - stock: 30, - }, - { - name: 'Sony A7 IV', - description: 'Full-frame mirrorless camera with 33MP sensor.', - price: 2499, - category: 'electronics', - image: 'https://zshop.vn/images/detailed/92/1634812545_1667800.jpg', - brand: 'Sony', - stock: 10, - }, - { - name: 'LG C1 OLED TV', - description: 'Stunning OLED TV with great picture quality.', - price: 1999, - category: 'electronics', - image: 'https://product.hstatic.net/200000574527/product/dz-6_ac9672a6534245fcbb1a4938a1337907_1024x1024.jpg', - brand: 'LG', - stock: 15, - }, - { - name: 'Microsoft Surface Laptop 4', - description: 'Sleek and powerful laptop from Microsoft.', - price: 1299, - category: 'computers', - image: 'https://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RE4LiXm?ver=45be&q=90&m=6&h=705&w=1253&b=%23FFFFFFFF&f=jpg&o=f&p=140&aim=true', - brand: 'Microsoft', - stock: 25, - }, - { - name: 'GoPro Hero 10 Black', - description: 'High-performance action camera for capturing your adventures.', - price: 499, - category: 'electronics', - image: 'https://cdn.vjshop.vn/camera-hanh-dong/gopro/gopro-hero-10/gopro-hero-10-1000x1000.png', - brand: 'GoPro', - stock: 50, - }, - { - name: 'Bose QuietComfort 45 Headphones', - description: 'Premium noise-canceling headphones for immersive audio experience.', - price: 329, - category: 'electronics', - image: 'https://cdn.nguyenkimmall.com/images/detailed/848/10054167-tai-nghe-khong-day-bose-quietcomfort-45-den-866724-0100-1.jpg', - brand: 'Bose', - stock: 100, - }, - { - name: 'iPad Pro 12.9" (2022)', - description: 'Powerful tablet with M1 chip and stunning Liquid Retina XDR display.', - price: 1099, - category: 'electronics', - image: 'https://cdn2.cellphones.com.vn/x/media/catalog/product/i/p/ipad-pro-13-select-202210_3_3_1_1.png', - brand: 'Apple', - stock: 50, - }, - { - name: 'Samsung Galaxy S22 Ultra', - description: 'Samsung\'s latest flagship smartphone with S Pen support.', - price: 1199, - category: 'electronics', - image: 'https://i5.walmartimages.com/seo/Samsung-Galaxy-S22-Ultra-5G-SM-S908U1-256GB-Green-US-Model-Factory-Unlocked-Cell-Phone-Very-Good-Condition_0b4b7166-2688-4e0c-954d-539d5ea29ad9.5946110fe2a2c21cda0b256e2969a555.jpeg', - brand: 'Samsung', - stock: 30, - }, - { - name: 'Sony A7C', - description: 'Compact full-frame mirrorless camera with 24MP sensor.', - price: 1799, - category: 'electronics', - image: 'https://cdn.vjshop.vn/may-anh/mirrorless/sony/sony-alpha-a7c/sony-a7c-black-1.jpg', - brand: 'Sony', - stock: 10, - }, - { - name: 'LG Gram 17" Laptop', - description: 'Ultra-lightweight laptop with long battery life.', - price: 1499, - category: 'computers', - image: 'https://lapvip.vn/upload/products/original/lg-gram-17-20232-1700187329.jpg', - brand: 'LG', - stock: 20, - }, - { - name: 'Canon EOS R6', - description: 'High-performance mirrorless camera with 20MP sensor.', - price: 2499, - category: 'electronics', - image: 'https://cdn.vjshop.vn/may-anh/mirrorless/canon/canon-eos-r6/canon-eos-r6-1-1500x1500.jpg', - brand: 'Canon', - stock: 5, - }, - { - name: 'Apple AirPods Max', - description: 'Premium over-ear headphones with high-fidelity audio.', - price: 549, - category: 'electronics', - image: 'https://store.storeimages.cdn-apple.com/8756/as-images.apple.com/is/airpods-max-hero-select-202011_FMT_WHH?wid=607&hei=556&fmt=jpeg&qlt=90&.v=1633623988000', - brand: 'Apple', - stock: 50, - }, - { - name: 'Dell XPS 13', - description: 'Ultra-portable laptop with stunning InfinityEdge display.', - price: 999, - category: 'computers', - image: 'https://product.hstatic.net/1000331874/product/dell_xps_13_dc9a366cc90c495b9a3da844f2a08cb9_1024x1024.jpg', - brand: 'Dell', - stock: 30, - }, - { - name: 'Samsung Galaxy Watch 4', - description: 'Stylish smartwatch with advanced health tracking features.', - price: 249, - category: 'electronics', - image: 'https://cdn-v2.didongviet.vn/files/media/catalog/product/s/a/samsung-galaxy-watch-4-40mm-likenew-mau-den-didongviet.jpeg', - brand: 'Samsung', - stock: 100, - }, - { - name: 'Sony WH-1000XM4 Headphones', - description: 'Premium noise-canceling headphones with long battery life.', - price: 349, - category: 'electronics', - image: 'https://www.sony.com.vn/image/5d02da5df552836db894cead8a68f5f3?fmt=pjpeg&wid=330&bgcolor=FFFFFF&bgc=FFFFFF', - brand: 'Sony', - stock: 100, - } + { + name: 'iPhone 15 Pro Max', + description: "Apple's latest flagship smartphone.", + price: 1099, + category: 'electronics', + image: 'https://cdn.tgdd.vn/Products/Images/42/305658/iphone-15-pro-max-blue-thumbnew-600x600.jpg', + brand: 'Apple', + stock: 100, + }, + { + name: 'MacBook Air M2', + description: 'Powerful and lightweight laptop from Apple.', + price: 1199, + category: 'computers', + image: 'https://cdn8.web4s.vn/media/products/mac-air-m2/macbookairm2-midnight%201.jpg', + brand: 'Apple', + stock: 50, + }, + { + name: 'Sony WH-1000XM5 Headphones', + description: 'Industry-leading noise-canceling headphones.', + price: 399, + category: 'electronics', + image: 'https://bizweb.dktcdn.net/100/340/129/products/wh1000xm5-midnightblue-2-cuongphanvn.jpg?v=1714306049613', + brand: 'Sony', + stock: 200, + }, + { + name: 'Samsung 65" QLED TV', + description: 'Immersive 4K TV experience with QLED technology.', + price: 1499, + category: 'electronics', + image: 'https://cdn.mediamart.vn/images/product/qled-tivi-4k-samsung-65-inch-65q80c-smart-tv_5304e716.png', // Replace with actual image URL + brand: 'Samsung', + stock: 10, + }, + { + name: 'Canon EOS R5', + description: 'High-performance mirrorless camera from Canon.', + price: 3799, + category: 'electronics', + image: 'https://i1.adis.ws/i/canon/eos-r5_front_rf24-105mmf4lisusm_square_32c26ad194234d42b3cd9e582a21c99b', + brand: 'Canon', + stock: 5, + }, + { + name: 'Apple Watch Series 7', + description: 'Stay connected and healthy with the latest Apple Watch.', + price: 399, + category: 'electronics', + image: 'https://akbroshop.com/wp-content/uploads/2022/08/hinh-aw-s7-xanh.jpg', + brand: 'Apple', + stock: 100, + }, + { + name: 'Dell XPS 15', + description: 'Powerful laptop with stunning 4K display.', + price: 1799, + category: 'computers', + image: 'https://minhvu.vn/thumb/dellxps/dellxps159530/dellxps159530cbfbjco_480_360.jpg', + brand: 'Dell', + stock: 20, + }, + { + name: 'Samsung Galaxy Tab S7+', + description: 'Premium Android tablet with stunning AMOLED display.', + price: 849, + category: 'electronics', + image: 'https://hanoicomputercdn.com/media/product/60370_may_tinh_bang_samsung_galaxy_tab_s7_plus_128gb_den.png', + brand: 'Samsung', + stock: 30, + }, + { + name: 'Sony A7 IV', + description: 'Full-frame mirrorless camera with 33MP sensor.', + price: 2499, + category: 'electronics', + image: 'https://zshop.vn/images/detailed/92/1634812545_1667800.jpg', + brand: 'Sony', + stock: 10, + }, + { + name: 'LG C1 OLED TV', + description: 'Stunning OLED TV with great picture quality.', + price: 1999, + category: 'electronics', + image: 'https://product.hstatic.net/200000574527/product/dz-6_ac9672a6534245fcbb1a4938a1337907_1024x1024.jpg', + brand: 'LG', + stock: 15, + }, + { + name: 'Microsoft Surface Laptop 4', + description: 'Sleek and powerful laptop from Microsoft.', + price: 1299, + category: 'computers', + image: + 'https://img-prod-cms-rt-microsoft-com.akamaized.net/cms/api/am/imageFileData/RE4LiXm?ver=45be&q=90&m=6&h=705&w=1253&b=%23FFFFFFFF&f=jpg&o=f&p=140&aim=true', + brand: 'Microsoft', + stock: 25, + }, + { + name: 'GoPro Hero 10 Black', + description: 'High-performance action camera for capturing your adventures.', + price: 499, + category: 'electronics', + image: 'https://cdn.vjshop.vn/camera-hanh-dong/gopro/gopro-hero-10/gopro-hero-10-1000x1000.png', + brand: 'GoPro', + stock: 50, + }, + { + name: 'Bose QuietComfort 45 Headphones', + description: 'Premium noise-canceling headphones for immersive audio experience.', + price: 329, + category: 'electronics', + image: 'https://cdn.nguyenkimmall.com/images/detailed/848/10054167-tai-nghe-khong-day-bose-quietcomfort-45-den-866724-0100-1.jpg', + brand: 'Bose', + stock: 100, + }, + { + name: 'iPad Pro 12.9" (2022)', + description: 'Powerful tablet with M1 chip and stunning Liquid Retina XDR display.', + price: 1099, + category: 'electronics', + image: 'https://cdn2.cellphones.com.vn/x/media/catalog/product/i/p/ipad-pro-13-select-202210_3_3_1_1.png', + brand: 'Apple', + stock: 50, + }, + { + name: 'Samsung Galaxy S22 Ultra', + description: "Samsung's latest flagship smartphone with S Pen support.", + price: 1199, + category: 'electronics', + image: + 'https://i5.walmartimages.com/seo/Samsung-Galaxy-S22-Ultra-5G-SM-S908U1-256GB-Green-US-Model-Factory-Unlocked-Cell-Phone-Very-Good-Condition_0b4b7166-2688-4e0c-954d-539d5ea29ad9.5946110fe2a2c21cda0b256e2969a555.jpeg', + brand: 'Samsung', + stock: 30, + }, + { + name: 'Sony A7C', + description: 'Compact full-frame mirrorless camera with 24MP sensor.', + price: 1799, + category: 'electronics', + image: 'https://cdn.vjshop.vn/may-anh/mirrorless/sony/sony-alpha-a7c/sony-a7c-black-1.jpg', + brand: 'Sony', + stock: 10, + }, + { + name: 'LG Gram 17" Laptop', + description: 'Ultra-lightweight laptop with long battery life.', + price: 1499, + category: 'computers', + image: 'https://lapvip.vn/upload/products/original/lg-gram-17-20232-1700187329.jpg', + brand: 'LG', + stock: 20, + }, + { + name: 'Canon EOS R6', + description: 'High-performance mirrorless camera with 20MP sensor.', + price: 2499, + category: 'electronics', + image: 'https://cdn.vjshop.vn/may-anh/mirrorless/canon/canon-eos-r6/canon-eos-r6-1-1500x1500.jpg', + brand: 'Canon', + stock: 5, + }, + { + name: 'Apple AirPods Max', + description: 'Premium over-ear headphones with high-fidelity audio.', + price: 549, + category: 'electronics', + image: + 'https://store.storeimages.cdn-apple.com/8756/as-images.apple.com/is/airpods-max-hero-select-202011_FMT_WHH?wid=607&hei=556&fmt=jpeg&qlt=90&.v=1633623988000', + brand: 'Apple', + stock: 50, + }, + { + name: 'Dell XPS 13', + description: 'Ultra-portable laptop with stunning InfinityEdge display.', + price: 999, + category: 'computers', + image: 'https://product.hstatic.net/1000331874/product/dell_xps_13_dc9a366cc90c495b9a3da844f2a08cb9_1024x1024.jpg', + brand: 'Dell', + stock: 30, + }, + { + name: 'Samsung Galaxy Watch 4', + description: 'Stylish smartwatch with advanced health tracking features.', + price: 249, + category: 'electronics', + image: 'https://cdn-v2.didongviet.vn/files/media/catalog/product/s/a/samsung-galaxy-watch-4-40mm-likenew-mau-den-didongviet.jpeg', + brand: 'Samsung', + stock: 100, + }, + { + name: 'Sony WH-1000XM4 Headphones', + description: 'Premium noise-canceling headphones with long battery life.', + price: 349, + category: 'electronics', + image: 'https://www.sony.com.vn/image/5d02da5df552836db894cead8a68f5f3?fmt=pjpeg&wid=330&bgcolor=FFFFFF&bgc=FFFFFF', + brand: 'Sony', + stock: 100, + }, ]; const seedDB = async () => { - await Product.deleteMany({}); - await Product.insertMany(productSeeds); - console.log('Products data seeded successfully!'); + await Product.deleteMany({}); + await Product.insertMany(productSeeds); + console.log('Products data seeded successfully!'); }; //only run when the "node productSeeds.js dev" command is executed manually -if(process.argv[2] == "dev") { - dotenv.config({ path: path.resolve(__dirname, '../.env') }); - mongoose - .connect(process.env.MONGO_URI, { }) - .then(async() => { - await seedDB(); - process.exit(); - }); - } +if (process.argv[2] == 'dev') { + dotenv.config({ path: path.resolve(__dirname, '../.env') }); + mongoose.connect(process.env.MONGO_URI, {}).then(async () => { + await seedDB(); + process.exit(); + }); +} module.exports = seedDB; diff --git a/package-lock.json b/package-lock.json index 09df00a..15038bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,13 @@ { - "name": "ecommerce-fullstack-website", - "version": "0.1.0", + "name": "ecommerce-fullstack-website-frontend", + "version": "1.1.0", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "ecommerce-fullstack-website", - "version": "0.1.0", + "name": "ecommerce-fullstack-website-frontend", + "version": "1.1.0", + "license": "MIT", "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", @@ -22,6 +23,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.7.2", "http-proxy-middleware": "^3.0.0", + "prettier": "^3.3.3", "react": "^18.3.1", "react-credit-cards-2": "^1.0.2", "react-dom": "^18.3.1", @@ -15205,6 +15207,20 @@ "node": ">= 0.8.0" } }, + "node_modules/prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", @@ -29665,6 +29681,11 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" }, + "prettier": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==" + }, "pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", diff --git a/package.json b/package.json index 59ed25c..b4cc494 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.7.2", "http-proxy-middleware": "^3.0.0", + "prettier": "^3.3.3", "react": "^18.3.1", "react-credit-cards-2": "^1.0.2", "react-dom": "^18.3.1", @@ -40,7 +41,8 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "format": "prettier --config .prettierrc --write \"setupProxy.js\" \"src/components/*.{html,css,js,jsx,ts,tsx}\" \"src/dev/*.{html,css,js,jsx,ts,tsx}\" \"src/pages/*.{html,css,js,jsx,ts,tsx}\" \"src/*.{html,css,js,jsx,ts,tsx}\" \"backend/*.{html,css,js,jsx,ts,tsx}\" \"backend/seed/*.{html,css,js,jsx,ts,tsx}\" \"backend/routes/*.{html,css,js,jsx,ts,tsx}\" \"backend/docs/*.{html,css,js,jsx,ts,tsx}\" \"backend/config/*.{html,css,js,jsx,ts,tsx}\"" }, "eslintConfig": { "extends": [ diff --git a/setupProxy.js b/setupProxy.js index fbc1540..2198671 100644 --- a/setupProxy.js +++ b/setupProxy.js @@ -1,11 +1,11 @@ const { createProxyMiddleware } = require('http-proxy-middleware'); -module.exports = function(app) { - app.use( - '/api', - createProxyMiddleware({ - target: 'http://localhost:5000', - changeOrigin: true, - }) - ); +module.exports = function (app) { + app.use( + '/api', + createProxyMiddleware({ + target: 'http://localhost:5000', + changeOrigin: true, + }) + ); }; diff --git a/src/App.css b/src/App.css index ecad11b..8ee55a2 100644 --- a/src/App.css +++ b/src/App.css @@ -1,93 +1,95 @@ /* General Styles */ body { - font-family: sans-serif; - margin: 0; - padding: 0; - background-color: #f5f5f5; + font-family: sans-serif; + margin: 0; + padding: 0; + background-color: #f5f5f5; } .container { - max-width: 1200px; - margin: 0 auto; - padding: 20px; + max-width: 1200px; + margin: 0 auto; + padding: 20px; } -h1, h2, h3 { - color: #333; +h1, +h2, +h3 { + color: #333; } /* Navigation Bar */ .navbar { - background-color: #2874f0; - color: white; - padding: 15px; + background-color: #2874f0; + color: white; + padding: 15px; } .navbar ul { - list-style-type: none; - margin: 0; - padding: 0; - display: flex; + list-style-type: none; + margin: 0; + padding: 0; + display: flex; } .navbar li { - margin-right: 20px; + margin-right: 20px; } .navbar a { - color: white; - text-decoration: none; + color: white; + text-decoration: none; } /* Home Page */ .home { - text-align: center; - padding: 50px; + text-align: center; + padding: 50px; } .home h2 { - font-size: 3rem; + font-size: 3rem; } /* Shop Page */ .product-grid { - display: grid; - grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); - gap: 20px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; } .product-card { - border: 1px solid #ddd; - padding: 20px; + border: 1px solid #ddd; + padding: 20px; } .product-card img { - max-width: 100%; - height: auto; + max-width: 100%; + height: auto; } /* Cart Page */ .cart-item { - display: flex; - align-items: center; - margin-bottom: 20px; + display: flex; + align-items: center; + margin-bottom: 20px; } .cart-item img { - width: 80px; - height: 80px; - margin-right: 20px; + width: 80px; + height: 80px; + margin-right: 20px; } /* Checkout Page */ .checkout { - display: grid; - grid-template-columns: 1fr 1fr; - gap: 20px; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 20px; } /* Order Success Page */ .order-success { - text-align: center; - padding: 50px; + text-align: center; + padding: 50px; } diff --git a/src/App.jsx b/src/App.jsx index c388bb0..3d261ab 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -11,89 +11,69 @@ import OrderSuccess from './pages/OrderSuccess'; import ProductDetails from './pages/ProductDetails'; const theme = createTheme({ - palette: { - primary: { - main: '#2874f0', - }, - secondary: { - main: '#f50057', - }, + palette: { + primary: { + main: '#2874f0', }, - typography: { - fontFamily: 'Roboto, sans-serif', + secondary: { + main: '#f50057', }, + }, + typography: { + fontFamily: 'Roboto, sans-serif', + }, }); function App() { - const [products, setProducts] = React.useState([]); - const [cart, setCart] = React.useState([]); - const [loading, setLoading] = React.useState(true); + const [products, setProducts] = React.useState([]); + const [cart, setCart] = React.useState([]); + const [loading, setLoading] = React.useState(true); - React.useEffect(() => { - const fetchProducts = async () => { - try { - // Be sure to replace the endpoint if your API (backend server) is running on a different port - // This is the default port for the Express server - port 5000 - const response = await fetch('http://localhost:5000/api/products'); - const data = await response.json(); - setProducts(data); - } - catch (error) { - console.error('Error fetching products:', error); - } - finally { - setLoading(false); - } - }; + React.useEffect(() => { + const fetchProducts = async () => { + try { + // Be sure to replace the endpoint if your API (backend server) is running on a different port + // This is the default port for the Express server - port 5000 + const response = await fetch('http://localhost:5000/api/products'); + const data = await response.json(); + setProducts(data); + } catch (error) { + console.error('Error fetching products:', error); + } finally { + setLoading(false); + } + }; - fetchProducts(); - }, []); + fetchProducts(); + }, []); - const addToCart = (product) => { - setCart([...cart, product]); - }; + const addToCart = product => { + setCart([...cart, product]); + }; - return ( - - - - - - - } - /> + return ( + + + + + + + } /> - } - /> + } /> - } - /> + } /> - } - /> + } /> - } - /> + } /> - } - /> - - - - - ); + } /> + + + + + ); } export default App; diff --git a/src/components/CheckoutForm.jsx b/src/components/CheckoutForm.jsx index e2310bb..e4dfcbf 100644 --- a/src/components/CheckoutForm.jsx +++ b/src/components/CheckoutForm.jsx @@ -4,196 +4,165 @@ import Cards from 'react-credit-cards-2'; import 'react-credit-cards-2/dist/es/styles-compiled.css'; function CheckoutForm({ onSubmit }) { - const [formData, setFormData] = useState({ - name: '', - email: '', - shippingAddress: '', - cardNumber: '', - cardName: '', - expiry: '', - cvc: '', - }); - const [cardFocused, setCardFocused] = useState(''); - const [loading, setLoading] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); - - const handleInputChange = (e) => { - const { name, value } = e.target; - let sanitizedValue = value; - - switch (name) { - case 'cardNumber': - sanitizedValue = value.replace(/\D/g, ''); - break; - case 'expiry': - sanitizedValue = value.replace(/[^0-9/]/g, ''); - if (sanitizedValue.length > 7) sanitizedValue = sanitizedValue.slice(0, 7); - break; - case 'cvc': - sanitizedValue = value.replace(/\D/g, ''); - if (sanitizedValue.length > 4) sanitizedValue = sanitizedValue.slice(0, 4); - break; - } - - setFormData({ ...formData, [name]: sanitizedValue }); - }; - - const handleInputFocus = (e) => { - setCardFocused(e.target.name); - }; - - const handleSubmit = async (event) => { - event.preventDefault(); - setLoading(true); - setErrorMessage(''); - - try { - await onSubmit(formData); - setLoading(false); - } - catch (error) { - setLoading(false); - setErrorMessage(error.response.data.error || 'An error occurred'); - } - }; - - return ( -
- - Billing Information - - - - - - - - - - - - - Shipping Information - - - - - - - - - - Payment Details - - - - - - - - - - - - - - - - - - - - - - {loading && } - {errorMessage && {errorMessage}} - - - - ); + const [formData, setFormData] = useState({ + name: '', + email: '', + shippingAddress: '', + cardNumber: '', + cardName: '', + expiry: '', + cvc: '', + }); + const [cardFocused, setCardFocused] = useState(''); + const [loading, setLoading] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + + const handleInputChange = e => { + const { name, value } = e.target; + let sanitizedValue = value; + + switch (name) { + case 'cardNumber': + sanitizedValue = value.replace(/\D/g, ''); + break; + case 'expiry': + sanitizedValue = value.replace(/[^0-9/]/g, ''); + if (sanitizedValue.length > 7) sanitizedValue = sanitizedValue.slice(0, 7); + break; + case 'cvc': + sanitizedValue = value.replace(/\D/g, ''); + if (sanitizedValue.length > 4) sanitizedValue = sanitizedValue.slice(0, 4); + break; + } + + setFormData({ ...formData, [name]: sanitizedValue }); + }; + + const handleInputFocus = e => { + setCardFocused(e.target.name); + }; + + const handleSubmit = async event => { + event.preventDefault(); + setLoading(true); + setErrorMessage(''); + + try { + await onSubmit(formData); + setLoading(false); + } catch (error) { + setLoading(false); + setErrorMessage(error.response.data.error || 'An error occurred'); + } + }; + + return ( +
+ + Billing Information + + + + + + + + + + + + + Shipping Information + + + + + + + + + + Payment Details + + + + + + + + + + + + + + + + + + + + + + {loading && } + {errorMessage && {errorMessage}} + + + + ); } export default CheckoutForm; diff --git a/src/components/NavigationBar.jsx b/src/components/NavigationBar.jsx index edd58a5..ab60e26 100644 --- a/src/components/NavigationBar.jsx +++ b/src/components/NavigationBar.jsx @@ -8,181 +8,164 @@ import axios from 'axios'; import SearchResults from './SearchResults'; function NavigationBar({ cartItemCount }) { - const [anchorEl, setAnchorEl] = React.useState(null); - const [searchQuery, setSearchQuery] = React.useState(''); - const [searchResults, setSearchResults] = React.useState([]); - const searchBarRef = React.useRef(null); - const open = Boolean(anchorEl); - const location = useLocation(); - const isMobile = useMediaQuery('(max-width:600px)'); + const [anchorEl, setAnchorEl] = React.useState(null); + const [searchQuery, setSearchQuery] = React.useState(''); + const [searchResults, setSearchResults] = React.useState([]); + const searchBarRef = React.useRef(null); + const open = Boolean(anchorEl); + const location = useLocation(); + const isMobile = useMediaQuery('(max-width:600px)'); - const handleClick = (event) => { - setAnchorEl(event.currentTarget); - }; + const handleClick = event => { + setAnchorEl(event.currentTarget); + }; - const handleClose = () => { - setAnchorEl(null); - }; + const handleClose = () => { + setAnchorEl(null); + }; - const handleSearchChange = (event) => { - setSearchQuery(event.target.value); - }; + const handleSearchChange = event => { + setSearchQuery(event.target.value); + }; - const handleSearchResultClick = () => { - setSearchResults([]); - }; + const handleSearchResultClick = () => { + setSearchResults([]); + }; - const handleSearchSubmit = async (event) => { - event.preventDefault(); - try { - const response = await axios.get(`http://localhost:5000/api/search?q=${searchQuery}`); // Specify port 5000 - setSearchResults(response.data); - } - catch (error) { - console.error('Error fetching search results:', error); - setSearchResults([]); - } - }; + const handleSearchSubmit = async event => { + event.preventDefault(); + try { + const response = await axios.get(`http://localhost:5000/api/search?q=${searchQuery}`); // Specify port 5000 + setSearchResults(response.data); + } catch (error) { + console.error('Error fetching search results:', error); + setSearchResults([]); + } + }; - return ( - + + {isMobile ? ( + <> + + + + + + Home + + + Shop + + + Cart + + + + + FUSION ELECTRONICS + + + + ) : ( + <> + + + FUSION ELECTRONICS + + +
+ + + + + + + + + + + + )} +
+ + {searchResults.length > 0 && ( + + - - {isMobile ? ( - <> - - - - - - Home - - - Shop - - - Cart - - - - FUSION ELECTRONICS - - - ) : ( - <> - - FUSION ELECTRONICS - -
- - - - - - - - - - - - )} -
- - {searchResults.length > 0 && ( - - - - - - )} -
- ); + > + + + + )} + + ); } export default NavigationBar; diff --git a/src/components/OrderConfirmation.jsx b/src/components/OrderConfirmation.jsx index ce42da8..053910f 100644 --- a/src/components/OrderConfirmation.jsx +++ b/src/components/OrderConfirmation.jsx @@ -4,26 +4,26 @@ import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; import { useNavigate } from 'react-router-dom'; function OrderConfirmation() { - const navigate = useNavigate(); + const navigate = useNavigate(); - const handleContinueShopping = () => { - navigate('/shop'); - }; + const handleContinueShopping = () => { + navigate('/shop'); + }; - return ( - - - - Order Successful! - - - Thank you for your purchase. Your order is being processed. - - - - ); + return ( + + + + Order Successful! + + + Thank you for your purchase. Your order is being processed. + + + + ); } export default OrderConfirmation; diff --git a/src/components/ProductCard.jsx b/src/components/ProductCard.jsx index b4f2b3d..d6e789d 100644 --- a/src/components/ProductCard.jsx +++ b/src/components/ProductCard.jsx @@ -8,50 +8,50 @@ import Typography from '@mui/material/Typography'; import { useNavigate } from 'react-router-dom'; export default function ProductCard({ product, addToCart }) { - const navigate = useNavigate(); + const navigate = useNavigate(); - const handleViewDetails = () => { - navigate(`/product/${product._id}`); - }; + const handleViewDetails = () => { + navigate(`/product/${product._id}`); + }; - return ( - -
- -
+ return ( + +
+ +
- - - {product.name} - - - {product.description} - - - ${product.price.toFixed(2)} - - + + + {product.name} + + + {product.description} + + + ${product.price.toFixed(2)} + + - - - - -
- ); + + + + +
+ ); } diff --git a/src/components/ProductListing.jsx b/src/components/ProductListing.jsx index a6cb8c3..60df35a 100644 --- a/src/components/ProductListing.jsx +++ b/src/components/ProductListing.jsx @@ -3,13 +3,13 @@ import { Grid } from '@mui/material'; import ProductCard from './ProductCard'; export default function ProductListing({ products, addToCart }) { - return ( - - {products.map((product) => ( - - - - ))} + return ( + + {products.map(product => ( + + - ); + ))} + + ); } diff --git a/src/components/SearchResults.jsx b/src/components/SearchResults.jsx index ab8bdf8..5d15f57 100644 --- a/src/components/SearchResults.jsx +++ b/src/components/SearchResults.jsx @@ -3,67 +3,65 @@ import { Link } from 'react-router-dom'; import { List, ListItem, ListItemText, ListItemAvatar, Avatar, Typography, Paper } from '@mui/material'; function SearchResults({ results, onResultClick, setSearchResults }) { - const handleItemClick = () => { - onResultClick(); - setSearchResults([]); - }; + const handleItemClick = () => { + onResultClick(); + setSearchResults([]); + }; - return ( - - - {results.length > 0 ? ( - results.slice(0, 3).map(product => ( - - - - - - - ${product.price} - - {" - " + product.description.slice(0, 50) + "..."} - - } - /> - - )) - ) : ( - - - - )} - - - ); + return ( + + + {results.length > 0 ? ( + results.slice(0, 3).map(product => ( + + + + + + + ${product.price} + + {' - ' + product.description.slice(0, 50) + '...'} + + } + /> + + )) + ) : ( + + + + )} + + + ); } export default SearchResults; diff --git a/src/components/ShoppingCart.jsx b/src/components/ShoppingCart.jsx index 3cb67cf..29e0455 100644 --- a/src/components/ShoppingCart.jsx +++ b/src/components/ShoppingCart.jsx @@ -1,77 +1,62 @@ import * as React from 'react'; -import { - List, - ListItem, - ListItemText, - ListItemAvatar, - Avatar, - Button, - Typography, - Divider, - Box, -} from '@mui/material'; +import { List, ListItem, ListItemText, ListItemAvatar, Avatar, Button, Typography, Divider, Box } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import { useNavigate } from 'react-router-dom'; function ShoppingCart({ cart, setCart }) { - const navigate = useNavigate(); + const navigate = useNavigate(); - const removeFromCart = (productId) => { - setCart(cart.filter((item) => item.id !== productId)); - }; + const removeFromCart = productId => { + setCart(cart.filter(item => item.id !== productId)); + }; - const calculateTotal = () => { - return cart.reduce((total, item) => total + item.price, 0); - }; + const calculateTotal = () => { + return cart.reduce((total, item) => total + item.price, 0); + }; - const handleCheckout = () => { - navigate('/checkout'); - }; + const handleCheckout = () => { + navigate('/checkout'); + }; - return ( - - Shopping Cart + return ( + + + Shopping Cart + - {cart.length === 0 ? ( - Your cart is empty. - ) : ( - <> - - {cart.map((item) => ( - - removeFromCart(item.id)} - startIcon={} - color="error" - > - Remove - - } - > - - - - - - - - ))} - - - Total: ${calculateTotal().toFixed(2)} - - - - )} - - ); + } + > + + + + + + + + ))} + + + Total: ${calculateTotal().toFixed(2)} + + + + )} + + ); } export default ShoppingCart; diff --git a/src/dev/index.js b/src/dev/index.js index fd3755f..846ceed 100644 --- a/src/dev/index.js +++ b/src/dev/index.js @@ -1,9 +1,6 @@ -import React from "react" -import { useInitial } from "./useInitial" +import React from 'react'; +import { useInitial } from './useInitial'; -const ComponentPreviews = React.lazy(() => import("./previews")) +const ComponentPreviews = React.lazy(() => import('./previews')); -export { - ComponentPreviews, - useInitial -} +export { ComponentPreviews, useInitial }; diff --git a/src/dev/palette.jsx b/src/dev/palette.jsx index 6dd92f5..3013656 100644 --- a/src/dev/palette.jsx +++ b/src/dev/palette.jsx @@ -1,27 +1,20 @@ -import { Fragment } from "react" -import { - Category, - Component, - Variant, - Palette, -} from "@react-buddy/ide-toolbox" -import MUIPalette from "@react-buddy/palette-mui"; +import { Fragment } from 'react'; +import { Category, Component, Variant, Palette } from '@react-buddy/ide-toolbox'; +import MUIPalette from '@react-buddy/palette-mui'; export const PaletteTree = () => ( - - - - - - - - - - -) + + + + + + + + + + +); export function ExampleLoaderComponent() { - return ( - Loading... - ) + return Loading...; } diff --git a/src/dev/previews.jsx b/src/dev/previews.jsx index 0613e6d..0cbcea9 100644 --- a/src/dev/previews.jsx +++ b/src/dev/previews.jsx @@ -1,11 +1,8 @@ -import { Previews } from '@react-buddy/ide-toolbox' -import { PaletteTree } from './palette' +import { Previews } from '@react-buddy/ide-toolbox'; +import { PaletteTree } from './palette'; const ComponentPreviews = () => { - return ( - }> - - ) -} + return }>; +}; -export default ComponentPreviews +export default ComponentPreviews; diff --git a/src/dev/useInitial.js b/src/dev/useInitial.js index 5e5f31b..dffb96b 100644 --- a/src/dev/useInitial.js +++ b/src/dev/useInitial.js @@ -1,10 +1,10 @@ -import { useState } from 'react' +import { useState } from 'react'; export const useInitial = () => { - const [status, setStatus] = useState({ - loading: false, - error: false - }) + const [status, setStatus] = useState({ + loading: false, + error: false, + }); - return status -} + return status; +}; diff --git a/src/index.js b/src/index.js index ed6cc3d..0e4db72 100644 --- a/src/index.js +++ b/src/index.js @@ -2,16 +2,14 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; import './App.css'; -import {DevSupport} from "@react-buddy/ide-toolbox"; -import {ComponentPreviews, useInitial} from "./dev"; +import { DevSupport } from '@react-buddy/ide-toolbox'; +import { ComponentPreviews, useInitial } from './dev'; const root = ReactDOM.createRoot(document.getElementById('root')); root.render( - - - - - + + + + + ); diff --git a/src/pages/Cart.jsx b/src/pages/Cart.jsx index aa037be..366a748 100644 --- a/src/pages/Cart.jsx +++ b/src/pages/Cart.jsx @@ -1,77 +1,67 @@ import React from 'react'; -import { - List, - ListItem, - ListItemText, - ListItemAvatar, - Avatar, - Button, - Typography, - Divider, -} from '@mui/material'; +import { List, ListItem, ListItemText, ListItemAvatar, Avatar, Button, Typography, Divider } from '@mui/material'; import DeleteIcon from '@mui/icons-material/Delete'; import { useNavigate } from 'react-router-dom'; function Cart({ cart, setCart }) { - const navigate = useNavigate(); + const navigate = useNavigate(); - const removeFromCart = (productId) => { - setCart(cart.filter(item => item.id !== productId)); - }; + const removeFromCart = productId => { + setCart(cart.filter(item => item.id !== productId)); + }; - const calculateTotal = () => { - return cart.reduce((total, item) => { - const price = typeof item.price === 'number' ? item.price : 0; - return total + price; - }, 0); - }; + const calculateTotal = () => { + return cart.reduce((total, item) => { + const price = typeof item.price === 'number' ? item.price : 0; + return total + price; + }, 0); + }; - const handleCheckout = () => { - navigate('/checkout'); - }; + const handleCheckout = () => { + navigate('/checkout'); + }; - return ( -
- - Shopping Cart - + return ( +
+ + Shopping Cart + - {cart.length === 0 ? ( - Your cart is empty. - ) : ( - <> - - {cart.map(item => ( - - removeFromCart(item.id)} startIcon={} color="error"> - Remove - - }> - - - - - - - - ))} - + {cart.length === 0 ? ( + Your cart is empty. + ) : ( + <> + + {cart.map(item => ( + + removeFromCart(item.id)} startIcon={} color="error"> + Remove + + } + > + + + + + + + + ))} + - - Total: ${calculateTotal().toFixed(2)} - + + Total: ${calculateTotal().toFixed(2)} + - - - )} -
- ); + + + )} +
+ ); } export default Cart; diff --git a/src/pages/Checkout.jsx b/src/pages/Checkout.jsx index c8b3df0..d7ccf4d 100644 --- a/src/pages/Checkout.jsx +++ b/src/pages/Checkout.jsx @@ -1,71 +1,70 @@ import React, { useState } from 'react'; import { useNavigate } from 'react-router-dom'; -import axios from "axios"; +import axios from 'axios'; import CheckoutForm from '../components/CheckoutForm'; import { Typography, CircularProgress } from '@mui/material'; function Checkout({ cartItems }) { - const navigate = useNavigate(); - const [orderCreated, setOrderCreated] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); - const [loading, setLoading] = useState(false); + const navigate = useNavigate(); + const [orderCreated, setOrderCreated] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const [loading, setLoading] = useState(false); - const handleSubmit = async (formData) => { - setLoading(true); + const handleSubmit = async formData => { + setLoading(true); - try { - // Simulate API call to create an order - This is a demo website so we do not have capacity to handle a real order yet - // const response = await axios.post('http://localhost:5000/api/checkout/create-order', { - // items: cartItems, - // name: formData.name, - // email: formData.email, - // shippingAddress: formData.shippingAddress, - // cardNumber: formData.cardNumber, - // cardName: formData.cardName, - // expiry: formData.expiry, - // cvc: formData.cvc, - // }); + try { + // Simulate API call to create an order - This is a demo website so we do not have capacity to handle a real order yet + // const response = await axios.post('http://localhost:5000/api/checkout/create-order', { + // items: cartItems, + // name: formData.name, + // email: formData.email, + // shippingAddress: formData.shippingAddress, + // cardNumber: formData.cardNumber, + // cardName: formData.cardName, + // expiry: formData.expiry, + // cvc: formData.cvc, + // }); - // Simulating success - setTimeout(() => { - setLoading(false); - setOrderCreated(true); - navigate('/order-success'); - }, 1); + // Simulating success + setTimeout(() => { + setLoading(false); + setOrderCreated(true); + navigate('/order-success'); + }, 1); - // Example of handling real response: - // if (response.status === 201) { - // setLoading(false); - // setOrderCreated(true); - // navigate('/order-success'); - // } - // else { - // setLoading(false); - // setErrorMessage(response.data.error || 'An error occurred'); - // } - } - catch (error) { - console.error('Error creating order:', error); - setLoading(false); - setErrorMessage('An error occurred'); - } - }; + // Example of handling real response: + // if (response.status === 201) { + // setLoading(false); + // setOrderCreated(true); + // navigate('/order-success'); + // } + // else { + // setLoading(false); + // setErrorMessage(response.data.error || 'An error occurred'); + // } + } catch (error) { + console.error('Error creating order:', error); + setLoading(false); + setErrorMessage('An error occurred'); + } + }; - return ( -
- {orderCreated ? ( - - Thank you for your order! You will be redirected shortly. - - ) : ( - <> - {loading && } - {errorMessage && {errorMessage}} - - - )} -
- ); + return ( +
+ {orderCreated ? ( + + Thank you for your order! You will be redirected shortly. + + ) : ( + <> + {loading && } + {errorMessage && {errorMessage}} + + + )} +
+ ); } export default Checkout; diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index b1fc32c..95b1034 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -1,143 +1,135 @@ import * as React from 'react'; -import { - Typography, - Grid, - Box, - Container, - Button, - CircularProgress, - Alert, - Paper, - styled, -} from '@mui/material'; +import { Typography, Grid, Box, Container, Button, CircularProgress, Alert, Paper, styled } from '@mui/material'; import Carousel from 'react-material-ui-carousel'; import ProductCard from '../components/ProductCard'; const StyledCarousel = styled(Carousel)({ - '& .Carousel-indicators-container': { - bottom: '20px', - '& button': { - backgroundColor: 'white', - opacity: 0.6, - '&:hover': { - opacity: 1, - }, - '&.selected': { - opacity: 1, - }, - }, + '& .Carousel-indicators-container': { + bottom: '20px', + '& button': { + backgroundColor: 'white', + opacity: 0.6, + '&:hover': { + opacity: 1, + }, + '&.selected': { + opacity: 1, + }, }, + }, }); // Replace these links with any image you'd like! function Home({ products, addToCart, error, loading }) { - const bannerImages = [ - { - url: 'https://thumbs.dreamstime.com/b/electronics-sale-banner-devices-online-shopping-delivery-basket-273419752.jpg', - title: 'Summer Sale - Up to 50% Off', - description: 'Shop now for the best deals on summer essentials!' - }, - { - url: 'https://magesolution.com/wp-content/uploads/2022/07/1_7u7eYPpkr5baaBOzYcdNHw.jpeg', - title: 'New Tech Gadgets', - description: 'Explore the latest in tech and gadgets.' - }, - { - url: 'https://images.pexels.com/photos/3768005/pexels-photo-3768005.jpeg?cs=srgb&dl=pexels-willoworld-3768005.jpg&fm=jpg', - title: 'Trending Fashion', - description: 'Discover the newest fashion trends for this season.' - }, - ]; - - const featuredProducts = products.slice(0, 3); // Display the first 3 products as featured - - return ( - - {/* Hero Section */} - - - {bannerImages.map((item, i) => ( - - {item.title} + const bannerImages = [ + { + url: 'https://thumbs.dreamstime.com/b/electronics-sale-banner-devices-online-shopping-delivery-basket-273419752.jpg', + title: 'Summer Sale - Up to 50% Off', + description: 'Shop now for the best deals on summer essentials!', + }, + { + url: 'https://magesolution.com/wp-content/uploads/2022/07/1_7u7eYPpkr5baaBOzYcdNHw.jpeg', + title: 'New Tech Gadgets', + description: 'Explore the latest in tech and gadgets.', + }, + { + url: 'https://images.pexels.com/photos/3768005/pexels-photo-3768005.jpeg?cs=srgb&dl=pexels-willoworld-3768005.jpg&fm=jpg', + title: 'Trending Fashion', + description: 'Discover the newest fashion trends for this season.', + }, + ]; - - - {item.title} - + const featuredProducts = products.slice(0, 3); // Display the first 3 products as featured - - {item.description} - - - - ))} - - + return ( + + {/* Hero Section */} + + + {bannerImages.map((item, i) => ( + + {item.title} - {/* Featured Products Section */} - - - Featured Products + + + {item.title} - {error ? ( - {error.message} - ) : loading ? ( - - - - ) : ( - - {featuredProducts.map((product) => ( - - - - ))} - - )} - - {/* Call to Action */} - - + + {item.description} + + - - ); + ))} + + + + {/* Featured Products Section */} + + + Featured Products + + {error ? ( + {error.message} + ) : loading ? ( + + + + ) : ( + + {featuredProducts.map(product => ( + + + + ))} + + )} + + + {/* Call to Action */} + + + + + ); } export default Home; diff --git a/src/pages/OrderSuccess.jsx b/src/pages/OrderSuccess.jsx index 27ce957..79a2f19 100644 --- a/src/pages/OrderSuccess.jsx +++ b/src/pages/OrderSuccess.jsx @@ -3,19 +3,17 @@ import { Typography, Box } from '@mui/material'; import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; function OrderSuccess() { - return ( - - + return ( + + - - Order Successful! - + + Order Successful! + - - Thank you for your purchase. Your order is being processed. - - - ); + Thank you for your purchase. Your order is being processed. + + ); } export default OrderSuccess; diff --git a/src/pages/ProductDetails.jsx b/src/pages/ProductDetails.jsx index 1d8bcb3..99eafbf 100644 --- a/src/pages/ProductDetails.jsx +++ b/src/pages/ProductDetails.jsx @@ -1,73 +1,77 @@ import React, { useState, useEffect } from 'react'; import { useParams, useNavigate } from 'react-router-dom'; import axios from 'axios'; -import { - Typography, Container, Grid, Paper, Button, CircularProgress -} from '@mui/material'; +import { Typography, Container, Grid, Paper, Button, CircularProgress } from '@mui/material'; function ProductDetails({ addToCart }) { - const { id } = useParams(); - const navigate = useNavigate(); - const [product, setProduct] = useState(null); - const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); + const { id } = useParams(); + const navigate = useNavigate(); + const [product, setProduct] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); - useEffect(() => { - async function fetchProduct() { - try { - const response = await axios.get(`http://localhost:5000/api/products/${id}`); - if (response.data) { - setProduct(response.data); - } - } - catch (error) { - setError(error); - } - finally { - setLoading(false); - } + useEffect(() => { + async function fetchProduct() { + try { + const response = await axios.get(`http://localhost:5000/api/products/${id}`); + if (response.data) { + setProduct(response.data); } - fetchProduct(); - }, [id, navigate]); - - const handleAddToCart = () => { - if (product) { - addToCart(product); - } - }; - - if (loading) { - return ( - - - - ); + } catch (error) { + setError(error); + } finally { + setLoading(false); + } } + fetchProduct(); + }, [id, navigate]); - if (error) { - return Error loading product details.; + const handleAddToCart = () => { + if (product) { + addToCart(product); } + }; + if (loading) { return ( - - - - - {product.name} - + + + + ); + } - - {product.name} - ${product.price} - {product.description} - - - - - + if (error) { + return ( + + Error loading product details. + ); + } + + return ( + + + + + {product.name} + + + + + {product.name} + + ${product.price} + + {product.description} + + + + + + + ); } export default ProductDetails; diff --git a/src/pages/Shop.jsx b/src/pages/Shop.jsx index a2f2b68..8a5e005 100644 --- a/src/pages/Shop.jsx +++ b/src/pages/Shop.jsx @@ -1,83 +1,62 @@ import * as React from 'react'; -import { - Grid, - Typography, - Container, - Box, - FormControl, - InputLabel, - Select, - MenuItem, - Pagination -} from '@mui/material'; +import { Grid, Typography, Container, Box, FormControl, InputLabel, Select, MenuItem, Pagination } from '@mui/material'; import ProductCard from '../components/ProductCard'; function Shop({ products, addToCart }) { - const [categoryFilter, setCategoryFilter] = React.useState('all'); - const [page, setPage] = React.useState(1); - const itemsPerPage = 6; - - const uniqueCategories = Array.from(new Set(products.map(product => product.category))); - - const filteredProducts = categoryFilter === 'all' - ? products - : products.filter(product => product.category === categoryFilter); - - const pageCount = Math.ceil(filteredProducts.length / itemsPerPage); - - const startIndex = (page - 1) * itemsPerPage; - const endIndex = startIndex + itemsPerPage; - const productsToShow = filteredProducts.slice(startIndex, endIndex); - - const handlePageChange = (event, value) => { - setPage(value); - }; - - const handleCategoryChange = (event) => { - setCategoryFilter(event.target.value); - setPage(1); - }; - - return ( - - - Shop - - - - Filter by Category - - - - - {productsToShow.map(product => ( - - - - ))} - - - - - - - ); + const [categoryFilter, setCategoryFilter] = React.useState('all'); + const [page, setPage] = React.useState(1); + const itemsPerPage = 6; + + const uniqueCategories = Array.from(new Set(products.map(product => product.category))); + + const filteredProducts = categoryFilter === 'all' ? products : products.filter(product => product.category === categoryFilter); + + const pageCount = Math.ceil(filteredProducts.length / itemsPerPage); + + const startIndex = (page - 1) * itemsPerPage; + const endIndex = startIndex + itemsPerPage; + const productsToShow = filteredProducts.slice(startIndex, endIndex); + + const handlePageChange = (event, value) => { + setPage(value); + }; + + const handleCategoryChange = event => { + setCategoryFilter(event.target.value); + setPage(1); + }; + + return ( + + + Shop + + + + Filter by Category + + + + + {productsToShow.map(product => ( + + + + ))} + + + + + + + ); } export default Shop;