diff --git a/package-lock.json b/package-lock.json index d01ddfa..bbd664e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,6 +5,7 @@ "requires": true, "packages": { "": { + "name": "react-ecommerce", "version": "0.0.1", "dependencies": { "@headlessui/react": "^1.6.0", diff --git a/src/Components/Cart.jsx b/src/Components/Cart.jsx index 917b7a1..425667a 100644 --- a/src/Components/Cart.jsx +++ b/src/Components/Cart.jsx @@ -1,15 +1,20 @@ -import { Dialog, Transition } from "@headlessui/react"; -import { XIcon } from "@heroicons/react/outline"; -import React, { Fragment } from "react"; +import { Dialog, Transition } from '@headlessui/react' +import { XIcon, ShoppingCartIcon } from '@heroicons/react/outline' +import React, { Fragment } from 'react' export default function Cart({ open, setOpen, cart, updateCart }) { + const subTotal = cart.reduce((s, p) => p.price * p.quantity + s, 0) + return ( - + { - setOpen; + setOpen }} >
@@ -21,6 +26,7 @@ export default function Cart({ open, setOpen, cart, updateCart }) { leave="ease-in-out duration-500" leaveFrom="opacity-100" leaveTo="opacity-0" + onClick={() => setOpen(false)} > @@ -39,7 +45,10 @@ export default function Cart({ open, setOpen, cart, updateCart }) {
- Shopping cart + + {' '} + Shopping cart{' '} +
-
+
-
    - {cart.map((product) => ( -
  • -
    - {product.imageAlt} -
    + {cart.length === 0 ? ( +
    +
    + ) : ( +
      + {cart.map((product) => ( +
    • +
      + {product.imageAlt} +
      -
      -
      -
      -

      {product.name}

      -

      ${product.price}

      +
      +
      +
      +

      {product.name}

      +

      ${product.price}

      +
      -
      -
      -

      Qty {product.quantity}

      +
      +

      + Qty {product.quantity} +

      -
      - + return p.quantity > 0 + }) + updateCart(newCart) + // Issue #2 + localStorage.setItem( + 'cart', + JSON.stringify(newCart) + ) + }} + type="button" + className="font-medium text-gray-500 hover:text-black" + > + Remove + +
      -
      -
    • - ))} -
    +
  • + ))} +
+ )}
@@ -105,9 +146,11 @@ export default function Cart({ open, setOpen, cart, updateCart }) {

Subtotal

-

$262.00

+

${subTotal.toFixed(2)}

-

Shipping and taxes calculated at checkout.

+

+ Shipping and taxes calculated at checkout. +

- ); + ) } diff --git a/src/Components/NavBar.jsx b/src/Components/NavBar.jsx index b1d0db9..56ea32f 100644 --- a/src/Components/NavBar.jsx +++ b/src/Components/NavBar.jsx @@ -1,7 +1,7 @@ -import { ShoppingBagIcon } from "@heroicons/react/outline"; -import React from "react"; +import { ShoppingBagIcon } from '@heroicons/react/outline' +import React from 'react' -export default function NavBar({ setOpen }) { +export default function NavBar({ setOpen, productsInCart }) { return (
@@ -23,7 +23,10 @@ export default function NavBar({ setOpen }) {
{/* Logo (lg-) */} -
+ Workflow {/* Cart Icon */}
-
@@ -54,5 +64,5 @@ export default function NavBar({ setOpen }) { - ); + ) } diff --git a/src/Components/ProductFilters.jsx b/src/Components/ProductFilters.jsx index 0f3262a..141b1d1 100644 --- a/src/Components/ProductFilters.jsx +++ b/src/Components/ProductFilters.jsx @@ -1,19 +1,52 @@ -import { Disclosure, Menu, Transition } from "@headlessui/react"; -import { ChevronDownIcon, FilterIcon } from "@heroicons/react/solid"; -import React, { Fragment } from "react"; +import { Disclosure, Menu, Transition } from '@headlessui/react' +import { ChevronDownIcon, FilterIcon } from '@heroicons/react/solid' +import React, { Fragment, useEffect, useState } from 'react' function classNames(...classes) { - return classes.filter(Boolean).join(" "); + return classes.filter(Boolean).join(' ') } -export default function ProductFilters({ filterOptions, setFilterOptions, sortOptions, setSortOptions }) { +export default function ProductFilters({ + filterOptions, + setFilterOptions, + sortOptions, + setSortOptions, + handleClear, +}) { + function handleFilterChange(event) { + const filter = event.target.value + + if (event.target.name.startsWith('color')) { + // Color filter changed + const newColorArray = filterOptions.color.map((c) => + c.value === filter ? { ...c, checked: !c.checked } : c + ) + setFilterOptions({ ...filterOptions, color: newColorArray }) + } else { + // Price filter changed + const newPriceArray = filterOptions.price.map((p) => + p.minValue === Number(filter) ? { ...p, checked: !p.checked } : p + ) + setFilterOptions({ ...filterOptions, price: newPriceArray }) + } + } + function countFilter() { + return ( + filterOptions.price.reduce((s, p) => Number(p.checked) + s, 0) + + filterOptions.color.reduce((s, c) => Number(c.checked) + s, 0) + ) + } + return ( -

+

Filters

@@ -24,11 +57,15 @@ export default function ProductFilters({ filterOptions, setFilterOptions, sortOp className="flex-none w-5 h-5 mr-2 text-gray-400 group-hover:text-gray-500" aria-hidden="true" /> - 0 Filters + {countFilter()} Filters
-
@@ -41,16 +78,23 @@ export default function ProductFilters({ filterOptions, setFilterOptions, sortOp Price
{filterOptions.price.map((option, optionIdx) => ( -
+
-
@@ -61,16 +105,24 @@ export default function ProductFilters({ filterOptions, setFilterOptions, sortOp Color
{filterOptions.color.map((option, optionIdx) => ( -
+
-
@@ -82,7 +134,10 @@ export default function ProductFilters({ filterOptions, setFilterOptions, sortOp
- +
Sort @@ -110,11 +165,28 @@ export default function ProductFilters({ filterOptions, setFilterOptions, sortOp
- ); + ) } diff --git a/src/Components/ProductTable.jsx b/src/Components/ProductTable.jsx index ea76621..aa63e91 100644 --- a/src/Components/ProductTable.jsx +++ b/src/Components/ProductTable.jsx @@ -1,57 +1,119 @@ -import React, { useEffect, useState } from "react"; -import ProductFilters from "./ProductFilters"; +import React, { useEffect, useState } from 'react' +import ProductFilters from './ProductFilters' const getDefaultFilterOptions = () => { return { price: [ - { minValue: 0, maxValue: 25, label: "$0 - $25", checked: false }, - { minValue: 25, maxValue: 50, label: "$25 - $50", checked: false }, - { minValue: 50, maxValue: 75, label: "$50 - $75", checked: false }, - { minValue: 75, maxValue: Number.MAX_VALUE, label: "$75+", checked: false }, + { minValue: 0, maxValue: 25, label: '$0 - $25', checked: false }, + { minValue: 25, maxValue: 50, label: '$25 - $50', checked: false }, + { minValue: 50, maxValue: 75, label: '$50 - $75', checked: false }, + { + minValue: 75, + maxValue: Number.MAX_VALUE, + label: '$75+', + checked: false, + }, ], color: [ - { value: "beige", label: "Beige", checked: false }, - { value: "green", label: "Green", checked: false }, - { value: "white", label: "White", checked: false }, - { value: "black", label: "Black", checked: false }, - { value: "gray", label: "Gray", checked: false }, - { value: "teal", label: "Teal", checked: false }, + { value: 'beige', label: 'Beige', checked: false }, + { value: 'green', label: 'Green', checked: false }, + { value: 'white', label: 'White', checked: false }, + { value: 'black', label: 'Black', checked: false }, + { value: 'gray', label: 'Gray', checked: false }, + { value: 'teal', label: 'Teal', checked: false }, ], - }; -}; + } +} const getDefaultSortOptions = () => { return [ - { name: "Price", current: false }, - { name: "Newest", current: false }, - ]; -}; + { name: 'Price', current: false }, + { name: 'Newest', current: false }, + ] +} export default function ProductTable({ cart, updateCart }) { - let [products, setProducts] = useState([]); + let [products, setProducts] = useState([]) - const [filterOptions, setFilterOptions] = useState(getDefaultFilterOptions()); - const [sortOptions, setSortOptions] = useState(getDefaultSortOptions()); + const [filterOptions, setFilterOptions] = useState(getDefaultFilterOptions()) + const [sortOptions, setSortOptions] = useState(getDefaultSortOptions()) useEffect(() => { let fetchProducts = async () => { - console.info("Fetching Products..."); - let res = await fetch("http://localhost:3001/products"); - let body = await res.json(); - setProducts(body); - }; - fetchProducts(); - }); + console.info('Fetching Products...') + let res = await fetch('http://localhost:3001/products') + let body = await res.json() + + setProducts(body) + } + fetchProducts() + }, []) + + // Filter products + const filterProducts = () => { + let filteredProducts = products + const colorFilter = filterOptions.color + .filter((c) => c.checked) + .map((c) => c.value) + const priceFilter = filterOptions.price + .filter((p) => p.checked) + .map((p) => { + return { minF: p.minValue, maxF: p.maxValue } + }) + + if (colorFilter.length > 0) + filteredProducts = filteredProducts.filter((p) => + colorFilter.includes(p.color) + ) + if (priceFilter.length > 0) { + filteredProducts = filteredProducts.filter((p) => + priceFilter.some(({ minF, maxF }) => p.price >= minF && p.price <= maxF) + ) + } + return filteredProducts + } + + // Sort products + const sortProducts = (p) => { + const price = sortOptions.find((o) => o.name === 'Price') + const newest = sortOptions.find((o) => o.name === 'Newest') + + if (price.current) { + return p.sort((a, b) => a.price - b.price) + } else if (newest.current) { + return p.sort((a, b) => a.releaseDate - b.releaseDate) + } else { + return p + } + } + + function handleClear(event) { + setFilterOptions(getDefaultFilterOptions()) + } + + const filteredProducts = filterProducts() + const productsToShow = sortProducts(filteredProducts) return (
- ); + ) } diff --git a/src/Pages/Home.jsx b/src/Pages/Home.jsx index 1a5d5d9..017c1b4 100644 --- a/src/Pages/Home.jsx +++ b/src/Pages/Home.jsx @@ -1,19 +1,33 @@ -import React, { useState } from "react"; -import Cart from "../Components/Cart"; -import NavBar from "../Components/NavBar"; -import ProductTable from "../Components/ProductTable"; +import React, { useEffect, useState } from 'react' +import Cart from '../Components/Cart' +import NavBar from '../Components/NavBar' +import ProductTable from '../Components/ProductTable' function Home() { - const [open, setOpen] = useState(false); - const [cart, updateCart] = useState([]); + const [open, setOpen] = useState(false) + const [cart, updateCart] = useState([]) + + // Issue #2 + useEffect(() => { + const storedCart = localStorage.getItem('cart') + if (storedCart) { + updateCart(JSON.parse(storedCart)) + } + }, []) + + useEffect(() => { + localStorage.setItem('cart', JSON.stringify(cart)) + }, [cart]) + + const productsInCart = cart.reduce((s, p) => p.quantity + s, 0) return (
- +
- ); + ) } -export default Home; +export default Home diff --git a/todo.md b/todo.md index e69de29..c8e4798 100644 --- a/todo.md +++ b/todo.md @@ -0,0 +1 @@ +#1 fix that only one option is active