diff --git a/cypress.config.ts b/cypress.config.ts index 6aa317d0101..3f52f1248e9 100644 --- a/cypress.config.ts +++ b/cypress.config.ts @@ -1,9 +1,10 @@ -const { defineConfig } = require('cypress'); +/* eslint-disable */ +import { defineConfig } from 'cypress'; -module.exports = defineConfig({ +export default defineConfig({ e2e: { baseUrl: 'http://localhost:3000', - specPattern: 'cypress/integration/**/*.spec.{js,ts,jsx,tsx}', + specPattern: 'cypress/integration/**/*.{cy,spec}.{js,ts,jsx,tsx}', }, video: true, viewportHeight: 1920, diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json index 0211a30742d..c44713bd6b1 100644 --- a/cypress/tsconfig.json +++ b/cypress/tsconfig.json @@ -1,6 +1,7 @@ { "extends": "@mate-academy/students-ts-config", "compilerOptions": { - "sourceMap": false + "sourceMap": false, + "baseUrl": "." } } diff --git a/index.html b/index.html index 095fb3a4537..0f5ac63bf4f 100644 --- a/index.html +++ b/index.html @@ -3,9 +3,10 @@ - Vite + React + TS + Welcome to Nice Gadgets store! + - +
diff --git a/package-lock.json b/package-lock.json index 836b9e63b46..b65cac4c40e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,11 +16,12 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.25.1", - "react-transition-group": "^4.4.5" + "react-transition-group": "^4.4.5", + "swiper": "^12.1.4" }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", @@ -1184,10 +1185,11 @@ } }, "node_modules/@mate-academy/scripts": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.8.5.tgz", - "integrity": "sha512-mHRY2FkuoYCf5U0ahIukkaRo5LSZsxrTSgMJheFoyf3VXsTvfM9OfWcZIDIDB521kdPrScHHnRp+JRNjCfUO5A==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-2.1.3.tgz", + "integrity": "sha512-a07wHTj/1QUK2Aac5zHad+sGw4rIvcNl5lJmJpAD7OxeSbnCdyI6RXUHwXhjF5MaVo9YHrJ0xVahyERS2IIyBQ==", "dev": true, + "license": "MIT", "dependencies": { "@octokit/rest": "^17.11.2", "@types/get-port": "^4.2.0", @@ -9930,6 +9932,25 @@ "integrity": "sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==", "dev": true }, + "node_modules/swiper": { + "version": "12.1.4", + "resolved": "https://registry.npmjs.org/swiper/-/swiper-12.1.4.tgz", + "integrity": "sha512-bihiwoKMOQwW8FfdUbo1DgkVH25E+4ZELIq0oopL1KTKBteLuaTMi/wwFjMxtlhTkk45k3XQ89D1Fvv0spSqBA==", + "funding": [ + { + "type": "custom", + "url": "https://sponsors.nolimits4web.com" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nolimits4web" + } + ], + "license": "MIT", + "engines": { + "node": ">= 4.7.0" + } + }, "node_modules/synckit": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", diff --git a/package.json b/package.json index ae251685c8b..48bb48bab1f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react_phone-catalog", - "homepage": "react_phone-catalog", + "homepage": "https://u5135039754-dev.github.io/react_phone-catalog/", "version": "0.1.0", "keywords": [], "author": "Mate Academy", @@ -12,11 +12,12 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.25.1", - "react-transition-group": "^4.4.5" + "react-transition-group": "^4.4.5", + "swiper": "^12.1.4" }, "devDependencies": { "@cypress/react18": "^2.0.1", - "@mate-academy/scripts": "^1.8.5", + "@mate-academy/scripts": "^2.1.3", "@mate-academy/students-ts-config": "*", "@mate-academy/stylelint-config": "*", "@types/node": "^20.14.10", diff --git a/public/api/accessories.json b/public/api/accessories.json index 6a36890cec7..eba8266c643 100644 --- a/public/api/accessories.json +++ b/public/api/accessories.json @@ -1428,3 +1428,5 @@ "cell": ["Wi-Fi", "Bluetooth", "LTE"] } ] + + diff --git a/public/api/phones.json b/public/api/phones.json index e1535893222..fb045b2009c 100644 --- a/public/api/phones.json +++ b/public/api/phones.json @@ -5833,3 +5833,5 @@ "cell": ["GPRS", "EDGE", "WCDMA", "UMTS", "HSPA", "LTE", "5G"] } ] + + diff --git a/public/api/products.json b/public/api/products.json index 41ecb5e5a6f..2d12cf45257 100644 --- a/public/api/products.json +++ b/public/api/products.json @@ -2715,4 +2715,5 @@ "year": 2022, "image": "img/phones/apple-iphone-14-pro/gold/00.webp" } -] \ No newline at end of file +] + diff --git a/public/api/tablets.json b/public/api/tablets.json index dc555396f72..3e7f68806f0 100644 --- a/public/api/tablets.json +++ b/public/api/tablets.json @@ -1700,3 +1700,5 @@ "cell": ["Not applicable"] } ] + + diff --git a/setup.md b/setup.md index db99c0a748e..7a3835e07b0 100644 --- a/setup.md +++ b/setup.md @@ -155,7 +155,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-node@v1 with: - node-version: "12.x" + node-version: [20.x] - run: npm ci - run: npm run build - name: Deploy diff --git a/src/App.scss b/src/App.scss index 71bc413aade..28d13f89768 100644 --- a/src/App.scss +++ b/src/App.scss @@ -1 +1,52 @@ -// not empty +@import './Components/Home/Home'; +@import './Components/TopBar/top-bar'; +@import './Components/TopBar/nav'; +@import './utils/vars'; +@import './utils/mixins'; +@import './fonts/fonts'; +@import './Components/Header/header'; +@import './styles/section'; +@import './styles/WelcomeBlock'; +@import './styles/icon'; +@import './styles/categories'; +@import './styles/Page'; +@import './Components/Footer/Footer'; +@import './Components/ErrorNotification/ErrorNotification'; +@import './styles/newModels'; +@import './styles/Aside'; + + +html, +body { + width: 100%; + min-height: 100%; + overflow-x: hidden; +} + +html { + box-sizing: border-box; +} + +*, *::before, *::after { + box-sizing: inherit; + min-width: 0; +} + +img, +svg, +button, +a { + max-width: 100%; +} + +body { + margin: 0; + min-height: 100vh; + background-color: #0F1121; + color: #F1F2F9; + font-family: Mont, sans-serif; +} + +.container { + @include content-padding-inline; +} diff --git a/src/App.tsx b/src/App.tsx index 372e4b42066..979ae650c88 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,10 @@ -import './App.scss'; +import { Outlet } from 'react-router-dom'; +import React from 'react'; -export const App = () => ( -
-

Product Catalog

-
-); +export const App = () => { + return ( +
+ +
+ ); +}; diff --git a/src/Components/Accessories/Accessories.scss b/src/Components/Accessories/Accessories.scss new file mode 100644 index 00000000000..08dae30b633 --- /dev/null +++ b/src/Components/Accessories/Accessories.scss @@ -0,0 +1,21 @@ +.accessories { + &__path { + display: flex; + + gap: 8px; + + align-items: center; + + margin-top: 25px; + + + &-phones { + font-family: "Mont SemiBold", sans-serif; + + color: #75767F; + font-weight: 700; + font-size: 12px; + line-height: 100%; + } + } +} diff --git a/src/Components/Accessories/Accessories.tsx b/src/Components/Accessories/Accessories.tsx new file mode 100644 index 00000000000..c1bcb30a80b --- /dev/null +++ b/src/Components/Accessories/Accessories.tsx @@ -0,0 +1,207 @@ +/* eslint-disable max-len */ +import { Link } from 'react-router-dom'; +import { Filter, FilterValue, ItemQuantity } from '../Filter/Filter'; +import { Header } from '../Header/header'; +import ArrowGray from '../../images/icons/ChevronGray.svg'; +import Home from '../../images/icons/Home.svg'; +import './Accessories.scss'; +import { getAccessories } from '../../api/api'; +import { useEffect, useState } from 'react'; +import { Accessorie } from '../../types/Accessories'; +import { Products } from '../../types/Products'; +import { useCart } from '../../Context/Context'; +import { useFav } from '../../Context/FavouritesContext'; +import React from 'react'; +import { ActiveQuantity16 } from '../ActiveQuantity/ActiveQuantityAcc/ActiveQuantity16'; +import { PageSliderAcc } from '../Page__Slider/PageSliderAcc'; +import { ActiveQuantity32 } from '../ActiveQuantity/ActiveQuantityAcc/ActiveQuantity32'; +import { ActiveQuantity64 } from '../ActiveQuantity/ActiveQuantityAcc/ActiveQuantity64'; +import { Footer } from '../Footer/Footer'; +import { Aside } from '../Aside/Aside'; +import { Loader } from '../Loader/Loader'; + +export const Accessories = () => { + const [menuOpen, setMenuOpen] = useState(false); + const [accessories, setAccessories] = useState([]); + const [Loading, setLoading] = useState(true); + const [, setErrorMessage] = useState(''); + const { totalQuantity } = useCart(); + const { totalFavourites } = useFav(); + const [activeQuantity, setActiveQuantity] = useState(16); + const [activeFilter, setActiveFilter] = useState('Newest'); + const [activePage, setActivePage] = useState(0); + const mapAccessorieToProduct = (accessorie: Accessorie): Products => ({ + id: String(accessorie.id), + itemId: String(accessorie.id), + name: accessorie.name, + category: accessorie.category, + fullPrice: Number(accessorie.priceRegular), + price: Number(accessorie.priceDiscount || accessorie.priceRegular), + screen: accessorie.screen, + capacity: accessorie.capacity, + color: accessorie.color || accessorie.colorsAvailable?.[0] || '—', + ram: accessorie.ram, + year: new Date().getFullYear(), + image: + typeof accessorie.images === 'string' + ? accessorie.images + : accessorie.images?.[0], + }); + + const getAccessoryOrder = (name: string) => { + if (name.includes('XS')) { + return 10.2; + } + + if (name.includes('XR')) { + return 10.1; + } + + if (name.includes('X')) { + return 10; + } + + const match = name.match(/\d+/); + + return match ? parseInt(match[0], 10) : 0; + }; + + const sortedAcc = [...accessories].sort((a, b) => { + return getAccessoryOrder(b.name) - getAccessoryOrder(a.name); + }); + + const sortedAccLowToHigh = + activeFilter === 'Price: Low to High' + ? [...accessories].sort((a, b) => a.priceRegular - b.priceRegular) + : accessories; + + const sortedAccHighToLow = + activeFilter === 'Price: High to Low' + ? [...accessories].sort((a, b) => b.priceRegular - a.priceRegular) + : accessories; + + useEffect(() => { + setLoading(true); + setErrorMessage(''); + + getAccessories() + .then(setAccessories) + .catch(() => setErrorMessage(`Couldn't load any accessories`)) + .finally(() => setLoading(false)); + }, []); + + return ( +
+
+ {menuOpen && ( +
+ ); +}; diff --git a/src/Components/ActiveQuantity/ActiveQuantity16.tsx b/src/Components/ActiveQuantity/ActiveQuantity16.tsx new file mode 100644 index 00000000000..9f4df3521ed --- /dev/null +++ b/src/Components/ActiveQuantity/ActiveQuantity16.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { ProductCard } from '../ProductCard/ProductCard'; +import { Phone } from '../../types/Phone'; +import { Products } from '../../types/Products'; +import { Link } from 'react-router-dom'; + +type ActiveQuantity16Props = { + activeQuantity: number; + activePage: number; + phones: Phone[]; + mapPhoneToProduct: (p: Phone) => Products; +}; + +export const ActiveQuantity16: React.FC = ({ + activePage, + phones, + mapPhoneToProduct, +}) => { + return ( +
+ {activePage === 0 && + phones.slice(0, 16).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 1 && + phones.slice(16, 32).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 2 && + phones.slice(32, 48).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 3 && + phones.slice(48, 64).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} +
+ ); +}; diff --git a/src/Components/ActiveQuantity/ActiveQuantity32.tsx b/src/Components/ActiveQuantity/ActiveQuantity32.tsx new file mode 100644 index 00000000000..e9110df169d --- /dev/null +++ b/src/Components/ActiveQuantity/ActiveQuantity32.tsx @@ -0,0 +1,146 @@ +import React from 'react'; +import { ProductCard } from '../ProductCard/ProductCard'; +import { Phone } from '../../types/Phone'; +import { Products } from '../../types/Products'; + +type ActiveQuantity32Props = { + activeQuantity: number; + activePage: number; + phones: Phone[]; + mapPhoneToProduct: (p: Phone) => Products; +}; + +export const ActiveQuantity32: React.FC = ({ + phones, + mapPhoneToProduct, + activePage, +}: ActiveQuantity32Props) => { + return ( +
+ {activePage === 0 && + phones.slice(0, 32).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 1 && + phones.slice(32, 64).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 2 && + phones.slice(64, 96).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 3 && + phones.slice(96, 128).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} +
+ ); +}; diff --git a/src/Components/ActiveQuantity/ActiveQuantity64.tsx b/src/Components/ActiveQuantity/ActiveQuantity64.tsx new file mode 100644 index 00000000000..4b3dedf9b53 --- /dev/null +++ b/src/Components/ActiveQuantity/ActiveQuantity64.tsx @@ -0,0 +1,146 @@ +import React from 'react'; +import { ProductCard } from '../ProductCard/ProductCard'; +import { Phone } from '../../types/Phone'; +import { Products } from '../../types/Products'; + +type ActiveQuantity64Props = { + activeQuantity: number; + activePage: number; + phones: Phone[]; + mapPhoneToProduct: (p: Phone) => Products; +}; + +export const ActiveQuantity64: React.FC = ({ + activePage, + phones, + mapPhoneToProduct, +}) => { + return ( +
+ {activePage === 0 && + phones.slice(0, 64).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 1 && + phones.slice(64, 128).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 2 && + phones.slice(128, 192).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 3 && + phones.slice(192, 256).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} +
+ ); +}; diff --git a/src/Components/ActiveQuantity/ActiveQuantityAcc/ActiveQuantity16.tsx b/src/Components/ActiveQuantity/ActiveQuantityAcc/ActiveQuantity16.tsx new file mode 100644 index 00000000000..aac655192bd --- /dev/null +++ b/src/Components/ActiveQuantity/ActiveQuantityAcc/ActiveQuantity16.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { ProductCard } from '../../ProductCard/ProductCard'; +import { Products } from '../../../types/Products'; +import { Accessorie } from '../../../types/Accessories'; +import { Link } from 'react-router-dom'; + +type ActiveQuantity16Props = { + activeQuantity: number; + activePage: number; + phones: Accessorie[]; + mapAccessorieToProduct: (p: Accessorie) => Products; +}; + +export const ActiveQuantity16: React.FC = ({ + activePage, + phones, + mapAccessorieToProduct, +}) => { + return ( +
+ {activePage === 0 && + phones.slice(0, 16).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 1 && + phones.slice(16, 32).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 2 && + phones.slice(32, 48).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 3 && + phones.slice(48, 64).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} +
+ ); +}; diff --git a/src/Components/ActiveQuantity/ActiveQuantityAcc/ActiveQuantity32.tsx b/src/Components/ActiveQuantity/ActiveQuantityAcc/ActiveQuantity32.tsx new file mode 100644 index 00000000000..c0c80d456f7 --- /dev/null +++ b/src/Components/ActiveQuantity/ActiveQuantityAcc/ActiveQuantity32.tsx @@ -0,0 +1,146 @@ +import React from 'react'; +import { ProductCard } from '../../ProductCard/ProductCard'; +import { Products } from '../../../types/Products'; +import { Accessorie } from '../../../types/Accessories'; + +type ActiveQuantity32Props = { + activeQuantity: number; + activePage: number; + phones: Accessorie[]; + mapAccessorieToProduct: (p: Accessorie) => Products; +}; + +export const ActiveQuantity32: React.FC = ({ + phones, + mapAccessorieToProduct, + activePage, +}: ActiveQuantity32Props) => { + return ( +
+ {activePage === 0 && + phones.slice(0, 32).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 1 && + phones.slice(32, 64).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 2 && + phones.slice(64, 96).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 3 && + phones.slice(96, 128).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} +
+ ); +}; diff --git a/src/Components/ActiveQuantity/ActiveQuantityAcc/ActiveQuantity64.tsx b/src/Components/ActiveQuantity/ActiveQuantityAcc/ActiveQuantity64.tsx new file mode 100644 index 00000000000..cacc0c175dc --- /dev/null +++ b/src/Components/ActiveQuantity/ActiveQuantityAcc/ActiveQuantity64.tsx @@ -0,0 +1,146 @@ +import React from 'react'; +import { ProductCard } from '../../ProductCard/ProductCard'; +import { Products } from '../../../types/Products'; +import { Accessorie } from '../../../types/Accessories'; + +type ActiveQuantity64Props = { + activeQuantity: number; + activePage: number; + phones: Accessorie[]; + mapAccessorieToProduct: (p: Accessorie) => Products; +}; + +export const ActiveQuantity64: React.FC = ({ + activePage, + phones, + mapAccessorieToProduct, +}) => { + return ( +
+ {activePage === 0 && + phones.slice(0, 64).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 1 && + phones.slice(64, 128).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 2 && + phones.slice(128, 192).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} + {activePage === 3 && + phones.slice(192, 256).map(phone => ( +
+
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+
+ ))} +
+ ); +}; diff --git a/src/Components/ActiveQuantity/ActiveQuantityTablets/ActiveQuantity16.tsx b/src/Components/ActiveQuantity/ActiveQuantityTablets/ActiveQuantity16.tsx new file mode 100644 index 00000000000..40437da858d --- /dev/null +++ b/src/Components/ActiveQuantity/ActiveQuantityTablets/ActiveQuantity16.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { Products } from '../../../types/Products'; +import { Tablet } from '../../../types/Tablets'; +import { ProductCard } from '../../ProductCard/ProductCard'; +import { Link } from 'react-router-dom'; + +type ActiveQuantity16Props = { + activeQuantity: number; + activePage: number; + phones: Tablet[]; + mapTabletToProduct: (p: Tablet) => Products; +}; + +export const ActiveQuantity16: React.FC = ({ + activePage, + phones, + mapTabletToProduct, +}) => { + return ( +
+ {activePage === 0 && + phones.slice(0, 16).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 1 && + phones.slice(16, 32).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 2 && + phones.slice(32, 48).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 3 && + phones.slice(48, 64).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} +
+ ); +}; diff --git a/src/Components/ActiveQuantity/ActiveQuantityTablets/ActiveQuantity32.tsx b/src/Components/ActiveQuantity/ActiveQuantityTablets/ActiveQuantity32.tsx new file mode 100644 index 00000000000..f826cde9f62 --- /dev/null +++ b/src/Components/ActiveQuantity/ActiveQuantityTablets/ActiveQuantity32.tsx @@ -0,0 +1,163 @@ +import React from 'react'; +import { Products } from '../../../types/Products'; +import { Tablet } from '../../../types/Tablets'; +import { ProductCard } from '../../ProductCard/ProductCard'; +import { Link } from 'react-router-dom'; + +type ActiveQuantity32Props = { + activeQuantity: number; + activePage: number; + phones: Tablet[]; + mapTabletToProduct: (p: Tablet) => Products; +}; + +export const ActiveQuantity32: React.FC = ({ + phones, + mapTabletToProduct, + activePage, +}: ActiveQuantity32Props) => { + return ( +
+ {activePage === 0 && + phones.slice(0, 32).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 1 && + phones.slice(32, 64).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 2 && + phones.slice(64, 96).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 3 && + phones.slice(96, 128).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} +
+ ); +}; diff --git a/src/Components/ActiveQuantity/ActiveQuantityTablets/ActiveQuantity64.tsx b/src/Components/ActiveQuantity/ActiveQuantityTablets/ActiveQuantity64.tsx new file mode 100644 index 00000000000..7a07ed5912f --- /dev/null +++ b/src/Components/ActiveQuantity/ActiveQuantityTablets/ActiveQuantity64.tsx @@ -0,0 +1,162 @@ +import React from 'react'; +import { Tablet } from '../../../types/Tablets'; +import { Products } from '../../../types/Products'; +import { ProductCard } from '../../ProductCard/ProductCard'; +import { Link } from 'react-router-dom'; +type ActiveQuantity64Props = { + activeQuantity: number; + activePage: number; + phones: Tablet[]; + mapTabletToProduct: (p: Tablet) => Products; +}; + +export const ActiveQuantity64: React.FC = ({ + activePage, + phones, + mapTabletToProduct, +}) => { + return ( +
+ {activePage === 0 && + phones.slice(0, 64).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 1 && + phones.slice(64, 128).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 2 && + phones.slice(128, 192).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} + {activePage === 3 && + phones.slice(192, 256).map(phone => ( + +
+
+ +
+

{phone.name}

+ {phone.priceRegular}$ +
+
+

+ Screen{' '} + {phone.screen} +

+

+ Capacity{' '} + {phone.capacity} +

+

+ RAM {phone.ram} +

+
+ +
+ + ))} +
+ ); +}; diff --git a/src/Components/Aside/Aside.scss b/src/Components/Aside/Aside.scss new file mode 100644 index 00000000000..6f08e3b8336 --- /dev/null +++ b/src/Components/Aside/Aside.scss @@ -0,0 +1,168 @@ +@import "../../utils/mixins"; + +.aside { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: #0F1121; + align-items: stretch; + z-index: 1000; + + &__header { + display: flex; + height: 48px; + width: 100%; + padding-left: 16px; + box-shadow: 0 1px #323542; + align-items: center; + justify-content: space-between; + } + + &__close { + height: 48px; + width: 48px; + border: none; + + box-shadow: -1px 0 #323542; + + background-color: transparent; + } + + &__nav { + display: flex; + flex-direction: column; + gap: 16px; + + padding: 32px 16px; + + &-list { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: 32px; + width: 100%; + } + + &-item { + display: flex; + justify-content: center; + width: 100%; + } + + &-button { + display: flex; + align-items: center; + justify-content: center; + height: 48px; + border: none; + + width: 100%; + + box-shadow: 1px 0 #323542; + + background-color: transparent; + + &_selected { + width: 100%; + + &::after { + content: ""; + display: block; + position: absolute; + left: 0; + top: 61px; + bottom: 0; + width: 100%; + height: 3px; + background: #F1F2F9; + } + } + } + + &-link { + display: flex; + position: relative; + justify-content: center; + color: #75767F; + text-decoration: none; + + width: fit-content; + + font-family: Mont, sans-serif; + font-weight: 800; + font-size: 12px; + line-height: 11px; + letter-spacing: 4%; + text-transform: uppercase; + + &_active { + color: #F1F2F9; + + &::after { + content: ""; + display: block; + position: absolute; + left: 0; + top: 17px; + bottom: 0; + width: 100%; + height: 3px; + background: #F1F2F9; + } + } + + } + } + + &__logo { + width: 64px; + } + + &__footer { + position: fixed; + width: 100%; + bottom: 0; + margin-top: auto; + display: flex; + justify-content: center; + align-items: center; + + &-link { + width: 100%; + } + + &-button { + position: relative; + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 64px; + border: none; + + box-shadow: 1px -1px #323542; + + background-color: transparent; + } + } + + &__count { + position: absolute; + top: 18px; + right: 42%; + + display: flex; + justify-content: center; + align-items: center; + + width: 16px; + height: 16px; + + background-color: #F1F2F9; + border-radius: 50%; + } +} diff --git a/src/Components/Aside/Aside.tsx b/src/Components/Aside/Aside.tsx new file mode 100644 index 00000000000..008af13b792 --- /dev/null +++ b/src/Components/Aside/Aside.tsx @@ -0,0 +1,89 @@ +import React from 'react'; +import './Aside.scss'; +import Close from '../../images/icons/Close.svg'; +import logo from '../../images/Logo.png'; +import { NavLink } from 'react-router-dom'; +import favourites from '../../images/fav/Icons/Favourites (Heart Like).svg'; +import cart from '../../images/fav/Shopping bag (Cart).svg'; +import classNames from 'classnames'; + +type Props = { + setMenuOpen: React.Dispatch>; + totalFavourites: number; + totalQuantity: number; +}; + +export const Aside: React.FC = ({ + setMenuOpen, + totalQuantity, + totalFavourites, +}) => { + // className for main navigation links (no "selected" decoration) + const getLink = ({ isActive }: { isActive: boolean }) => + classNames('aside__nav-link', { + 'aside__nav-link_active': isActive, + }); + + // className for footer links (apply extra "selected" style when active) + const getFooterLink = ({ isActive }: { isActive: boolean }) => + classNames('aside__nav-button', { + 'aside__nav-button_selected': isActive, + }); + + return ( +
+
+ + +
+ +
+ + + + + + +
+
+ ); +}; diff --git a/src/Components/Cart/Cart.scss b/src/Components/Cart/Cart.scss new file mode 100644 index 00000000000..b7722b7429b --- /dev/null +++ b/src/Components/Cart/Cart.scss @@ -0,0 +1,409 @@ +@import '../../utils/mixins'; + +.cart { + display: flex; + flex-direction: column; + justify-content: center; + place-items: center; + min-height: 100vh; + min-width: 100%; + + &__items { + width: 100%; + flex: 2 1 0; + min-width: 0; + + &-empty { + font-family: Mont, sans-serif; + font-weight: 800; + font-size: 32px; + line-height: 41px; + letter-spacing: -1%; + + @include on-tablet { + font-size: 48px; + line-height: 56px; + } + } + } + + &__header { + width: 100%; + } + + &__content { + @include content-padding-inline; + + width: 100%; + + margin-bottom: 56px; + + margin-inline: 16px; + + @include on-tablet { + margin-inline: 24px; + margin-bottom: 64px; + } + + @include on-desktop { + margin-bottom: 0; + margin-inline: 32px; + } + } + + &__path { + margin-top: 25px; + margin-bottom: 40px; + + display: flex; + gap: 8px; + text-align: center; + align-items: center; + + &-name { + color: #75767F; + font-family: Mont, sans-serif; + font-weight: 700; + font-size: 12px; + line-height: 100%; + } + } + + &__sub { + margin-top: 8px; + font-family: 'Mont Regular', sans-serif; + font-weight: 600; + font-size: 14px; + line-height: 21px; + color: #75767F; + + margin-bottom: 32px; + } + + &__footer { + position: relative; + width: 100%; + margin-top: auto; + padding-top: 40px; + } + + &__grid { + display: flex; + flex-direction: column; + gap: 16px; + + width: 100%; + + margin-bottom: 80px; + } + + &__item { + display: flex; + flex-direction: column; + gap: 16px; + padding: 16px; + + width: 100%; + background-color: #161827; + justify-content: space-between; + + border: #161827 solid 1px; + + // padding-left: 25px; + // padding-right: 25px; + + max-width: none; + + text-decoration: none; + color: #F1F2F9; + + transition: 0.3s; + + @include on-tablet { + flex-direction: row; + gap: 24px; + align-items: center; + } + + &:hover { + border: #323542 solid 1px; + transition: 0.3s; + } + } + + &__right { + flex: 0 0 auto; + + display: flex; + justify-content: space-between; + + align-items: center; + + @include on-tablet { + justify-content: end; + } + } + + &__left { + gap: 16px; + display: flex; + align-items: center; + flex: 1 1 auto; + min-width: 0; + + @include on-tablet { + gap: 24px; + } + } + + &__img { + flex: 0 0 auto; + } + + &__image { + height: 80px; + width: auto; + display: block; + } + + &__title { + margin: 0; + + font-family: 'Mont Regular', sans-serif; + font-weight: 600; + font-size: 14px; + line-height: 21px; + letter-spacing: 0%; + + flex: 1 1 auto; + } + + &__quantity { + display: flex; + flex-direction: column; + gap: 8px; + align-items: center; + justify-content: center; + min-width: 48px; + + @include on-tablet { + flex-direction: row; + gap: 14px; + min-width: 0; + } + + &-button { + cursor: pointer; + + display: flex; + height: 32px; + width: 32px; + background-color: #323542; + align-items: center; + justify-content: center; + + @include on-tablet { + width: auto; + min-width: 32px; + padding: 0 8px; + } + + border: #161827 solid 1px; + + &--disabled { + cursor: not-allowed; + background-color: transparent; + border: #3B3E4A solid 1px; + + opacity: 0.5; + } + + &:hover:not(.cart__quantity-button--disabled) { + background-color: #4A4D58; + transition: 0.3s; + } + } + } + + &__price { + margin-inline: 0; + + font-family: Mont, sans-serif; + font-weight: 800; + font-size: 22px; + line-height: 140%; + text-align: right; + + @include on-tablet { + margin-inline: 24px; + } + } + + &__delete { + padding: 0; + + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + + background-color: transparent; + border: none; + + width: 32px !important; + height: 32px; + + transition: 0.3s; + + &-icon { + display: flex; + width: 16px; + height: 16px; + color: #F1F2F9; + } + } + + &__container { + @include on-desktop { + display: flex; + align-items: top; + gap: 16px; + } + + } + + &__total { + flex: 1 1 0; + min-width: 0; + height: 206px; + padding-inline: 24px; + width: auto; + + border: #3B3E4A solid 1px; + + justify-content: center; + align-items: center; + + display: flex; + flex-direction: column; + font-family: Mont, sans-serif; + font-weight: 800; + font-size: 32px; + line-height: 41px; + + &-subtotal { + color: #75767F; + font-family: 'Mont Regular', sans-serif; + font-weight: 600; + font-size: 14px; + line-height: 21px; + text-align: center; + + margin-bottom: 25px; + } + + &-none { + display: none; + } + } + + &__line { + width: 100%; + height: 1px; + background-color: #3B3E4A; + + margin-bottom: 25px; + } + + &__checkout { + color: #F1F2F9; + + width: 100%; + height: 48px; + background-color: #905BFF; + + font-family: 'Mont Regular', sans-serif; + font-weight: 700; + font-size: 14px; + line-height: 21px; + text-align: center; + + border: none; + cursor: pointer; + transition: 0.3s; + + &:hover { + transition: 0.3s; + background-color: #A378FF; + } + } + + &__check { + display: flex; + justify-content: center; + align-items: center; + + width: 100%; + + &-container { + display: flex; + flex-direction: column; + gap: 10px; + align-items: center; + justify-content: center; + + padding-inline: 24px; + + width: 400px; + height: 230px; + + background-color: transparent; + border: #3B3E4A solid 1px; + } + + &-title { + font-family: Mont, sans-serif; + font-weight: 800; + font-size: 32px; + line-height: 41px; + + margin-bottom: 10px; + } + + &-close { + width: 120px; + height: 48px; + + background-color: #905BFF; + + font-family: 'Mont Regular', sans-serif; + color: #F1F2F9; + font-weight: 700; + font-size: 14px; + line-height: 21px; + text-align: center; + + border: none; + cursor: pointer; + transition: 0.3s; + + &:hover { + transition: 0.3s; + background-color: #A378FF; + } + } + } + + &__title-cart { + font-family: Mont, sans-serif; + font-weight: 800; + font-size: 32px; + line-height: 41px; + letter-spacing: -1%; + + @include on-tablet { + font-size: 48px; + line-height: 56px; + } + } +} diff --git a/src/Components/Cart/Cart.tsx b/src/Components/Cart/Cart.tsx new file mode 100644 index 00000000000..8c8f04af9f3 --- /dev/null +++ b/src/Components/Cart/Cart.tsx @@ -0,0 +1,165 @@ +/* eslint-disable max-len */ +import React, { useState } from 'react'; +import { Header } from '../Header/header'; +import { useCart } from '../../Context/Context'; +import { useFav } from '../../Context/FavouritesContext'; +import { Footer } from '../Footer/Footer'; +import ArrowGray from '../../images/icons/ChevronGray.svg'; +import Home from '../../images/icons/Home.svg'; +import { Link } from 'react-router-dom'; +import './Cart.scss'; +import Delete from '../../images/icons/Close.svg'; +import Minus from '../../images/icons/Minus.svg'; +import Plus from '../../images/icons/Plus.svg'; +import { Aside } from '../Aside/Aside'; + +export const Cart = () => { + const [menuOpen, setMenuOpen] = useState(false); + const [checkOut, setCheckOut] = useState(false); + const { totalQuantity, items, removeFromCart, changeQuantity, clearCart } = + useCart(); + const { totalFavourites } = useFav(); + const totalPrice = items.reduce( + (total, item) => total + item.product.fullPrice * item.quantity, + 0, + ); + + return ( +
+
+
+ {menuOpen && ( +
+
+
+ + + + + Cart +
+

Cart

+
{totalQuantity} items
+
+
+ {totalQuantity === 0 && !checkOut && ( + Your cart is empty + )} +
+ {items.map(item => ( + +
+ +
+ +
+

{item.product.name}

+
+
+
+ + + {item.quantity} + + +
+ + ${item.product.fullPrice * item.quantity} + +
+ + ))} +
+
+
+ ${totalPrice} + + Total for {totalQuantity} items + +
+ +
+ {checkOut && ( +
+
+ <> +

+ Thank you for your purchase! +

+
+ + +
+
+ )} +
+
+
+
+
+
+ ); +}; diff --git a/src/Components/DevSpec/AccSpec.tsx b/src/Components/DevSpec/AccSpec.tsx new file mode 100644 index 00000000000..284009d5658 --- /dev/null +++ b/src/Components/DevSpec/AccSpec.tsx @@ -0,0 +1,496 @@ +/* eslint-disable react/no-unescaped-entities */ +/* eslint-disable react-hooks/exhaustive-deps */ +/* eslint-disable no-console */ +/* eslint-disable max-len */ +/* eslint-disable react/jsx-no-comment-textnodes */ +import { Link, useParams, useLocation, useNavigate } from 'react-router-dom'; +import { Header } from '../Header/header'; +import { useEffect, useState } from 'react'; +import { getAccessorieById } from '../../api/api'; +import home from '../../images/icons/Home.svg'; +import arr from '../../images/icons/Chevron (Arrow Right) grey.png'; +import backArr from '../../images/icons/Chevron (Arrow Left).svg'; +import './PhoneSpec.scss'; +import fav from '../../images/fav/Icons/Favourites (Heart Like).svg'; +import activeFav from '../../images/icons/ActiveFav.svg'; +import { useCart } from '../../Context/Context'; +import { useFav } from '../../Context/FavouritesContext'; +import { PhoneLike } from '../PhoneLike/PhoneLike'; +import { Footer } from '../Footer/Footer'; +import { Products } from '../../types/Products'; +import React from 'react'; +import { Accessorie } from '../../types/Accessories'; +import { Aside } from '../Aside/Aside'; +import { Loader } from '../Loader/Loader'; + +// type Props = { +// favouritesCount: number; +// }; + +enum Image { + first = 'first', + second = 'second', + third = 'third', + fourth = 'fourth', + fifth = 'fifth', +} + +export const AccSpec: React.FC = () => { + const { productId } = useParams<{ productId: string }>(); + const { pathname } = useLocation(); + const [accessorie, setAccessorie] = useState(); + const { totalQuantity, addToCart, removeFromCart, isInCart } = useCart(); + const { addToFav, removeFromFav, isInFav, totalFavourites } = useFav(); + const [menuOpen, setMenuOpen] = useState(false); + const [loader, setLoader] = useState(false); + + useEffect(() => { + window.scrollTo(0, 0); + }, [pathname]); + + const mapAccessorieToProduct = (a: Accessorie): Products => ({ + id: String(a.id), + itemId: String(a.id), + name: a.name ?? 'Unknown', + category: 'accessories', + fullPrice: Number(a.priceRegular ?? 0), + price: Number(a.priceDiscount ?? a.priceRegular ?? 0), + screen: a.screen ?? '—', + capacity: a.capacity ?? '—', + color: Array.isArray(a.color) + ? (a.color[0] ?? '—') + : (a.color ?? a.colorsAvailable?.[0] ?? '—'), + ram: a.ram ?? '—', + year: new Date().getFullYear(), + image: Array.isArray(a.images) ? (a.images[0] ?? '') : (a.images ?? ''), + }); + const navigate = useNavigate(); + const [, setErrorMessage] = useState(false); + const [image, setImage] = useState(Image.first); + const [color, setColor] = useState(null); + const images = accessorie?.images || []; + const path = accessorie?.id; + + const normalizePathValue = (value: string) => + value.toLowerCase().replace(/\s+/g, '-'); + + const getDefaultColor = (acc?: Accessorie) => { + if (!acc) { + return null; + } + + const rawColor = acc.color as unknown; + + if (typeof rawColor === 'string' && rawColor.trim()) { + return rawColor; + } + + if (Array.isArray(rawColor) && rawColor.length) { + return rawColor[0]; + } + + return acc.colorsAvailable?.[0] ?? null; + }; + + // const capacitiesRaw = accessorie?.capacity ?? []; + const capacities: string[] = Array.isArray(accessorie?.capacity) + ? accessorie.capacity + : accessorie?.capacity + ? [accessorie.capacity] + : []; + + const [selectedCapacity, setSelectedCapacity] = useState(null); + + useEffect(() => { + if (capacities.length) { + setSelectedCapacity(capacities[0]); + } + }, [capacities]); + + useEffect(() => { + if (!accessorie) { + return; + } + + const defaultColor = getDefaultColor(accessorie); + + if (defaultColor) { + setColor(defaultColor); + } + }, [accessorie]); + const imageKeys: Image[] = [ + Image.first, + Image.second, + Image.third, + Image.fourth, + Image.fifth, + ]; + + const mainIndex = + imageKeys.indexOf(image) === -1 ? 0 : imageKeys.indexOf(image); + const imageByIndex = (i: number): Image => imageKeys[i]; + + useEffect(() => { + if (images.length <= 1) { + return; + } + + const enums = [ + Image.first, + Image.second, + Image.third, + Image.fourth, + Image.fifth, + ]; + const id = setInterval(() => { + setImage(prev => { + const idx = enums.indexOf(prev); + const safeIdx = idx === -1 ? 0 : idx; + const next = enums[(safeIdx + 1) % images.length]; + + return next; + }); + }, 5000); + + return () => clearInterval(id); + }, [images.length]); + + useEffect(() => { + if (accessorie?.capacity) { + const caps = Array.isArray(accessorie.capacity) + ? accessorie.capacity + : [accessorie.capacity]; + + setSelectedCapacity(caps[0]); + } + }, [accessorie?.capacity]); + + useEffect(() => { + if (!productId) { + return; + } + + setLoader(true); + getAccessorieById(productId) + .then(product => { + if (product) { + setAccessorie(product); + console.log(product); + } + }) + .catch(() => setErrorMessage(true)) + .finally(() => setLoader(false)); + }, [productId]); + console.log('productId from params:', productId); + + return ( +
+
+ {menuOpen && ( +