diff --git a/README.md b/README.md index 43ff5232..6e8d398f 100644 --- a/README.md +++ b/README.md @@ -1,58 +1,13 @@ -# Amazona ECommerce Website -![amazona](/template/images/amazona.jpg) +## How to run -## PLEASE DO NOT SEND PULL REQUEST -## ALL PRs WILL BE REJECTED UNTIL DECEMBER 2020 - -# React & Node Tutorial - Full ECommerce in 9 Hours [2021] - -Welcome to my React and Node tutorial to build a fully-functional e-commerce website exactly like amazon. Open your code editor and follow me for the next hours to build an e-commerce website using MERN stack (MongoDB, ExpressJS, React and Node.JS). - -## Demo Website - -- 👉 Heroku : [https://newamazona-final.herokuapp.com](https://newamazona-final.herokuapp.com) -- 👉 AWS : [https://amazona.webacademy.pro](https://amazona.webacademy.pro) - - -## You Will Learn - -- HTML5 and CSS3: Semantic Elements, CSS Grid, Flexbox -- React: Components, Props, Events, Hooks, Router, Axios -- Redux: Store, Reducers, Actions -- Node & Express: Web API, Body Parser, File Upload, JWT -- MongoDB: Mongoose, Aggregation -- Development: ESLint, Babel, Git, Github, -- Deployment: Heroku -- Watch React & Node Tutorial - -## Run Locally - -### 1. Clone repo - -``` -$ git clone git@github.com:basir/amazona.git -$ cd amazona -``` - -### 2. Setup MongoDB - -- Local MongoDB - - Install it from [here](https://www.mongodb.com/try/download/community) - - Create .env file in root folder - - Set MONGODB_URL=mongodb://localhost/amazona -- Atlas Cloud MongoDB - - Create database at [https://cloud.mongodb.com](https://cloud.mongodb.com) - - Create .env file in root folder - - Set MONGODB_URL=mongodb+srv://your-db-connection - -### 3. Run Backend +### 1. Run Backend ``` $ npm install $ npm start ``` -### 4. Run Frontend +### 2. Run Frontend ``` # open new terminal @@ -61,278 +16,7 @@ $ npm install $ npm start ``` -### 5. Seed Users and Products - -- Run this on chrome: http://localhost:5000/api/users -- It returns admin email and password -- Run this on chrome: http://localhost:5000/api/products -- It creates 6 sample products - -### 6. Admin Login +### 3. Admin Login - Run http://localhost:3000/signin - Enter admin email and password and click signin - -## Support - -- Q/A: https://webacademy.pro/amazona -- Contact Instructor: [Basir](mailto:basir.jafarzadeh@gmail.com) - -## Lessons - -1. Introduction to this course - 1. what you will build - 2. what you will learn - 3. who are audiences -2. Install Tools - 1. Code Editor - 2. Web Browser - 3. VS Code Extension -3. Website Template - 1. Create amazona folder - 2. create template folder - 3. create index.html - 4. add default HTML code - 5. link to style.css - 6. create header, main and footer - 7. style elements -4. Display Products - 1. create products div - 2. add product attributes - 3. add link, image, name and price -5. Create React App - 1. npx create-react-app frontend - 2. npm start - 3. Remove unused files - 4. copy index.html content to App.js - 5. copy style.css content to index.css - 6. replace class with className -6. Share Code On Github - 1. Initialize git repository - 2. Commit changes - 3. Create github account - 4. Create repo on github - 5. connect local repo to github repo - 6. push changes to github -7. Create Rating and Product Component - 1. create components/Rating.js - 2. create div.rating - 3. style div.rating, span and last span - 4. Create Product component - 5. Use Rating component -8. Build Product Screen - 1. Install react-router-dom - 2. Use BrowserRouter and Route for Home Screen - 3. Create HomeScreen.js - 4. Add product list code there - 5. Create ProductScreen.js - 6. Add new Route from product details to App.js - 7. Create 3 columns for product image, info and action -9. Create Node.JS Server - 1. run npm init in root folder - 2. Update package.json set type: module - 3. Add .js to imports - 4. npm install express - 5. create server.js - 6. add start command as node backend/server.js - 7. require express - 8. create route for / return backend is ready. - 9. move products.js from frontend to backend - 10. create route for /api/products - 11. return products - 12. run npm start -10. Load Products From Backend - 1. edit HomeScreen.js - 2. define products, loading and error. - 3. create useEffect - 4. define async fetchData and call it - 5. install axios - 6. get data from /api/products - 7. show them in the list - 8. create Loading Component - 9. create Message Box Component - 10. use them in HomeScreen -11. Install ESlint For Code Linting - 1. install VSCode eslint extension - 2. npm install -D eslint - 3. run ./node_modules/.bin/eslint --init - 4. Create ./frontend/.env - 5. Add SKIP_PREFLIGHT_CHECK=true -12. Add Redux to Home Screen - 1. npm install redux react-redux - 2. Create store.js - 3. initState= {products:[]} - 4. reducer = (state, action) => switch LOAD_PRODUCTS: {products: action.payload} - 5. export default createStore(reducer, initState) - 6. Edit HomeScreen.js - 7. shopName = useSelector(state=>state.products) - 8. const dispatch = useDispatch() - 9. useEffect(()=>dispatch({type: LOAD_PRODUCTS, payload: data}) - 10. Add store to index.js -13. Add Redux to Product Screen - 1. create product details constants, actions and reducers - 2. add reducer to store.js - 3. use action in ProductScreen.js - 4. add /api/product/:id to backend api -14. Handle Add To Cart Button - 1. Handle Add To Cart in ProductScreen.js - 2. create CartScreen.js -15. Implement Add to Cart Action - 1. create addToCart constants, actions and reducers - 2. add reducer to store.js - 3. use action in CartScreen.js - 4. render cartItems.length -16. Build Cart Screen - 1. create 2 columns for cart items and cart action - 2. cartItems.length === 0 ? cart is empty - 3. show item image, name, qty and price - 4. Proceed to Checkout button - 5. Implement remove from cart action -17. Implement Remove From Cart Action - 1. create removeFromCart constants, actions and reducers - 2. add reducer to store.js - 3. use action in CartScreen.js -18. Create Sample Users In MongoDB - 1. npm install mongoose - 2. connect to mongodb - 3. create config.js - 4. npm install dotenv - 5. export MONGODB_URL - 6. create models/userModel.js - 7. create userSchema and userModel - 8. create userRoute - 9. Seed sample data -19. Create Sample Products In MongoDB - 1. create models/productModel.js - 2. create productSchema and productModel - 3. create productRoute - 4. Seed sample data -20. Create Sign-in Backend - 1. create /signin api - 2. check email and password - 3. generate token - 4. install json web token - 5. install dotenv - 6. return token and data - 7. test it using postman -21. Design SignIn Screen - 1. create SigninScreen - 2. render email and password fields - 3. create signin constants, actions and reducers - 4. Update Header based on user login -22. Implement SignIn Action - 1. create signin constants, actions and reducers - 2. add reducer to store.js - 3. use action in SigninScreen.js -23. Create Register Screen - 1. create API for /api/users/register - 2. insert new user to database - 3. return user info and token - 4. create RegisterScreen - 5. Add fields - 6. Style fields - 7. Add screen to App.js - 8. create register action and reducer - 9. check validation and create user -24. Create Shipping Screen - 1. create CheckoutSteps.js component - 2. create shipping fields - 3. implement shipping constant, actions and reducers -25. Create Payment Screen - 1. create payment fields - 2. implement shipping constant, actions and reducers -26. Design Place Order Screen - 1. design order summary fields - 2. design order action -27. Create Place Order API - 1. createOrder api - 2. create orderModel - 3. create orderRouter - 4. create post order route -28. Implement PlaceOrder Action - 1. handle place order button click - 2. create place order constants, action and reducer -29. Create Order Screen - 1. build order api for /api/orders/:id - 2. create OrderScreen.js - 3. dispatch order details action in useEffect - 4. load data with useSelector - 5. show data like place order screen - 6. create order details constant, action and reducer -30. Add PayPal Button - 1. get client id from paypal - 2. set it in .env file - 3. create route form /api/paypal/clientId - 4. create getPaypalClientID in api.js - 5. add paypal checkout script in OrderScreen.js - 6. show paypal button -31. Implement Order Payment - 1. update order after payment - 2. create payOrder in api.js - 3. create route for /:id/pay in orderRouter.js - 4. rerender after pay order -32. Display Orders History - 1. create customer orders api - 2. create api for getMyOrders - 3. show orders in profile screen - 4. style orders -33. Display User Profile - 1. create user details api - 2. show user information -34. Update User Profile - 1. create user update api - 2. update user info -35. Create Admin View - 1. Create Admin Menu - 2. Create Admin Middleware in Backend - 3. Create Admin Route in Frontend -36. List Products - 1. Create Product List Screen - 2. Add reducer to store - 3. show products on the screen -37. Create Product - 1. build create product api - 2. build Create Product button - 3. define product create constant, action and reducer - 4. use action in Product List Screen -38. Build Product Edit Screen - 1. create edit screen - 2. define state - 3. create fields - 4. load product details - 5. add to routes -39. Update Product - 1. define update api - 2. define product update constant, action and reducer - 3. use action in Product Edit Screen -40. Upload Product Image - 1. npm install multer - 7. define upload router - 8. create uploads folder - 9. Handle frontend -41. Delete Product - 1. create delete api in backend - 2. create delete constants, action and reducer - 3. use it in product list screen -42. List Orders - 1. create order list api - 2. create Order List Screen - 3. Add reducer to store - 4. show products on the screen -43. Delete Order - 2. create delete order action and reducer - 3. add order delete action to order list -44. Deliver Order - 1. create constant, actions and reducers for deliver order - 2. add order deliver action to order details screen -45. Publish To Heroku - 1. Create git repository - 2. Create heroku account - 3. install Heroku CLI - 4. heroku login - 5. heroku apps:create amazona - 6. Edit package.json for build script - 10. Create Procfile - 12. Create mongodb atlas database - 19. Set database connection in heroku env variables - 20. Commit and push \ No newline at end of file diff --git a/backend/data.js b/backend/data.js index e52201b7..f3278992 100644 --- a/backend/data.js +++ b/backend/data.js @@ -1,86 +1,86 @@ -import bcrypt from 'bcryptjs'; +import bcrypt from "bcryptjs"; const data = { users: [ { - name: 'Basir', - email: 'admin@example.com', - password: bcrypt.hashSync('1234', 8), + name: "admin", + email: "admin@example.com", + password: bcrypt.hashSync("1234", 8), isAdmin: true, }, { - name: 'John', - email: 'user@example.com', - password: bcrypt.hashSync('1234', 8), + name: "John", + email: "user@example.com", + password: bcrypt.hashSync("1234", 8), isAdmin: false, }, ], products: [ { - name: 'Nike Slim Shirt', - category: 'Shirts', - image: '/images/p1.jpg', + name: "Bicycle 1", + category: "T type", + image: "/images/p1.jpg", price: 120, countInStock: 10, - brand: 'Nike', + brand: "T", rating: 4.5, numReviews: 10, - description: 'high quality product', + description: "high quality product", }, { - name: 'Adidas Fit Shirt', - category: 'Shirts', - image: '/images/p2.jpg', + name: "Bicycle 2", + category: "Y type", + image: "/images/p2.jpg", price: 100, countInStock: 20, - brand: 'Adidas', + brand: "Y", rating: 4.0, numReviews: 10, - description: 'high quality product', + description: "high quality product", }, { - name: 'Lacoste Free Shirt', - category: 'Shirts', - image: '/images/p3.jpg', + name: "Bicycle 3", + category: "T type", + image: "/images/p3.jpg", price: 220, countInStock: 0, - brand: 'Lacoste', + brand: "T", rating: 4.8, numReviews: 17, - description: 'high quality product', + description: "high quality product", }, { - name: 'Nike Slim Pant', - category: 'Pants', - image: '/images/p4.jpg', + name: "Bicycle 4", + category: "T type", + image: "/images/p4.jpg", price: 78, countInStock: 15, - brand: 'Nike', + brand: "T", rating: 4.5, numReviews: 14, - description: 'high quality product', + description: "high quality product", }, { - name: 'Puma Slim Pant', - category: 'Pants', - image: '/images/p5.jpg', + name: "Bicycle 5", + category: "Y type", + image: "/images/p5.jpg", price: 65, countInStock: 5, - brand: 'Puma', + brand: "Y", rating: 4.5, numReviews: 10, - description: 'high quality product', + description: "high quality product", }, { - name: 'Adidas Fit Pant', - category: 'Pants', - image: '/images/p6.jpg', + name: "Bicycle 6", + category: "Y type", + image: "/images/p6.jpg", price: 139, countInStock: 12, - brand: 'Adidas', + brand: "Y", rating: 4.5, numReviews: 15, - description: 'high quality product', + description: "high quality product", }, ], }; diff --git a/backend/models/userModel.js b/backend/models/userModel.js index 35ddc82c..87dbc4eb 100644 --- a/backend/models/userModel.js +++ b/backend/models/userModel.js @@ -1,4 +1,4 @@ -import mongoose from 'mongoose'; +import mongoose from "mongoose"; const userSchema = new mongoose.Schema( { @@ -6,10 +6,11 @@ const userSchema = new mongoose.Schema( email: { type: String, required: true, unique: true }, password: { type: String, required: true }, isAdmin: { type: Boolean, default: false, required: true }, + isSeller: { type: Boolean, default: false, required: true }, }, { timestamps: true, } ); -const User = mongoose.model('User', userSchema); +const User = mongoose.model("User", userSchema); export default User; diff --git a/backend/routers/productRouter.js b/backend/routers/productRouter.js index c9d43994..c0b4f3ed 100644 --- a/backend/routers/productRouter.js +++ b/backend/routers/productRouter.js @@ -1,13 +1,13 @@ -import express from 'express'; -import expressAsyncHandler from 'express-async-handler'; -import data from '../data.js'; -import Product from '../models/productModel.js'; -import { isAdmin, isAuth } from '../utils.js'; +import express from "express"; +import expressAsyncHandler from "express-async-handler"; +import data from "../data.js"; +import Product from "../models/productModel.js"; +import { isAdmin, isAuth } from "../utils.js"; const productRouter = express.Router(); productRouter.get( - '/', + "/", expressAsyncHandler(async (req, res) => { const products = await Product.find({}); res.send(products); @@ -15,48 +15,48 @@ productRouter.get( ); productRouter.get( - '/seed', + "/seed", expressAsyncHandler(async (req, res) => { - // await Product.remove({}); + await Product.remove({}); const createdProducts = await Product.insertMany(data.products); res.send({ createdProducts }); }) ); productRouter.get( - '/:id', + "/:id", expressAsyncHandler(async (req, res) => { const product = await Product.findById(req.params.id); if (product) { res.send(product); } else { - res.status(404).send({ message: 'Product Not Found' }); + res.status(404).send({ message: "Product Not Found" }); } }) ); productRouter.post( - '/', + "/", isAuth, isAdmin, expressAsyncHandler(async (req, res) => { const product = new Product({ - name: 'sample name ' + Date.now(), - image: '/images/p1.jpg', + name: "sample name " + Date.now(), + image: "/images/p1.jpg", price: 0, - category: 'sample category', - brand: 'sample brand', + category: "sample category", + brand: "sample brand", countInStock: 0, rating: 0, numReviews: 0, - description: 'sample description', + description: "sample description", }); const createdProduct = await product.save(); - res.send({ message: 'Product Created', product: createdProduct }); + res.send({ message: "Product Created", product: createdProduct }); }) ); productRouter.put( - '/:id', + "/:id", isAuth, isAdmin, expressAsyncHandler(async (req, res) => { @@ -71,24 +71,24 @@ productRouter.put( product.countInStock = req.body.countInStock; product.description = req.body.description; const updatedProduct = await product.save(); - res.send({ message: 'Product Updated', product: updatedProduct }); + res.send({ message: "Product Updated", product: updatedProduct }); } else { - res.status(404).send({ message: 'Product Not Found' }); + res.status(404).send({ message: "Product Not Found" }); } }) ); productRouter.delete( - '/:id', + "/:id", isAuth, isAdmin, expressAsyncHandler(async (req, res) => { const product = await Product.findById(req.params.id); if (product) { const deleteProduct = await product.remove(); - res.send({ message: 'Product Deleted', product: deleteProduct }); + res.send({ message: "Product Deleted", product: deleteProduct }); } else { - res.status(404).send({ message: 'Product Not Found' }); + res.status(404).send({ message: "Product Not Found" }); } }) ); diff --git a/backend/routers/userRouter.js b/backend/routers/userRouter.js index 96b55093..770736a9 100644 --- a/backend/routers/userRouter.js +++ b/backend/routers/userRouter.js @@ -1,23 +1,23 @@ -import express from 'express'; -import expressAsyncHandler from 'express-async-handler'; -import bcrypt from 'bcryptjs'; -import data from '../data.js'; -import User from '../models/userModel.js'; -import { generateToken, isAuth } from '../utils.js'; +import express from "express"; +import expressAsyncHandler from "express-async-handler"; +import bcrypt from "bcryptjs"; +import data from "../data.js"; +import User from "../models/userModel.js"; +import { generateToken, isAdmin, isAuth } from "../utils.js"; const userRouter = express.Router(); userRouter.get( - '/seed', + "/seed", expressAsyncHandler(async (req, res) => { - // await User.remove({}); + await User.remove({}); const createdUsers = await User.insertMany(data.users); res.send({ createdUsers }); }) ); userRouter.post( - '/signin', + "/signin", expressAsyncHandler(async (req, res) => { const user = await User.findOne({ email: req.body.email }); if (user) { @@ -32,12 +32,12 @@ userRouter.post( return; } } - res.status(401).send({ message: 'Invalid email or password' }); + res.status(401).send({ message: "Invalid email or password" }); }) ); userRouter.post( - '/register', + "/register", expressAsyncHandler(async (req, res) => { const user = new User({ name: req.body.name, @@ -56,18 +56,18 @@ userRouter.post( ); userRouter.get( - '/:id', + "/:id", expressAsyncHandler(async (req, res) => { const user = await User.findById(req.params.id); if (user) { res.send(user); } else { - res.status(404).send({ message: 'User Not Found' }); + res.status(404).send({ message: "User Not Found" }); } }) ); userRouter.put( - '/profile', + "/profile", isAuth, expressAsyncHandler(async (req, res) => { const user = await User.findById(req.user._id); @@ -88,4 +88,15 @@ userRouter.put( } }) ); + +userRouter.get( + "/", + isAuth, + isAdmin, + expressAsyncHandler(async (req, res) => { + const users = await User.find({}); + res.send(users); + }) +); + export default userRouter; diff --git a/backend/server.js b/backend/server.js index ad95b17a..c85e8573 100644 --- a/backend/server.js +++ b/backend/server.js @@ -1,11 +1,12 @@ -import express from 'express'; -import mongoose from 'mongoose'; -import dotenv from 'dotenv'; -import path from 'path'; -import productRouter from './routers/productRouter.js'; -import userRouter from './routers/userRouter.js'; -import orderRouter from './routers/orderRouter.js'; -import uploadRouter from './routers/uploadRouter.js'; +import express from "express"; +import mongoose from "mongoose"; +import dotenv from "dotenv"; +import path from "path"; +import morgan from "morgan"; +import productRouter from "./routers/productRouter.js"; +import userRouter from "./routers/userRouter.js"; +import orderRouter from "./routers/orderRouter.js"; +import uploadRouter from "./routers/uploadRouter.js"; dotenv.config(); @@ -13,27 +14,32 @@ const app = express(); app.use(express.json()); app.use(express.urlencoded({ extended: true })); -mongoose.connect(process.env.MONGODB_URL || 'mongodb://localhost/amazona', { +app.use(morgan("dev")); + +const uri = process.env.REACT_APP_ATLAS_URI; +mongoose.connect(uri, { useNewUrlParser: true, useUnifiedTopology: true, useCreateIndex: true, }); -app.use('/api/uploads', uploadRouter); -app.use('/api/users', userRouter); -app.use('/api/products', productRouter); -app.use('/api/orders', orderRouter); -app.get('/api/config/paypal', (req, res) => { - res.send(process.env.PAYPAL_CLIENT_ID || 'sb'); +const connection = mongoose.connection; +connection.once("open", () => { + console.log("MongoDB Atlas database connection established successfully"); +}); + +app.use("/api/uploads", uploadRouter); +app.use("/api/users", userRouter); +app.use("/api/products", productRouter); +app.use("/api/orders", orderRouter); +app.get("/api/config/paypal", (req, res) => { + res.send(process.env.PAYPAL_CLIENT_ID || "sb"); }); const __dirname = path.resolve(); -app.use('/uploads', express.static(path.join(__dirname, '/uploads'))); -app.use(express.static(path.join(__dirname, '/frontend/build'))); -app.get('*', (req, res) => - res.sendFile(path.join(__dirname, '/frontend/build/index.html')) +app.use("/uploads", express.static(path.join(__dirname, "/uploads"))); +app.use(express.static(path.join(__dirname, "/frontend/build"))); +app.get("*", (req, res) => + res.sendFile(path.join(__dirname, "/frontend/build/index.html")) ); -// app.get('/', (req, res) => { -// res.send('Server is ready'); -// }); app.use((err, req, res, next) => { res.status(500).send({ message: err.message }); diff --git a/frontend/public/images/p1.jpg b/frontend/public/images/p1.jpg index 599e9cf5..5649312b 100644 Binary files a/frontend/public/images/p1.jpg and b/frontend/public/images/p1.jpg differ diff --git a/frontend/public/images/p2.jpg b/frontend/public/images/p2.jpg index aa82fad9..9cbdbe45 100644 Binary files a/frontend/public/images/p2.jpg and b/frontend/public/images/p2.jpg differ diff --git a/frontend/public/images/p3.jpg b/frontend/public/images/p3.jpg index a8a726ea..e37e09e5 100644 Binary files a/frontend/public/images/p3.jpg and b/frontend/public/images/p3.jpg differ diff --git a/frontend/public/images/p4.jpg b/frontend/public/images/p4.jpg index d212ae64..82279e06 100644 Binary files a/frontend/public/images/p4.jpg and b/frontend/public/images/p4.jpg differ diff --git a/frontend/public/images/p5.jpg b/frontend/public/images/p5.jpg index 8787f49a..47a9b31a 100644 Binary files a/frontend/public/images/p5.jpg and b/frontend/public/images/p5.jpg differ diff --git a/frontend/public/images/p6.jpg b/frontend/public/images/p6.jpg index 0d18ddb9..3fdf1fec 100644 Binary files a/frontend/public/images/p6.jpg and b/frontend/public/images/p6.jpg differ diff --git a/frontend/src/App.js b/frontend/src/App.js index 9a61689a..e5b93e73 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -1,23 +1,24 @@ -import React from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { BrowserRouter, Link, Route } from 'react-router-dom'; -import { signout } from './actions/userActions'; -import AdminRoute from './components/AdminRoute'; -import PrivateRoute from './components/PrivateRoute'; -import CartScreen from './screens/CartScreen'; -import HomeScreen from './screens/HomeScreen'; -import OrderHistoryScreen from './screens/OrderHistoryScreen'; -import OrderScreen from './screens/OrderScreen'; -import PaymentMethodScreen from './screens/PaymentMethodScreen'; -import PlaceOrderScreen from './screens/PlaceOrderScreen'; -import ProductListScreen from './screens/ProductListScreen'; -import ProductScreen from './screens/ProductScreen'; -import ProfileScreen from './screens/ProfileScreen'; -import RegisterScreen from './screens/RegisterScreen'; -import ShippingAddressScreen from './screens/ShippingAddressScreen'; -import SigninScreen from './screens/SigninScreen'; -import ProductEditScreen from './screens/ProductEditScreen'; -import OrderListScreen from './screens/OrderListScreen'; +import React from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { BrowserRouter, Link, Route } from "react-router-dom"; +import { signout } from "./actions/userActions"; +import AdminRoute from "./components/AdminRoute"; +import PrivateRoute from "./components/PrivateRoute"; +import CartScreen from "./screens/CartScreen"; +import HomeScreen from "./screens/HomeScreen"; +import OrderHistoryScreen from "./screens/OrderHistoryScreen"; +import OrderScreen from "./screens/OrderScreen"; +import PaymentMethodScreen from "./screens/PaymentMethodScreen"; +import PlaceOrderScreen from "./screens/PlaceOrderScreen"; +import ProductListScreen from "./screens/ProductListScreen"; +import ProductScreen from "./screens/ProductScreen"; +import ProfileScreen from "./screens/ProfileScreen"; +import RegisterScreen from "./screens/RegisterScreen"; +import ShippingAddressScreen from "./screens/ShippingAddressScreen"; +import SigninScreen from "./screens/SigninScreen"; +import ProductEditScreen from "./screens/ProductEditScreen"; +import OrderListScreen from "./screens/OrderListScreen"; +import UserListScreen from "./screens/UserListScreen"; function App() { const cart = useSelector((state) => state.cart); @@ -34,7 +35,7 @@ function App() {
- amazona + Bicycles Shop
@@ -47,7 +48,7 @@ function App() { {userInfo ? (
- {userInfo.name} {' '} + {userInfo.name} {" "}
  • @@ -116,6 +117,7 @@ function App() { path="/orderlist" component={OrderListScreen} > +
    All right reserved
    diff --git a/frontend/src/actions/userActions.js b/frontend/src/actions/userActions.js index 322838c2..a9f2766b 100644 --- a/frontend/src/actions/userActions.js +++ b/frontend/src/actions/userActions.js @@ -1,8 +1,11 @@ -import Axios from 'axios'; +import Axios from "axios"; import { USER_DETAILS_FAIL, USER_DETAILS_REQUEST, USER_DETAILS_SUCCESS, + USER_LIST_FAIL, + USER_LIST_REQUEST, + USER_LIST_SUCCESS, USER_REGISTER_FAIL, USER_REGISTER_REQUEST, USER_REGISTER_SUCCESS, @@ -13,19 +16,19 @@ import { USER_UPDATE_PROFILE_FAIL, USER_UPDATE_PROFILE_REQUEST, USER_UPDATE_PROFILE_SUCCESS, -} from '../constants/userConstants'; +} from "../constants/userConstants"; export const register = (name, email, password) => async (dispatch) => { dispatch({ type: USER_REGISTER_REQUEST, payload: { email, password } }); try { - const { data } = await Axios.post('/api/users/register', { + const { data } = await Axios.post("/api/users/register", { name, email, password, }); dispatch({ type: USER_REGISTER_SUCCESS, payload: data }); dispatch({ type: USER_SIGNIN_SUCCESS, payload: data }); - localStorage.setItem('userInfo', JSON.stringify(data)); + localStorage.setItem("userInfo", JSON.stringify(data)); } catch (error) { dispatch({ type: USER_REGISTER_FAIL, @@ -40,9 +43,9 @@ export const register = (name, email, password) => async (dispatch) => { export const signin = (email, password) => async (dispatch) => { dispatch({ type: USER_SIGNIN_REQUEST, payload: { email, password } }); try { - const { data } = await Axios.post('/api/users/signin', { email, password }); + const { data } = await Axios.post("/api/users/signin", { email, password }); dispatch({ type: USER_SIGNIN_SUCCESS, payload: data }); - localStorage.setItem('userInfo', JSON.stringify(data)); + localStorage.setItem("userInfo", JSON.stringify(data)); } catch (error) { dispatch({ type: USER_SIGNIN_FAIL, @@ -55,11 +58,13 @@ export const signin = (email, password) => async (dispatch) => { }; export const signout = () => (dispatch) => { - localStorage.removeItem('userInfo'); - localStorage.removeItem('cartItems'); - localStorage.removeItem('shippingAddress'); + localStorage.removeItem("userInfo"); + localStorage.removeItem("cartItems"); + localStorage.removeItem("shippingAddress"); dispatch({ type: USER_SIGNOUT }); + document.location.location.href = "/signin"; }; + export const detailsUser = (userId) => async (dispatch, getState) => { dispatch({ type: USER_DETAILS_REQUEST, payload: userId }); const { @@ -78,6 +83,7 @@ export const detailsUser = (userId) => async (dispatch, getState) => { dispatch({ type: USER_DETAILS_FAIL, payload: message }); } }; + export const updateUserProfile = (user) => async (dispatch, getState) => { dispatch({ type: USER_UPDATE_PROFILE_REQUEST, payload: user }); const { @@ -89,7 +95,7 @@ export const updateUserProfile = (user) => async (dispatch, getState) => { }); dispatch({ type: USER_UPDATE_PROFILE_SUCCESS, payload: data }); dispatch({ type: USER_SIGNIN_SUCCESS, payload: data }); - localStorage.setItem('userInfo', JSON.stringify(data)); + localStorage.setItem("userInfo", JSON.stringify(data)); } catch (error) { const message = error.response && error.response.data.message @@ -98,3 +104,24 @@ export const updateUserProfile = (user) => async (dispatch, getState) => { dispatch({ type: USER_UPDATE_PROFILE_FAIL, payload: message }); } }; + +export const listUsers = () => async (dispatch, getState) => { + dispatch({ type: USER_LIST_REQUEST }); + try { + const { + userSignin: { userInfo }, + } = getState(); + const { data } = await Axios.get("/api/users", { + headers: { + Authorization: `TESTTES${userInfo.token}`, + }, + }); + dispatch({ type: USER_LIST_SUCCESS, payload: data }); + } catch (error) { + const message = + error.response && error.response.data.message + ? error.response.data.message + : error.message; + dispatch({ type: USER_LIST_FAIL, payload: message }); + } +}; diff --git a/frontend/src/constants/userConstants.js b/frontend/src/constants/userConstants.js index d984e54f..7182d32c 100644 --- a/frontend/src/constants/userConstants.js +++ b/frontend/src/constants/userConstants.js @@ -1,18 +1,22 @@ -export const USER_REGISTER_REQUEST = 'USER_REGISTER_REQUEST'; -export const USER_REGISTER_SUCCESS = 'USER_REGISTER_SUCCESS'; -export const USER_REGISTER_FAIL = 'USER_REGISTER_FAIL'; +export const USER_REGISTER_REQUEST = "USER_REGISTER_REQUEST"; +export const USER_REGISTER_SUCCESS = "USER_REGISTER_SUCCESS"; +export const USER_REGISTER_FAIL = "USER_REGISTER_FAIL"; -export const USER_SIGNIN_REQUEST = 'USER_SIGNIN_REQUEST'; -export const USER_SIGNIN_SUCCESS = 'USER_SIGNIN_SUCCESS'; -export const USER_SIGNIN_FAIL = 'USER_SIGNIN_FAIL'; +export const USER_SIGNIN_REQUEST = "USER_SIGNIN_REQUEST"; +export const USER_SIGNIN_SUCCESS = "USER_SIGNIN_SUCCESS"; +export const USER_SIGNIN_FAIL = "USER_SIGNIN_FAIL"; -export const USER_SIGNOUT = 'USER_SIGNOUT'; +export const USER_SIGNOUT = "USER_SIGNOUT"; -export const USER_DETAILS_REQUEST = 'USER_DETAILS_REQUEST'; -export const USER_DETAILS_SUCCESS = 'USER_DETAILS_SUCCESS'; -export const USER_DETAILS_FAIL = 'USER_DETAILS_FAIL'; +export const USER_DETAILS_REQUEST = "USER_DETAILS_REQUEST"; +export const USER_DETAILS_SUCCESS = "USER_DETAILS_SUCCESS"; +export const USER_DETAILS_FAIL = "USER_DETAILS_FAIL"; -export const USER_UPDATE_PROFILE_REQUEST = 'USER_UPDATE_PROFILE_REQUEST'; -export const USER_UPDATE_PROFILE_SUCCESS = 'USER_UPDATE_PROFILE_SUCCESS'; -export const USER_UPDATE_PROFILE_FAIL = 'USER_UPDATE_PROFILE_FAIL'; -export const USER_UPDATE_PROFILE_RESET = 'USER_UPDATE_PROFILE_RESET'; +export const USER_LIST_REQUEST = "USER_LIST_REQUEST"; +export const USER_LIST_SUCCESS = "USER_LIST_SUCCESS"; +export const USER_LIST_FAIL = "USER_LIST_FAIL"; + +export const USER_UPDATE_PROFILE_REQUEST = "USER_UPDATE_PROFILE_REQUEST"; +export const USER_UPDATE_PROFILE_SUCCESS = "USER_UPDATE_PROFILE_SUCCESS"; +export const USER_UPDATE_PROFILE_FAIL = "USER_UPDATE_PROFILE_FAIL"; +export const USER_UPDATE_PROFILE_RESET = "USER_UPDATE_PROFILE_RESET"; diff --git a/frontend/src/data.js b/frontend/src/data.js deleted file mode 100644 index cdc897d4..00000000 --- a/frontend/src/data.js +++ /dev/null @@ -1,77 +0,0 @@ -const data = { - products: [ - { - _id: '1', - name: 'Nike Slim Shirt', - category: 'Shirts', - image: '/images/p1.jpg', - price: 120, - countInStock: 10, - brand: 'Nike', - rating: 4.5, - numReviews: 10, - description: 'high quality product', - }, - { - _id: '2', - name: 'Adidas Fit Shirt', - category: 'Shirts', - image: '/images/p2.jpg', - price: 100, - countInStock: 20, - brand: 'Adidas', - rating: 4.0, - numReviews: 10, - description: 'high quality product', - }, - { - _id: '3', - name: 'Lacoste Free Shirt', - category: 'Shirts', - image: '/images/p3.jpg', - price: 220, - countInStock: 0, - brand: 'Lacoste', - rating: 4.8, - numReviews: 17, - description: 'high quality product', - }, - { - _id: '4', - name: 'Nike Slim Pant', - category: 'Pants', - image: '/images/p4.jpg', - price: 78, - countInStock: 15, - brand: 'Nike', - rating: 4.5, - numReviews: 14, - description: 'high quality product', - }, - { - _id: '5', - name: 'Puma Slim Pant', - category: 'Pants', - image: '/images/p5.jpg', - price: 65, - countInStock: 5, - brand: 'Puma', - rating: 4.5, - numReviews: 10, - description: 'high quality product', - }, - { - _id: '6', - name: 'Adidas Fit Pant', - category: 'Pants', - image: '/images/p6.jpg', - price: 139, - countInStock: 12, - brand: 'Adidas', - rating: 4.5, - numReviews: 15, - description: 'high quality product', - }, - ], -}; -export default data; diff --git a/frontend/src/reducers/userReducers.js b/frontend/src/reducers/userReducers.js index 70affa5b..12638ebb 100644 --- a/frontend/src/reducers/userReducers.js +++ b/frontend/src/reducers/userReducers.js @@ -2,6 +2,9 @@ import { USER_DETAILS_FAIL, USER_DETAILS_REQUEST, USER_DETAILS_SUCCESS, + USER_LIST_FAIL, + USER_LIST_REQUEST, + USER_LIST_SUCCESS, USER_REGISTER_FAIL, USER_REGISTER_REQUEST, USER_REGISTER_SUCCESS, @@ -13,7 +16,7 @@ import { USER_UPDATE_PROFILE_REQUEST, USER_UPDATE_PROFILE_RESET, USER_UPDATE_PROFILE_SUCCESS, -} from '../constants/userConstants'; +} from "../constants/userConstants"; export const userRegisterReducer = (state = {}, action) => { switch (action.type) { @@ -42,6 +45,7 @@ export const userSigninReducer = (state = {}, action) => { return state; } }; + export const userDetailsReducer = (state = { loading: true }, action) => { switch (action.type) { case USER_DETAILS_REQUEST: @@ -54,6 +58,7 @@ export const userDetailsReducer = (state = { loading: true }, action) => { return state; } }; + export const userUpdateProfileReducer = (state = {}, action) => { switch (action.type) { case USER_UPDATE_PROFILE_REQUEST: @@ -68,3 +73,16 @@ export const userUpdateProfileReducer = (state = {}, action) => { return state; } }; + +export const userListReducer = (state = { loading: true }, action) => { + switch (action.type) { + case USER_LIST_REQUEST: + return { loading: true }; + case USER_LIST_SUCCESS: + return { loading: false, users: action.payload }; + case USER_LIST_FAIL: + return { loading: false, error: action.payload }; + default: + return state; + } +}; diff --git a/frontend/src/screens/OrderListScreen.js b/frontend/src/screens/OrderListScreen.js index a42b33b8..b744e802 100644 --- a/frontend/src/screens/OrderListScreen.js +++ b/frontend/src/screens/OrderListScreen.js @@ -1,9 +1,9 @@ -import React, { useEffect } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { deleteOrder, listOrders } from '../actions/orderActions'; -import LoadingBox from '../components/LoadingBox'; -import MessageBox from '../components/MessageBox'; -import { ORDER_DELETE_RESET } from '../constants/orderConstants'; +import React, { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { deleteOrder, listOrders } from "../actions/orderActions"; +import LoadingBox from "../components/LoadingBox"; +import MessageBox from "../components/MessageBox"; +import { ORDER_DELETE_RESET } from "../constants/orderConstants"; export default function OrderListScreen(props) { const orderList = useSelector((state) => state.orderList); @@ -20,7 +20,7 @@ export default function OrderListScreen(props) { dispatch(listOrders()); }, [dispatch, successDelete]); const deleteHandler = (order) => { - if (window.confirm('Are you sure to delete?')) { + if (window.confirm("Are you sure to delete?")) { dispatch(deleteOrder(order._id)); } }; @@ -50,14 +50,14 @@ export default function OrderListScreen(props) { {orders.map((order) => ( {order._id} - {order.user.name} + order.user.name {order.createdAt.substring(0, 10)} {order.totalPrice.toFixed(2)} - {order.isPaid ? order.paidAt.substring(0, 10) : 'No'} + {order.isPaid ? order.paidAt.substring(0, 10) : "No"} {order.isDelivered ? order.deliveredAt.substring(0, 10) - : 'No'} + : "No"}