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/assets/css/main.css b/src/assets/css/main.css index 96d1251..4503078 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,45 @@ height: 100%; object-fit: cover; } - +.modal-container { + position: fixed; + display: flex; + z-index: 10; + width: 100%; + height: 100%; + background-color: var(--faded-gray); + justify-content: center; + align-items: center; +} +.modal { + background-color: var(--gray); + height: 80%; + width: 50%; + display: flex; + padding: 2rem; + border-radius: 2rem; + 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 { + font-size: 2rem; + cursor: pointer; +} +.modal .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 +262,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 c25aa5f..b7d2218 100644 --- a/src/components/product-components/Card.tsx +++ b/src/components/product-components/Card.tsx @@ -1,22 +1,24 @@ -import React from 'react'; -import { Product } from '../../types'; +import React from "react"; +import { Product } from "../../types"; interface CardProps { + product: Product; + openModal: (product: Product) => void; } -const Card: React.FC = () => { - +const Card: React.FC = ({ product, openModal }: CardProps) => { + const { image, price, rating, category } = product; return ( -
-
- +
openModal(product)}> +
+
-
-

-
-

Price:

-

Rating:

-

Category:

+
+

+
+

Price: {price}

+

Rating:{rating.rate}

+

Category: {category}

diff --git a/src/components/product-components/Modal.tsx b/src/components/product-components/Modal.tsx new file mode 100644 index 0000000..258a7b1 --- /dev/null +++ b/src/components/product-components/Modal.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import { Product } from "../../types"; +import { MODAL_CONSTANTS } from "../../constants/Modal"; + +interface ModalProps { + product: Product; + closeModal: () => void; +} + +const Modal = ({ product, closeModal }: ModalProps) => { + const { image, title, category, price, rating } = product; + return ( +
+ +
e.stopPropagation()} + style={{ + backgroundImage: `url(${image})`, + }} + > +
+

+ {MODAL_CONSTANTS.X} +

+
+
+

+ {MODAL_CONSTANTS.TITLE} {title} +

+

+ {MODAL_CONSTANTS.CATEGORY} {category} +

+

+ {MODAL_CONSTANTS.PRICE} {price} +

+

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

+

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

+
+
+
+ ); +}; + +export default Modal; 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 4eff3ea..9a5b47a 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,31 +1,56 @@ -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"; +import Modal from "../components/product-components/Modal"; +import { HOME_CONSTANTS } from "../constants/Home"; 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 - + const [isModalOpen, setIsModalOpen] = useState(false); + const [modalProduct, setModalProduct] = useState(null); + useEffect(() => { + const getData = async () => { + try { + const res = await axios.get(HOME_CONSTANTS.URL_API); + setProducts(res.data); + setAllProducts(res.data); + } catch (error) { + setError(HOME_CONSTANTS.FAILED); + } finally { + setLoading(false); + } + }; + getData(); + }, []); const showFilterByCategory = () => { setIsDropdownOpen(!isDropdownOpen); }; - - // TODO - filter products by category - BONUS + const openModal = (product: Product): void => { + setIsModalOpen(true); + setModalProduct(product); + }; + const closeModal = (): void => { + setIsModalOpen(false); + setModalProduct(null); + }; const filterByCategory = (category: string) => { setSelectedCategory(category); - //TODO - filter products by category + if (category === HOME_CONSTANTS.ALL) { + setProducts(allProducts); + return; + } + setProducts(allProducts.filter((p) => p.category === category)); }; if (loading) { @@ -34,17 +59,21 @@ const Home: React.FC = () => { if (error) { return ( -
-

Error: {error}

+
+

{HOME_CONSTANTS.ERROR} {error}

); } return ( <> + {isModalOpen && modalProduct && ( + + )} + -
-
+
+
{ />
-
-
+
+
-
- {/* 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, }