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/ProducComponents.tsx b/src/02-component-patterns/components/ProducComponents.tsx
new file mode 100644
index 000000000..04ebd1ebb
--- /dev/null
+++ b/src/02-component-patterns/components/ProducComponents.tsx
@@ -0,0 +1,36 @@
+import { useContext } from "react"
+import ProductContext from "./productContext"
+import styles from '../styles/styles.module.css'
+import noImage from '../assets/no-image.jpg'
+import { Props as ProductImageProps } from "./ProductImage"
+import { Props as ProductTitleProps } from "./ProductTitle"
+import { Props as ProdtuctButtonsProps } from "./ProductButtons"
+
+
+export const ProductImage = ({img='', className = '', style }:ProductImageProps):JSX.Element => {
+ const {product} = useContext(ProductContext)
+ const { img:imageContext } = product
+ return
+}
+
+export const ProductTitle = ({title = '', className = '', style}: ProductTitleProps):JSX.Element => {
+ const {product} = useContext(ProductContext)
+ const { title: titleContext } = product
+ return { title ? title : titleContext }
+}
+export const ProductButtons = ({className = '', style}: ProdtuctButtonsProps):JSX.Element => {
+
+ const {counter, increasedBy} = useContext(ProductContext)
+
+ return (
+
+
+
{counter}
+
+
+ )
+}
\ No newline at end of file
diff --git a/src/02-component-patterns/components/ProductButtons.tsx b/src/02-component-patterns/components/ProductButtons.tsx
new file mode 100644
index 000000000..edcb24b4d
--- /dev/null
+++ b/src/02-component-patterns/components/ProductButtons.tsx
@@ -0,0 +1,29 @@
+import { CSSProperties, useContext } from "react";
+import ProductContext from "./productContext";
+
+import styles from '../styles/styles.module.css'
+
+
+export interface Props {
+ className?: string
+ style?: CSSProperties
+}
+
+export const ProductButtons = ({className, style}:Props) => {
+
+ const { increasedBy, counter } = 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..4bd195882
--- /dev/null
+++ b/src/02-component-patterns/components/ProductCard.tsx
@@ -0,0 +1,38 @@
+import styles from '../styles/styles.module.css'
+import { useProduct } from '../hooks/useProduct';
+import {ProductProps } from '../interfaces';
+import ProductContext from './productContext';
+
+import '../styles/custom-styles.css'
+import { CSSProperties, useEffect } from 'react';
+import { Product } from '../interfaces/interfaces';
+
+export interface Props extends ProductProps {
+ className?: string
+ style?: CSSProperties
+ onChange?: (count:number, product: Product ) => void
+ value?: number
+}
+
+export const ProductCard = ({product, children, className, style, onChange, value }:Props):JSX.Element => {
+ const { Provider } = ProductContext
+ // console.log(styles)
+ // console.log(className)
+
+
+ const {counter, increasedBy} = useProduct({ initialState: 0 , onChange, product, value } )
+
+ return (
+ <>
+
+
+ {children}
+
+ Provider>
+ >
+ )
+}
diff --git a/src/02-component-patterns/components/ProductImage.tsx b/src/02-component-patterns/components/ProductImage.tsx
new file mode 100644
index 000000000..2cd5be8ad
--- /dev/null
+++ b/src/02-component-patterns/components/ProductImage.tsx
@@ -0,0 +1,30 @@
+import { CSSProperties, useContext } from 'react';
+import ProductContext from './productContext';
+
+import styles from '../styles/styles.module.css'
+import noImage from '../assets/no-image.jpg';
+
+export interface Props {
+ img?: string
+ className?: string
+ style?: 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..3c3a9d7b9
--- /dev/null
+++ b/src/02-component-patterns/components/ProductTitle.tsx
@@ -0,0 +1,22 @@
+import { CSSProperties, useContext } from 'react';
+import ProductContext from './productContext';
+
+import styles from '../styles/styles.module.css'
+import '../styles/custom-styles.css'
+
+export interface Props {
+ title?: string;
+ className?: string
+ style?: 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..3cad2f0cc
--- /dev/null
+++ b/src/02-component-patterns/components/index.ts
@@ -0,0 +1,14 @@
+import { ProductCard as ProductCardHOC } from "./ProductCard";
+import { ProductImage, ProductTitle, ProductButtons } from "./ProducComponents";
+// import { IProductCard } from "../interfaces";
+import { ProductCardHOCProps, ProductCardProps } from "../interfaces/interfaces";
+
+export const ProductCard:ProductCardHOCProps = Object.assign(ProductCardHOC, {
+ Image: ProductImage,
+ Title: ProductTitle,
+ Buttons: ProductButtons,
+});
+
+export default ProductCard;
+export * from "./ProducComponents";
+export { default as ProductContext } from "./productContext";
diff --git a/src/02-component-patterns/components/productContext.ts b/src/02-component-patterns/components/productContext.ts
new file mode 100644
index 000000000..ce4d4c9d2
--- /dev/null
+++ b/src/02-component-patterns/components/productContext.ts
@@ -0,0 +1,6 @@
+import { createContext } from "react";
+import { ProductContextProps } from "../interfaces";
+
+const ProductContext = createContext({} as ProductContextProps)
+
+export default ProductContext
\ No newline at end of file
diff --git a/src/02-component-patterns/data/products.ts b/src/02-component-patterns/data/products.ts
new file mode 100644
index 000000000..16c7ea323
--- /dev/null
+++ b/src/02-component-patterns/data/products.ts
@@ -0,0 +1,18 @@
+import { Product } from "../interfaces/interfaces";
+
+export const products:Product[] = [
+ {
+ id: '1',
+ title: 'Coffee Mug | Card 1',
+ img: './coffee-mug.png'
+ },
+ {
+ id: '2',
+ title: 'Coffee Mug | Card 2',
+ img: './coffee-mug2.png'
+ },
+ // {
+ // id: '3',
+ // title: 'Coffee Mug | Card 3',
+ // },
+]
\ No newline at end of file
diff --git a/src/02-component-patterns/hooks/useProduct.tsx b/src/02-component-patterns/hooks/useProduct.tsx
new file mode 100644
index 000000000..a20f7f0a2
--- /dev/null
+++ b/src/02-component-patterns/hooks/useProduct.tsx
@@ -0,0 +1,45 @@
+import { useState, useEffect, useRef } from "react";
+import { Product } from "../interfaces/interfaces";
+
+type UseProductType = {
+ counter: number;
+ increasedBy: (value: number) => void;
+};
+
+type OnChangeType = (count: number, product: Product) => void;
+
+type UseProductArgsType = {
+ initialState: number;
+ onChange?: OnChangeType;
+ product?: Product;
+ value?: number;
+};
+
+export const useProduct = ({
+ initialState,
+ onChange,
+ product,
+ value,
+}: UseProductArgsType): UseProductType => {
+ const [counter, setcounter] = useState(initialState);
+
+ const isControlled = useRef(!!onChange);
+ // console.log("hook", value, counter, !!onChange, !!product);
+ const increasedBy = (value: number): void => {
+ if (isControlled.current) {
+ onChange!(value, product!);
+ return;
+ }
+ const newValue = Math.max(counter + value, 0);
+ setcounter(newValue);
+ };
+
+ useEffect(() => {
+ setcounter(value!);
+ }, [value, product]);
+
+ return {
+ counter,
+ increasedBy,
+ };
+};
diff --git a/src/02-component-patterns/hooks/useShoppingCart.tsx b/src/02-component-patterns/hooks/useShoppingCart.tsx
new file mode 100644
index 000000000..7c0331d09
--- /dev/null
+++ b/src/02-component-patterns/hooks/useShoppingCart.tsx
@@ -0,0 +1,43 @@
+import { useState } from 'react'
+import { Product, ProductsCart } from '../interfaces/interfaces'
+
+type ProductInCart = {[key:string] : ProductsCart}
+
+
+type UseShoppingCartResponse = {
+ onProductCountChange: (count: number, product: Product) => void;
+ shoppingCart: ProductInCart;
+}
+
+export const useShoppingCart = (): UseShoppingCartResponse => {
+
+ const [shoppingCart, setShoppingCart] = useState({ })
+
+ const onProductCountChange = (count:number, product:Product) => {
+ setShoppingCart((prev) => {
+ // console.log(count, product.id)
+
+ const productInCart: ProductsCart = prev[product.id] || { ...product, count:0 }
+
+ if( Math.max(productInCart.count + count, 0) > 0 ){
+ productInCart.count += count
+ return {
+ ...prev,
+ [productInCart.id]: productInCart
+ }
+ }
+ /* Borra elemento del carrito */
+ // const cartWithoutZeros = { ...shoppingCart }
+ // delete cartWithoutZeros[product.id]
+ const { [product.id] : toDelete, ...rest } = prev
+ return {...rest }
+
+
+ })
+}
+
+ return {
+ onProductCountChange,
+ shoppingCart
+ }
+}
diff --git a/src/02-component-patterns/interfaces/index.ts b/src/02-component-patterns/interfaces/index.ts
new file mode 100644
index 000000000..fc765a720
--- /dev/null
+++ b/src/02-component-patterns/interfaces/index.ts
@@ -0,0 +1 @@
+export * from './productsInterfaces'
\ 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..8db04a5d7
--- /dev/null
+++ b/src/02-component-patterns/interfaces/interfaces.ts
@@ -0,0 +1,36 @@
+import { CSSProperties, ReactElement } from "react";
+import { Props as ProductCardType } from "../components/ProductCard";
+import { Props as PropsProductImage } from "../components/ProductImage";
+import { Props as PropsProductTitle } from "../components/ProductTitle";
+
+export interface ProductCardProps {
+ product: Product;
+ children?: ReactElement | ReactElement[];
+}
+
+export interface Product {
+ id: string;
+ title: string;
+ img?: string;
+}
+
+export interface ProductContextProps {
+ counter: number;
+ increaseBy: (value: number) => void;
+ product: Product;
+}
+
+export interface ProductCardHOCProps {
+ ({ children, product, className }: ProductCardType): JSX.Element;
+ Title: (Props: PropsProductTitle) => JSX.Element;
+ Image: (Props: PropsProductImage) => JSX.Element;
+ Buttons: ({
+ className,
+ }: {
+ className?: string;
+ style?: CSSProperties;
+ }) => JSX.Element;
+}
+export interface ProductsCart extends Product {
+ count: number;
+}
diff --git a/src/02-component-patterns/interfaces/productsInterfaces.ts b/src/02-component-patterns/interfaces/productsInterfaces.ts
new file mode 100644
index 000000000..343809672
--- /dev/null
+++ b/src/02-component-patterns/interfaces/productsInterfaces.ts
@@ -0,0 +1,26 @@
+import { ReactNode } from "react";
+
+export interface IProduct {
+ id: string;
+ title: string;
+ img?: string;
+ className?: string;
+}
+
+export interface ProductProps {
+ product: IProduct;
+ children?: ReactNode;
+}
+
+export interface ProductContextProps {
+ product: IProduct;
+ counter: number;
+ increasedBy: (value: number) => void;
+}
+
+export interface IProductCard {
+ ({ product, children }: ProductProps): JSX.Element;
+ Image: ({ img }: { img?: string | undefined }) => JSX.Element;
+ Title: ({ title }: { title?: string | undefined }) => JSX.Element;
+ Buttons: () => JSX.Element;
+}
diff --git a/src/02-component-patterns/pages/ShoppingPage.tsx b/src/02-component-patterns/pages/ShoppingPage.tsx
new file mode 100644
index 000000000..3483f4d72
--- /dev/null
+++ b/src/02-component-patterns/pages/ShoppingPage.tsx
@@ -0,0 +1,147 @@
+import{ FC } from 'react'
+import { ProductButtons, ProductCard, ProductImage, ProductTitle } from '../components'
+import '../styles/custom-styles.css'
+import { products } from '../data/products';
+import { useShoppingCart } from '../hooks/useShoppingCart';
+
+
+
+const ShoppingPage:FC = ():JSX.Element => {
+ // Record
+ const { shoppingCart, onProductCountChange } = useShoppingCart()
+
+ return (
+
+
ShoppingPage
+
+
+ { products.map(product =>(
+
onProductCountChange(count, product)}
+ key={product.id}
+ >
+
+
+
+
+ ))}
+
+ {/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+ */}
+
+
+ {
+ (
+ Object.entries(shoppingCart).map( ([key, product]) => (
+
onProductCountChange(count, product)}
+ key={key}
+ >
+
+
+
+ ))
+ )
+ }
+
+
+ {/*
+
+ {JSON.stringify(shoppingCart, null, 5)}
+
+
*/}
+
+ )
+}
+
+export default ShoppingPage
diff --git a/src/02-component-patterns/styles/custom-styles.css b/src/02-component-patterns/styles/custom-styles.css
new file mode 100644
index 000000000..11e2ad19a
--- /dev/null
+++ b/src/02-component-patterns/styles/custom-styles.css
@@ -0,0 +1,28 @@
+.bg-dark {
+ background-color: rgb(56, 56, 56) !important;
+}
+
+.text-white {
+ color: white
+}
+
+.text-bold {
+ font-weight: bold;
+}
+
+.custom-image {
+ padding: 10px;
+ border-radius: 20px 20px 20px 20px;;
+ width: calc(100% - 20px);
+}
+
+.custom-buttons button, .custom-buttons div {
+ border-color: white;
+ color:white;
+}
+
+.shopping-cart {
+ position: fixed;
+ top: 0;
+ right: 10px;
+}
\ No newline at end of file
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..defa29458
--- /dev/null
+++ b/src/02-component-patterns/styles/styles.module.css
@@ -0,0 +1,63 @@
+
+
+.productCard {
+ background-color: white;
+ border-radius: 15px;
+ color: black;
+ padding-bottom: 5px;
+ 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 black;
+ border-radius: 5px 0px 0px 5px;
+ font-size: 20px;
+ width: 30px;
+}
+
+.buttonMinus:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
+.countLabel {
+ border-bottom: 1px solid black;
+ border-top: 1px solid black;
+ font-size: 16px;
+ height: 25px;
+ padding-top: 5px;
+ text-align: center;
+ width: 30px;
+}
+
+.buttonAdd {
+ cursor: pointer;
+ background-color: transparent;
+ border: 1px solid black;
+ border-radius: 0px 5px 5px 0px;
+ font-size: 20px;
+ width: 30px;
+}
+
+.buttonAdd:hover {
+ background-color: rgba(0, 0, 0, 0.1);
+}
+
diff --git a/src/routes/Navigation.tsx b/src/routes/Navigation.tsx
index a9bfe83b0..24301ec7e 100644
--- a/src/routes/Navigation.tsx
+++ b/src/routes/Navigation.tsx
@@ -4,9 +4,12 @@ import {
Route,
NavLink
} from 'react-router-dom';
+import ShoppingPage from '../02-component-patterns/pages/ShoppingPage';
+
import logo from '../logo.svg';
+
export const Navigation = () => {
return (
@@ -15,7 +18,7 @@ export const Navigation = () => {
-
- Home
+ Shopping
-
About
@@ -36,7 +39,7 @@ export const Navigation = () => {
Users
- Home
+