├── .github └── workflows │ └── main.yml ├── .gitignore ├── .prettierrc ├── Procfile ├── README.md ├── backend ├── config │ └── db.js ├── controllers │ ├── orderController.js │ ├── productContoller.js │ └── userController.js ├── data │ ├── products.js │ └── users.js ├── middleware │ ├── authMiddleware.js │ └── errorMiddleware.js ├── models │ ├── orderModel.js │ ├── productModel.js │ └── userModel.js ├── routes │ ├── orderRoutes.js │ ├── productRoutes.js │ ├── uploadRoutes.js │ └── userRoutes.js ├── seeder.js ├── server.js └── utils │ └── generateToken.js ├── frontend ├── README.md ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ ├── homepage.png │ ├── images │ │ ├── airpods.jpg │ │ ├── alexa.jpg │ │ ├── camera.jpg │ │ ├── mouse.jpg │ │ ├── phone.jpg │ │ ├── playstation.jpg │ │ └── sample.jpg │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── manifest.json │ └── robots.txt ├── src │ ├── App.js │ ├── actions │ │ ├── cartActions.js │ │ ├── orderActions.js │ │ ├── productActions.js │ │ └── userActions.js │ ├── assets │ │ ├── cart.png │ │ ├── homepage.png │ │ ├── paymentPage.png │ │ ├── placeOrder.png │ │ ├── product.png │ │ ├── shipping.png │ │ └── signin.png │ ├── bootstrap.min.css │ ├── components │ │ ├── CheckoutSteps.js │ │ ├── Footer.js │ │ ├── FormContainer.js │ │ ├── Header.js │ │ ├── Loader.js │ │ ├── Message.js │ │ ├── Meta.js │ │ ├── Paginate.js │ │ ├── Product.js │ │ ├── ProductCarousel.js │ │ ├── Rating.js │ │ └── SearchBox.js │ ├── constants │ │ ├── cartConstants.js │ │ ├── orderConstants.js │ │ ├── productConstants.js │ │ └── userConstants.js │ ├── index.css │ ├── index.js │ ├── reducers │ │ ├── cartReducers.js │ │ ├── orderReducers.js │ │ ├── productReducers.js │ │ └── userReducers.js │ ├── reportWebVitals.js │ ├── screens │ │ ├── CartScreen.js │ │ ├── HomeScreen.js │ │ ├── LoginScreen.js │ │ ├── OrderListScreen.js │ │ ├── OrderScreen.js │ │ ├── PaymentScreen.js │ │ ├── PlaceOrderScreen.js │ │ ├── ProductEditScreen.js │ │ ├── ProductListScreen.js │ │ ├── ProductScreen.js │ │ ├── ProfileScreen.js │ │ ├── RegisterScreen.js │ │ ├── ShippingScreen.js │ │ ├── UserEditScreen.js │ │ └── UserListScreen.js │ └── store.js └── yarn.lock ├── package-lock.json ├── package.json ├── renovate.json └── uploads ├── file.txt └── image-1607604569956.jpeg /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: Build and Test 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build_and_test: 11 | runs-on: "macOS-latest" 12 | 13 | strategy: 14 | matrix: 15 | node: [14] 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v5 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v6 22 | with: 23 | node-version: ${{ matrix.node-version }} 24 | - name: Install project 25 | run: npm install 26 | - name: Build the project 27 | run: npm run build --if-present 28 | - name: Run tests 29 | run: npm test 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | node_modules 6 | node_modules/ 7 | /.pnp 8 | .pnp.js 9 | 10 | # testing 11 | /coverage 12 | 13 | # production 14 | /frontend/build 15 | 16 | # misc 17 | .DS_Store 18 | .env 19 | .eslintcache 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /Procfile: -------------------------------------------------------------------------------- 1 | web: node backend/server.js -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PROSHOP - MERN Stack eCommerce 2 | 3 | This is an eCommerce web application created with M(ongoDb)E(xpress)R(eact)N(ode) Stack. It has user authentication system, an admin user who can add new products in the app, check for order and payments and also choose which order has been delivered from the store. It is also connected to PayPal for payments. It is deployed in Heroku and below you can find the link to visit the app. 4 | 5 | ![Landing Page](frontend/src/assets/homepage.png) 6 | ![Sign In Page](frontend/src/assets/signin.png) 7 | ![Cart Page](frontend/src/assets/cart.png) 8 | ![Product Page](frontend/src/assets/product.png) 9 | ![Place Order Page](frontend/src/assets/shipping.png) 10 | ![Shipping Details Page](frontend/src/assets/placeOrder.png) 11 | ![Payment Page](frontend/src/assets/paymentPage.png) 12 | 13 | # Features 14 | 15 | - Full featured shopping cart 16 | - Product reviews and ratings 17 | - Top products carousel 18 | - Product pagination 19 | - Product search feature 20 | - User profile with orders 21 | - Admin product management 22 | - Admin user management 23 | - Admin Order details page 24 | - Mark orders as delivered option 25 | - Checkout process (shipping, payment method, etc) 26 | - PayPal / credit card integration 27 | - Database seeder (products & users) 28 | 29 | # Built with 30 | 31 | - NodeJs 32 | - Express 33 | - ReactJs 34 | - Redux 35 | - MongoDb 36 | - React Hooks 37 | - React Bootstrap 38 | - Axios 39 | - Bcrypt 40 | - JSON Web Tokens 41 | - Prettier 42 | - Eslint 43 | - React Helmet 44 | 45 | # Live Version 46 | 47 | https://proshopapp-by-mariosknl.herokuapp.com/ 48 | 49 | # AUTHOR 50 | 51 | - Github: [@mariosknl](https://github.com/mariosknl) 52 | - Twitter: [@mariosknl](https://twitter.com/MariosKnl) 53 | - Linkedln: [marios-kanellopoulos](https://www.linkedin.com/in/marios-kanellopoulos) 54 | - Portfolio: [marios-kanellopoulos](https://marioskanellopoulos.com/) 55 | 56 | # Show your support 57 | 58 | Give ⭐️ if you like this project! 59 | 60 | # Acknowledgments 61 | 62 | - Github: [@bradtraversy](https://github.com/bradtraversy) 63 | - Website: [traversy-media](http://traversymedia.com/) 64 | 65 | ### 🤝 Contributing 66 | 67 | Contributions, issues, and feature requests are welcome! 68 | Feel free to check the issues page. 69 | -------------------------------------------------------------------------------- /backend/config/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const connectDB = async () => { 4 | try { 5 | const conn = await mongoose.connect(process.env.MONGO_URI, { 6 | useUnifiedTopology: true, 7 | useNewUrlParser: true, 8 | useCreateIndex: true, 9 | }); 10 | 11 | console.log(`MongoDB Connected: ${conn.connection.host}`.cyan.underline); 12 | } catch (error) { 13 | console.error(`Error: ${error.message}`.red.underline.bold); 14 | process.exit(1); 15 | } 16 | }; 17 | 18 | export default connectDB; 19 | -------------------------------------------------------------------------------- /backend/controllers/orderController.js: -------------------------------------------------------------------------------- 1 | import asyncHandler from "express-async-handler"; 2 | 3 | import Order from "../models/orderModel.js"; 4 | 5 | // @desc Create new order 6 | // @route POST /api/orders 7 | // @access Private 8 | const addOrderItems = asyncHandler(async (req, res) => { 9 | const { 10 | orderItems, 11 | shippingAddress, 12 | paymentMethod, 13 | itemsPrice, 14 | taxPrice, 15 | shippingPrice, 16 | totalPrice, 17 | } = req.body; 18 | 19 | if (orderItems && orderItems.length === 0) { 20 | res.status(400); 21 | throw new Error("No order items"); 22 | return; 23 | } else { 24 | const order = new Order({ 25 | orderItems, 26 | user: req.user._id, 27 | shippingAddress, 28 | paymentMethod, 29 | itemsPrice, 30 | taxPrice, 31 | shippingPrice, 32 | totalPrice, 33 | }); 34 | 35 | const createdOrder = await order.save(); 36 | 37 | res.status(201).json(createdOrder); 38 | } 39 | }); 40 | 41 | // @desc Get order by ID 42 | // @route GET /api/orders/:id 43 | // @access Private 44 | const getOrderById = asyncHandler(async (req, res) => { 45 | const order = await Order.findById(req.params.id).populate( 46 | "user", 47 | "name email" 48 | ); 49 | 50 | if (order) { 51 | res.json(order); 52 | } else { 53 | res.status(404); 54 | throw new Error("Order not found"); 55 | } 56 | }); 57 | 58 | // @desc Update order to paid 59 | // @route GET /api/orders/:id/pay 60 | // @access Private 61 | const updateOrderToPaid = asyncHandler(async (req, res) => { 62 | const order = await Order.findById(req.params.id); 63 | 64 | if (order) { 65 | order.isPaid = true; 66 | order.paidAt = Date.now(); 67 | order.paymentResult = { 68 | id: req.body.id, 69 | status: req.body.status, 70 | update_time: req.body.update_time, 71 | email_address: req.body.payer.email_address, 72 | }; 73 | 74 | const updatedOrder = await order.save(); 75 | 76 | res.json(updatedOrder); 77 | } else { 78 | res.status(404); 79 | throw new Error("Order not found"); 80 | } 81 | }); 82 | 83 | // @desc Update order to delivered 84 | // @route GET /api/orders/:id/delivered 85 | // @access Private/Admin 86 | const updateOrderToDelivered = asyncHandler(async (req, res) => { 87 | const order = await Order.findById(req.params.id); 88 | 89 | if (order) { 90 | order.isDelivered = true; 91 | order.deliveredAt = Date.now(); 92 | 93 | const updatedOrder = await order.save(); 94 | 95 | res.json(updatedOrder); 96 | } else { 97 | res.status(404); 98 | throw new Error("Order not found"); 99 | } 100 | }); 101 | 102 | // @desc Get logged in user orders 103 | // @route GET /api/orders/myorders 104 | // @access Private 105 | const getMyOrders = asyncHandler(async (req, res) => { 106 | const orders = await Order.find({ user: req.user._id }); 107 | 108 | res.json(orders); 109 | }); 110 | 111 | // @desc Get all orders 112 | // @route GET /api/orders 113 | // @access Private/Admin 114 | const getOrders = asyncHandler(async (req, res) => { 115 | const orders = await Order.find({}).populate("user", "id name"); 116 | 117 | res.json(orders); 118 | }); 119 | 120 | export { 121 | addOrderItems, 122 | getOrderById, 123 | updateOrderToPaid, 124 | updateOrderToDelivered, 125 | getMyOrders, 126 | getOrders, 127 | }; 128 | -------------------------------------------------------------------------------- /backend/controllers/productContoller.js: -------------------------------------------------------------------------------- 1 | import asyncHandler from "express-async-handler"; 2 | 3 | import Product from "../models/productModel.js"; 4 | 5 | // @desc Fetch all Products 6 | // @route Get /api/products 7 | // @access Public 8 | const getProducts = asyncHandler(async (req, res) => { 9 | const pageSize = 3; 10 | const page = Number(req.query.pageNumber) || 1; 11 | 12 | const keyword = req.query.keyword 13 | ? { 14 | name: { 15 | $regex: req.query.keyword, 16 | $options: "i", 17 | }, 18 | } 19 | : {}; 20 | 21 | const count = await Product.countDocuments({ ...keyword }); 22 | const products = await Product.find({ ...keyword }) 23 | .limit(pageSize) 24 | .skip(pageSize * (page - 1)); 25 | 26 | res.json({ products, page, pages: Math.ceil(count / pageSize) }); 27 | }); 28 | 29 | // @desc Fetch Single Product 30 | // @route Get /api/product/:id 31 | // @access Public 32 | const getProductById = asyncHandler(async (req, res) => { 33 | const product = await Product.findById(req.params.id); 34 | 35 | if (product) { 36 | res.json(product); 37 | } else { 38 | res.status(404); 39 | throw new Error("Product not Found!"); 40 | } 41 | }); 42 | 43 | // @desc Delete a product 44 | // @route DELETE /api/product/:id 45 | // @access Private/Admin 46 | const deleteProduct = asyncHandler(async (req, res) => { 47 | const product = await Product.findById(req.params.id); 48 | 49 | if (product) { 50 | await product.remove(); 51 | res.json({ message: "Product removed" }); 52 | } else { 53 | res.status(404); 54 | throw new Error("Product not Found!"); 55 | } 56 | }); 57 | 58 | // @desc Create a product 59 | // @route POST /api/products 60 | // @access Private/Admin 61 | const createProduct = asyncHandler(async (req, res) => { 62 | const product = new Product({ 63 | name: "Sample name", 64 | price: 0, 65 | user: req.user._id, 66 | image: "/images/sample.jpg", 67 | brand: "Sample brand", 68 | category: "Sample Category", 69 | countInStock: 0, 70 | numReviews: 0, 71 | description: "Sample description", 72 | }); 73 | 74 | const createdProduct = await product.save(); 75 | res.status(201).json(createdProduct); 76 | }); 77 | 78 | // @desc Update a product 79 | // @route PUT /api/products/:id 80 | // @access Private/Admin 81 | const updateProduct = asyncHandler(async (req, res) => { 82 | const { 83 | name, 84 | price, 85 | description, 86 | image, 87 | brand, 88 | category, 89 | countInStock, 90 | } = req.body; 91 | 92 | const product = await Product.findById(req.params.id); 93 | 94 | if (product) { 95 | product.name = name; 96 | product.price = price; 97 | product.description = description; 98 | product.image = image; 99 | product.brand = brand; 100 | product.category = category; 101 | product.countInStock = countInStock; 102 | 103 | const updatedProduct = await product.save(); 104 | res.json(updatedProduct); 105 | } else { 106 | res.status(404); 107 | throw new Error("Product not Found"); 108 | } 109 | }); 110 | 111 | // @desc Create new review 112 | // @route POST /api/products/:id/reviews 113 | // @access Private 114 | const createProductReview = asyncHandler(async (req, res) => { 115 | const { rating, comment } = req.body; 116 | 117 | const product = await Product.findById(req.params.id); 118 | 119 | if (product) { 120 | const alreadyReviewed = product.reviews.find( 121 | (r) => r.user.toString() === req.user._id.toString() 122 | ); 123 | 124 | if (alreadyReviewed) { 125 | res.status(400); 126 | throw new Error("Product already reviewed"); 127 | } 128 | 129 | const review = { 130 | name: req.user.name, 131 | rating: Number(rating), 132 | comment, 133 | user: req.user._id, 134 | }; 135 | 136 | product.reviews.push(review); 137 | 138 | product.numReviews = product.reviews.length; 139 | 140 | product.rating = 141 | product.reviews.reduce((acc, item) => item.rating + acc, 0) / 142 | product.reviews.length; 143 | 144 | await product.save(); 145 | res.status(201).json({ message: "Review added" }); 146 | } else { 147 | res.status(404); 148 | throw new Error("Product not Found"); 149 | } 150 | }); 151 | 152 | // @desc Get Top rated products 153 | // @route GET /api/products/top 154 | // @access Public 155 | const getTopProducts = asyncHandler(async (req, res) => { 156 | const products = await Product.find({}).sort({ rating: -1 }).limit(3); 157 | 158 | res.json(products); 159 | }); 160 | 161 | export { 162 | getProducts, 163 | getProductById, 164 | createProduct, 165 | updateProduct, 166 | deleteProduct, 167 | createProductReview, 168 | getTopProducts, 169 | }; 170 | -------------------------------------------------------------------------------- /backend/controllers/userController.js: -------------------------------------------------------------------------------- 1 | import asyncHandler from "express-async-handler"; 2 | 3 | import generateToken from "../utils/generateToken.js"; 4 | import User from "../models/userModel.js"; 5 | 6 | // @desc Auth user & get token 7 | // @route Get /api/users/login 8 | // @access Public 9 | const authUser = asyncHandler(async (req, res) => { 10 | const { email, password } = req.body; 11 | 12 | const user = await User.findOne({ email }); 13 | 14 | if (user && (await user.matchPassword(password))) { 15 | res.json({ 16 | _id: user._id, 17 | name: user.name, 18 | email: user.email, 19 | isAdmin: user.isAdmin, 20 | token: generateToken(user._id), 21 | }); 22 | } else { 23 | res.status(401); 24 | throw new Error("Invalid email or password"); 25 | } 26 | }); 27 | 28 | // @desc Register a new user 29 | // @route Get /api/users 30 | // @access Public 31 | const registerUser = asyncHandler(async (req, res) => { 32 | const { name, email, password } = req.body; 33 | 34 | const userExists = await User.findOne({ email }); 35 | 36 | if (userExists) { 37 | res.status(400); 38 | throw new Error("User already exists"); 39 | } 40 | 41 | const user = await User.create({ 42 | name, 43 | email, 44 | password, 45 | }); 46 | 47 | if (user) { 48 | res.status(201).json({ 49 | _id: user._id, 50 | name: user.name, 51 | email: user.email, 52 | isAdmin: user.isAdmin, 53 | token: generateToken(user._id), 54 | }); 55 | } else { 56 | res.status(400); 57 | throw new Error("Invalid user data"); 58 | } 59 | }); 60 | 61 | // @desc Get user profile 62 | // @route GET /api/users/profile 63 | // @access Private 64 | const getUserProfile = asyncHandler(async (req, res) => { 65 | const user = await User.findById(req.user._id); 66 | 67 | if (user) { 68 | res.json({ 69 | _id: user._id, 70 | name: user.name, 71 | email: user.email, 72 | isAdmin: user.isAdmin, 73 | }); 74 | } else { 75 | res.status(404); 76 | throw new Error("User not Found"); 77 | } 78 | }); 79 | 80 | // @desc Update user profile 81 | // @route Put /api/users/profile 82 | // @access Private 83 | const updateUserProfile = asyncHandler(async (req, res) => { 84 | const user = await User.findById(req.user._id); 85 | 86 | if (user) { 87 | user.name = req.body.name || user.name; 88 | user.email = req.body.email || user.email; 89 | if (req.body.password) { 90 | user.password = req.body.password; 91 | } 92 | 93 | const updatedUser = await user.save(); 94 | 95 | res.json({ 96 | _id: updatedUser._id, 97 | name: updatedUser.name, 98 | email: updatedUser.email, 99 | isAdmin: updatedUser.isAdmin, 100 | token: generateToken(updatedUser._id), 101 | }); 102 | } else { 103 | res.status(404); 104 | throw new Error("User not Found"); 105 | } 106 | }); 107 | 108 | // @desc Get all users 109 | // @route GET /api/users 110 | // @access Private/Admin 111 | const getUsers = asyncHandler(async (req, res) => { 112 | const users = await User.find({}); 113 | res.json(users); 114 | }); 115 | 116 | // @desc Delete user 117 | // @route DELETE /api/users/:id 118 | // @access Private/Admin 119 | const deleteUser = asyncHandler(async (req, res) => { 120 | const user = await User.findById(req.params.id); 121 | 122 | if (user) { 123 | await user.remove(); 124 | res.json({ message: "User removed" }); 125 | } else { 126 | res.status(404); 127 | throw new Error("User not found"); 128 | } 129 | }); 130 | 131 | // @desc Update user 132 | // @route Put /api/users/:id 133 | // @access Private/Admin 134 | const updateUser = asyncHandler(async (req, res) => { 135 | const user = await User.findById(req.params.id); 136 | 137 | if (user) { 138 | user.name = req.body.name || user.name; 139 | user.email = req.body.email || user.email; 140 | user.isAdmin = req.body.isAdmin; 141 | 142 | const updatedUser = await user.save(); 143 | 144 | res.json({ 145 | _id: updatedUser._id, 146 | name: updatedUser.name, 147 | email: updatedUser.email, 148 | isAdmin: updatedUser.isAdmin, 149 | }); 150 | } else { 151 | res.status(404); 152 | throw new Error("User not Found"); 153 | } 154 | }); 155 | 156 | // @desc Get user by ID 157 | // @route GET /api/users/:id 158 | // @access Private/Admin 159 | const getUserById = asyncHandler(async (req, res) => { 160 | const user = await User.findById(req.params.id).select("-password"); 161 | if (user) { 162 | res.json(user); 163 | } else { 164 | res.status(404); 165 | throw new Error("User not found"); 166 | } 167 | }); 168 | 169 | export { 170 | authUser, 171 | getUserProfile, 172 | registerUser, 173 | updateUserProfile, 174 | getUsers, 175 | deleteUser, 176 | getUserById, 177 | updateUser, 178 | }; 179 | -------------------------------------------------------------------------------- /backend/data/products.js: -------------------------------------------------------------------------------- 1 | const products = [ 2 | { 3 | name: "Airpods Wireless Bluetooth Headphones", 4 | image: "/images/airpods.jpg", 5 | description: 6 | "Bluetooth technology lets you connect it with compatible devices wirelessly High-quality AAC audio offers immersive listening experience Built-in microphone allows you to take calls while working", 7 | brand: "Apple", 8 | category: "Electronics", 9 | price: 89.99, 10 | countInStock: 10, 11 | rating: 4.5, 12 | numReviews: 12, 13 | }, 14 | { 15 | name: "iPhone 11 Pro 256GB Memory", 16 | image: "/images/phone.jpg", 17 | description: 18 | "Introducing the iPhone 11 Pro. A transformative triple-camera system that adds tons of capability without complexity. An unprecedented leap in battery life", 19 | brand: "Apple", 20 | category: "Electronics", 21 | price: 599.99, 22 | countInStock: 7, 23 | rating: 4.0, 24 | numReviews: 8, 25 | }, 26 | { 27 | name: "Cannon EOS 80D DSLR Camera", 28 | image: "/images/camera.jpg", 29 | description: 30 | "Characterized by versatile imaging specs, the Canon EOS 80D further clarifies itself using a pair of robust focusing systems and an intuitive design", 31 | brand: "Cannon", 32 | category: "Electronics", 33 | price: 929.99, 34 | countInStock: 5, 35 | rating: 3, 36 | numReviews: 12, 37 | }, 38 | { 39 | name: "Sony Playstation 4 Pro White Version", 40 | image: "/images/playstation.jpg", 41 | description: 42 | "The ultimate home entertainment center starts with PlayStation. Whether you are into gaming, HD movies, television, music", 43 | brand: "Sony", 44 | category: "Electronics", 45 | price: 399.99, 46 | countInStock: 11, 47 | rating: 5, 48 | numReviews: 12, 49 | }, 50 | { 51 | name: "Logitech G-Series Gaming Mouse", 52 | image: "/images/mouse.jpg", 53 | description: 54 | "Get a better handle on your games with this Logitech LIGHTSYNC gaming mouse. The six programmable buttons allow customization for a smooth playing experience", 55 | brand: "Logitech", 56 | category: "Electronics", 57 | price: 49.99, 58 | countInStock: 7, 59 | rating: 3.5, 60 | numReviews: 10, 61 | }, 62 | { 63 | name: "Amazon Echo Dot 3rd Generation", 64 | image: "/images/alexa.jpg", 65 | description: 66 | "Meet Echo Dot - Our most popular smart speaker with a fabric design. It is our most compact smart speaker that fits perfectly into small space", 67 | brand: "Amazon", 68 | category: "Electronics", 69 | price: 29.99, 70 | countInStock: 0, 71 | rating: 4, 72 | numReviews: 12, 73 | }, 74 | ]; 75 | 76 | export default products; 77 | -------------------------------------------------------------------------------- /backend/data/users.js: -------------------------------------------------------------------------------- 1 | import bcrypt from "bcryptjs"; 2 | 3 | const users = [ 4 | { 5 | name: "Admin User", 6 | email: "admin@example.com", 7 | password: bcrypt.hashSync("123456", 10), 8 | isAdmin: true, 9 | }, 10 | { 11 | name: "John Doe", 12 | email: "john@example.com", 13 | password: bcrypt.hashSync("123456", 10), 14 | }, 15 | { 16 | name: "Jane Doe", 17 | email: "jane@example.com", 18 | password: bcrypt.hashSync("123456", 10), 19 | }, 20 | ]; 21 | 22 | export default users; 23 | -------------------------------------------------------------------------------- /backend/middleware/authMiddleware.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | import asyncHandler from "express-async-handler"; 3 | import User from "../models/userModel.js"; 4 | 5 | const protect = asyncHandler(async (req, res, next) => { 6 | let token; 7 | 8 | if ( 9 | req.headers.authorization && 10 | req.headers.authorization.startsWith("Bearer") 11 | ) { 12 | try { 13 | token = req.headers.authorization.split(" ")[1]; 14 | 15 | const decoded = jwt.verify(token, process.env.JWT_SECRET); 16 | 17 | req.user = await User.findById(decoded.id).select("-password"); 18 | 19 | next(); 20 | } catch (error) { 21 | console.error(error); 22 | res.status(401); 23 | throw new Error("Not authorized, token failed"); 24 | } 25 | } 26 | 27 | if (!token) { 28 | res.status(401); 29 | throw new Error("Not authorized, no token"); 30 | } 31 | }); 32 | 33 | const admin = (req, res, next) => { 34 | if (req.user && req.user.isAdmin) { 35 | next(); 36 | } else { 37 | res.status(401); 38 | throw new Error("Not authorized as an admin"); 39 | } 40 | }; 41 | 42 | export { protect, admin }; 43 | -------------------------------------------------------------------------------- /backend/middleware/errorMiddleware.js: -------------------------------------------------------------------------------- 1 | const notFound = (req, res, next) => { 2 | const error = new Error(`Not Found - ${req.originalUrl}`); 3 | res.status(404); 4 | next(err); 5 | }; 6 | 7 | const errorHandler = (err, req, res, next) => { 8 | const statusCode = res.statusCode === 200 ? 500 : res.statusCode; 9 | res.status(statusCode); 10 | res.json({ 11 | message: err.message, 12 | stack: process.env.NODE_ENV === "production" ? null : err.stack, 13 | }); 14 | }; 15 | 16 | export { notFound, errorHandler }; 17 | -------------------------------------------------------------------------------- /backend/models/orderModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const orderSchema = mongoose.Schema( 4 | { 5 | user: { 6 | type: mongoose.Schema.Types.ObjectId, 7 | required: true, 8 | ref: "User", 9 | }, 10 | orderItems: [ 11 | { 12 | name: { type: String, required: true }, 13 | qty: { type: Number, required: true }, 14 | image: { type: String, required: true }, 15 | price: { type: Number, required: true }, 16 | product: { 17 | type: mongoose.Schema.Types.ObjectId, 18 | required: true, 19 | ref: "Product", 20 | }, 21 | }, 22 | ], 23 | shippingAddress: { 24 | address: { type: String, required: true }, 25 | city: { type: String, required: true }, 26 | postalCode: { type: String, required: true }, 27 | country: { type: String, required: true }, 28 | }, 29 | paymentMethod: { 30 | type: String, 31 | required: true, 32 | }, 33 | paymentResult: { 34 | id: { type: String }, 35 | status: { type: String }, 36 | update_time: { type: String }, 37 | email_address: { type: String }, 38 | }, 39 | taxPrice: { 40 | type: Number, 41 | required: true, 42 | default: 0.0, 43 | }, 44 | shippingPrice: { 45 | type: Number, 46 | required: true, 47 | default: 0.0, 48 | }, 49 | totalPrice: { 50 | type: Number, 51 | required: true, 52 | default: 0.0, 53 | }, 54 | isPaid: { 55 | type: Boolean, 56 | required: true, 57 | default: false, 58 | }, 59 | paidAt: { 60 | type: Date, 61 | }, 62 | isDelivered: { 63 | type: Boolean, 64 | required: true, 65 | default: false, 66 | }, 67 | deliveredAt: { 68 | type: Date, 69 | }, 70 | }, 71 | { 72 | timestamps: true, 73 | } 74 | ); 75 | 76 | const Order = mongoose.model("Order", orderSchema); 77 | 78 | export default Order; 79 | -------------------------------------------------------------------------------- /backend/models/productModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const reviewSchema = mongoose.Schema( 4 | { 5 | name: { type: String, required: true }, 6 | rating: { type: Number, required: true }, 7 | comment: { type: String, required: true }, 8 | user: { 9 | type: mongoose.Schema.Types.ObjectId, 10 | required: true, 11 | ref: "User", 12 | }, 13 | }, 14 | { 15 | timestamps: true, 16 | } 17 | ); 18 | 19 | const productSchema = mongoose.Schema( 20 | { 21 | user: { 22 | type: mongoose.Schema.Types.ObjectId, 23 | required: true, 24 | ref: "User", 25 | }, 26 | name: { 27 | type: String, 28 | required: true, 29 | }, 30 | image: { 31 | type: String, 32 | required: true, 33 | }, 34 | brand: { 35 | type: String, 36 | required: true, 37 | }, 38 | category: { 39 | type: String, 40 | required: true, 41 | }, 42 | description: { 43 | type: String, 44 | required: true, 45 | }, 46 | reviews: [reviewSchema], 47 | rating: { 48 | type: Number, 49 | required: true, 50 | default: 0, 51 | }, 52 | numReviews: { 53 | type: Number, 54 | required: true, 55 | default: 0, 56 | }, 57 | price: { 58 | type: Number, 59 | required: true, 60 | default: 0, 61 | }, 62 | countInStock: { 63 | type: Number, 64 | required: true, 65 | default: 0, 66 | }, 67 | }, 68 | { 69 | timestamps: true, 70 | } 71 | ); 72 | 73 | const Product = mongoose.model("Product", productSchema); 74 | 75 | export default Product; 76 | -------------------------------------------------------------------------------- /backend/models/userModel.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import bcrypt from "bcryptjs"; 3 | 4 | const userSchema = mongoose.Schema( 5 | { 6 | name: { 7 | type: String, 8 | required: true, 9 | }, 10 | email: { 11 | type: String, 12 | required: true, 13 | unique: true, 14 | }, 15 | password: { 16 | type: String, 17 | required: true, 18 | }, 19 | isAdmin: { 20 | type: Boolean, 21 | required: true, 22 | default: false, 23 | }, 24 | }, 25 | { 26 | timestamps: true, 27 | } 28 | ); 29 | 30 | userSchema.methods.matchPassword = async function (enteredPassword) { 31 | return await bcrypt.compare(enteredPassword, this.password); 32 | }; 33 | 34 | userSchema.pre("save", async function (next) { 35 | if (!this.isModified("password")) { 36 | next(); 37 | } 38 | 39 | const salt = await bcrypt.genSalt(10); 40 | this.password = await bcrypt.hash(this.password, salt); 41 | }); 42 | 43 | const User = mongoose.model("User", userSchema); 44 | 45 | export default User; 46 | -------------------------------------------------------------------------------- /backend/routes/orderRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | 4 | import { 5 | addOrderItems, 6 | getOrderById, 7 | updateOrderToPaid, 8 | updateOrderToDelivered, 9 | getMyOrders, 10 | getOrders, 11 | } from "../controllers/orderController.js"; 12 | import { protect, admin } from "../middleware/authMiddleware.js"; 13 | 14 | router.route("/").post(protect, addOrderItems).get(protect, admin, getOrders); 15 | router.route("/myorders").get(protect, getMyOrders); 16 | router.route("/:id").get(protect, getOrderById); 17 | router.route("/:id/pay").put(protect, updateOrderToPaid); 18 | router.route("/:id/deliver").put(protect, admin, updateOrderToDelivered); 19 | 20 | export default router; 21 | -------------------------------------------------------------------------------- /backend/routes/productRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | 4 | import { 5 | getProductById, 6 | getProducts, 7 | deleteProduct, 8 | updateProduct, 9 | createProduct, 10 | createProductReview, 11 | getTopProducts, 12 | } from "../controllers/productContoller.js"; 13 | import { protect, admin } from "../middleware/authMiddleware.js"; 14 | 15 | router.route("/").get(getProducts).post(protect, admin, createProduct); 16 | router.route("/:id/reviews").post(protect, createProductReview); 17 | router.get("/top", getTopProducts); 18 | router 19 | .route("/:id") 20 | .get(getProductById) 21 | .delete(protect, admin, deleteProduct) 22 | .put(protect, admin, updateProduct); 23 | 24 | export default router; 25 | -------------------------------------------------------------------------------- /backend/routes/uploadRoutes.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import express from "express"; 3 | import multer from "multer"; 4 | 5 | const router = express.Router(); 6 | 7 | const storage = multer.diskStorage({ 8 | destination(req, file, cb) { 9 | cb(null, "uploads/"); 10 | }, 11 | filename(req, file, cb) { 12 | cb( 13 | null, 14 | `${file.fieldname}-${Date.now()}${path.extname(file.originalname)}` 15 | ); 16 | }, 17 | }); 18 | 19 | function checkFileType(file, cb) { 20 | const filetypes = /jpg|jpeg|png/; 21 | const extname = filetypes.test(path.extname(file.originalname).toLowerCase()); 22 | const mimetype = filetypes.test(file.mimetype); 23 | 24 | if (extname && mimetype) { 25 | return cb(null, true); 26 | } else { 27 | cb("Images only"); 28 | } 29 | } 30 | 31 | const upload = multer({ 32 | storage, 33 | fileFilter: function (req, file, cb) { 34 | checkFileType(file, cb); 35 | }, 36 | }); 37 | 38 | router.post("/", upload.single("image"), (req, res) => { 39 | res.send(`/${req.file.path}`); 40 | }); 41 | 42 | export default router; 43 | -------------------------------------------------------------------------------- /backend/routes/userRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | const router = express.Router(); 3 | 4 | import { 5 | authUser, 6 | getUserProfile, 7 | registerUser, 8 | updateUserProfile, 9 | getUsers, 10 | deleteUser, 11 | getUserById, 12 | updateUser, 13 | } from "../controllers/userController.js"; 14 | import { protect, admin } from "../middleware/authMiddleware.js"; 15 | 16 | router.route("/").post(registerUser).get(protect, admin, getUsers); 17 | router.post("/login", authUser); 18 | router 19 | .route("/profile") 20 | .get(protect, getUserProfile) 21 | .put(protect, updateUserProfile); 22 | 23 | router 24 | .route("/:id") 25 | .delete(protect, admin, deleteUser) 26 | .get(protect, admin, getUserById) 27 | .put(protect, admin, updateUser); 28 | export default router; 29 | -------------------------------------------------------------------------------- /backend/seeder.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | import dotenv from "dotenv"; 3 | import colors from "colors"; 4 | 5 | import users from "./data/users.js"; 6 | import products from "./data/products.js"; 7 | import User from "./models/userModel.js"; 8 | import Product from "./models/productModel.js"; 9 | import Order from "./models/orderModel.js"; 10 | import connectDB from "./config/db.js"; 11 | 12 | dotenv.config(); 13 | 14 | connectDB(); 15 | 16 | const importData = async () => { 17 | try { 18 | await Order.deleteMany(); 19 | await Product.deleteMany(); 20 | await User.deleteMany(); 21 | 22 | const createdUsers = await User.insertMany(users); 23 | 24 | const adminUser = createdUsers[0]._id; 25 | 26 | const sampleProducts = products.map((product) => { 27 | return { 28 | ...product, 29 | user: adminUser, 30 | }; 31 | }); 32 | 33 | await Product.insertMany(sampleProducts); 34 | 35 | console.log("Data Imported!".green.inverse); 36 | process.exit(); 37 | } catch (error) { 38 | console.error(`${error}`.red.inverse); 39 | process.exit(1); 40 | } 41 | }; 42 | 43 | const destroyData = async () => { 44 | try { 45 | await Order.deleteMany(); 46 | await Product.deleteMany(); 47 | await User.deleteMany(); 48 | 49 | console.log("Data Destroyed!".red.inverse); 50 | process.exit(); 51 | } catch (error) { 52 | console.error(`${error}`.red.inverse); 53 | process.exit(1); 54 | } 55 | }; 56 | 57 | if (process.argv[2] === "-d") { 58 | destroyData(); 59 | } else { 60 | importData(); 61 | } 62 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | import path from "path"; 2 | import express from "express"; 3 | import dotenv from "dotenv"; 4 | import colors from "colors"; 5 | import morgan from "morgan"; 6 | import { notFound, errorHandler } from "./middleware/errorMiddleware.js"; 7 | 8 | import connectDB from "./config/db.js"; 9 | import productRoutes from "./routes/productRoutes.js"; 10 | import userRoutes from "./routes/userRoutes.js"; 11 | import orderRoutes from "./routes/orderRoutes.js"; 12 | import uploadRoutes from "./routes/uploadRoutes.js"; 13 | 14 | dotenv.config(); 15 | 16 | connectDB(); 17 | 18 | const app = express(); 19 | 20 | if (process.env.NODE_ENV === "development") { 21 | app.use(morgan("dev")); 22 | } 23 | 24 | app.use(express.json()); 25 | 26 | app.use("/api/products", productRoutes); 27 | app.use("/api/users", userRoutes); 28 | app.use("/api/orders", orderRoutes); 29 | app.use("/api/upload", uploadRoutes); 30 | 31 | app.get("/api/config/paypal", (req, res) => 32 | res.send(process.env.PAYPAL_CLIENT_ID) 33 | ); 34 | 35 | const __dirname = path.resolve(); 36 | app.use("/uploads", express.static(path.join(__dirname, "/uploads"))); 37 | 38 | if (process.env.NODE_ENV === "production") { 39 | app.use(express.static(path.join(__dirname, "/frontend/build"))); 40 | 41 | app.get("*", (req, res) => 42 | res.sendFile(path.resolve(__dirname, "frontend", "build", "index.html")) 43 | ); 44 | } else { 45 | app.get("/", (req, res) => { 46 | res.send("API is running..."); 47 | }); 48 | } 49 | 50 | app.use(notFound); 51 | 52 | app.use(errorHandler); 53 | 54 | const PORT = process.env.PORT || 5000; 55 | 56 | app.listen( 57 | PORT, 58 | console.log( 59 | `Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow.bold 60 | ) 61 | ); 62 | -------------------------------------------------------------------------------- /backend/utils/generateToken.js: -------------------------------------------------------------------------------- 1 | import jwt from "jsonwebtoken"; 2 | 3 | const generateToken = (id) => { 4 | return jwt.sign({ id }, process.env.JWT_SECRET, { 5 | expiresIn: "30d", 6 | }); 7 | }; 8 | 9 | export default generateToken; 10 | -------------------------------------------------------------------------------- /frontend/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | 48 | ### Code Splitting 49 | 50 | This section has moved here: [https://facebook.github.io/create-react-app/docs/code-splitting](https://facebook.github.io/create-react-app/docs/code-splitting) 51 | 52 | ### Analyzing the Bundle Size 53 | 54 | This section has moved here: [https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size](https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size) 55 | 56 | ### Making a Progressive Web App 57 | 58 | This section has moved here: [https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app](https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app) 59 | 60 | ### Advanced Configuration 61 | 62 | This section has moved here: [https://facebook.github.io/create-react-app/docs/advanced-configuration](https://facebook.github.io/create-react-app/docs/advanced-configuration) 63 | 64 | ### Deployment 65 | 66 | This section has moved here: [https://facebook.github.io/create-react-app/docs/deployment](https://facebook.github.io/create-react-app/docs/deployment) 67 | 68 | ### `yarn build` fails to minify 69 | 70 | This section has moved here: [https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify](https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify) 71 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "proxy": "http://127.0.0.1:5000", 4 | "version": "0.1.0", 5 | "private": true, 6 | "dependencies": { 7 | "@testing-library/jest-dom": "6.5.0", 8 | "@testing-library/react": "14.3.1", 9 | "react": "17.0.2", 10 | "react-bootstrap": "2.10.5", 11 | "react-dom": "17.0.2", 12 | "react-helmet": "6.1.0", 13 | "react-paypal-button-v2": "2.6.3", 14 | "react-redux": "9.0.4", 15 | "react-router-bootstrap": "0.26.3", 16 | "react-router-dom": "6.27.0", 17 | "react-scripts": "5.0.1", 18 | "redux": "5.0.1", 19 | "@redux-devtools/extension": "3.3.0", 20 | "redux-thunk": "3.1.0", 21 | "web-vitals": "3.5.1" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test", 27 | "eject": "react-scripts eject" 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "browserslist": { 36 | "production": [ 37 | ">0.2%", 38 | "not dead", 39 | "not op_mini all" 40 | ], 41 | "development": [ 42 | "last 1 chrome version", 43 | "last 1 firefox version", 44 | "last 1 safari version" 45 | ] 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/homepage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/homepage.png -------------------------------------------------------------------------------- /frontend/public/images/airpods.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/images/airpods.jpg -------------------------------------------------------------------------------- /frontend/public/images/alexa.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/images/alexa.jpg -------------------------------------------------------------------------------- /frontend/public/images/camera.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/images/camera.jpg -------------------------------------------------------------------------------- /frontend/public/images/mouse.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/images/mouse.jpg -------------------------------------------------------------------------------- /frontend/public/images/phone.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/images/phone.jpg -------------------------------------------------------------------------------- /frontend/public/images/playstation.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/images/playstation.jpg -------------------------------------------------------------------------------- /frontend/public/images/sample.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/images/sample.jpg -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 20 | Welcome to ProShop 21 | 22 | 23 | 24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /frontend/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/logo192.png -------------------------------------------------------------------------------- /frontend/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mariosknl/ProShop/97c26df85e134c7beccac1e892bbd57b974f4d34/frontend/public/logo512.png -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter as Router, Route } from "react-router-dom"; 3 | 4 | import { Container } from "react-bootstrap"; 5 | import Header from "./components/Header"; 6 | import Footer from "./components/Footer"; 7 | import HomeScreen from "./screens/HomeScreen"; 8 | import ProductScreen from "./screens/ProductScreen"; 9 | import CartScreen from "./screens/CartScreen"; 10 | import LoginScreen from "./screens/LoginScreen"; 11 | import RegisterScreen from "./screens/RegisterScreen"; 12 | import ProfileScreen from "./screens/ProfileScreen"; 13 | import ShippingScreen from "./screens/ShippingScreen"; 14 | import PaymentScreen from "./screens/PaymentScreen"; 15 | import PlaceOrderScreen from "./screens/PlaceOrderScreen"; 16 | import OrderScreen from "./screens/OrderScreen"; 17 | import UserListScreen from "./screens/UserListScreen"; 18 | import UserEditScreen from "./screens/UserEditScreen"; 19 | import ProductListScreen from "./screens/ProductListScreen"; 20 | import ProductEditScreen from "./screens/ProductEditScreen"; 21 | import OrderListScreen from "./screens/OrderListScreen"; 22 | 23 | const App = () => { 24 | return ( 25 | 26 |
27 |
28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 46 | 51 | 52 | 53 | 54 | 59 | 60 | 61 |
62 |