diff --git a/public/coffee-mug.png b/public/coffee-mug.png
new file mode 100644
index 000000000..c19c2088d
Binary files /dev/null and b/public/coffee-mug.png differ
diff --git a/public/coffee-mug2.png b/public/coffee-mug2.png
new file mode 100644
index 000000000..531608948
Binary files /dev/null and b/public/coffee-mug2.png differ
diff --git a/src/02-component-patterns/assets/no-image.jpg b/src/02-component-patterns/assets/no-image.jpg
new file mode 100644
index 000000000..ae122b097
Binary files /dev/null and b/src/02-component-patterns/assets/no-image.jpg differ
diff --git a/src/02-component-patterns/components/ProductButtons.tsx b/src/02-component-patterns/components/ProductButtons.tsx
new file mode 100644
index 000000000..cd1b6ce28
--- /dev/null
+++ b/src/02-component-patterns/components/ProductButtons.tsx
@@ -0,0 +1,35 @@
+import { useContext } from "react";
+import { ProductContext } from "./ProductCard";
+
+import styles from '../styles/styles.module.css'
+
+export interface Props {
+ className?: string;
+ style?: React.CSSProperties;
+}
+
+export const ProductButtons = ({ className, style }: Props) => {
+
+ const { increaseBy, counter, isMaxCountedReached } = useContext( ProductContext );
+
+
+
+
+ return (
+
+
+
+
{ counter }
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/src/02-component-patterns/components/ProductCard.tsx b/src/02-component-patterns/components/ProductCard.tsx
new file mode 100644
index 000000000..eb3834b20
--- /dev/null
+++ b/src/02-component-patterns/components/ProductCard.tsx
@@ -0,0 +1,53 @@
+import { createContext } from 'react';
+
+import { useProduct } from '../hooks/useProduct';
+import { ProductContextProps, Product, onChangeArgs, InitialValuesProps, ProductCardHandler } from '../interfaces/interfaces';
+
+import styles from '../styles/styles.module.css'
+
+export const ProductContext = createContext({} as ProductContextProps);
+const { Provider } = ProductContext;
+
+
+export interface Props {
+ product: Product;
+ // children?: React.ReactElement | React.ReactElement[];
+ children?: (args: ProductCardHandler) => JSX.Element
+ className?: string;
+ style?: React.CSSProperties;
+ onChange?: ( args: onChangeArgs ) => void;
+ value?: number;
+ initialValues?:InitialValuesProps
+}
+
+
+export const ProductCard = ({ children, product, className, style, onChange, value, initialValues }: Props ) => {
+
+ const { counter, increaseBy, maxCount, isMaxCountedReached, reset } = useProduct({ onChange, product, value, initialValues });
+
+ return (
+
+
+ { children && children(
+ {
+ count: counter,
+ isMaxCountedReached,
+ product,
+ maxCount,
+ increaseBy,
+ reset
+ })
+ }
+
+
+ )
+}
diff --git a/src/02-component-patterns/components/ProductImage.tsx b/src/02-component-patterns/components/ProductImage.tsx
new file mode 100644
index 000000000..cc777c41e
--- /dev/null
+++ b/src/02-component-patterns/components/ProductImage.tsx
@@ -0,0 +1,36 @@
+import { useContext } from 'react';
+import { ProductContext } from './ProductCard';
+
+import styles from '../styles/styles.module.css'
+import noImage from '../assets/no-image.jpg';
+
+export interface Props {
+ img?: string;
+ className?: string;
+ style?: React.CSSProperties
+}
+
+
+export const ProductImage = ({ img, className, style }: Props ) => {
+
+ const { product } = useContext( ProductContext );
+ let imgToShow: string;
+
+ if ( img ) {
+ imgToShow = img;
+ } else if ( product.img ) {
+ imgToShow = product.img
+ } else {
+ imgToShow = noImage;
+ }
+
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/02-component-patterns/components/ProductTitle.tsx b/src/02-component-patterns/components/ProductTitle.tsx
new file mode 100644
index 000000000..85454fd4d
--- /dev/null
+++ b/src/02-component-patterns/components/ProductTitle.tsx
@@ -0,0 +1,26 @@
+import { useContext } from 'react';
+import { ProductContext } from "./ProductCard";
+
+import styles from '../styles/styles.module.css'
+
+
+export interface Props {
+ className?: string
+ title?: string,
+ activeClass?: string;
+ style?: React.CSSProperties
+}
+
+export const ProductTitle = ({ title, className, style }: Props) => {
+
+ const { product } = useContext( ProductContext )
+
+ return (
+
+ { title ? title : product.title }
+
+ );
+}
\ No newline at end of file
diff --git a/src/02-component-patterns/components/index.ts b/src/02-component-patterns/components/index.ts
new file mode 100644
index 000000000..579387b8b
--- /dev/null
+++ b/src/02-component-patterns/components/index.ts
@@ -0,0 +1,21 @@
+import { ProductCard as ProductCardHOC } from './ProductCard';
+import { ProductCardHOCProps } from '../interfaces/interfaces';
+
+import { ProductButtons } from './ProductButtons';
+import { ProductImage } from './ProductImage';
+import { ProductTitle } from './ProductTitle';
+
+export { ProductButtons } from './ProductButtons';
+export { ProductImage } from './ProductImage';
+export { ProductTitle } from './ProductTitle';
+
+
+export const ProductCard: ProductCardHOCProps = Object.assign( ProductCardHOC, {
+ Title: ProductTitle,
+ Image: ProductImage,
+ Buttons: ProductButtons
+})
+
+
+export default ProductCard;
+
diff --git a/src/02-component-patterns/data/products.ts b/src/02-component-patterns/data/products.ts
new file mode 100644
index 000000000..c7f21c651
--- /dev/null
+++ b/src/02-component-patterns/data/products.ts
@@ -0,0 +1,15 @@
+import { Product } from '../interfaces/interfaces';
+
+const product1 = {
+ id: '1',
+ title: 'Coffee Mug - Card',
+ img: './coffee-mug.png'
+}
+
+const product2 = {
+ id: '2',
+ title: 'Coffee Mug - Meme',
+ img: './coffee-mug2.png'
+}
+
+export const products: Product[] = [ product1, product2 ];
\ No newline at end of file
diff --git a/src/02-component-patterns/hooks/useProduct.ts b/src/02-component-patterns/hooks/useProduct.ts
new file mode 100644
index 000000000..3a4225004
--- /dev/null
+++ b/src/02-component-patterns/hooks/useProduct.ts
@@ -0,0 +1,71 @@
+import { useCallback, useEffect, useMemo, useRef, useState } from "react";
+import {
+ onChangeArgs,
+ Product,
+ InitialValuesProps,
+} from "../interfaces/interfaces";
+
+interface useProductArgs {
+ product: Product;
+ onChange?: (args: onChangeArgs) => void;
+ value?: number;
+ initialValues?: InitialValuesProps;
+}
+
+export const useProduct = ({
+ onChange,
+ product,
+ value = 0,
+ initialValues,
+}: useProductArgs) => {
+ const [counter, setCounter] = useState(initialValues?.count || value);
+ const isMounted = useRef(false);
+
+ const maxCount = useMemo(
+ () => initialValues?.maxCount,
+ [initialValues?.maxCount]
+ );
+
+ const increaseBy = (value: number) => {
+ // if (counter === initialValues?.maxCount) {
+ // return;
+ // }
+ let newValue = Math.max(counter + value, 0);
+ if (initialValues?.maxCount) {
+ newValue = Math.min(initialValues.maxCount, newValue);
+ }
+ setCounter(newValue);
+
+ onChange && onChange({ count: newValue, product });
+ };
+
+ const isMaxCountedReached = useCallback(() => {
+ if (counter === maxCount) {
+ return true;
+ }
+ return false;
+ }, [counter, maxCount]);
+
+ const reset = () => {
+ setCounter(initialValues?.count || value);
+ };
+
+ useEffect(() => {
+ if (!isMounted.current) {
+ return;
+ }
+ setCounter(value);
+ }, [value]);
+
+ useEffect(() => {
+ isMounted.current = true;
+ }, []);
+
+ return {
+ counter,
+ increaseBy,
+ maxCount,
+ isMaxCountedReached,
+ reset,
+ };
+};
diff --git a/src/02-component-patterns/hooks/useShoppingCart.ts b/src/02-component-patterns/hooks/useShoppingCart.ts
new file mode 100644
index 000000000..d62d2710f
--- /dev/null
+++ b/src/02-component-patterns/hooks/useShoppingCart.ts
@@ -0,0 +1,34 @@
+import { useState } from 'react';
+import { Product, ProductInCart } from '../interfaces/interfaces';
+
+
+
+export const useShoppingCart = () => {
+
+ const [ shoppingCart, setShoppingCart ] = useState<{ [key:string]: ProductInCart }>({});
+
+ const onProductCountChange = ({ count, product }: { count:number, product: Product }) => {
+
+ console.log({ count })
+
+ setShoppingCart( oldShoppingCart => {
+
+ if( count === 0 ) {
+ const { [product.id]: toDelete, ...rest } = oldShoppingCart;
+ return rest;
+ }
+
+ return {
+ ...oldShoppingCart,
+ [ product.id ]: { ...product, count }
+ }
+ })
+
+ }
+
+ return {
+ shoppingCart,
+ onProductCountChange,
+ }
+
+}
\ No newline at end of file
diff --git a/src/02-component-patterns/interfaces/interfaces.ts b/src/02-component-patterns/interfaces/interfaces.ts
new file mode 100644
index 000000000..8d7e844a5
--- /dev/null
+++ b/src/02-component-patterns/interfaces/interfaces.ts
@@ -0,0 +1,47 @@
+import { Props as ProductButtonsProps } from "../components/ProductButtons";
+import { Props as ProductCardProps } from "../components/ProductCard";
+import { Props as ProductImageProps } from "../components/ProductImage";
+import { Props as ProductTitleProps } from "../components/ProductTitle";
+
+export interface Product {
+ id: string;
+ img?: string;
+ title: string;
+}
+
+export interface ProductContextProps {
+ counter: number;
+ product: Product;
+ increaseBy: (value: number) => void;
+ maxCount?: number;
+ isMaxCountedReached: () => boolean;
+}
+
+export interface ProductCardHOCProps {
+ ({ children, product }: ProductCardProps): JSX.Element;
+ Buttons: (Props: ProductButtonsProps) => JSX.Element;
+ Image: (Props: ProductImageProps) => JSX.Element;
+ Title: (Props: ProductTitleProps) => JSX.Element;
+}
+
+export interface onChangeArgs {
+ product: Product;
+ count: number;
+}
+
+export interface ProductInCart extends Product {
+ count: number;
+}
+
+export interface InitialValuesProps {
+ count?: number;
+ maxCount?: number;
+}
+export interface ProductCardHandler {
+ count?: number;
+ isMaxCountedReached: () => boolean;
+ maxCount?: number;
+ product: Product;
+ increaseBy: (value: number) => void;
+ reset: () => void;
+}
diff --git a/src/02-component-patterns/pages/ShoppingPage.tsx b/src/02-component-patterns/pages/ShoppingPage.tsx
new file mode 100644
index 000000000..b4d56835a
--- /dev/null
+++ b/src/02-component-patterns/pages/ShoppingPage.tsx
@@ -0,0 +1,41 @@
+import { ProductCard, ProductImage, ProductTitle, ProductButtons } from '../components';
+
+import { products } from '../data/products';
+
+
+const product = products[0]
+
+
+
+export const ShoppingPage = () => {
+
+ return (
+
+
Shopping Store
+
+
+
+ {
+ ( { reset, increaseBy, count, isMaxCountedReached, maxCount }) => (
+ <>
+
+
+
+
+
+ >
+ )
+ }
+
+
+
+
+ )
+}
diff --git a/src/02-component-patterns/styles/styles.module.css b/src/02-component-patterns/styles/styles.module.css
new file mode 100644
index 000000000..46851a2d8
--- /dev/null
+++ b/src/02-component-patterns/styles/styles.module.css
@@ -0,0 +1,74 @@
+
+
+.productCard {
+ background-color: #1E2025;
+ border-radius: 15px;
+ box-shadow: 0px 2px 10px rgba(0,0,0,0.15);
+ color: white;
+ padding-bottom: 5px;
+ font-family: Arial, Helvetica, sans-serif;
+ width: 250px;
+ margin-right: 5px;
+ margin-top: 5px;
+}
+
+.productImg {
+ border-radius: 15px 15px 0px 0px;
+ width: 100%;
+}
+
+.productDescription {
+ margin: 10px;
+}
+
+.buttonsContainer {
+ margin: 10px;
+ display: flex;
+ flex-direction: row;
+}
+
+.buttonMinus {
+ cursor: pointer;
+ background-color: transparent;
+ border: 1px solid white;
+ border-radius: 5px 0px 0px 5px;
+ color: white;
+ font-size: 20px;
+ width: 30px;
+}
+
+.buttonMinus:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.countLabel {
+ border-bottom: 1px solid white;
+ border-top: 1px solid white;
+ color: white;
+ font-size: 16px;
+ height: 25px;
+ padding-top: 5px;
+ text-align: center;
+ width: 30px;
+}
+
+.buttonAdd {
+ cursor: pointer;
+ background-color: transparent;
+ border: 1px solid white;
+ border-radius: 0px 5px 5px 0px;
+ color: white;
+ font-size: 20px;
+ width: 30px;
+}
+
+.buttonAdd:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+
+.disabled {
+ border-color: grey !important;
+ border-left: 1px solid white !important;
+ color: grey !important;
+}
\ No newline at end of file
diff --git a/src/routes/Navigation.tsx b/src/routes/Navigation.tsx
index a9bfe83b0..4d59ed414 100644
--- a/src/routes/Navigation.tsx
+++ b/src/routes/Navigation.tsx
@@ -6,6 +6,7 @@ import {
} from 'react-router-dom';
import logo from '../logo.svg';
+import { ShoppingPage } from '../02-component-patterns/pages/ShoppingPage';
export const Navigation = () => {
return (
@@ -15,7 +16,7 @@ export const Navigation = () => {
-
- Home
+ Shopping
-
About
@@ -36,7 +37,7 @@ export const Navigation = () => {
Users
- Home
+