From 561571a4be05ca6542e580320236f0383c335cd0 Mon Sep 17 00:00:00 2001 From: brauni18 Date: Fri, 26 Dec 2025 16:20:07 +0200 Subject: [PATCH 1/2] superduper store --- src/components/product-components/Card.tsx | 19 +++++------- src/pages/Home.tsx | 34 ++++++++++++++++++---- src/types.ts | 21 +++++++++---- 3 files changed, 52 insertions(+), 22 deletions(-) diff --git a/src/components/product-components/Card.tsx b/src/components/product-components/Card.tsx index c25aa5f..9bb670f 100644 --- a/src/components/product-components/Card.tsx +++ b/src/components/product-components/Card.tsx @@ -1,22 +1,19 @@ import React from 'react'; -import { Product } from '../../types'; +import {Product} from '../../types'; -interface CardProps { -} - -const Card: React.FC = () => { +const Card: React.FC = ({category,description,id,image,price,title,rating:{rate,count}}) => { return ( -
+
- + {title}
-

+

{title}

-

Price:

-

Rating:

-

Category:

+

Price: {price}

+

Rating: {rate}

+

Category: {category}

diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 4eff3ea..a748834 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -16,16 +16,35 @@ const Home: React.FC = () => { const [loading, setLoading] = useState(true); const [error, setError] = useState(null); -//TODO - useEffect + axios - const showFilterByCategory = () => { +useEffect(() =>{ + const fetchProducts = async () => { + try { + const response = await axios.get('https://fakestoreapi.com/products'); + console.log(response.data); + setAllProducts(response.data); + setProducts(response.data); + setLoading(false); + } + catch(error:any) { + setError(error.message) + setLoading(false); + } + } + + fetchProducts(); +}, []); +const showFilterByCategory = () => { setIsDropdownOpen(!isDropdownOpen); }; - - // TODO - filter products by category - BONUS const filterByCategory = (category: string) => { setSelectedCategory(category); - //TODO - filter products by category + if (category === 'All') { + setProducts(allProducts); + } else { + const filteredProducts = allProducts.filter(product => product.category === category); + setProducts(filteredProducts); + } }; if (loading) { @@ -57,7 +76,10 @@ const Home: React.FC = () => {
- {/* TODO - map products */} + {products.map((product: Product) => ( + + ))} +
diff --git a/src/types.ts b/src/types.ts index 1e3d610..effde33 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,11 +1,22 @@ -export interface Rating { -//TODO -} +// export interface Rating { +// count: number; +// rate: number; +// } export interface Product { -//TODO + title: string; + category: string; + description: string; + id: string; + image: string; + price: number; + rating:{ + rate: number; + count: number; + } } export interface Category { -//TODO + id: string; + name: string; } From 270ad3b9a59e65baa4dc014c9a02602ea3c293fc Mon Sep 17 00:00:00 2001 From: brauni18 Date: Sun, 4 Jan 2026 15:49:19 +0200 Subject: [PATCH 2/2] =?UTF-8?q?React=20Router=20=E2=80=93=20Assignment=20E?= =?UTF-8?q?xtension?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 12 ++- src/assets/css/product-page.css | 72 +++++++++++++++++ src/components/product-components/Card.tsx | 4 +- src/components/ui-elements/Navbar.tsx | 2 +- src/main.tsx | 6 +- src/pages/Home.tsx | 40 ++++++++-- src/pages/NotFound.tsx | 16 ++++ src/pages/ProductPage.tsx | 93 ++++++++++++++++++++++ 8 files changed, 233 insertions(+), 12 deletions(-) create mode 100644 src/assets/css/product-page.css create mode 100644 src/pages/NotFound.tsx create mode 100644 src/pages/ProductPage.tsx diff --git a/src/App.tsx b/src/App.tsx index 935d10b..061fb72 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,9 +1,19 @@ +import {Route, Routes} from 'react-router' import React from 'react'; import Home from './pages/Home'; +import ProductPage from './pages/ProductPage'; +import NotFound from './pages/NotFound'; const App: React.FC = () => { return ( - + <> + + } /> + } /> + } /> + } /> + + ); }; diff --git a/src/assets/css/product-page.css b/src/assets/css/product-page.css new file mode 100644 index 0000000..00ee7d1 --- /dev/null +++ b/src/assets/css/product-page.css @@ -0,0 +1,72 @@ + +.product-page { + padding: 2rem 0; +} + +.product-page__grid { + display: grid; + grid-template-columns: 1fr 1.2fr; + gap: 2rem; + align-items: flex-start; +} + +/* Image column */ +.product-page__image-wrapper { + display: flex; + justify-content: center; + align-items: center; +} + +.product-page__image { + max-width: 100%; + height: auto; + object-fit: contain; +} + +/* Info column */ +.product-page__info { + display: flex; + flex-direction: column; + gap: 1rem; +} + +.product-page__title { + font-size: 2rem; + margin: 0; +} + +.product-page__category span { + font-weight: 600; +} + +.product-page__price-row { + display: flex; + align-items: center; + gap: 1rem; +} + +.product-page__price { + font-size: 1.5rem; + font-weight: 700; +} + +.product-page__rating { + font-size: 0.9rem; + color: #777; +} + +.product-page__description { + line-height: 1.5; +} + +.product-page__actions { + display: flex; + gap: 1rem; +} + +/* Responsive: stack on small screens */ +@media (max-width: 768px) { + .product-page__grid { + grid-template-columns: 1fr; + } +} \ No newline at end of file diff --git a/src/components/product-components/Card.tsx b/src/components/product-components/Card.tsx index 9bb670f..ee4c324 100644 --- a/src/components/product-components/Card.tsx +++ b/src/components/product-components/Card.tsx @@ -1,10 +1,12 @@ import React from 'react'; import {Product} from '../../types'; +import { Navigate, useNavigate } from 'react-router-dom'; const Card: React.FC = ({category,description,id,image,price,title,rating:{rate,count}}) => { + const navigate = useNavigate(); return ( -
+
navigate(`/products/${id}`)} key={id}>
{title}
diff --git a/src/components/ui-elements/Navbar.tsx b/src/components/ui-elements/Navbar.tsx index 5dd37fb..25e3d21 100644 --- a/src/components/ui-elements/Navbar.tsx +++ b/src/components/ui-elements/Navbar.tsx @@ -21,7 +21,7 @@ const Navbar: React.FC = () => {
); } - return ( <> @@ -75,9 +95,13 @@ const showFilterByCategory = () => {
-
+
{products.map((product: Product) => ( - + ))}
diff --git a/src/pages/NotFound.tsx b/src/pages/NotFound.tsx new file mode 100644 index 0000000..14dce1c --- /dev/null +++ b/src/pages/NotFound.tsx @@ -0,0 +1,16 @@ +import Navbar from "../components/ui-elements/Navbar"; + +function NotFound() { + return ( + <> + +
+

404 - Page Not Found

+

Sorry, the page you are looking for does not exist.

+
+ + ); +} + +export default NotFound; + diff --git a/src/pages/ProductPage.tsx b/src/pages/ProductPage.tsx new file mode 100644 index 0000000..1bc2ba0 --- /dev/null +++ b/src/pages/ProductPage.tsx @@ -0,0 +1,93 @@ +import React, {useEffect, useState} from 'react'; +import axios from 'axios'; +import '../assets/css/common.css'; +import '../assets/css/main.css'; +import '../assets/css/product-page.css'; +import Navbar from '../components/ui-elements/Navbar'; +import Loading from '../components/ui-elements/Loading'; +import { useParams } from 'react-router-dom'; +import { Product } from '../types'; + + +const ProductPage: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const [product, setProduct] = useState(null); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchProduct = async () => { + try { + const response = await axios.get(`https://fakestoreapi.com/products/${id}`); + console.log(response.data); + setProduct(response.data); + setLoading(false); + } + catch(error:any) { + setError(error.message) + setLoading(false); + } + } + fetchProduct(); + }, [id]); + if (loading) { + return ; + } + if (error || !product) { + return ( +
+

Error: {error}

+
+ ); + } + return ( + <> + +
+
+
+
+ +
+ {product.title} +
+ + +
+

{product.title}

+ +

+ Category: {product.category} +

+ +
+ ${product.price} + {product.rating && ( + + ⭐ {product.rating.rate} ({product.rating.count} reviews) + + )} +
+ +

+ {product.description} +

+ +
+ + +
+
+
+
+
+
+ + ); +}; + +export default ProductPage; \ No newline at end of file