From 24dacde5a984746dba3141f8338d5f89210af6b5 Mon Sep 17 00:00:00 2001 From: Philippe Torrel Date: Thu, 5 Nov 2026 14:46:04 +0100 Subject: [PATCH] --- .../u17-19_vitest-shop-tests/.gitignore | 24 + .../u17-19_vitest-shop-tests/.oxlintrc.json | 8 + .../u17-19_vitest-shop-tests/README.md | 35 + .../u17-19_vitest-shop-tests/index.html | 63 + .../u17-19_vitest-shop-tests/package.json | 39 + .../public/favicon.svg | 1 + .../u17-19_vitest-shop-tests/public/icons.svg | 24 + .../u17-19_vitest-shop-tests/src/App.tsx | 39 + .../src/assets/react.svg | 1 + .../src/assets/vite.svg | 1 + .../src/components/CategorySection.tsx | 77 + .../src/components/Footer.tsx | 55 + .../src/components/Header.tsx | 53 + .../src/components/Hero.tsx | 23 + .../src/components/ImageGallery.tsx | 71 + .../src/components/ProductList.tsx | 24 + .../src/components/ProductListCard.tsx | 64 + .../u17-19_vitest-shop-tests/src/index.css | 810 +++++ .../src/lib/datatypes.ts | 45 + .../src/lib/helpers.ts | 12 + .../u17-19_vitest-shop-tests/src/main.tsx | 10 + .../src/pages/accessories.tsx | 76 + .../src/pages/men.tsx | 76 + .../src/pages/product.tsx | 188 ++ .../src/pages/root.tsx | 39 + .../src/pages/women.tsx | 76 + .../src/tests/Hero.test.tsx | 36 + .../src/tests/ImageGallery.test.tsx | 62 + .../src/tests/ProductList.test.tsx | 42 + .../src/tests/__mocks__/categories.mock.ts | 21 + .../src/tests/__mocks__/product.mock.ts | 56 + .../src/tests/__mocks__/products.mock.ts | 168 + .../src/tests/add.test.ts | 11 + .../src/tests/config/setupTests.ts | 16 + .../src/tests/main.test.ts | 19 + .../src/tests/multiply.test.ts | 11 + .../src/utils/helpers.ts | 12 + .../tsconfig.app.json | 26 + .../u17-19_vitest-shop-tests/tsconfig.json | 7 + .../tsconfig.node.json | 23 + .../u17-19_vitest-shop-tests/vite.config.ts | 13 + .../uebungen-17-19_vitest-shop-tests.zip | Bin 0 -> 40700 bytes .../tag44/01_vitest-shop/package-lock.json | 2938 ----------------- .../tag44/01_vitest-shop/package.json | 7 + .../tag44/01_vitest-shop/src/pages/root.tsx | 12 +- .../01_vitest-shop/src/tests/Hero.test.tsx | 36 + .../src/tests/ImageGallery.test.tsx | 62 + .../src/tests/ProductList.test.tsx | 42 + .../src/tests/__mocks__/product.mock.ts | 56 + .../src/tests/__mocks__/products.mock.ts | 168 + .../01_vitest-shop/src/tests/add.test.ts | 11 + .../src/tests/config/setupTests.ts | 16 + .../01_vitest-shop/src/tests/main.test.ts | 19 + .../01_vitest-shop/src/tests/multiply.test.ts | 11 + .../tag44/01_vitest-shop/src/utils/helpers.ts | 12 + .../tag44/01_vitest-shop/vite.config.ts | 18 +- 56 files changed, 2911 insertions(+), 2954 deletions(-) create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/.gitignore create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/.oxlintrc.json create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/README.md create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/index.html create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/package.json create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/public/favicon.svg create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/public/icons.svg create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/App.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/assets/react.svg create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/assets/vite.svg create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/CategorySection.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Footer.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Header.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Hero.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ImageGallery.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ProductList.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ProductListCard.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/index.css create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/lib/datatypes.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/lib/helpers.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/main.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/accessories.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/men.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/product.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/root.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/women.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/Hero.test.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/ImageGallery.test.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/ProductList.test.tsx create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/__mocks__/categories.mock.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/__mocks__/product.mock.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/__mocks__/products.mock.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/add.test.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/config/setupTests.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/main.test.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/tests/multiply.test.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/src/utils/helpers.ts create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/tsconfig.app.json create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/tsconfig.json create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/tsconfig.node.json create mode 100644 07_js-debug/uebungen/u17-19_vitest-shop-tests/vite.config.ts create mode 100644 07_js-debug/uebungen/uebungen-17-19_vitest-shop-tests.zip delete mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/package-lock.json create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/tests/Hero.test.tsx create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/tests/ImageGallery.test.tsx create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/tests/ProductList.test.tsx create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/tests/__mocks__/product.mock.ts create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/tests/__mocks__/products.mock.ts create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/tests/add.test.ts create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/tests/config/setupTests.ts create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/tests/main.test.ts create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/tests/multiply.test.ts create mode 100644 07_js-debug/unterricht/tag44/01_vitest-shop/src/utils/helpers.ts diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/.gitignore b/07_js-debug/uebungen/u17-19_vitest-shop-tests/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/.oxlintrc.json b/07_js-debug/uebungen/u17-19_vitest-shop-tests/.oxlintrc.json new file mode 100644 index 0000000..6fa991d --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/.oxlintrc.json @@ -0,0 +1,8 @@ +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/README.md b/07_js-debug/uebungen/u17-19_vitest-shop-tests/README.md new file mode 100644 index 0000000..355e0e2 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/README.md @@ -0,0 +1,35 @@ +# React + TypeScript + Vite + +This template provides a minimal setup to get React working in Vite with HMR and some Oxlint rules. + +Currently, two official plugins are available: + +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is enabled on this template. See [this documentation](https://react.dev/learn/react-compiler) for more information. + +Note: This will impact Vite dev & build performances. +You can also try [the experimental native React Compiler support in plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md#rust-react-compiler) by using `compiler: true` in the plugin options instead of using the Babel plugin. + +## Expanding the Oxlint configuration + +If you are developing a production application, we recommend enabling type-aware lint rules by installing `oxlint-tsgolint` and editing `.oxlintrc.json`: + +```json +{ + "$schema": "./node_modules/oxlint/configuration_schema.json", + "plugins": ["react", "typescript", "oxc"], + "options": { + "typeAware": true + }, + "rules": { + "react/rules-of-hooks": "error", + "react/only-export-components": ["warn", { "allowConstantExport": true }] + } +} +``` + +See the [Oxlint rules documentation](https://oxc.rs/docs/guide/usage/linter/rules) for the full list of rules and categories. diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/index.html b/07_js-debug/uebungen/u17-19_vitest-shop-tests/index.html new file mode 100644 index 0000000..8984afb --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/index.html @@ -0,0 +1,63 @@ + + + + + + + Übung 17 - Übung 19 - Komponententests + + + +
+ +
+
+

