From a788a68bb8ce02bfab46dbb250351b0097855eb3 Mon Sep 17 00:00:00 2001 From: Elad Abutbul Date: Sun, 21 Dec 2025 21:44:28 +0200 Subject: [PATCH 1/3] implement a async function to get all the products and set categorys function --- package-lock.json | 10 ---- src/components/product-components/Card.tsx | 33 ++++++----- src/pages/Home.tsx | 65 ++++++++++++++-------- src/types.ts | 14 ++++- 4 files changed, 72 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 4458a6d..aea387a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -75,7 +75,6 @@ "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@ampproject/remapping": "^2.2.0", "@babel/code-frame": "^7.26.0", @@ -1640,7 +1639,6 @@ "integrity": "sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1658,7 +1656,6 @@ "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -1700,7 +1697,6 @@ "integrity": "sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==", "dev": true, "license": "MIT", - "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2003,7 +1999,6 @@ } ], "license": "MIT", - "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001688", "electron-to-chromium": "^1.5.73", @@ -2610,7 +2605,6 @@ "integrity": "sha512-evtlNcpJg+cZLcnVKwsai8fExnqjGPicK7gnUtlNuzu+Fv9bI0aLpND5T44VLQtoMEnI57LoXO9XAkIXwohKrA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.12.1", @@ -4359,7 +4353,6 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4372,7 +4365,6 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", - "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -4603,7 +4595,6 @@ "integrity": "sha512-qsSxlayzoOjdvXMVLkzF84DJFc2HZEL/rFyGIKbbilYtAvlCxyuzUeff9LawTn4btVnLKg75Z8MMr1lxU1lfGw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "chokidar": "^4.0.0", "immutable": "^5.0.2", @@ -5158,7 +5149,6 @@ "integrity": "sha512-akD5IAH/ID5imgue2DYhzsEwCi0/4VKY31uhMLEYJwPP4TiUp8pL5PIK+Wo7H8qT8JY9i+pVfPydcFPYD1EL7g==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "esbuild": "0.24.0", "postcss": "^8.4.49", diff --git a/src/components/product-components/Card.tsx b/src/components/product-components/Card.tsx index c25aa5f..73dadf9 100644 --- a/src/components/product-components/Card.tsx +++ b/src/components/product-components/Card.tsx @@ -1,22 +1,29 @@ -import React from 'react'; -import { Product } from '../../types'; +import React from "react"; +import { Product } from "../../types"; interface CardProps { + product: Product + key:number } -const Card: React.FC = () => { - +const Card: React.FC = ( {product} : CardProps) => { return ( -
-
- +
+
+
-
-

-
-

Price:

-

Rating:

-

Category:

+
+

+
+

+ Price: {product.price} +

+

+ Rating:{product.rating.rate} +

+

+ Category: {product.category} +

diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4eff3ea..9ecef9c 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,32 +1,47 @@ -import React, { useState, useEffect } from 'react'; -import axios from 'axios'; -import '../assets/css/common.css'; -import '../assets/css/main.css'; -import Navbar from '../components/ui-elements/Navbar'; -import DropDownFilter from '../components/filters/DropDownFilter'; -import Loading from '../components/ui-elements/Loading'; -import Card from '../components/product-components/Card'; -import { Product } from '../types'; +import React, { useState, useEffect } from "react"; +import axios from "axios"; +import "../assets/css/common.css"; +import "../assets/css/main.css"; +import Navbar from "../components/ui-elements/Navbar"; +import DropDownFilter from "../components/filters/DropDownFilter"; +import Loading from "../components/ui-elements/Loading"; +import Card from "../components/product-components/Card"; +import { Product } from "../types"; const Home: React.FC = () => { const [allProducts, setAllProducts] = useState([]); const [products, setProducts] = useState([]); const [isDropdownOpen, setIsDropdownOpen] = useState(false); - const [selectedCategory, setSelectedCategory] = useState('All'); + const [selectedCategory, setSelectedCategory] = useState("All"); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); -//TODO - useEffect + axios - + useEffect(() => { + const api = async () => { + try { + const res = await axios.get("https://fakestoreapi.com/products"); + setProducts(res.data); + setAllProducts(res.data); + } catch (error) { + setError("failed"); + } finally { + setLoading(false); + } + }; + api(); + }, []); const showFilterByCategory = () => { setIsDropdownOpen(!isDropdownOpen); }; - // TODO - filter products by category - BONUS - const filterByCategory = (category: string) => { - setSelectedCategory(category); - //TODO - filter products by category - }; + const filterByCategory = (category: string) => { + setSelectedCategory(category); + if (category === "All") { + setProducts(allProducts); + return; + } + setProducts(allProducts.filter((p) => p.category === category)); +}; if (loading) { return ; @@ -34,7 +49,7 @@ const Home: React.FC = () => { if (error) { return ( -
+

