Skip to content

fixed all issues #79

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

26 changes: 21 additions & 5 deletions src/Components/Cart.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,17 @@
import { Dialog, Transition } from "@headlessui/react";
import { XIcon } from "@heroicons/react/outline";
import { ShoppingCartIcon, XIcon } from "@heroicons/react/outline";
import React, { Fragment } from "react";

export default function Cart({ open, setOpen, cart, updateCart }) {

const subtotal = () => {
let total = 0;
for(let cartItem of cart){
total += cartItem.price * cartItem.quantity
}
return total.toFixed(2);
}

return (
<Transition.Root show={open} as={Fragment}>
<Dialog
Expand All @@ -22,7 +31,7 @@ export default function Cart({ open, setOpen, cart, updateCart }) {
leaveFrom="opacity-100"
leaveTo="opacity-0"
>
<Dialog.Overlay className="absolute inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
<Dialog.Overlay onClick={() => setOpen(false)} className="absolute inset-0 bg-gray-500 bg-opacity-75 transition-opacity" />
</Transition.Child>

<div className="pointer-events-none fixed inset-y-0 right-0 flex max-w-full pl-10">
Expand Down Expand Up @@ -51,7 +60,6 @@ export default function Cart({ open, setOpen, cart, updateCart }) {
</button>
</div>
</div>

<div className="mt-8">
<div className="flow-root">
<ul role="list" className="-my-6 divide-y divide-gray-200">
Expand Down Expand Up @@ -101,11 +109,19 @@ export default function Cart({ open, setOpen, cart, updateCart }) {
</div>
</div>
</div>

{/* show Empty Cart text when no cart is empty */}
{cart.length === 0 && (
<div className="flex-grow justify-center items-center">
<div className="text-center">
<ShoppingCartIcon className="w-12 mx-auto" />
<p className="pt-2">Your Cart is Empty.</p>
</div>
</div>
)}
<div className="border-t border-gray-200 py-6 px-4 sm:px-6">
<div className="flex justify-between text-base font-medium text-gray-900">
<p>Subtotal</p>
<p>$262.00</p>
<p>${subtotal()}</p>
</div>
<p className="mt-0.5 text-sm text-gray-500">Shipping and taxes calculated at checkout.</p>
<div className="mt-6">
Expand Down
4 changes: 2 additions & 2 deletions src/Components/NavBar.jsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { ShoppingBagIcon } from "@heroicons/react/outline";
import React from "react";

export default function NavBar({ setOpen }) {
export default function NavBar({ setOpen, cartSize=0 }) {
return (
<div className="bg-white">
<header className="relative">
Expand Down Expand Up @@ -41,7 +41,7 @@ export default function NavBar({ setOpen }) {
className="flex-shrink-0 h-6 w-6 text-gray-400 group-hover:text-gray-500"
aria-hidden="true"
/>
<span className="ml-2 text-sm font-medium text-gray-700 group-hover:text-gray-800">0</span>
<span className="ml-2 text-sm font-medium text-gray-700 group-hover:text-gray-800">{cartSize}</span>
<span className="sr-only">items in cart, view bag</span>
</button>
</div>
Expand Down
46 changes: 41 additions & 5 deletions src/Components/ProductFilters.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,44 @@
import { Disclosure, Menu, Transition } from "@headlessui/react";
import { ChevronDownIcon, FilterIcon } from "@heroicons/react/solid";
import React, { Fragment } from "react";
import React, { Fragment, useState } from "react";

function classNames(...classes) {
return classes.filter(Boolean).join(" ");
}

export default function ProductFilters({ filterOptions, setFilterOptions, sortOptions, setSortOptions }) {
const [filterCount, setFilterCount] = useState(0)

const handleUpdateOption = (category, index, checked) => {
let newFilterOptions = { ...filterOptions };
newFilterOptions[category][index] = {
...newFilterOptions[category][index],
checked
}
// update filter options
setFilterOptions(newFilterOptions);
updateFilterCount(newFilterOptions)
}

const updateFilterCount = (newFilterOptions) => {
// count enabled filters
let priceFilterCount = newFilterOptions.price.filter(filter => filter.checked).length;
let colorFilterCount = newFilterOptions.color.filter(filter => filter.checked).length;

// update filter count
setFilterCount(priceFilterCount + colorFilterCount);
}

const handleClearFilters = () => {
let newFilterOptions = {
price: filterOptions.price.map(f => ({ ...f, checked: false })),
color: filterOptions.color.map(f => ({ ...f, checked: false })),
}

setFilterOptions(newFilterOptions);
updateFilterCount(newFilterOptions)
}

return (
<Disclosure
as="section"
Expand All @@ -24,11 +56,11 @@ 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
{filterCount} Filters
</Disclosure.Button>
</div>
<div className="pl-6">
<button type="button" className="text-gray-500">
<button type="button" onClick={handleClearFilters} className="text-gray-500">
Clear all
</button>
</div>
Expand All @@ -47,8 +79,9 @@ export default function ProductFilters({ filterOptions, setFilterOptions, sortOp
name="price[]"
defaultValue={option.minValue}
type="checkbox"
onChange={(e) => handleUpdateOption('price', optionIdx, e.target.checked)}
checked={option.checked}
className="flex-shrink-0 h-4 w-4 border-gray-300 rounded text-black focus:ring-black"
defaultChecked={option.checked}
/>
<label htmlFor={`price-${optionIdx}`} className="ml-3 min-w-0 flex-1 text-gray-600">
{option.label}
Expand All @@ -67,8 +100,9 @@ export default function ProductFilters({ filterOptions, setFilterOptions, sortOp
name="color[]"
defaultValue={option.value}
type="checkbox"
onChange={(e) => handleUpdateOption('color', optionIdx, e.target.checked)}
className="flex-shrink-0 h-4 w-4 border-gray-300 rounded text-black focus:ring-black"
defaultChecked={option.checked}
checked={option.checked}
/>
<label htmlFor={`color-${optionIdx}`} className="ml-3 min-w-0 flex-1 text-gray-600">
{option.label}
Expand Down Expand Up @@ -110,6 +144,8 @@ export default function ProductFilters({ filterOptions, setFilterOptions, sortOp
<button
onClick={() => {
// TODO
let newSortOption = sortOptions.map((opt) => ({ ...opt, current: opt.name === option.name }))
setSortOptions(newSortOption)
}}
className={classNames(
option.current ? "font-medium text-gray-900" : "text-gray-500",
Expand Down
119 changes: 84 additions & 35 deletions src/Components/ProductTable.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,60 @@ export default function ProductTable({ cart, updateCart }) {
useEffect(() => {
let fetchProducts = async () => {
console.info("Fetching Products...");
let res = await fetch("http://localhost:3001/products");
let res = await fetch(`http://localhost:3001/products`);
let body = await res.json();
setProducts(body);
};
fetchProducts();
});
}, []);

// sort products based on sort option selected
useEffect(() => {
let newProducts = products.slice();
newProducts = newProducts.sort((prod1, prod2) => {
let sortOption = sortOptions.find((option) => option.current)
if (sortOption.name === 'Price') {
// sort asc by price
return prod1.price - prod2.price
} else {
// sort asc by release date
return prod1.releaseDate - prod2.releaseDate
}
})

setProducts(newProducts)
}, [sortOptions]);

// checks if a product matches color filter
const matchedColorFilter = (product) => {
const colorFilters = filterOptions.color;
//return true if no filter is checked
if (!colorFilters.find(f => f.checked)) return true;

for (let colorFilter of colorFilters) {
if (colorFilter.checked && product.color === colorFilter.value) {
return true;
}
}
return false;
}
// checks if a product matches price filter
const matchedPriceFilter = (product) => {
const priceFilters = filterOptions.price;
//return true if no filter is checked
if (!priceFilters.find(f => f.checked)) return true;

for (let priceFilter of priceFilters) {
if (priceFilter.checked && (product.price >= priceFilter.minValue && product.price <= priceFilter.maxValue)) {
return true;
}
}
return false;
}

const filterProducts = (products) => {
return products.filter(p => matchedColorFilter(p) && matchedPriceFilter(p))
}

return (
<div className="bg-white">
Expand All @@ -50,41 +98,42 @@ export default function ProductTable({ cart, updateCart }) {
<ProductFilters {...{ filterOptions, setFilterOptions, sortOptions, setSortOptions }} />

<div className="grid grid-cols-1 gap-y-10 sm:grid-cols-2 gap-x-6 lg:grid-cols-3 xl:grid-cols-4 xl:gap-x-8">
{products.map((product) => (
<a key={product.id} className="group">
<div className="w-full aspect-w-1 aspect-h-1 bg-gray-200 rounded-lg overflow-hidden xl:aspect-w-7 xl:aspect-h-8">
<img
src={product.imageSrc}
alt={product.imageAlt}
className="w-full h-full object-center object-cover"
/>
<button
type="button"
className="hidden group-hover:block group-hover:opacity-50 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-black"
onClick={() => {
let newCart = cart.slice();
{filterProducts(products)
.map((product) => (
<a key={product.id} className="group">
<div className="w-full aspect-w-1 aspect-h-1 bg-gray-200 rounded-lg overflow-hidden xl:aspect-w-7 xl:aspect-h-8">
<img
src={product.imageSrc}
alt={product.imageAlt}
className="w-full h-full object-center object-cover"
/>
<button
type="button"
className="hidden group-hover:block group-hover:opacity-50 border border-transparent text-base font-medium rounded-md shadow-sm text-white bg-black"
onClick={() => {
let newCart = cart.slice();

if (!newCart.includes(product)) {
product.quantity = 1;
newCart.push(product);
} else {
newCart.map((p) => {
if (p.id === product.id) {
p.quantity += 1;
}
});
}
if (!newCart.includes(product)) {
product.quantity = 1;
newCart.push(product);
} else {
newCart.map((p) => {
if (p.id === product.id) {
p.quantity += 1;
}
});
}

updateCart(newCart);
}}
>
Add To Cart
</button>
</div>
<h3 className="mt-4 text-sm text-gray-700">{product.name}</h3>
<p className="mt-1 text-lg font-medium text-gray-900">${product.price}</p>
</a>
))}
updateCart(newCart);
}}
>
Add To Cart
</button>
</div>
<h3 className="mt-4 text-sm text-gray-700">{product.name}</h3>
<p className="mt-1 text-lg font-medium text-gray-900">${product.price}</p>
</a>
))}
</div>
</div>
</div>
Expand Down
18 changes: 15 additions & 3 deletions src/Pages/Home.jsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
import React, { useState } from "react";
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 [cart, updateCart] = useState(JSON.parse(localStorage.getItem('cart') || '[]'));

useEffect(() => {
localStorage.setItem('cart',JSON.stringify(cart));
}, [cart])

const cartSize = () => {
let size = 0;
for(let cartItem of cart){
size += cartItem.quantity
}
return size;
}

return (
<main>
<NavBar {...{ setOpen }} />
<NavBar cartSize={cartSize()} {...{ setOpen }} />
<Cart {...{ open, setOpen, cart, updateCart }} />
<ProductTable {...{ cart, updateCart }} />
</main>
Expand Down
8 changes: 8 additions & 0 deletions todo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
- ✔ Sort products by price and release date
- ✔ Cart Persistence on Refresh
- ✔ Add Product Filters Functionality
- ✔ Product Fetch is Causing Infinite Loop
- ✔ Need to display "Empty Cart" when cart is empty on cart page
- ✔ Update Cart in Nav with Cart Size
- ✔ Cart not closing when user clicks gray region
- ✔ Subtotal Is not Implemented on Cart Page