Übung 17: ImageGallery Benutzerinteraktionstest

+

+ In dieser Übung ist es deine Aufgabe, einen neuen Testfall zu ImageGallery.test.tsx hinzuzufügen, + in dem du testest, ob sich die Bilder ändern, wenn die Pfeiltasten gedrückt werden (Benutzerverhalten). +

+

+ Wenn du den Shop öffnest und zu einem beliebigen Produkt gehst und die Pfeiltasten benutzt, solltest du die + Bilder ändern können. Du kannst dies mit der Methode .keyboard('{ArrowRight}') oder + .keyboard('{ArrowLeft}') tun. +

+

Das folgende Verhalten sollte getestet werden:

+
    +
  1. + Du musst zum zweiten Bild gehen können, wenn du die rechte Pfeiltaste drückst. Überprüfe das Attribut src. +
  2. +
  3. Gehe zurück zum ersten Bild nach Schritt 1.
  4. + +
  5. Wenn du nach den Schritten 1 und 2 die linke Pfeiltaste drückst, wird das letzte Bild angezeigt.
  6. +
+
+
+

Übung 18: Test der Footer-Komponente

+

+ Deine Aufgabe ist es, einen Unit-Test für die Footer-Komponente in src/components/Footer.tsx zu + schreiben. Der Test sollte einen Fall enthalten, der die korrekte Darstellung der Navigationselemente + überprüft. +

+
    +
  • Verwende dein eigenes, benutzerdefiniertes Array für Navigationselemente.
  • +
  • Stelle sicher, dass alle Linktexte wie erwartet dargestellt werden.
  • +
+
+
+

Übung 19: CategorySection Komponentenprüfung

+

+ Deine Aufgabe ist es, einen Unit-Test für die Komponente Category Section in + src/components/CategorySection.tsx zu schreiben. Der Test sollte zwei Testfälle enthalten, die + auf Folgendes testen: +

+
    +
  1. Der erste Testfall sollte die Überschrift des Kategoriebereichs prüfen
  2. +
  3. Der zweite Testfall sollte prüfen, ob alle drei Links und Bilder gerendert werden.
  4. +

    Verwende das untenstehende Kategorienfeld, um den Test zu erstellen.

    +