Error: {error}

); @@ -43,8 +58,8 @@ const Home: React.FC = () => { return ( <> -
-
+
+
{ />
-
-
+
+
-
- {/* TODO - map products */} +
+ {products.map((product) => { + return ; + })}
diff --git a/src/types.ts b/src/types.ts index 1e3d610..5e3d6f0 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,11 +1,19 @@ export interface Rating { -//TODO +rate:number, +count:number } export interface Product { -//TODO + id: number; + title: string; + price: number; + description: string; + category: string; + image: string; + rating: Rating } export interface Category { -//TODO + id:string, + name:string, } From f482571a89b29af7291aefb8c870da4bb1eb323b Mon Sep 17 00:00:00 2001 From: Elad Abutbul Date: Mon, 22 Dec 2025 18:32:31 +0200 Subject: [PATCH 2/3] add a modal --- src/assets/css/main.css | 47 +++++++++++++++++++-- src/components/product-components/Card.tsx | 8 ++-- src/components/product-components/Modal.tsx | 35 +++++++++++++++ src/pages/Home.tsx | 41 +++++++++++++----- 4 files changed, 112 insertions(+), 19 deletions(-) create mode 100644 src/components/product-components/Modal.tsx diff --git a/src/assets/css/main.css b/src/assets/css/main.css index 96d1251..25aa3e1 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -50,7 +50,7 @@ padding: 15px; padding-left: 60px; font-size: 18px; - font-family: 'Nunito Sans', sans-serif; + font-family: "Nunito Sans", sans-serif; font-weight: inherit; outline: none; border: none; @@ -181,7 +181,9 @@ box-shadow: 0 2px 15px rgba(0, 0, 0, 0.08); background: white; overflow: hidden; - transition: transform 0.3s linear, box-shadow 0.2s linear, background-color 0.2s linear; + cursor: pointer; + transition: transform 0.3s linear, box-shadow 0.2s linear, + background-color 0.2s linear; } .products-grid .product:hover { @@ -208,7 +210,44 @@ height: 100%; object-fit: cover; } - +.modal-container { + position: fixed; + display: flex; + z-index: 10; + width: 100%; + height: 100%; + background-color: #111517c1; + justify-content: center; + align-items: center; +} +.modal { + background-color: rgb(54, 54, 54); + height: 80%; + width: 50%; + display: flex; + justify-content: center; + padding: 2rem; + border-radius: 2rem; + position: relative; + align-items: center; + color: white; + background-blend-mode: multiply; + background-size: cover; +} +.modal .close { + position: absolute; + right: 2rem; + top: 2rem; + font-size: 2rem; + cursor: pointer; +} +.modal .content { + display: flex; + flex-direction: column; + height: 50%; + justify-content: space-around; + font-size: 1.1rem; +} .products-grid .product-info { position: relative; padding: 30px 25px; @@ -222,4 +261,4 @@ font-size: 16px; text-transform: capitalize; margin-bottom: 15px; -} \ No newline at end of file +} diff --git a/src/components/product-components/Card.tsx b/src/components/product-components/Card.tsx index 73dadf9..89d8e7a 100644 --- a/src/components/product-components/Card.tsx +++ b/src/components/product-components/Card.tsx @@ -2,13 +2,13 @@ import React from "react"; import { Product } from "../../types"; interface CardProps { - product: Product - key:number + product: Product; + openModal: (product: Product) => void; } -const Card: React.FC = ( {product} : CardProps) => { +const Card: React.FC = ({ product, openModal }: CardProps) => { return ( -
+
openModal(product)}>
diff --git a/src/components/product-components/Modal.tsx b/src/components/product-components/Modal.tsx new file mode 100644 index 0000000..47cd08a --- /dev/null +++ b/src/components/product-components/Modal.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import { Product } from "../../types"; + +interface ModalProps { + product: Product; + closeModal: () => void; +} + +const Modal = ({ product, closeModal }: ModalProps) => { + return ( +
+
e.stopPropagation()} + style={{ + backgroundImage: `url(${product.image})`, + + }} + > +

+ X +

+
+

Title: {product.title}

+

Category: {product.category}

+

Price: {product.price}

+

Rating Count: {product.rating.count}

+

Rating Rate: {product.rating.rate}

+
+
+
+ ); +}; + +export default Modal; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 9ecef9c..5136150 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -7,6 +7,7 @@ import DropDownFilter from "../components/filters/DropDownFilter"; import Loading from "../components/ui-elements/Loading"; import Card from "../components/product-components/Card"; import { Product } from "../types"; +import Modal from "../components/product-components/Modal"; const Home: React.FC = () => { const [allProducts, setAllProducts] = useState([]); @@ -15,7 +16,8 @@ const Home: React.FC = () => { const [selectedCategory, setSelectedCategory] = useState("All"); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - + const [modal, setModal] = useState(false); + const [modalProduct, setModalProduct] = useState(null); useEffect(() => { const api = async () => { try { @@ -33,15 +35,22 @@ const Home: React.FC = () => { const showFilterByCategory = () => { setIsDropdownOpen(!isDropdownOpen); }; - - const filterByCategory = (category: string) => { - setSelectedCategory(category); - if (category === "All") { - setProducts(allProducts); - return; - } - setProducts(allProducts.filter((p) => p.category === category)); -}; + const openModal = (product: Product): void => { + setModal(true); + setModalProduct(product); + }; + const closeModal = (): void => { + setModal(false); + setModalProduct(null); + }; + const filterByCategory = (category: string) => { + setSelectedCategory(category); + if (category === "All") { + setProducts(allProducts); + return; + } + setProducts(allProducts.filter((p) => p.category === category)); + }; if (loading) { return ; @@ -57,6 +66,10 @@ const Home: React.FC = () => { return ( <> + {modal && modalProduct && ( + + )} +
@@ -73,7 +86,13 @@ const Home: React.FC = () => {
{products.map((product) => { - return ; + return ( + + ); })}
From 9918edc656cefe51d4552b962c63354db6f8d598 Mon Sep 17 00:00:00 2001 From: Elad Abutbul Date: Sun, 28 Dec 2025 11:01:21 +0200 Subject: [PATCH 3/3] elad abutbul --- src/assets/css/main.css | 21 +++++++------- src/components/product-components/Card.tsx | 15 ++++------ src/components/product-components/Modal.tsx | 32 +++++++++++++++------ src/constants/Home.ts | 6 ++++ src/constants/Modal.ts | 8 ++++++ src/index.css | 3 ++ src/pages/Home.tsx | 21 +++++++------- 7 files changed, 67 insertions(+), 39 deletions(-) create mode 100644 src/constants/Home.ts create mode 100644 src/constants/Modal.ts diff --git a/src/assets/css/main.css b/src/assets/css/main.css index 25aa3e1..4503078 100644 --- a/src/assets/css/main.css +++ b/src/assets/css/main.css @@ -216,32 +216,33 @@ z-index: 10; width: 100%; height: 100%; - background-color: #111517c1; + background-color: var(--faded-gray); justify-content: center; align-items: center; } .modal { - background-color: rgb(54, 54, 54); + background-color: var(--gray); height: 80%; width: 50%; display: flex; - justify-content: center; padding: 2rem; border-radius: 2rem; - position: relative; - align-items: center; - color: white; + color: var(--white); background-blend-mode: multiply; background-size: cover; + flex-direction: column; + gap: 4rem ; +} +.modal-header { + display: flex; + width: 100%; + justify-content: flex-end; } .modal .close { - position: absolute; - right: 2rem; - top: 2rem; font-size: 2rem; cursor: pointer; } -.modal .content { +.modal .modal-content { display: flex; flex-direction: column; height: 50%; diff --git a/src/components/product-components/Card.tsx b/src/components/product-components/Card.tsx index 89d8e7a..b7d2218 100644 --- a/src/components/product-components/Card.tsx +++ b/src/components/product-components/Card.tsx @@ -7,23 +7,18 @@ interface CardProps { } const Card: React.FC = ({ product, openModal }: CardProps) => { + const { image, price, rating, category } = product; return (
openModal(product)}>
- +

-

- Price: {product.price} -

-

- Rating:{product.rating.rate} -

-

- Category: {product.category} -

+

Price: {price}

+

Rating:{rating.rate}

+

Category: {category}

diff --git a/src/components/product-components/Modal.tsx b/src/components/product-components/Modal.tsx index 47cd08a..258a7b1 100644 --- a/src/components/product-components/Modal.tsx +++ b/src/components/product-components/Modal.tsx @@ -1,5 +1,6 @@ import React from "react"; import { Product } from "../../types"; +import { MODAL_CONSTANTS } from "../../constants/Modal"; interface ModalProps { product: Product; @@ -7,25 +8,38 @@ interface ModalProps { } const Modal = ({ product, closeModal }: ModalProps) => { + const { image, title, category, price, rating } = product; return (
+
e.stopPropagation()} style={{ - backgroundImage: `url(${product.image})`, - + backgroundImage: `url(${image})`, }} > +

- X + {MODAL_CONSTANTS.X}

-
-

Title: {product.title}

-

Category: {product.category}

-

Price: {product.price}

-

Rating Count: {product.rating.count}

-

Rating Rate: {product.rating.rate}

+
+
+

+ {MODAL_CONSTANTS.TITLE} {title} +

+

+ {MODAL_CONSTANTS.CATEGORY} {category} +

+

+ {MODAL_CONSTANTS.PRICE} {price} +

+

+ {MODAL_CONSTANTS.RATING_COUNT} {rating.count} +

+

+ {MODAL_CONSTANTS.RATING_RATE} {rating.rate} +

diff --git a/src/constants/Home.ts b/src/constants/Home.ts new file mode 100644 index 0000000..4914201 --- /dev/null +++ b/src/constants/Home.ts @@ -0,0 +1,6 @@ +export const HOME_CONSTANTS = { + URL_API: "https://fakestoreapi.com/products", + FAILED: "failed", + ALL: "All", + ERROR: "Error:", +}as const; diff --git a/src/constants/Modal.ts b/src/constants/Modal.ts new file mode 100644 index 0000000..c1e8670 --- /dev/null +++ b/src/constants/Modal.ts @@ -0,0 +1,8 @@ +export const MODAL_CONSTANTS = { + X: "x", + TITLE: "Title:", + CATEGORY: "Category:", + PRICE: "Price:", + RATING_COUNT: "Rating Count:", + RATING_RATE: "Rating Rate:", +} as const; diff --git a/src/index.css b/src/index.css index 0a84a20..10d5d0c 100644 --- a/src/index.css +++ b/src/index.css @@ -11,6 +11,9 @@ text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; + --faded-gray:rgba(124, 124, 124, 0.775); + --gray:rgb(82, 82, 82); + --white:rgb(255, 255, 255); } a { diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 5136150..9a5b47a 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -8,6 +8,7 @@ import Loading from "../components/ui-elements/Loading"; import Card from "../components/product-components/Card"; import { Product } from "../types"; import Modal from "../components/product-components/Modal"; +import { HOME_CONSTANTS } from "../constants/Home"; const Home: React.FC = () => { const [allProducts, setAllProducts] = useState([]); @@ -16,36 +17,36 @@ const Home: React.FC = () => { const [selectedCategory, setSelectedCategory] = useState("All"); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); - const [modal, setModal] = useState(false); + const [isModalOpen, setIsModalOpen] = useState(false); const [modalProduct, setModalProduct] = useState(null); useEffect(() => { - const api = async () => { + const getData = async () => { try { - const res = await axios.get("https://fakestoreapi.com/products"); + const res = await axios.get(HOME_CONSTANTS.URL_API); setProducts(res.data); setAllProducts(res.data); } catch (error) { - setError("failed"); + setError(HOME_CONSTANTS.FAILED); } finally { setLoading(false); } }; - api(); + getData(); }, []); const showFilterByCategory = () => { setIsDropdownOpen(!isDropdownOpen); }; const openModal = (product: Product): void => { - setModal(true); + setIsModalOpen(true); setModalProduct(product); }; const closeModal = (): void => { - setModal(false); + setIsModalOpen(false); setModalProduct(null); }; const filterByCategory = (category: string) => { setSelectedCategory(category); - if (category === "All") { + if (category === HOME_CONSTANTS.ALL) { setProducts(allProducts); return; } @@ -59,14 +60,14 @@ const Home: React.FC = () => { if (error) { return (
-

Error: {error}

+

{HOME_CONSTANTS.ERROR} {error}

); } return ( <> - {modal && modalProduct && ( + {isModalOpen && modalProduct && ( )}