diff --git a/.eslintrc.js b/.eslintrc.js
index 7c6c7923ca..21521299b8 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -2,6 +2,19 @@ module.exports = {
extends: [
'@mate-academy/eslint-config-react-typescript',
'plugin:cypress/recommended',
+ "plugin:@typescript-eslint/recommended",
+ "prettier",
],
- rules: {},
+ "plugins": ["prettier"],
+ rules: {
+ "prettier/prettier": "error" ,
+ "no-param-reassign": [
+ "error",
+ {
+ "props": true,
+ "ignorePropertyModificationsFor": ["state"]
+ }
+ ],
+ "no-console": "off",
+ }
};
diff --git a/package-lock.json b/package-lock.json
index 98392dc382..a36ec5d60c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -9,6 +9,12 @@
"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA=="
},
+ "@adobe/css-tools": {
+ "version": "4.3.3",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.3.3.tgz",
+ "integrity": "sha512-rE0Pygv0sEZ4vBWHlAgJLGDU7Pm8xoO6p3wsEceb7GYAjScrOHpEo8KK/eVkAcnSM+slAEtXjA2JpdjLp4fJQQ==",
+ "dev": true
+ },
"@alloc/quick-lru": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
@@ -1566,6 +1572,24 @@
}
}
},
+ "@emotion/is-prop-valid": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+ "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+ "requires": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA=="
+ },
+ "@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ=="
+ },
"@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -1671,19 +1695,6 @@
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug=="
},
- "color-convert": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
- "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
- "requires": {
- "color-name": "~1.1.4"
- }
- },
- "color-name": {
- "version": "1.1.4",
- "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
- },
"string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
@@ -1694,36 +1705,6 @@
"strip-ansi": "^7.0.1"
}
},
- "string-width-cjs": {
- "version": "npm:string-width@4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
- },
- "emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- }
- }
- },
"strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
@@ -1732,21 +1713,6 @@
"ansi-regex": "^6.0.1"
}
},
- "strip-ansi-cjs": {
- "version": "npm:strip-ansi@6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "requires": {
- "ansi-regex": "^5.0.1"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
- }
- }
- },
"wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
@@ -1756,54 +1722,6 @@
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
}
- },
- "wrap-ansi-cjs": {
- "version": "npm:wrap-ansi@7.0.0",
- "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
- "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
- "requires": {
- "ansi-styles": "^4.0.0",
- "string-width": "^4.1.0",
- "strip-ansi": "^6.0.0"
- },
- "dependencies": {
- "ansi-regex": {
- "version": "5.0.1",
- "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
- "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
- },
- "ansi-styles": {
- "version": "4.3.0",
- "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
- "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
- "requires": {
- "color-convert": "^2.0.1"
- }
- },
- "emoji-regex": {
- "version": "8.0.0",
- "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
- "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
- },
- "string-width": {
- "version": "4.2.3",
- "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
- "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
- "requires": {
- "emoji-regex": "^8.0.0",
- "is-fullwidth-code-point": "^3.0.0",
- "strip-ansi": "^6.0.1"
- }
- },
- "strip-ansi": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
- "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
- "requires": {
- "ansi-regex": "^5.0.1"
- }
- }
- }
}
}
},
@@ -2420,60 +2338,6 @@
"eslint-plugin-react-hooks": "^4.6.0"
}
},
- "@mate-academy/scripts": {
- "version": "1.7.9",
- "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.7.9.tgz",
- "integrity": "sha512-TDtSLf9BVwkaib4xpMB8r8VA18N6ABRpePGxpqk+aYOHcXq1DFwrzqCbOW9LyrOxWbqLVJBhP5exEgFXiaWhfw==",
- "dev": true,
- "requires": {
- "@octokit/rest": "^17.11.2",
- "@types/get-port": "^4.2.0",
- "commander": "^5.1.0",
- "cross-env": "^7.0.3",
- "dotenv": "^8.6.0",
- "fs-extra": "^9.1.0",
- "get-port": "^5.1.1",
- "open": "^7.4.2",
- "sinon": "^9.2.4",
- "tree-kill": "^1.2.2"
- },
- "dependencies": {
- "commander": {
- "version": "5.1.0",
- "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz",
- "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==",
- "dev": true
- },
- "dotenv": {
- "version": "8.6.0",
- "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz",
- "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==",
- "dev": true
- },
- "fs-extra": {
- "version": "9.1.0",
- "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz",
- "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==",
- "dev": true,
- "requires": {
- "at-least-node": "^1.0.0",
- "graceful-fs": "^4.2.0",
- "jsonfile": "^6.0.1",
- "universalify": "^2.0.0"
- }
- },
- "open": {
- "version": "7.4.2",
- "resolved": "https://registry.npmjs.org/open/-/open-7.4.2.tgz",
- "integrity": "sha512-MVHddDVweXZF3awtlAS+6pgKLlm/JgxZ90+/NBurBoQctVOOB/zDdVjcyPzQ+0laDGbsWgrRkflI65sQeOgT9Q==",
- "dev": true,
- "requires": {
- "is-docker": "^2.0.0",
- "is-wsl": "^2.1.1"
- }
- }
- }
- },
"@mate-academy/students-ts-config": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/@mate-academy/students-ts-config/-/students-ts-config-0.0.4.tgz",
@@ -2532,223 +2396,6 @@
"fastq": "^1.6.0"
}
},
- "@octokit/auth-token": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz",
- "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==",
- "dev": true,
- "requires": {
- "@octokit/types": "^6.0.3"
- },
- "dependencies": {
- "@octokit/types": {
- "version": "6.41.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
- "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
- "dev": true,
- "requires": {
- "@octokit/openapi-types": "^12.11.0"
- }
- }
- }
- },
- "@octokit/core": {
- "version": "2.5.4",
- "resolved": "https://registry.npmjs.org/@octokit/core/-/core-2.5.4.tgz",
- "integrity": "sha512-HCp8yKQfTITYK+Nd09MHzAlP1v3Ii/oCohv0/TW9rhSLvzb98BOVs2QmVYuloE6a3l6LsfyGIwb6Pc4ycgWlIQ==",
- "dev": true,
- "requires": {
- "@octokit/auth-token": "^2.4.0",
- "@octokit/graphql": "^4.3.1",
- "@octokit/request": "^5.4.0",
- "@octokit/types": "^5.0.0",
- "before-after-hook": "^2.1.0",
- "universal-user-agent": "^5.0.0"
- }
- },
- "@octokit/endpoint": {
- "version": "6.0.12",
- "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz",
- "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==",
- "dev": true,
- "requires": {
- "@octokit/types": "^6.0.3",
- "is-plain-object": "^5.0.0",
- "universal-user-agent": "^6.0.0"
- },
- "dependencies": {
- "@octokit/types": {
- "version": "6.41.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
- "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
- "dev": true,
- "requires": {
- "@octokit/openapi-types": "^12.11.0"
- }
- },
- "universal-user-agent": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
- "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
- "dev": true
- }
- }
- },
- "@octokit/graphql": {
- "version": "4.8.0",
- "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz",
- "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==",
- "dev": true,
- "requires": {
- "@octokit/request": "^5.6.0",
- "@octokit/types": "^6.0.3",
- "universal-user-agent": "^6.0.0"
- },
- "dependencies": {
- "@octokit/types": {
- "version": "6.41.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
- "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
- "dev": true,
- "requires": {
- "@octokit/openapi-types": "^12.11.0"
- }
- },
- "universal-user-agent": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
- "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
- "dev": true
- }
- }
- },
- "@octokit/openapi-types": {
- "version": "12.11.0",
- "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-12.11.0.tgz",
- "integrity": "sha512-VsXyi8peyRq9PqIz/tpqiL2w3w80OgVMwBHltTml3LmVvXiphgeqmY9mvBw9Wu7e0QWk/fqD37ux8yP5uVekyQ==",
- "dev": true
- },
- "@octokit/plugin-paginate-rest": {
- "version": "2.21.3",
- "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.21.3.tgz",
- "integrity": "sha512-aCZTEf0y2h3OLbrgKkrfFdjRL6eSOo8komneVQJnYecAxIej7Bafor2xhuDJOIFau4pk0i/P28/XgtbyPF0ZHw==",
- "dev": true,
- "requires": {
- "@octokit/types": "^6.40.0"
- },
- "dependencies": {
- "@octokit/types": {
- "version": "6.41.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
- "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
- "dev": true,
- "requires": {
- "@octokit/openapi-types": "^12.11.0"
- }
- }
- }
- },
- "@octokit/plugin-request-log": {
- "version": "1.0.4",
- "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz",
- "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==",
- "dev": true
- },
- "@octokit/plugin-rest-endpoint-methods": {
- "version": "3.17.0",
- "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-3.17.0.tgz",
- "integrity": "sha512-NFV3vq7GgoO2TrkyBRUOwflkfTYkFKS0tLAPym7RNpkwLCttqShaEGjthOsPEEL+7LFcYv3mU24+F2yVd3npmg==",
- "dev": true,
- "requires": {
- "@octokit/types": "^4.1.6",
- "deprecation": "^2.3.1"
- },
- "dependencies": {
- "@octokit/types": {
- "version": "4.1.10",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-4.1.10.tgz",
- "integrity": "sha512-/wbFy1cUIE5eICcg0wTKGXMlKSbaAxEr00qaBXzscLXpqhcwgXeS6P8O0pkysBhRfyjkKjJaYrvR1ExMO5eOXQ==",
- "dev": true,
- "requires": {
- "@types/node": ">= 8"
- }
- }
- }
- },
- "@octokit/request": {
- "version": "5.6.3",
- "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz",
- "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==",
- "dev": true,
- "requires": {
- "@octokit/endpoint": "^6.0.1",
- "@octokit/request-error": "^2.1.0",
- "@octokit/types": "^6.16.1",
- "is-plain-object": "^5.0.0",
- "node-fetch": "^2.6.7",
- "universal-user-agent": "^6.0.0"
- },
- "dependencies": {
- "@octokit/types": {
- "version": "6.41.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
- "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
- "dev": true,
- "requires": {
- "@octokit/openapi-types": "^12.11.0"
- }
- },
- "universal-user-agent": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.1.tgz",
- "integrity": "sha512-yCzhz6FN2wU1NiiQRogkTQszlQSlpWaw8SvVegAc+bDxbzHgh1vX8uIe8OYyMH6DwH+sdTJsgMl36+mSMdRJIQ==",
- "dev": true
- }
- }
- },
- "@octokit/request-error": {
- "version": "2.1.0",
- "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz",
- "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==",
- "dev": true,
- "requires": {
- "@octokit/types": "^6.0.3",
- "deprecation": "^2.0.0",
- "once": "^1.4.0"
- },
- "dependencies": {
- "@octokit/types": {
- "version": "6.41.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.41.0.tgz",
- "integrity": "sha512-eJ2jbzjdijiL3B4PrSQaSjuF2sPEQPVCPzBvTHJD9Nz+9dw2SGH4K4xeQJ77YfTq5bRQ+bD8wT11JbeDPmxmGg==",
- "dev": true,
- "requires": {
- "@octokit/openapi-types": "^12.11.0"
- }
- }
- }
- },
- "@octokit/rest": {
- "version": "17.11.2",
- "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-17.11.2.tgz",
- "integrity": "sha512-4jTmn8WossTUaLfNDfXk4fVJgbz5JgZE8eCs4BvIb52lvIH8rpVMD1fgRCrHbSd6LRPE5JFZSfAEtszrOq3ZFQ==",
- "dev": true,
- "requires": {
- "@octokit/core": "^2.4.3",
- "@octokit/plugin-paginate-rest": "^2.2.0",
- "@octokit/plugin-request-log": "^1.0.0",
- "@octokit/plugin-rest-endpoint-methods": "3.17.0"
- }
- },
- "@octokit/types": {
- "version": "5.5.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-5.5.0.tgz",
- "integrity": "sha512-UZ1pErDue6bZNjYOotCNveTXArOMZQFG6hKJfOnGnulVCMcVVi7YIIuuR4WfBhjo7zgpmzn/BkPDnUXtNx+PcQ==",
- "dev": true,
- "requires": {
- "@types/node": ">= 8"
- }
- },
"@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -2777,6 +2424,24 @@
"source-map": "^0.7.3"
}
},
+ "@reduxjs/toolkit": {
+ "version": "2.2.4",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.4.tgz",
+ "integrity": "sha512-EoIC9iC2V/DLRBVMXRHrO/oM3QBT7RuJNeBRx8Cpnz/NHINeZBEqgI8YOxAYUjLp+KYxGgc4Wd6KoAKsaUBGhg==",
+ "requires": {
+ "immer": "^10.0.3",
+ "redux": "^5.0.1",
+ "redux-thunk": "^3.1.0",
+ "reselect": "^5.1.0"
+ },
+ "dependencies": {
+ "immer": {
+ "version": "10.1.1",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz",
+ "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw=="
+ }
+ }
+ },
"@remix-run/router": {
"version": "1.15.3",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.15.3.tgz",
@@ -2856,23 +2521,6 @@
"@sinonjs/commons": "^1.7.0"
}
},
- "@sinonjs/samsam": {
- "version": "5.3.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-5.3.1.tgz",
- "integrity": "sha512-1Hc0b1TtyfBu8ixF/tpfSHTVWKwCBLY4QJbkgnE7HcwyvT2xArDxb4K7dMgqRm3szI+LJbzmW/s4xxEhv6hwDg==",
- "dev": true,
- "requires": {
- "@sinonjs/commons": "^1.6.0",
- "lodash.get": "^4.4.2",
- "type-detect": "^4.0.8"
- }
- },
- "@sinonjs/text-encoding": {
- "version": "0.7.2",
- "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz",
- "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==",
- "dev": true
- },
"@surma/rollup-plugin-off-main-thread": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/@surma/rollup-plugin-off-main-thread/-/rollup-plugin-off-main-thread-2.2.3.tgz",
@@ -3169,15 +2817,6 @@
}
}
},
- "@types/get-port": {
- "version": "4.2.0",
- "resolved": "https://registry.npmjs.org/@types/get-port/-/get-port-4.2.0.tgz",
- "integrity": "sha512-Iv2FAb5RnIk/eFO2CTu8k+0VMmIR15pKbcqRWi+s3ydW+aKXlN2yemP92SrO++ERyJx+p6Ie1ggbLBMbU1SjiQ==",
- "dev": true,
- "requires": {
- "get-port": "*"
- }
- },
"@types/graceful-fs": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz",
@@ -3196,6 +2835,15 @@
}
}
},
+ "@types/hoist-non-react-statics": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
+ "integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
+ "requires": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"@types/html-minifier-terser": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -3301,6 +2949,24 @@
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
"integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw=="
},
+ "@types/postcss-modules-local-by-default": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.2.tgz",
+ "integrity": "sha512-CtYCcD+L+trB3reJPny+bKWKMzPfxEyQpKIwit7kErnOexf5/faaGpkFy4I5AwbV4hp1sk7/aTg0tt0B67VkLQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "@types/postcss-modules-scope": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/postcss-modules-scope/-/postcss-modules-scope-3.0.4.tgz",
+ "integrity": "sha512-//ygSisVq9kVI0sqx3UPLzWIMCmtSVrzdljtuaAEJtGoGnpjBikZ2sXO5MpH9SnWX9HRfXxHifDAXcQjupWnIQ==",
+ "dev": true,
+ "requires": {
+ "postcss": "^8.0.0"
+ }
+ },
"@types/prettier": {
"version": "2.7.3",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz",
@@ -3345,6 +3011,27 @@
"@types/react": "*"
}
},
+ "@types/react-redux": {
+ "version": "7.1.33",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.33.tgz",
+ "integrity": "sha512-NF8m5AjWCkert+fosDsN3hAlHzpjSiXlVy9EgQEmLoBhaNXbmyeGs/aj5dQzKuF+/q+S7JQagorGDW8pJ28Hmg==",
+ "requires": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ },
+ "dependencies": {
+ "redux": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+ "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+ "requires": {
+ "@babel/runtime": "^7.9.2"
+ }
+ }
+ }
+ },
"@types/react-transition-group": {
"version": "4.4.10",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz",
@@ -3353,6 +3040,15 @@
"@types/react": "*"
}
},
+ "@types/redux-persist": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/@types/redux-persist/-/redux-persist-4.3.1.tgz",
+ "integrity": "sha512-YkMnMUk+4//wPtiSTMfsxST/F9Gh9sPWX0LVxHuOidGjojHtMdpep2cYvQgfiDMnj34orXyZI+QJCQMZDlafKA==",
+ "dev": true,
+ "requires": {
+ "redux-persist": "*"
+ }
+ },
"@types/resolve": {
"version": "1.17.1",
"resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.17.1.tgz",
@@ -3468,11 +3164,21 @@
"resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
"integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw=="
},
+ "@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw=="
+ },
"@types/trusted-types": {
"version": "2.0.7",
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
"integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw=="
},
+ "@types/use-sync-external-store": {
+ "version": "0.0.3",
+ "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+ "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+ },
"@types/ws": {
"version": "8.5.10",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
@@ -4481,12 +4187,6 @@
"tweetnacl": "^0.14.3"
}
},
- "before-after-hook": {
- "version": "2.2.3",
- "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.3.tgz",
- "integrity": "sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==",
- "dev": true
- },
"bfj": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/bfj/-/bfj-7.1.0.tgz",
@@ -4719,6 +4419,11 @@
}
}
},
+ "camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ=="
+ },
"caniuse-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz",
@@ -4877,6 +4582,11 @@
"wrap-ansi": "^7.0.0"
}
},
+ "clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA=="
+ },
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
@@ -5033,6 +4743,15 @@
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
},
+ "copy-anything": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-2.0.6.tgz",
+ "integrity": "sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==",
+ "dev": true,
+ "requires": {
+ "is-what": "^3.14.1"
+ }
+ },
"core-js": {
"version": "3.36.0",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.36.0.tgz",
@@ -5077,6 +4796,14 @@
"cross-spawn": "^7.0.1"
}
},
+ "cross-fetch": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz",
+ "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==",
+ "requires": {
+ "node-fetch": "^2.6.12"
+ }
+ },
"cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -5100,6 +4827,11 @@
"postcss-selector-parser": "^6.0.9"
}
},
+ "css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg=="
+ },
"css-declaration-sorter": {
"version": "6.4.1",
"resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz",
@@ -5210,6 +4942,16 @@
"resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz",
"integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w=="
},
+ "css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "requires": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"css-tree": {
"version": "1.0.0-alpha.37",
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz",
@@ -5670,12 +5412,6 @@
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
"integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="
},
- "deprecation": {
- "version": "2.3.1",
- "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz",
- "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==",
- "dev": true
- },
"dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -5725,12 +5461,6 @@
"resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
"integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw=="
},
- "diff": {
- "version": "4.0.2",
- "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
- "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
- "dev": true
- },
"diff-sequences": {
"version": "27.5.1",
"resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-27.5.1.tgz",
@@ -5965,6 +5695,16 @@
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A=="
},
+ "errno": {
+ "version": "0.1.8",
+ "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz",
+ "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "prr": "~1.0.1"
+ }
+ },
"error-ex": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
@@ -7539,12 +7279,6 @@
"resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz",
"integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q=="
},
- "get-port": {
- "version": "5.1.1",
- "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.1.1.tgz",
- "integrity": "sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==",
- "dev": true
- },
"get-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz",
@@ -7828,6 +7562,14 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
},
+ "hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "requires": {
+ "react-is": "^16.7.0"
+ }
+ },
"hoopy": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -7935,6 +7677,14 @@
}
}
},
+ "html-parse-stringify": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz",
+ "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==",
+ "requires": {
+ "void-elements": "3.1.0"
+ }
+ },
"html-tags": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz",
@@ -8070,6 +7820,30 @@
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
"integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw=="
},
+ "i18next": {
+ "version": "23.15.1",
+ "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.15.1.tgz",
+ "integrity": "sha512-wB4abZ3uK7EWodYisHl/asf8UYEhrI/vj/8aoSsrj/ZDxj4/UXPOa1KvFt1Fq5hkUHquNqwFlDprmjZ8iySgYA==",
+ "requires": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
+ "i18next-browser-languagedetector": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz",
+ "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==",
+ "requires": {
+ "@babel/runtime": "^7.23.2"
+ }
+ },
+ "i18next-http-backend": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.6.1.tgz",
+ "integrity": "sha512-rCilMAnlEQNeKOZY1+x8wLM5IpYOj10guGvEpeC59tNjj6MMreLIjIW8D1RclhD3ifLwn6d/Y9HEM1RUE6DSog==",
+ "requires": {
+ "cross-fetch": "4.0.0"
+ }
+ },
"iconv-lite": {
"version": "0.4.24",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
@@ -8107,6 +7881,13 @@
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
"integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw=="
},
+ "image-size": {
+ "version": "0.5.5",
+ "resolved": "https://registry.npmjs.org/image-size/-/image-size-0.5.5.tgz",
+ "integrity": "sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==",
+ "dev": true,
+ "optional": true
+ },
"immer": {
"version": "9.0.21",
"resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
@@ -8474,6 +8255,12 @@
"get-intrinsic": "^1.2.4"
}
},
+ "is-what": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/is-what/-/is-what-3.14.1.tgz",
+ "integrity": "sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==",
+ "dev": true
+ },
"is-wsl": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
@@ -10317,12 +10104,6 @@
"object.values": "^1.1.6"
}
},
- "just-extend": {
- "version": "4.2.1",
- "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz",
- "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==",
- "dev": true
- },
"keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -10380,6 +10161,64 @@
"integrity": "sha512-cc8oEVoctTvsFZ/Oje/kGnHbpWHYBe8IAJe4C0QNc3t8uM/0Y8+erSz/7Y1ALuXTEZTMvxXwO6YbX1ey3ujiZw==",
"dev": true
},
+ "less": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/less/-/less-4.2.0.tgz",
+ "integrity": "sha512-P3b3HJDBtSzsXUl0im2L7gTO5Ubg8mEN6G8qoTS77iXxXX4Hvu4Qj540PZDvQ8V6DmX6iXo98k7Md0Cm1PrLaA==",
+ "dev": true,
+ "requires": {
+ "copy-anything": "^2.0.1",
+ "errno": "^0.1.1",
+ "graceful-fs": "^4.1.2",
+ "image-size": "~0.5.0",
+ "make-dir": "^2.1.0",
+ "mime": "^1.4.1",
+ "needle": "^3.1.0",
+ "parse-node-version": "^1.0.1",
+ "source-map": "~0.6.0",
+ "tslib": "^2.3.0"
+ },
+ "dependencies": {
+ "make-dir": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+ "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+ "dev": true,
+ "optional": true,
+ "requires": {
+ "pify": "^4.0.1",
+ "semver": "^5.6.0"
+ }
+ },
+ "pify": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+ "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+ "dev": true,
+ "optional": true
+ },
+ "semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "optional": true
+ },
+ "source-map": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+ "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+ "dev": true,
+ "optional": true
+ },
+ "tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
+ "dev": true
+ }
+ }
+ },
"leven": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz",
@@ -10448,17 +10287,17 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
},
+ "lodash.camelcase": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz",
+ "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==",
+ "dev": true
+ },
"lodash.debounce": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz",
"integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow=="
},
- "lodash.get": {
- "version": "4.4.2",
- "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
- "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
- "dev": true
- },
"lodash.isempty": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz",
@@ -10667,12 +10506,6 @@
"yallist": "^3.0.2"
}
},
- "macos-release": {
- "version": "2.5.1",
- "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.1.tgz",
- "integrity": "sha512-DXqXhEM7gW59OjZO8NIjBCz9AQ1BEMrfiOAl4AYByHCtVHRF4KoGNO8mqQeM8lRCtQe/UnJ4imO/d2HdkKsd+A==",
- "dev": true
- },
"magic-string": {
"version": "0.25.9",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz",
@@ -11302,61 +11135,39 @@
"resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
"integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g=="
},
- "negotiator": {
- "version": "0.6.3",
- "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
- "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
- },
- "neo-async": {
- "version": "2.6.2",
- "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
- "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
- },
- "nice-try": {
- "version": "1.0.5",
- "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
- "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
- "dev": true
- },
- "nise": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/nise/-/nise-4.1.0.tgz",
- "integrity": "sha512-eQMEmGN/8arp0xsvGoQ+B1qvSkR73B1nWSCh7nOt5neMCtwcQVYQGdzQMhcNscktTsWB54xnlSQFzOAPJD8nXA==",
+ "needle": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/needle/-/needle-3.3.1.tgz",
+ "integrity": "sha512-6k0YULvhpw+RoLNiQCRKOl09Rv1dPLr8hHnVjHqdolKwDrdNyk+Hmrthi4lIGPPz3r39dLx0hsF5s40sZ3Us4Q==",
"dev": true,
+ "optional": true,
"requires": {
- "@sinonjs/commons": "^1.7.0",
- "@sinonjs/fake-timers": "^6.0.0",
- "@sinonjs/text-encoding": "^0.7.1",
- "just-extend": "^4.0.2",
- "path-to-regexp": "^1.7.0"
+ "iconv-lite": "^0.6.3",
+ "sax": "^1.2.4"
},
"dependencies": {
- "@sinonjs/fake-timers": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
- "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
- "dev": true,
- "requires": {
- "@sinonjs/commons": "^1.7.0"
- }
- },
- "isarray": {
- "version": "0.0.1",
- "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
- "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
- "dev": true
- },
- "path-to-regexp": {
- "version": "1.8.0",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz",
- "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==",
+ "iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"dev": true,
+ "optional": true,
"requires": {
- "isarray": "0.0.1"
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
}
}
}
},
+ "negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg=="
+ },
+ "neo-async": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz",
+ "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw=="
+ },
"no-case": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
@@ -11377,7 +11188,6 @@
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
- "dev": true,
"requires": {
"whatwg-url": "^5.0.0"
},
@@ -11385,20 +11195,17 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
- "dev": true
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw=="
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
- "dev": true
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ=="
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
- "dev": true,
"requires": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
@@ -11626,28 +11433,12 @@
"word-wrap": "~1.2.3"
}
},
- "os-name": {
- "version": "3.1.0",
- "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz",
- "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==",
- "dev": true,
- "requires": {
- "macos-release": "^2.2.0",
- "windows-release": "^3.1.0"
- }
- },
"ospath": {
"version": "1.2.2",
"resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz",
"integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==",
"dev": true
},
- "p-finally": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
- "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==",
- "dev": true
- },
"p-limit": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
@@ -11722,6 +11513,12 @@
"lines-and-columns": "^1.1.6"
}
},
+ "parse-node-version": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parse-node-version/-/parse-node-version-1.0.1.tgz",
+ "integrity": "sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==",
+ "dev": true
+ },
"parse5": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz",
@@ -12777,6 +12574,13 @@
"integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==",
"dev": true
},
+ "prr": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
+ "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==",
+ "dev": true,
+ "optional": true
+ },
"psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
@@ -12993,11 +12797,39 @@
"resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz",
"integrity": "sha512-/6UZ2qgEyH2aqzYZgQPxEnz33NJ2gNsnHA2o5+o4wW9bLM/JYQitNP9xPhsXwC08hMMovfGe/8retsdDsczPRg=="
},
+ "react-i18next": {
+ "version": "15.0.2",
+ "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz",
+ "integrity": "sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==",
+ "requires": {
+ "@babel/runtime": "^7.25.0",
+ "html-parse-stringify": "^3.0.1"
+ },
+ "dependencies": {
+ "@babel/runtime": {
+ "version": "7.25.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.6.tgz",
+ "integrity": "sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ==",
+ "requires": {
+ "regenerator-runtime": "^0.14.0"
+ }
+ }
+ }
+ },
"react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "react-redux": {
+ "version": "9.1.2",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz",
+ "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==",
+ "requires": {
+ "@types/use-sync-external-store": "^0.0.3",
+ "use-sync-external-store": "^1.0.0"
+ }
+ },
"react-refresh": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -13226,6 +13058,14 @@
}
}
},
+ "react-toastify": {
+ "version": "10.0.5",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-10.0.5.tgz",
+ "integrity": "sha512-mNKt2jBXJg4O7pSdbNUfDdTsK9FIdikfsIE/yUCxbAEXl4HMyJaivrVFcn3Elvt5xvCQYhUZm+hqTIu1UXM3Pw==",
+ "requires": {
+ "clsx": "^2.1.0"
+ }
+ },
"react-transition-group": {
"version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
@@ -13328,6 +13168,21 @@
}
}
},
+ "redux": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
+ "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w=="
+ },
+ "redux-persist": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
+ "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ=="
+ },
+ "redux-thunk": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
+ "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw=="
+ },
"reflect.getprototypeof": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.6.tgz",
@@ -13572,6 +13427,17 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
+ "reselect": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.0.tgz",
+ "integrity": "sha512-aw7jcGLDpSgNDyWBQLv2cedml85qd95/iszJjN988zX1t7AVRJi19d9kto5+W7oCfQ94gyo40dVbT6g2k4/kXg=="
+ },
+ "reserved-words": {
+ "version": "0.1.2",
+ "resolved": "https://registry.npmjs.org/reserved-words/-/reserved-words-0.1.2.tgz",
+ "integrity": "sha512-0S5SrIUJ9LfpbVl4Yzij6VipUdafHrOTzvmfazSw/jeZrZtQK303OPZW+obtkaw7jQlTQppy0UvZWm9872PbRw==",
+ "dev": true
+ },
"resolve": {
"version": "1.22.8",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
@@ -14044,6 +13910,11 @@
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
+ "shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ=="
+ },
"shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -14078,46 +13949,6 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ=="
},
- "sinon": {
- "version": "9.2.4",
- "resolved": "https://registry.npmjs.org/sinon/-/sinon-9.2.4.tgz",
- "integrity": "sha512-zljcULZQsJxVra28qIAL6ow1Z9tpattkCTEJR4RBP3TGc00FcttsP5pK284Nas5WjMZU5Yzy3kAIp3B3KRf5Yg==",
- "dev": true,
- "requires": {
- "@sinonjs/commons": "^1.8.1",
- "@sinonjs/fake-timers": "^6.0.1",
- "@sinonjs/samsam": "^5.3.1",
- "diff": "^4.0.2",
- "nise": "^4.0.4",
- "supports-color": "^7.1.0"
- },
- "dependencies": {
- "@sinonjs/fake-timers": {
- "version": "6.0.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-6.0.1.tgz",
- "integrity": "sha512-MZPUxrmFubI36XS1DI3qmI0YdN1gks62JtFZvxR67ljjSNCeK6U08Zx4msEWOXuofgqUt6zPHSi1H9fbjR/NRA==",
- "dev": true,
- "requires": {
- "@sinonjs/commons": "^1.7.0"
- }
- },
- "has-flag": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
- "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
- "dev": true
- },
- "supports-color": {
- "version": "7.2.0",
- "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
- "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
- "dev": true,
- "requires": {
- "has-flag": "^4.0.0"
- }
- }
- }
- },
"sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@@ -14379,6 +14210,23 @@
}
}
},
+ "string-width-cjs": {
+ "version": "npm:string-width@4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "dependencies": {
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ }
+ }
+ },
"string.prototype.matchall": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.10.tgz",
@@ -14514,6 +14362,14 @@
"ansi-regex": "^5.0.1"
}
},
+ "strip-ansi-cjs": {
+ "version": "npm:strip-ansi@6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
"strip-bom": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
@@ -14524,12 +14380,6 @@
"resolved": "https://registry.npmjs.org/strip-comments/-/strip-comments-2.0.1.tgz",
"integrity": "sha512-ZprKx+bBLXv067WTCALv8SSz5l2+XhpYCsVtSqlMnkAXMWDq+/ekVbl1ghqP9rUHTzv6sm/DwCOiYutU/yp1fw=="
},
- "strip-eof": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
- "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==",
- "dev": true
- },
"strip-final-newline": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz",
@@ -14569,6 +14419,44 @@
"integrity": "sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==",
"dev": true
},
+ "styled-components": {
+ "version": "6.1.11",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.11.tgz",
+ "integrity": "sha512-Ui0jXPzbp1phYij90h12ksljKGqF8ncGx+pjrNPsSPhbUUjWT2tD1FwGo2LF6USCnbrsIhNngDfodhxbegfEOA==",
+ "requires": {
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.38",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
+ },
+ "dependencies": {
+ "postcss": {
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "requires": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "source-map-js": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz",
+ "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg=="
+ },
+ "tslib": {
+ "version": "2.6.2",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
+ "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
+ }
+ }
+ },
"stylehacks": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz",
@@ -14788,6 +14676,32 @@
"postcss-value-parser": "^4.2.0"
}
},
+ "stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg=="
+ },
+ "stylus": {
+ "version": "0.62.0",
+ "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.62.0.tgz",
+ "integrity": "sha512-v3YCf31atbwJQIMtPNX8hcQ+okD4NQaTuKGUWfII8eaqn+3otrbttGL1zSMZAAtiPsBztQnujVBugg/cXFUpyg==",
+ "dev": true,
+ "requires": {
+ "@adobe/css-tools": "~4.3.1",
+ "debug": "^4.3.2",
+ "glob": "^7.1.6",
+ "sax": "~1.3.0",
+ "source-map": "^0.7.3"
+ },
+ "dependencies": {
+ "sax": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
+ "integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA==",
+ "dev": true
+ }
+ }
+ },
"sucrase": {
"version": "3.35.0",
"resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz",
@@ -15215,12 +15129,6 @@
"punycode": "^2.1.1"
}
},
- "tree-kill": {
- "version": "1.2.2",
- "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz",
- "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==",
- "dev": true
- },
"trim-newlines": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-4.1.1.tgz",
@@ -15384,6 +15292,59 @@
"integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==",
"dev": true
},
+ "typescript-plugin-css-modules": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/typescript-plugin-css-modules/-/typescript-plugin-css-modules-5.1.0.tgz",
+ "integrity": "sha512-6h+sLBa4l+XYSTn/31vZHd/1c3SvAbLpobY6FxDiUOHJQG1eD9Gh3eCs12+Eqc+TCOAdxcO+zAPvUq0jBfdciw==",
+ "dev": true,
+ "requires": {
+ "@types/postcss-modules-local-by-default": "^4.0.2",
+ "@types/postcss-modules-scope": "^3.0.4",
+ "dotenv": "^16.4.2",
+ "icss-utils": "^5.1.0",
+ "less": "^4.2.0",
+ "lodash.camelcase": "^4.3.0",
+ "postcss": "^8.4.35",
+ "postcss-load-config": "^3.1.4",
+ "postcss-modules-extract-imports": "^3.0.0",
+ "postcss-modules-local-by-default": "^4.0.4",
+ "postcss-modules-scope": "^3.1.1",
+ "reserved-words": "^0.1.2",
+ "sass": "^1.70.0",
+ "source-map-js": "^1.0.2",
+ "stylus": "^0.62.0",
+ "tsconfig-paths": "^4.2.0"
+ },
+ "dependencies": {
+ "dotenv": {
+ "version": "16.4.5",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz",
+ "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==",
+ "dev": true
+ },
+ "postcss-load-config": {
+ "version": "3.1.4",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz",
+ "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==",
+ "dev": true,
+ "requires": {
+ "lilconfig": "^2.0.5",
+ "yaml": "^1.10.2"
+ }
+ },
+ "tsconfig-paths": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-4.2.0.tgz",
+ "integrity": "sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==",
+ "dev": true,
+ "requires": {
+ "json5": "^2.2.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ }
+ }
+ },
"unbox-primitive": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
@@ -15437,15 +15398,6 @@
"crypto-random-string": "^2.0.0"
}
},
- "universal-user-agent": {
- "version": "5.0.0",
- "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-5.0.0.tgz",
- "integrity": "sha512-B5TPtzZleXyPrUMKCpEHFmVhMN6EhmJYjG5PQna9s7mXeSqGTLap4OpqLl5FCEFUI3UBmllkETwKf/db66Y54Q==",
- "dev": true,
- "requires": {
- "os-name": "^3.1.0"
- }
- },
"universalify": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
@@ -15498,6 +15450,11 @@
"requires-port": "^1.0.0"
}
},
+ "use-sync-external-store": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz",
+ "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw=="
+ },
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -15592,6 +15549,11 @@
}
}
},
+ "void-elements": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz",
+ "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w=="
+ },
"w3c-hr-time": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz",
@@ -15949,105 +15911,6 @@
"has-tostringtag": "^1.0.2"
}
},
- "windows-release": {
- "version": "3.3.3",
- "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.3.tgz",
- "integrity": "sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==",
- "dev": true,
- "requires": {
- "execa": "^1.0.0"
- },
- "dependencies": {
- "cross-spawn": {
- "version": "6.0.5",
- "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
- "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
- "dev": true,
- "requires": {
- "nice-try": "^1.0.4",
- "path-key": "^2.0.1",
- "semver": "^5.5.0",
- "shebang-command": "^1.2.0",
- "which": "^1.2.9"
- }
- },
- "execa": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
- "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
- "dev": true,
- "requires": {
- "cross-spawn": "^6.0.0",
- "get-stream": "^4.0.0",
- "is-stream": "^1.1.0",
- "npm-run-path": "^2.0.0",
- "p-finally": "^1.0.0",
- "signal-exit": "^3.0.0",
- "strip-eof": "^1.0.0"
- }
- },
- "get-stream": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
- "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
- "dev": true,
- "requires": {
- "pump": "^3.0.0"
- }
- },
- "is-stream": {
- "version": "1.1.0",
- "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
- "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==",
- "dev": true
- },
- "npm-run-path": {
- "version": "2.0.2",
- "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
- "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==",
- "dev": true,
- "requires": {
- "path-key": "^2.0.0"
- }
- },
- "path-key": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
- "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==",
- "dev": true
- },
- "semver": {
- "version": "5.7.2",
- "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
- "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
- "dev": true
- },
- "shebang-command": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
- "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==",
- "dev": true,
- "requires": {
- "shebang-regex": "^1.0.0"
- }
- },
- "shebang-regex": {
- "version": "1.0.0",
- "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
- "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==",
- "dev": true
- },
- "which": {
- "version": "1.3.1",
- "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
- "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
- "dev": true,
- "requires": {
- "isexe": "^2.0.0"
- }
- }
- }
- },
"word-wrap": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
@@ -16346,6 +16209,39 @@
}
}
},
+ "wrap-ansi-cjs": {
+ "version": "npm:wrap-ansi@7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ }
+ }
+ },
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
diff --git a/package.json b/package.json
index 222c88a139..78862dc918 100644
--- a/package.json
+++ b/package.json
@@ -8,24 +8,36 @@
"dependencies": {
"@cypress/react18": "^2.0.0",
"@fortawesome/fontawesome-free": "^6.2.0",
+ "@reduxjs/toolkit": "^2.2.4",
+ "@types/react-redux": "^7.1.33",
"@types/react-transition-group": "^4.4.5",
"bulma": "^0.9.4",
"classnames": "^2.5.1",
+ "i18next": "^23.15.1",
+ "i18next-browser-languagedetector": "^8.0.0",
+ "i18next-http-backend": "^2.6.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+ "react-i18next": "^15.0.2",
+ "react-redux": "^9.1.2",
"react-router-dom": "^6.22.3",
"react-scripts": "5.0.1",
- "react-transition-group": "^4.4.5"
+ "react-toastify": "^10.0.5",
+ "react-transition-group": "^4.4.5",
+ "redux-persist": "^6.0.0",
+ "styled-components": "^6.1.11"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@mate-academy/eslint-config-react-typescript": "latest",
- "@mate-academy/scripts": "^1.7.9",
+ "@mate-academy/scripts": "^1.8.1",
"@mate-academy/students-ts-config": "latest",
"@mate-academy/stylelint-config": "latest",
"@types/node": "^16.18.80",
"@types/react": "^18.2.55",
"@types/react-dom": "^18.2.19",
+ "@types/redux-persist": "^4.3.1",
+ "cross-env": "^7.0.3",
"cypress": "^12.17.4",
"eslint": "^7.32.0",
"eslint-plugin-cypress": "^2.11.2",
@@ -37,7 +49,8 @@
"prettier": "^3.2.5",
"sass": "^1.72.0",
"stylelint": "^15.11.0",
- "typescript": "5.1.6"
+ "typescript": "5.1.6",
+ "typescript-plugin-css-modules": "^5.1.0"
},
"scripts": {
"start": "mate-scripts start -l",
diff --git a/public/img/favicon.ico b/public/img/favicon.ico
new file mode 100644
index 0000000000..bc86fad626
Binary files /dev/null and b/public/img/favicon.ico differ
diff --git a/public/index.html b/public/index.html
index 4b622dad39..a41f9e0bea 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,6 +4,45 @@
Phone catalog
+
+
+
diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json
new file mode 100644
index 0000000000..a654f99a05
--- /dev/null
+++ b/public/locales/en/translation.json
@@ -0,0 +1,62 @@
+{
+ "WELCOME_MESSAGE": "Welcome to Nice Gadgets store!",
+ "NEW_MODELS": "Brand new models",
+ "SCREEN": "Screen",
+ "CAPACITY": "Capacity",
+ "RAM": "RAM",
+ "ADD_TO_CART": "Add to cart",
+ "SHOP_BY_CATEGORY": "Shop by category",
+ "MOBILE_PHONES": "Mobile phones",
+ "MODELS": "models",
+ "TABLETS": "Tablets",
+ "ACCESSORIES": "Accessories",
+ "HOT_PRICES": "Hot prices",
+ "CONTACTS": "Contacts",
+ "RIGHTS": "Rights",
+ "BACK_TO_TOP": "Back to top",
+ "THEME": "Theme",
+ "LANGUAGE": "Language",
+ "HOME": "Home",
+ "PHONES": "Phones",
+ "ADDED": "Added",
+ "PAGE_NOT_FOUND": "Page Not Found. Go to home page?",
+ "SORT_BY": "Sort by",
+ "ITEMS_ON_PAGE": "Items on page",
+ "SORT_AGE": "Newest",
+ "SORT_NAME": "Alphabetically",
+ "SORT_PRICE": "Cheapest",
+ "NOT_PHONES": "Sorry... There are no phones yet... Go to phones page?",
+ "NOT_TABLETS": "Sorry... There are no tablets yet... Go to tablets page?",
+ "NOT_ACCESSORIES": "Sorry... There are no accessories yet... Go to accessories page?",
+ "GO_BACK": "GO BACK",
+ "GO_HOME": "HOME PAGE",
+ "SEARCH_TEXT": "Enter search text!",
+ "FAVOURITES": "Favourites",
+ "NO_SEARCH": "No search results...",
+ "SEE_ALL": "Search",
+ "SEE_PHONES": "See phones",
+ "SEE_TABLETS": "See tablets",
+ "SEE_ACCESORIES": "See accesories",
+ "CLEAR": "Clear",
+ "NOT_FAVOURITES": "There are no favourites yet...",
+ "NOT_CART": "Your cart is empty...",
+ "BACK": "Back",
+ "CART": "Cart",
+ "TOTAL_FOR": "Total for",
+ "ITEMS": "items",
+ "CHECKOUT": "Checkout",
+ "CART_CHECKOUT_MSG": "Checkout is not implemented yet. Do you want to clear the Cart?",
+ "CONFIRM": "Confirm",
+ "CANCEL": "Cancel",
+ "AVAILABLE_COLORS": "Available Colors",
+ "SELECT_CAPACITY": "Select capacity",
+ "RESOLUTION": "Resolution",
+ "PROCESSOR": "Processor",
+ "ABOUT": "About",
+ "TECH_SPECS": "Tech specs",
+ "BUILT_IN_MEMORY": "Built in memory",
+ "CELL": "Cell",
+ "CAMERA": "Camera",
+ "ZOOM": "Zoom",
+ "PRODUCT_NOT": "Product not found. Go home page?"
+}
diff --git a/public/locales/uk/translation.json b/public/locales/uk/translation.json
new file mode 100644
index 0000000000..3a74b9db06
--- /dev/null
+++ b/public/locales/uk/translation.json
@@ -0,0 +1,62 @@
+{
+ "WELCOME_MESSAGE": "Ласкаво просимо до магазину Nice Gadgets!",
+ "NEW_MODELS": "Нові моделі",
+ "SCREEN": "Екран",
+ "CAPACITY": "Ємність",
+ "RAM": "ОЗП",
+ "ADD_TO_CART": "Додати до кошика",
+ "SHOP_BY_CATEGORY": "Купити за категоріями",
+ "MOBILE_PHONES": "Мобільні телефони",
+ "MODELS": "моделі",
+ "TABLETS": "Планшети",
+ "ACCESSORIES": "Аксесуари",
+ "HOT_PRICES": "Гарячі ціни",
+ "CONTACTS": "Контакти",
+ "RIGHTS": "Права",
+ "BACK_TO_TOP": "Повернутися нагору",
+ "THEME": "Тема",
+ "LANGUAGE": "Мова",
+ "HOME": "Головна",
+ "PHONES": "Телефони",
+ "ADDED": "Добавлено",
+ "PAGE_NOT_FOUND": "Сторінка не знайдена. Повернутись на домашню сторінку?",
+ "SORT_BY": "Сортувати за",
+ "ITEMS_ON_PAGE": "Елементів",
+ "SORT_AGE": "Новизною",
+ "SORT_NAME": "Назвою",
+ "SORT_PRICE": "Дешевизною",
+ "NOT_PHONES": "Вибачте... Наразі немає телефонів... Перейти на сторінку телефонів?",
+ "NOT_TABLETS": "Вибачте... Наразі немає планшетів... Перейти на сторінку планшетів?",
+ "NOT_ACCESSORIES": "Вибачте... Наразі немає аксесуарів... Перейти на сторінку аксесуарів?",
+ "GO_BACK": "ПОВЕРНУТИСЬ",
+ "GO_HOME": "ДОМАШНЯ СТОРІНКА",
+ "SEARCH_TEXT": "Введіть пошуковий текст!",
+ "FAVOURITES": "Збереження",
+ "NO_SEARCH": "Немає результатів пошуку...",
+ "SEE_ALL": "Пошук",
+ "SEE_PHONES": "Телефони",
+ "SEE_TABLETS": "Планшети",
+ "SEE_ACCESORIES": "Аксесуари",
+ "CLEAR": "Очистити",
+ "NOT_FAVOURITES": "Наразі ваші збереження пусті...",
+ "NOT_CART": "Наразі ваш кошик пустий...",
+ "BACK": "Повернутись",
+ "CART": "Кошик",
+ "TOTAL_FOR": "Разом за",
+ "ITEMS": "шт.",
+ "CHECKOUT": "Оплата",
+ "CART_CHECKOUT_MSG": "Оформлення замовлення ще не реалізовано. Ви хочете очистити кошик?",
+ "CONFIRM": "Підтвердити",
+ "CANCEL": "Скасувати",
+ "AVAILABLE_COLORS": "Доступні кольори",
+ "SELECT_CAPACITY": "Вибрати об'єм",
+ "RESOLUTION": "Роздільна здатність",
+ "PROCESSOR": "Процесор",
+ "ABOUT": "Про продукт",
+ "TECH_SPECS": "Технічні характеристики",
+ "BUILT_IN_MEMORY": "Вбудована пам'ять",
+ "CELL": "Мобільний зв'язок",
+ "CAMERA": "Камера",
+ "ZOOM": "Приближення",
+ "PRODUCT_NOT": "Товар не знайдено. Перейти на домашнюю сторінку?"
+}
diff --git a/src/App.scss b/src/App.scss
index 71bc413aad..989e9dcc50 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -1 +1,23 @@
-// not empty
+* {
+ box-sizing: border-box;
+ transition: all 0.3s;
+}
+
+body {
+ margin: 0;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+}
+
+input:-webkit-autofill,
+input:-webkit-autofill:hover,
+input:-webkit-autofill:focus,
+textarea:-webkit-autofill,
+textarea:-webkit-autofill:hover,
+textarea:-webkit-autofill:focus,
+select:-webkit-autofill,
+select:-webkit-autofill:hover,
+select:-webkit-autofill:focus {
+ -webkit-text-fill-color: black;
+ -webkit-box-shadow: 0 0 0 1000px white inset;
+}
diff --git a/src/App.tsx b/src/App.tsx
index 372e4b4206..dbf1f97dfc 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,7 +1,63 @@
+import { Outlet, useLocation } from 'react-router-dom';
import './App.scss';
+import { Header } from './components/Header';
+import { useEffect } from 'react';
+import { getFrom } from './api/api';
+import { useAppDispatch } from './hooks/hookStore';
+import { setAccessories } from './features/accessoriesSlice';
+import { setTables } from './features/tablesSlice';
+import { setPhones } from './features/phoneSlice';
+import { setProducts } from './features/productsSlice';
+import { Footer } from './components/Footer';
+import { ThemeProvider } from './components/Themes/ThemeProvider';
+import { AppStyled, MainStyled } from './AppStyled';
-export const App = () => (
-
-
Product Catalog
-
-);
+export const App = () => {
+ const dispatch = useAppDispatch();
+ const { pathname } = useLocation();
+
+ useEffect(() => {
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth',
+ });
+ }, [pathname]);
+
+ useEffect(() => {
+ const fetchItems = async () => {
+ try {
+ const [phones, tables, accesories, products] = await Promise.all([
+ getFrom.getPhones(),
+ getFrom.getTables(),
+ getFrom.getAccessories(),
+ getFrom.getProducts(),
+ ]);
+
+ dispatch(setPhones(phones));
+ dispatch(setTables(tables));
+ dispatch(setAccessories(accesories));
+ dispatch(setProducts(products));
+ } catch (error) {
+ console.error('Error, get products:', error);
+ }
+ };
+
+ fetchItems();
+ }, [dispatch]);
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/AppStyled.ts b/src/AppStyled.ts
new file mode 100644
index 0000000000..a554fc79bf
--- /dev/null
+++ b/src/AppStyled.ts
@@ -0,0 +1,22 @@
+/* eslint-disable */
+import styled from 'styled-components';
+import { media } from './utils/const';
+
+const AppStyled = styled.div`
+ background-color: ${({ theme }) => theme.bacgroundPage};
+ padding-top: 48px;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ min-width: 320px;
+
+ ${media.desktop} {
+ padding-top: 64px;
+ }
+`;
+
+const MainStyled = styled.main`
+ flex: 1;
+`;
+
+export { AppStyled, MainStyled };
diff --git a/src/Root.tsx b/src/Root.tsx
new file mode 100644
index 0000000000..e763d00434
--- /dev/null
+++ b/src/Root.tsx
@@ -0,0 +1,54 @@
+import { HashRouter, Route, Routes } from 'react-router-dom';
+import { App } from './App';
+import { Provider } from 'react-redux';
+import store, { persistor } from './app/store';
+import { HomePage } from './modules/HomePage/HomePage';
+import ProductsPage from './modules/ProductsPage/ProductsPage';
+import PageNotFound from './modules/PageNotFound/PageNotFound';
+import FavoritePage from './modules/FavoritePage/FavoritePage';
+import { PersistGate } from 'redux-persist/integration/react';
+import { startTransition } from 'react';
+import CartPage from './modules/CartPage/CartPage';
+import ItemPage from './modules/ItemPage/ItemPage';
+
+export const Root = () => {
+ const handleLoad = () => {
+ startTransition(() => {});
+ };
+
+ return (
+
+
+
+
+ }>
+ } />
+ }
+ />
+ }
+ />
+ }
+ />
+ } />
+ } />
+ } />
+ } />
+ } />
+ } />
+
+
+
+
+
+ );
+};
diff --git a/src/api/api.ts b/src/api/api.ts
new file mode 100644
index 0000000000..237f22295c
--- /dev/null
+++ b/src/api/api.ts
@@ -0,0 +1,18 @@
+import {
+ AccessoryType,
+ PhoneType,
+ ProductType,
+ TabletType,
+} from '../types/productsType';
+import { getItems } from './fetch';
+
+export const getFrom = {
+ getPhones: async (): Promise =>
+ getItems('api/phones.json'),
+ getTables: async (): Promise =>
+ getItems('api/tablets.json'),
+ getAccessories: async (): Promise =>
+ getItems('api/accessories.json'),
+ getProducts: async (): Promise =>
+ getItems('api/products.json'),
+};
diff --git a/src/api/fetch.ts b/src/api/fetch.ts
new file mode 100644
index 0000000000..d0fc2d891a
--- /dev/null
+++ b/src/api/fetch.ts
@@ -0,0 +1,19 @@
+const BASE_URL = 'https://anothar.github.io/react_phone-catalog/';
+
+function wait(delay: number) {
+ return new Promise(resolve => {
+ setTimeout(resolve, delay);
+ });
+}
+
+export const getItems = async (url: string): Promise => {
+ return wait(300)
+ .then(() => fetch(BASE_URL + url))
+ .then(response => {
+ if (!response.ok) {
+ throw new Error();
+ }
+
+ return response.json();
+ });
+};
diff --git a/src/app/store.ts b/src/app/store.ts
new file mode 100644
index 0000000000..ca4c10e034
--- /dev/null
+++ b/src/app/store.ts
@@ -0,0 +1,39 @@
+import { combineReducers, configureStore } from '@reduxjs/toolkit';
+import accessoriesSlice from '../features/accessoriesSlice';
+import phoneSlice from '../features/phoneSlice';
+import productsSlice from '../features/productsSlice';
+import tablesSlice from '../features/tablesSlice';
+import backetSlice from '../features/basketSlice';
+import favoritSlice from '../features/favoritSlice';
+import coreSlice from '../features/core';
+import storage from 'redux-persist/lib/storage';
+import { persistReducer, persistStore } from 'redux-persist';
+
+const rootReducer = combineReducers({
+ accessories: accessoriesSlice,
+ phones: phoneSlice,
+ products: productsSlice,
+ tables: tablesSlice,
+ backets: backetSlice,
+ favorit: favoritSlice,
+ core: coreSlice,
+});
+
+const persistConfig = {
+ key: 'root-config',
+ storage,
+ blacklist: [],
+ whitelist: ['backets', 'favorit'],
+};
+
+const persistedReducer = persistReducer(persistConfig, rootReducer);
+
+export const store = configureStore({
+ reducer: persistedReducer,
+});
+
+export const persistor = persistStore(store);
+
+export default store;
+export type RootState = ReturnType;
+export type AppDispatch = typeof store.dispatch;
diff --git a/src/components/Button/Button.tsx b/src/components/Button/Button.tsx
new file mode 100644
index 0000000000..75cf6b47b8
--- /dev/null
+++ b/src/components/Button/Button.tsx
@@ -0,0 +1,33 @@
+import { ButtonStyled } from './styled';
+
+type Props = {
+ variant:
+ | 'dark'
+ | 'white'
+ | 'disabled'
+ | 'activate'
+ | 'pagination'
+ | 'capacity'
+ | 'notCapacity';
+ css?: string;
+ children: React.ReactNode;
+ onFunc?: () => void;
+};
+
+export const Button: React.FC = ({
+ variant,
+ css = '',
+ children,
+ onFunc = () => {},
+}) => {
+ const handleChildClick = (event: React.MouseEvent) => {
+ event.stopPropagation();
+ onFunc();
+ };
+
+ return (
+
+ {children}
+
+ );
+};
diff --git a/src/components/Button/styled.ts b/src/components/Button/styled.ts
new file mode 100644
index 0000000000..d6b39f41fd
--- /dev/null
+++ b/src/components/Button/styled.ts
@@ -0,0 +1,129 @@
+import styled, { css } from 'styled-components';
+
+type ButtonProps = {
+ variant:
+ | 'dark'
+ | 'white'
+ | 'disabled'
+ | 'activate'
+ | 'pagination'
+ | 'capacity'
+ | 'notCapacity';
+ cssStyle: string;
+};
+
+const ButtonStyled = styled.div`
+ cursor: pointer;
+
+ ${({ variant }) => {
+ switch (variant) {
+ case 'dark': {
+ return css`
+ background-color: ${({ theme }) => theme.buttonFirstColor};
+ color: ${({ theme }) => theme.textThreeColor};
+ border-radius: ${({ theme }) => theme.buttonFirstRadius};
+
+ &:hover {
+ background-color: ${({ theme }) => theme.buttonHoverBacground};
+ box-shadow: 0 3px 13px 0 ${({ theme }) => theme.buttonHoverShadow};
+ }
+ `;
+ }
+
+ case 'white': {
+ return css`
+ background-color: ${({ theme }) => theme.buttonSecondBacground};
+ border: 1px solid ${({ theme }) => theme.buttonSecondBorder};
+ border-radius: ${({ theme }) => theme.borderThreeRadius};
+ svg {
+ fill: ${({ theme }) => theme.buttonSecondColor};
+ }
+ color: ${({ theme }) => theme.buttonSecondColor};
+
+ &:hover {
+ background-color: ${({ theme }) =>
+ theme.buttonSecondHoverBacground};
+ border: 1px solid ${({ theme }) => theme.buttonSecondHoverBorder};
+ }
+ `;
+ }
+
+ case 'disabled': {
+ return css`
+ background-color: inherit;
+ border: 1px solid ${({ theme }) => theme.cardSliderNotActive};
+ border-radius: ${({ theme }) => theme.borderThreeRadius};
+ svg {
+ fill: ${({ theme }) => theme.buttonSecondNotColor};
+ }
+ color: ${({ theme }) => theme.buttonSecondNotColor};
+ cursor: default;
+ `;
+ }
+
+ case 'activate': {
+ return css`
+ background-color: ${({ theme }) => theme.buttonSecondBacground};
+ border: 1px solid ${({ theme }) => theme.cardSliderNotActive};
+ color: ${({ theme }) => theme.buttonThreeColor};
+ border-radius: ${({ theme }) => theme.buttonFirstRadius};
+ cursor: default;
+ `;
+ }
+
+ case 'pagination': {
+ return css`
+ background-color: ${({ theme }) => theme.cardBacground};
+ border: 1px solid ${({ theme }) => theme.cardBorder};
+ color: ${({ theme }) => theme.buttonSecondColor};
+ border-radius: ${({ theme }) => theme.borderThreeRadius};
+ cursor: pointer;
+
+ &:hover {
+ background-color: ${({ theme }) => theme.buttonPaginationHover};
+ border: 1px solid ${({ theme }) => theme.butPaginHovBor};
+ }
+ `;
+ }
+
+ case 'capacity': {
+ return css`
+ background-color: ${({ theme }) => theme.capacityBack};
+ color: ${({ theme }) => theme.capacityColor};
+ border-radius: ${({ theme }) => theme.capacityRadius};
+
+ &:hover {
+ box-shadow: 0 3px 13px 0 ${({ theme }) => theme.buttonHoverShadow};
+ }
+ `;
+ }
+
+ case 'notCapacity': {
+ return css`
+ color: ${({ theme }) => theme.capacityBack};
+ border-radius: ${({ theme }) => theme.capacityRadius};
+ border: 1px solid ${({ theme }) => theme.capacityBorder};
+
+ &:hover {
+ border: 1px solid ${({ theme }) => theme.capacityBack};
+ }
+ `;
+ }
+ }
+ }}
+
+ padding-inline: 9.5px;
+ height: 40px;
+
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-family: 'Mont-Regular', sans-serif;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+
+ ${({ cssStyle }) => cssStyle}
+`;
+
+export { ButtonStyled };
diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx
new file mode 100644
index 0000000000..495b8b8c45
--- /dev/null
+++ b/src/components/Footer/Footer.tsx
@@ -0,0 +1,134 @@
+import {
+ ContainerStyled,
+ FooterStyled,
+ GoTopStyled,
+ InfoBlockStyled,
+ LogoImgStyled,
+ SelectorsStyled,
+} from './styled';
+import logo from '../../icons/Logo.png';
+import logo2 from '../../icons/Logo2.png';
+import logo3 from '../../icons/Logo3.png';
+import logo45 from '../../icons/Logo45.png';
+import { Button } from '../Button/Button';
+import { VECTOR_SVG } from '../../utils/SVG';
+import { SelectInput } from '../Inputs/SelectInput/SelectInput';
+import { useEffect, useState } from 'react';
+import { themeMap, useTheme } from '../Themes/ThemeProvider';
+import i18n from 'i18next';
+import { StrCode } from '../../utils/enums';
+import { useTranslation } from 'react-i18next';
+import { Link } from 'react-router-dom';
+
+const scrollToTop = () => {
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth',
+ });
+};
+
+export const Footer = () => {
+ const { theme, setTheme } = useTheme();
+ const valueLanguage = localStorage.getItem('valueLanguage') as
+ | 'English'
+ | 'Українська';
+ const [value2, setValue2] = useState<'English' | 'Українська'>(
+ valueLanguage || 'English',
+ );
+ const themeKeys = Object.keys(themeMap) as Array;
+ const { t } = useTranslation();
+
+ const setLanguage = (name: 'English' | 'Українська') => {
+ setValue2(name);
+ localStorage.setItem('valueLanguage', name);
+ };
+
+ const languageVariant = {
+ English: 'en',
+ Українська: 'uk',
+ };
+
+ useEffect(() => {
+ i18n.changeLanguage(languageVariant[value2]);
+ }, [value2]);
+
+ const variantLogo = () => {
+ const themeVariant =
+ themeKeys.find(key => themeMap[key] === theme) || 'White';
+
+ switch (themeVariant) {
+ case 'White': {
+ return logo;
+ }
+
+ case 'Dark': {
+ return logo2;
+ }
+
+ case 'Theme3': {
+ return logo3;
+ }
+
+ case 'Theme4': {
+ return logo45;
+ }
+
+ case 'Theme5': {
+ return logo45;
+ }
+ }
+ };
+
+ const handleGithub = () => {
+ window.open('https://github.com/Anothar', '_blank');
+ };
+
+ return (
+
+
+
+
+
+
+
+ Github
+
+ {t(StrCode.Contacts)}
+
+ {t(StrCode.Rights)}
+
+
+
+ {t(StrCode.BackToTop)}
+
+
+
+
+ themeMap[key] === theme) || 'White'}
+ setValue={setTheme}
+ width="140px"
+ />
+
+
+
+
+
+ );
+};
diff --git a/src/components/Footer/index.ts b/src/components/Footer/index.ts
new file mode 100644
index 0000000000..ddcc5a9cd1
--- /dev/null
+++ b/src/components/Footer/index.ts
@@ -0,0 +1 @@
+export * from './Footer';
diff --git a/src/components/Footer/styled.ts b/src/components/Footer/styled.ts
new file mode 100644
index 0000000000..5c37275379
--- /dev/null
+++ b/src/components/Footer/styled.ts
@@ -0,0 +1,116 @@
+import styled from 'styled-components';
+import { media } from '../../utils/const';
+
+const LogoImgStyled = styled.img`
+ width: 89px;
+ height: 32px;
+ user-select: none;
+`;
+
+const ContainerStyled = styled.footer`
+ width: 100%;
+ box-shadow: 0px -1px 0px 0px #e2e6e9;
+ display: flex;
+ justify-content: center;
+ background-color: ${({ theme }) => theme.bacgroundDefault};
+
+ ${media.desktop} {
+ gap: 80px;
+ }
+`;
+
+const FooterStyled = styled.div`
+ padding: 32px 16px 32px 16px;
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+ width: 100%;
+ max-width: 1200px;
+
+ ${media.tablet} {
+ flex-direction: row;
+ gap: 0;
+ justify-content: space-between;
+ padding: 32px;
+ flex-wrap: wrap;
+ }
+`;
+
+const InfoBlockStyled = styled.ul`
+ list-style: none;
+ padding: 0;
+ margin: 0;
+
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ > * {
+ text-transform: uppercase;
+ font-weight: 800;
+ font-family: 'Mont-Bold', sans-serif;
+ font-size: 12px;
+ line-height: 11px;
+ color: ${({ theme }) => theme.footerButton};
+ cursor: pointer;
+
+ &:hover {
+ color: ${({ theme }) => theme.buttonHoverBacground};
+ }
+ }
+
+ ${media.tablet} {
+ flex-direction: row;
+ gap: 13.5px;
+ align-items: center;
+ }
+
+ ${media.desktop} {
+ gap: 106.83px;
+ }
+`;
+
+const GoTopStyled = styled.div`
+ color: ${({ theme }) => theme.backToTop};
+ width: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 16px;
+ font-weight: 700;
+ font-size: 12px;
+ line-height: 15.34px;
+ cursor: pointer;
+ font-family: 'Mont-SemiBold', sans-serif;
+
+ ${media.tablet} {
+ width: auto;
+ }
+
+ &:hover {
+ color: ${({ theme }) => theme.textColor};
+ }
+`;
+
+const SelectorsStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 10px;
+ flex: 1 1 100%;
+
+ ${media.tablet} {
+ flex-direction: row;
+ justify-content: center;
+ margin-top: 20px;
+ }
+`;
+
+export {
+ ContainerStyled,
+ LogoImgStyled,
+ FooterStyled,
+ InfoBlockStyled,
+ GoTopStyled,
+ SelectorsStyled,
+};
diff --git a/src/components/Header/Header.tsx b/src/components/Header/Header.tsx
new file mode 100644
index 0000000000..40471bcaa6
--- /dev/null
+++ b/src/components/Header/Header.tsx
@@ -0,0 +1,188 @@
+import {
+ CountStyled,
+ HeaderStyled,
+ ListItemStyled,
+ LogoStyled,
+ MenuBoxStyled,
+ MenuImgStyled,
+ MenuStyled,
+ NavLinkStyled,
+ NavListStyled,
+ NavStyled,
+} from './styled';
+import logo from '../../icons/Logo.png';
+import logo2 from '../../icons/Logo2.png';
+import logo3 from '../../icons/Logo3.png';
+import logo45 from '../../icons/Logo45.png';
+import { useEffect, useState } from 'react';
+import { themeMap, useTheme } from '../Themes/ThemeProvider';
+import {
+ BURGERMENU_SVG,
+ CLOSING_SVG,
+ LIKE_SVG,
+ SHOPPING_SVG,
+} from '../../utils/SVG';
+import { useTranslation } from 'react-i18next';
+import { StrCode } from '../../utils/enums';
+import { useAppSelector } from '../../hooks/hookStore';
+import SearchForm from './SerchBar/SerchBar';
+import SearchResult from './SearchResult/SearchResult';
+import { Link } from 'react-router-dom';
+
+export const Header = () => {
+ const [isManuActive, setIsActiveMenu] = useState(false);
+ const { theme } = useTheme();
+ const themeKeys = Object.keys(themeMap) as Array;
+ const { t } = useTranslation();
+ const { backetsId } = useAppSelector(state => state.backets);
+ const { favoritId } = useAppSelector(state => state.favorit);
+
+ const backsetCount = backetsId.reduce((cu, item) => cu + item.count, 0);
+ const favoritCount = favoritId.length;
+
+ const variantLogo = () => {
+ const themeVariant =
+ themeKeys.find(key => themeMap[key] === theme) || 'White';
+
+ switch (themeVariant) {
+ case 'White': {
+ return logo;
+ }
+
+ case 'Dark': {
+ return logo2;
+ }
+
+ case 'Theme3': {
+ return logo3;
+ }
+
+ case 'Theme4': {
+ return logo45;
+ }
+
+ case 'Theme5': {
+ return logo45;
+ }
+ }
+ };
+
+ useEffect(() => {
+ if (isManuActive) {
+ document.body.style.overflow = 'hidden';
+ } else {
+ document.body.style.overflow = 'auto';
+ }
+ }, [isManuActive]);
+
+ return (
+
+
+
+
+
+
+
+ setIsActiveMenu(prevValue => !prevValue)}
+ >
+ {isManuActive ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+
+
+
+
+ {
+ setIsActiveMenu(false);
+ }}
+ >
+ {t(StrCode.Home)}
+
+
+
+
+ {
+ setIsActiveMenu(false);
+ }}
+ >
+ {t(StrCode.Phones)}
+
+
+
+
+ {
+ setIsActiveMenu(false);
+ }}
+ >
+ {t(StrCode.Tablets)}
+
+
+
+
+ {
+ setIsActiveMenu(false);
+ }}
+ >
+ {t(StrCode.Accessories)}
+
+
+
+
+
+
+ {
+ setIsActiveMenu(false);
+ }}
+ >
+
+
+
+
+ {!!favoritCount && {favoritCount}}
+
+
+
+
+ {
+ setIsActiveMenu(false);
+ }}
+ >
+
+
+
+
+ {!!backsetCount && {backsetCount}}
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/Header/SearchResult/SearchResult.tsx b/src/components/Header/SearchResult/SearchResult.tsx
new file mode 100644
index 0000000000..24a8092a1a
--- /dev/null
+++ b/src/components/Header/SearchResult/SearchResult.tsx
@@ -0,0 +1,170 @@
+import React from 'react';
+import { useAppDispatch, useAppSelector } from '../../../hooks/hookStore';
+import {
+ ButtonsStyled,
+ ImgStyled,
+ NoResultStyled,
+ PriceStyled,
+ SearchItemsStyled,
+ SearchListStyled,
+ SearchResultStyled,
+} from './styled';
+import { Button } from '../../Button/Button';
+import { setIsFocused, setSearchValue } from '../../../features/core';
+import { useLocation, useNavigate, useSearchParams } from 'react-router-dom';
+import { useTranslation } from 'react-i18next';
+import { StrCode } from '../../../utils/enums';
+import { ProductType } from '../../../types/productsType';
+
+const SearchResult: React.FC = () => {
+ const { products } = useAppSelector(state => state.products);
+ const { searchValue } = useAppSelector(state => state.core);
+ const dispatch = useAppDispatch();
+ const { pathname } = useLocation();
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+
+ const [searchParams, setSearchParams] = useSearchParams();
+
+ const handleInputChange = () => {
+ if (searchValue === '') {
+ return [];
+ }
+
+ return products
+ .filter(item => {
+ switch (pathname) {
+ case '/phones':
+ return item.category === 'phones';
+ case '/tablets':
+ return item.category === 'tablets';
+ case '/accessories':
+ return item.category === 'accessories';
+ default:
+ return true;
+ }
+ })
+ .filter(item => {
+ return item.name
+ .replace(/\s+/g, '')
+ .toLowerCase()
+ .includes(searchValue.replace(/\s+/g, '').toLowerCase());
+ });
+ };
+
+ const handleClearSearch = () => {
+ dispatch(setSearchValue(''));
+ dispatch(setIsFocused(false));
+ };
+
+ const handleSearch = (path = 'phones') => {
+ switch (pathname) {
+ case '/phones':
+ searchParams.set('search', searchValue);
+ searchParams.set('page', '1');
+ setSearchParams(searchParams);
+ dispatch(setSearchValue(''));
+ dispatch(setIsFocused(false));
+ break;
+ case '/tablets':
+ searchParams.set('search', searchValue);
+ searchParams.set('page', '1');
+ setSearchParams(searchParams);
+ dispatch(setSearchValue(''));
+ dispatch(setIsFocused(false));
+ break;
+ case '/accessories':
+ searchParams.set('search', searchValue);
+ searchParams.set('page', '1');
+ setSearchParams(searchParams);
+ dispatch(setSearchValue(''));
+ dispatch(setIsFocused(false));
+ break;
+ default:
+ dispatch(setSearchValue(''));
+ dispatch(setIsFocused(false));
+ navigate(`/${path}?search=${searchValue}`);
+ break;
+ }
+ };
+
+ const isProductPathname =
+ pathname === '/phones' ||
+ pathname === '/tablets' ||
+ pathname === '/accessories';
+
+ const handleViewItem = (product: ProductType) => {
+ navigate(`/${product.category}/${product.itemId}`);
+ dispatch(setSearchValue(''));
+ dispatch(setIsFocused(false));
+ };
+
+ return (
+
+ {handleInputChange().length ? (
+
+ {handleInputChange().map(item => (
+ handleViewItem(item)}
+ >
+
+
+ {item.name}
+
+ {`${item.price}$`}
+
+ ))}
+
+ ) : (
+
+ {t(StrCode.NotSearch)}
+
+
+ )}
+
+
+ {!!isProductPathname ? (
+
+ ) : (
+ <>
+
+
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default SearchResult;
diff --git a/src/components/Header/SearchResult/styled.ts b/src/components/Header/SearchResult/styled.ts
new file mode 100644
index 0000000000..6f5d729495
--- /dev/null
+++ b/src/components/Header/SearchResult/styled.ts
@@ -0,0 +1,131 @@
+import styled, { css } from 'styled-components';
+import { media } from '../../../utils/const';
+
+type SearchResultType = {
+ isActive: boolean;
+};
+
+const SearchResultStyled = styled.div`
+ position: absolute;
+ top: 53px;
+ width: calc(100vw - 32px);
+ margin-inline: 16px;
+ max-width: 500px;
+ right: 0;
+ gap: 10px;
+ min-width: 286px;
+
+ height: ${({ isActive }) => (isActive ? '400px' : '0px')};
+ border: 1px solid ${({ theme }) => theme.buttonSecondBorder};
+ background-color: ${({ theme }) => theme.bacgroundDefault};
+
+ opacity: ${({ isActive }) => (isActive ? '1' : '0')};
+ box-shadow: 0 3px 5px 0 ${({ theme }) => theme.buttonHoverShadow};
+
+ display: flex;
+ flex-direction: column;
+
+ ${media.desktop} {
+ top: 69px;
+ }
+
+ ${({ isActive }) =>
+ !isActive &&
+ css`
+ & * {
+ display: none !important;
+ }
+ `}
+`;
+
+const SearchListStyled = styled.ul`
+ z-index: 3;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+ overflow: auto;
+ padding: 30px;
+`;
+
+const NoResultStyled = styled.div`
+ font-size: 25px;
+ line-height: 41px;
+ color: #313237;
+ padding: 30px 30px 10px;
+ height: 266px;
+
+ display: flex;
+ flex-direction: column;
+
+ ${media.tablet} {
+ height: 319px;
+ }
+
+ & * {
+ max-height: 185px;
+ object-fit: contain;
+
+ ${media.tablet} {
+ max-height: 238px;
+ }
+ }
+`;
+
+const SearchItemsStyled = styled.li`
+ display: flex;
+ border: 1px solid ${({ theme }) => theme.buttonSecondBorder};
+ padding: 10px;
+ text-align: center;
+ align-items: center;
+ gap: 5px;
+ justify-content: space-between;
+ cursor: pointer;
+
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+ color: ${({ theme }) => theme.textColor};
+
+ &:hover {
+ transform: scale(1.1);
+ box-shadow: 0 3px 13px 0 ${({ theme }) => theme.buttonHoverShadow};
+ }
+`;
+
+const ImgStyled = styled.img`
+ width: 66px;
+ height: 66px;
+ object-fit: contain;
+
+ &:hover {
+ transform: scale(1.1);
+ }
+`;
+
+const PriceStyled = styled.div`
+ font-weight: 800;
+ font-size: 22px;
+ line-height: 30.8px;
+ font-family: 'Mont-Bold', sans-serif;
+`;
+
+const ButtonsStyled = styled.div`
+ display: flex;
+ gap: 10px;
+ align-items: start;
+ margin: 0 30px 30px;
+ flex-wrap: wrap;
+`;
+
+export {
+ SearchResultStyled,
+ SearchListStyled,
+ NoResultStyled,
+ SearchItemsStyled,
+ ImgStyled,
+ PriceStyled,
+ ButtonsStyled,
+};
diff --git a/src/components/Header/SerchBar/SerchBar.tsx b/src/components/Header/SerchBar/SerchBar.tsx
new file mode 100644
index 0000000000..8407321af1
--- /dev/null
+++ b/src/components/Header/SerchBar/SerchBar.tsx
@@ -0,0 +1,50 @@
+import React from 'react';
+import { FormStyled, InputStyled, IconStyled } from './styled';
+import { SEARCH_SVG } from '../../../utils/SVG';
+import { useTranslation } from 'react-i18next';
+import { StrCode } from '../../../utils/enums';
+import { useAppDispatch, useAppSelector } from '../../../hooks/hookStore';
+import { setIsFocused, setSearchValue } from '../../../features/core';
+
+const SearchForm: React.FC = () => {
+ const inputRef = React.useRef(null);
+ const { t } = useTranslation();
+
+ const { searchValue } = useAppSelector(state => state.core);
+ const { isFocused } = useAppSelector(state => state.core);
+ const dispatch = useAppDispatch();
+
+ const handleChangeValue = (e: React.ChangeEvent) => {
+ dispatch(setSearchValue(e.target.value));
+ };
+
+ const handleFocus = () => {
+ dispatch(setIsFocused(true));
+ inputRef.current?.focus();
+ };
+
+ const handleBlur = () => {
+ if (!inputRef.current?.value) {
+ dispatch(setIsFocused(false));
+ }
+ };
+
+ return (
+
+
+
+
+
+
+ );
+};
+
+export default SearchForm;
diff --git a/src/components/Header/SerchBar/styled.ts b/src/components/Header/SerchBar/styled.ts
new file mode 100644
index 0000000000..68ab780b94
--- /dev/null
+++ b/src/components/Header/SerchBar/styled.ts
@@ -0,0 +1,78 @@
+import styled from 'styled-components';
+import { media } from '../../../utils/const';
+
+type Props = {
+ isFocused: boolean;
+};
+
+const FormStyled = styled.div`
+ position: absolute;
+ right: 50px;
+ width: ${({ isFocused }) => (isFocused ? '150px' : '48px')};
+ height: 48px;
+ background: ${({ theme }) => theme.bacgroundDefault};
+ z-index: 3;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ ${media.tablet} {
+ right: 98px;
+ }
+
+ ${media.desktop} {
+ right: 130px;
+ height: 64px;
+ width: ${({ isFocused }) => (isFocused ? '300px' : '64px')};
+ }
+
+ box-shadow: -1px 0 0 ${({ theme }) => theme.borderDefault};
+`;
+
+const InputStyled = styled.input`
+ width: ${({ isFocused }) => (isFocused ? '100px' : '0')};
+ height: 42.5px;
+ line-height: 30px;
+ outline: 0;
+ border: 0;
+ padding: ${({ isFocused }) => (isFocused ? '0 2px 0 5px' : '0')};
+ color: ${({ theme }) => theme.textColor};
+ background: ${({ theme }) => theme.bacgroundDefault};
+ font-size: 15px;
+ font-weight: 600;
+ font-family: 'Mont-Regular', sans-serif;
+
+ &::-webkit-search-decoration,
+ &::-webkit-search-results-button,
+ &::-webkit-search-results-decoration {
+ display: none;
+ }
+
+ appearance: none;
+
+ ${media.desktop} {
+ width: ${({ isFocused }) => (isFocused ? '236px' : '0')};
+ }
+`;
+
+const IconStyled = styled.div`
+ width: 39px;
+ height: 39px;
+ border-radius: 4px;
+ background: ${({ isFocused, theme }) =>
+ isFocused ? theme.textColor : theme.bacgroundDefault};
+ cursor: pointer;
+
+ display: flex;
+ justify-content: center;
+ align-items: center;
+
+ svg {
+ stroke: ${({ isFocused, theme }) =>
+ isFocused ? theme.bacgroundDefault : theme.textColor};
+ transition: all 0s;
+ }
+`;
+
+export { FormStyled, InputStyled, IconStyled };
diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts
new file mode 100644
index 0000000000..266dec8a1b
--- /dev/null
+++ b/src/components/Header/index.ts
@@ -0,0 +1 @@
+export * from './Header';
diff --git a/src/components/Header/styled.ts b/src/components/Header/styled.ts
new file mode 100644
index 0000000000..b1ab29a145
--- /dev/null
+++ b/src/components/Header/styled.ts
@@ -0,0 +1,286 @@
+import styled, { css, keyframes } from 'styled-components';
+import { media } from '../../utils/const';
+import { NavLink } from 'react-router-dom';
+
+const HeaderStyled = styled.header`
+ width: 100%;
+ box-shadow: 0 1px 0 ${({ theme }) => theme.borderDefault};
+
+ display: flex;
+ justify-content: space-between;
+ position: fixed;
+ top: 0;
+ z-index: 4;
+
+ background-color: ${({ theme }) => theme.bacgroundDefault};
+`;
+
+const LogoStyled = styled.div`
+ width: 64px;
+ height: 22px;
+ margin: 13px 16px;
+ flex-shrink: 0;
+ user-select: none;
+
+ ${media.desktop} {
+ width: 80px;
+ height: 28px;
+ margin: 18px 24px;
+ }
+
+ > * {
+ width: 100%;
+ height: 100%;
+ }
+`;
+
+type MenuProps = {
+ isActive?: boolean;
+ isMenu: boolean;
+};
+
+const MenuStyled = styled.div`
+ ${({ isMenu }) => {
+ if (isMenu) {
+ return css`
+ ${media.tablet} {
+ display: none;
+ }
+ `;
+ }
+
+ return '';
+ }}
+
+ ${({ isActive = true }) => {
+ if (isActive) {
+ return css`
+ width: 48px;
+ height: 48px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: -1px 0 0 ${({ theme }) => theme.borderDefault};
+
+ ${media.tablet} {
+ width: auto;
+ height: auto;
+ }
+ `;
+ } else {
+ return css`
+ width: 100%;
+ border-top: 1px solid ${({ theme }) => theme.borderDefault};
+ display: flex;
+ `;
+ }
+ }}
+`;
+
+type NavProps = {
+ isActive: boolean;
+};
+
+const NavStyled = styled.nav`
+ transform: translateX(100%);
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ position: absolute;
+ top: 48px;
+ left: 0;
+ padding-top: 32px;
+ width: 100vw;
+ background-color: ${({ theme }) => theme.bacgroundDefault};
+ height: calc(100dvh - 48.4px);
+
+ ${media.tablet} {
+ transform: translateX(0);
+ display: flex;
+ flex-direction: row;
+ position: static;
+ padding: 0;
+ height: 100%;
+ background-color: none;
+ width: calc(100vw - 96px);
+ opacity: 1;
+ }
+
+ ${({ isActive }) => {
+ if (isActive) {
+ return css`
+ transform: translateX(0);
+ z-index: 2;
+ `;
+ } else {
+ return css``;
+ }
+ }}
+`;
+
+const NavListStyled = styled.ul`
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 16px;
+ width: 100vw;
+ z-index: 1;
+ background-color: ${({ theme }) => theme.bacgroundDefault};
+
+ ${media.tablet} {
+ flex-direction: row;
+ gap: 32px;
+ width: auto;
+ margin-inline: 16px;
+ }
+`;
+
+const ListItemStyled = styled.li`
+ text-transform: uppercase;
+ height: 27px;
+ padding-block: 8px;
+ font-size: 12px;
+ font-weight: 800;
+ font-family: 'Mont-Bold', sans-serif;
+ line-height: 11px;
+ position: relative;
+
+ ${media.tablet} {
+ padding: 0;
+ height: 100%;
+ }
+`;
+
+const appearAnimation = keyframes`
+ 0% {
+ transform: scale(0);
+ }
+ 100% {
+ transform: scale(1);
+ }
+`;
+
+type NavLinkType = {
+ isIcon?: boolean;
+};
+
+const NavLinkStyled = styled(NavLink)`
+ text-decoration: none;
+ color: ${({ theme }) => theme.textSecondColor};
+ display: flex;
+ height: 100%;
+ justify-content: center;
+ align-items: center;
+
+ &:hover {
+ color: ${({ theme }) => theme.textColor};
+ }
+
+ &.active {
+ color: ${({ theme }) => theme.textColor};
+
+ &:after {
+ content: '';
+ position: absolute;
+ width: 100%;
+ height: 2px;
+ background-color: ${({ theme }) => theme.textColor};
+ left: 0;
+ bottom: 0;
+ border-radius: 1px;
+ animation: ${appearAnimation} 0.3s ease forwards;
+
+ ${({ isIcon }) => {
+ if (isIcon) {
+ return css`
+ bottom: 0;
+ border-radius: 0;
+ `;
+ }
+
+ return css`
+ bottom: 0;
+ `;
+ }}
+ }
+ }
+
+ ${({ isIcon }) => {
+ if (isIcon) {
+ return css`
+ width: 100%;
+ height: 100%;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ `;
+ }
+
+ return;
+ }}
+`;
+
+const MenuBoxStyled = styled.div`
+ width: 50%;
+ height: 64px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-shadow: -1px 0 0 ${({ theme }) => theme.borderDefault};
+ position: relative;
+
+ ${media.tablet} {
+ width: 48px;
+ height: 48px;
+ }
+
+ ${media.desktop} {
+ width: 64px;
+ height: 64px;
+ }
+`;
+
+const MenuImgStyled = styled.div`
+ width: 16px;
+ height: 16px;
+
+ svg {
+ fill: ${({ theme }) => theme.buttonSecondColor} !important;
+ }
+`;
+
+const CountStyled = styled.div`
+ position: absolute;
+ width: 14px;
+ height: 14px;
+ border: 1px solid ${({ theme }) => theme.bacgroundDefault};
+ background: ${({ theme }) => theme.iconColor};
+ border-radius: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-weight: 600;
+ font-size: 9px;
+ line-height: 11.5px;
+ color: ${({ theme }) => theme.textThreeColor};
+
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%) translate(7px, -7px);
+`;
+
+export {
+ HeaderStyled,
+ LogoStyled,
+ MenuStyled,
+ NavStyled,
+ NavListStyled,
+ ListItemStyled,
+ NavLinkStyled,
+ MenuBoxStyled,
+ MenuImgStyled,
+ CountStyled,
+};
diff --git a/src/components/Inputs/SelectInput/OptionSwiper/OptionSwiper.tsx b/src/components/Inputs/SelectInput/OptionSwiper/OptionSwiper.tsx
new file mode 100644
index 0000000000..a510561a11
--- /dev/null
+++ b/src/components/Inputs/SelectInput/OptionSwiper/OptionSwiper.tsx
@@ -0,0 +1,35 @@
+import { OptionStyled, OptionsSelectStyled } from './styled';
+
+type Props = {
+ items: string[];
+ isActive: boolean;
+ css: { [key: string]: string };
+ isClick: (item: string) => void;
+ activeItem: string;
+ variant: 'default' | 'topSwipe';
+};
+
+const OptionSwiper: React.FC = ({
+ items,
+ isActive,
+ css,
+ isClick,
+ activeItem,
+ variant,
+}) => {
+ return (
+
+ {items.map(item => (
+ isClick(item)}
+ isActive={activeItem === item}
+ key={item}
+ >
+ {item}
+
+ ))}
+
+ );
+};
+
+export default OptionSwiper;
diff --git a/src/components/Inputs/SelectInput/OptionSwiper/styled.ts b/src/components/Inputs/SelectInput/OptionSwiper/styled.ts
new file mode 100644
index 0000000000..da61869562
--- /dev/null
+++ b/src/components/Inputs/SelectInput/OptionSwiper/styled.ts
@@ -0,0 +1,53 @@
+import styled from 'styled-components';
+
+type OptionsSelectType = {
+ css: { [key: string]: string };
+ isActive: boolean;
+ variant: 'default' | 'topSwipe';
+};
+
+const OptionsSelectStyled = styled.div`
+ max-height: 144px;
+ overflow: auto;
+ position: absolute;
+ display: flex;
+ flex-direction: column;
+ background-color: ${({ theme }) => theme.optionBackground};
+ box-shadow: 0px 2px 15px 0px #0000000d;
+ border: 1px solid ${({ theme }) => theme.optionBorder};
+ padding-block: 8px;
+ transform: ${({ isActive }) => (isActive ? 'scaleY(100%)' : 'scaleY(0%)')};
+ opacity: ${({ isActive }) => (isActive ? '1' : '0')};
+ transform-origin: ${({ variant }) =>
+ variant === 'default' ? 'top' : 'bottom'};
+ z-index: 1000;
+ border-radius: ${({ theme }) => theme.borderRadius};
+
+ ${({ css }) => css}
+`;
+
+type OptionType = {
+ isActive: boolean;
+};
+
+const OptionStyled = styled.p`
+ flex-shrink: 0;
+ cursor: pointer;
+ margin: 0;
+ padding-left: 12px;
+ width: 100%;
+ height: 32px;
+ font-weight: 500;
+ font-size: 14px;
+ line-height: 21px;
+ display: flex;
+ align-items: center;
+ color: ${({ theme }) => theme.optionText};
+
+ &:hover {
+ background-color: ${({ theme }) => theme.optionHoverItem};
+ color: ${({ theme }) => theme.optionHoverText};
+ }
+`;
+
+export { OptionsSelectStyled, OptionStyled };
diff --git a/src/components/Inputs/SelectInput/SelectInput.tsx b/src/components/Inputs/SelectInput/SelectInput.tsx
new file mode 100644
index 0000000000..b7ab47ef66
--- /dev/null
+++ b/src/components/Inputs/SelectInput/SelectInput.tsx
@@ -0,0 +1,101 @@
+import { useRef, useState } from 'react';
+import {
+ InputBlockStyled,
+ ContainerStyled,
+ LabelStyled,
+ SelectStyled,
+ ImgRightStyled,
+} from './styled';
+import OptionSwiper from './OptionSwiper/OptionSwiper';
+import { VECTOR_SVG } from '../../../utils/SVG';
+import { useOutsideClick } from '../../../hooks/useOutsideClick';
+
+type Props = {
+ label?: string;
+ items: string[];
+ width?: string;
+ variant?: 'default' | 'topSwipe';
+ value: string;
+ setValue: (item: any) => void;
+};
+
+export const SelectInput: React.FC = ({
+ label = '',
+ items,
+ width = '100%',
+ variant = 'default',
+ value,
+ setValue,
+}) => {
+ const [isOpenOptions, setIsOpenOptions] = useState(false);
+ const selectRef = useRef(null);
+
+ const styledOptions = (): { [key: string]: string } => {
+ switch (variant) {
+ case 'default': {
+ return {
+ left: '0',
+ top: !!label ? '63px' : '44px',
+ width: width,
+ };
+ }
+
+ case 'topSwipe': {
+ return {
+ left: '0',
+ bottom: '44px',
+ width: width,
+ };
+ }
+ }
+ };
+
+ useOutsideClick(selectRef, () => {
+ if (isOpenOptions) {
+ setIsOpenOptions(false);
+ }
+ });
+
+ const handleClickChange = (item: string) => {
+ setValue(item);
+ setIsOpenOptions(false);
+ };
+
+ return (
+
+
+ {!!label && (
+ setIsOpenOptions(prewValue => !prewValue)}
+ >
+ {label}
+
+ )}
+
+ setIsOpenOptions(prewValue => !prewValue)}
+ >
+ {value}
+
+
+ setIsOpenOptions(prewValue => !prewValue)}
+ isOpenOptions={isOpenOptions}
+ >
+
+
+
+
+
+
+ );
+};
diff --git a/src/components/Inputs/SelectInput/styled.ts b/src/components/Inputs/SelectInput/styled.ts
new file mode 100644
index 0000000000..1e8572ca86
--- /dev/null
+++ b/src/components/Inputs/SelectInput/styled.ts
@@ -0,0 +1,98 @@
+import styled, { css } from 'styled-components';
+
+type InputType = {
+ width: string;
+};
+
+const InputBlockStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ position: relative;
+
+ width: ${({ width }) => width};
+`;
+
+const ContainerStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ position: relative;
+`;
+
+const LabelStyled = styled.label`
+ font-weight: 600;
+ font-size: 12px;
+ line-height: 15.34px;
+ width: 100%;
+ color: ${({ theme }) => theme.textSecondColor};
+ height: 19px;
+`;
+
+type SelectType = {
+ isOpened: boolean;
+};
+
+const SelectStyled = styled.div`
+ cursor: pointer;
+ width: 100%;
+ height: 40px;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+ border: ${({ isOpened, theme }) =>
+ isOpened
+ ? `1px solid ${theme.selectBorderFocus}`
+ : `1px solid ${theme.selectBorder}`};
+ padding: 10px 12px;
+ color: ${({ theme }) => theme.textColor};
+ position: relative;
+ background-color: ${({ theme }) => theme.selectBackground};
+ border-radius: ${({ theme }) => theme.borderRadius};
+
+ &:hover {
+ border: ${({ isOpened, theme }) => {
+ if (isOpened) {
+ return `1px solid ${theme.selectBorderFocus}`;
+ } else {
+ return `1px solid ${theme.selectBorderHover}`;
+ }
+ }};
+ }
+`;
+
+type ImgRightProps = {
+ isLabel: boolean;
+ isOpenOptions: boolean;
+};
+
+const ImgRightStyled = styled.div`
+ position: absolute;
+ width: 10px;
+ height: 6px;
+ ${({ isLabel }) => (isLabel ? `top: 35.33px;` : 'top: 17.33px;')}
+ right: 18px;
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ transform-origin: center;
+
+ SVG {
+ fill: ${({ theme }) => theme.selectImgColor};
+ }
+
+ ${({ isOpenOptions }) => {
+ return isOpenOptions
+ ? css`
+ transform: rotate(-180deg);
+ `
+ : '';
+ }}
+`;
+
+export {
+ InputBlockStyled,
+ ContainerStyled,
+ LabelStyled,
+ SelectStyled,
+ ImgRightStyled,
+};
diff --git a/src/components/Popup/Popup.tsx b/src/components/Popup/Popup.tsx
new file mode 100644
index 0000000000..89e0cdc986
--- /dev/null
+++ b/src/components/Popup/Popup.tsx
@@ -0,0 +1,28 @@
+import { CLOSING_SVG } from '../../utils/SVG';
+import { ContainerStyled, ExitPopUpStyled, PopUpStyled } from './styled';
+import ReactDOM from 'react-dom';
+
+type ModalProps = {
+ isOpen: boolean;
+ onClose: () => void;
+ children: React.ReactNode;
+};
+
+const Modal: React.FC = ({ isOpen, onClose, children }) => {
+ if (!isOpen) return null;
+
+ return ReactDOM.createPortal(
+
+
+
+
+
+
+ {children}
+
+ ,
+ document.getElementById('modal-root') as HTMLElement,
+ );
+};
+
+export default Modal;
diff --git a/src/components/Popup/styled.ts b/src/components/Popup/styled.ts
new file mode 100644
index 0000000000..1a7197ced7
--- /dev/null
+++ b/src/components/Popup/styled.ts
@@ -0,0 +1,68 @@
+import styled, { keyframes } from 'styled-components';
+
+const fadeIn = keyframes`
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: 1;
+ }
+`;
+
+const fadeContent = keyframes`
+ from {
+ transform: translateY(-40px);
+ }
+ to {
+ transform: translateY(0);
+ }
+`;
+
+const PopUpStyled = styled.div`
+ width: 100vw;
+ height: 100vh;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: rgba(0, 0, 0, 0.3);
+ position: fixed;
+ left: 0;
+ top: 0;
+ z-index: 100;
+ min-width: 320px;
+ padding: 16px;
+
+ animation: ${fadeIn} 0.3s ease;
+`;
+
+const ContainerStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ border: 1px solid ${({ theme }) => theme.cardBorder};
+ background-color: ${({ theme }) => theme.cardBacground};
+ color: ${({ theme }) => theme.textColor};
+ position: relative;
+
+ gap: 20px;
+ padding: 40px 30px 30px;
+
+ &:hover {
+ box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.1);
+ border: 1px solid ${({ theme }) => theme.cardBorderHover};
+ }
+
+ animation: ${fadeContent} 0.3s ease;
+`;
+
+const ExitPopUpStyled = styled.div`
+ position: absolute;
+ right: 10px;
+ top: 10px;
+ cursor: pointer;
+
+ svg {
+ fill: ${({ theme }) => theme.buttonSecondColor} !important;
+ }
+`;
+
+export { PopUpStyled, ContainerStyled, ExitPopUpStyled };
diff --git a/src/components/Themes/ThemeProvider.tsx b/src/components/Themes/ThemeProvider.tsx
new file mode 100644
index 0000000000..26b95dc7e0
--- /dev/null
+++ b/src/components/Themes/ThemeProvider.tsx
@@ -0,0 +1,47 @@
+import React, { createContext, useState, useContext } from 'react';
+import { ThemeProvider as StyledThemeProvider } from 'styled-components';
+import { theme1, theme2, theme3, theme4, theme5 } from './themes';
+
+export type ThemeNames = 'White' | 'Dark' | 'Theme3' | 'Theme4' | 'Theme5';
+
+export const themeMap = {
+ White: theme1,
+ Dark: theme2,
+ Theme3: theme3,
+ Theme4: theme4,
+ Theme5: theme5,
+};
+
+const defaultThemeName = 'White';
+
+const ThemeContext = createContext({
+ theme: themeMap[defaultThemeName],
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ setTheme: (_name: ThemeNames) => {},
+});
+
+type Props = {
+ children: React.ReactNode;
+};
+
+export const ThemeProvider: React.FC = ({ children }) => {
+ const storedThemeName = localStorage.getItem('themeName') as ThemeNames;
+ const [themeName, setThemeName] = useState(
+ storedThemeName || defaultThemeName,
+ );
+
+ const setTheme = (name: ThemeNames) => {
+ setThemeName(name);
+ localStorage.setItem('themeName', name);
+ };
+
+ return (
+
+
+ {children}
+
+
+ );
+};
+
+export const useTheme = () => useContext(ThemeContext);
diff --git a/src/components/Themes/themes.ts b/src/components/Themes/themes.ts
new file mode 100644
index 0000000000..7f131cd6fb
--- /dev/null
+++ b/src/components/Themes/themes.ts
@@ -0,0 +1,244 @@
+export const theme1 = {
+ bacgroundDefault: '#fff',
+ bacgroundPage: '#fff',
+ cardBacground: '#fff',
+ cardBorder: '#e2e6e9',
+ cardBorderHover: '#e2e6e9',
+ textColor: '#313237',
+ textSecondColor: '#89939a',
+ textThreeColor: '#fff',
+ borderDefault: '#e2e6e9',
+ buttonFirstColor: '#313237',
+ buttonSecondColor: '#313237',
+ buttonSecondBorder: '#b4bdc3',
+ buttonThreeColor: '#27AE60',
+ buttonSecondBacground: 'none',
+ buttonSecondNotColor: '#b4bdc3',
+ buttonSecondNotBacground: 'none',
+ footerButton: '#89939a',
+ backToTop: '#89939a',
+ selectBorder: '#b4bdc3',
+ selectBackground: '#fff',
+ selectBorderHover: '#89939a',
+ selectImgColor: '#b4bdc3',
+ selectBorderFocus: '#89939a',
+ optionBorder: '#e2e6e9',
+ optionBackground: '#fff',
+ optionText: '#89939a',
+ optionHoverItem: '#fafbfc',
+ optionHoverText: '#313237',
+ cardSliderActive: '#313237',
+ cardSliderNotActive: '#e2e6e9',
+ buttonHoverBacground: '#313237',
+ buttonHoverShadow: '#17203166',
+ buttonSecondHoverBacground: 'none',
+ buttonSecondHoverBorder: '#313237',
+ buttonPaginationHover: 'none',
+ butPaginHovBor: '#313237',
+ capacityRadius: '0',
+ capacityColor: '#fff',
+ capacityBack: '#313237',
+ capacityBorder: '#b4bdc3',
+ borderRadius: '0',
+ borderSecRdius: '0',
+ borderThreeRadius: '0',
+ buttonFirstRadius: '0',
+ iconColor: '#eb5757',
+ favoritIconColor: '#eb5757',
+};
+
+export const theme2 = {
+ bacgroundDefault: '#0f1121',
+ bacgroundPage: '#0f1121',
+ cardBacground: '#161827',
+ cardBorder: '#161827',
+ cardBorderHover: '#323542',
+ textColor: '#f1f2f9',
+ textSecondColor: '#75767f',
+ textThreeColor: '#f1f2f9',
+ borderDefault: '#323542',
+ buttonFirstColor: '#905bff',
+ buttonSecondColor: '#f1f2f9',
+ buttonSecondBorder: '#323542',
+ buttonThreeColor: '#F1F2F9',
+ buttonSecondBacground: '#323542',
+ buttonSecondNotColor: '#4a4d58',
+ buttonSecondNotBacground: '#0f1121',
+ footerButton: '#f1f2f9',
+ backToTop: '#75767f',
+ selectBorder: '#323542',
+ selectBackground: '#323542',
+ selectBorderHover: '#4a4d58',
+ selectImgColor: '#75767f',
+ selectBorderFocus: '#905bff',
+ optionBorder: '#3b3e4a',
+ optionBackground: '#0f1121',
+ optionText: '#75767f',
+ optionHoverItem: '#323542',
+ optionHoverText: '#f1f2f9',
+ cardSliderActive: '#f1f2f9',
+ cardSliderNotActive: '#3b3e4a',
+ buttonHoverBacground: '#a378ff',
+ buttonHoverShadow: 'none',
+ buttonSecondHoverBacground: '#4a4d58',
+ buttonSecondHoverBorder: '#4a4d58',
+ buttonPaginationHover: '#3b3e4a',
+ butPaginHovBor: '#3b3e4a',
+ capacityRadius: '0',
+ capacityColor: '#0f1121',
+ capacityBack: '#f1f2f9',
+ capacityBorder: '#4a4d58',
+ borderRadius: '0',
+ borderSecRdius: '0',
+ borderThreeRadius: '0',
+ buttonFirstRadius: '0',
+ iconColor: '#eb5757',
+ favoritIconColor: '#eb5757',
+};
+
+export const theme3 = {
+ bacgroundDefault: '#fff',
+ bacgroundPage: '#fafbfc',
+ cardBacground: '#fff',
+ cardBorder: '#e2e6e9',
+ cardBorderHover: '#e2e6e9',
+ textColor: '#0f0f11',
+ textSecondColor: '#89939a',
+ textThreeColor: '#fff',
+ borderDefault: '#e2e6e9',
+ buttonFirstColor: '#216CFF',
+ buttonSecondColor: '#0f0f11',
+ buttonSecondBorder: '#b4bdc3',
+ buttonThreeColor: '#216CFF',
+ buttonSecondBacground: 'none',
+ buttonSecondNotColor: '#e2e6e9',
+ buttonSecondNotBacground: 'none',
+ footerButton: '#89939a',
+ backToTop: '#89939a',
+ selectBorder: '#b4bdc3',
+ selectBackground: '#fff',
+ selectBorderHover: '#89939a',
+ selectImgColor: '#b4bdc3',
+ selectBorderFocus: '#0f0f11',
+ optionBorder: '#e2e6e9',
+ optionBackground: '#fff',
+ optionText: '#89939a',
+ optionHoverItem: '#fafbfc',
+ optionHoverText: '#0f0f11',
+ cardSliderActive: '#0f0f11',
+ cardSliderNotActive: '#e2e6e9',
+ buttonHoverBacground: '#216CFF',
+ buttonHoverShadow: '#17203166',
+ buttonSecondHoverBacground: 'none',
+ buttonSecondHoverBorder: '#0f0f11',
+ buttonPaginationHover: 'none',
+ butPaginHovBor: '#0f0f11',
+ capacityRadius: '4px',
+ capacityColor: '#fff',
+ capacityBack: '#0f0f11',
+ capacityBorder: '#b4bdc3',
+ borderRadius: '8px',
+ borderSecRdius: '16px',
+ borderThreeRadius: '48px',
+ buttonFirstRadius: '8px',
+ iconColor: '#f447af',
+ favoritIconColor: '#f447af',
+};
+
+export const theme4 = {
+ bacgroundDefault: '#fff',
+ bacgroundPage: '#fafbfc',
+ cardBacground: '#fff',
+ cardBorder: '#e2e6e9',
+ cardBorderHover: '#e2e6e9',
+ textColor: '#0f0f11',
+ textSecondColor: '#89939a',
+ textThreeColor: '#fff',
+ borderDefault: '#e2e6e9',
+ buttonFirstColor: '#f86800',
+ buttonSecondColor: '#0f0f11',
+ buttonSecondBorder: '#b4bdc3',
+ buttonThreeColor: '#f86800',
+ buttonSecondBacground: 'none',
+ buttonSecondNotColor: '#e2e6e9',
+ buttonSecondNotBacground: 'none',
+ footerButton: '#89939a',
+ backToTop: '#89939a',
+ selectBorder: '#b4bdc3',
+ selectBackground: '#fff',
+ selectBorderHover: '#89939a',
+ selectImgColor: '#b4bdc3',
+ selectBorderFocus: '#0f0f11',
+ optionBorder: '#e2e6e9',
+ optionBackground: '#fff',
+ optionText: '#89939a',
+ optionHoverItem: '#fafbfc',
+ optionHoverText: '#0f0f11',
+ cardSliderActive: '#0f0f11',
+ cardSliderNotActive: '#e2e6e9',
+ buttonHoverBacground: '#f86800',
+ buttonHoverShadow: '#17203166',
+ buttonSecondHoverBacground: 'none',
+ buttonSecondHoverBorder: '#0f0f11',
+ buttonPaginationHover: 'none',
+ butPaginHovBor: '#0f0f11',
+ capacityRadius: '4px',
+ capacityColor: '#fff',
+ capacityBack: '#0f0f11',
+ capacityBorder: '#b4bdc3',
+ borderRadius: '8px',
+ borderSecRdius: '16px',
+ borderThreeRadius: '48px',
+ buttonFirstRadius: '8px',
+ iconColor: '#476df4',
+ favoritIconColor: '#476df4',
+};
+
+export const theme5 = {
+ bacgroundDefault: '#fff',
+ bacgroundPage: '#fafbfc',
+ cardBacground: '#fff',
+ cardBorder: '#e2e6e9',
+ cardBorderHover: '#e2e6e9',
+ textColor: '#0f0f11',
+ textSecondColor: '#89939a',
+ textThreeColor: '#fff',
+ borderDefault: '#e2e6e9',
+ buttonFirstColor: '#4219d0',
+ buttonSecondColor: '#0f0f11',
+ buttonSecondBorder: '#b4bdc3',
+ buttonThreeColor: '#4219d0',
+ buttonSecondBacground: 'none',
+ buttonSecondNotColor: '#e2e6e9',
+ buttonSecondNotBacground: 'none',
+ footerButton: '#89939a',
+ backToTop: '#89939a',
+ selectBorder: '#b4bdc3',
+ selectBackground: '#fff',
+ selectBorderHover: '#89939a',
+ selectImgColor: '#b4bdc3',
+ selectBorderFocus: '#0f0f11',
+ optionBorder: '#e2e6e9',
+ optionBackground: '#fff',
+ optionText: '#89939a',
+ optionHoverItem: '#fafbfc',
+ optionHoverText: '#0f0f11',
+ cardSliderActive: '#0f0f11',
+ cardSliderNotActive: '#e2e6e9',
+ buttonHoverBacground: '#4219d0',
+ buttonHoverShadow: '#17203166',
+ buttonSecondHoverBacground: 'none',
+ buttonSecondHoverBorder: '#0f0f11',
+ buttonPaginationHover: 'none',
+ butPaginHovBor: '#0f0f11',
+ capacityRadius: '4px',
+ capacityColor: '#fff',
+ capacityBack: '#0f0f11',
+ capacityBorder: '#b4bdc3',
+ borderRadius: '8px',
+ borderSecRdius: '16px',
+ borderThreeRadius: '48px',
+ buttonFirstRadius: '48px',
+ iconColor: '#4219d0',
+ favoritIconColor: '#f4ba47',
+};
diff --git a/src/features/accessoriesSlice.ts b/src/features/accessoriesSlice.ts
new file mode 100644
index 0000000000..b9e199bcf5
--- /dev/null
+++ b/src/features/accessoriesSlice.ts
@@ -0,0 +1,26 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+import { AccessoryType } from '../types/productsType';
+
+export interface InitialStateType {
+ accessories: AccessoryType[];
+}
+
+const initialState: InitialStateType = {
+ accessories: [],
+};
+
+const accessoriesSlice = createSlice({
+ name: 'accesories',
+ initialState,
+ reducers: {
+ setAccessories: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.accessories = action.payload;
+ },
+ },
+});
+
+export const { setAccessories } = accessoriesSlice.actions;
+export default accessoriesSlice.reducer;
diff --git a/src/features/basketSlice.ts b/src/features/basketSlice.ts
new file mode 100644
index 0000000000..53fdadf254
--- /dev/null
+++ b/src/features/basketSlice.ts
@@ -0,0 +1,69 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+import { ProductType } from '../types/productsType';
+
+type BacketType = {
+ itemId: string;
+ count: number;
+ product: ProductType;
+};
+
+export interface InitialStateType {
+ backetsId: BacketType[];
+}
+
+const initialState: InitialStateType = {
+ backetsId: [],
+};
+
+const backetSlice = createSlice({
+ name: 'backets',
+ initialState,
+ reducers: {
+ addBacketId: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.backetsId.push(action.payload);
+ },
+ addCoundBacketId: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.backetsId = state.backetsId.map(item =>
+ item.itemId === action.payload
+ ? { ...item, count: item.count + 1 }
+ : item,
+ );
+ },
+ subtractionCoundBacketId: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.backetsId = state.backetsId.map(item =>
+ item.itemId === action.payload
+ ? { ...item, count: item.count - 1 }
+ : item,
+ );
+ },
+ deleteBacketId: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.backetsId = state.backetsId.filter(
+ item => item.itemId !== action.payload,
+ );
+ },
+ clearBacketId: (state: InitialStateType) => {
+ state.backetsId = [];
+ },
+ },
+});
+
+export const {
+ addBacketId,
+ addCoundBacketId,
+ subtractionCoundBacketId,
+ deleteBacketId,
+ clearBacketId,
+} = backetSlice.actions;
+export default backetSlice.reducer;
diff --git a/src/features/core.tsx b/src/features/core.tsx
new file mode 100644
index 0000000000..2f410a120c
--- /dev/null
+++ b/src/features/core.tsx
@@ -0,0 +1,30 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+
+export interface InitialStateType {
+ searchValue: string;
+ isFocused: boolean;
+}
+
+const initialState: InitialStateType = {
+ searchValue: '',
+ isFocused: false,
+};
+
+const coreSlice = createSlice({
+ name: 'backets',
+ initialState,
+ reducers: {
+ setSearchValue: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.searchValue = action.payload;
+ },
+ setIsFocused: (state: InitialStateType, action: PayloadAction) => {
+ state.isFocused = action.payload;
+ },
+ },
+});
+
+export const { setSearchValue, setIsFocused } = coreSlice.actions;
+export default coreSlice.reducer;
diff --git a/src/features/favoritSlice.ts b/src/features/favoritSlice.ts
new file mode 100644
index 0000000000..6e2466cebe
--- /dev/null
+++ b/src/features/favoritSlice.ts
@@ -0,0 +1,30 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+
+export interface InitialStateType {
+ favoritId: string[];
+}
+
+const initialState: InitialStateType = {
+ favoritId: [],
+};
+
+const favoritSlice = createSlice({
+ name: 'favorites',
+ initialState,
+ reducers: {
+ addFavoritId: (state: InitialStateType, action: PayloadAction) => {
+ state.favoritId.push(action.payload);
+ },
+ deleteFavoritId: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.favoritId = [
+ ...state.favoritId.filter(item => item !== action.payload),
+ ];
+ },
+ },
+});
+
+export const { addFavoritId, deleteFavoritId } = favoritSlice.actions;
+export default favoritSlice.reducer;
diff --git a/src/features/phoneSlice.ts b/src/features/phoneSlice.ts
new file mode 100644
index 0000000000..55a3a2794e
--- /dev/null
+++ b/src/features/phoneSlice.ts
@@ -0,0 +1,26 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+import { PhoneType } from '../types/productsType';
+
+export interface InitialStateType {
+ phones: PhoneType[];
+}
+
+const initialState: InitialStateType = {
+ phones: [],
+};
+
+const phoneSlice = createSlice({
+ name: 'phones',
+ initialState,
+ reducers: {
+ setPhones: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.phones = action.payload;
+ },
+ },
+});
+
+export const { setPhones } = phoneSlice.actions;
+export default phoneSlice.reducer;
diff --git a/src/features/productsSlice.ts b/src/features/productsSlice.ts
new file mode 100644
index 0000000000..00e0e94509
--- /dev/null
+++ b/src/features/productsSlice.ts
@@ -0,0 +1,26 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+import { ProductType } from '../types/productsType';
+
+export interface InitialStateType {
+ products: ProductType[];
+}
+
+const initialState: InitialStateType = {
+ products: [],
+};
+
+const productSlice = createSlice({
+ name: 'products',
+ initialState,
+ reducers: {
+ setProducts: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.products = action.payload;
+ },
+ },
+});
+
+export const { setProducts } = productSlice.actions;
+export default productSlice.reducer;
diff --git a/src/features/tablesSlice.ts b/src/features/tablesSlice.ts
new file mode 100644
index 0000000000..701e2618f7
--- /dev/null
+++ b/src/features/tablesSlice.ts
@@ -0,0 +1,26 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+import { TabletType } from '../types/productsType';
+
+export interface InitialStateType {
+ tables: TabletType[];
+}
+
+const initialState: InitialStateType = {
+ tables: [],
+};
+
+const tablesSlice = createSlice({
+ name: 'tables',
+ initialState,
+ reducers: {
+ setTables: (
+ state: InitialStateType,
+ action: PayloadAction,
+ ) => {
+ state.tables = action.payload;
+ },
+ },
+});
+
+export const { setTables } = tablesSlice.actions;
+export default tablesSlice.reducer;
diff --git a/src/hooks/hookStore.ts b/src/hooks/hookStore.ts
new file mode 100644
index 0000000000..a9715db523
--- /dev/null
+++ b/src/hooks/hookStore.ts
@@ -0,0 +1,5 @@
+import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
+import { AppDispatch, RootState } from '../app/store';
+
+export const useAppDispatch = () => useDispatch();
+export const useAppSelector: TypedUseSelectorHook = useSelector;
diff --git a/src/hooks/useAddCartFavorit.ts b/src/hooks/useAddCartFavorit.ts
new file mode 100644
index 0000000000..8c9e982457
--- /dev/null
+++ b/src/hooks/useAddCartFavorit.ts
@@ -0,0 +1,63 @@
+import { addBacketId } from '../features/basketSlice';
+import { addFavoritId, deleteFavoritId } from '../features/favoritSlice';
+import { useAppDispatch, useAppSelector } from './hookStore';
+
+export const useAddCartFavorit = (productId?: string) => {
+ const dispatch = useAppDispatch();
+ const { backetsId } = useAppSelector(state => state.backets);
+ const { favoritId } = useAppSelector(state => state.favorit);
+ const { products } = useAppSelector(state => state.products);
+
+ const product = products.find(item => item.itemId === productId);
+
+ const handleToBasket = () => {
+ if (!product) {
+ return;
+ }
+
+ if (!backetsId.map(item => item.itemId).includes(product.itemId)) {
+ dispatch(
+ addBacketId({
+ itemId: product.itemId,
+ count: 1,
+ product: product,
+ }),
+ );
+ }
+ };
+
+ const handleToFavorit = () => {
+ if (!product) {
+ return;
+ }
+
+ if (favoritId.includes(product.itemId)) {
+ dispatch(deleteFavoritId(product.itemId));
+ } else {
+ dispatch(addFavoritId(product.itemId));
+ }
+ };
+
+ const includesBacket = () => {
+ if (!product) {
+ return;
+ }
+
+ return backetsId.map(item => item.itemId).includes(product.itemId);
+ };
+
+ const includesFavorit = () => {
+ if (!product) {
+ return false;
+ }
+
+ return favoritId.includes(product.itemId);
+ };
+
+ return {
+ handleToBasket,
+ handleToFavorit,
+ includesBacket,
+ includesFavorit,
+ };
+};
diff --git a/src/hooks/useOutsideClick.ts b/src/hooks/useOutsideClick.ts
new file mode 100644
index 0000000000..a9a7b49169
--- /dev/null
+++ b/src/hooks/useOutsideClick.ts
@@ -0,0 +1,24 @@
+import { useEffect } from 'react';
+
+export const useOutsideClick = (
+ ref: React.MutableRefObject,
+ callback: () => void,
+) => {
+ const handleClick = (e: MouseEvent | TouchEvent) => {
+ if (!ref.current || ref.current.contains(e.target as Node)) {
+ return;
+ }
+
+ callback();
+ };
+
+ useEffect(() => {
+ document.addEventListener('mousedown', handleClick);
+ document.addEventListener('touchstart', handleClick);
+
+ return () => {
+ document.removeEventListener('mousedown', handleClick);
+ document.removeEventListener('touchstart', handleClick);
+ };
+ }, [ref, callback]);
+};
diff --git a/src/hooks/useProduct.ts b/src/hooks/useProduct.ts
new file mode 100644
index 0000000000..809ee430e6
--- /dev/null
+++ b/src/hooks/useProduct.ts
@@ -0,0 +1,27 @@
+import { useAppSelector } from './hookStore';
+import { useParams, useLocation } from 'react-router-dom';
+
+const useProduct = () => {
+ const { pathname } = useLocation();
+ const basePath = pathname.split('/')[1];
+ const { productId } = useParams();
+
+ const products = useAppSelector(state => {
+ switch (basePath) {
+ case 'phones':
+ return state.phones.phones;
+ case 'accessories':
+ return state.accessories.accessories;
+ case 'tablets':
+ return state.tables.tables;
+ default:
+ return null;
+ }
+ });
+
+ const product = products?.find(item => item.id === productId);
+
+ return { product };
+};
+
+export default useProduct;
diff --git a/src/hooks/useProductPage.ts b/src/hooks/useProductPage.ts
new file mode 100644
index 0000000000..7e5891a0c9
--- /dev/null
+++ b/src/hooks/useProductPage.ts
@@ -0,0 +1,158 @@
+import { useEffect, useState } from 'react';
+import { useSearchParams } from 'react-router-dom';
+import { useAppSelector } from './hookStore';
+import { useTranslation } from 'react-i18next';
+import { StrCode } from '../utils/enums';
+
+type SortOptions = 'Newest' | 'Alphabetically' | 'Cheapest';
+
+export const useProductPage = (
+ variant: 'phones' | 'tabless' | 'accesories',
+) => {
+ const { t } = useTranslation();
+ const [searchParams, setSearchParams] = useSearchParams();
+
+ const pageParam = searchParams.get('page') || 1;
+ const perPageParam = searchParams.get('perPage') || '16';
+ const searchParam = searchParams.get('search');
+
+ const sortParamObj = {
+ age: t(StrCode.SortAge),
+ name: t(StrCode.SortName),
+ price: t(StrCode.SortPrice),
+ };
+
+ const param = searchParams.get('sort');
+
+ const sortParam = () => {
+ if (param && param in sortParamObj) {
+ return sortParamObj[param as keyof typeof sortParamObj];
+ }
+
+ return sortParamObj.age;
+ };
+
+ const { products } = useAppSelector(state => state.products);
+
+ const [valueSort, setValueSort] = useState(sortParam());
+ const [valuePerPage, setValuePerPage] = useState(perPageParam);
+ const [currentPage, setCurrentPage] = useState(Number(pageParam));
+
+ const updateCurrentPage = (value: number) => {
+ searchParams.set('page', value.toString());
+ setSearchParams(searchParams);
+ setCurrentPage(value);
+ };
+
+ useEffect(() => {
+ setValueSort(sortParam());
+ setValuePerPage(perPageParam);
+ setCurrentPage(Number(pageParam));
+ }, [variant, param, perPageParam, pageParam, t]);
+
+ const updateSort = (value: string) => {
+ const valueChange = () => {
+ switch (value) {
+ case 'Newest':
+ return 'age';
+ case 'Новизною':
+ return 'age';
+ case 'Alphabetically':
+ return 'name';
+ case 'Назвою':
+ return 'name';
+ case 'Cheapest':
+ return 'price';
+ case 'Дешевизною':
+ return 'price';
+ default:
+ return 'age';
+ }
+ };
+
+ searchParams.set('sort', valueChange());
+ searchParams.set('page', '1');
+ setSearchParams(searchParams);
+ setValueSort(value as SortOptions);
+ };
+
+ const updatePerPage = (value: string) => {
+ searchParams.set('perPage', value);
+ searchParams.set('page', '1');
+ setSearchParams(searchParams);
+ setValuePerPage(value);
+ };
+
+ const productsVariant = products.filter(item => {
+ switch (variant) {
+ case 'phones':
+ return item.category === 'phones';
+ case 'accesories':
+ return item.category === 'accessories';
+ case 'tabless':
+ return item.category === 'tablets';
+ }
+ });
+
+ const productsUsed = searchParam
+ ? [...productsVariant].filter(item => {
+ return item.name
+ .replace(/\s+/g, '')
+ .toLowerCase()
+ .includes(searchParam.replace(/\s+/g, '').toLowerCase());
+ })
+ : [...productsVariant];
+
+ const sortedAndPaginatedProducts = () => {
+ const sortedProducts = [...productsUsed];
+
+ switch (valueSort) {
+ case 'Newest':
+ sortedProducts.sort((a, b) => b.year - a.year);
+ break;
+ case 'Новизною':
+ sortedProducts.sort((a, b) => b.year - a.year);
+ break;
+ case 'Alphabetically':
+ sortedProducts.sort((a, b) => a.name.localeCompare(b.name));
+ break;
+ case 'Назвою':
+ sortedProducts.sort((a, b) => a.name.localeCompare(b.name));
+ break;
+ case 'Cheapest':
+ sortedProducts.sort((a, b) => a.price - b.price);
+ break;
+ case 'Дешевизною':
+ sortedProducts.sort((a, b) => a.price - b.price);
+ break;
+ default:
+ break;
+ }
+
+ if (valuePerPage === 'all') {
+ return sortedProducts;
+ } else {
+ const perPage = Number(valuePerPage);
+ const startIndex = (currentPage - 1) * perPage;
+ const endIndex = startIndex + perPage;
+
+ return sortedProducts.slice(startIndex, endIndex);
+ }
+ };
+
+ const sordedProduct = sortedAndPaginatedProducts();
+ const productsLength = productsUsed.length;
+ const productsVariantLength = productsVariant.length;
+
+ return {
+ productsLength,
+ productsVariantLength,
+ valueSort,
+ updateSort,
+ valuePerPage,
+ updatePerPage,
+ sordedProduct,
+ currentPage,
+ updateCurrentPage,
+ };
+};
diff --git a/src/hooks/useScrollButtons.ts b/src/hooks/useScrollButtons.ts
new file mode 100644
index 0000000000..35ea33f39b
--- /dev/null
+++ b/src/hooks/useScrollButtons.ts
@@ -0,0 +1,71 @@
+import { useEffect, useRef, useState } from 'react';
+
+const useScrollButtons = () => {
+ const scrollRef = useRef(null);
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
+ const [canScrollRight, setCanScrollRight] = useState(true);
+
+ const updateScrollButtons = () => {
+ if (scrollRef.current) {
+ const isAbleToScrollLeft = scrollRef.current.scrollLeft > 0;
+ const isAbleToScrollRight =
+ scrollRef.current.scrollLeft <
+ scrollRef.current.scrollWidth - scrollRef.current.clientWidth;
+
+ setCanScrollLeft(isAbleToScrollLeft);
+ setCanScrollRight(isAbleToScrollRight);
+ }
+ };
+
+ useEffect(() => {
+ const container = scrollRef.current;
+
+ if (container) {
+ container.addEventListener('scroll', updateScrollButtons);
+ }
+
+ return () => {
+ if (container) {
+ container.removeEventListener('scroll', updateScrollButtons);
+ }
+ };
+ }, []);
+
+ const getScrollAmount = (pos: 'left' | 'right') => {
+ if (window.innerWidth >= 1200) {
+ return pos === 'left' ? -1152 : 1152;
+ } else if (window.innerWidth >= 640) {
+ return pos === 'left' ? -508 : 508;
+ } else {
+ return pos === 'left' ? -229 : 229;
+ }
+ };
+
+ const handleScrollLeft = () => {
+ if (scrollRef.current && canScrollLeft) {
+ scrollRef.current.scrollBy({
+ left: getScrollAmount('left'),
+ behavior: 'smooth',
+ });
+ }
+ };
+
+ const handleScrollRight = () => {
+ if (scrollRef.current && canScrollRight) {
+ scrollRef.current.scrollBy({
+ left: getScrollAmount('right'),
+ behavior: 'smooth',
+ });
+ }
+ };
+
+ return {
+ scrollRef,
+ canScrollLeft,
+ canScrollRight,
+ handleScrollLeft,
+ handleScrollRight,
+ };
+};
+
+export default useScrollButtons;
diff --git a/src/icons/Close.png b/src/icons/Close.png
new file mode 100644
index 0000000000..276586a082
Binary files /dev/null and b/src/icons/Close.png differ
diff --git a/src/icons/Favourites.png b/src/icons/Favourites.png
new file mode 100644
index 0000000000..3820027dc6
Binary files /dev/null and b/src/icons/Favourites.png differ
diff --git a/src/icons/Logo.png b/src/icons/Logo.png
new file mode 100644
index 0000000000..b4d8f50248
Binary files /dev/null and b/src/icons/Logo.png differ
diff --git a/src/icons/Logo2.png b/src/icons/Logo2.png
new file mode 100644
index 0000000000..1b78014e51
Binary files /dev/null and b/src/icons/Logo2.png differ
diff --git a/src/icons/Logo3.png b/src/icons/Logo3.png
new file mode 100644
index 0000000000..51eaaed3ce
Binary files /dev/null and b/src/icons/Logo3.png differ
diff --git a/src/icons/Logo45.png b/src/icons/Logo45.png
new file mode 100644
index 0000000000..195f3bc76d
Binary files /dev/null and b/src/icons/Logo45.png differ
diff --git a/src/icons/Menu.png b/src/icons/Menu.png
new file mode 100644
index 0000000000..2065cc5097
Binary files /dev/null and b/src/icons/Menu.png differ
diff --git a/src/icons/ProductSlider/ProductSlide.png b/src/icons/ProductSlider/ProductSlide.png
new file mode 100644
index 0000000000..1f79162946
Binary files /dev/null and b/src/icons/ProductSlider/ProductSlide.png differ
diff --git a/src/icons/ProductSlider/ProductSlide1.png b/src/icons/ProductSlider/ProductSlide1.png
new file mode 100644
index 0000000000..ce6f886af7
Binary files /dev/null and b/src/icons/ProductSlider/ProductSlide1.png differ
diff --git a/src/icons/ProductSlider/ProductSlide2.png b/src/icons/ProductSlider/ProductSlide2.png
new file mode 100644
index 0000000000..19dcab158c
Binary files /dev/null and b/src/icons/ProductSlider/ProductSlide2.png differ
diff --git a/src/icons/ProductSlider/Tablet/Banner.png b/src/icons/ProductSlider/Tablet/Banner.png
new file mode 100644
index 0000000000..221f4c8a7c
Binary files /dev/null and b/src/icons/ProductSlider/Tablet/Banner.png differ
diff --git a/src/icons/ProductSlider/Tablet/Banner1.png b/src/icons/ProductSlider/Tablet/Banner1.png
new file mode 100644
index 0000000000..1d22d7fa26
Binary files /dev/null and b/src/icons/ProductSlider/Tablet/Banner1.png differ
diff --git a/src/icons/ProductSlider/Tablet/Banner2.png b/src/icons/ProductSlider/Tablet/Banner2.png
new file mode 100644
index 0000000000..dc72da8f13
Binary files /dev/null and b/src/icons/ProductSlider/Tablet/Banner2.png differ
diff --git a/src/icons/Shopping.png b/src/icons/Shopping.png
new file mode 100644
index 0000000000..9b4049e1cc
Binary files /dev/null and b/src/icons/Shopping.png differ
diff --git a/src/index.tsx b/src/index.tsx
index 50470f1508..c9c7e3b2a2 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,4 +1,5 @@
import { createRoot } from 'react-dom/client';
-import { App } from './App';
+import { Root } from './Root';
+import './utils/language';
-createRoot(document.getElementById('root') as HTMLElement).render();
+createRoot(document.getElementById('root') as HTMLElement).render();
diff --git a/src/modules/CartPage/CartPage.tsx b/src/modules/CartPage/CartPage.tsx
new file mode 100644
index 0000000000..240a38a334
--- /dev/null
+++ b/src/modules/CartPage/CartPage.tsx
@@ -0,0 +1,95 @@
+import { useTranslation } from 'react-i18next';
+import { Button } from '../../components/Button/Button';
+import { useAppDispatch, useAppSelector } from '../../hooks/hookStore';
+import CartCard from './components/CartCard/CartCard';
+import {
+ AllPriceStyled,
+ ButtonsModalStyled,
+ CartStyled,
+ ContainerStyled,
+ ModalTextStyled,
+ PriceBlockStyled,
+ PriceInfoStyled,
+ ProductItemsStyled,
+ TitleStyled,
+} from './styled';
+import { StrCode } from '../../utils/enums';
+import { NotFoundImg, ProductsNotFound } from '../ProductsPage/styled';
+import { useState } from 'react';
+import Modal from '../../components/Popup/Popup';
+import { clearBacketId } from '../../features/basketSlice';
+import GoBack from '../_shared/GoBack/GoBack';
+
+const CartPage = () => {
+ const [isBying, setIsBying] = useState(false);
+ const { backetsId } = useAppSelector(state => state.backets);
+ const { t } = useTranslation();
+ const dispatch = useAppDispatch();
+
+ const handleClear = () => {
+ dispatch(clearBacketId());
+ setIsBying(false);
+ };
+
+ const backetsPrice = backetsId
+ .map(item => item.count * item.product.price)
+ .reduce((cou, item) => cou + item, 0);
+
+ const backetsCoung = backetsId.reduce((acc, item) => acc + item.count, 0);
+
+ return (
+
+ setIsBying(false)}>
+ {t(StrCode.CheckoutCart)}
+
+
+
+
+
+
+
+
+
+
+ {t(StrCode.Cart)}
+
+ {!!backetsId.length ? (
+
+
+ {backetsId.map(item => (
+
+ ))}
+
+
+
+ {`$${backetsPrice}`}
+
+
+ {`${t(StrCode.TotalFor)} ${backetsCoung} ${t(StrCode.Items)}`}
+
+
+
+
+
+ ) : (
+
+ {t(StrCode.NotCart)}
+
+
+
+ )}
+
+ );
+};
+
+export default CartPage;
diff --git a/src/modules/CartPage/components/CartCard/CartCard.tsx b/src/modules/CartPage/components/CartCard/CartCard.tsx
new file mode 100644
index 0000000000..6cf768ccba
--- /dev/null
+++ b/src/modules/CartPage/components/CartCard/CartCard.tsx
@@ -0,0 +1,94 @@
+import { useNavigate } from 'react-router-dom';
+import { Button } from '../../../../components/Button/Button';
+import {
+ addCoundBacketId,
+ deleteBacketId,
+ subtractionCoundBacketId,
+} from '../../../../features/basketSlice';
+import { useAppDispatch } from '../../../../hooks/hookStore';
+import { ProductType } from '../../../../types/productsType';
+import { CLOSING_SVG, MINUS_SVG, PLUS_SVG } from '../../../../utils/SVG';
+import {
+ CartCardStyled,
+ CountCalcStyled,
+ CountNumberStyled,
+ InfoImgStyled,
+ InfoSecondStyled,
+ InfoStyled,
+ PriceItemStyled,
+} from './styled';
+
+type Props = {
+ product: ProductType;
+ count: number;
+};
+
+const CartCard: React.FC = ({ product, count }) => {
+ const dispatch = useAppDispatch();
+ const navigate = useNavigate();
+
+ const handleDeleteItem = (event: React.MouseEvent) => {
+ event.stopPropagation();
+ dispatch(deleteBacketId(product.itemId));
+ };
+
+ const handleAddCount = () => {
+ dispatch(addCoundBacketId(product.itemId));
+ };
+
+ const handleSubtractionCount = () => {
+ if (count <= 1) {
+ return;
+ } else {
+ dispatch(subtractionCoundBacketId(product.itemId));
+ }
+ };
+
+ const handleViewItem = () => {
+ if (!product) {
+ return;
+ }
+
+ navigate(`/${product.category}/${product.itemId}`);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+ {product.name}
+
+
+
+
+
+
+ {count}
+
+
+
+
+ {`$${count * product.price}`}
+
+
+ );
+};
+
+export default CartCard;
diff --git a/src/modules/CartPage/components/CartCard/styled.ts b/src/modules/CartPage/components/CartCard/styled.ts
new file mode 100644
index 0000000000..aea7c0046f
--- /dev/null
+++ b/src/modules/CartPage/components/CartCard/styled.ts
@@ -0,0 +1,102 @@
+import styled from 'styled-components';
+import { media } from '../../../../utils/const';
+
+const CartCardStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ border: 1px solid ${({ theme }) => theme.cardBorder};
+ background-color: ${({ theme }) => theme.cardBacground};
+ color: ${({ theme }) => theme.textColor};
+ cursor: pointer;
+ border-radius: ${({ theme }) => theme.borderSecRdius};
+
+ gap: 16px;
+ padding: 16px;
+
+ &:hover {
+ box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.1);
+ border: 1px solid ${({ theme }) => theme.cardBorderHover};
+ }
+
+ ${media.tablet} {
+ padding: 24px;
+ flex-direction: row;
+ justify-content: space-between;
+ }
+`;
+
+const InfoStyled = styled.div`
+ display: flex;
+ gap: 16px;
+ align-items: center;
+ height: 80px;
+
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+
+ & * {
+ flex-shrink: 0;
+ }
+
+ svg {
+ fill: ${({ theme }) => theme.buttonSecondNotColor};
+ cursor: pointer;
+
+ &:hover {
+ transform: scale(1.3);
+ }
+ }
+`;
+
+const InfoImgStyled = styled.img`
+ height: 100%;
+ object-fit: cover;
+ user-select: none;
+
+ &:hover {
+ transform: scale(1.1);
+ }
+`;
+
+const InfoSecondStyled = styled.div`
+ display: flex;
+ gap: 16px;
+ justify-content: space-between;
+ align-items: center;
+`;
+
+const CountCalcStyled = styled.div`
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 96px;
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+ color: ${({ theme }) => theme.textColor};
+ user-select: none;
+`;
+
+const CountNumberStyled = styled.div`
+ width: 32px;
+ text-align: center;
+`;
+
+const PriceItemStyled = styled.div`
+ font-weight: 800;
+ font-size: 22px;
+ line-height: 30.8px;
+ font-family: Mont-Bold, sans-serif;
+ width: 90px;
+`;
+
+export {
+ CartCardStyled,
+ InfoStyled,
+ InfoImgStyled,
+ InfoSecondStyled,
+ CountCalcStyled,
+ CountNumberStyled,
+ PriceItemStyled,
+};
diff --git a/src/modules/CartPage/styled.ts b/src/modules/CartPage/styled.ts
new file mode 100644
index 0000000000..c59b03579c
--- /dev/null
+++ b/src/modules/CartPage/styled.ts
@@ -0,0 +1,122 @@
+import styled from 'styled-components';
+import { media } from '../../utils/const';
+
+const CartStyled = styled.div`
+ padding: 24px 16px 56px;
+
+ ${media.desktop} {
+ max-width: 1136px;
+ min-width: 1136px;
+ margin-inline: auto;
+ padding: 24px 0 64px;
+ }
+`;
+
+const TitleStyled = styled.h2`
+ margin: 0;
+ margin-bottom: 32px;
+ font-weight: 800;
+ font-size: 32px;
+ line-height: 41px;
+ font-family: 'Mont-Bold', sans-serif;
+ color: ${({ theme }) => theme.textColor};
+
+ ${media.tablet} {
+ font-size: 48px;
+ line-height: 56px;
+ }
+`;
+
+const ContainerStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+
+ gap: 32px;
+
+ ${media.desktop} {
+ flex-direction: row;
+ gap: 16px;
+ }
+`;
+
+const ProductItemsStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ width: 100%;
+ flex-shrink: 0;
+
+ ${media.desktop} {
+ width: 752px;
+ }
+`;
+
+const PriceBlockStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ border: 1px solid ${({ theme }) => theme.optionBorder};
+ width: 100%;
+ padding: 24px;
+ justify-content: center;
+ border-radius: ${({ theme }) => theme.borderSecRdius};
+
+ ${media.desktop} {
+ align-self: start;
+ }
+`;
+
+const AllPriceStyled = styled.div`
+ font-weight: 800;
+ font-size: 32px;
+ line-height: 41px;
+ text-align: center;
+ width: 100%;
+ color: ${({ theme }) => theme.textColor};
+ font-family: Mont-Bold, sans-serif;
+`;
+
+const PriceInfoStyled = styled.div`
+ font-family: 600;
+ font-size: 14px;
+ line-height: 21px;
+ text-align: center;
+ width: 100%;
+ color: ${({ theme }) => theme.textSecondColor};
+
+ padding-bottom: 16px;
+ margin-bottom: 16px;
+
+ border-bottom: 1px solid ${({ theme }) => theme.optionBorder};
+
+ ${media.desktop} {
+ padding-bottom: 25px;
+ margin-bottom: 25px;
+ }
+`;
+
+const ButtonsModalStyled = styled.div`
+ display: flex;
+ gap: 10px;
+`;
+
+const ModalTextStyled = styled.div`
+ font-size: 16px;
+ font-weight: 700;
+ font-family: Mont-SemiBold, sans-serif;
+
+ ${media.tablet} {
+ font-size: 20px;
+ }
+`;
+
+export {
+ CartStyled,
+ TitleStyled,
+ ContainerStyled,
+ ProductItemsStyled,
+ PriceBlockStyled,
+ AllPriceStyled,
+ PriceInfoStyled,
+ ButtonsModalStyled,
+ ModalTextStyled,
+};
diff --git a/src/modules/FavoritePage/FavoritePage.tsx b/src/modules/FavoritePage/FavoritePage.tsx
new file mode 100644
index 0000000000..a577b3deb6
--- /dev/null
+++ b/src/modules/FavoritePage/FavoritePage.tsx
@@ -0,0 +1,44 @@
+import { useTranslation } from 'react-i18next';
+import BreadCrumbs from '../_shared/BreadCrumbs/BreadCrumbs';
+import {
+ FavoritePageStyled,
+ ModelsStyled,
+ NotFoundStyled,
+ TitleStyled,
+} from './styled';
+import { useAppSelector } from '../../hooks/hookStore';
+import { StrCode } from '../../utils/enums';
+import ProductList from '../_shared/ProductList/ProductList';
+import { NotFoundImg } from '../ProductsPage/styled';
+
+const FavoritePage = () => {
+ const { t } = useTranslation();
+ const { products } = useAppSelector(state => state.products);
+ const { favoritId } = useAppSelector(state => state.favorit);
+
+ const productFilter = products.filter(item =>
+ favoritId.includes(item.itemId),
+ );
+
+ return (
+
+
+
+ {t(StrCode.Favourites)}
+
+ {favoritId.length ? (
+ {`${productFilter.length} ${t(StrCode.Models)}`}
+ ) : (
+ <>
+ {t(StrCode.NotFavourites)}
+
+
+ >
+ )}
+
+
+
+ );
+};
+
+export default FavoritePage;
diff --git a/src/modules/FavoritePage/styled.ts b/src/modules/FavoritePage/styled.ts
new file mode 100644
index 0000000000..a35af80f1d
--- /dev/null
+++ b/src/modules/FavoritePage/styled.ts
@@ -0,0 +1,46 @@
+import styled from 'styled-components';
+import { media } from '../../utils/const';
+
+const FavoritePageStyled = styled.div`
+ padding: 24px 16px 56px;
+
+ ${media.desktop} {
+ max-width: 1136px;
+ min-width: 1136px;
+ margin-inline: auto;
+ padding: 24px 0 64px;
+ }
+`;
+
+const TitleStyled = styled.h2`
+ margin: 24px 0 8px;
+ font-weight: 800;
+ font-size: 32px;
+ line-height: 41px;
+ font-family: 'Mont-Bold', sans-serif;
+ color: ${({ theme }) => theme.textColor};
+
+ ${media.tablet} {
+ font-size: 48px;
+ line-height: 56px;
+ margin: 40px 0 8px;
+ }
+`;
+
+const ModelsStyled = styled.div`
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+ color: ${({ theme }) => theme.textSecondColor};
+ margin-bottom: 32px;
+`;
+
+const NotFoundStyled = styled.div`
+ font-size: 32px;
+ line-height: 41px;
+ margin-top: 20px;
+
+ color: ${({ theme }) => theme.textColor};
+`;
+
+export { FavoritePageStyled, TitleStyled, ModelsStyled, NotFoundStyled };
diff --git a/src/modules/HomePage/HomePage.tsx b/src/modules/HomePage/HomePage.tsx
new file mode 100644
index 0000000000..72ace08bc8
--- /dev/null
+++ b/src/modules/HomePage/HomePage.tsx
@@ -0,0 +1,42 @@
+import { useAppSelector } from '../../hooks/hookStore';
+import { StrCode } from '../../utils/enums';
+import CategoryBlock from './components/CategoryBlock/CategoryBlock';
+import { PicturesSlider } from './components/PicturesSlider/PicturesSlider';
+import ProductsSlider from '../_shared/ProductsSlider/ProductsSlider';
+import { HomePageStyled, TitleStyled } from './styled';
+import { useTranslation } from 'react-i18next';
+
+export const HomePage = () => {
+ const { t } = useTranslation();
+ const { products = [] } = useAppSelector(state => state.products) || {};
+
+ const newModelProducts = [...products]
+ .sort((a, b) => b.year - a.year)
+ .slice(0, 10);
+ const hotPriceProducts = [...products]
+ .sort((a, b) => b.fullPrice - b.price - (a.fullPrice - a.price))
+ .slice(0, 15);
+
+ return (
+
+
Product Catalog
+
+
{t(StrCode.WelcomeMessage)}
+
+
+
+
+
+
+
+
+
+
+ );
+};
diff --git a/src/modules/HomePage/components/CategoryBlock/CategoryBlock.tsx b/src/modules/HomePage/components/CategoryBlock/CategoryBlock.tsx
new file mode 100644
index 0000000000..f1be2092e8
--- /dev/null
+++ b/src/modules/HomePage/components/CategoryBlock/CategoryBlock.tsx
@@ -0,0 +1,92 @@
+import { useTranslation } from 'react-i18next';
+import {
+ CategoryImageStyled,
+ CategoryItemStyled,
+ CategoryTextStyled,
+ CategoryTitleStyled,
+ CategotyStyled,
+ ContainerImgStyled,
+ ContainerStyled,
+ SecondTextStyled,
+} from './styled';
+import { StrCode } from '../../../../utils/enums';
+import { useAppSelector } from '../../../../hooks/hookStore';
+import { useTheme } from '../../../../components/Themes/ThemeProvider';
+
+const CategoryBlock = () => {
+ const { t } = useTranslation();
+ const { phones } = useAppSelector(state => state.phones);
+ const { tables } = useAppSelector(state => state.tables);
+ const { accessories } = useAppSelector(state => state.accessories);
+ const { theme } = useTheme();
+
+ return (
+
+ {t(StrCode.ShopByCategory)}
+
+
+
+
+
+
+
+
+
+ {t(StrCode.MobilePhones)}
+
+
+ {`${phones.length} ${t(StrCode.Models)}`}
+
+
+
+
+
+
+
+
+
+
+ {t(StrCode.Tablets)}
+
+
+ {`${tables.length} ${t(StrCode.Models)}`}
+
+
+
+
+
+
+
+
+
+
+ {t(StrCode.Accessories)}
+
+
+ {`${accessories.length} ${t(StrCode.Models)}`}
+
+
+
+
+ );
+};
+
+export default CategoryBlock;
diff --git a/src/modules/HomePage/components/CategoryBlock/styled.ts b/src/modules/HomePage/components/CategoryBlock/styled.ts
new file mode 100644
index 0000000000..d8c016f47f
--- /dev/null
+++ b/src/modules/HomePage/components/CategoryBlock/styled.ts
@@ -0,0 +1,148 @@
+import styled, { css } from 'styled-components';
+import { media } from '../../../../utils/const';
+import { NavLink } from 'react-router-dom';
+
+const ContainerStyled = styled.div`
+ padding-inline: 16px;
+
+ ${media.desktop} {
+ padding: 0;
+ }
+`;
+
+const CategoryTitleStyled = styled.div`
+ margin-bottom: 24px;
+ font-size: 22px;
+ font-weight: 800;
+ font-family: 'Mont-Bold', sans-serif;
+ line-height: 30.8px;
+ color: ${({ theme }) => theme.textColor};
+
+ ${media.tablet} {
+ font-size: 32px;
+ line-height: 41px;
+ }
+`;
+
+const CategotyStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+
+ ${media.tablet} {
+ width: 100%;
+ flex-direction: row;
+ gap: 16px;
+ }
+`;
+
+const CategoryItemStyled = styled(NavLink)`
+ text-decoration: none;
+ display: flex;
+ gap: 4px;
+ flex-direction: column;
+ border-radius: ${({ theme }) => theme.borderRadius};
+
+ ${media.tablet} {
+ flex: 1;
+ }
+`;
+
+type ImgContainerType = {
+ variang: 'first' | 'second' | 'three';
+};
+
+const ContainerImgStyled = styled.div`
+ width: 100%;
+ position: relative;
+ border-radius: ${({ theme }) => theme.borderRadius};
+
+ ${({ variang }) => {
+ switch (variang) {
+ case 'first': {
+ return css`
+ background-color: #6d6474;
+ padding: 60px 0 0 60px;
+ `;
+ }
+
+ case 'second': {
+ return css`
+ background-color: #8d8d92;
+ padding: 20px 0 0 20px;
+ `;
+ }
+
+ case 'three': {
+ return css`
+ background-color: #973d5f;
+ padding: 60px 0 0 60px;
+ `;
+ }
+ }
+ }}
+
+ &:hover {
+ transform: scale(1.1);
+ }
+`;
+
+const CategoryImageStyled = styled.div`
+ width: 100%;
+ padding-top: 100%;
+ background-image: url('/react_phone-catalog/img/category-phones.webp');
+ background-position: top left;
+ background-repeat: no-repeat;
+ border-radius: ${({ theme }) => theme.borderRadius};
+
+ ${({ variang }) => {
+ switch (variang) {
+ case 'first': {
+ return css`
+ background-image: url('/react_phone-catalog/img/category-phones.webp');
+ background-size: 110%;
+ `;
+ }
+
+ case 'second': {
+ return css`
+ background-image: url('/react_phone-catalog/img/category-tablets.png');
+ background-size: 155%;
+ `;
+ }
+
+ case 'three': {
+ return css`
+ background-image: url('/react_phone-catalog/img/category-accessories.webp');
+ background-size: 130%;
+ `;
+ }
+ }
+ }}
+`;
+
+const CategoryTextStyled = styled.div`
+ font-weight: 700;
+ font-size: 20px;
+ line-height: 25.56px;
+ color: ${({ theme }) => theme.textColor};
+ font-family: 'Mont-SemiBold', sans-serif;
+`;
+
+const SecondTextStyled = styled.div`
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+ color: ${({ theme }) => theme.textSecondColor};
+`;
+
+export {
+ ContainerStyled,
+ CategoryTitleStyled,
+ CategotyStyled,
+ CategoryItemStyled,
+ ContainerImgStyled,
+ CategoryImageStyled,
+ CategoryTextStyled,
+ SecondTextStyled,
+};
diff --git a/src/modules/HomePage/components/PicturesSlider/PicturesSlider.tsx b/src/modules/HomePage/components/PicturesSlider/PicturesSlider.tsx
new file mode 100644
index 0000000000..707a3813dd
--- /dev/null
+++ b/src/modules/HomePage/components/PicturesSlider/PicturesSlider.tsx
@@ -0,0 +1,144 @@
+import { useEffect, useRef, useState } from 'react';
+import productNew from '../../../../icons/ProductSlider/ProductSlide.png';
+import productNew1 from '../../../../icons/ProductSlider/ProductSlide1.png';
+import productNew2 from '../../../../icons/ProductSlider/ProductSlide2.png';
+import banner from '../../../../icons/ProductSlider/Tablet/Banner.png';
+import banner1 from '../../../../icons/ProductSlider/Tablet/Banner1.png';
+import banner2 from '../../../../icons/ProductSlider/Tablet/Banner2.png';
+import {
+ ButStyled,
+ ButtonBlockStyled,
+ ButtonSliderStyled,
+ ButtonsStyled,
+ CarouselStyled,
+ ImglStyled,
+ MainContentStyled,
+ PicturesSliderStyled,
+} from './styled';
+import { VECTOR_SVG } from '../../../../utils/SVG';
+import { Button } from '../../../../components/Button/Button';
+
+export const PicturesSlider = () => {
+ const [caruselPosition, setCarouselPosition] = useState(0);
+ const [itemOnUsed, setIitemOnUsed] = useState([]);
+ const [isTouching, setIsTouching] = useState(false);
+ const touchStartX = useRef(0);
+ const touchEndX = useRef(0);
+
+ const isWitdhGetItem = () => {
+ if (window.innerWidth >= 1200) {
+ return [banner, banner1, banner2];
+ } else if (window.innerWidth >= 640) {
+ return [banner, banner1, banner2];
+ } else {
+ return [productNew, productNew1, productNew2];
+ }
+ };
+
+ useEffect(() => {
+ const itemsToSet = isWitdhGetItem();
+
+ setIitemOnUsed(itemsToSet);
+ }, [window.innerWidth]);
+
+ useEffect(() => {
+ if (!isTouching) {
+ const interval = setInterval(() => {
+ setCarouselPosition(prevPosition => (prevPosition + 1) % 3);
+ }, 5000);
+
+ return () => clearInterval(interval);
+ }
+
+ return;
+ }, [isTouching]);
+
+ const handleTouchStart = (e: React.TouchEvent) => {
+ touchStartX.current = e.targetTouches[0].clientX;
+ setIsTouching(true);
+ };
+
+ const handleTouchEnd = () => {
+ const deltaX = touchEndX.current - touchStartX.current;
+
+ if (deltaX < -50) {
+ if (caruselPosition < 2) {
+ setCarouselPosition(prevIndex => prevIndex + 1);
+ } else {
+ setCarouselPosition(0);
+ }
+ } else if (deltaX > 50) {
+ if (caruselPosition > 0) {
+ setCarouselPosition(prevIndex => prevIndex - 1);
+ } else {
+ setCarouselPosition(2);
+ }
+ }
+
+ setIsTouching(false);
+ };
+
+ const handleTouchMove = (e: React.TouchEvent) => {
+ touchEndX.current = e.targetTouches[0].clientX;
+ };
+
+ const handleLeftSwipe = () => {
+ setCarouselPosition(prevPosition => (prevPosition ? prevPosition - 1 : 2));
+ };
+
+ const handleRightSwipe = () => {
+ setCarouselPosition(prevPosition => (prevPosition + 1) % 3);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ setCarouselPosition(0)}>
+
+
+ setCarouselPosition(1)}>
+
+
+ setCarouselPosition(2)}>
+
+
+
+
+ );
+};
diff --git a/src/modules/HomePage/components/PicturesSlider/styled.ts b/src/modules/HomePage/components/PicturesSlider/styled.ts
new file mode 100644
index 0000000000..75b6208b64
--- /dev/null
+++ b/src/modules/HomePage/components/PicturesSlider/styled.ts
@@ -0,0 +1,95 @@
+import styled from 'styled-components';
+import { media } from '../../../../utils/const';
+
+interface ImglStyledProps {
+ positionTr: number;
+}
+
+interface ButStyledProps {
+ isActive: boolean;
+}
+
+const PicturesSliderStyled = styled.div`
+ ${media.tablet} {
+ padding-inline: 24px;
+ }
+
+ ${media.desktop} {
+ padding-inline: 0;
+ }
+
+ user-select: none;
+`;
+
+const MainContentStyled = styled.div`
+ margin-bottom: 8px;
+
+ ${media.tablet} {
+ display: flex;
+ gap: 16px;
+ height: 100%;
+ }
+`;
+
+const ButtonSliderStyled = styled.div`
+ display: none;
+
+ ${media.tablet} {
+ display: flex;
+ }
+
+ > * {
+ ${media.tablet} {
+ height: 100%;
+ width: 32px;
+ flex-shrink: 0;
+ }
+ }
+`;
+
+const CarouselStyled = styled.div`
+ position: relative;
+ display: flex;
+ overflow: hidden;
+ z-index: 1;
+
+ border-radius: ${({ theme }) => theme.borderRadius};
+`;
+
+const ImglStyled = styled.img`
+ width: 100%;
+ z-index: -1;
+
+ transform: ${({ positionTr }) => `translateX(-${positionTr * 100}%)`};
+`;
+
+const ButtonsStyled = styled.div`
+ width: 100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 4px;
+`;
+
+const ButtonBlockStyled = styled.div`
+ padding: 10px 5px;
+ cursor: pointer;
+`;
+
+const ButStyled = styled.div`
+ width: 14px;
+ height: 4px;
+ background-color: ${({ isActive, theme }) =>
+ isActive ? theme.cardSliderActive : theme.cardSliderNotActive};
+`;
+
+export {
+ CarouselStyled,
+ ImglStyled,
+ ButtonsStyled,
+ ButtonBlockStyled,
+ ButStyled,
+ PicturesSliderStyled,
+ MainContentStyled,
+ ButtonSliderStyled,
+};
diff --git a/src/modules/HomePage/styled.ts b/src/modules/HomePage/styled.ts
new file mode 100644
index 0000000000..0bb7d95f18
--- /dev/null
+++ b/src/modules/HomePage/styled.ts
@@ -0,0 +1,48 @@
+import styled from 'styled-components';
+import { media } from '../../utils/const';
+
+const HomePageStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 56px;
+ padding-bottom: 56px;
+
+ ${media.tablet} {
+ gap: 56px;
+ }
+
+ ${media.desktop} {
+ max-width: 1136px;
+ margin-inline: auto;
+ gap: 80px;
+ padding-bottom: 64px;
+ }
+`;
+
+const TitleStyled = styled.h2`
+ font-weight: 800;
+ font-family: 'Mont-Bold', sans-serif;
+ font-size: 32px;
+ line-height: 41px;
+ margin: 0;
+ padding: 24px 16px;
+ color: ${({ theme }) => theme.textColor};
+ width: 100%;
+
+ ${media.tablet} {
+ padding: 32px 24px;
+ font-size: 48px;
+ line-height: 56px;
+ width: 490px;
+ }
+
+ ${media.desktop} {
+ padding: 56px 0;
+ margin: 0;
+ width: 100%;
+ max-width: 1136px;
+ margin-inline: auto;
+ }
+`;
+
+export { TitleStyled, HomePageStyled };
diff --git a/src/modules/ItemPage/ItemInfo/ItemInfo.tsx b/src/modules/ItemPage/ItemInfo/ItemInfo.tsx
new file mode 100644
index 0000000000..0702c8d106
--- /dev/null
+++ b/src/modules/ItemPage/ItemInfo/ItemInfo.tsx
@@ -0,0 +1,143 @@
+import { useTranslation } from 'react-i18next';
+import { Button } from '../../../components/Button/Button';
+import { useAddCartFavorit } from '../../../hooks/useAddCartFavorit';
+import { AccessoryType } from '../../../types/productsType';
+import { StrCode } from '../../../utils/enums';
+import {
+ BlockStyled,
+ ButtonColorInStyled,
+ ButtonsBlockStyled,
+ ButtonsStyled,
+ ColorButtonStyled,
+ CountBlockStyled,
+ InfoBlockStyled,
+ InfoStyled,
+ ItemInfoStyled,
+ RegularPriceStyled,
+} from './styled';
+import { FAVORIT_SVG, LIKE_SVG } from '../../../utils/SVG';
+import { useLocation, useNavigate, useParams } from 'react-router-dom';
+import { useTheme } from '../../../components/Themes/ThemeProvider';
+
+type Props = {
+ product: AccessoryType;
+};
+
+const ItemInfo: React.FC = ({ product }) => {
+ const { t } = useTranslation();
+ const { handleToBasket, handleToFavorit, includesBacket, includesFavorit } =
+ useAddCartFavorit(product.id);
+ const { pathname } = useLocation();
+ const navigate = useNavigate();
+ const { productId } = useParams();
+ const { theme } = useTheme();
+
+ const changeProduct = (newProductId: string) => {
+ const basePath = pathname.split('/')[1];
+
+ navigate(`/${basePath}/${newProductId}`);
+ };
+
+ const handleReplase = (oldRout: string, newRout: string) => {
+ if (!productId || oldRout === newRout) {
+ return;
+ }
+
+ const formattedOldRout = oldRout.toLowerCase().replace(/\s+/g, '-');
+ const formattedNewRout = newRout.toLowerCase().replace(/\s+/g, '-');
+ const formatParam = productId.replace(formattedOldRout, formattedNewRout);
+
+ changeProduct(formatParam);
+ };
+
+ return (
+
+
+ {t(StrCode.AvailableColor)}
+
+
+ {product.colorsAvailable.map(item => (
+ handleReplase(product.color, item)}
+ key={item}
+ >
+
+
+ ))}
+
+
+
+
+ {t(StrCode.SelectCapacity)}
+
+
+ {product.capacityAvailable.map(item => (
+
+ ))}
+
+
+
+
+ ${product.priceDiscount}
+ ${product.priceRegular}
+
+
+
+
+
+
+
+
+
+
+ {t(StrCode.Screen)}
+ {product.screen}
+
+
+
+ {t(StrCode.Resolution)}
+ {product.resolution}
+
+
+
+ {t(StrCode.Processor)}
+ {product.processor}
+
+
+
+ {t(StrCode.Ram)}
+ {product.ram}
+
+
+
+ );
+};
+
+export default ItemInfo;
diff --git a/src/modules/ItemPage/ItemInfo/styled.ts b/src/modules/ItemPage/ItemInfo/styled.ts
new file mode 100644
index 0000000000..4beb6c6de2
--- /dev/null
+++ b/src/modules/ItemPage/ItemInfo/styled.ts
@@ -0,0 +1,133 @@
+import styled from 'styled-components';
+import { media } from '../../../utils/const';
+
+const ItemInfoStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ width: 100%;
+
+ ${media.tablet} {
+ width: 237px;
+ flex-shrink: 0;
+ }
+
+ ${media.desktop} {
+ width: 320px;
+ margin-right: auto;
+ }
+`;
+
+const BlockStyled = styled.div`
+ width: 100%;
+ color: ${({ theme }) => theme.textSecondColor};
+ font-weight: 600;
+ font-size: 12px;
+ line-height: 15.34px;
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ padding-bottom: 24px;
+ border-bottom: 1px solid ${({ theme }) => theme.optionBorder};
+ margin-bottom: 24px;
+`;
+
+const ButtonsStyled = styled.div`
+ display: flex;
+ gap: 8px;
+ align-items: start;
+`;
+
+type MiniImgProps = {
+ isActive: boolean;
+};
+
+const ColorButtonStyled = styled.div`
+ width: 32px;
+ height: 32px;
+ border: 1px solid
+ ${({ isActive, theme }) =>
+ isActive ? theme.textColor : theme.optionBorder};
+ border-radius: 100%;
+ cursor: pointer;
+
+ &:hover {
+ border: 1px solid
+ ${({ isActive, theme }) =>
+ isActive ? theme.textColor : theme.selectImgColor};
+ }
+`;
+
+const ButtonColorInStyled = styled.div`
+ border: 2px solid ${({ theme }) => theme.bacgroundDefault};
+ width: 100%;
+ height: 100%;
+ border-radius: 100%;
+`;
+
+const CountBlockStyled = styled.div`
+ font-weight: 800;
+ font-size: 32px;
+ line-height: 41px;
+ color: ${({ theme }) => theme.textColor};
+ text-align: left;
+ font-family: 'Mont-Bold', sans-serif;
+ margin-block: 6px 16px;
+
+ display: flex;
+ align-items: center;
+ gap: 8px;
+`;
+
+const RegularPriceStyled = styled.div`
+ font-family: 'Mont-Regular', sans-serif;
+ font-weight: 600;
+ font-size: 22px;
+ line-height: 28.12px;
+ color: ${({ theme }) => theme.textSecondColor};
+ text-decoration: line-through;
+`;
+
+const InfoBlockStyled = styled.div`
+ height: 84px;
+ display: flex;
+ justify-content: space-between;
+ flex-direction: column;
+ color: ${({ theme }) => theme.textSecondColor};
+ font-weight: 700;
+ font-size: 12px;
+ line-height: 15.34px;
+ font-family: 'Mont-SemiBold', sans-serif;
+
+ > * {
+ display: flex;
+ justify-content: space-between;
+ }
+`;
+
+const InfoStyled = styled.div`
+ color: ${({ theme }) => theme.textColor};
+`;
+
+const ButtonsBlockStyled = styled.div`
+ display: flex;
+ gap: 8px;
+
+ & > :last-child {
+ flex-shrink: 0;
+ }
+
+ margin-bottom: 32px;
+`;
+
+export {
+ ItemInfoStyled,
+ BlockStyled,
+ ButtonsStyled,
+ ColorButtonStyled,
+ ButtonColorInStyled,
+ CountBlockStyled,
+ RegularPriceStyled,
+ InfoBlockStyled,
+ InfoStyled,
+ ButtonsBlockStyled,
+};
diff --git a/src/modules/ItemPage/ItemPage.tsx b/src/modules/ItemPage/ItemPage.tsx
new file mode 100644
index 0000000000..570224c58e
--- /dev/null
+++ b/src/modules/ItemPage/ItemPage.tsx
@@ -0,0 +1,162 @@
+import BreadCrumbs from '../_shared/BreadCrumbs/BreadCrumbs';
+import {
+ AboutInfoStyled,
+ AboutSpecsStyled,
+ AboutStyled,
+ ImagesStyled,
+ ImgMiniBlockStyled,
+ InfoSpecsSecStyled,
+ InfoSpecsStyled,
+ ItemPageStyled,
+ MainImgStyled,
+ MainInfoStyled,
+ MiniImg,
+ SpecsStyled,
+ TitleNameStyled,
+ TitleStyled,
+} from './styled';
+import GoBack from '../_shared/GoBack/GoBack';
+import useProduct from '../../hooks/useProduct';
+import { useState } from 'react';
+import ItemInfo from './ItemInfo/ItemInfo';
+import { StrCode } from '../../utils/enums';
+import { useTranslation } from 'react-i18next';
+import ProductsSlider from '../_shared/ProductsSlider/ProductsSlider';
+import { useAppSelector } from '../../hooks/hookStore';
+import { shuffleAndTrimArray } from '../../utils/const';
+import { NotFoundImg, ProductsNotFound } from '../ProductsPage/styled';
+import { Button } from '../../components/Button/Button';
+import { useNavigate } from 'react-router-dom';
+import { Skeleton } from '../_shared/Skeleton/Skeleton';
+
+const ItemPage = () => {
+ const [activeImg, setActiveImg] = useState(0);
+ const { product } = useProduct();
+ const { t } = useTranslation();
+ const { products } = useAppSelector(state => state.products);
+ const productsAlsoLike = shuffleAndTrimArray([...products]);
+ const navigate = useNavigate();
+
+ return (
+
+
+
+
+
+ {!!product && !!products.length ? (
+ <>
+ {product.name}
+
+
+
+
+ {product.images.map((item, index) => (
+ setActiveImg(index)}
+ />
+ ))}
+
+
+
+
+
+
+
+
+
+
+ {t(StrCode.About)}
+
+ {product.description.map(item => (
+
+ {item.title}
+ {item.text}
+
+ ))}
+
+
+
+ {t(StrCode.TechSpecs)}
+
+
+
+ {t(StrCode.Screen)}
+ {product.screen}
+
+
+
+ {t(StrCode.Resolution)}
+ {product.resolution}
+
+
+
+ {t(StrCode.Processor)}
+ {product.processor}
+
+
+
+ {t(StrCode.Ram)}
+ {product.ram}
+
+
+
+ {t(StrCode.BuiltMemory)}
+ {product.capacity}
+
+
+ {!!product.camera && (
+
+ {t(StrCode.Camera)}
+ {product.camera}
+
+ )}
+
+ {!!product.zoom && (
+
+ {t(StrCode.Zoom)}
+ {product.zoom}
+
+ )}
+
+
+ {t(StrCode.Cell)}
+
+ {product.cell.join(', ')}
+
+
+
+
+
+
+
+ >
+ ) : (
+ !products.length &&
+ )}
+
+ {!product && !!products.length && (
+
+ {t(StrCode.ProductNot)}
+
+
+
+
+
+ )}
+
+ );
+};
+
+export default ItemPage;
diff --git a/src/modules/ItemPage/styled.ts b/src/modules/ItemPage/styled.ts
new file mode 100644
index 0000000000..83cb65b725
--- /dev/null
+++ b/src/modules/ItemPage/styled.ts
@@ -0,0 +1,254 @@
+import styled from 'styled-components';
+import { media } from '../../utils/const';
+
+const ItemPageStyled = styled.div`
+ padding: 24px 16px 64px;
+ width: 100%;
+
+ ${media.desktop} {
+ max-width: 1136px;
+ margin-inline: auto;
+ padding: 24px 0 64px;
+ }
+
+ > :first-child {
+ margin-bottom: 24px;
+
+ ${media.tablet} {
+ margin-bottom: 40px;
+ }
+ }
+`;
+
+const TitleStyled = styled.h2`
+ margin: 0;
+ margin-bottom: 32px;
+ font-weight: 800;
+ font-size: 32px;
+ line-height: 41px;
+ font-family: 'Mont-Bold', sans-serif;
+ color: ${({ theme }) => theme.textColor};
+
+ ${media.tablet} {
+ margin-bottom: 40px;
+ font-size: 48px;
+ line-height: 56px;
+ }
+`;
+
+const MainInfoStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 40px;
+ align-items: center;
+ margin-bottom: 56px;
+
+ ${media.tablet} {
+ gap: 17px;
+ flex-direction: row;
+ justify-content: space-between;
+ align-items: start;
+ margin-bottom: 64px;
+ }
+
+ ${media.desktop} {
+ margin-bottom: 80px;
+ }
+`;
+
+const ImagesStyled = styled.div`
+ display: flex;
+ flex-direction: column-reverse;
+ gap: 16px;
+ align-items: center;
+ width: 288px;
+
+ ${media.tablet} {
+ width: 100%;
+ flex-direction: row;
+ align-items: start;
+ }
+
+ ${media.desktop} {
+ width: 560px;
+ }
+`;
+
+const MainImgStyled = styled.img`
+ width: 100%;
+ object-fit: contain;
+
+ &:hover {
+ transform: scale(1.1);
+ }
+
+ ${media.tablet} {
+ width: 70%;
+ min-width: 287px;
+ max-height: 435px;
+ }
+
+ ${media.desktop} {
+ width: 464px;
+ max-height: 465px;
+ }
+`;
+
+const ImgMiniBlockStyled = styled.div`
+ width: 100%;
+ height: 49px;
+ display: flex;
+ gap: 8px;
+ overflow: auto;
+ flex-shrink: 0;
+
+ ${media.tablet} {
+ width: 35px;
+ height: 100%;
+ flex-direction: column;
+ }
+
+ ${media.desktop} {
+ width: 80px;
+ }
+`;
+
+type MiniImgProps = {
+ isActive: boolean;
+};
+
+const MiniImg = styled.img`
+ width: 51px;
+ object-fit: contain;
+ border: 1px solid
+ ${({ isActive, theme }) =>
+ isActive ? theme.textColor : theme.optionBorder};
+
+ cursor: pointer;
+
+ ${media.tablet} {
+ width: 35px;
+ height: 35px;
+ }
+
+ ${media.desktop} {
+ width: 80px;
+ height: 80px;
+ }
+`;
+
+const AboutSpecsStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 56px;
+ margin-bottom: 56px;
+
+ ${media.tablet} {
+ margin-bottom: 64px;
+ gap: 64px;
+ }
+
+ ${media.desktop} {
+ margin-bottom: 80px;
+ flex-direction: row;
+ }
+`;
+
+const AboutStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 32px;
+
+ ${media.desktop} {
+ width: 560px;
+ }
+`;
+
+const SpecsStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 30px;
+
+ ${media.desktop} {
+ width: 512px;
+ }
+`;
+
+const TitleNameStyled = styled.div`
+ padding-bottom: 16px;
+ font-weight: 700;
+ font-size: 20px;
+ line-height: 25.56px;
+ border-bottom: 1px solid ${({ theme }) => theme.optionBorder};
+ font-family: Mont-SemiBold, sans-serif;
+ color: ${({ theme }) => theme.textColor};
+
+ ${media.tablet} {
+ font-family: Mont-Bold, sans-serif;
+ font-weight: 800;
+ font-size: 22px;
+ line-height: 30.8px;
+ }
+`;
+
+const InfoSpecsStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ color: ${({ theme }) => theme.textSecondColor};
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+
+ > * {
+ display: flex;
+ justify-content: space-between;
+ }
+`;
+
+const InfoSpecsSecStyled = styled.div`
+ color: ${({ theme }) => theme.textColor};
+`;
+
+const AboutInfoStyled = styled.div`
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+
+ > *:first-child {
+ font-weight: 700;
+ font-family: Mont-SemiBold, sans-serif;
+ font-size: 16px;
+ line-height: 20.45px;
+ color: ${({ theme }) => theme.textColor};
+
+ ${media.tablet} {
+ font-size: 20px;
+ line-height: 25.56px;
+ }
+ }
+
+ > *:last-child {
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+ color: ${({ theme }) => theme.textSecondColor};
+ }
+`;
+
+export {
+ ItemPageStyled,
+ TitleStyled,
+ MainInfoStyled,
+ ImagesStyled,
+ MainImgStyled,
+ ImgMiniBlockStyled,
+ MiniImg,
+ AboutSpecsStyled,
+ AboutStyled,
+ SpecsStyled,
+ TitleNameStyled,
+ InfoSpecsStyled,
+ InfoSpecsSecStyled,
+ AboutInfoStyled,
+};
diff --git a/src/modules/PageNotFound/PageNotFound.tsx b/src/modules/PageNotFound/PageNotFound.tsx
new file mode 100644
index 0000000000..f426048a13
--- /dev/null
+++ b/src/modules/PageNotFound/PageNotFound.tsx
@@ -0,0 +1,32 @@
+import { useNavigate } from 'react-router-dom';
+import { Button } from '../../components/Button/Button';
+import BreadCrumbs from '../_shared/BreadCrumbs/BreadCrumbs';
+import { NotFoundImg } from '../ProductsPage/styled';
+import { NotFoundStyled } from './styled';
+import { useTranslation } from 'react-i18next';
+import { StrCode } from '../../utils/enums';
+
+const PageNotFound = () => {
+ const navigate = useNavigate();
+ const { t } = useTranslation();
+
+ return (
+
+
+
+ {t(StrCode.PageNotFound)}
+
+
+
+
+
+ );
+};
+
+export default PageNotFound;
diff --git a/src/modules/PageNotFound/styled.ts b/src/modules/PageNotFound/styled.ts
new file mode 100644
index 0000000000..57eb607cbc
--- /dev/null
+++ b/src/modules/PageNotFound/styled.ts
@@ -0,0 +1,29 @@
+import styled from 'styled-components';
+import { media } from '../../utils/const';
+
+const NotFoundStyled = styled.div`
+ padding: 24px 16px 64px;
+ display: flex;
+ flex-direction: column;
+
+ align-items: center;
+ gap: 20px;
+
+ font-size: 32px;
+ line-height: 41px;
+
+ color: ${({ theme }) => theme.textColor};
+
+ ${media.desktop} {
+ max-width: 1136px;
+ min-width: 1136px;
+ margin-inline: auto;
+ padding: 24px 0 64px;
+ }
+
+ & > :first-child {
+ align-self: start;
+ }
+`;
+
+export { NotFoundStyled };
diff --git a/src/modules/ProductsPage/ProductsPage.tsx b/src/modules/ProductsPage/ProductsPage.tsx
new file mode 100644
index 0000000000..d8395a7f96
--- /dev/null
+++ b/src/modules/ProductsPage/ProductsPage.tsx
@@ -0,0 +1,136 @@
+import BreadCrumbs from '../_shared/BreadCrumbs/BreadCrumbs';
+import {
+ ModelsStyled,
+ NotFoundImg,
+ ProductsNotFound,
+ ProductsPageStyled,
+ SelectFixInput,
+ SelectFixSecond,
+ SelectsStyled,
+ TitleStyled,
+} from './styled';
+import { useTranslation } from 'react-i18next';
+import { StrCode } from '../../utils/enums';
+import { SelectInput } from '../../components/Inputs/SelectInput/SelectInput';
+import Pagination from './components/Pagination/Pagination';
+import { useProductPage } from '../../hooks/useProductPage';
+import { Button } from '../../components/Button/Button';
+import { useLocation, useNavigate } from 'react-router-dom';
+import ProductList from '../_shared/ProductList/ProductList';
+
+type Props = {
+ variant: 'phones' | 'tabless' | 'accesories';
+};
+
+const ProductsPage: React.FC = ({ variant }) => {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const location = useLocation();
+
+ const handleGoBack = () => {
+ const basePath = location.pathname;
+
+ navigate(basePath);
+ };
+
+ const {
+ productsVariantLength,
+ productsLength,
+ valueSort,
+ updateSort,
+ valuePerPage,
+ updatePerPage,
+ sordedProduct,
+ currentPage,
+ updateCurrentPage,
+ } = useProductPage(variant);
+
+ const firstTitleH1 = {
+ phones: 'Phones page',
+ tabless: 'Tablets page',
+ accesories: 'Accessories page',
+ };
+
+ const productNoYet = {
+ phones: t(StrCode.NotPhones),
+ tabless: t(StrCode.NotTablets),
+ accesories: t(StrCode.NotAccessories),
+ };
+
+ const sortBy = [
+ t(StrCode.SortAge),
+ t(StrCode.SortName),
+ t(StrCode.SortPrice),
+ ];
+ const itemOnPage = ['4', '8', '16', 'all'];
+
+ const titleName = {
+ phones: t(StrCode.MobilePhones),
+ tabless: t(StrCode.Tablets),
+ accesories: t(StrCode.Accessories),
+ };
+
+ return (
+
+
{firstTitleH1[variant]}
+
+
+
+
+ {titleName[variant]}
+
+ {`${productsLength} ${t(StrCode.Models)}`}
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {productsVariantLength !== 0 && sordedProduct.length === 0 && (
+
+ {productNoYet[variant]}
+
+
+
+
+
+ )}
+
+
+
+
+ );
+};
+
+export default ProductsPage;
diff --git a/src/modules/ProductsPage/components/Pagination/Pagination.tsx b/src/modules/ProductsPage/components/Pagination/Pagination.tsx
new file mode 100644
index 0000000000..21d60e66ad
--- /dev/null
+++ b/src/modules/ProductsPage/components/Pagination/Pagination.tsx
@@ -0,0 +1,169 @@
+import { Button } from '../../../../components/Button/Button';
+import { useTheme } from '../../../../components/Themes/ThemeProvider';
+import { VECTOR_SVG } from '../../../../utils/SVG';
+import { ListItemStyled, PaginationStyled } from './styled';
+
+type Props = {
+ total: number;
+ perPage: string;
+ currentPage: number;
+ onPageChange: (value: number) => void;
+};
+
+const Pagination: React.FC = ({
+ total,
+ perPage,
+ currentPage,
+ onPageChange,
+}) => {
+ const { theme } = useTheme();
+
+ if (perPage === 'all' || total <= Number(perPage)) {
+ return <>>;
+ }
+
+ const handleChangePage = (num: number) => {
+ onPageChange(num);
+
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth',
+ });
+ };
+
+ const pageCount = Array.from(
+ { length: Math.ceil(total / Number(perPage)) },
+ (_, i) => i + 1,
+ );
+
+ const handleLeftClick = () => {
+ if (currentPage !== 1) {
+ handleChangePage(currentPage - 1);
+ }
+ };
+
+ const handleRightClick = () => {
+ if (currentPage < pageCount[pageCount.length - 1]) {
+ handleChangePage(currentPage + 1);
+ }
+ };
+
+ return (
+
+
+
+
+
+ {pageCount.length <= 5 ? (
+ <>
+ {pageCount.map(item => (
+
+
+
+ ))}
+ >
+ ) : (
+ <>
+
+
+
+
+ {currentPage > 3 && ...
}
+
+ {(() => {
+ let startPage: number;
+ let endPage: number;
+
+ if (currentPage === 1 || currentPage === 2) {
+ startPage = 2;
+ endPage = 4;
+ } else if (
+ currentPage === pageCount[pageCount.length - 1] ||
+ currentPage === pageCount[pageCount.length - 2]
+ ) {
+ startPage = pageCount[pageCount.length - 4];
+ endPage = pageCount[pageCount.length - 2];
+ } else {
+ startPage = currentPage - 1;
+ endPage = currentPage + 1;
+ }
+
+ return Array.from(
+ { length: endPage - startPage + 1 },
+ (_, i) => startPage + i,
+ ).map(page => (
+
+
+
+ ));
+ })()}
+
+ {currentPage < pageCount[pageCount.length - 3] && (
+ ...
+ )}
+
+
+
+
+ >
+ )}
+
+
+
+
+
+ );
+};
+
+export default Pagination;
diff --git a/src/modules/ProductsPage/components/Pagination/styled.ts b/src/modules/ProductsPage/components/Pagination/styled.ts
new file mode 100644
index 0000000000..2cc87162bf
--- /dev/null
+++ b/src/modules/ProductsPage/components/Pagination/styled.ts
@@ -0,0 +1,23 @@
+import styled from 'styled-components';
+import { media } from '../../../../utils/const';
+
+const PaginationStyled = styled.ul`
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ display: flex;
+ justify-content: center;
+ gap: 8px;
+ margin-top: 24px;
+ flex-wrap: wrap;
+
+ ${media.tablet} {
+ margin-top: 40px;
+ }
+`;
+
+const ListItemStyled = styled.li`
+ flex-shrink: 0;
+`;
+
+export { PaginationStyled, ListItemStyled };
diff --git a/src/modules/ProductsPage/components/ProductsList/ProductsList.tsx b/src/modules/ProductsPage/components/ProductsList/ProductsList.tsx
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/modules/ProductsPage/components/ProductsList/styled.ts b/src/modules/ProductsPage/components/ProductsList/styled.ts
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/src/modules/ProductsPage/styled.ts b/src/modules/ProductsPage/styled.ts
new file mode 100644
index 0000000000..98e25996c1
--- /dev/null
+++ b/src/modules/ProductsPage/styled.ts
@@ -0,0 +1,88 @@
+import styled from 'styled-components';
+import { media } from '../../utils/const';
+
+const ProductsPageStyled = styled.div`
+ padding: 24px 16px 64px;
+
+ ${media.desktop} {
+ max-width: 1136px;
+ margin-inline: auto;
+ padding: 24px 0 64px;
+ }
+`;
+
+const TitleStyled = styled.h2`
+ margin: 24px 0 8px;
+ font-weight: 800;
+ font-size: 32px;
+ line-height: 41px;
+ font-family: 'Mont-Bold', sans-serif;
+ color: ${({ theme }) => theme.textColor};
+
+ ${media.tablet} {
+ font-size: 48px;
+ line-height: 56px;
+ margin: 40px 0 8px;
+ }
+`;
+
+const ModelsStyled = styled.div`
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+ color: ${({ theme }) => theme.textSecondColor};
+ margin-bottom: 32px;
+`;
+
+const SelectsStyled = styled.div`
+ display: flex;
+ gap: 16px;
+
+ width: 100%;
+`;
+
+const SelectFixInput = styled.div`
+ max-width: 187px;
+ width: 100%;
+
+ ${media.desktop} {
+ max-width: 176px;
+ }
+`;
+
+const SelectFixSecond = styled.div`
+ flex-shrink: 0.2;
+ width: 136px;
+
+ ${media.desktop} {
+ width: 128px;
+ }
+`;
+
+const ProductsNotFound = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ gap: 20px;
+ text-align: center;
+
+ font-size: 32px;
+ line-height: 41px;
+
+ color: ${({ theme }) => theme.textColor};
+`;
+
+const NotFoundImg = styled.img`
+ width: 100%;
+`;
+
+export {
+ ProductsPageStyled,
+ TitleStyled,
+ ModelsStyled,
+ SelectsStyled,
+ SelectFixInput,
+ SelectFixSecond,
+ ProductsNotFound,
+ NotFoundImg,
+};
diff --git a/src/modules/_shared/BreadCrumbs/BreadCrumbs.tsx b/src/modules/_shared/BreadCrumbs/BreadCrumbs.tsx
new file mode 100644
index 0000000000..f505ea1805
--- /dev/null
+++ b/src/modules/_shared/BreadCrumbs/BreadCrumbs.tsx
@@ -0,0 +1,88 @@
+import { useLocation, useNavigate, useParams } from 'react-router-dom';
+import { BreadCrumbsStyled, ContainerSVGStyled, CrumbStyled } from './styled';
+import { HOME_SVG, VECTOR_SVG } from '../../../utils/SVG';
+import { useTranslation } from 'react-i18next';
+import { StrCode } from '../../../utils/enums';
+import { useEffect, useState } from 'react';
+
+const BreadCrumbs = () => {
+ const { pathname } = useLocation();
+ const { productId } = useParams();
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+
+ const basePath = pathname.split('/').slice(0, 2).join('/');
+
+ const adressName = () => {
+ switch (basePath) {
+ case '/phones':
+ return t(StrCode.Phones);
+ case '/tablets':
+ return t(StrCode.Tablets);
+ case '/accessories':
+ return t(StrCode.Accessories);
+ case '/favorites':
+ return t(StrCode.Favourites);
+ default:
+ return 'Page not found!';
+ }
+ };
+
+ const navigateTo = (aress: string) => {
+ switch (aress) {
+ case t(StrCode.Phones):
+ navigate('/phones');
+ break;
+ case t(StrCode.Tablets):
+ navigate('/tablets');
+ break;
+ case t(StrCode.Accessories):
+ navigate('/accessories');
+ break;
+ default:
+ break;
+ }
+ };
+
+ const formatProductName = (productName: string) => {
+ const formattedName = productName.replace(/-/g, ' ');
+
+ return formattedName.charAt(0).toUpperCase() + formattedName.slice(1);
+ };
+
+ const itemsUsed = !productId
+ ? [adressName()]
+ : [adressName(), formatProductName(productId)];
+
+ const [itemUsed, setItemUsed] = useState(itemsUsed);
+
+ useEffect(() => {
+ setItemUsed(
+ !productId
+ ? [adressName()]
+ : [adressName(), formatProductName(productId)],
+ );
+ }, [t, pathname, productId]);
+
+ return (
+
+ navigate('/')}>
+
+
+
+
+ {itemUsed.map(item => (
+ <>
+
+
+
+
+ navigateTo(item)}>{item}
+ >
+ ))}
+
+
+ );
+};
+
+export default BreadCrumbs;
diff --git a/src/modules/_shared/BreadCrumbs/styled.ts b/src/modules/_shared/BreadCrumbs/styled.ts
new file mode 100644
index 0000000000..0fe0f490ad
--- /dev/null
+++ b/src/modules/_shared/BreadCrumbs/styled.ts
@@ -0,0 +1,52 @@
+import styled from 'styled-components';
+
+const BreadCrumbsStyled = styled.div`
+ display: flex;
+ gap: 8px;
+ align-items: center;
+
+ svg {
+ fill: ${({ theme }) => theme.buttonSecondColor} !important;
+ }
+
+ & > :first-child {
+ cursor: pointer;
+ }
+`;
+
+const CrumbStyled = styled.div`
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ font-weight: 600;
+ font-size: 12px;
+ color: ${({ theme }) => theme.textColor};
+ user-select: none;
+
+ svg {
+ fill: ${({ theme }) => theme.buttonSecondNotColor} !important;
+ }
+
+ & > :nth-child(2) {
+ cursor: pointer;
+
+ &:hover {
+ color: ${({ theme }) => theme.textColor};
+ }
+ }
+
+ & > :last-child {
+ color: ${({ theme }) => theme.textSecondColor};
+ cursor: default;
+ }
+`;
+
+const ContainerSVGStyled = styled.div`
+ width: 16px;
+ height: 16px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+`;
+
+export { BreadCrumbsStyled, CrumbStyled, ContainerSVGStyled };
diff --git a/src/modules/_shared/GoBack/GoBack.tsx b/src/modules/_shared/GoBack/GoBack.tsx
new file mode 100644
index 0000000000..c36cf29170
--- /dev/null
+++ b/src/modules/_shared/GoBack/GoBack.tsx
@@ -0,0 +1,31 @@
+import { useTranslation } from 'react-i18next';
+import { BackStyled } from './styled';
+import { useNavigate } from 'react-router-dom';
+import { VECTOR_SVG } from '../../../utils/SVG';
+import { StrCode } from '../../../utils/enums';
+
+const GoBack = () => {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+
+ const handleBack = () => {
+ if (
+ document.referrer &&
+ document.referrer.includes(window.location.hostname)
+ ) {
+ navigate(-1);
+ } else {
+ navigate('/');
+ }
+ };
+
+ return (
+
+
+
+ {t(StrCode.Back)}
+
+ );
+};
+
+export default GoBack;
diff --git a/src/modules/_shared/GoBack/styled.ts b/src/modules/_shared/GoBack/styled.ts
new file mode 100644
index 0000000000..61b07ba977
--- /dev/null
+++ b/src/modules/_shared/GoBack/styled.ts
@@ -0,0 +1,35 @@
+import styled from 'styled-components';
+import { media } from '../../../utils/const';
+
+const BackStyled = styled.div`
+ font-weight: 700;
+ font-size: 12px;
+ line-height: 15.34px;
+ font-family: Mont-SemiBold, sans-serif;
+ cursor: pointer;
+ width: 120px;
+
+ color: ${({ theme }) => theme.footerButton};
+
+ display: flex;
+ align-items: center;
+ gap: 4px;
+
+ &:hover {
+ color: ${({ theme }) => theme.buttonHoverBacground};
+ }
+
+ svg {
+ fill: ${({ theme }) => theme.textColor} !important;
+ padding: 3.33px 5.33px;
+ box-sizing: content-box;
+ }
+
+ margin-bottom: 24px;
+
+ ${media.tablet} {
+ margin-bottom: 16px;
+ }
+`;
+
+export { BackStyled };
diff --git a/src/modules/_shared/ProductList/ProductList.tsx b/src/modules/_shared/ProductList/ProductList.tsx
new file mode 100644
index 0000000000..cdbc8c5027
--- /dev/null
+++ b/src/modules/_shared/ProductList/ProductList.tsx
@@ -0,0 +1,24 @@
+import { ProductType } from '../../../types/productsType';
+import ProductCard from '../productCard/ProductCard';
+import { ProductListStyled } from './styled';
+
+type Props = {
+ productLength: number;
+ products: ProductType[];
+};
+
+const ProductList: React.FC = ({ productLength, products }) => {
+ return (
+
+ {productLength !== 0
+ ? products.map(item => (
+
+ ))
+ : [1, 2, 3, 4].map(item => (
+
+ ))}
+
+ );
+};
+
+export default ProductList;
diff --git a/src/modules/_shared/ProductList/styled.ts b/src/modules/_shared/ProductList/styled.ts
new file mode 100644
index 0000000000..55ed7b3a0c
--- /dev/null
+++ b/src/modules/_shared/ProductList/styled.ts
@@ -0,0 +1,10 @@
+import styled from 'styled-components';
+
+const ProductListStyled = styled.div`
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(229px, 1fr));
+ gap: 40px 16px;
+ margin-top: 24px;
+`;
+
+export { ProductListStyled };
diff --git a/src/modules/_shared/ProductsSlider/ProductsSlider.tsx b/src/modules/_shared/ProductsSlider/ProductsSlider.tsx
new file mode 100644
index 0000000000..73c254d331
--- /dev/null
+++ b/src/modules/_shared/ProductsSlider/ProductsSlider.tsx
@@ -0,0 +1,64 @@
+import {
+ ButtonsBlockStyled,
+ ItemsStyled,
+ ProductStyled,
+ TitleStyled,
+} from './styled';
+import { Button } from '../../../components/Button/Button';
+import { VECTOR_SVG } from '../../../utils/SVG';
+import { ProductType } from '../../../types/productsType';
+import ProductCard from '../productCard/ProductCard';
+import useScrollButtons from '../../../hooks/useScrollButtons';
+
+type Props = {
+ name: string;
+ products: ProductType[];
+};
+
+const ProductsSlider: React.FC = ({ name, products = [] }) => {
+ const {
+ scrollRef,
+ canScrollLeft,
+ canScrollRight,
+ handleScrollLeft,
+ handleScrollRight,
+ } = useScrollButtons();
+
+ return (
+
+
+ {name}
+
+
+
+
+
+
+
+
+
+ {products.length === 0
+ ? [1, 2, 3, 4, 5, 6].map(item => (
+
+ ))
+ : products.map(item => (
+
+ ))}
+
+
+ );
+};
+
+export default ProductsSlider;
diff --git a/src/modules/_shared/ProductsSlider/styled.ts b/src/modules/_shared/ProductsSlider/styled.ts
new file mode 100644
index 0000000000..6a9e8733d5
--- /dev/null
+++ b/src/modules/_shared/ProductsSlider/styled.ts
@@ -0,0 +1,56 @@
+import styled from 'styled-components';
+import { media } from '../../../utils/const';
+
+const ProductStyled = styled.div`
+ display: flex;
+ justify-content: space-between;
+ color: ${({ theme }) => theme.textColor};
+ padding-inline: 16px;
+ margin-bottom: 24px;
+
+ ${media.desktop} {
+ padding: 0;
+ }
+
+ > * {
+ width: 50%;
+ }
+`;
+
+const TitleStyled = styled.h2`
+ font-weight: 800;
+ font-family: 'Mont-Bold', sans-serif;
+ font-size: 22px;
+ line-height: 30.8px;
+ margin: 0;
+
+ ${media.tablet} {
+ font-size: 32px;
+ line-height: 41px;
+ }
+`;
+
+const ButtonsBlockStyled = styled.div`
+ display: flex;
+ justify-content: right;
+ align-items: center;
+ gap: 16px;
+`;
+
+const ItemsStyled = styled.div`
+ display: flex;
+ overflow: hidden;
+ overflow-x: scroll;
+ gap: 16px;
+ padding-inline: 16px;
+
+ ${media.desktop} {
+ padding: 0;
+ }
+
+ &::-webkit-scrollbar {
+ display: none;
+ }
+`;
+
+export { TitleStyled, ProductStyled, ButtonsBlockStyled, ItemsStyled };
diff --git a/src/modules/_shared/Skeleton/Skeleton.tsx b/src/modules/_shared/Skeleton/Skeleton.tsx
new file mode 100644
index 0000000000..1cb6778c45
--- /dev/null
+++ b/src/modules/_shared/Skeleton/Skeleton.tsx
@@ -0,0 +1,35 @@
+import styled, { keyframes } from 'styled-components';
+
+const shimmer = keyframes`
+ 0% {
+ background-position: -200px 0;
+ }
+ 100% {
+ background-position: calc(200px + 100%) 0;
+ }
+`;
+
+const SkeletonWrapper = styled.div`
+ display: inline-block;
+ width: 100%;
+ height: 100%;
+ background-color: #e0e0e0;
+ background-image: linear-gradient(
+ 90deg,
+ #e0e0e0 0px,
+ #f0f0f0 40px,
+ #e0e0e0 80px
+ );
+ background-size: 200px 100%;
+ background-repeat: no-repeat;
+ border-radius: 4px;
+ animation: ${shimmer} 1.5s infinite linear;
+`;
+
+export const Skeleton: React.FC<{
+ width?: string;
+ height?: string;
+ flexSrink?: string;
+}> = ({ width, height }) => {
+ return ;
+};
diff --git a/src/modules/_shared/productCard/ProductCard.tsx b/src/modules/_shared/productCard/ProductCard.tsx
new file mode 100644
index 0000000000..828698d629
--- /dev/null
+++ b/src/modules/_shared/productCard/ProductCard.tsx
@@ -0,0 +1,122 @@
+import {
+ ButtonsBlockStyled,
+ CardStyled,
+ CountBlockStyled,
+ ImgStyled,
+ InfoBlockStyled,
+ InfoStyled,
+ NameBlockStyled,
+ RegularPriceStyled,
+ SkeletonImg,
+} from './styled';
+import { Button } from '../../../components/Button/Button';
+import { FAVORIT_SVG, LIKE_SVG } from '../../../utils/SVG';
+import { useTranslation } from 'react-i18next';
+import { StrCode } from '../../../utils/enums';
+import { ProductType } from '../../../types/productsType';
+import { Skeleton } from '../Skeleton/Skeleton';
+import { useAddCartFavorit } from '../../../hooks/useAddCartFavorit';
+import { useNavigate } from 'react-router-dom';
+import { useTheme } from '../../../components/Themes/ThemeProvider';
+
+type Props = {
+ variant: 'HomePage' | 'ListPage';
+ product?: ProductType;
+};
+
+const ProductCard: React.FC = ({ variant, product }) => {
+ const { t } = useTranslation();
+ const navigate = useNavigate();
+ const { theme } = useTheme();
+
+ const { handleToBasket, handleToFavorit, includesBacket, includesFavorit } =
+ useAddCartFavorit(product?.itemId);
+
+ const handleViewItem = () => {
+ if (!product) {
+ return;
+ }
+
+ navigate(`/${product.category}/${product.itemId}`);
+ };
+
+ return (
+
+ {product ? (
+ <>
+
+
+ {product.name}
+
+
+ ${product.price}
+ ${product.fullPrice}
+
+
+
+
+ {t(StrCode.Screen)}
+ {product.screen}
+
+
+
+ {t(StrCode.Capacity)}
+ {product.capacity}
+
+
+
+ {t(StrCode.Ram)}
+ {product.ram}
+
+
+
+
+
+
+
+
+ >
+ ) : (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ );
+};
+
+export default ProductCard;
diff --git a/src/modules/_shared/productCard/styled.tsx b/src/modules/_shared/productCard/styled.tsx
new file mode 100644
index 0000000000..1f288b2196
--- /dev/null
+++ b/src/modules/_shared/productCard/styled.tsx
@@ -0,0 +1,185 @@
+import styled, { css } from 'styled-components';
+import { media } from '../../../utils/const';
+
+type CardProps = {
+ variant: 'HomePage' | 'ListPage';
+};
+
+const CardStyled = styled.div`
+ border: 1px solid ${({ theme }) => theme.cardBorder};
+ background-color: ${({ theme }) => theme.cardBacground};
+ padding: 32px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ cursor: pointer;
+ border-radius: ${({ theme }) => theme.borderRadius};
+
+ ${({ variant }) => {
+ switch (variant) {
+ case 'HomePage': {
+ return css`
+ width: 212px;
+ height: 439px;
+
+ ${media.tablet} {
+ width: 237px;
+ height: 512px;
+ }
+
+ ${media.desktop} {
+ width: 272px;
+ height: 506px;
+ }
+ `;
+ }
+
+ case 'ListPage': {
+ return css`
+ height: 440px;
+
+ ${media.tablet} {
+ height: 506px;
+ }
+ `;
+ }
+ }
+ }}
+
+ &:hover {
+ box-shadow: 0px 2px 16px 0px rgba(0, 0, 0, 0.1);
+ border: 1px solid ${({ theme }) => theme.cardBorderHover};
+ }
+`;
+
+const ImgStyled = styled.img`
+ width: 100%;
+ object-fit: contain;
+ align-self: center;
+
+ ${({ variant }) => {
+ switch (variant) {
+ case 'HomePage': {
+ return css`
+ width: 148px;
+ height: 129px;
+
+ ${media.tablet} {
+ width: 173px;
+ height: 202px;
+ }
+
+ ${media.desktop} {
+ width: 208px;
+ height: 196px;
+ }
+ `;
+ }
+
+ case 'ListPage': {
+ return css`
+ height: 130px;
+
+ ${media.tablet} {
+ height: 196px;
+ }
+ `;
+ }
+ }
+ }}
+
+ &:hover {
+ transform: scale(1.1);
+ }
+`;
+
+const NameBlockStyled = styled.div`
+ font-weight: 600;
+ font-size: 14px;
+ line-height: 21px;
+ color: ${({ theme }) => theme.textColor};
+ text-align: center;
+ padding-top: 16px;
+`;
+
+const CountBlockStyled = styled.div`
+ font-weight: 800;
+ font-size: 22px;
+ line-height: 30.8px;
+ color: ${({ theme }) => theme.textColor};
+ text-align: left;
+ font-family: 'Mont-Bold', sans-serif;
+
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ padding-bottom: 8px;
+
+ border-bottom: 1px solid ${({ theme }) => theme.optionBorder};
+`;
+
+const RegularPriceStyled = styled.div`
+ font-family: 'Mont-Regular', sans-serif;
+ font-weight: 600;
+ line-height: 28.12px;
+ color: ${({ theme }) => theme.textSecondColor};
+ text-decoration: line-through;
+`;
+
+const InfoBlockStyled = styled.div`
+ padding-block: 8px;
+ height: 77px;
+ display: flex;
+ justify-content: space-between;
+ flex-direction: column;
+ color: ${({ theme }) => theme.textSecondColor};
+ font-weight: 700;
+ font-size: 12px;
+ line-height: 15.34px;
+ font-family: 'Mont-SemiBold', sans-serif;
+
+ > * {
+ display: flex;
+ justify-content: space-between;
+ }
+`;
+
+const InfoStyled = styled.div`
+ color: ${({ theme }) => theme.textColor};
+`;
+
+const ButtonsBlockStyled = styled.div`
+ display: flex;
+ justify-content: space-between;
+ gap: 8px;
+
+ & > :last-child {
+ flex-shrink: 0;
+ }
+`;
+
+const ImgFavoriteStyled = styled.img`
+ width: 15.34px;
+ object-fit: cover;
+`;
+
+const SkeletonImg = styled.div`
+ height: 130px;
+
+ ${media.tablet} {
+ height: 196px;
+ }
+`;
+
+export {
+ CardStyled,
+ ImgStyled,
+ NameBlockStyled,
+ CountBlockStyled,
+ RegularPriceStyled,
+ InfoBlockStyled,
+ InfoStyled,
+ ButtonsBlockStyled,
+ ImgFavoriteStyled,
+ SkeletonImg,
+};
diff --git a/src/types/index.d.ts b/src/types/index.d.ts
new file mode 100644
index 0000000000..e42e722ae7
--- /dev/null
+++ b/src/types/index.d.ts
@@ -0,0 +1,6 @@
+declare module '*.module.scss';
+declare module '*.png';
+declare module 'redux-persist/lib/storage' {
+ const storage: any;
+ export default storage;
+}
diff --git a/src/types/productsType.ts b/src/types/productsType.ts
new file mode 100644
index 0000000000..ca94f60a14
--- /dev/null
+++ b/src/types/productsType.ts
@@ -0,0 +1,89 @@
+export type AccessoryType = {
+ 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;
+ cell: string[];
+ camera?: string;
+ zoom?: string;
+};
+
+export type PhoneType = {
+ 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[];
+};
+
+export type ProductType = {
+ id: number;
+ category: string;
+ itemId: string;
+ name: string;
+ fullPrice: number;
+ price: number;
+ screen: string;
+ capacity: string;
+ color: string;
+ ram: string;
+ year: number;
+ image: string;
+};
+
+export type TabletType = {
+ 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/utils/SVG.tsx b/src/utils/SVG.tsx
new file mode 100644
index 0000000000..ec5df4b3e9
--- /dev/null
+++ b/src/utils/SVG.tsx
@@ -0,0 +1,159 @@
+/* eslint-disable */
+
+type Props = {
+ variant?: 'right' | 'top' | 'bottom' | 'left';
+};
+
+export const VECTOR_SVG: React.FC = ({ variant = 'right' }) => {
+ const variantTransform = () => {
+ switch (variant) {
+ case 'right':
+ return {
+ transform: 'rotate(0deg)',
+ transformOrigin: 'center',
+ };
+
+ case 'top':
+ return {
+ transform: 'rotate(-90deg)',
+ transformOrigin: 'center',
+ };
+
+ case 'bottom':
+ return {
+ transform: 'rotate(90deg)',
+ transformOrigin: 'center',
+ };
+
+ case 'left':
+ return {
+ transform: 'rotate(180deg)',
+ transformOrigin: 'center',
+ };
+
+ default:
+ return {};
+ }
+ };
+
+ return (
+
+ );
+};
+
+export const LIKE_SVG = () => (
+
+);
+
+export const SHOPPING_SVG = () => (
+
+);
+
+export const BURGERMENU_SVG = () => (
+
+);
+
+export const CLOSING_SVG = () => (
+
+)
+
+export const FAVORIT_SVG: React.FC<{fill: string}> = ({ fill }) => (
+
+)
+
+export const HOME_SVG = () => (
+
+);
+
+export const SEARCH_SVG = () => (
+
+);
+
+export const MINUS_SVG = () => (
+
+);
+
+export const PLUS_SVG = () => (
+
+);
diff --git a/src/utils/const.ts b/src/utils/const.ts
new file mode 100644
index 0000000000..82731b883d
--- /dev/null
+++ b/src/utils/const.ts
@@ -0,0 +1,16 @@
+export const media = {
+ tablet: '@media (min-width: 640px)',
+ desktop: '@media (min-width: 1200px)',
+};
+
+export function shuffleAndTrimArray(arr: any[]) {
+ const arrayCopy = [...arr];
+
+ for (let i = arrayCopy.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+
+ [arrayCopy[i], arrayCopy[j]] = [arrayCopy[j], arrayCopy[i]];
+ }
+
+ return arrayCopy.slice(0, 15);
+}
diff --git a/src/utils/enums.ts b/src/utils/enums.ts
new file mode 100644
index 0000000000..e22ef241dd
--- /dev/null
+++ b/src/utils/enums.ts
@@ -0,0 +1,62 @@
+export enum StrCode {
+ WelcomeMessage = 'WELCOME_MESSAGE',
+ NewModels = 'NEW_MODELS',
+ Screen = 'SCREEN',
+ Capacity = 'CAPACITY',
+ Ram = 'RAM',
+ AddToCard = 'ADD_TO_CART',
+ ShopByCategory = 'SHOP_BY_CATEGORY',
+ MobilePhones = 'MOBILE_PHONES',
+ Models = 'MODELS',
+ Tablets = 'TABLETS',
+ Accessories = 'ACCESSORIES',
+ HotPrices = 'HOT_PRICES',
+ Contacts = 'CONTACTS',
+ Rights = 'RIGHTS',
+ BackToTop = 'BACK_TO_TOP',
+ Theme = 'THEME',
+ Language = 'LANGUAGE',
+ Home = 'HOME',
+ Phones = 'PHONES',
+ Added = 'ADDED',
+ SortBy = 'SORT_BY',
+ ItemsOnPage = 'ITEMS_ON_PAGE',
+ SortAge = 'SORT_AGE',
+ SortName = 'SORT_NAME',
+ SortPrice = 'SORT_PRICE',
+ NotPhones = 'NOT_PHONES',
+ NotTablets = 'NOT_TABLETS',
+ NotAccessories = 'NOT_ACCESSORIES',
+ GoBack = 'GO_BACK',
+ PageNotFound = 'PAGE_NOT_FOUND',
+ GoHome = 'GO_HOME',
+ SearchText = 'SEARCH_TEXT',
+ Favourites = 'FAVOURITES',
+ NotSearch = 'NO_SEARCH',
+ SeeAll = 'SEE_ALL',
+ SeePhones = 'SEE_PHONES',
+ SeeTablets = 'SEE_TABLETS',
+ SeeAccesories = 'SEE_ACCESORIES',
+ Clear = 'CLEAR',
+ NotFavourites = 'NOT_FAVOURITES',
+ NotCart = 'NOT_CART',
+ Back = 'BACK',
+ Cart = 'CART',
+ TotalFor = 'TOTAL_FOR',
+ Items = 'ITEMS',
+ Checkout = 'CHECKOUT',
+ CheckoutCart = 'CART_CHECKOUT_MSG',
+ Confirm = 'CONFIRM',
+ Cancel = 'CANCEL',
+ AvailableColor = 'AVAILABLE_COLORS',
+ SelectCapacity = 'SELECT_CAPACITY',
+ Resolution = 'RESOLUTION',
+ Processor = 'PROCESSOR',
+ About = 'ABOUT',
+ TechSpecs = 'TECH_SPECS',
+ BuiltMemory = 'BUILT_IN_MEMORY',
+ Cell = 'CELL',
+ Camera = 'CAMERA',
+ Zoom = 'ZOOM',
+ ProductNot = 'PRODUCT_NOT',
+}
diff --git a/src/utils/language.ts b/src/utils/language.ts
new file mode 100644
index 0000000000..d5144c49c2
--- /dev/null
+++ b/src/utils/language.ts
@@ -0,0 +1,21 @@
+import i18n from 'i18next';
+import { initReactI18next } from 'react-i18next';
+import Backend from 'i18next-http-backend';
+import LanguageDetector from 'i18next-browser-languagedetector';
+
+i18n
+ .use(Backend)
+ .use(LanguageDetector)
+ .use(initReactI18next)
+ .init({
+ fallbackLng: 'en',
+ debug: false,
+ interpolation: {
+ escapeValue: false,
+ },
+ backend: {
+ loadPath: './locales/{{lng}}/translation.json',
+ },
+ });
+
+export default i18n;