+
+
+ + + diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/package.json b/07_js-debug/uebungen/u17-19_vitest-shop-tests/package.json new file mode 100644 index 0000000..91bb51f --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/package.json @@ -0,0 +1,39 @@ +{ + "name": "04_vite-shop", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "test": "vitest", + "test:watch": "vitest --watch", + "test:ui": "vitest --ui", + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "oxlint", + "preview": "vite preview" + }, + "dependencies": { + "react": "^19.3.1", + "react-dom": "^19.3.1", + "react-router-dom": "^7.18.5" + }, + "devDependencies": { + "@babel/core": "^8.1.0", + "@rolldown/plugin-babel": "^0.3.0", + "@testing-library/jest-dom": "^7.0.1", + "@testing-library/react": "^16.3.3", + "@testing-library/user-event": "^14.6.8", + "@types/babel__core": "^7.20.5", + "@types/node": "^26.6.6", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "^6.2.1", + "@vitest/ui": "^5.1.0", + "babel-plugin-react-compiler": "^1.0.0", + "jsdom": "^30.1.3", + "oxlint": "^1.91.0", + "typescript": "~7.0.2", + "vite": "^8.3.6", + "vitest": "^5.1.0" + } +} diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/public/favicon.svg b/07_js-debug/uebungen/u17-19_vitest-shop-tests/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/public/icons.svg b/07_js-debug/uebungen/u17-19_vitest-shop-tests/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/App.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/App.tsx new file mode 100644 index 0000000..dcbbc55 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/App.tsx @@ -0,0 +1,39 @@ +import { createBrowserRouter, RouterProvider } from 'react-router-dom'; +import Homepage from './pages/root'; +import WomenProducts from './pages/women'; +import MenProducts from './pages/men'; +import AccessoryProducts from './pages/accessories'; +import ProductPage from './pages/product'; + +const router = createBrowserRouter([ + { + path: '/', + element: , + }, + { + path: '/women', + element: , + }, + { + path: '/men', + element: , + }, + { + path: '/accessories', + element: , + }, + { + path: '/products/:productId', + element: , + }, +]); + +function App() { + return ( + <> + + + ); +} + +export default App; diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/assets/react.svg b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/assets/react.svg new file mode 100644 index 0000000..6c87de9 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/assets/react.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/assets/vite.svg b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/CategorySection.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/CategorySection.tsx new file mode 100644 index 0000000..4fb8862 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/CategorySection.tsx @@ -0,0 +1,77 @@ +import React from 'react'; + +type CategorySectionProps = { + categories: { + imgSrc: string; + imgAlt: string; + href: string; + linkText: string; + }[]; +}; + +import { Link } from 'react-router-dom'; + +const CategorySection: React.FC = ({ categories }) => { + return ( +
+
+

+ Explore by Category +

+ +
+ + {categories[0].imgAlt} + + +
+ ); +}; + +export default CategorySection; diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Footer.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Footer.tsx new file mode 100644 index 0000000..86ea22e --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Footer.tsx @@ -0,0 +1,55 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; + +type NavigationItem = { + name: string; + href: string; +}; + +const navigationItems: NavigationItem[] = [ + { + name: 'Home', + href: '/', + }, + { + name: 'Women', + href: '/women', + }, + { + name: 'Men', + href: '/men', + }, + { + name: 'Accessories', + href: '/accessories', + }, +]; + +type FooterProps = { + navigation?: NavigationItem[]; +}; + +const Footer: React.FC = ({ navigation = navigationItems }) => { + return ( +
+
+
+ {navigation.map((item) => ( + + {item.name} + + ))} +
+ +
+

+ © {new Date().getFullYear()} Vogue Junction, Inc. All rights + reserved. +

+
+
+
+ ); +}; + +export default Footer; diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Header.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Header.tsx new file mode 100644 index 0000000..ae21fd5 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Header.tsx @@ -0,0 +1,53 @@ +import { Link } from 'react-router-dom'; + +const Header = () => { + return ( +
+ {/* Mobile Header */} +
+
+ +
Vogue Junction
+ +
+ +
+ + Women + + + Men + + + Accessories + +
+
+ + {/* Desktop Header */} +
+
+ +
Vogue Junction
+ + +
+ + Women + + + Men + + + Accessories + +
+
+
+ +
+
+ ); +}; + +export default Header; diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Hero.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Hero.tsx new file mode 100644 index 0000000..afa8772 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/Hero.tsx @@ -0,0 +1,23 @@ +import React from 'react'; + +type HeroProps = { + companyName?: string; +}; + +const Hero: React.FC = ({ companyName = 'Vogue Junction' }) => { + return ( +
+
+

Welcome to the world of {companyName}!

+

+ Discover a diverse collection of stylish products for everyone. From + trendy apparel and accessories to timeless classics, we offer + something for every taste and occasion. Shop now and elevate your + style! +

+
+
+ ); +}; + +export default Hero; diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ImageGallery.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ImageGallery.tsx new file mode 100644 index 0000000..10a9671 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ImageGallery.tsx @@ -0,0 +1,71 @@ +import React, { useState, useEffect } from 'react'; +import type { Product } from '../lib/datatypes'; + +type ImageGalleryProps = { + product: Product; +}; + +const ImageGallery: React.FC = ({ product }: { product: Product }) => { + const [selectedImage, setSelectedImage] = useState(product.images[0]); + const [selectedIndex, setSelectedIndex] = useState(0); + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === 'ArrowRight') { + setSelectedIndex((prevIndex) => { + const nextIndex = (prevIndex + 1) % product.images.length; + setSelectedImage(product.images[nextIndex]); + return nextIndex; + }); + } + + if (event.key === 'ArrowLeft') { + setSelectedIndex((prevIndex) => { + const nextIndex = (prevIndex - 1 + product.images.length) % product.images.length; + setSelectedImage(product.images[nextIndex]); + return nextIndex; + }); + } + }; + + useEffect(() => { + window.addEventListener('keydown', handleKeyDown); + + return () => { + window.removeEventListener('keydown', handleKeyDown); + }; + }, [product.images]); + + return ( +
+ {/* Image selector */} +
+
+ {product.images.map((image, index) => ( + + ))} +
+
+ + {/* Main Image Display */} +
+ {product.title} +
+
+ ); +}; + +export default ImageGallery; diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ProductList.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ProductList.tsx new file mode 100644 index 0000000..3859b46 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ProductList.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import type { Product } from '../lib/datatypes'; + +import ProductListCard from './ProductListCard'; + +type ProductListProps = { + data: Product[]; +}; + +const ProductList: React.FC = ({ data }) => { + return ( +
+

Products

+ +
+ {data.map((product) => ( + + ))} +
+
+ ); +}; + +export default ProductList; diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ProductListCard.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ProductListCard.tsx new file mode 100644 index 0000000..7c22ad4 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/components/ProductListCard.tsx @@ -0,0 +1,64 @@ +import React from 'react'; +import { Link } from 'react-router-dom'; +import type { Product } from '../lib/datatypes'; + +const ProductListCard = ({ product }: { product: Product }) => { + const { id, title, price, rating, stock, thumbnail } = product; + + return ( +
+
+ {title} +
+ +
+

+ +

+ +
+

{Math.round(rating)} out of 5 stars

+ +
+ {[0, 1, 2, 3, 4].map((ratingNumber) => ( + ratingNumber ? '#1e293b' : '#e5e7eb', + }} + aria-hidden="true" + /> + ))} +
+ +

