diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index d8b83df..0000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -package-lock.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 17514e7..0000000 --- a/.eslintrc.js +++ /dev/null @@ -1,63 +0,0 @@ -module.exports = { - env: { - browser: true, - es2021: true, - jest: true, - "jest/globals": true, - }, - extends: [ - "airbnb", - "eslint:recommended", - "plugin:react/recommended", - "plugin:jest/recommended", - "plugin:import/errors", - "plugin:import/warnings", - "plugin:jsx-a11y/recommended", - "prettier", - ], - parser: "@babel/eslint-parser", - parserOptions: { - ecmaVersion: 12, - sourceType: "module", - requireConfigFile: "false", - jsx: true, - }, - plugins: [ - "html", - "jest", - "react", - "react-hooks", - "jsx-a11y", - "markdown", - "react-hooks", - "import", - "prettier", - ], - settings: { - react: { - version: "detect", - }, - jest: { - version: 26, - }, - }, - overrides: [ - { - files: ["*.html"], - parser: "@html-eslint/parser", - extends: ["plugin:@html-eslint/recommended"], - }, - ], - rules: { - "prettier/prettier": "error", - "react/jsx-filename-extension": "off", - "import/prefer-default-export": "off", - "prefer-destructuring": "off", - "object-shorthand": "off", - "react/jsx-props-no-spreading": "off", - "arrow-body-style": "off", - "no-underscore-dangle": "off", - "react/forbid-prop-types": "off", - "react/prop-types": "off", - }, -}; diff --git a/.prettierrc b/.prettierrc deleted file mode 100644 index 8c4b2fa..0000000 --- a/.prettierrc +++ /dev/null @@ -1,19 +0,0 @@ -{ - "arrowParens": "always", - "bracketSpacing": true, - "embeddedLanguageFormatting": "auto", - "htmlWhitespaceSensitivity": "css", - "insertPragma": false, - "jsxBracketSameLine": false, - "jsxSingleQuote": false, - "printWidth": 80, - "proseWrap": "always", - "quoteProps": "as-needed", - "requirePragma": false, - "semi": true, - "singleQuote": false, - "tabWidth": 2, - "trailingComma": "all", - "useTabs": false, - "endOfLine": "lf" -} diff --git a/src/App.js b/src/App.js index bdd3b32..6b0fc61 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,4 @@ -import React, { Component } from "react"; +import React, { useState, useEffect } from "react"; import { BrowserRouter, Route } from "react-router-dom"; import Home from "./pages/Home"; @@ -9,266 +9,179 @@ import * as api from "./api"; const LOCAL_STORAGE_KEY = "react-sc-state"; function loadLocalStorageData() { - const prevItems = localStorage.getItem(LOCAL_STORAGE_KEY); + const prevItems = localStorage.getItem(LOCAL_STORAGE_KEY); - if (!prevItems) { - return null; - } + if (!prevItems) { + return null; + } - try { - return JSON.parse(prevItems); - } catch (error) { - return null; - } + try { + return JSON.parse(prevItems); + } catch (error) { + return null; + } } function buildNewCartItem(cartItem) { - if (cartItem.quantity >= cartItem.unitsInStock) { - return cartItem; - } - - return { - id: cartItem.id, - title: cartItem.title, - img: cartItem.img, - price: cartItem.price, - unitsInStock: cartItem.unitsInStock, - createdAt: cartItem.createdAt, - updatedAt: cartItem.updatedAt, - quantity: cartItem.quantity + 1, - }; + if (cartItem.quantity >= cartItem.unitsInStock) { + return cartItem; + } + + return { + id: cartItem.id, + title: cartItem.title, + img: cartItem.img, + price: cartItem.price, + unitsInStock: cartItem.unitsInStock, + createdAt: cartItem.createdAt, + updatedAt: cartItem.updatedAt, + quantity: cartItem.quantity + 1, + }; } -class App extends Component { - constructor(props) { - super(props); - - this.state = { - products: [], - cartItems: [], - isLoading: false, - hasError: false, - loadingError: null, - }; - - this.handleAddToCart = this.handleAddToCart.bind(this); - this.handleRemove = this.handleRemove.bind(this); - this.handleChange = this.handleChange.bind(this); - this.handleDownVote = this.handleDownVote.bind(this); - this.handleUpVote = this.handleUpVote.bind(this); - this.handleSetFavorite = this.handleSetFavorite.bind(this); - this.saveNewProduct = this.saveNewProduct.bind(this); - } - - componentDidMount() { - const prevItems = loadLocalStorageData(); - - if (!prevItems) { - this.setState({ - isLoading: true, - }); - - api.getProducts().then((data) => { - this.setState({ - products: data, - isLoading: false, - }); - }); - return; - } - - this.setState({ - cartItems: prevItems.cartItems, - products: prevItems.products, - }); - } - - componentDidUpdate() { - const { cartItems, products } = this.state; - - localStorage.setItem( - LOCAL_STORAGE_KEY, - JSON.stringify({ cartItems, products }), - ); - } - - handleAddToCart(productId) { - const { cartItems, products } = this.state; - - const prevCartItem = cartItems.find((item) => item.id === productId); - const foundProduct = products.find((product) => product.id === productId); - - if (prevCartItem) { - const updatedCartItems = cartItems.map((item) => { - if (item.id !== productId) { - return item; - } - - if (item.quantity >= item.unitsInStock) { - return item; - } - - return { - ...item, - quantity: item.quantity + 1, - }; - }); - - this.setState({ cartItems: updatedCartItems }); - return; - } - - const updatedProduct = buildNewCartItem(foundProduct); - this.setState((prevState) => ({ - cartItems: [...prevState.cartItems, updatedProduct], - })); - } - - handleChange(event, productId) { - const { cartItems } = this.state; - - const updatedCartItems = cartItems.map((item) => { - if (item.id === productId && item.quantity <= item.unitsInStock) { - return { - ...item, - quantity: Number(event.target.value), - }; - } - - return item; - }); - - this.setState({ cartItems: updatedCartItems }); - } - - handleRemove(productId) { - const { cartItems } = this.state; - const updatedCartItems = cartItems.filter((item) => item.id !== productId); - - this.setState({ - cartItems: updatedCartItems, - }); - } - - handleDownVote(productId) { - const { products } = this.state; - - const updatedProducts = products.map((product) => { - if ( - product.id === productId && - product.votes.downVotes.currentValue < - product.votes.downVotes.lowerLimit - ) { - return { - ...product, - votes: { - ...product.votes, - downVotes: { - ...product.votes.downVotes, - currentValue: product.votes.downVotes.currentValue + 1, - }, - }, - }; - } - - return product; - }); - - this.setState({ products: updatedProducts }); - } - - handleUpVote(productId) { - const { products } = this.state; - - const updatedProducts = products.map((product) => { - if ( - product.id === productId && - product.votes.upVotes.currentValue < product.votes.upVotes.upperLimit - ) { - return { - ...product, - votes: { - ...product.votes, - upVotes: { - ...product.votes.upVotes, - currentValue: product.votes.upVotes.currentValue + 1, - }, - }, - }; - } - - return product; - }); - - this.setState({ products: updatedProducts }); - } - - handleSetFavorite(productId) { - const { products } = this.state; - - const updatedProducts = products.map((product) => { - if (product.id === productId) { - return { - ...product, - isFavorite: !product.isFavorite, - }; - } - - return product; - }); - - this.setState({ products: updatedProducts }); - } - - saveNewProduct(newProduct) { - this.setState((prevState) => ({ - products: [newProduct, ...prevState.products], - newProductFormOpen: !prevState.newProductFormOpen, - })); - } - - render() { - const { - cartItems, - products, - isLoading, - hasError, - loadingError, - } = this.state; - - return ( - - ( - - )} - /> - ( - - )} - /> - - ); - } +function App(props) { + const [products, setProducts] = useState([]); + const [cartItems, setCartItems] = useState([]); + const [{ isLoading, hasError, loadingError }, setLoadingInfo] = useState({ isLoading: false, hasError: false, loadingError: null }); + + useEffect(() => { + const prevItems = loadLocalStorageData(); + + if (!prevItems) { + setLoadingInfo((prevState) => ({ ...prevState, isLoading: true })); + + api.getProducts().then((data) => { + setProducts(() => data); + setLoadingInfo((prevState) => ({ ...prevState, isLoading: true })); + }); + + return; + } + + setCartItems(() => prevItems.cartItems); + setProducts(() => prevItems.products); + }, []); + + useEffect(() => { + localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify({ cartItems, products })); + }, [cartItems, products]); + + function handleAddToCart(productId) { + const prevCartItem = cartItems.find((item) => item.id === productId); + const foundProduct = products.find((product) => product.id === productId); + + if (prevCartItem) { + const updatedCartItems = cartItems.map((item) => { + if (item.id !== productId) { + return item; + } + + if (item.quantity >= item.unitsInStock) { + return item; + } + + return { + ...item, + quantity: item.quantity + 1, + }; + }); + + setCartItems(() => updatedCartItems); + return; + } + + const updatedProduct = buildNewCartItem(foundProduct); + setCartItems((prevCartItems) => [...prevCartItems, updatedProduct]); + } + + function handleChange(event, productId) { + const updatedCartItems = cartItems.map((item) => { + if (item.id === productId && item.quantity <= item.unitsInStock) { + return { + ...item, + quantity: Number(event.target.value), + }; + } + + return item; + }); + + setCartItems(() => updatedCartItems); + } + + function handleRemove(productId) { + const updatedCartItems = cartItems.filter((item) => item.id !== productId); + + setCartItems(() => updatedCartItems); + } + + function handleDownVote(productId) { + const updatedProducts = products.map((product) => { + if (product.id === productId && product.votes.downVotes.currentValue < product.votes.downVotes.lowerLimit) { + return { + ...product, + votes: { + ...product.votes, + downVotes: { + ...product.votes.downVotes, + currentValue: product.votes.downVotes.currentValue + 1, + }, + }, + }; + } + + return product; + }); + + setProducts(() => updatedProducts); + } + + function handleUpVote(productId) { + const updatedProducts = products.map((product) => { + if (product.id === productId && product.votes.upVotes.currentValue < product.votes.upVotes.upperLimit) { + return { + ...product, + votes: { + ...product.votes, + upVotes: { + ...product.votes.upVotes, + currentValue: product.votes.upVotes.currentValue + 1, + }, + }, + }; + } + + return product; + }); + + setProducts(() => updatedProducts); + } + + function handleSetFavorite(productId) { + const updatedProducts = products.map((product) => { + if (product.id === productId) { + return { + ...product, + isFavorite: !product.isFavorite, + }; + } + + return product; + }); + + setProducts(() => updatedProducts); + } + + function saveNewProduct(newProduct) { + setProducts((prevProducts) => [newProduct, ...prevProducts]); + } + + return ( + + } /> + } /> + + ); } export default App; diff --git a/src/components/NewProductForm/NewProductForm.js b/src/components/NewProductForm/NewProductForm.js index 3bb3ea1..b56939c 100644 --- a/src/components/NewProductForm/NewProductForm.js +++ b/src/components/NewProductForm/NewProductForm.js @@ -1,7 +1,7 @@ -import React, { Component } from "react"; +import React, { useState } from "react"; import { Redirect } from "react-router-dom"; import { v4 as uuid } from "uuid"; -import { Formik } from "formik"; +import { useFormik } from "formik"; import Input from "../Input"; import Button from "../Button"; @@ -9,195 +9,75 @@ import Button from "../Button"; import productSchema from "./product-schema"; function addProductDetails(product) { - return { - id: uuid(), - ...product, - quantity: 0, - isFavorite: false, - createdAt: new Date().toISOString(), - updatedAt: new Date().toISOString(), - votes: { - upVotes: { - upperLimit: 10, - currentValue: 0, - }, - downVotes: { - lowerLimit: 10, - currentValue: 0, - }, - }, - author: { - id: uuid(), - ...product.author, - }, - }; + return { + id: uuid(), + ...product, + quantity: 0, + isFavorite: false, + createdAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + votes: { + upVotes: { + upperLimit: 10, + currentValue: 0, + }, + downVotes: { + lowerLimit: 10, + currentValue: 0, + }, + }, + author: { + id: uuid(), + ...product.author, + }, + }; } -class NewProductForm extends Component { - constructor(props) { - super(props); - this.state = { - submitted: false, - }; +function NewProductForm(props) { + const [submitted, setSubmitted] = useState(false); + const { saveNewProduct } = props; + const { handleChange, handleBlur, handleSubmit, errors, values, touched, isValidating, isValid, isSubmitting } = useFormik({ + initialValues: { + title: "", + price: 0, + img: "", + shortDescription: "", + longDescription: "", + unitsInStock: 0, + authorFirstName: "", + authorLastName: "", + authorEmail: "", + }, + validationSchema: productSchema, + onSubmit: function (values, { setSubmitting }) { + const newProduct = addProductDetails(values); - this.setSubmitted = this.setSubmitted.bind(this); - } + saveNewProduct(newProduct); + setSubmitting(true); + setTimeout(() => setSubmitted(true), 1000); + }, + }); - setSubmitted() { - setTimeout(() => { - this.setState({ - submitted: true, - }); - }, 500); - } - - render() { - const { submitted } = this.state; - const { saveNewProduct } = this.props; - - return ( - <> - { - const newProduct = addProductDetails(values); - saveNewProduct(newProduct); - setSubmitting(true); - this.setSubmitted(); - }} - > - {({ - handleChange, - handleBlur, - handleSubmit, - errors, - values, - touched, - isValidating, - isValid, - isSubmitting, - }) => ( -
- - - - - - - - - - -
- )} -
- {submitted && } - - ); - } + return ( + <> +
+ {" "} + {" "} + {" "} + {" "} + {" "} + {" "} + {" "} + {" "} + {" "} + +
+ {submitted && } + + ); } export default NewProductForm;