+ {loading ? (
+
+
+
+ ) : (
+ <>
+ {item ? (
+
+
+
+
+
+
+
navigate(-1)}
+ >
+
navigate(-1)}
+ />
+
+
+
navigate(-1)}
+ >
+ Back
+
+
+
+
+
{item.name}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
${item.priceDiscount}
+
+
+ ${item.priceRegular}
+
+
+
+
+
+
+
+
+
+
+
Screen
+
+
{item.screen}
+
+
+
+
Resolution
+
+
{item.resolution}
+
+
+
+
Processor
+
+
{item.processor}
+
+
+
+
+
+
+
+
+
+
+ {item.description.map(section => {
+ return (
+
+
{section.title}
+
+ {section.text.map((parag, i) => {
+ return
{parag}
;
+ })}
+
+ );
+ })}
+
+
+
+
+
+
+ ) : (
+
+
Sorry Product Not Found
+
+
+ )}
+ >
+ )}
+
+ );
+};
diff --git a/src/modules/ProductDetailsPage/index.ts b/src/modules/ProductDetailsPage/index.ts
new file mode 100644
index 0000000000..6615089e5e
--- /dev/null
+++ b/src/modules/ProductDetailsPage/index.ts
@@ -0,0 +1 @@
+export * from './ProductDetailsPage';
diff --git a/src/modules/Root/Root.tsx b/src/modules/Root/Root.tsx
new file mode 100644
index 0000000000..3c3385b2d9
--- /dev/null
+++ b/src/modules/Root/Root.tsx
@@ -0,0 +1,93 @@
+import {
+ HashRouter as Router,
+ Navigate,
+ Route,
+ Routes,
+} from 'react-router-dom';
+
+import { App } from '../App/App';
+import { HomePage } from '../HomePage/HomePage';
+import { PhonePage } from '../PhonePage';
+import { FavoritePage } from '../FavotitePage';
+import { CartPage } from '../CartPage';
+import { TabletPage } from '../TabletPage';
+import { AccessoriesPage } from '../AccessoriesPage';
+import { ProductDetailsPage } from '../ProductDetailsPage';
+import { MenuItems } from '../../types/MenuItems';
+import { PageNotFoundPage } from '../PageNotFoundPage';
+
+export const Root = () => {
+ return (
+
+
+ }
+ >
+ }
+ />
+
+
+ }
+ />
+
+
+ }
+ />
+ }
+ />
+
+
+
+ }
+ />
+ }
+ />
+
+
+
+ }
+ />
+ }
+ />
+
+
+ }
+ />
+
+ }
+ />
+
+ }
+ />
+
+
+
+ );
+};
diff --git a/src/modules/Root/index.ts b/src/modules/Root/index.ts
new file mode 100644
index 0000000000..c78e011529
--- /dev/null
+++ b/src/modules/Root/index.ts
@@ -0,0 +1 @@
+export * from './Root';
diff --git a/src/modules/TabletPage/TabletPage.module.scss b/src/modules/TabletPage/TabletPage.module.scss
new file mode 100644
index 0000000000..c3b9bf1178
--- /dev/null
+++ b/src/modules/TabletPage/TabletPage.module.scss
@@ -0,0 +1,6 @@
+@import '../../styles/main';
+
+.tabletPage_container {
+ width: 100%;
+ height: 100%;
+}
diff --git a/src/modules/TabletPage/TabletPage.tsx b/src/modules/TabletPage/TabletPage.tsx
new file mode 100644
index 0000000000..14990fa418
--- /dev/null
+++ b/src/modules/TabletPage/TabletPage.tsx
@@ -0,0 +1,40 @@
+import style from './TabletPage.module.scss';
+import { useContext, useEffect, useMemo } from 'react';
+import { DispatchContext, StateContext } from '../../components/GlobalProvider';
+import { Catalog } from '../../components/Catalog/Catalog';
+import { getProducts } from '../../utils/getProducts';
+import { MenuItems } from '../../types/MenuItems';
+import { useSearchParams } from 'react-router-dom';
+import { SearchParams } from '../../types/SearchParams';
+
+export const TabletPage = () => {
+ const { products, showSearch } = useContext(StateContext);
+ const [searchParams] = useSearchParams();
+ const dispatch = useContext(DispatchContext);
+
+ const tablets = useMemo(() => {
+ const allTablets = getProducts.getProductByCategory(
+ products,
+ MenuItems.tablets,
+ );
+
+ return getProducts.getFilteredByQuery(
+ allTablets,
+ searchParams.get(SearchParams.query),
+ );
+ }, [products, searchParams]);
+
+ useEffect(
+ () => dispatch({ type: 'setShowSearch', payload: true }),
+ [dispatch, showSearch],
+ );
+
+ return (
+
+
+
+ );
+};
diff --git a/src/modules/TabletPage/index.ts b/src/modules/TabletPage/index.ts
new file mode 100644
index 0000000000..74893971d9
--- /dev/null
+++ b/src/modules/TabletPage/index.ts
@@ -0,0 +1 @@
+export * from './TabletPage';
diff --git a/src/modules/constants/index.ts b/src/modules/constants/index.ts
new file mode 100644
index 0000000000..bf232d8ffe
--- /dev/null
+++ b/src/modules/constants/index.ts
@@ -0,0 +1 @@
+export * from './productColors';
diff --git a/src/modules/constants/productColors.ts b/src/modules/constants/productColors.ts
new file mode 100644
index 0000000000..46e854c17b
--- /dev/null
+++ b/src/modules/constants/productColors.ts
@@ -0,0 +1,45 @@
+type ProductColors = {
+ black: string;
+ blue: string;
+ coral: string;
+ gold: string;
+ graphite: string;
+ green: string;
+ midnight: string;
+ midnightgreen: string;
+ pink: string;
+ purple: string;
+ red: string;
+ rosegold: string;
+ sierrablue: string;
+ silver: string;
+ skyblue: string;
+ spaceblack: string;
+ spacegray: string;
+ starlight: string;
+ white: string;
+ yellow: string;
+};
+
+export const productColors: ProductColors = {
+ black: '#3C4042',
+ blue: '#CED5D9',
+ coral: '#FF6E5A',
+ gold: '#F4E8CE',
+ graphite: '#54524F',
+ green: '#576856',
+ midnight: '#232A31',
+ midnightgreen: '#394C38',
+ pink: '#FADDD7',
+ purple: '#594F63',
+ red: '#FC0324',
+ rosegold: '#F7E8DD',
+ sierrablue: '#A7C1D9',
+ silver: '#F1F2ED',
+ skyblue: '#276787',
+ spaceblack: '#403E3D',
+ spacegray: '#535150',
+ starlight: '#FAF6F2',
+ white: '#F6F2EF',
+ yellow: '#FFE681',
+};
diff --git a/src/notes.txt b/src/notes.txt
new file mode 100644
index 0000000000..c36cde3133
--- /dev/null
+++ b/src/notes.txt
@@ -0,0 +1,7 @@
+Progress:
+stopeped:
+- greated grid with media and mixins
+- finished scss typography
+
+Next:
+- start builwing header
diff --git a/src/styles/buttons.scss b/src/styles/buttons.scss
new file mode 100644
index 0000000000..608bd81567
--- /dev/null
+++ b/src/styles/buttons.scss
@@ -0,0 +1,38 @@
+.buttons {
+ &_container {
+ background-color: var(--c-buttons-background);
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ padding-inline: 5px;
+ cursor: pointer;
+ transition: all 0.3s;
+
+ &:hover {
+ box-shadow: 0 3px 13px 0 #17203166;
+ background-color: var(--c-buttons-background-hover);
+ }
+
+ &_selected {
+ background-color: var(--c-buttons-background-selected);
+ border: 1px solid var(--c-elements);
+
+ &:hover {
+ box-shadow: 0 3px 13px 0 #17203166;
+ background-color: var(--c-buttons-background-selected);
+ }
+ }
+ }
+}
+
+.buttons_text {
+ @extend %buttons;
+
+ cursor: pointer;
+ color: var(--c-buttons-text);
+
+ &_selected {
+ color: var(--c-buttons-text-selected);
+ }
+}
diff --git a/src/styles/icons.scss b/src/styles/icons.scss
new file mode 100644
index 0000000000..92d22632b6
--- /dev/null
+++ b/src/styles/icons.scss
@@ -0,0 +1,18 @@
+@import './mixines';
+
+%icon-container {
+ cursor: pointer;
+ box-sizing: border-box;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ overflow: hidden;
+ transition: border-color 0.3s;
+ border: 1px solid var(--c-icons);
+ width: 32px;
+ height: 32px;
+
+ &:hover {
+ border-color: var(--c-primary);
+ }
+}
diff --git a/src/styles/main.scss b/src/styles/main.scss
new file mode 100644
index 0000000000..0fa223222f
--- /dev/null
+++ b/src/styles/main.scss
@@ -0,0 +1,72 @@
+@import './mixines';
+@import './icons';
+@import './buttons';
+@import './typography';
+@import './variables';
+@import './theme';
+
+@font-face {
+ font-family: Mont;
+ src: url('../fonts/Mont-Regular.otf');
+ font-weight: 500;
+}
+
+@font-face {
+ font-family: Mont;
+ src: url('../fonts/Mont-SemiBold.otf');
+ font-weight: 600;
+}
+
+@font-face {
+ font-family: Mont;
+ src: url('../fonts/Mont-Bold.otf');
+ font-weight: bold;
+}
+
+body {
+ margin: 0;
+ padding: 0;
+ background-color: var(--c-background);
+ min-height: 100vh;
+ box-sizing: border-box;
+}
+
+html {
+ font-family: Mont, sans-serif;
+ scroll-behavior: smooth;
+ box-sizing: border-box;
+}
+
+p {
+ margin: 0;
+ color: var(--c-primary);
+}
+
+h1 {
+ margin: 0;
+}
+
+h2 {
+ margin: 0;
+}
+
+h3 {
+ margin: 0;
+}
+
+%link {
+ text-decoration: none;
+ color: var(--c-secondary);
+}
+
+.isActive_link {
+ @include is-active;
+}
+
+.hidden {
+ display: none;
+}
+
+.loader_container {
+ flex-grow: 1;
+}
diff --git a/src/styles/mixines.scss b/src/styles/mixines.scss
new file mode 100644
index 0000000000..9d4be51162
--- /dev/null
+++ b/src/styles/mixines.scss
@@ -0,0 +1,76 @@
+@import './variables';
+@import './theme';
+
+@mixin ondesktop {
+ @media (min-width: $desktop-min-width) {
+ @content;
+ }
+}
+
+@mixin ontablet {
+ @media (min-width: $tablet-min-width) {
+ @content;
+ }
+}
+
+@mixin page-grid {
+ box-sizing: border-box;
+
+ --columns: 4;
+
+ display: grid;
+ column-gap: 16px;
+
+ grid-template-columns: repeat(var(--columns), 1fr);
+
+ @include ontablet {
+ --columns: 12;
+ }
+
+ @include ondesktop {
+ --columns: 24;
+
+ grid-template-columns: repeat(var(--columns), 32px);
+ }
+}
+
+@mixin inline-padding {
+ padding-inline: $mobil-padding-inline;
+
+ @include ontablet {
+ padding-inline: $tablet-padding-inline;
+ }
+}
+
+@mixin headings {
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6 {
+ @content;
+ }
+}
+
+@mixin hover($property: transform, $toValue: scale(1.1)) {
+ transition: #{$property} 0.3s;
+
+ &:hover {
+ color: var(--c-primary);
+ #{$property}: $toValue;
+ }
+}
+
+@mixin is-active {
+ border-bottom: 3px solid var(--c-primary);
+ color: var(--c-primary);
+}
+
+@mixin icon-bg($url, $bgColor: var(--c-primary)) {
+ background-image: #{$url};
+ width: 16px;
+ height: 16px;
+ background-repeat: no-repeat;
+ background-size: cover;
+}
diff --git a/src/styles/theme.scss b/src/styles/theme.scss
new file mode 100644
index 0000000000..c836b6ebc6
--- /dev/null
+++ b/src/styles/theme.scss
@@ -0,0 +1,73 @@
+.theme {
+ --c-white: #FFF;
+ --c-background: var(--c-white);
+ --c-accent: #F86800;
+ --c-accent-secondary: #476DF4;
+ --c-primary: #313237;
+ --c-secondary: #89939A;
+ --c-icons: #B4BDC3;
+ --c-elements: #E2E6E9;
+ --c-green: #27ae60;
+ --c-buttons-background: var(--c-primary);
+ --c-buttons-background-selected: var(--c-white);
+ --c-buttons-background-hover: var(--c-primary);
+ --c-buttons-text: var(--c-white);
+ --c-buttons-text-selected: var(--c-green);
+ --c-buttons-nav: var(--c-background);
+ --fls-logo: url('../img/icons/Logo.png');
+ --fls-right: url('../img/icons/arrowRight.svg');
+ --fls-right-disabled: url('../img/icons/arrowRight_disabled.svg');
+ --fls-left: url('../img/icons/arrowLeft.svg');
+ --fls-left-disabled: url('../img/icons/arrowLeft_disabled.svg');
+ --fls-up: url('../img/icons/arrowUp.svg');
+ --fls-up-disabled: url('../img/icons/arrowUp_disabled.svg');
+ --fls-down-disabled: url('../img/icons/arrowDown_disabled.svg');
+ --fls-home: url('../img/icons/home.svg');
+ --fls-favorite: url('../img/icons/favorite.svg');
+ --fls-favorite-selected: url('../img/icons/favorite_selected.svg');
+ --fls-cart: url('../img/icons/cart.svg');
+ --fls-dark-mode: url('../img/icons/dark_mode.svg');
+ --fls-menu: url('../img/icons/menu.svg');
+ --fls-close: url('../img/icons/close.svg');
+ --fls-plus: url('../img/icons/plus.svg');
+ --fls-minus: url('../img/icons/minus.svg');
+ --fls-minus-disabled: url('../img/icons/minus_disabled.svg');
+ --fls-search: url('../img/icons/Search.svg');
+
+ &_dark {
+ --c-accent: #905BFF;
+ --c-accent-secondary: #476DF4;
+ --c-primary: #fff;
+ --c-secondary: #75767F;
+ --c-icons: #4A4D58;
+ --c-elements: #3B3E4A;
+ --c-background: #0F1121;
+ --c-white: #F1F2F9;
+ --c-green: #27ae60;
+ --c-buttons-background: var(--c-accent);
+ --c-buttons-background-selected: #323542;
+ --c-buttons-background-hover: #A378FF;
+ --c-buttons-text: var(--c-white);
+ --c-buttons-text-selected: #F1F2F9;
+ --c-buttons-nav: #323542;
+ --fls-logo: url('../img/icons/darkMode/Logo-dark.png');
+ --fls-right: url('../img/icons/darkMode/arrowRight.svg');
+ --fls-right-disabled: url('../img/icons/arrowRight_disabled.svg');
+ --fls-left: url('../img/icons/darkMode/arrowLeft.svg');
+ --fls-left-disabled: url('../img/icons/arrowLeft_disabled.svg');
+ --fls-up: url('../img/icons/darkMode/arrowUp.svg');
+ --fls-up-disabled: url('../img/icons/arrowUp_disabled.svg');
+ --fls-down-disabled: url('../img/icons/arrowDown_disabled.svg');
+ --fls-home: url('../img/icons/darkMode/home.svg');
+ --fls-favorite: url('../img/icons/darkMode/favorite.svg');
+ --fls-favorite-selected: url('../img/icons/favorite_selected.svg');
+ --fls-cart: url('../img/icons/darkMode/cart.svg');
+ --fls-dark-mode: url('../img/icons/darkMode/dark_mode.svg');
+ --fls-menu: url('../img/icons/darkMode/menu.svg');
+ --fls-close: url('../img/icons/darkMode/close.svg');
+ --fls-plus: url('../img/icons/darkMode/plus.svg');
+ --fls-minus: url('../img/icons/darkMode/minus.svg');
+ --fls-minus-disabled: url('../img/icons/minus_disabled.svg');
+ --fls-search: url('../img/icons/darkMode/Search.svg');
+ }
+}
diff --git a/src/styles/typography.scss b/src/styles/typography.scss
new file mode 100644
index 0000000000..22b5a9fde9
--- /dev/null
+++ b/src/styles/typography.scss
@@ -0,0 +1,100 @@
+@import './theme';
+@import './mixines';
+
+@include headings {
+ font-family: Mont, sans-serif;;
+ letter-spacing: 0;
+ color: var(--c-primary);
+}
+
+body {
+ font-family: Mont, sans-serif;;
+ margin: 0;
+}
+
+h1 {
+ font-size: 32px;
+ font-weight: 800;
+ line-height: 41px;
+ letter-spacing: -0.01em;
+ margin: 0;
+ text-align: left;
+
+ @include ontablet {
+ font-size: 48px;
+ line-height: 56px;
+ }
+}
+
+h2 {
+ font-size: 22px;
+ font-weight: 800;
+ line-height: 30.8px;
+ margin: 0;
+
+ @include ontablet {
+ font-size: 32px;
+ line-height: 41px;
+ letter-spacing: -0.01em;
+ }
+}
+
+h3 {
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 25.56px;
+ margin: 0;
+
+ @include ontablet {
+ font-size: 22px;
+ font-weight: 800;
+ line-height: 30.8px;
+ }
+}
+
+h4 {
+ font-size: 16px;
+ font-weight: 700;
+ line-height: 20.45px;
+ margin: 0;
+
+ @include ontablet {
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 25.56px;
+ }
+}
+
+p {
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 21px;
+ color: var(--c-primary);
+ margin: 0;
+}
+
+
+%uppercase {
+ font-size: 12px;
+ font-weight: 800;
+ line-height: 11px;
+ letter-spacing: 0.04em;
+ text-transform: uppercase;
+ margin: 0;
+}
+
+%buttons {
+ font-size: 12px;
+ font-weight: 800;
+ line-height: 11px;
+ letter-spacing: 0.04em;
+ margin: 0;
+ text-align: center;
+}
+
+%small-text {
+ font-size: 12px;
+ font-weight: 700;
+ line-height: 15.34px;
+ margin: 0;
+}
diff --git a/src/styles/variables.scss b/src/styles/variables.scss
new file mode 100644
index 0000000000..b6bb225209
--- /dev/null
+++ b/src/styles/variables.scss
@@ -0,0 +1,5 @@
+$desktop-min-width: 1200px;
+$tablet-min-width: 640px;
+$mobil-padding-inline: 16px;
+$tablet-padding-inline: 24px;
+$desktop-padding-inline: 32px;
diff --git a/src/types/MenuItems.ts b/src/types/MenuItems.ts
new file mode 100644
index 0000000000..11316969a1
--- /dev/null
+++ b/src/types/MenuItems.ts
@@ -0,0 +1,5 @@
+export enum MenuItems {
+ 'phones' = 'phones',
+ 'tablets' = 'tablets',
+ 'accessories' = 'accessories',
+}
diff --git a/src/types/Product.ts b/src/types/Product.ts
new file mode 100644
index 0000000000..04549884e1
--- /dev/null
+++ b/src/types/Product.ts
@@ -0,0 +1,16 @@
+export interface Product {
+ id: number;
+ category: string;
+ itemId: string;
+ name: string;
+ fullPrice: number;
+ price: number;
+ screen: string;
+ capacity: string;
+ color: string;
+ ram: string;
+ year: number;
+ image: string;
+ discountPrice?: number;
+ discount?: number;
+}
diff --git a/src/types/ProductItem.ts b/src/types/ProductItem.ts
new file mode 100644
index 0000000000..1872c27409
--- /dev/null
+++ b/src/types/ProductItem.ts
@@ -0,0 +1,21 @@
+export interface ProductItem {
+ id: string;
+ category: string;
+ namespaceId: string;
+ name: string;
+ capacityAvailable: string[];
+ capacity: string;
+ priceRegular: number;
+ priceDiscount: number;
+ colorsAvailable: string[];
+ color: string;
+ images: string[];
+ description: { title: string; text: string[] }[];
+ screen: string;
+ resolution: string;
+ processor: string;
+ ram: string;
+ camera: string;
+ zoom: string;
+ cell: string[];
+}
diff --git a/src/types/SearchParams.ts b/src/types/SearchParams.ts
new file mode 100644
index 0000000000..23e4f513d6
--- /dev/null
+++ b/src/types/SearchParams.ts
@@ -0,0 +1,8 @@
+export enum SearchParams {
+ 'order' = 'order',
+ 'perPage' = 'perPage',
+ 'page' = 'page',
+ 'productColor' = 'productColor',
+ 'capacity' = 'capacity',
+ 'query' = 'query',
+}
diff --git a/src/types/SortFilter.ts b/src/types/SortFilter.ts
new file mode 100644
index 0000000000..f445ad3560
--- /dev/null
+++ b/src/types/SortFilter.ts
@@ -0,0 +1,5 @@
+export enum SortingTypes {
+ 'Newest' = 'Newest',
+ 'Alphabetical' = 'Alphabetical',
+ 'Cheapest' = 'Cheapest',
+}
diff --git a/src/utils/LocalAccessKeys.ts b/src/utils/LocalAccessKeys.ts
new file mode 100644
index 0000000000..ca0c27314f
--- /dev/null
+++ b/src/utils/LocalAccessKeys.ts
@@ -0,0 +1,4 @@
+export enum LocalAccessKeys {
+ 'favorites' = 'favorites',
+ 'cart' = 'cart',
+}
diff --git a/src/utils/accessLocalStorage.ts b/src/utils/accessLocalStorage.ts
new file mode 100644
index 0000000000..cc14530075
--- /dev/null
+++ b/src/utils/accessLocalStorage.ts
@@ -0,0 +1,77 @@
+import { Product } from '../types/Product';
+import { LocalAccessKeys } from './LocalAccessKeys';
+
+function addProduct(data: Product[], target: Product) {
+ return [...data, target];
+}
+
+function removeProduct(data: Product[], target: Product) {
+ return [...data.filter(item => item.itemId !== target.itemId)];
+}
+
+export const accessLocalStorage = {
+ get(key: LocalAccessKeys) {
+ const data = localStorage.getItem(key);
+
+ try {
+ return data ? JSON.parse(data) : [];
+ } catch {
+ return [];
+ }
+ },
+
+ set(data: Product[], key: LocalAccessKeys) {
+ try {
+ localStorage.setItem(key, JSON.stringify(data));
+
+ return this.get(key);
+ } catch {
+ return [];
+ }
+ },
+
+ toggle(product: Product | undefined, key: LocalAccessKeys) {
+ const inMemory = this.get(key);
+ let newList = [];
+
+ if (product) {
+ if (!inMemory.find((prod: Product) => prod.itemId === product.itemId)) {
+ newList = addProduct(inMemory, product);
+ } else {
+ newList = removeProduct(inMemory, product);
+ }
+
+ this.set(newList, key);
+ }
+
+ return this.get(key);
+ },
+
+ append(product: Product | undefined, key: LocalAccessKeys) {
+ if (product) {
+ const newList = addProduct(this.get(key), product);
+
+ this.set(newList, key);
+
+ return newList;
+ }
+
+ return;
+ },
+
+ remove(product: Product | undefined, key: LocalAccessKeys) {
+ if (product) {
+ const newList = removeProduct(this.get(key), product);
+
+ this.set(newList, key);
+
+ return newList;
+ }
+
+ return;
+ },
+
+ clearKey(key: LocalAccessKeys) {
+ localStorage.removeItem(key);
+ },
+};
diff --git a/src/utils/catalogHelper.ts b/src/utils/catalogHelper.ts
new file mode 100644
index 0000000000..f586f9f8e1
--- /dev/null
+++ b/src/utils/catalogHelper.ts
@@ -0,0 +1,50 @@
+import { Product } from '../types/Product';
+import { SearchParams } from '../types/SearchParams';
+import { SortingTypes } from '../types/SortFilter';
+
+export const catalogHelper = {
+ sort(order: string | null = null, products: Product[] = []): Product[] {
+ const sortedProds = [...products];
+
+ switch (order) {
+ case SortingTypes.Alphabetical:
+ default:
+ sortedProds.sort((a, b) => a.name.localeCompare(b.name));
+ break;
+
+ case SortingTypes.Cheapest:
+ sortedProds.sort((a, b) => a.price - b.price);
+ break;
+
+ case SortingTypes.Newest:
+ sortedProds.sort((a, b) => b.year - a.year);
+ break;
+ }
+
+ return sortedProds ? sortedProds : [];
+ },
+
+ perPage(sizeStr: string | null = '', products: Product[] = []) {
+ if (!sizeStr || isNaN(parseInt(sizeStr))) {
+ return [products];
+ }
+
+ const size = parseInt(sizeStr);
+ const chunk = Array.from(
+ { length: Math.ceil(products.length / size) },
+ (_, i) => products.slice(i * size, i * size + size),
+ );
+
+ return chunk;
+ },
+
+ getCurrenPageParam(searchParams: URLSearchParams) {
+ const curPgParam = searchParams.get(SearchParams.page);
+
+ if (!curPgParam || isNaN(parseInt(curPgParam))) {
+ return 1;
+ }
+
+ return parseInt(curPgParam);
+ },
+};
diff --git a/src/utils/fetch.ts b/src/utils/fetch.ts
new file mode 100644
index 0000000000..009201f898
--- /dev/null
+++ b/src/utils/fetch.ts
@@ -0,0 +1,43 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+function wait(delay: number) {
+ return new Promise(resolve => {
+ setTimeout(resolve, delay);
+ });
+}
+
+export const BASE_API_URL =
+ 'https://syavayki.github.io/react_phone-catalog/api/';
+
+type RequestMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';
+
+function request
(
+ url: string,
+ method: RequestMethod = 'GET',
+ data: any = null,
+): Promise {
+ const options: RequestInit = { method };
+
+ if (data) {
+ options.body = JSON.stringify(data);
+ options.headers = {
+ 'Content-Type': 'application/json; charset=UTF-8',
+ };
+ }
+
+ return wait(1000)
+ .then(() => fetch(BASE_API_URL + url, options))
+ .then(response => {
+ if (!response.ok) {
+ throw new Error('Error in Fetch');
+ }
+
+ return response.json();
+ });
+}
+
+export const client = {
+ get: (url: string) => request(url),
+ post: (url: string, data: any) => request(url, 'POST', data),
+ patch: (url: string, data: any) => request(url, 'PATCH', data),
+ delete: (url: string) => request(url, 'DELETE'),
+};
diff --git a/src/utils/getProductItems.ts b/src/utils/getProductItems.ts
new file mode 100644
index 0000000000..899a468e8c
--- /dev/null
+++ b/src/utils/getProductItems.ts
@@ -0,0 +1,62 @@
+import { MenuItems } from '../types/MenuItems';
+import { ProductItem } from '../types/ProductItem';
+import { client } from './fetch';
+
+export const getProductItems = {
+ fetchByCategory(category: string): Promise {
+ return client.get(`${category}.json`);
+ },
+
+ fetchAllCategories() {
+ const allPromises: Promise[] = [];
+
+ Object.values(MenuItems).forEach(category => {
+ allPromises.push(getProductItems.fetchByCategory(category));
+ });
+
+ return Promise.allSettled(allPromises);
+ },
+
+ fetchDetails(category: string, id: string): Promise {
+ return this.fetchByCategory(category).then((result: ProductItem[]) =>
+ this.getById(result, id),
+ );
+ },
+
+ getById(
+ products: ProductItem[] | undefined,
+ id: string,
+ ): ProductItem | undefined {
+ if (products) {
+ return products.find(product => product.id === id);
+ }
+
+ return;
+ },
+
+ getColorVariant(
+ products: ProductItem[],
+ item: ProductItem,
+ selectedColor: string,
+ ): ProductItem | undefined {
+ const { namespaceId, capacity } = { ...item };
+
+ return products
+ .filter(itm => itm.namespaceId === namespaceId)
+ .filter(itm2 => itm2.capacity === capacity)
+ .find(itm3 => itm3.color === selectedColor);
+ },
+
+ getCapacityVariant(
+ products: ProductItem[],
+ item: ProductItem,
+ selectedCapacity: string,
+ ): ProductItem | undefined {
+ const { namespaceId, color } = { ...item };
+
+ return products
+ .filter(itm => itm.namespaceId === namespaceId)
+ .filter(itm3 => itm3.color === color)
+ .find(itm2 => itm2.capacity === selectedCapacity);
+ },
+};
diff --git a/src/utils/getProducts.ts b/src/utils/getProducts.ts
new file mode 100644
index 0000000000..6854d6d7d4
--- /dev/null
+++ b/src/utils/getProducts.ts
@@ -0,0 +1,61 @@
+import { MenuItems } from '../types/MenuItems';
+import { Product } from '../types/Product';
+import { client } from './fetch';
+
+export const getProducts = {
+ fetchProducts(): Promise {
+ return client.get('products.json');
+ },
+
+ getDiscount(item: Product) {
+ return item.fullPrice - item.price;
+ },
+
+ getHotDealsProducts(products: Product[]) {
+ const sorted = products.sort((a, b) => {
+ return this.getDiscount(b) - this.getDiscount(a);
+ });
+
+ return sorted;
+ },
+
+ getNewProducts(products: Product[]) {
+ const maxYear: number = products
+ .map(prod => prod.year)
+ .reduce((prev, cur) => (cur > prev ? cur : prev), 0);
+
+ return products.filter(product => product.year >= maxYear);
+ },
+
+ getProductById(products: Product[], itemId: string) {
+ if (products) {
+ return products.find(product => product.itemId === itemId);
+ }
+
+ return;
+ },
+
+ getProductByCategory(
+ products: Product[],
+ category: MenuItems,
+ ): Product[] | undefined {
+ if (products) {
+ return products.filter(product => product.category === category);
+ }
+
+ return;
+ },
+
+ getFilteredByQuery(
+ products: Product[] | undefined,
+ query: string | null,
+ ): Product[] | [] {
+ if (products && query) {
+ return products.filter(product =>
+ product.name.toLocaleLowerCase().includes(query.toLocaleLowerCase()),
+ );
+ }
+
+ return products;
+ },
+};
diff --git a/src/utils/sentenseFormating.ts b/src/utils/sentenseFormating.ts
new file mode 100644
index 0000000000..147d4f9537
--- /dev/null
+++ b/src/utils/sentenseFormating.ts
@@ -0,0 +1,3 @@
+export const sentenseFormating = (text: string) => {
+ return text.charAt(0).toUpperCase() + text.slice(1);
+};