{stock} in stock

+
+ +

+ {Number(price).toLocaleString('en-US', { + style: 'currency', + currency: 'USD', + })} +

+
+
+ ); +}; + +export default ProductListCard; + +function StarIcon(props: React.SVGProps) { + return ( + + + + ); +} diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/index.css b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/index.css new file mode 100644 index 0000000..1b82055 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/index.css @@ -0,0 +1,810 @@ +*, +*::before, +*::after { + box-sizing: border-box; +} + +*, +html, +body { + margin: 0; + padding: 0; + font-family: + system-ui, + -apple-system, + BlinkMacSystemFont, + 'Segoe UI', + Roboto, + Oxygen, + Ubuntu, + Cantarell, + 'Open Sans', + 'Helvetica Neue', + sans-serif; + color: #0f172a; +} + +button { + background-color: transparent; + border: none; +} + +a { + text-decoration: none; + color: inherit; +} + +header { + position: relative; + width: 100%; +} + +footer { + border: 1px solid #e5e7eb; + border-top-width: 1px; + margin-top: 3.5rem /* 56px */; +} + +.screen-reader-text { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; +} + +.star-icons { + height: 1.25rem /* 20px */; + width: 1.25rem /* 20px */; + flex-shrink: 0; +} + +/* ===== Header Component ===== */ +.logo-text { + font-size: 1.25rem /* 20px */; + line-height: 1.75rem /* 28px */; + font-weight: 600; + color: #1f2937; +} + +.header-container { + position: relative; + display: flex; + align-items: center; + justify-content: space-between; + padding: 1.5rem 1rem; /* 24px 16px */ + max-width: 80rem; /* 1280px */ + margin: 0 auto; +} + +.mobile-header-wrapper { + display: block; +} + +.mobile-header-links-container { + display: flex; + justify-content: center; + gap: 1.5rem; /* 24px */ + padding-bottom: 1rem /* 16px */; +} + +.mobile-header-logo-link { + display: flex; + align-items: center; + justify-content: center; + gap: 1rem; /* 16px */ + width: 100%; +} + +.desktop-header-wrapper { + display: none; +} + +.desktop-header-logo-link { + display: flex; + align-items: center; + gap: 1rem; /* 16px */ +} + +.desktop-header-links-container { + position: absolute; + left: 50%; + transform: translateX(-50%); + display: flex; + gap: 1.5rem; /* 24px */ +} + +.header-link-text { + padding: 0.5rem /* 8px */; + color: #1f2937; +} + +.header-link-text:hover { + color: #4b5563; +} + +.header-border { + border-bottom-width: 2px; + width: 100%; + box-shadow: + 0 4px 6px -1px rgb(0 0 0 / 0.1), + 0 2px 4px -2px rgb(0 0 0 / 0.1); + border-color: #f3f4f6; +} + +/* ===== Hero Component ===== */ +.hero-wrapper { + max-width: 42rem /* 672px */; + margin: 0 auto; + padding: 6rem 1rem; /* 96px 16px */ +} + +.hero-text-container { + text-align: center; +} + +.hero-title { + font-size: 2.25rem /* 36px */; + line-height: 2.5rem /* 40px */; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; +} + +.hero-subtitle { + margin-top: 2.5rem /* 40px */; + font-size: 1.125rem /* 18px */; + line-height: 2rem /* 32px */; + color: #4b5563; +} + +/* ===== CategorySection Component ===== */ +.category-section-wrapper { + margin: 0 auto; + max-width: 80rem /* 1280px */; + padding: 2.5rem 1rem; /* 40px 16px */ +} + +.category-section-title { + font-size: 1.5rem /* 24px */; + line-height: 2rem /* 32px */; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; +} + +.category-section-grid-wrapper { + margin-top: 1.5rem /* 24px */; + display: grid; + grid-template-columns: repeat(1, minmax(0, 1fr)); + row-gap: 1.5rem /* 24px */; +} + +.category-section-grid-main { + position: relative; + overflow: hidden; + border-radius: 0.5rem /* 8px */; + aspect-ratio: 1 / 2; + width: 100%; + height: 100%; + max-height: 250px; +} + +.category-section-grid-main:hover { + opacity: 0.85; +} + +.category-section-grid-main-img { + height: 100%; + width: 100%; + object-fit: cover; + object-position: top; + filter: grayscale(100%); +} + +.category-section-gradient-bg { + position: absolute; + bottom: 0; + z-index: 100; + height: 100%; + width: 100%; + background: linear-gradient(to bottom, transparent, black); + opacity: 0.5; +} + +.category-section-cta-container { + position: absolute; + z-index: 100; + bottom: 0; + display: flex; + align-items: flex-end; + padding: 1.5rem /* 24px */; +} + +.category-section-cta-title { + font-weight: 600; + color: white; +} + +.category-section-cta-span { + position: absolute; + inset: 0px; + z-index: 100; +} + +.category-section-cta { + margin-top: 0.25rem /* 4px */; + font-size: 0.875rem /* 14px */; + line-height: 1.25rem /* 20px */; + color: white; +} + +.category-section-categories-container { + position: relative; + aspect-ratio: 1 / 2; + overflow: hidden; + border-radius: 0.5rem /* 8px */; + height: 100%; + width: 100%; + max-height: 250px; +} + +.category-section-categories-container:hover { + opacity: 0.85; +} + +.category-section-categories-img { + height: 100%; + width: 100%; + object-fit: cover; + object-position: center; + filter: grayscale(100%); +} + +.category-section-categories-cta-container { + position: absolute; + z-index: 100; + bottom: 0; + display: flex; + align-items: flex-end; + padding: 1.5rem; /* 24px */ +} + +/* ===== Footer Component ===== */ +.footer-wrapper { + margin: 0 auto; + max-width: 80rem /* 1280px */; + padding: 3rem 1.5rem; /* 48px 24px */ +} + +.footer-links-container { + display: flex; + justify-content: center; + gap: 1.5rem; /* 24px */ +} + +.footer-links-text { + color: #9ca3af; +} + +.footer-links-text:hover { + color: #4b5563; +} + +.footer-copyright { + margin-top: 2rem /* 32px */; +} + +.footer-copyright-text { + text-align: center; + font-size: 0.75rem /* 12px */; + line-height: 1rem /* 16px */; + line-height: 1.25rem /* 20px */; + color: #6b7280; +} + +/* ===== ProductList Component ===== */ +.products-list { + margin: 0 -1px; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); +} + +/* ===== ProductListCard Component ===== */ +.product-card-container { + position: relative; + padding: 1rem; +} + +.product-card-img-wrapper { + aspect-ratio: 1 / 1; + overflow: hidden; + border-radius: 0.5rem /* 8px */; + background-color: white; +} + +.product-card-container:hover .product-card-img-wrapper { + opacity: 0.75; +} + +.product-card-img { + height: 100%; + width: 100%; + object-fit: cover; + object-position: center; +} + +.product-card-info-box { + padding-bottom: 1rem /* 16px */; + padding-top: 2.5rem /* 40px */; + text-align: center; +} + +.product-card-name { + font-size: 0.875rem /* 14px */; + line-height: 1.25rem /* 20px */; + font-weight: 500; + color: #111827; +} + +.product-card-name-span { + position: absolute; + inset: 0px; +} + +.product-card-desc-box { + margin-top: 0.75rem /* 12px */; + display: flex; + flex-direction: column; + align-items: center; +} + +.product-card-ratings-wrapper { + display: flex; + align-items: center; +} + +.product-card-stock { + margin-top: 0.25rem /* 4px */; + font-size: 0.875rem /* 14px */; + line-height: 1.25rem /* 20px */; + color: #6b7280; +} + +.product-card-price { + margin-top: 1rem /* 16px */; + font-size: 1rem /* 16px */; + line-height: 1.5rem /* 24px */; + font-weight: 500; + color: #111827; +} + +/* ===== ImageGallery Component ===== */ +.image-gallery-wrapper { + display: flex; + flex-direction: column-reverse; +} + +.image-selector-wrapper { + margin-left: auto; + margin-right: auto; + margin-top: 1.5rem /* 24px */; + display: none; + width: 100%; + max-width: 42rem /* 672px */; +} + +.image-selector-container { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 1.5rem /* 24px */; +} + +.image-selector-btn { + position: relative; + display: flex; + height: 6rem /* 96px */; + cursor: pointer; + align-items: center; + justify-content: center; + border-radius: 0.375rem /* 6px */; + background-color: white; + font-size: 0.875rem /* 14px */; + line-height: 1.25rem /* 20px */; + font-weight: 500; + text-transform: uppercase; + color: #111827; +} + +.image-selector-btn:hover { + background-color: #f9fafb; +} + +.image-selector-img-wrapper { + position: absolute; + inset: 0px; + display: flex; + overflow: hidden; + border-radius: 0.375rem /* 6px */; +} + +.image-selector-img { + height: 100%; + width: 100%; + object-fit: cover; + object-position: center; +} + +.image-selector-main-img-wrapper { + aspect-ratio: 1 / 1; + width: 100%; +} + +.image-selector-main-img { + height: 100%; + width: 100%; + object-fit: cover; + object-position: center; +} + +/* ===== Category Page ===== */ +.category-page-wrapper { + padding: 3.5rem 0; /* 56px 0 */ +} + +.category-page-container { + margin: 0 auto; + max-width: 80rem /* 1280px */; + overflow: hidden; + padding: 0 1rem; /* 0 16px */ +} + +.category-page-header { + display: flex; + justify-content: space-between; + align-items: top; +} + +.category-page-header-title { + font-size: 1.5rem /* 24px */; + line-height: 2rem /* 32px */; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; + margin-bottom: 2rem /* 32px */; +} + +.category-page-select { + margin: 0.5rem 0; /* 8px 0 */ + display: block; + width: 11rem /* 176px */; + border: 1px solid #d1d5db; + border-radius: 0.375rem /* 6px */; + padding-top: 0.5rem /* 8px */; + padding-bottom: 0.5rem /* 8px */; + color: #0f172a; +} + +.category-page-select:focus { + border: 1px solid #1e293b; +} + +.category-page-products-wrapper { + margin-top: 2.5rem /* 40px */; +} + +/* ===== Single Product Page ===== */ +.back-button-wrapper { + margin: 0 auto; + max-width: 42rem /* 672px */; + padding-left: 1rem /* 16px */; + padding-right: 1rem /* 16px */; + padding-top: 2rem /* 32px */; +} + +.back-button { + display: flex; + align-items: center; + margin-bottom: 1rem /* 16px */; + font-weight: 500; + margin-top: 1.5rem /* 24px */; +} + +.back-button-icon { + height: 1.5rem /* 24px */; + width: 1.5rem /* 24px */; + stroke: #1e293b; +} + +.back-button-icon-span { + margin-left: 0.75rem /* 12px */; + font-size: 1rem /* 16px */; + line-height: 1.5rem /* 24px */; +} + +.product-page-wrapper { + margin: 0 auto; + max-width: 42rem /* 672px */; + padding: 2.5rem 1rem; /* 40px 16px */ +} + +.product-page-container { + display: block; +} + +.product-page-info-container { + margin-top: 2.5rem /* 40px */; + padding-left: 1rem /* 16px */; + padding-right: 1rem /* 16px */; +} + +.product-page-info-title { + font-size: 1.875rem /* 30px */; + line-height: 2.25rem /* 36px */; + font-weight: 700; + letter-spacing: -0.025em; + color: #111827; +} + +.product-page-mt-3 { + margin-top: 0.75rem /* 12px */; +} + +.product-page-mt-6 { + margin-top: 1.5rem /* 24px */; +} + +.product-page-text { + font-size: 0.875rem /* 14px */; + line-height: 1.25rem /* 20px */; + color: #6b7280; +} + +.product-page-info-price { + font-size: 1.875rem /* 30px */; + line-height: 2.25rem /* 36px */; + letter-spacing: -0.025em; + color: #111827; +} + +.product-page-info-rating-container { + display: flex; + align-items: center; +} + +.product-page-description { + font-size: 1rem /* 16px */; + line-height: 1.5rem /* 24px */; + line-height: 2; + color: #374151; +} + +.product-page-tags-container { + display: flex; + align-items: center; + gap: 0.5rem; /* 8px */ +} + +.product-page-tag { + font-size: 0.75rem /* 12px */; + line-height: 1rem /* 16px */; + font-weight: 500; + text-transform: uppercase; + padding: 0.25rem 0.5rem; + border-radius: 9999px; + background-color: #1e293b; + color: white; +} + +.product-page-reviews-wrapper { + margin-top: 3.5rem /* 56px */; +} + +.product-page-reviews-title { + font-size: 1.5rem /* 24px */; + line-height: 2rem /* 32px */; + font-weight: 500; + color: #111827; +} + +.product-page-reviews-container { + display: flex; + flex-direction: column; + gap: 1.25rem /* 20px */; + margin-top: 0.5rem /* 8px */; +} + +.product-page-ratings-box { + display: flex; + align-items: center; +} + +.product-page-reviews-text-box { + display: flex; + align-items: center; + gap: 1rem; /* 16px */ + margin-top: 0.5rem /* 8px */; +} + +.product-page-reviews-comment { + margin-top: 0.5rem /* 8px */; + font-size: 1.125rem /* 18px */; + line-height: 1.75rem /* 28px */; + color: #374151; +} + +/* ===== Breakpoints ===== */ +@media (min-width: 640px) { + .desktop-header-container { + padding: 1.5rem 2rem; /* 24px 32px */ + } + + .hero-wrapper { + padding: 6rem 2rem; /* 96px 32px */ + } + + .hero-title { + font-size: 3.75rem /* 60px */; + line-height: 1; + } + + .category-section-wrapper { + padding: 2.5rem 1.5rem; /* 40px 24px */ + } + + .category-section-grid-wrapper { + grid-template-columns: repeat(2, minmax(0, 1fr)); + grid-template-rows: repeat(2, minmax(0, 1fr)); + column-gap: 1.5rem /* 24px */; + grid-row: span 2 / span 2; + } + + .category-section-grid-main { + aspect-ratio: 1 / 1; + grid-row: span 2 / span 2; + max-height: 100%; + } + + .category-section-grid-main-img { + object-position: center; + } + + .category-section-categories-container { + position: relative; + max-height: 300px; + } + + .category-section-categories-img { + position: absolute; + inset: 0px; + height: 100%; + width: 100%; + } + + .category-section-categories-cta-container { + position: absolute; + inset: 0px; + } + + .category-page-container { + padding: 0 1.5rem; /* 0 24px */ + } + + .products-list { + margin: 0; + } + + .product-card-container { + padding: 1.5rem; + } + + .back-button-wrapper { + padding-left: 1.5rem /* 24px */; + padding-right: 1.5rem /* 24px */; + } + + .product-page-wrapper { + padding: 2.5rem 1.5rem; /* 40px 24px */ + } + + .image-selector-wrapper { + display: block; + } + + .image-selector-main-img { + border-radius: 0.5rem /* 8px */; + } + + .product-page-info-container { + margin-top: 4rem /* 64px */; + padding-left: 0px; + padding-right: 0px; + } +} + +@media (min-width: 768px) { + footer { + margin-top: 5rem; /* 80px */ + } + + .mobile-header-wrapper { + display: none; + } + + .desktop-header-wrapper { + display: block; + } + + .logo-text { + font-size: 1.5rem /* 24px */; + line-height: 2rem /* 32px */; + } + + .footer-wrapper { + display: flex; + align-items: center; + justify-content: space-between; + padding: 3rem 2rem; /* 48px 32px */ + } + + .footer-links-container { + order: 2; + } + + .footer-copyright { + order: 1; + margin-top: 0; + } + + .products-list { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + +@media (min-width: 1024px) { + footer { + margin-top: 6rem; /* 96px */ + } + + .category-section-wrapper { + padding: 2.5rem 2rem /* 40px 32px */; + } + + .category-page-container { + padding: 0 2rem; /* 0 32px */ + } + + .products-list { + grid-template-columns: repeat(4, minmax(0, 1fr)); + } + + .back-button-wrapper { + max-width: 80rem /* 1280px */; + padding-left: 2rem /* 32px */; + padding-right: 2rem /* 32px */; + } + + .product-page-wrapper { + padding: 2.5rem 2rem; /* 40px 32px */ + max-width: 80rem /* 1280px */; + } + + .product-page-container { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + align-items: center; + column-gap: 2rem /* 32px */; + } + + .image-selector-wrapper { + max-width: none; + } + + .product-page-info-container { + margin-top: 0px; + } +} diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/lib/datatypes.ts b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/lib/datatypes.ts new file mode 100644 index 0000000..f0e5483 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/lib/datatypes.ts @@ -0,0 +1,45 @@ +export interface Product { + id: number; + title: string; + description: string; + category: string; + price: number; + discountPercentage: number; + rating: number; + stock: number; + tags: string[]; + brand: string; + sku: string; + weight: number; + dimensions: Dimensions; + warrantyInformation: string; + shippingInformation: string; + availabilityStatus: string; + reviews: Review[]; + returnPolicy: string; + minimumOrderQuantity: number; + meta: Meta; + images: string[]; + thumbnail: string; +} + +export interface Dimensions { + width: number; + height: number; + depth: number; +} + +export interface Review { + rating: number; + comment: string; + date: Date; + reviewerName: string; + reviewerEmail: string; +} + +export interface Meta { + createdAt: Date; + updatedAt: Date; + barcode: string; + qrCode: string; +} diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/lib/helpers.ts b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/lib/helpers.ts new file mode 100644 index 0000000..2168c83 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/lib/helpers.ts @@ -0,0 +1,12 @@ +import type { Product } from './datatypes'; + +export const fetchProductsByCategory = async (category: string): Promise => { + try { + const response = await fetch(`https://dummyjson.com/products/category/${category}`); + const data = await response.json(); + return data.products; + } catch (error) { + console.error(`Failed to fetch products for category ${category}:`, error); + return []; + } +}; diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/main.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/main.tsx new file mode 100644 index 0000000..bef5202 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/main.tsx @@ -0,0 +1,10 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' +import './index.css' +import App from './App.tsx' + +createRoot(document.getElementById('root')!).render( + + + , +) diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/accessories.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/accessories.tsx new file mode 100644 index 0000000..036dd23 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/accessories.tsx @@ -0,0 +1,76 @@ +import { useState, useEffect } from 'react'; + +import Header from '../components/Header'; +import ProductList from '../components/ProductList'; +import Footer from '../components/Footer'; + +import type { Product } from '../lib/datatypes'; +import { fetchProductsByCategory } from '../lib/helpers'; + +export default function AccessoryProducts() { + const [products, setProducts] = useState([]); + const [selectedCategory, setSelectedCategory] = useState('all'); + + const categories = ['all', 'mobile-accessories', 'sports-accessories', 'kitchen-accessories']; + + const fetchProducts = async () => { + try { + const fetchedProducts = + selectedCategory === 'all' + ? await Promise.all(categories.slice(1).map(fetchProductsByCategory)) + : await fetchProductsByCategory(selectedCategory); + + const allProducts = Array.isArray(fetchedProducts) ? fetchedProducts.flat() : fetchedProducts; + + setProducts(allProducts); + } catch (error) { + console.error('Failed to fetch products:', error); + } + }; + + useEffect(() => { + fetchProducts(); + }, [selectedCategory]); + + return ( +
+
+ +
+
+
+

+ Accessories +

+ +
+ + +
+
+ +
{products && }
+
+
+ +
+
+ ); +} diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/men.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/men.tsx new file mode 100644 index 0000000..5f73e73 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/men.tsx @@ -0,0 +1,76 @@ +import { useState, useEffect } from 'react'; + +import Header from '../components/Header'; +import ProductList from '../components/ProductList'; +import Footer from '../components/Footer'; + +import type { Product } from '../lib/datatypes'; +import { fetchProductsByCategory } from '../lib/helpers'; + +export default function MenProducts() { + const [products, setProducts] = useState([]); + const [selectedCategory, setSelectedCategory] = useState('all'); + + const categories = ['all', 'mens-shirts', 'mens-shoes', 'mens-watches']; + + const fetchProducts = async () => { + try { + const fetchedProducts = + selectedCategory === 'all' + ? await Promise.all(categories.slice(1).map(fetchProductsByCategory)) + : await fetchProductsByCategory(selectedCategory); + + const allProducts = Array.isArray(fetchedProducts) ? fetchedProducts.flat() : fetchedProducts; + + setProducts(allProducts); + } catch (error) { + console.error('Failed to fetch products:', error); + } + }; + + useEffect(() => { + fetchProducts(); + }, [selectedCategory]); + + return ( +
+
+ +
+
+
+

+ Men Products +

+ +
+ + +
+
+ +
{products && }
+
+
+ +
+
+ ); +} diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/product.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/product.tsx new file mode 100644 index 0000000..5538385 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/product.tsx @@ -0,0 +1,188 @@ +import { useState, useEffect } from 'react'; +import type { Product, Review } from '../lib/datatypes'; + +import { useParams, useNavigate } from 'react-router-dom'; + +import Header from '../components/Header'; +import ImageGallery from '../components/ImageGallery'; +import Footer from '../components/Footer'; + +const ProductPage = () => { + const { productId } = useParams<{ productId: string }>(); + const [product, setProduct] = useState(null); + + const fetchProduct = async () => { + try { + const productRes = await fetch(`https://dummyjson.com/products/${productId}`); + const productData = await productRes.json(); + setProduct(productData); + } catch (error) { + console.error('Failed to fetch products:', error); + } + }; + + useEffect(() => { + fetchProduct(); + }, [productId]); + + if (!product) { + return null; + } + + return ( +
+
+ +
+ + +
+
+ + + {/* Product info */} +
+

{product.title}

+ +
+

Product information

+ +

+ {Number(product.price).toLocaleString('en-US', { + style: 'currency', + currency: 'USD', + })} +

+ + {/* Rating */} +
+

Rating

+ +
+
+ {[0, 1, 2, 3, 4].map((rating) => ( + rating ? '#1e293b' : '#e5e7eb', + }} + aria-hidden="true" + /> + ))} +
+

{product.rating} out of 5 stars

+
+
+ + {/* Availability */} +
+

{product.stock} in stock

+
+ + {/* Description */} +
+

Description

+ +

{product.description}

+
+ + {/* Tags */} +
+

Tags

+
+ {product.tags.map((tag) => ( + + {tag} + + ))} +
+
+
+
+ + +
+
+
+ +
+
+ ); +}; + +export default ProductPage; + +function BackButton() { + const navigate = useNavigate(); + + return ( +
+ +
+ ); +} + +function Reviews({ reviews }: { reviews: Review[] }) { + return ( +
+

Reviews

+ +
+ {reviews.map((review, index) => ( +
+
+ {[0, 1, 2, 3, 4].map((rating) => ( + rating ? '#1e293b' : '#e5e7eb', + }} + aria-hidden="true" + /> + ))} +
+ +
+

{review.reviewerName}

+ +

{new Date(review.date).toLocaleDateString()}

+
+ +

{review.comment}

+
+ ))} +
+
+ ); +} + +function ChevronRightIcon(props: React.SVGProps) { + return ( + + + + ); +} + +function StarIcon(props: React.SVGProps) { + return ( + + + + ); +} diff --git a/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/root.tsx b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/root.tsx new file mode 100644 index 0000000..a20d395 --- /dev/null +++ b/07_js-debug/uebungen/u17-19_vitest-shop-tests/src/pages/root.tsx @@ -0,0 +1,39 @@ +import Header from '../components/Header'; +import Hero from '../components/Hero'; +import CategorySection from '../components/CategorySection'; +import Footer from '../components/Footer'; + +const categories = [ + { + imgSrc: 'https://images.unsplash.com/photo-1530011940847-04cba36df39f?q=80&w=1920', + imgAlt: 'Two female models sitting on a bed. Photo by Billie (@billiebodybrand) on Unsplash.', + href: '/women', + linkText: 'Women Clothing', + }, + { + imgSrc: 'https://images.unsplash.com/photo-1595909315417-2edd382a56dc?q=80&w=1920', + imgAlt: + 'Jumping rope, smartphone, sports bra, a pair of sneakers, and a water bottle. Photo by erica steeves on Unsplash.', + href: '/accessories', + linkText: 'Accessories', + }, + { + imgSrc: 'https://images.unsplash.com/photo-1467779009031-53938b78ca38?q=80&w=1920', + imgAlt: 'Group of men sitting in a van. Photo by Luke Porter on Unsplash.', + href: '/men', + linkText: 'Men Clothing', + }, +]; + +export default function Root() { + return ( + <> +
+
+ + +
+