diff --git a/package-lock.json b/package-lock.json
index 3f3efaac38..bbb65d9d44 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2121,9 +2121,9 @@
}
},
"@mate-academy/scripts": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.2.0.tgz",
- "integrity": "sha512-tQJkIK7KlRHSXkNiI79yGoSFAocFFY765RHOvCoPjCmLjLWeKVx9MY0x2rFMnnneg83rvcvGdVg9DvvUqRXfag==",
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@mate-academy/scripts/-/scripts-1.2.8.tgz",
+ "integrity": "sha512-MqvuqrG8UUzQkRc375ZUIOd23nJ0BYqae/Nn5t01aDutSqZnz1ye65W4sLHiSuQJGIuHRO0CEyJxAO72wX1efw==",
"dev": true,
"requires": {
"@octokit/rest": "^17.11.2",
@@ -2264,12 +2264,12 @@
},
"dependencies": {
"@octokit/types": {
- "version": "6.34.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
- "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
+ "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": "^11.2.0"
+ "@octokit/openapi-types": "^12.11.0"
}
}
}
@@ -2300,12 +2300,12 @@
},
"dependencies": {
"@octokit/types": {
- "version": "6.34.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
- "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
+ "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": "^11.2.0"
+ "@octokit/openapi-types": "^12.11.0"
}
},
"is-plain-object": {
@@ -2334,12 +2334,12 @@
},
"dependencies": {
"@octokit/types": {
- "version": "6.34.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
- "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
+ "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": "^11.2.0"
+ "@octokit/openapi-types": "^12.11.0"
}
},
"universal-user-agent": {
@@ -2351,27 +2351,27 @@
}
},
"@octokit/openapi-types": {
- "version": "11.2.0",
- "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz",
- "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==",
+ "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.17.0",
- "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.17.0.tgz",
- "integrity": "sha512-tzMbrbnam2Mt4AhuyCHvpRkS0oZ5MvwwcQPYGtMv4tUa5kkzG58SVB0fcsLulOZQeRnOgdkZWkRUiyBlh0Bkyw==",
+ "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.34.0"
+ "@octokit/types": "^6.40.0"
},
"dependencies": {
"@octokit/types": {
- "version": "6.34.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
- "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
+ "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": "^11.2.0"
+ "@octokit/openapi-types": "^12.11.0"
}
}
}
@@ -2418,12 +2418,12 @@
},
"dependencies": {
"@octokit/types": {
- "version": "6.34.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
- "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
+ "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": "^11.2.0"
+ "@octokit/openapi-types": "^12.11.0"
}
},
"is-plain-object": {
@@ -2452,12 +2452,12 @@
},
"dependencies": {
"@octokit/types": {
- "version": "6.34.0",
- "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz",
- "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==",
+ "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": "^11.2.0"
+ "@octokit/openapi-types": "^12.11.0"
}
}
}
@@ -2503,6 +2503,29 @@
}
}
},
+ "@reduxjs/toolkit": {
+ "version": "1.9.5",
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
+ "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
+ "requires": {
+ "immer": "^9.0.21",
+ "redux": "^4.2.1",
+ "redux-thunk": "^2.4.2",
+ "reselect": "^4.1.8"
+ },
+ "dependencies": {
+ "immer": {
+ "version": "9.0.21",
+ "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
+ "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="
+ }
+ }
+ },
+ "@remix-run/router": {
+ "version": "1.7.2",
+ "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.7.2.tgz",
+ "integrity": "sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A=="
+ },
"@rollup/plugin-node-resolve": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-7.1.3.tgz",
@@ -2569,9 +2592,9 @@
}
},
"@sinonjs/text-encoding": {
- "version": "0.7.1",
- "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz",
- "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==",
+ "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
},
"@stylelint/postcss-css-in-js": {
@@ -2793,6 +2816,15 @@
"@types/node": "*"
}
},
+ "@types/hoist-non-react-statics": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+ "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+ "requires": {
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0"
+ }
+ },
"@types/html-minifier-terser": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-5.1.2.tgz",
@@ -2872,8 +2904,7 @@
"@types/prop-types": {
"version": "15.7.4",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.4.tgz",
- "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ==",
- "dev": true
+ "integrity": "sha512-rZ5drC/jWjrArrS8BR6SIr4cWpW09RNTYt9AMZo3Jwwif+iacXAqgVjm0B0Bv/S1jhDXKHqRVNCbACkJ89RAnQ=="
},
"@types/q": {
"version": "1.5.5",
@@ -2884,7 +2915,6 @@
"version": "17.0.43",
"resolved": "https://registry.npmjs.org/@types/react/-/react-17.0.43.tgz",
"integrity": "sha512-8Q+LNpdxf057brvPu1lMtC5Vn7J119xrP1aq4qiaefNioQUYANF/CYeK4NsKorSZyUGJ66g0IM+4bbjwx45o2A==",
- "dev": true,
"requires": {
"@types/prop-types": "*",
"@types/scheduler": "*",
@@ -2892,9 +2922,29 @@
}
},
"@types/react-dom": {
- "version": "17.0.14",
- "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-17.0.14.tgz",
- "integrity": "sha512-H03xwEP1oXmSfl3iobtmQ/2dHF5aBHr8aUMwyGZya6OW45G+xtdzmq6HkncefiBt5JU8DVyaWl/nWZbjZCnzAQ==",
+ "version": "18.2.7",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.2.7.tgz",
+ "integrity": "sha512-GRaAEriuT4zp9N4p1i8BDBYmEyfo+xQ3yHjJU4eiK5NDa1RmUZG+unZABUTK4/Ox/M+GaHwb6Ow8rUITrtjszA==",
+ "dev": true,
+ "requires": {
+ "@types/react": "*"
+ }
+ },
+ "@types/react-redux": {
+ "version": "7.1.25",
+ "resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz",
+ "integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==",
+ "requires": {
+ "@types/hoist-non-react-statics": "^3.3.0",
+ "@types/react": "*",
+ "hoist-non-react-statics": "^3.3.0",
+ "redux": "^4.0.0"
+ }
+ },
+ "@types/react-slick": {
+ "version": "0.23.10",
+ "resolved": "https://registry.npmjs.org/@types/react-slick/-/react-slick-0.23.10.tgz",
+ "integrity": "sha512-ZiqdencANDZy6sWOWJ54LDvebuXFEhDlHtXU9FFipQR2BcYU2QJxZhvJPW6YK7cocibUiNn+YvDTbt1HtCIBVA==",
"dev": true,
"requires": {
"@types/react": "*"
@@ -2911,8 +2961,7 @@
"@types/scheduler": {
"version": "0.16.2",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.2.tgz",
- "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==",
- "dev": true
+ "integrity": "sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew=="
},
"@types/sinonjs__fake-timers": {
"version": "8.1.1",
@@ -2962,6 +3011,11 @@
"integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==",
"dev": true
},
+ "@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/webpack": {
"version": "4.41.32",
"resolved": "https://registry.npmjs.org/@types/webpack/-/webpack-4.41.32.tgz",
@@ -4142,9 +4196,9 @@
}
},
"before-after-hook": {
- "version": "2.2.2",
- "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz",
- "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==",
+ "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": {
@@ -4779,6 +4833,11 @@
}
}
},
+ "classnames": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.2.tgz",
+ "integrity": "sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw=="
+ },
"clean-css": {
"version": "4.2.4",
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
@@ -5646,8 +5705,7 @@
"csstype": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.0.11.tgz",
- "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw==",
- "dev": true
+ "integrity": "sha512-sa6P2wJ+CAbgyy4KFssIb/JNMLxFvKF1pCYCSXS8ZMuqZnMsrxqI2E5sPyoTpxoPU/gVZMzr2zjOfg8GIZOMsw=="
},
"cyclist": {
"version": "1.0.1",
@@ -9442,6 +9500,14 @@
"minimalistic-crypto-utils": "^1.0.1"
}
},
+ "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",
@@ -12219,7 +12285,7 @@
"lodash.get": {
"version": "4.4.2",
"resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz",
- "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=",
+ "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==",
"dev": true
},
"lodash.isempty": {
@@ -12402,9 +12468,9 @@
"integrity": "sha512-WY9wjJNQt9+PZilnLbuFKM+SwDull9+6IAguOrarOMoOHTcJ9GnXSO11+Gw6c7xtDkBkthR57OZMtZKYr+1CEw=="
},
"macos-release": {
- "version": "2.5.0",
- "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz",
- "integrity": "sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g==",
+ "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": {
@@ -13267,7 +13333,7 @@
"isarray": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
- "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=",
+ "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==",
"dev": true
},
"path-to-regexp": {
@@ -13298,9 +13364,9 @@
}
},
"node-fetch": {
- "version": "2.6.7",
- "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
- "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
+ "version": "2.6.12",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.12.tgz",
+ "integrity": "sha512-C/fGU2E8ToujUivIO0H+tpQ6HWo4eEmchoPIoXtxCrVghxdKq+QOHqEZW7tuP3KlV3bC8FRMO5nMCC7Zm1VP6g==",
"dev": true,
"requires": {
"whatwg-url": "^5.0.0"
@@ -13309,19 +13375,19 @@
"tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
- "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=",
+ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"dev": true
},
"webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
- "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=",
+ "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"dev": true
},
"whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
- "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=",
+ "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"dev": true,
"requires": {
"tr46": "~0.0.3",
@@ -15803,11 +15869,48 @@
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
},
+ "react-redux": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.1.tgz",
+ "integrity": "sha512-5W0QaKtEhj+3bC0Nj0NkqkhIv8gLADH/2kYFMTHxCVqQILiWzLv6MaLuV5wJU3BQEdHKzTfcvPN0WMS6SC1oyA==",
+ "requires": {
+ "@babel/runtime": "^7.12.1",
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "@types/use-sync-external-store": "^0.0.3",
+ "hoist-non-react-statics": "^3.3.2",
+ "react-is": "^18.0.0",
+ "use-sync-external-store": "^1.0.0"
+ },
+ "dependencies": {
+ "react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+ }
+ }
+ },
"react-refresh": {
"version": "0.8.3",
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.8.3.tgz",
"integrity": "sha512-X8jZHc7nCMjaCqoU+V2I0cOhNW+QMBwSUkeXnTi8IPe6zaRWfn60ZzvFDZqWPfmSJfjub7dDW1SP0jaHWLu/hg=="
},
+ "react-router": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.14.2.tgz",
+ "integrity": "sha512-09Zss2dE2z+T1D03IheqAFtK4UzQyX8nFPWx6jkwdYzGLXd5ie06A6ezS2fO6zJfEb/SpG6UocN2O1hfD+2urQ==",
+ "requires": {
+ "@remix-run/router": "1.7.2"
+ }
+ },
+ "react-router-dom": {
+ "version": "6.14.2",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.14.2.tgz",
+ "integrity": "sha512-5pWX0jdKR48XFZBuJqHosX3AAHjRAzygouMTyimnBPOLdY3WjzUSKhus2FVMihUFWzeLebDgr4r8UeQFAct7Bg==",
+ "requires": {
+ "@remix-run/router": "1.7.2",
+ "react-router": "6.14.2"
+ }
+ },
"react-scripts": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-4.0.3.tgz",
@@ -15987,6 +16090,19 @@
"strip-indent": "^3.0.0"
}
},
+ "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"
+ }
+ },
+ "redux-thunk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
+ "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q=="
+ },
"regenerate": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -16315,6 +16431,11 @@
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
},
+ "reselect": {
+ "version": "4.1.8",
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
+ "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
+ },
"resolve": {
"version": "1.18.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz",
@@ -18865,6 +18986,11 @@
"resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz",
"integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ=="
},
+ "use-sync-external-store": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+ "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA=="
+ },
"util": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/util/-/util-0.11.1.tgz",
diff --git a/package.json b/package.json
index b554f8b8b0..80aa013e5f 100755
--- a/package.json
+++ b/package.json
@@ -7,9 +7,14 @@
"license": "GPL-3.0",
"dependencies": {
"@cypress/react": "^5.12.4",
+ "@reduxjs/toolkit": "^1.9.5",
+ "@types/react-redux": "^7.1.25",
"bulma": "^0.9.3",
+ "classnames": "^2.3.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
+ "react-redux": "^8.1.1",
+ "react-router-dom": "^6.14.2",
"react-scripts": "^4.0.3"
},
"devDependencies": {
@@ -17,12 +22,13 @@
"@mate-academy/cypress-tools": "^1.0.4",
"@mate-academy/eslint-config-react": "*",
"@mate-academy/eslint-config-react-typescript": "*",
- "@mate-academy/scripts": "^1.2.1",
+ "@mate-academy/scripts": "^1.2.8",
"@mate-academy/students-ts-config": "*",
"@mate-academy/stylelint-config": "*",
"@types/node": "^17.0.23",
"@types/react": "^17.0.43",
- "@types/react-dom": "^17.0.14",
+ "@types/react-dom": "^18.2.7",
+ "@types/react-slick": "^0.23.10",
"cypress": "^9.5.3",
"eslint": "^7.32.0",
"eslint-plugin-cypress": "^2.11.2",
diff --git a/public/images/banner-accessories.png b/public/images/banner-accessories.png
new file mode 100644
index 0000000000..28b5c4c99a
Binary files /dev/null and b/public/images/banner-accessories.png differ
diff --git a/public/images/banner-phones.png b/public/images/banner-phones.png
new file mode 100644
index 0000000000..09b0654881
Binary files /dev/null and b/public/images/banner-phones.png differ
diff --git a/public/images/banner-tablets.png b/public/images/banner-tablets.png
new file mode 100644
index 0000000000..45e303b668
Binary files /dev/null and b/public/images/banner-tablets.png differ
diff --git a/public/images/icons/ArrowDown.svg b/public/images/icons/ArrowDown.svg
new file mode 100644
index 0000000000..8819d342b3
--- /dev/null
+++ b/public/images/icons/ArrowDown.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/ArrowLeft-dark.svg b/public/images/icons/ArrowLeft-dark.svg
new file mode 100644
index 0000000000..8fac3ce11f
--- /dev/null
+++ b/public/images/icons/ArrowLeft-dark.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/ArrowLeft.svg b/public/images/icons/ArrowLeft.svg
new file mode 100644
index 0000000000..abdbd71aee
--- /dev/null
+++ b/public/images/icons/ArrowLeft.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/ArrowRight.svg b/public/images/icons/ArrowRight.svg
new file mode 100644
index 0000000000..2f44f37df4
--- /dev/null
+++ b/public/images/icons/ArrowRight.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/ArrowUp.svg b/public/images/icons/ArrowUp.svg
new file mode 100644
index 0000000000..1579b82014
--- /dev/null
+++ b/public/images/icons/ArrowUp.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/ArrowUpDark.svg b/public/images/icons/ArrowUpDark.svg
new file mode 100644
index 0000000000..57bff8d3a9
--- /dev/null
+++ b/public/images/icons/ArrowUpDark.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/CloseButton.svg b/public/images/icons/CloseButton.svg
new file mode 100644
index 0000000000..3f0794f2b9
--- /dev/null
+++ b/public/images/icons/CloseButton.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/CloseButtonDark.svg b/public/images/icons/CloseButtonDark.svg
new file mode 100644
index 0000000000..2e578c91a8
--- /dev/null
+++ b/public/images/icons/CloseButtonDark.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/HeartLike.svg b/public/images/icons/HeartLike.svg
new file mode 100644
index 0000000000..8bc545b074
--- /dev/null
+++ b/public/images/icons/HeartLike.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/HeartLikeFilled.svg b/public/images/icons/HeartLikeFilled.svg
new file mode 100644
index 0000000000..089b4d6d6c
--- /dev/null
+++ b/public/images/icons/HeartLikeFilled.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/Home.svg b/public/images/icons/Home.svg
new file mode 100644
index 0000000000..8cdc044a35
--- /dev/null
+++ b/public/images/icons/Home.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/images/icons/Search.svg b/public/images/icons/Search.svg
new file mode 100644
index 0000000000..8685c5b0cd
--- /dev/null
+++ b/public/images/icons/Search.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/images/icons/ShoppingbagCart.svg b/public/images/icons/ShoppingbagCart.svg
new file mode 100644
index 0000000000..a44e90731e
--- /dev/null
+++ b/public/images/icons/ShoppingbagCart.svg
@@ -0,0 +1,9 @@
+
diff --git a/public/images/icons/icon-burger-menu-hover.svg b/public/images/icons/icon-burger-menu-hover.svg
new file mode 100644
index 0000000000..1063135c93
--- /dev/null
+++ b/public/images/icons/icon-burger-menu-hover.svg
@@ -0,0 +1,10 @@
+
diff --git a/public/images/icons/icon-burger-menu.svg b/public/images/icons/icon-burger-menu.svg
new file mode 100644
index 0000000000..33ecac98ff
--- /dev/null
+++ b/public/images/icons/icon-burger-menu.svg
@@ -0,0 +1,10 @@
+
diff --git a/public/images/icons/icon-close.svg b/public/images/icons/icon-close.svg
new file mode 100644
index 0000000000..8590db1515
--- /dev/null
+++ b/public/images/icons/icon-close.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/images/logo.jpg b/public/images/logo.jpg
new file mode 100644
index 0000000000..a1583b923e
Binary files /dev/null and b/public/images/logo.jpg differ
diff --git a/public/images/preview-category-accessories.png b/public/images/preview-category-accessories.png
new file mode 100644
index 0000000000..5ed772c82e
Binary files /dev/null and b/public/images/preview-category-accessories.png differ
diff --git a/public/images/preview-category-phone.png b/public/images/preview-category-phone.png
new file mode 100644
index 0000000000..debce1a69f
Binary files /dev/null and b/public/images/preview-category-phone.png differ
diff --git a/public/images/preview-category-tablets.png b/public/images/preview-category-tablets.png
new file mode 100644
index 0000000000..f663ca067f
Binary files /dev/null and b/public/images/preview-category-tablets.png differ
diff --git a/public/images/product-card-img.png b/public/images/product-card-img.png
new file mode 100644
index 0000000000..a7e8012d2c
Binary files /dev/null and b/public/images/product-card-img.png differ
diff --git a/public/img/category-accessories.png b/public/img/category-accessories.png
new file mode 100644
index 0000000000..67c5bfdb35
Binary files /dev/null and b/public/img/category-accessories.png differ
diff --git a/public/img/category-phones.png b/public/img/category-phones.png
new file mode 100644
index 0000000000..fd7616042f
Binary files /dev/null and b/public/img/category-phones.png differ
diff --git a/public/img/category-tablets.png b/public/img/category-tablets.png
new file mode 100644
index 0000000000..57e33c5807
Binary files /dev/null and b/public/img/category-tablets.png differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/gold/00.jpg b/public/img/phones/apple-iphone-11-pro-max/gold/00.jpg
new file mode 100644
index 0000000000..e4d9d3342a
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/gold/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/gold/01.jpg b/public/img/phones/apple-iphone-11-pro-max/gold/01.jpg
new file mode 100644
index 0000000000..37d9be6f94
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/gold/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/gold/02.jpg b/public/img/phones/apple-iphone-11-pro-max/gold/02.jpg
new file mode 100644
index 0000000000..62709fa9f8
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/gold/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/midnightgreen/00.jpg b/public/img/phones/apple-iphone-11-pro-max/midnightgreen/00.jpg
new file mode 100644
index 0000000000..6411725cd3
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/midnightgreen/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/midnightgreen/01.jpg b/public/img/phones/apple-iphone-11-pro-max/midnightgreen/01.jpg
new file mode 100644
index 0000000000..939bf834a0
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/midnightgreen/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/midnightgreen/02.jpg b/public/img/phones/apple-iphone-11-pro-max/midnightgreen/02.jpg
new file mode 100644
index 0000000000..ab306815f2
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/midnightgreen/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/midnightgreen/03.jpg b/public/img/phones/apple-iphone-11-pro-max/midnightgreen/03.jpg
new file mode 100644
index 0000000000..a9ac08c6b7
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/midnightgreen/03.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/silver/00.jpg b/public/img/phones/apple-iphone-11-pro-max/silver/00.jpg
new file mode 100644
index 0000000000..75aa39c460
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/silver/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/silver/01.jpg b/public/img/phones/apple-iphone-11-pro-max/silver/01.jpg
new file mode 100644
index 0000000000..eff84ae461
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/silver/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/silver/02.jpg b/public/img/phones/apple-iphone-11-pro-max/silver/02.jpg
new file mode 100644
index 0000000000..d2c0731926
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/silver/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/spacegray/00.jpg b/public/img/phones/apple-iphone-11-pro-max/spacegray/00.jpg
new file mode 100644
index 0000000000..cebdab5009
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/spacegray/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/spacegray/01.jpg b/public/img/phones/apple-iphone-11-pro-max/spacegray/01.jpg
new file mode 100644
index 0000000000..a64946746e
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/spacegray/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro-max/spacegray/02.jpg b/public/img/phones/apple-iphone-11-pro-max/spacegray/02.jpg
new file mode 100644
index 0000000000..426de3a6bd
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro-max/spacegray/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/gold/00.jpg b/public/img/phones/apple-iphone-11-pro/gold/00.jpg
new file mode 100644
index 0000000000..e4d9d3342a
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/gold/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/gold/01.jpg b/public/img/phones/apple-iphone-11-pro/gold/01.jpg
new file mode 100644
index 0000000000..37d9be6f94
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/gold/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/gold/02.jpg b/public/img/phones/apple-iphone-11-pro/gold/02.jpg
new file mode 100644
index 0000000000..62709fa9f8
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/gold/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/midnightgreen/00.jpg b/public/img/phones/apple-iphone-11-pro/midnightgreen/00.jpg
new file mode 100644
index 0000000000..6411725cd3
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/midnightgreen/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/midnightgreen/01.jpg b/public/img/phones/apple-iphone-11-pro/midnightgreen/01.jpg
new file mode 100644
index 0000000000..939bf834a0
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/midnightgreen/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/midnightgreen/02.jpg b/public/img/phones/apple-iphone-11-pro/midnightgreen/02.jpg
new file mode 100644
index 0000000000..ab306815f2
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/midnightgreen/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/midnightgreen/03.jpg b/public/img/phones/apple-iphone-11-pro/midnightgreen/03.jpg
new file mode 100644
index 0000000000..a9ac08c6b7
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/midnightgreen/03.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/silver/00.jpg b/public/img/phones/apple-iphone-11-pro/silver/00.jpg
new file mode 100644
index 0000000000..75aa39c460
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/silver/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/silver/01.jpg b/public/img/phones/apple-iphone-11-pro/silver/01.jpg
new file mode 100644
index 0000000000..eff84ae461
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/silver/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/silver/02.jpg b/public/img/phones/apple-iphone-11-pro/silver/02.jpg
new file mode 100644
index 0000000000..d2c0731926
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/silver/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/spacegray/00.jpg b/public/img/phones/apple-iphone-11-pro/spacegray/00.jpg
new file mode 100644
index 0000000000..cebdab5009
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/spacegray/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/spacegray/01.jpg b/public/img/phones/apple-iphone-11-pro/spacegray/01.jpg
new file mode 100644
index 0000000000..a64946746e
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/spacegray/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11-pro/spacegray/02.jpg b/public/img/phones/apple-iphone-11-pro/spacegray/02.jpg
new file mode 100644
index 0000000000..426de3a6bd
Binary files /dev/null and b/public/img/phones/apple-iphone-11-pro/spacegray/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11/black/00.jpg b/public/img/phones/apple-iphone-11/black/00.jpg
new file mode 100644
index 0000000000..092d5ffae5
Binary files /dev/null and b/public/img/phones/apple-iphone-11/black/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11/black/01.jpg b/public/img/phones/apple-iphone-11/black/01.jpg
new file mode 100644
index 0000000000..eccbc5a139
Binary files /dev/null and b/public/img/phones/apple-iphone-11/black/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11/black/02.jpg b/public/img/phones/apple-iphone-11/black/02.jpg
new file mode 100644
index 0000000000..78c71ad002
Binary files /dev/null and b/public/img/phones/apple-iphone-11/black/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11/black/03.jpg b/public/img/phones/apple-iphone-11/black/03.jpg
new file mode 100644
index 0000000000..9f96b73fc2
Binary files /dev/null and b/public/img/phones/apple-iphone-11/black/03.jpg differ
diff --git a/public/img/phones/apple-iphone-11/black/04.jpg b/public/img/phones/apple-iphone-11/black/04.jpg
new file mode 100644
index 0000000000..efc1f69db7
Binary files /dev/null and b/public/img/phones/apple-iphone-11/black/04.jpg differ
diff --git a/public/img/phones/apple-iphone-11/green/00.jpg b/public/img/phones/apple-iphone-11/green/00.jpg
new file mode 100644
index 0000000000..32c697c9cf
Binary files /dev/null and b/public/img/phones/apple-iphone-11/green/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11/green/01.jpg b/public/img/phones/apple-iphone-11/green/01.jpg
new file mode 100644
index 0000000000..4cfe7d916b
Binary files /dev/null and b/public/img/phones/apple-iphone-11/green/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11/green/02.jpg b/public/img/phones/apple-iphone-11/green/02.jpg
new file mode 100644
index 0000000000..3039d97109
Binary files /dev/null and b/public/img/phones/apple-iphone-11/green/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11/green/03.jpg b/public/img/phones/apple-iphone-11/green/03.jpg
new file mode 100644
index 0000000000..dbafedfd2a
Binary files /dev/null and b/public/img/phones/apple-iphone-11/green/03.jpg differ
diff --git a/public/img/phones/apple-iphone-11/green/04.jpg b/public/img/phones/apple-iphone-11/green/04.jpg
new file mode 100644
index 0000000000..b7a7c32a7a
Binary files /dev/null and b/public/img/phones/apple-iphone-11/green/04.jpg differ
diff --git a/public/img/phones/apple-iphone-11/purple/00.jpg b/public/img/phones/apple-iphone-11/purple/00.jpg
new file mode 100644
index 0000000000..1d77879d12
Binary files /dev/null and b/public/img/phones/apple-iphone-11/purple/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11/purple/01.jpg b/public/img/phones/apple-iphone-11/purple/01.jpg
new file mode 100644
index 0000000000..b94d8c7c95
Binary files /dev/null and b/public/img/phones/apple-iphone-11/purple/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11/purple/02.jpg b/public/img/phones/apple-iphone-11/purple/02.jpg
new file mode 100644
index 0000000000..f3c79f29eb
Binary files /dev/null and b/public/img/phones/apple-iphone-11/purple/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11/purple/03.jpg b/public/img/phones/apple-iphone-11/purple/03.jpg
new file mode 100644
index 0000000000..396e5dc8bf
Binary files /dev/null and b/public/img/phones/apple-iphone-11/purple/03.jpg differ
diff --git a/public/img/phones/apple-iphone-11/purple/04.jpg b/public/img/phones/apple-iphone-11/purple/04.jpg
new file mode 100644
index 0000000000..355d7a9f6b
Binary files /dev/null and b/public/img/phones/apple-iphone-11/purple/04.jpg differ
diff --git a/public/img/phones/apple-iphone-11/red/00.jpg b/public/img/phones/apple-iphone-11/red/00.jpg
new file mode 100644
index 0000000000..362a1805f6
Binary files /dev/null and b/public/img/phones/apple-iphone-11/red/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11/red/01.jpg b/public/img/phones/apple-iphone-11/red/01.jpg
new file mode 100644
index 0000000000..dee12358e1
Binary files /dev/null and b/public/img/phones/apple-iphone-11/red/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11/red/02.jpg b/public/img/phones/apple-iphone-11/red/02.jpg
new file mode 100644
index 0000000000..5fd8c396d7
Binary files /dev/null and b/public/img/phones/apple-iphone-11/red/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11/red/03.jpg b/public/img/phones/apple-iphone-11/red/03.jpg
new file mode 100644
index 0000000000..0c27676ad6
Binary files /dev/null and b/public/img/phones/apple-iphone-11/red/03.jpg differ
diff --git a/public/img/phones/apple-iphone-11/red/04.jpg b/public/img/phones/apple-iphone-11/red/04.jpg
new file mode 100644
index 0000000000..f02a628c78
Binary files /dev/null and b/public/img/phones/apple-iphone-11/red/04.jpg differ
diff --git a/public/img/phones/apple-iphone-11/white/00.jpg b/public/img/phones/apple-iphone-11/white/00.jpg
new file mode 100644
index 0000000000..a11af0e205
Binary files /dev/null and b/public/img/phones/apple-iphone-11/white/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11/white/01.jpg b/public/img/phones/apple-iphone-11/white/01.jpg
new file mode 100644
index 0000000000..425f017ea4
Binary files /dev/null and b/public/img/phones/apple-iphone-11/white/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11/white/02.jpg b/public/img/phones/apple-iphone-11/white/02.jpg
new file mode 100644
index 0000000000..af56e3d57a
Binary files /dev/null and b/public/img/phones/apple-iphone-11/white/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11/white/03.jpg b/public/img/phones/apple-iphone-11/white/03.jpg
new file mode 100644
index 0000000000..f4dddc606e
Binary files /dev/null and b/public/img/phones/apple-iphone-11/white/03.jpg differ
diff --git a/public/img/phones/apple-iphone-11/white/04.jpg b/public/img/phones/apple-iphone-11/white/04.jpg
new file mode 100644
index 0000000000..c421c91771
Binary files /dev/null and b/public/img/phones/apple-iphone-11/white/04.jpg differ
diff --git a/public/img/phones/apple-iphone-11/yellow/00.jpg b/public/img/phones/apple-iphone-11/yellow/00.jpg
new file mode 100644
index 0000000000..fa02c944ca
Binary files /dev/null and b/public/img/phones/apple-iphone-11/yellow/00.jpg differ
diff --git a/public/img/phones/apple-iphone-11/yellow/01.jpg b/public/img/phones/apple-iphone-11/yellow/01.jpg
new file mode 100644
index 0000000000..3615421183
Binary files /dev/null and b/public/img/phones/apple-iphone-11/yellow/01.jpg differ
diff --git a/public/img/phones/apple-iphone-11/yellow/02.jpg b/public/img/phones/apple-iphone-11/yellow/02.jpg
new file mode 100644
index 0000000000..ab5ef15db9
Binary files /dev/null and b/public/img/phones/apple-iphone-11/yellow/02.jpg differ
diff --git a/public/img/phones/apple-iphone-11/yellow/03.jpg b/public/img/phones/apple-iphone-11/yellow/03.jpg
new file mode 100644
index 0000000000..b5a963fd55
Binary files /dev/null and b/public/img/phones/apple-iphone-11/yellow/03.jpg differ
diff --git a/public/img/phones/apple-iphone-11/yellow/04.jpg b/public/img/phones/apple-iphone-11/yellow/04.jpg
new file mode 100644
index 0000000000..5e7367ef17
Binary files /dev/null and b/public/img/phones/apple-iphone-11/yellow/04.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/black/00.jpg b/public/img/phones/apple-iphone-7-plus/black/00.jpg
new file mode 100644
index 0000000000..e1eba91e0d
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/black/00.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/black/01.jpg b/public/img/phones/apple-iphone-7-plus/black/01.jpg
new file mode 100644
index 0000000000..67760630c0
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/black/01.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/black/02.jpg b/public/img/phones/apple-iphone-7-plus/black/02.jpg
new file mode 100644
index 0000000000..74c35d8b2c
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/black/02.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/black/03.jpg b/public/img/phones/apple-iphone-7-plus/black/03.jpg
new file mode 100644
index 0000000000..7fd73f3264
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/black/03.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/black/04.jpg b/public/img/phones/apple-iphone-7-plus/black/04.jpg
new file mode 100644
index 0000000000..02dd334b6a
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/black/04.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/gold/00.jpg b/public/img/phones/apple-iphone-7-plus/gold/00.jpg
new file mode 100644
index 0000000000..c017d52d9f
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/gold/00.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/gold/01.jpg b/public/img/phones/apple-iphone-7-plus/gold/01.jpg
new file mode 100644
index 0000000000..f23312b4d9
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/gold/01.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/gold/02.jpg b/public/img/phones/apple-iphone-7-plus/gold/02.jpg
new file mode 100644
index 0000000000..0f6ad58fbf
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/gold/02.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/gold/03.jpg b/public/img/phones/apple-iphone-7-plus/gold/03.jpg
new file mode 100644
index 0000000000..0dbebb70a4
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/gold/03.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/gold/04.jpg b/public/img/phones/apple-iphone-7-plus/gold/04.jpg
new file mode 100644
index 0000000000..bc8fa5c6a6
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/gold/04.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/rosegold/00.jpg b/public/img/phones/apple-iphone-7-plus/rosegold/00.jpg
new file mode 100644
index 0000000000..3fa3dab5aa
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/rosegold/00.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/rosegold/01.jpg b/public/img/phones/apple-iphone-7-plus/rosegold/01.jpg
new file mode 100644
index 0000000000..1b721f2312
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/rosegold/01.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/rosegold/02.jpg b/public/img/phones/apple-iphone-7-plus/rosegold/02.jpg
new file mode 100644
index 0000000000..de437ff861
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/rosegold/02.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/rosegold/03.jpg b/public/img/phones/apple-iphone-7-plus/rosegold/03.jpg
new file mode 100644
index 0000000000..501e3e7cdf
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/rosegold/03.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/rosegold/04.jpg b/public/img/phones/apple-iphone-7-plus/rosegold/04.jpg
new file mode 100644
index 0000000000..b947cd9f70
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/rosegold/04.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/silver/00.jpg b/public/img/phones/apple-iphone-7-plus/silver/00.jpg
new file mode 100644
index 0000000000..12afb5c13f
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/silver/00.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/silver/01.jpg b/public/img/phones/apple-iphone-7-plus/silver/01.jpg
new file mode 100644
index 0000000000..9d4cd391f2
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/silver/01.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/silver/02.jpg b/public/img/phones/apple-iphone-7-plus/silver/02.jpg
new file mode 100644
index 0000000000..ee9d67a3d6
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/silver/02.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/silver/03.jpg b/public/img/phones/apple-iphone-7-plus/silver/03.jpg
new file mode 100644
index 0000000000..47689876df
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/silver/03.jpg differ
diff --git a/public/img/phones/apple-iphone-7-plus/silver/04.jpg b/public/img/phones/apple-iphone-7-plus/silver/04.jpg
new file mode 100644
index 0000000000..60c96eb758
Binary files /dev/null and b/public/img/phones/apple-iphone-7-plus/silver/04.jpg differ
diff --git a/public/img/phones/apple-iphone-7/black/00.jpg b/public/img/phones/apple-iphone-7/black/00.jpg
new file mode 100644
index 0000000000..45910c5d78
Binary files /dev/null and b/public/img/phones/apple-iphone-7/black/00.jpg differ
diff --git a/public/img/phones/apple-iphone-7/black/01.jpg b/public/img/phones/apple-iphone-7/black/01.jpg
new file mode 100644
index 0000000000..d0d92d3662
Binary files /dev/null and b/public/img/phones/apple-iphone-7/black/01.jpg differ
diff --git a/public/img/phones/apple-iphone-7/black/02.jpg b/public/img/phones/apple-iphone-7/black/02.jpg
new file mode 100644
index 0000000000..2e3de67783
Binary files /dev/null and b/public/img/phones/apple-iphone-7/black/02.jpg differ
diff --git a/public/img/phones/apple-iphone-7/black/03.jpg b/public/img/phones/apple-iphone-7/black/03.jpg
new file mode 100644
index 0000000000..0e356ed7e6
Binary files /dev/null and b/public/img/phones/apple-iphone-7/black/03.jpg differ
diff --git a/public/img/phones/apple-iphone-7/black/04.jpg b/public/img/phones/apple-iphone-7/black/04.jpg
new file mode 100644
index 0000000000..d0c369bb4e
Binary files /dev/null and b/public/img/phones/apple-iphone-7/black/04.jpg differ
diff --git a/public/img/phones/apple-iphone-7/gold/00.jpg b/public/img/phones/apple-iphone-7/gold/00.jpg
new file mode 100644
index 0000000000..c5a6425b8f
Binary files /dev/null and b/public/img/phones/apple-iphone-7/gold/00.jpg differ
diff --git a/public/img/phones/apple-iphone-7/gold/01.jpg b/public/img/phones/apple-iphone-7/gold/01.jpg
new file mode 100644
index 0000000000..0e9cfccde1
Binary files /dev/null and b/public/img/phones/apple-iphone-7/gold/01.jpg differ
diff --git a/public/img/phones/apple-iphone-7/gold/02.jpg b/public/img/phones/apple-iphone-7/gold/02.jpg
new file mode 100644
index 0000000000..9156acec8d
Binary files /dev/null and b/public/img/phones/apple-iphone-7/gold/02.jpg differ
diff --git a/public/img/phones/apple-iphone-7/gold/03.jpg b/public/img/phones/apple-iphone-7/gold/03.jpg
new file mode 100644
index 0000000000..15f3f7ba5f
Binary files /dev/null and b/public/img/phones/apple-iphone-7/gold/03.jpg differ
diff --git a/public/img/phones/apple-iphone-7/gold/04.jpg b/public/img/phones/apple-iphone-7/gold/04.jpg
new file mode 100644
index 0000000000..ded0393dca
Binary files /dev/null and b/public/img/phones/apple-iphone-7/gold/04.jpg differ
diff --git a/public/img/phones/apple-iphone-7/rosegold/00.jpg b/public/img/phones/apple-iphone-7/rosegold/00.jpg
new file mode 100644
index 0000000000..2f5f74d1a9
Binary files /dev/null and b/public/img/phones/apple-iphone-7/rosegold/00.jpg differ
diff --git a/public/img/phones/apple-iphone-7/rosegold/01.jpg b/public/img/phones/apple-iphone-7/rosegold/01.jpg
new file mode 100644
index 0000000000..939031e8ab
Binary files /dev/null and b/public/img/phones/apple-iphone-7/rosegold/01.jpg differ
diff --git a/public/img/phones/apple-iphone-7/rosegold/02.jpg b/public/img/phones/apple-iphone-7/rosegold/02.jpg
new file mode 100644
index 0000000000..b9dafd95ec
Binary files /dev/null and b/public/img/phones/apple-iphone-7/rosegold/02.jpg differ
diff --git a/public/img/phones/apple-iphone-7/rosegold/03.jpg b/public/img/phones/apple-iphone-7/rosegold/03.jpg
new file mode 100644
index 0000000000..2f3f57fd4c
Binary files /dev/null and b/public/img/phones/apple-iphone-7/rosegold/03.jpg differ
diff --git a/public/img/phones/apple-iphone-7/rosegold/04.jpg b/public/img/phones/apple-iphone-7/rosegold/04.jpg
new file mode 100644
index 0000000000..bd0ecb751e
Binary files /dev/null and b/public/img/phones/apple-iphone-7/rosegold/04.jpg differ
diff --git a/public/img/phones/apple-iphone-7/silver/00.jpg b/public/img/phones/apple-iphone-7/silver/00.jpg
new file mode 100644
index 0000000000..67283d06cb
Binary files /dev/null and b/public/img/phones/apple-iphone-7/silver/00.jpg differ
diff --git a/public/img/phones/apple-iphone-7/silver/01.jpg b/public/img/phones/apple-iphone-7/silver/01.jpg
new file mode 100644
index 0000000000..d8c5770959
Binary files /dev/null and b/public/img/phones/apple-iphone-7/silver/01.jpg differ
diff --git a/public/img/phones/apple-iphone-7/silver/02.jpg b/public/img/phones/apple-iphone-7/silver/02.jpg
new file mode 100644
index 0000000000..31b3a7a74b
Binary files /dev/null and b/public/img/phones/apple-iphone-7/silver/02.jpg differ
diff --git a/public/img/phones/apple-iphone-7/silver/03.jpg b/public/img/phones/apple-iphone-7/silver/03.jpg
new file mode 100644
index 0000000000..b7b07e7e4b
Binary files /dev/null and b/public/img/phones/apple-iphone-7/silver/03.jpg differ
diff --git a/public/img/phones/apple-iphone-7/silver/04.jpg b/public/img/phones/apple-iphone-7/silver/04.jpg
new file mode 100644
index 0000000000..d861adc226
Binary files /dev/null and b/public/img/phones/apple-iphone-7/silver/04.jpg differ
diff --git a/public/img/phones/apple-iphone-8/gold/00.jpg b/public/img/phones/apple-iphone-8/gold/00.jpg
new file mode 100644
index 0000000000..f0cab8bd70
Binary files /dev/null and b/public/img/phones/apple-iphone-8/gold/00.jpg differ
diff --git a/public/img/phones/apple-iphone-8/gold/01.jpg b/public/img/phones/apple-iphone-8/gold/01.jpg
new file mode 100644
index 0000000000..5954ce3cb3
Binary files /dev/null and b/public/img/phones/apple-iphone-8/gold/01.jpg differ
diff --git a/public/img/phones/apple-iphone-8/gold/02.jpg b/public/img/phones/apple-iphone-8/gold/02.jpg
new file mode 100644
index 0000000000..4ef513cb27
Binary files /dev/null and b/public/img/phones/apple-iphone-8/gold/02.jpg differ
diff --git a/public/img/phones/apple-iphone-8/gold/03.jpg b/public/img/phones/apple-iphone-8/gold/03.jpg
new file mode 100644
index 0000000000..bf04d31554
Binary files /dev/null and b/public/img/phones/apple-iphone-8/gold/03.jpg differ
diff --git a/public/img/phones/apple-iphone-8/silver/00.jpg b/public/img/phones/apple-iphone-8/silver/00.jpg
new file mode 100644
index 0000000000..dae21f12ff
Binary files /dev/null and b/public/img/phones/apple-iphone-8/silver/00.jpg differ
diff --git a/public/img/phones/apple-iphone-8/silver/01.jpg b/public/img/phones/apple-iphone-8/silver/01.jpg
new file mode 100644
index 0000000000..2430f101ea
Binary files /dev/null and b/public/img/phones/apple-iphone-8/silver/01.jpg differ
diff --git a/public/img/phones/apple-iphone-8/silver/02.jpg b/public/img/phones/apple-iphone-8/silver/02.jpg
new file mode 100644
index 0000000000..eac1785bd1
Binary files /dev/null and b/public/img/phones/apple-iphone-8/silver/02.jpg differ
diff --git a/public/img/phones/apple-iphone-8/silver/03.jpg b/public/img/phones/apple-iphone-8/silver/03.jpg
new file mode 100644
index 0000000000..716f23e172
Binary files /dev/null and b/public/img/phones/apple-iphone-8/silver/03.jpg differ
diff --git a/public/img/phones/apple-iphone-8/spacegray/00.jpg b/public/img/phones/apple-iphone-8/spacegray/00.jpg
new file mode 100644
index 0000000000..08642051e9
Binary files /dev/null and b/public/img/phones/apple-iphone-8/spacegray/00.jpg differ
diff --git a/public/img/phones/apple-iphone-8/spacegray/01.jpg b/public/img/phones/apple-iphone-8/spacegray/01.jpg
new file mode 100644
index 0000000000..a5c5a58aee
Binary files /dev/null and b/public/img/phones/apple-iphone-8/spacegray/01.jpg differ
diff --git a/public/img/phones/apple-iphone-8/spacegray/02.jpg b/public/img/phones/apple-iphone-8/spacegray/02.jpg
new file mode 100644
index 0000000000..45865ba0ef
Binary files /dev/null and b/public/img/phones/apple-iphone-8/spacegray/02.jpg differ
diff --git a/public/img/phones/apple-iphone-8/spacegray/03.jpg b/public/img/phones/apple-iphone-8/spacegray/03.jpg
new file mode 100644
index 0000000000..5ac0721fd1
Binary files /dev/null and b/public/img/phones/apple-iphone-8/spacegray/03.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/coral/00.jpg b/public/img/phones/apple-iphone-xr/coral/00.jpg
new file mode 100644
index 0000000000..331c6e1e51
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/coral/00.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/coral/01.jpg b/public/img/phones/apple-iphone-xr/coral/01.jpg
new file mode 100644
index 0000000000..f1dd9b0ed2
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/coral/01.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/coral/02.jpg b/public/img/phones/apple-iphone-xr/coral/02.jpg
new file mode 100644
index 0000000000..6f5718928b
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/coral/02.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/red/00.jpg b/public/img/phones/apple-iphone-xr/red/00.jpg
new file mode 100644
index 0000000000..d2859155f2
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/red/00.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/red/01.jpg b/public/img/phones/apple-iphone-xr/red/01.jpg
new file mode 100644
index 0000000000..c8a2d34a62
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/red/01.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/red/02.jpg b/public/img/phones/apple-iphone-xr/red/02.jpg
new file mode 100644
index 0000000000..f83b048915
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/red/02.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/red/03.jpg b/public/img/phones/apple-iphone-xr/red/03.jpg
new file mode 100644
index 0000000000..9e7673337c
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/red/03.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/red/04.jpg b/public/img/phones/apple-iphone-xr/red/04.jpg
new file mode 100644
index 0000000000..c1fbba68d3
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/red/04.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/white/00.jpg b/public/img/phones/apple-iphone-xr/white/00.jpg
new file mode 100644
index 0000000000..0de65ed44a
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/white/00.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/white/01.jpg b/public/img/phones/apple-iphone-xr/white/01.jpg
new file mode 100644
index 0000000000..4e87555a17
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/white/01.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/white/02.jpg b/public/img/phones/apple-iphone-xr/white/02.jpg
new file mode 100644
index 0000000000..83d0c09811
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/white/02.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/white/03.jpg b/public/img/phones/apple-iphone-xr/white/03.jpg
new file mode 100644
index 0000000000..1268469607
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/white/03.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/yellow/00.jpg b/public/img/phones/apple-iphone-xr/yellow/00.jpg
new file mode 100644
index 0000000000..3a59b4ec9e
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/yellow/00.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/yellow/01.jpg b/public/img/phones/apple-iphone-xr/yellow/01.jpg
new file mode 100644
index 0000000000..d6c1880250
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/yellow/01.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/yellow/02.jpg b/public/img/phones/apple-iphone-xr/yellow/02.jpg
new file mode 100644
index 0000000000..351da2a187
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/yellow/02.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/yellow/03.jpg b/public/img/phones/apple-iphone-xr/yellow/03.jpg
new file mode 100644
index 0000000000..4430384248
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/yellow/03.jpg differ
diff --git a/public/img/phones/apple-iphone-xr/yellow/04.jpg b/public/img/phones/apple-iphone-xr/yellow/04.jpg
new file mode 100644
index 0000000000..6653c2faa9
Binary files /dev/null and b/public/img/phones/apple-iphone-xr/yellow/04.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/gold/00.jpg b/public/img/phones/apple-iphone-xs-max/gold/00.jpg
new file mode 100644
index 0000000000..f514725e92
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/gold/00.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/gold/01.jpg b/public/img/phones/apple-iphone-xs-max/gold/01.jpg
new file mode 100644
index 0000000000..de3d36f07b
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/gold/01.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/gold/02.jpg b/public/img/phones/apple-iphone-xs-max/gold/02.jpg
new file mode 100644
index 0000000000..b635f8bd8b
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/gold/02.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/gold/03.jpg b/public/img/phones/apple-iphone-xs-max/gold/03.jpg
new file mode 100644
index 0000000000..2d7abf1880
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/gold/03.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/gold/04.jpg b/public/img/phones/apple-iphone-xs-max/gold/04.jpg
new file mode 100644
index 0000000000..756fadc4b3
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/gold/04.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/silver/00.jpg b/public/img/phones/apple-iphone-xs-max/silver/00.jpg
new file mode 100644
index 0000000000..6bdf69f355
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/silver/00.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/silver/01.jpg b/public/img/phones/apple-iphone-xs-max/silver/01.jpg
new file mode 100644
index 0000000000..d587f6b853
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/silver/01.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/silver/02.jpg b/public/img/phones/apple-iphone-xs-max/silver/02.jpg
new file mode 100644
index 0000000000..2d0c8b3509
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/silver/02.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/silver/03.jpg b/public/img/phones/apple-iphone-xs-max/silver/03.jpg
new file mode 100644
index 0000000000..22b8424e87
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/silver/03.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/silver/04.jpg b/public/img/phones/apple-iphone-xs-max/silver/04.jpg
new file mode 100644
index 0000000000..ae04c2d14b
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/silver/04.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/spacegray/00.jpg b/public/img/phones/apple-iphone-xs-max/spacegray/00.jpg
new file mode 100644
index 0000000000..304b7af54f
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/spacegray/00.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/spacegray/01.jpg b/public/img/phones/apple-iphone-xs-max/spacegray/01.jpg
new file mode 100644
index 0000000000..6c7900e9bb
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/spacegray/01.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/spacegray/02.jpg b/public/img/phones/apple-iphone-xs-max/spacegray/02.jpg
new file mode 100644
index 0000000000..eb7699cfda
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/spacegray/02.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/spacegray/03.jpg b/public/img/phones/apple-iphone-xs-max/spacegray/03.jpg
new file mode 100644
index 0000000000..f44eb25d16
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/spacegray/03.jpg differ
diff --git a/public/img/phones/apple-iphone-xs-max/spacegray/04.jpg b/public/img/phones/apple-iphone-xs-max/spacegray/04.jpg
new file mode 100644
index 0000000000..f466a7440f
Binary files /dev/null and b/public/img/phones/apple-iphone-xs-max/spacegray/04.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/gold/00.jpg b/public/img/phones/apple-iphone-xs/gold/00.jpg
new file mode 100644
index 0000000000..af0833518c
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/gold/00.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/gold/01.jpg b/public/img/phones/apple-iphone-xs/gold/01.jpg
new file mode 100644
index 0000000000..7520a86dd0
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/gold/01.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/gold/02.jpg b/public/img/phones/apple-iphone-xs/gold/02.jpg
new file mode 100644
index 0000000000..36909d1005
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/gold/02.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/gold/03.jpg b/public/img/phones/apple-iphone-xs/gold/03.jpg
new file mode 100644
index 0000000000..7e678f7cde
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/gold/03.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/gold/04.jpg b/public/img/phones/apple-iphone-xs/gold/04.jpg
new file mode 100644
index 0000000000..65dbeae83f
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/gold/04.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/spacegray/00.jpg b/public/img/phones/apple-iphone-xs/spacegray/00.jpg
new file mode 100644
index 0000000000..2a716b08c4
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/spacegray/00.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/spacegray/01.jpg b/public/img/phones/apple-iphone-xs/spacegray/01.jpg
new file mode 100644
index 0000000000..a79e22a180
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/spacegray/01.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/spacegray/02.jpg b/public/img/phones/apple-iphone-xs/spacegray/02.jpg
new file mode 100644
index 0000000000..4334c3d8ab
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/spacegray/02.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/spacegray/03.jpg b/public/img/phones/apple-iphone-xs/spacegray/03.jpg
new file mode 100644
index 0000000000..d8689d3be9
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/spacegray/03.jpg differ
diff --git a/public/img/phones/apple-iphone-xs/spacegray/04.jpg b/public/img/phones/apple-iphone-xs/spacegray/04.jpg
new file mode 100644
index 0000000000..124af1fd04
Binary files /dev/null and b/public/img/phones/apple-iphone-xs/spacegray/04.jpg differ
diff --git a/public/img/products/dell-streak-7.0.jpg b/public/img/phones/dell-streak-7.0.jpg
similarity index 100%
rename from public/img/products/dell-streak-7.0.jpg
rename to public/img/phones/dell-streak-7.0.jpg
diff --git a/public/img/products/dell-streak-7.1.jpg b/public/img/phones/dell-streak-7.1.jpg
similarity index 100%
rename from public/img/products/dell-streak-7.1.jpg
rename to public/img/phones/dell-streak-7.1.jpg
diff --git a/public/img/products/dell-streak-7.2.jpg b/public/img/phones/dell-streak-7.2.jpg
similarity index 100%
rename from public/img/products/dell-streak-7.2.jpg
rename to public/img/phones/dell-streak-7.2.jpg
diff --git a/public/img/products/dell-streak-7.3.jpg b/public/img/phones/dell-streak-7.3.jpg
similarity index 100%
rename from public/img/products/dell-streak-7.3.jpg
rename to public/img/phones/dell-streak-7.3.jpg
diff --git a/public/img/products/dell-streak-7.4.jpg b/public/img/phones/dell-streak-7.4.jpg
similarity index 100%
rename from public/img/products/dell-streak-7.4.jpg
rename to public/img/phones/dell-streak-7.4.jpg
diff --git a/public/img/products/dell-venue.0.jpg b/public/img/phones/dell-venue.0.jpg
similarity index 100%
rename from public/img/products/dell-venue.0.jpg
rename to public/img/phones/dell-venue.0.jpg
diff --git a/public/img/products/dell-venue.1.jpg b/public/img/phones/dell-venue.1.jpg
similarity index 100%
rename from public/img/products/dell-venue.1.jpg
rename to public/img/phones/dell-venue.1.jpg
diff --git a/public/img/products/dell-venue.2.jpg b/public/img/phones/dell-venue.2.jpg
similarity index 100%
rename from public/img/products/dell-venue.2.jpg
rename to public/img/phones/dell-venue.2.jpg
diff --git a/public/img/products/dell-venue.3.jpg b/public/img/phones/dell-venue.3.jpg
similarity index 100%
rename from public/img/products/dell-venue.3.jpg
rename to public/img/phones/dell-venue.3.jpg
diff --git a/public/img/products/dell-venue.4.jpg b/public/img/phones/dell-venue.4.jpg
similarity index 100%
rename from public/img/products/dell-venue.4.jpg
rename to public/img/phones/dell-venue.4.jpg
diff --git a/public/img/products/dell-venue.5.jpg b/public/img/phones/dell-venue.5.jpg
similarity index 100%
rename from public/img/products/dell-venue.5.jpg
rename to public/img/phones/dell-venue.5.jpg
diff --git a/public/img/products/droid-2-global-by-motorola.0.jpg b/public/img/phones/droid-2-global-by-motorola.0.jpg
similarity index 100%
rename from public/img/products/droid-2-global-by-motorola.0.jpg
rename to public/img/phones/droid-2-global-by-motorola.0.jpg
diff --git a/public/img/products/droid-2-global-by-motorola.1.jpg b/public/img/phones/droid-2-global-by-motorola.1.jpg
similarity index 100%
rename from public/img/products/droid-2-global-by-motorola.1.jpg
rename to public/img/phones/droid-2-global-by-motorola.1.jpg
diff --git a/public/img/products/droid-2-global-by-motorola.2.jpg b/public/img/phones/droid-2-global-by-motorola.2.jpg
similarity index 100%
rename from public/img/products/droid-2-global-by-motorola.2.jpg
rename to public/img/phones/droid-2-global-by-motorola.2.jpg
diff --git a/public/img/products/droid-pro-by-motorola.0.jpg b/public/img/phones/droid-pro-by-motorola.0.jpg
similarity index 100%
rename from public/img/products/droid-pro-by-motorola.0.jpg
rename to public/img/phones/droid-pro-by-motorola.0.jpg
diff --git a/public/img/products/droid-pro-by-motorola.1.jpg b/public/img/phones/droid-pro-by-motorola.1.jpg
similarity index 100%
rename from public/img/products/droid-pro-by-motorola.1.jpg
rename to public/img/phones/droid-pro-by-motorola.1.jpg
diff --git a/public/img/products/lg-axis.0.jpg b/public/img/phones/lg-axis.0.jpg
similarity index 100%
rename from public/img/products/lg-axis.0.jpg
rename to public/img/phones/lg-axis.0.jpg
diff --git a/public/img/products/lg-axis.1.jpg b/public/img/phones/lg-axis.1.jpg
similarity index 100%
rename from public/img/products/lg-axis.1.jpg
rename to public/img/phones/lg-axis.1.jpg
diff --git a/public/img/products/lg-axis.2.jpg b/public/img/phones/lg-axis.2.jpg
similarity index 100%
rename from public/img/products/lg-axis.2.jpg
rename to public/img/phones/lg-axis.2.jpg
diff --git a/public/img/products/motorola-atrix-4g.0.jpg b/public/img/phones/motorola-atrix-4g.0.jpg
similarity index 100%
rename from public/img/products/motorola-atrix-4g.0.jpg
rename to public/img/phones/motorola-atrix-4g.0.jpg
diff --git a/public/img/products/motorola-atrix-4g.1.jpg b/public/img/phones/motorola-atrix-4g.1.jpg
similarity index 100%
rename from public/img/products/motorola-atrix-4g.1.jpg
rename to public/img/phones/motorola-atrix-4g.1.jpg
diff --git a/public/img/products/motorola-atrix-4g.2.jpg b/public/img/phones/motorola-atrix-4g.2.jpg
similarity index 100%
rename from public/img/products/motorola-atrix-4g.2.jpg
rename to public/img/phones/motorola-atrix-4g.2.jpg
diff --git a/public/img/products/motorola-atrix-4g.3.jpg b/public/img/phones/motorola-atrix-4g.3.jpg
similarity index 100%
rename from public/img/products/motorola-atrix-4g.3.jpg
rename to public/img/phones/motorola-atrix-4g.3.jpg
diff --git a/public/img/products/motorola-bravo-with-motoblur.0.jpg b/public/img/phones/motorola-bravo-with-motoblur.0.jpg
similarity index 100%
rename from public/img/products/motorola-bravo-with-motoblur.0.jpg
rename to public/img/phones/motorola-bravo-with-motoblur.0.jpg
diff --git a/public/img/products/motorola-bravo-with-motoblur.1.jpg b/public/img/phones/motorola-bravo-with-motoblur.1.jpg
similarity index 100%
rename from public/img/products/motorola-bravo-with-motoblur.1.jpg
rename to public/img/phones/motorola-bravo-with-motoblur.1.jpg
diff --git a/public/img/products/motorola-bravo-with-motoblur.2.jpg b/public/img/phones/motorola-bravo-with-motoblur.2.jpg
similarity index 100%
rename from public/img/products/motorola-bravo-with-motoblur.2.jpg
rename to public/img/phones/motorola-bravo-with-motoblur.2.jpg
diff --git a/public/img/products/motorola-charm-with-motoblur.0.jpg b/public/img/phones/motorola-charm-with-motoblur.0.jpg
similarity index 100%
rename from public/img/products/motorola-charm-with-motoblur.0.jpg
rename to public/img/phones/motorola-charm-with-motoblur.0.jpg
diff --git a/public/img/products/motorola-charm-with-motoblur.1.jpg b/public/img/phones/motorola-charm-with-motoblur.1.jpg
similarity index 100%
rename from public/img/products/motorola-charm-with-motoblur.1.jpg
rename to public/img/phones/motorola-charm-with-motoblur.1.jpg
diff --git a/public/img/products/motorola-charm-with-motoblur.2.jpg b/public/img/phones/motorola-charm-with-motoblur.2.jpg
similarity index 100%
rename from public/img/products/motorola-charm-with-motoblur.2.jpg
rename to public/img/phones/motorola-charm-with-motoblur.2.jpg
diff --git a/public/img/products/motorola-defy-with-motoblur.0.jpg b/public/img/phones/motorola-defy-with-motoblur.0.jpg
similarity index 100%
rename from public/img/products/motorola-defy-with-motoblur.0.jpg
rename to public/img/phones/motorola-defy-with-motoblur.0.jpg
diff --git a/public/img/products/motorola-defy-with-motoblur.1.jpg b/public/img/phones/motorola-defy-with-motoblur.1.jpg
similarity index 100%
rename from public/img/products/motorola-defy-with-motoblur.1.jpg
rename to public/img/phones/motorola-defy-with-motoblur.1.jpg
diff --git a/public/img/products/motorola-defy-with-motoblur.2.jpg b/public/img/phones/motorola-defy-with-motoblur.2.jpg
similarity index 100%
rename from public/img/products/motorola-defy-with-motoblur.2.jpg
rename to public/img/phones/motorola-defy-with-motoblur.2.jpg
diff --git a/public/img/products/motorola-xoom-with-wi-fi.0.jpg b/public/img/phones/motorola-xoom-with-wi-fi.0.jpg
similarity index 100%
rename from public/img/products/motorola-xoom-with-wi-fi.0.jpg
rename to public/img/phones/motorola-xoom-with-wi-fi.0.jpg
diff --git a/public/img/products/motorola-xoom-with-wi-fi.1.jpg b/public/img/phones/motorola-xoom-with-wi-fi.1.jpg
similarity index 100%
rename from public/img/products/motorola-xoom-with-wi-fi.1.jpg
rename to public/img/phones/motorola-xoom-with-wi-fi.1.jpg
diff --git a/public/img/products/motorola-xoom-with-wi-fi.2.jpg b/public/img/phones/motorola-xoom-with-wi-fi.2.jpg
similarity index 100%
rename from public/img/products/motorola-xoom-with-wi-fi.2.jpg
rename to public/img/phones/motorola-xoom-with-wi-fi.2.jpg
diff --git a/public/img/products/motorola-xoom-with-wi-fi.3.jpg b/public/img/phones/motorola-xoom-with-wi-fi.3.jpg
similarity index 100%
rename from public/img/products/motorola-xoom-with-wi-fi.3.jpg
rename to public/img/phones/motorola-xoom-with-wi-fi.3.jpg
diff --git a/public/img/products/motorola-xoom-with-wi-fi.4.jpg b/public/img/phones/motorola-xoom-with-wi-fi.4.jpg
similarity index 100%
rename from public/img/products/motorola-xoom-with-wi-fi.4.jpg
rename to public/img/phones/motorola-xoom-with-wi-fi.4.jpg
diff --git a/public/img/products/motorola-xoom-with-wi-fi.5.jpg b/public/img/phones/motorola-xoom-with-wi-fi.5.jpg
similarity index 100%
rename from public/img/products/motorola-xoom-with-wi-fi.5.jpg
rename to public/img/phones/motorola-xoom-with-wi-fi.5.jpg
diff --git a/public/img/products/motorola-xoom.0.jpg b/public/img/phones/motorola-xoom.0.jpg
similarity index 100%
rename from public/img/products/motorola-xoom.0.jpg
rename to public/img/phones/motorola-xoom.0.jpg
diff --git a/public/img/products/motorola-xoom.1.jpg b/public/img/phones/motorola-xoom.1.jpg
similarity index 100%
rename from public/img/products/motorola-xoom.1.jpg
rename to public/img/phones/motorola-xoom.1.jpg
diff --git a/public/img/products/motorola-xoom.2.jpg b/public/img/phones/motorola-xoom.2.jpg
similarity index 100%
rename from public/img/products/motorola-xoom.2.jpg
rename to public/img/phones/motorola-xoom.2.jpg
diff --git a/public/img/products/nexus-s.0.jpg b/public/img/phones/nexus-s.0.jpg
similarity index 100%
rename from public/img/products/nexus-s.0.jpg
rename to public/img/phones/nexus-s.0.jpg
diff --git a/public/img/products/nexus-s.1.jpg b/public/img/phones/nexus-s.1.jpg
similarity index 100%
rename from public/img/products/nexus-s.1.jpg
rename to public/img/phones/nexus-s.1.jpg
diff --git a/public/img/products/nexus-s.2.jpg b/public/img/phones/nexus-s.2.jpg
similarity index 100%
rename from public/img/products/nexus-s.2.jpg
rename to public/img/phones/nexus-s.2.jpg
diff --git a/public/img/products/nexus-s.3.jpg b/public/img/phones/nexus-s.3.jpg
similarity index 100%
rename from public/img/products/nexus-s.3.jpg
rename to public/img/phones/nexus-s.3.jpg
diff --git a/public/img/products/samsung-galaxy-tab.0.jpg b/public/img/phones/samsung-galaxy-tab.0.jpg
similarity index 100%
rename from public/img/products/samsung-galaxy-tab.0.jpg
rename to public/img/phones/samsung-galaxy-tab.0.jpg
diff --git a/public/img/products/samsung-galaxy-tab.1.jpg b/public/img/phones/samsung-galaxy-tab.1.jpg
similarity index 100%
rename from public/img/products/samsung-galaxy-tab.1.jpg
rename to public/img/phones/samsung-galaxy-tab.1.jpg
diff --git a/public/img/products/samsung-galaxy-tab.2.jpg b/public/img/phones/samsung-galaxy-tab.2.jpg
similarity index 100%
rename from public/img/products/samsung-galaxy-tab.2.jpg
rename to public/img/phones/samsung-galaxy-tab.2.jpg
diff --git a/public/img/products/samsung-galaxy-tab.3.jpg b/public/img/phones/samsung-galaxy-tab.3.jpg
similarity index 100%
rename from public/img/products/samsung-galaxy-tab.3.jpg
rename to public/img/phones/samsung-galaxy-tab.3.jpg
diff --git a/public/img/products/samsung-galaxy-tab.4.jpg b/public/img/phones/samsung-galaxy-tab.4.jpg
similarity index 100%
rename from public/img/products/samsung-galaxy-tab.4.jpg
rename to public/img/phones/samsung-galaxy-tab.4.jpg
diff --git a/public/img/products/samsung-galaxy-tab.5.jpg b/public/img/phones/samsung-galaxy-tab.5.jpg
similarity index 100%
rename from public/img/products/samsung-galaxy-tab.5.jpg
rename to public/img/phones/samsung-galaxy-tab.5.jpg
diff --git a/public/img/products/samsung-galaxy-tab.6.jpg b/public/img/phones/samsung-galaxy-tab.6.jpg
similarity index 100%
rename from public/img/products/samsung-galaxy-tab.6.jpg
rename to public/img/phones/samsung-galaxy-tab.6.jpg
diff --git a/public/img/products/samsung-gem.0.jpg b/public/img/phones/samsung-gem.0.jpg
similarity index 100%
rename from public/img/products/samsung-gem.0.jpg
rename to public/img/phones/samsung-gem.0.jpg
diff --git a/public/img/products/samsung-gem.1.jpg b/public/img/phones/samsung-gem.1.jpg
similarity index 100%
rename from public/img/products/samsung-gem.1.jpg
rename to public/img/phones/samsung-gem.1.jpg
diff --git a/public/img/products/samsung-gem.2.jpg b/public/img/phones/samsung-gem.2.jpg
similarity index 100%
rename from public/img/products/samsung-gem.2.jpg
rename to public/img/phones/samsung-gem.2.jpg
diff --git a/public/img/products/samsung-mesmerize-a-galaxy-s-phone.0.jpg b/public/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg
similarity index 100%
rename from public/img/products/samsung-mesmerize-a-galaxy-s-phone.0.jpg
rename to public/img/phones/samsung-mesmerize-a-galaxy-s-phone.0.jpg
diff --git a/public/img/products/samsung-mesmerize-a-galaxy-s-phone.1.jpg b/public/img/phones/samsung-mesmerize-a-galaxy-s-phone.1.jpg
similarity index 100%
rename from public/img/products/samsung-mesmerize-a-galaxy-s-phone.1.jpg
rename to public/img/phones/samsung-mesmerize-a-galaxy-s-phone.1.jpg
diff --git a/public/img/products/samsung-mesmerize-a-galaxy-s-phone.2.jpg b/public/img/phones/samsung-mesmerize-a-galaxy-s-phone.2.jpg
similarity index 100%
rename from public/img/products/samsung-mesmerize-a-galaxy-s-phone.2.jpg
rename to public/img/phones/samsung-mesmerize-a-galaxy-s-phone.2.jpg
diff --git a/public/img/products/samsung-mesmerize-a-galaxy-s-phone.3.jpg b/public/img/phones/samsung-mesmerize-a-galaxy-s-phone.3.jpg
similarity index 100%
rename from public/img/products/samsung-mesmerize-a-galaxy-s-phone.3.jpg
rename to public/img/phones/samsung-mesmerize-a-galaxy-s-phone.3.jpg
diff --git a/public/img/products/samsung-showcase-a-galaxy-s-phone.0.jpg b/public/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg
similarity index 100%
rename from public/img/products/samsung-showcase-a-galaxy-s-phone.0.jpg
rename to public/img/phones/samsung-showcase-a-galaxy-s-phone.0.jpg
diff --git a/public/img/products/samsung-showcase-a-galaxy-s-phone.1.jpg b/public/img/phones/samsung-showcase-a-galaxy-s-phone.1.jpg
similarity index 100%
rename from public/img/products/samsung-showcase-a-galaxy-s-phone.1.jpg
rename to public/img/phones/samsung-showcase-a-galaxy-s-phone.1.jpg
diff --git a/public/img/products/samsung-showcase-a-galaxy-s-phone.2.jpg b/public/img/phones/samsung-showcase-a-galaxy-s-phone.2.jpg
similarity index 100%
rename from public/img/products/samsung-showcase-a-galaxy-s-phone.2.jpg
rename to public/img/phones/samsung-showcase-a-galaxy-s-phone.2.jpg
diff --git a/public/img/products/samsung-transform.0.jpg b/public/img/phones/samsung-transform.0.jpg
similarity index 100%
rename from public/img/products/samsung-transform.0.jpg
rename to public/img/phones/samsung-transform.0.jpg
diff --git a/public/img/products/samsung-transform.1.jpg b/public/img/phones/samsung-transform.1.jpg
similarity index 100%
rename from public/img/products/samsung-transform.1.jpg
rename to public/img/phones/samsung-transform.1.jpg
diff --git a/public/img/products/samsung-transform.2.jpg b/public/img/phones/samsung-transform.2.jpg
similarity index 100%
rename from public/img/products/samsung-transform.2.jpg
rename to public/img/phones/samsung-transform.2.jpg
diff --git a/public/img/products/samsung-transform.3.jpg b/public/img/phones/samsung-transform.3.jpg
similarity index 100%
rename from public/img/products/samsung-transform.3.jpg
rename to public/img/phones/samsung-transform.3.jpg
diff --git a/public/img/products/samsung-transform.4.jpg b/public/img/phones/samsung-transform.4.jpg
similarity index 100%
rename from public/img/products/samsung-transform.4.jpg
rename to public/img/phones/samsung-transform.4.jpg
diff --git a/public/img/products/sanyo-zio.0.jpg b/public/img/phones/sanyo-zio.0.jpg
similarity index 100%
rename from public/img/products/sanyo-zio.0.jpg
rename to public/img/phones/sanyo-zio.0.jpg
diff --git a/public/img/products/sanyo-zio.1.jpg b/public/img/phones/sanyo-zio.1.jpg
similarity index 100%
rename from public/img/products/sanyo-zio.1.jpg
rename to public/img/phones/sanyo-zio.1.jpg
diff --git a/public/img/products/sanyo-zio.2.jpg b/public/img/phones/sanyo-zio.2.jpg
similarity index 100%
rename from public/img/products/sanyo-zio.2.jpg
rename to public/img/phones/sanyo-zio.2.jpg
diff --git a/public/img/products/t-mobile-g2.0.jpg b/public/img/phones/t-mobile-g2.0.jpg
similarity index 100%
rename from public/img/products/t-mobile-g2.0.jpg
rename to public/img/phones/t-mobile-g2.0.jpg
diff --git a/public/img/products/t-mobile-g2.1.jpg b/public/img/phones/t-mobile-g2.1.jpg
similarity index 100%
rename from public/img/products/t-mobile-g2.1.jpg
rename to public/img/phones/t-mobile-g2.1.jpg
diff --git a/public/img/products/t-mobile-g2.2.jpg b/public/img/phones/t-mobile-g2.2.jpg
similarity index 100%
rename from public/img/products/t-mobile-g2.2.jpg
rename to public/img/phones/t-mobile-g2.2.jpg
diff --git a/public/img/products/t-mobile-mytouch-4g.0.jpg b/public/img/phones/t-mobile-mytouch-4g.0.jpg
similarity index 100%
rename from public/img/products/t-mobile-mytouch-4g.0.jpg
rename to public/img/phones/t-mobile-mytouch-4g.0.jpg
diff --git a/public/img/products/t-mobile-mytouch-4g.1.jpg b/public/img/phones/t-mobile-mytouch-4g.1.jpg
similarity index 100%
rename from public/img/products/t-mobile-mytouch-4g.1.jpg
rename to public/img/phones/t-mobile-mytouch-4g.1.jpg
diff --git a/public/img/products/t-mobile-mytouch-4g.2.jpg b/public/img/phones/t-mobile-mytouch-4g.2.jpg
similarity index 100%
rename from public/img/products/t-mobile-mytouch-4g.2.jpg
rename to public/img/phones/t-mobile-mytouch-4g.2.jpg
diff --git a/public/img/products/t-mobile-mytouch-4g.3.jpg b/public/img/phones/t-mobile-mytouch-4g.3.jpg
similarity index 100%
rename from public/img/products/t-mobile-mytouch-4g.3.jpg
rename to public/img/phones/t-mobile-mytouch-4g.3.jpg
diff --git a/public/img/products/t-mobile-mytouch-4g.4.jpg b/public/img/phones/t-mobile-mytouch-4g.4.jpg
similarity index 100%
rename from public/img/products/t-mobile-mytouch-4g.4.jpg
rename to public/img/phones/t-mobile-mytouch-4g.4.jpg
diff --git a/public/img/products/t-mobile-mytouch-4g.5.jpg b/public/img/phones/t-mobile-mytouch-4g.5.jpg
similarity index 100%
rename from public/img/products/t-mobile-mytouch-4g.5.jpg
rename to public/img/phones/t-mobile-mytouch-4g.5.jpg
diff --git a/public/img/picthree.bdd2e0fc.png b/public/img/picthree.bdd2e0fc.png
new file mode 100644
index 0000000000..28b5c4c99a
Binary files /dev/null and b/public/img/picthree.bdd2e0fc.png differ
diff --git a/public/index.html b/public/index.html
index 4b622dad39..2ba173b393 100644
--- a/public/index.html
+++ b/public/index.html
@@ -3,6 +3,7 @@
+
Phone catalog
diff --git a/src/App.scss b/src/App.scss
index 71bc413aad..79be5b867c 100644
--- a/src/App.scss
+++ b/src/App.scss
@@ -1 +1,30 @@
-// not empty
+@font-face {
+ font-family: Mont-Regular;
+ src: url("../src/fonts/Mont-Regular.otf") format("truetype");
+}
+@font-face {
+ font-family: Mont-SemiBold;
+ src: url("../src/fonts/Mont-SemiBold.otf") format("truetype");
+}
+@font-face {
+ font-family: Mont-Bold;
+ src: url("../src/fonts/Mont-Bold.otf") format("truetype");
+}
+
+html,
+body {
+ margin: 0;
+ scroll-behavior: smooth;
+}
+
+button {
+ cursor: pointer;
+}
+
+.App {
+ font-family: Mont-Regular, Arial, Helvetica, sans-serif;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ min-height: 100vh;
+}
diff --git a/src/App.tsx b/src/App.tsx
index a1715e52b3..b4308f1615 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,9 +1,147 @@
+import { FC, useEffect } from 'react';
+import {
+ Navigate, Route, Routes, useLocation,
+} from 'react-router-dom';
import './App.scss';
-const App = () => (
-
-
React Phone Catalog
-
-);
+import { Header } from './pages/components/Header';
+import { HomePage } from './pages/HomePage';
+import { PhonesPage } from './pages/PhonesPage';
+import { Footer } from './pages/components/Footer';
+import { ProductDetailsPage } from './pages/ProductDetailsPage';
+import { useAppDispatch, useAppSelector } from './app/hooks';
+import { incrementAsync as loadedPhones } from './features/phones/phonesSlice';
+import { FavoritesPage } from './pages/FavoritesPage';
+import { CardPage } from './pages/CardPage';
+import { TabletsPage } from './pages/TabletsPage';
+import {
+ incrementAsync as loadedProducts,
+} from './features/products/productsSlice';
+import { AccessoriesPage } from './pages/AccessoriesPage';
+import { searchBarSelector } from './app/selector';
+import { unsetSearchingValue } from './features/SearchBar/searchBarSlice';
-export default App;
+export const App: FC = () => {
+ const dispatch = useAppDispatch();
+ const locationPathName = useLocation().pathname;
+ const searchBarValue = useAppSelector(searchBarSelector);
+
+ useEffect(() => {
+ if (searchBarValue) {
+ dispatch(unsetSearchingValue());
+ }
+ }, [locationPathName]);
+
+ useEffect(() => {
+ dispatch(loadedPhones());
+ dispatch(loadedProducts());
+ }, []);
+
+ return (
+
+
+
+
+
+
+ >
+ )}
+ />
+ } />
+
+
+
+
+
+ >
+ )}
+ />
+
+
+
+
+ >
+ )}
+ />
+
+
+
+
+
+
+ >
+ )}
+ />
+
+
+
+
+ >
+ )}
+ />
+
+
+
+
+
+
+ >
+ )}
+ />
+
+
+
+
+ >
+ )}
+ />
+
+
+
+
+
+ >
+ )}
+ />
+
+
+
+
+ >
+ )}
+ />
+ Page not found}
+ />
+
+
+ );
+};
diff --git a/src/api/banners.json b/src/api/banners.json
new file mode 100644
index 0000000000..c7b25e65fe
--- /dev/null
+++ b/src/api/banners.json
@@ -0,0 +1,54 @@
+[
+ {
+ "age": 0,
+ "id": "banner1",
+ "type": "banner",
+ "imageUrl": "images/BannerHomePage.png",
+ "name": "Banner phones",
+ "snippet": "Best phones in our shop",
+ "price": 100,
+ "discount": 0,
+ "screen": "Full size",
+ "capacity": "",
+ "ram": ""
+ },
+ {
+ "age": 0,
+ "id": "banner2",
+ "type": "banner",
+ "imageUrl": "images/BannerHomePage.png",
+ "name": "Banner phones",
+ "snippet": "Best phones in our shop",
+ "price": 100,
+ "discount": 0,
+ "screen": "Full size",
+ "capacity": "",
+ "ram": ""
+ },
+ {
+ "age": 0,
+ "id": "banner3",
+ "type": "banner",
+ "imageUrl": "images/BannerHomePage.png",
+ "name": "Banner phones",
+ "snippet": "Best phones in our shop",
+ "price": 100,
+ "discount": 0,
+ "screen": "Full size",
+ "capacity": "",
+ "ram": ""
+ },
+ {
+ "age": 0,
+ "id": "banner4",
+ "type": "banner",
+ "imageUrl": "images/BannerHomePage.png",
+ "name": "Banner phones",
+ "snippet": "Best phones in our shop",
+ "price": 100,
+ "discount": 0,
+ "screen": "Full size",
+ "capacity": "",
+ "ram": ""
+ }
+]
diff --git a/src/api/phone.ts b/src/api/phone.ts
new file mode 100644
index 0000000000..3aa613b234
--- /dev/null
+++ b/src/api/phone.ts
@@ -0,0 +1,20 @@
+import { Product } from '../types/Product';
+import { PhoneDetails } from '../types/PhoneDetails';
+import { client } from '../utils/fetchClient';
+import { OldApiPhoneDetails } from '../types/OldApiPhoneDetails';
+
+export const getPhones = () => {
+ return client.get('/_new/products.json');
+};
+
+export const getPhoneDetails = (phoneId: string) => {
+ return client.get(`/_new/products/${phoneId}.json`);
+};
+
+export const getProductDetails = (phoneId: string) => {
+ return client.get(`/api/products/${phoneId}.json`);
+};
+
+export const getProducts = () => {
+ return client.get('/api/products.json');
+};
diff --git a/src/app/helpers.ts b/src/app/helpers.ts
new file mode 100644
index 0000000000..cf356ed3e5
--- /dev/null
+++ b/src/app/helpers.ts
@@ -0,0 +1,61 @@
+import { OldApiPhoneDetails } from '../types/OldApiPhoneDetails';
+import { PhoneDetails } from '../types/PhoneDetails';
+import { Product } from '../types/Product';
+
+export const phoneDetailsSameType = (
+ phoneDetails: OldApiPhoneDetails,
+ selectedPhone: Product,
+): PhoneDetails => {
+ let features = '';
+ let connectivity = '';
+
+ Object.keys(phoneDetails.connectivity).forEach((key) => {
+ if (key === 'gps' && phoneDetails.connectivity.gps === 'true') {
+ connectivity += 'gps';
+ } else if (typeof phoneDetails.connectivity[key] === 'boolean') {
+ if (phoneDetails.connectivity[key] === 'true') {
+ connectivity += key;
+ }
+ } else {
+ connectivity += phoneDetails.connectivity[key];
+ }
+ });
+
+ phoneDetails.camera.features.forEach((item, index) => {
+ features += item;
+ if (index !== phoneDetails.camera.features.length - 1) {
+ features += ', ';
+ }
+ });
+
+ const priceWithDiscount = selectedPhone.discount
+ ? selectedPhone.price - selectedPhone.discount
+ : selectedPhone.price;
+
+ const capacityGB = `${Math.floor(+phoneDetails.storage.flash.slice(0, -2) / 1000)}GB`;
+
+ return {
+ id: phoneDetails.id,
+ namespaceId: selectedPhone.id,
+ name: selectedPhone.name,
+ capacity: capacityGB,
+ capacityAvailable: [capacityGB],
+ priceRegular: selectedPhone.price,
+ priceDiscount: priceWithDiscount,
+ colorsAvailable: [''],
+ color: '',
+ images: phoneDetails.images,
+ description: [
+ { title: 'Introducing', text: [phoneDetails.description] },
+ { title: 'More about', text: [phoneDetails.description] },
+ { title: 'Finally', text: [phoneDetails.description] },
+ ],
+ screen: ` ${phoneDetails.display.screenSize} ${phoneDetails.display.touchScreen ? 'screen touch' : ''}`,
+ resolution: phoneDetails.display.screenResolution,
+ processor: `${phoneDetails.android.ui} ${phoneDetails.android.os} ${phoneDetails.hardware.cpu}`,
+ ram: phoneDetails.storage.ram,
+ camera: phoneDetails.camera.primary,
+ zoom: 'no',
+ cell: [phoneDetails.battery.type, features, connectivity],
+ };
+};
diff --git a/src/app/hooks.ts b/src/app/hooks.ts
new file mode 100644
index 0000000000..f06605041c
--- /dev/null
+++ b/src/app/hooks.ts
@@ -0,0 +1,34 @@
+import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
+import { SetURLSearchParams } from 'react-router-dom';
+import type { RootState, AppDispatch } from './store';
+import { Product } from '../types/Product';
+import { SelectedOptions } from '../types/SelectedOptions';
+import { AsyncStatus } from '../types/AsyncStatus';
+import { getVisibleProducts } from './utils';
+
+// Use these hooks everywhere instead of useDispatch and useSelector
+export const useAppDispatch = () => useDispatch();
+export const useAppSelector: TypedUseSelectorHook = useSelector;
+export function updateStateProductsAndUrl(
+ setVisibleStateOfProducts: React.Dispatch>,
+ productsFromApi: Product[],
+ selectedOptions: SelectedOptions,
+ statusOfLoadingProducts: AsyncStatus,
+ currentPage: number,
+ setSearchParams: SetURLSearchParams,
+) {
+ if (statusOfLoadingProducts === AsyncStatus.IDLE
+ && productsFromApi.length > 0) {
+ const result = getVisibleProducts(productsFromApi, selectedOptions);
+
+ setVisibleStateOfProducts(result);
+ }
+
+ const params = new URLSearchParams();
+
+ params.set('page', `${currentPage}`);
+ params.set('sort', selectedOptions.sortBy);
+ params.set('perPage', selectedOptions.itemsShow as string);
+
+ setSearchParams(params);
+}
diff --git a/src/app/selector.ts b/src/app/selector.ts
new file mode 100644
index 0000000000..7bffd99008
--- /dev/null
+++ b/src/app/selector.ts
@@ -0,0 +1,46 @@
+// eslint-disable-next-line import/no-cycle
+import { RootState } from './store';
+
+export const phoneCardSelector = (
+ state: RootState,
+) => state.phonesCarded.value;
+
+export const favoriteProductsSelector = (
+ state: RootState,
+) => state.phonesFavorites.value;
+
+export const productsSelector = (
+ state: RootState,
+) => state.products.value;
+
+export const productsStatusSelector = (
+ state: RootState,
+) => state.products.status;
+
+export const selectedPhoneSelector = (
+ state: RootState,
+) => state.selectedPhone.value;
+
+export const selectedPhoneStatusSelector = (
+ state: RootState,
+) => state.selectedPhone.status;
+
+export const searchBarSelector = (
+ state: RootState,
+) => state.searchBar.value;
+
+export const phonesSelector = (
+ state: RootState,
+) => state.phones.value;
+
+export const phonesStatusSelector = (
+ state: RootState,
+) => state.phones.status;
+
+export const phonesDetailsSelector = (
+ state: RootState,
+) => state.phoneDetails.value;
+
+export const phonesDetaildStatusSelector = (
+ state: RootState,
+) => state.phoneDetails.status;
diff --git a/src/app/store.ts b/src/app/store.ts
new file mode 100644
index 0000000000..0375c21b84
--- /dev/null
+++ b/src/app/store.ts
@@ -0,0 +1,45 @@
+/* eslint-disable import/no-cycle */
+import { configureStore, ThunkAction, Action } from '@reduxjs/toolkit';
+import { reducer as phonesReducer } from '../features/phones/phonesSlice';
+import {
+ reducer as selectedPhoneReducer,
+} from '../features/selectedPhone/selectedPhoneSlice';
+import {
+ reducer as phoneDetailsReducer,
+} from '../features/PhoneDetails/phoneDetailsSlice';
+import {
+ reducer as phonesFavoritesReducer,
+} from '../features/PhonesFavorites/phonesFavoritesSlice';
+import {
+ reducer as phonesCardedReducer,
+} from '../features/PhonesInCard/phonesInCardSlice';
+import {
+ reducer as searchBarReducer,
+} from '../features/SearchBar/searchBarSlice';
+import {
+ reducer as productsReducer,
+} from '../features/products/productsSlice';
+
+const reducer = {
+ phones: phonesReducer,
+ selectedPhone: selectedPhoneReducer,
+ phoneDetails: phoneDetailsReducer,
+ phonesFavorites: phonesFavoritesReducer,
+ phonesCarded: phonesCardedReducer,
+ searchBar: searchBarReducer,
+ products: productsReducer,
+};
+
+export const store = configureStore({ reducer });
+
+export type AppDispatch = typeof store.dispatch;
+export type RootState = ReturnType;
+
+/* eslint-disable @typescript-eslint/indent */
+export type AppThunk = ThunkAction<
+ ReturnType,
+ RootState,
+ unknown,
+ Action
+>;
+/* eslint-enable @typescript-eslint/indent */
diff --git a/src/app/utils.ts b/src/app/utils.ts
new file mode 100644
index 0000000000..ceb589e4d5
--- /dev/null
+++ b/src/app/utils.ts
@@ -0,0 +1,47 @@
+import { Product } from '../types/Product';
+import { SelectedOptions } from '../types/SelectedOptions';
+import { SortByOptions } from '../types/SortByOptions';
+
+export function getVisibleProducts(
+ arr: Product[],
+ selectedOptions: SelectedOptions,
+) {
+ let result: Product[] = [...arr];
+
+ switch (selectedOptions.sortBy) {
+ case SortByOptions.AGE:
+ result = result.sort((a, b) => b.year - a.year);
+ break;
+ case SortByOptions.NAME:
+ result = result.sort((a, b) => a.name.localeCompare(b.name));
+ break;
+ case SortByOptions.PRICE:
+ result = result.sort((a, b) => a.price - b.price);
+ break;
+
+ default:
+ break;
+ }
+
+ return result;
+}
+
+export function filteringVisibleSearchedProducts(
+ visibleProducts: Product[],
+ searchBarValue: string,
+): Product[] {
+ if (!searchBarValue) {
+ return visibleProducts;
+ }
+
+ return visibleProducts.filter((product) => {
+ if (searchBarValue.trim() === '') {
+ return true;
+ }
+
+ const queryWords = searchBarValue.toLowerCase().split(' ');
+ const productName = product.name.toLowerCase();
+
+ return queryWords.every((word) => productName.includes(word));
+ });
+}
diff --git a/src/features/PhoneDetails/phoneDetailsSlice.ts b/src/features/PhoneDetails/phoneDetailsSlice.ts
new file mode 100644
index 0000000000..b27ba388bf
--- /dev/null
+++ b/src/features/PhoneDetails/phoneDetailsSlice.ts
@@ -0,0 +1,76 @@
+/* eslint-disable import/no-cycle */
+/* eslint-disable no-param-reassign */
+import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
+import { PhoneDetails } from '../../types/PhoneDetails';
+import { getPhoneDetails, getProductDetails } from '../../api/phone';
+import { AsyncStatus } from '../../types/AsyncStatus';
+import { KeyJson } from '../../types/KeyJson';
+import { phoneDetailsSameType } from '../../app/helpers';
+import { Product } from '../../types/Product';
+
+export interface PhonesState {
+ value: PhoneDetails | null,
+ status: AsyncStatus,
+}
+const localeStorage = window.localStorage.getItem(KeyJson.DETAILS);
+
+const initialState: PhonesState = {
+ value: localeStorage
+ ? JSON.parse(localeStorage)
+ : null,
+ status: AsyncStatus.IDLE,
+};
+
+export const incrementAsync = createAsyncThunk(
+ 'phoneDetails/fetchPhones',
+ async (phoneId: string) => {
+ const phoneDetails = await getPhoneDetails(phoneId);
+
+ return phoneDetails;
+ },
+);
+
+export const incrementAsyncOld = createAsyncThunk(
+ 'phoneDetails/fetchPhonesOld',
+ async (phone: Product) => {
+ const phoneDetails = await getProductDetails(phone.id);
+ const sameTypeDetails = phoneDetailsSameType({
+ ...phoneDetails,
+ id: phone.id,
+ }, phone);
+
+ return sameTypeDetails;
+ },
+);
+
+const phoneDetaildSlice = createSlice({
+ name: 'phoneDetails',
+ initialState,
+ reducers: {
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(incrementAsync.pending, (state) => {
+ state.status = AsyncStatus.LOADING;
+ })
+ .addCase(incrementAsync.fulfilled, (state, action) => {
+ state.status = AsyncStatus.IDLE;
+ state.value = action.payload;
+ })
+ .addCase(incrementAsync.rejected, (state) => {
+ state.status = AsyncStatus.FAILED;
+ })
+ .addCase(incrementAsyncOld.pending, (state) => {
+ state.status = AsyncStatus.LOADING;
+ })
+ .addCase(incrementAsyncOld.fulfilled, (state, action) => {
+ state.status = AsyncStatus.IDLE;
+ state.value = action.payload;
+ })
+ .addCase(incrementAsyncOld.rejected, (state) => {
+ state.status = AsyncStatus.FAILED;
+ });
+ },
+});
+
+export const { reducer, actions } = phoneDetaildSlice;
diff --git a/src/features/PhonesFavorites/phonesFavoritesSlice.ts b/src/features/PhonesFavorites/phonesFavoritesSlice.ts
new file mode 100644
index 0000000000..1291f1c580
--- /dev/null
+++ b/src/features/PhonesFavorites/phonesFavoritesSlice.ts
@@ -0,0 +1,34 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+/* eslint-disable no-param-reassign */
+import { Product } from '../../types/Product';
+import { SelectedStatus } from '../../types/SelectedStatus';
+
+export interface FavoritePhoneReducer {
+ value: Product[],
+ status: SelectedStatus
+}
+
+const initialState: FavoritePhoneReducer = {
+ value: [],
+ status: SelectedStatus.UNSELECTED,
+};
+
+const favoritePhoneReducer = createSlice({
+ name: 'likedPhone',
+ initialState,
+ reducers: {
+ setFavoritePhone: (state, action: PayloadAction) => {
+ state.status = SelectedStatus.SELECTED;
+ state.value.push(action.payload);
+ },
+ unsetFavoritePhone: (state, action: PayloadAction) => {
+ state.status = SelectedStatus.UNSELECTED;
+ state.value = state.value.filter(phone => phone.id !== action.payload.id);
+ },
+ },
+});
+
+export const { reducer } = favoritePhoneReducer;
+export const {
+ setFavoritePhone, unsetFavoritePhone,
+} = favoritePhoneReducer.actions;
diff --git a/src/features/PhonesInCard/phonesInCardSlice.ts b/src/features/PhonesInCard/phonesInCardSlice.ts
new file mode 100644
index 0000000000..e06f9b8115
--- /dev/null
+++ b/src/features/PhonesInCard/phonesInCardSlice.ts
@@ -0,0 +1,77 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+/* eslint-disable no-param-reassign */
+import { Product } from '../../types/Product';
+import { SelectedStatus } from '../../types/SelectedStatus';
+import { SavedCard } from '../../types/SavedCard';
+import { KeyJson } from '../../types/KeyJson';
+// eslint-disable-next-line import/no-cycle
+
+export interface CardPhoneReducer {
+ value: SavedCard[],
+ status: SelectedStatus
+}
+const localeStorage = window.localStorage.getItem(KeyJson.CARD);
+const cardsFormStorage: SavedCard[] | null = localeStorage
+ ? JSON.parse(localeStorage) : null;
+
+const initialState: CardPhoneReducer = {
+ value: cardsFormStorage || [],
+ status: SelectedStatus.UNSELECTED,
+};
+
+const cardedPhoneReducer = createSlice({
+ name: 'cardedPhones',
+ initialState,
+ reducers: {
+ increase: (state, action: PayloadAction) => {
+ state.value = state.value.map(card => {
+ if (action.payload.id === card.id) {
+ return {
+ ...card,
+ amount: card.amount + 1,
+ };
+ }
+
+ return card;
+ });
+ },
+ decrease: (state, action: PayloadAction) => {
+ state.value = state.value.map(card => {
+ if (action.payload.id === card.id) {
+ return {
+ ...card,
+ amount: Math.max(card.amount - 1, 1),
+ };
+ }
+
+ return card;
+ });
+ },
+
+ setInCardPhone: (state, action: PayloadAction) => {
+ state.status = SelectedStatus.SELECTED;
+ state.value.push({
+ id: action.payload.itemId ? action.payload.itemId : action.payload.id,
+ amount: 1,
+ value: action.payload,
+ });
+ },
+ unsetFromCardPhone: (state, action: PayloadAction) => {
+ state.status = SelectedStatus.UNSELECTED;
+ state.value = state.value.filter(
+ phone => {
+ if (action.payload.itemId) {
+ return phone.id !== action.payload.itemId;
+ }
+
+ return phone.id !== action.payload.id;
+ },
+ );
+ },
+ },
+});
+
+export const { reducer } = cardedPhoneReducer;
+export const {
+ setInCardPhone, unsetFromCardPhone, increase, decrease,
+} = cardedPhoneReducer.actions;
diff --git a/src/features/SearchBar/searchBarSlice.ts b/src/features/SearchBar/searchBarSlice.ts
new file mode 100644
index 0000000000..48cc98c647
--- /dev/null
+++ b/src/features/SearchBar/searchBarSlice.ts
@@ -0,0 +1,37 @@
+/* eslint-disable no-param-reassign */
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+
+export enum SearchBarStatus {
+ EMPTY = 'empty',
+ FILLED = 'filled',
+}
+
+export interface SeachBarReducer {
+ value: string,
+ status: SearchBarStatus,
+}
+
+const initialState: SeachBarReducer = {
+ value: '',
+ status: SearchBarStatus.EMPTY,
+};
+
+const searchBarReducer = createSlice({
+ name: 'searchBar',
+ initialState,
+ reducers: {
+ setSearchingValue: (state, action: PayloadAction) => {
+ state.status = SearchBarStatus.FILLED;
+ state.value = action.payload;
+ },
+ unsetSearchingValue: (state) => {
+ state.status = SearchBarStatus.EMPTY;
+ state.value = '';
+ },
+ },
+});
+
+export const { reducer } = searchBarReducer;
+export const {
+ setSearchingValue, unsetSearchingValue,
+} = searchBarReducer.actions;
diff --git a/src/features/phones/phonesSlice.ts b/src/features/phones/phonesSlice.ts
new file mode 100644
index 0000000000..5924d8bc63
--- /dev/null
+++ b/src/features/phones/phonesSlice.ts
@@ -0,0 +1,46 @@
+/* eslint-disable no-param-reassign */
+import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
+import { Product } from '../../types/Product';
+import { getPhones } from '../../api/phone';
+import { AsyncStatus } from '../../types/AsyncStatus';
+
+export interface PhonesState {
+ value: Product[],
+ status: AsyncStatus,
+}
+
+const initialState: PhonesState = {
+ value: [],
+ status: AsyncStatus.IDLE,
+};
+
+export const incrementAsync = createAsyncThunk(
+ 'phones/fetchPhones',
+ async () => {
+ const phones = await getPhones();
+
+ return phones;
+ },
+);
+
+const phonesSlice = createSlice({
+ name: 'phones',
+ initialState,
+ reducers: {
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(incrementAsync.pending, (state) => {
+ state.status = AsyncStatus.LOADING;
+ })
+ .addCase(incrementAsync.fulfilled, (state, action) => {
+ state.status = AsyncStatus.IDLE;
+ state.value = action.payload;
+ })
+ .addCase(incrementAsync.rejected, (state) => {
+ state.status = AsyncStatus.FAILED;
+ });
+ },
+});
+
+export const { reducer, actions } = phonesSlice;
diff --git a/src/features/products/productsSlice.ts b/src/features/products/productsSlice.ts
new file mode 100644
index 0000000000..6b00b38470
--- /dev/null
+++ b/src/features/products/productsSlice.ts
@@ -0,0 +1,46 @@
+/* eslint-disable no-param-reassign */
+import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
+import { getProducts } from '../../api/phone';
+import { AsyncStatus } from '../../types/AsyncStatus';
+import { Product } from '../../types/Product';
+
+export interface ProductsState {
+ value: Product[],
+ status: AsyncStatus,
+}
+
+const initialState: ProductsState = {
+ value: [],
+ status: AsyncStatus.IDLE,
+};
+
+export const incrementAsync = createAsyncThunk(
+ 'products/fetchProducts',
+ async () => {
+ const products = await getProducts();
+
+ return products;
+ },
+);
+
+const productsSlice = createSlice({
+ name: 'products',
+ initialState,
+ reducers: {
+ },
+ extraReducers: (builder) => {
+ builder
+ .addCase(incrementAsync.pending, (state) => {
+ state.status = AsyncStatus.LOADING;
+ })
+ .addCase(incrementAsync.fulfilled, (state, action) => {
+ state.status = AsyncStatus.IDLE;
+ state.value = action.payload;
+ })
+ .addCase(incrementAsync.rejected, (state) => {
+ state.status = AsyncStatus.FAILED;
+ });
+ },
+});
+
+export const { reducer, actions } = productsSlice;
diff --git a/src/features/selectedPhone/selectedPhoneSlice.ts b/src/features/selectedPhone/selectedPhoneSlice.ts
new file mode 100644
index 0000000000..8dcd85f1fc
--- /dev/null
+++ b/src/features/selectedPhone/selectedPhoneSlice.ts
@@ -0,0 +1,32 @@
+import { PayloadAction, createSlice } from '@reduxjs/toolkit';
+/* eslint-disable no-param-reassign */
+import { Product } from '../../types/Product';
+import { SelectedStatus } from '../../types/SelectedStatus';
+
+export interface SelectedPhoneReducer {
+ value: Product | null,
+ status: SelectedStatus
+}
+
+const initialState: SelectedPhoneReducer = {
+ value: null,
+ status: SelectedStatus.UNSELECTED,
+};
+
+const selectedPhoneReducer = createSlice({
+ name: 'selectedPhone',
+ initialState,
+ reducers: {
+ setPhone: (state, action: PayloadAction) => {
+ state.status = SelectedStatus.SELECTED;
+ state.value = action.payload;
+ },
+ unsetPhone: (state) => {
+ state.status = SelectedStatus.UNSELECTED;
+ state.value = null;
+ },
+ },
+});
+
+export const { reducer } = selectedPhoneReducer;
+export const { setPhone, unsetPhone } = selectedPhoneReducer.actions;
diff --git a/src/fonts/Mont-Bold.otf b/src/fonts/Mont-Bold.otf
new file mode 100644
index 0000000000..7f1598293a
Binary files /dev/null and b/src/fonts/Mont-Bold.otf differ
diff --git a/src/fonts/Mont-Regular.otf b/src/fonts/Mont-Regular.otf
new file mode 100644
index 0000000000..d5543feaf0
Binary files /dev/null and b/src/fonts/Mont-Regular.otf differ
diff --git a/src/fonts/Mont-SemiBold.otf b/src/fonts/Mont-SemiBold.otf
new file mode 100644
index 0000000000..a9fa16a9c5
Binary files /dev/null and b/src/fonts/Mont-SemiBold.otf differ
diff --git a/src/index.tsx b/src/index.tsx
index 5f7410fd92..8b8401159a 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,8 +1,16 @@
import ReactDOM from 'react-dom';
+import { Provider } from 'react-redux';
+import { HashRouter as Router } from 'react-router-dom';
-import App from './App';
+import { store } from './app/store';
+import { App } from './App';
-ReactDOM.render(
- ,
- document.getElementById('root'),
+const Root = () => (
+
+
+
+
+
);
+
+ReactDOM.render(, document.getElementById('root'));
diff --git a/src/pages/AccessoriesPage.tsx b/src/pages/AccessoriesPage.tsx
new file mode 100644
index 0000000000..551c41f419
--- /dev/null
+++ b/src/pages/AccessoriesPage.tsx
@@ -0,0 +1,142 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import {
+ FC, useEffect, useMemo, useState,
+} from 'react';
+import { useSearchParams } from 'react-router-dom';
+import Breadcrumbs from './components/Breadcrumbs';
+import { ProductsList } from './components/ProductsList';
+// eslint-disable-next-line import/no-cycle
+import CustomSelect from './components/CustomSelect';
+import '../styles/styles.scss';
+import { Pagination } from './components/Pagination';
+import {
+ updateStateProductsAndUrl,
+ useAppDispatch,
+ useAppSelector,
+} from '../app/hooks';
+import { Loader } from './components/Loader';
+import { AsyncStatus } from '../types/AsyncStatus';
+import {
+ incrementAsync as loadedProducts,
+} from '../features/products/productsSlice';
+import { Product } from '../types/Product';
+import { SortByOptions } from '../types/SortByOptions';
+import { SelectAmountItems } from '../types/SelectAmountItems';
+import { filteringVisibleSearchedProducts } from '../app/utils';
+import { itemsOnPageOptions, sortByOptions } from '../types/SelectOptionsArr';
+import {
+ productsSelector,
+ productsStatusSelector,
+ searchBarSelector,
+} from '../app/selector';
+
+export const AccessoriesPage: FC = () => {
+ const products: Product[] = useAppSelector(productsSelector);
+ const statusLadingProducts = useAppSelector(productsStatusSelector);
+ const [selectedOptions, setSelectedOptions] = useState({
+ sortBy: SortByOptions.AGE,
+ itemsShow: SelectAmountItems.SIXTEEN,
+ });
+ const [visibleProducts, setVisibleProducts] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+ const lastPhoneIndex = currentPage * +`${selectedOptions.itemsShow === 'all' ? Infinity : selectedOptions.itemsShow}`;
+ const firstPhoneIndex = lastPhoneIndex - +selectedOptions.itemsShow;
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const [_, setSearchParams] = useSearchParams();
+ const dispatch = useAppDispatch();
+ const searchBarValue = useAppSelector(searchBarSelector);
+
+ const paginate = (pagenumber: number) => setCurrentPage(pagenumber);
+
+ useEffect(() => {
+ dispatch(loadedProducts());
+ }, []);
+
+ useEffect(() => {
+ updateStateProductsAndUrl(
+ setVisibleProducts,
+ products,
+ selectedOptions,
+ statusLadingProducts,
+ currentPage,
+ setSearchParams,
+ );
+ setCurrentPage(1);
+ }, [selectedOptions]);
+
+ useEffect(() => {
+ updateStateProductsAndUrl(
+ setVisibleProducts,
+ products,
+ selectedOptions,
+ statusLadingProducts,
+ currentPage,
+ setSearchParams,
+ );
+ }, [currentPage, products]);
+
+ const accessoriesSearched = useMemo(() => {
+ return filteringVisibleSearchedProducts(
+ visibleProducts, searchBarValue,
+ ).filter(
+ p => p.type === 'accessory',
+ );
+ }, [products]);
+
+ const accessoriesSliced = accessoriesSearched.slice(
+ firstPhoneIndex, lastPhoneIndex,
+ );
+
+ return (
+
+ {!searchBarValue ? (
+ <>
+
+
Accessories
+
+ {`${accessoriesSearched.length} models`}
+
+ {accessoriesSearched.length > 0 && (
+
+
+
Sort by
+
+
+
+
Items on page
+
+
+
+ )}
+ >
+ ) : (
+
{`${accessoriesSearched.length} results`}
+ )}
+
+ {statusLadingProducts === AsyncStatus.LOADING ? (
+
+ ) : (
+ <>
+
+ {!searchBarValue
+ && accessoriesSearched.length > accessoriesSliced.length
+ && (
+
+ )}
+ >
+ )}
+
+ );
+};
diff --git a/src/pages/CardPage.tsx b/src/pages/CardPage.tsx
new file mode 100644
index 0000000000..5894a09be6
--- /dev/null
+++ b/src/pages/CardPage.tsx
@@ -0,0 +1,78 @@
+/* eslint-disable no-prototype-builtins */
+import { FC, useEffect, useState } from 'react';
+import { Link } from 'react-router-dom';
+import '../styles/styles.scss';
+// eslint-disable-next-line import/no-cycle
+import { CardedProduct } from './components/CardedProduct';
+import { useAppSelector } from '../app/hooks';
+import { PopUp } from './components/PopUp';
+// eslint-disable-next-line import/no-cycle
+import { phoneCardSelector } from '../app/selector';
+
+export const CardPage: FC = () => {
+ const cardedPhones = useAppSelector(phoneCardSelector);
+ const [popUpState, setPopUpState] = useState(false);
+
+ const handlePopUp = () => {
+ setPopUpState(true);
+ };
+
+ useEffect(() => {
+ if (popUpState) {
+ document.body.style.overflow = 'hidden';
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth',
+ });
+ } else {
+ document.body.style.overflow = 'auto';
+ }
+ }, [popUpState]);
+
+ return (
+
+
+ {popUpState && (
)}
+
+
+
+ Back
+
+
Cart
+ {cardedPhones.length < 0 ? (
+
Add something to card...
+ ) : (
+
+
+ {cardedPhones.map(card => (
+ -
+
+
+ ))}
+
+
+
{`$${cardedPhones.reduce((a, i) => a + (i.amount * i.value.price), 0)}`}
+
{`Total for ${cardedPhones.reduce((a, i) => a + i.amount, 0)} items`}
+
+
+
+ )}
+
+
+ );
+};
diff --git a/src/pages/FavoritesPage.tsx b/src/pages/FavoritesPage.tsx
new file mode 100644
index 0000000000..4403501d29
--- /dev/null
+++ b/src/pages/FavoritesPage.tsx
@@ -0,0 +1,28 @@
+import { FC } from 'react';
+import Breadcrumbs from './components/Breadcrumbs';
+import { ProductsList } from './components/ProductsList';
+import { useAppSelector } from '../app/hooks';
+import { filteringVisibleSearchedProducts } from '../app/utils';
+import { favoriteProductsSelector, searchBarSelector } from '../app/selector';
+
+export const FavoritesPage: FC = () => {
+ const favoritesPhones = useAppSelector(favoriteProductsSelector);
+ const searchBarValue = useAppSelector(searchBarSelector);
+
+ const phonesSearched = filteringVisibleSearchedProducts(
+ favoritesPhones, searchBarValue,
+ );
+
+ return (
+
+
+
+
Favourites
+
+ {`${favoritesPhones.length} items`}
+
+
+
+
+ );
+};
diff --git a/src/pages/HomePage.tsx b/src/pages/HomePage.tsx
new file mode 100644
index 0000000000..e5b101ace4
--- /dev/null
+++ b/src/pages/HomePage.tsx
@@ -0,0 +1,132 @@
+/* eslint-disable jsx-a11y/anchor-is-valid */
+import React from 'react';
+import { Link } from 'react-router-dom';
+import '../styles/styles.scss';
+import { ProductsSlider } from './components/ProductsSlider';
+import { PreviewSlider } from './components/PreviewSlider';
+import { Product } from '../types/Product';
+import { useAppSelector } from '../app/hooks';
+import { AsyncStatus } from '../types/AsyncStatus';
+import { Loader } from './components/Loader';
+import {
+ phonesSelector,
+ phonesStatusSelector,
+ productsSelector,
+} from '../app/selector';
+
+export const HomePage: React.FC = () => {
+ const phones: Product[] = useAppSelector(phonesSelector);
+ const statusLadingPhones = useAppSelector(phonesStatusSelector);
+ const tabletsAmount = useAppSelector(productsSelector).filter(
+ p => p.type === 'tablet',
+ ).length;
+ const accessoryAmount = useAppSelector(productsSelector).filter(
+ p => p.type === 'accessory',
+ ).length;
+
+ const hotPricePhones = [...phones].sort(
+ (a: Product, b: Product) => {
+ const aValue = (a.fullPrice - a.price) / a.fullPrice;
+ const bValue = (b.fullPrice - b.price) / b.fullPrice;
+
+ return aValue - bValue;
+ },
+ );
+
+ const brandNewModels = [...phones].sort(
+ (a: Product, b: Product) => +b.year - +a.year,
+ );
+
+ const images = [
+ { imgUrl: 'images/banner-phones.png', id: '01' },
+ { imgUrl: 'images/banner-phones.png', id: '02' },
+ { imgUrl: 'images/banner-phones.png', id: '03' },
+ ];
+
+ return (
+ <>
+
+
+
+ {images.map(img => (
+
+ ))}
+
+
+
+
+
+ Hot prices
+
+ {statusLadingPhones === AsyncStatus.LOADING ? (
+
+ ) : (
+
+ )}
+
+
+
+
Shop by category
+
+
+
+
+
+
+ Mobile phones
+
+
{`${phones.length} models`}
+
+
+
+
+
+
+ Tablets
+
+
{`${tabletsAmount} models`}
+
+
+
+
+
+
+ Accessories
+
+
{`${accessoryAmount} models`}
+
+
+
+
+
+
+ Brand new models
+
+ {statusLadingPhones === AsyncStatus.LOADING ? (
+
+ ) : (
+
+ )}
+
+
+
+ >
+ );
+};
diff --git a/src/pages/PhonesPage.tsx b/src/pages/PhonesPage.tsx
new file mode 100644
index 0000000000..f0f384deb1
--- /dev/null
+++ b/src/pages/PhonesPage.tsx
@@ -0,0 +1,130 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import {
+ FC, useEffect, useState,
+} from 'react';
+import { useSearchParams } from 'react-router-dom';
+
+import Breadcrumbs from './components/Breadcrumbs';
+import { ProductsList } from './components/ProductsList';
+// eslint-disable-next-line import/no-cycle
+// import CustomSelect from './components/CustomSelect';
+import { Product } from '../types/Product';
+
+import '../styles/styles.scss';
+import { Pagination } from './components/Pagination';
+import {
+ updateStateProductsAndUrl,
+ useAppDispatch,
+ useAppSelector,
+} from '../app/hooks';
+import { Loader } from './components/Loader';
+import { AsyncStatus } from '../types/AsyncStatus';
+import { incrementAsync as loadedPhones } from '../features/phones/phonesSlice';
+// eslint-disable-next-line import/no-cycle
+import CustomSelect from './components/CustomSelect';
+import { SortByOptions } from '../types/SortByOptions';
+import { SelectAmountItems } from '../types/SelectAmountItems';
+import { filteringVisibleSearchedProducts } from '../app/utils';
+import { itemsOnPageOptions, sortByOptions } from '../types/SelectOptionsArr';
+import { phonesSelector, phonesStatusSelector } from '../app/selector';
+
+export const PhonesPage: FC = () => {
+ const phones: Product[] = useAppSelector(phonesSelector);
+ const statusLadingPhones = useAppSelector(phonesStatusSelector);
+ const [selectedOptions, setSelectedOptions] = useState({
+ sortBy: SortByOptions.AGE,
+ itemsShow: SelectAmountItems.SIXTEEN,
+ });
+
+ const [visiblePhones, setVisiblePhones] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+ const lastPhoneIndex = currentPage * +`${selectedOptions.itemsShow === 'all' ? Infinity : selectedOptions.itemsShow}`;
+ const firstPhoneIndex = lastPhoneIndex - +selectedOptions.itemsShow;
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const [_, setSearchParams] = useSearchParams();
+ const dispatch = useAppDispatch();
+ const searchBarValue = useAppSelector(state => state.searchBar.value);
+ const paginate = (pagenumber: number) => setCurrentPage(pagenumber);
+
+ useEffect(() => {
+ dispatch(loadedPhones());
+ }, []);
+
+ useEffect(() => {
+ updateStateProductsAndUrl(
+ setVisiblePhones,
+ phones,
+ selectedOptions,
+ statusLadingPhones,
+ currentPage,
+ setSearchParams,
+ );
+
+ setCurrentPage(1);
+ }, [selectedOptions]);
+
+ useEffect(() => {
+ updateStateProductsAndUrl(
+ setVisiblePhones,
+ phones,
+ selectedOptions,
+ statusLadingPhones,
+ currentPage,
+ setSearchParams,
+ );
+ }, [currentPage, phones]);
+
+ const phonesSearched = filteringVisibleSearchedProducts(
+ visiblePhones, searchBarValue,
+ );
+
+ const phonesSliced = phonesSearched.slice(firstPhoneIndex, lastPhoneIndex);
+
+ return (
+
+ {!searchBarValue ? (
+ <>
+
+
Mobile phones
+
+ {`${visiblePhones.length} models`}
+
+
+
+
Sort by
+
+
+
+
Items on page
+
+
+
+ >
+ ) : (
+
{`${phonesSearched.length} results`}
+ )}
+ {statusLadingPhones === AsyncStatus.LOADING ? (
+
+ ) : (
+ <>
+
+ {!searchBarValue && visiblePhones.length > phonesSliced.length && (
+
+ )}
+ >
+ )}
+
+ );
+};
diff --git a/src/pages/ProductDetailsPage.tsx b/src/pages/ProductDetailsPage.tsx
new file mode 100644
index 0000000000..92f3f279e3
--- /dev/null
+++ b/src/pages/ProductDetailsPage.tsx
@@ -0,0 +1,193 @@
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable max-len */
+import { FC, useEffect, useState } from 'react';
+import { Link, useLocation } from 'react-router-dom';
+import Breadcrumbs from './components/Breadcrumbs';
+import '../styles/styles.scss';
+import { useAppDispatch, useAppSelector } from '../app/hooks';
+import { Product } from '../types/Product';
+import { Loader } from './components/Loader';
+import { incrementAsyncOld, incrementAsync as loadPhoneDetails } from '../features/PhoneDetails/phoneDetailsSlice';
+import { AsyncStatus } from '../types/AsyncStatus';
+import { ProductsSlider } from './components/ProductsSlider';
+import {
+ favoriteProductsSelector,
+ phoneCardSelector,
+ phonesDetaildStatusSelector,
+ phonesDetailsSelector,
+ phonesSelector,
+ phonesStatusSelector,
+ productsSelector,
+ selectedPhoneSelector,
+} from '../app/selector';
+import { setInCardPhone, unsetFromCardPhone } from '../features/PhonesInCard/phonesInCardSlice';
+import { incrementAsync as loadPhones } from '../features/phones/phonesSlice';
+import { KeyJson } from '../types/KeyJson';
+import { setFavoritePhone, unsetFavoritePhone } from '../features/PhonesFavorites/phonesFavoritesSlice';
+import { DesriptionNewApi } from './components/DesriptionApi';
+import { incrementAsync as loadProducts } from '../features/products/productsSlice';
+
+export const ProductDetailsPage: FC = () => {
+ const dispatch = useAppDispatch();
+ const selectedProduct = useAppSelector(selectedPhoneSelector);
+ const phonesFromServer = useAppSelector(phonesSelector);
+ const productFromServer = useAppSelector(productsSelector);
+ const statusPhones = useAppSelector(phonesStatusSelector);
+ const phoneDetails = useAppSelector(phonesDetailsSelector);
+ const phoneDetailsStatus = useAppSelector(phonesDetaildStatusSelector);
+ const cardedPhones = useAppSelector(phoneCardSelector);
+ const favoritesPhones = useAppSelector(favoriteProductsSelector);
+ const [brandNewModels, setBrandNewModels] = useState([]);
+ const location = useLocation().pathname.split('/')[2];
+
+ const handleCardedProducts = () => {
+ if (phoneDetails && phonesFromServer) {
+ const currentPhone = phonesFromServer.find(p => p.itemId === phoneDetails.id);
+ const oldApiProduct = productFromServer.find(p => p.id === phoneDetails.id);
+
+ if (currentPhone) {
+ if (cardedPhones.some(card => card.id === currentPhone.itemId)) {
+ dispatch(unsetFromCardPhone(currentPhone));
+ } else {
+ dispatch(setInCardPhone(currentPhone));
+ }
+ }
+
+ if (oldApiProduct) {
+ if (cardedPhones.some(card => card.id === oldApiProduct.id)) {
+ dispatch(unsetFromCardPhone(oldApiProduct));
+ } else {
+ dispatch(setInCardPhone(oldApiProduct));
+ }
+ }
+ }
+ };
+
+ const handleFavoritesProducts = () => {
+ if (phoneDetails && phonesFromServer) {
+ const currentPhone = phonesFromServer.find(p => p.itemId === phoneDetails.id);
+ const oldApiProduct = productFromServer.find(p => p.id === phoneDetails.id);
+
+ if (currentPhone) {
+ if (favoritesPhones.find(p => p.id === currentPhone.id)) {
+ dispatch(unsetFavoritePhone(currentPhone));
+ } else {
+ dispatch(setFavoritePhone(currentPhone));
+ }
+ }
+
+ if (oldApiProduct) {
+ if (favoritesPhones.some(card => card.id === oldApiProduct.id)) {
+ dispatch(unsetFavoritePhone(oldApiProduct));
+ } else {
+ dispatch(setFavoritePhone(oldApiProduct));
+ }
+ }
+ }
+ };
+
+ useEffect(() => {
+ dispatch(loadProducts());
+ dispatch(loadPhones());
+ if (selectedProduct) {
+ if (selectedProduct.itemId) {
+ dispatch(loadPhoneDetails(selectedProduct.itemId));
+ } else {
+ dispatch(incrementAsyncOld(selectedProduct));
+ }
+ }
+ }, []);
+
+ useEffect(() => {
+ dispatch(loadProducts());
+ dispatch(loadPhones());
+ if (selectedProduct) {
+ if (selectedProduct.itemId) {
+ dispatch(loadPhoneDetails(selectedProduct.itemId));
+ } else {
+ dispatch(incrementAsyncOld(selectedProduct));
+ }
+ }
+ }, [location]);
+
+ useEffect(() => {
+ if (phoneDetails && phoneDetailsStatus === AsyncStatus.IDLE) {
+ if (phoneDetails.capacity) {
+ window.localStorage.setItem(KeyJson.DETAILS, JSON.stringify(phoneDetails));
+ } else if (selectedProduct) {
+ window.localStorage.setItem(KeyJson.DETAILS, JSON.stringify({
+ ...phoneDetails,
+ priceDiscount: selectedProduct.price - selectedProduct.discount,
+ priceRegular: selectedProduct.price,
+ }));
+ }
+ }
+ }, [phoneDetails]);
+ useEffect(() => {
+ if (statusPhones === AsyncStatus.IDLE) {
+ const sortByYear = [...phonesFromServer].sort(
+ (a: Product, b: Product) => b.year - a.year,
+ );
+
+ setBrandNewModels(sortByYear);
+ }
+ }, [phonesFromServer]);
+
+ const isLoading = phoneDetailsStatus === AsyncStatus.LOADING
+ || statusPhones === AsyncStatus.LOADING || !phoneDetails || !phonesFromServer;
+
+ const [bigImgIndex, setBigImgIndex] = useState(0);
+ const [capacityIndex, setCapacityIndex] = useState(0);
+ const [colorIndex, setColorIndex] = useState(0);
+
+ const handleGalleryImg = (index: number) => {
+ setBigImgIndex(index);
+ };
+
+ const handleCapacityItem = (index: number) => {
+ setCapacityIndex(index);
+ };
+
+ const handleColorItem = (index: number) => {
+ setColorIndex(index);
+ };
+
+ return (
+
+ {isLoading ? (
+
+ ) : (
+ <>
+
+
+
+ Back
+
+
+ {phoneDetails.name}
+
+
+
+
You may also like
+ {statusPhones === AsyncStatus.IDLE && brandNewModels.length && (
+
+ )}
+
+ >
+ )}
+
+ );
+};
diff --git a/src/pages/TabletsPage.tsx b/src/pages/TabletsPage.tsx
new file mode 100644
index 0000000000..f7d284f693
--- /dev/null
+++ b/src/pages/TabletsPage.tsx
@@ -0,0 +1,137 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
+import {
+ FC, useEffect, useState,
+} from 'react';
+import { useSearchParams } from 'react-router-dom';
+
+import Breadcrumbs from './components/Breadcrumbs';
+import { ProductsList } from './components/ProductsList';
+
+import '../styles/styles.scss';
+import { Pagination } from './components/Pagination';
+import {
+ updateStateProductsAndUrl,
+ useAppDispatch,
+ useAppSelector,
+} from '../app/hooks';
+import { Loader } from './components/Loader';
+import { AsyncStatus } from '../types/AsyncStatus';
+import {
+ incrementAsync as loadedProducts,
+} from '../features/products/productsSlice';
+import { Product } from '../types/Product';
+import { SortByOptions } from '../types/SortByOptions';
+import { SelectAmountItems } from '../types/SelectAmountItems';
+import { filteringVisibleSearchedProducts } from '../app/utils';
+import { itemsOnPageOptions, sortByOptions } from '../types/SelectOptionsArr';
+import CustomSelect from './components/CustomSelect';
+import {
+ productsSelector,
+ productsStatusSelector,
+ searchBarSelector,
+} from '../app/selector';
+
+export const TabletsPage: FC = () => {
+ const products: Product[] = useAppSelector(productsSelector);
+ const statusLadingProducts = useAppSelector(productsStatusSelector);
+ const [selectedOptions, setSelectedOptions] = useState({
+ sortBy: SortByOptions.AGE,
+ itemsShow: SelectAmountItems.SIXTEEN,
+ });
+ const [visibleProducts, setVisibleProducts] = useState([]);
+ const [currentPage, setCurrentPage] = useState(1);
+ const lastPhoneIndex = currentPage * +`${selectedOptions.itemsShow === 'all' ? Infinity : selectedOptions.itemsShow}`;
+ const firstPhoneIndex = lastPhoneIndex - +selectedOptions.itemsShow;
+ // eslint-disable-next-line @typescript-eslint/naming-convention
+ const [_, setSearchParams] = useSearchParams();
+ const dispatch = useAppDispatch();
+ const searchBarValue = useAppSelector(searchBarSelector);
+ const paginate = (pagenumber: number) => setCurrentPage(pagenumber);
+
+ useEffect(() => {
+ dispatch(loadedProducts());
+ }, []);
+
+ useEffect(() => {
+ updateStateProductsAndUrl(
+ setVisibleProducts,
+ products,
+ selectedOptions,
+ statusLadingProducts,
+ currentPage,
+ setSearchParams,
+ );
+
+ setCurrentPage(1);
+ }, [selectedOptions]);
+
+ useEffect(() => {
+ updateStateProductsAndUrl(
+ setVisibleProducts,
+ products,
+ selectedOptions,
+ statusLadingProducts,
+ currentPage,
+ setSearchParams,
+ );
+ }, [currentPage, products]);
+
+ const tabletsSearched = filteringVisibleSearchedProducts(
+ visibleProducts, searchBarValue,
+ ).filter(
+ p => p.type === 'tablet',
+ );
+
+ const tabletsSliced = tabletsSearched.slice(firstPhoneIndex, lastPhoneIndex);
+
+ return (
+
+ {!searchBarValue ? (
+ <>
+
+
Tablets
+
+ {`${tabletsSearched.length} models`}
+
+ {tabletsSearched.length > 0 && (
+
+
+
Sort by
+
+
+
+
Items on page
+
+
+
+ )}
+ >
+ ) : (
+
{`${tabletsSearched.length} results`}
+ )}
+
+ {statusLadingProducts === AsyncStatus.LOADING ? (
+
+ ) : (
+ <>
+
+ {!searchBarValue && tabletsSearched.length > tabletsSliced.length && (
+
+ )}
+ >
+ )}
+
+ );
+};
diff --git a/src/pages/components/BreadcrumbItem.tsx b/src/pages/components/BreadcrumbItem.tsx
new file mode 100644
index 0000000000..458f8f2b2a
--- /dev/null
+++ b/src/pages/components/BreadcrumbItem.tsx
@@ -0,0 +1,34 @@
+// BreadcrumbItem.js
+import { FC } from 'react';
+import '../../styles/styles.scss';
+import { NavLink } from 'react-router-dom';
+
+type Props = {
+ text: string,
+ link: string,
+};
+
+const BreadcrumbItem: FC = ({ text, link }) => {
+ const capitalizeFirstLetter = (str: string) => {
+ return str.charAt(0).toUpperCase() + str.slice(1);
+ };
+
+ return (
+ <>
+ {(text === '') ? (
+
+
+
+ ) : (
+ <>
+
+
+ {capitalizeFirstLetter(text)}
+
+ >
+ )}
+ >
+ );
+};
+
+export default BreadcrumbItem;
diff --git a/src/pages/components/Breadcrumbs.tsx b/src/pages/components/Breadcrumbs.tsx
new file mode 100644
index 0000000000..01f782ed32
--- /dev/null
+++ b/src/pages/components/Breadcrumbs.tsx
@@ -0,0 +1,31 @@
+/* eslint-disable react/require-default-props */
+// Breadcrumbs.js
+import { FC } from 'react';
+import { useLocation } from 'react-router-dom';
+import BreadcrumbItem from './BreadcrumbItem';
+import '../../styles/styles.scss';
+
+const Breadcrumbs: FC = () => {
+ const location = useLocation();
+ const pathArray = location.pathname.split('/');
+
+ return (
+
+ );
+};
+
+export default Breadcrumbs;
diff --git a/src/pages/components/CardedProduct.tsx b/src/pages/components/CardedProduct.tsx
new file mode 100644
index 0000000000..ac87c116c8
--- /dev/null
+++ b/src/pages/components/CardedProduct.tsx
@@ -0,0 +1,81 @@
+/* eslint-disable import/no-cycle */
+/* eslint-disable @typescript-eslint/no-use-before-define */
+import { FC } from 'react';
+import { useAppDispatch } from '../../app/hooks';
+import {
+ decrease,
+ increase,
+ unsetFromCardPhone,
+} from '../../features/PhonesInCard/phonesInCardSlice';
+import { SavedCard } from '../../types/SavedCard';
+
+type Props = {
+ card: SavedCard,
+};
+
+export const CardedProduct: FC = ({
+ card,
+}) => {
+ const dispatch = useAppDispatch();
+
+ const handleUnsetProduct = () => {
+ dispatch(unsetFromCardPhone(card.value));
+ };
+
+ const handleCounter = (type: string) => {
+ switch (type) {
+ case 'decrease':
+ dispatch(decrease(card));
+ break;
+ case 'increase':
+ dispatch(increase(card));
+ break;
+ default:
+ break;
+ }
+ };
+
+ return (
+ <>
+
+
+
+
+
+ {card.value.name}
+
+
+
+
{card.amount}
+
+
+ {`$${card.value.price}`}
+ >
+ );
+};
diff --git a/src/pages/components/CustomSelect.tsx b/src/pages/components/CustomSelect.tsx
new file mode 100644
index 0000000000..2cb835841a
--- /dev/null
+++ b/src/pages/components/CustomSelect.tsx
@@ -0,0 +1,98 @@
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+import {
+ Dispatch, FC, SetStateAction, useState, useEffect,
+} from 'react';
+import classNames from 'classnames';
+// eslint-disable-next-line import/no-cycle
+import '../../styles/styles.scss';
+import { SortByOptions } from '../../types/SortByOptions';
+import { SelectAmountItems } from '../../types/SelectAmountItems';
+
+type Props = {
+ options: { text: string, value: SortByOptions | SelectAmountItems }[],
+ defaultOption: string,
+ onChange: Dispatch>;
+};
+
+const CustomSelect: FC = ({ options, defaultOption, onChange }) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [selectedOption, setSelectedOption] = useState(() => {
+ if (defaultOption === SortByOptions.AGE) {
+ return 'Newest';
+ }
+
+ return defaultOption;
+ });
+
+ const toggleDropdown = () => {
+ setIsOpen(!isOpen);
+ };
+
+ const handleOptionClick = (
+ option: { text: string, value: SortByOptions | SelectAmountItems },
+ ) => {
+ setSelectedOption(option.text);
+
+ setIsOpen(false);
+ if (onChange) {
+ if (SortByOptions.AGE === option.value
+ || SortByOptions.NAME === option.value
+ || SortByOptions.PRICE === option.value) {
+ onChange((prev) => (
+ { ...prev, sortBy: option.value as SortByOptions }));
+ } else {
+ onChange((prev) => (
+ { ...prev, itemsShow: option.value as SelectAmountItems }));
+ }
+ }
+ };
+
+ // Add an event listener to the document body to handle clicks outside the selector
+ useEffect(() => {
+ const handleOutsideClick = (event: MouseEvent) => {
+ const target = event.target as HTMLElement;
+ const customSelect = document.querySelectorAll('.custom-select');
+
+ if (customSelect
+ && !customSelect[0]?.contains(target)
+ && !customSelect[1]?.contains(target)) {
+ setIsOpen(false);
+ }
+ };
+
+ document.body.addEventListener('click', handleOutsideClick);
+
+ return () => {
+ document.body.removeEventListener('click', handleOutsideClick);
+ };
+ }, []);
+
+ return (
+
+
{selectedOption}
+ {isOpen && (
+
+ {options.map((option) => (
+ - handleOptionClick(option)}
+ >
+ {option.text}
+
+ ))}
+
+ )}
+
+ );
+};
+
+export default CustomSelect;
diff --git a/src/pages/components/DesriptionApi.tsx b/src/pages/components/DesriptionApi.tsx
new file mode 100644
index 0000000000..749de9c01f
--- /dev/null
+++ b/src/pages/components/DesriptionApi.tsx
@@ -0,0 +1,238 @@
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { FC } from 'react';
+import classNames from 'classnames';
+import { PhoneDetails } from '../../types/PhoneDetails';
+import { SavedCard } from '../../types/SavedCard';
+import { Product } from '../../types/Product';
+
+type Props = {
+ phoneDetails: PhoneDetails,
+ bigImgIndex: number,
+ capacityIndex: number,
+ onGalleryImg: (index: number) => void,
+ onCapacityItem: (index: number) => void,
+ cardedPhones: SavedCard[],
+ onCardedProducts: () => void,
+ favoritesPhones: Product[],
+ onFavoritesProducts: () => void,
+ onColorItem: (index: number) => void,
+ colorIndex: number,
+};
+
+export const DesriptionNewApi: FC = ({
+ phoneDetails,
+ bigImgIndex,
+ capacityIndex,
+ onGalleryImg,
+ onCapacityItem,
+ cardedPhones,
+ onCardedProducts,
+ favoritesPhones,
+ onFavoritesProducts,
+ onColorItem,
+ colorIndex,
+}) => {
+ return (
+ <>
+
+
+
+ {phoneDetails.images.map((img, index) => (
+
onGalleryImg(index)}
+ />
+ ))}
+
+
+
+
+
+
+
+
Available colors
+
+ {phoneDetails.colorsAvailable.map((color, index) => (
+ - onColorItem(index)}
+ >
+
+
+
+ ))}
+
+
+ {phoneDetails.capacityAvailable && (
+
+
Select capacity
+
+ {phoneDetails.capacityAvailable.map((item, index) => (
+ - onCapacityItem(index)}
+ >
+ {item}
+
+
+ ))}
+
+
+ )}
+
+
+
{`${phoneDetails.priceDiscount}$`}
+
{`${phoneDetails.priceRegular}$`}
+
+
+
+
+
+
+
+
+
+ - Screen
+ -
+ {phoneDetails.screen}
+
+ - Resolution
+ -
+ {phoneDetails.resolution}
+
+ - Processor
+ -
+ {phoneDetails.processor}
+
+ - RAM
+ - {phoneDetails.ram}
+
+
+
+
+
+
+ About
+ {Array.isArray(phoneDetails.description)
+ && phoneDetails.description.map(article => (
+
+
+ {article.title && article.title}
+
+ {article.text.map(text => (
+
+ {text}
+
+ ))}
+
+ ))}
+
+
+ Tech specs
+ {phoneDetails.cell && (
+
+ - Screen
+ - {phoneDetails.screen}
+ - Resolution
+ -
+ {phoneDetails.resolution}
+
+ - Processor
+ -
+ {phoneDetails.processor}
+
+ - RAM
+ - {phoneDetails.ram}
+ - Built in memory
+ -
+ {phoneDetails.capacity}
+
+ - Camera
+ -
+ {phoneDetails.camera}
+
+ - Zoom
+ - {phoneDetails.zoom}
+ - Cell
+ -
+ {phoneDetails.cell ? (
+ phoneDetails.cell.map((item, index) => (
+
+ {`${item}${index !== phoneDetails.cell.length - 1 ? ',' : ''}`}
+
+ ))
+ ) : (
+ ''
+ )}
+
+
+ )}
+
+
+ >
+ );
+};
diff --git a/src/pages/components/Footer.tsx b/src/pages/components/Footer.tsx
new file mode 100644
index 0000000000..2a58e1ec3a
--- /dev/null
+++ b/src/pages/components/Footer.tsx
@@ -0,0 +1,49 @@
+/* eslint-disable jsx-a11y/anchor-is-valid */
+import { FC } from 'react';
+import {
+ Link,
+} from 'react-router-dom';
+import '../../styles/styles.scss';
+
+export const Footer: FC = () => {
+ // Function to scroll back to top when the button is clicked
+ const scrollToTop = () => {
+ window.scrollTo({
+ top: 0,
+ behavior: 'smooth',
+ });
+ };
+
+ return (
+
+ );
+};
diff --git a/src/pages/components/Header.tsx b/src/pages/components/Header.tsx
new file mode 100644
index 0000000000..e7c3f3f776
--- /dev/null
+++ b/src/pages/components/Header.tsx
@@ -0,0 +1,222 @@
+/* eslint-disable jsx-a11y/anchor-is-valid */
+import { FC, useEffect, useState } from 'react';
+import {
+ NavLink, useLocation,
+} from 'react-router-dom';
+import '../../styles/styles.scss';
+import classNames from 'classnames';
+import { SearchBar } from './SearchBar';
+import { useAppSelector } from '../../app/hooks';
+import {
+ favoriteProductsSelector,
+ phoneCardSelector,
+} from '../../app/selector';
+
+export const Header: FC = () => {
+ const location = useLocation();
+ const productsInCardCount = useAppSelector(phoneCardSelector).length;
+ const productsFavoriteCount = useAppSelector(favoriteProductsSelector).length;
+
+ const showSearchBar = location.pathname === '/favorites'
+ || location.pathname === '/phones'
+ || location.pathname === '/tablets';
+ const [isBurgerMenu, setIsBurgerMenu] = useState(false);
+
+ useEffect(() => {
+ if (isBurgerMenu) {
+ document.body.style.overflow = 'hidden';
+ } else {
+ document.body.style.overflow = 'auto';
+ }
+ }, [isBurgerMenu]);
+
+ const handleBurgerMenu = () => {
+ setIsBurgerMenu(prev => !prev);
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ {showSearchBar && (
)}
+
classNames(
+ 'favorites-card-buttons__link',
+ { 'favorites-card-buttons__link--active': isActive },
+ )}
+ >
+
+ {productsFavoriteCount > 0 && (
+
+ {productsFavoriteCount}
+
+ )}
+
+
classNames(
+ 'favorites-card-buttons__link',
+ { 'favorites-card-buttons__link--active': isActive },
+ )}
+ >
+
+ {productsInCardCount > 0 && (
+
+ {productsInCardCount}
+
+ )}
+
+
+
+
+
+ >
+ );
+};
diff --git a/src/pages/components/Loader/Loader.scss b/src/pages/components/Loader/Loader.scss
new file mode 100644
index 0000000000..040c96200b
--- /dev/null
+++ b/src/pages/components/Loader/Loader.scss
@@ -0,0 +1,25 @@
+.Loader {
+ display: flex;
+ width: 100%;
+ justify-content: center;
+ align-items: center;
+
+ &__content {
+ border-radius: 50%;
+ width: 2em;
+ height: 2em;
+ margin: 1em auto;
+ border: 0.3em solid #ddd;
+ border-left-color: #000;
+ animation: load8 1.2s infinite linear;
+ }
+}
+
+@keyframes load8 {
+ 0% {
+ transform: rotate(0deg);
+ }
+ 100% {
+ transform: rotate(360deg);
+ }
+}
diff --git a/src/pages/components/Loader/Loader.tsx b/src/pages/components/Loader/Loader.tsx
new file mode 100644
index 0000000000..29d906bbdf
--- /dev/null
+++ b/src/pages/components/Loader/Loader.tsx
@@ -0,0 +1,7 @@
+import './Loader.scss';
+
+export const Loader = () => (
+
+);
diff --git a/src/pages/components/Loader/index.tsx b/src/pages/components/Loader/index.tsx
new file mode 100644
index 0000000000..d5ce981151
--- /dev/null
+++ b/src/pages/components/Loader/index.tsx
@@ -0,0 +1 @@
+export * from './Loader';
diff --git a/src/pages/components/Pagination.tsx b/src/pages/components/Pagination.tsx
new file mode 100644
index 0000000000..e8dbfd33e0
--- /dev/null
+++ b/src/pages/components/Pagination.tsx
@@ -0,0 +1,70 @@
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-plusplus */
+/* eslint-disable jsx-a11y/anchor-is-valid */
+import React from 'react';
+import '../../styles/styles.scss';
+import { useSearchParams } from 'react-router-dom';
+import classNames from 'classnames';
+
+type Props = {
+ phonesPepPege: number,
+ totalPhones: number,
+ onPaginate: (pagenumber: number) => void,
+};
+
+export const Pagination: React.FC = ({
+ phonesPepPege, totalPhones, onPaginate,
+}) => {
+ const pageNumers: number[] = [];
+ const [params] = useSearchParams();
+ const page = params.get('page') || 1;
+
+ const totalPages = Math.ceil(totalPhones / phonesPepPege);
+ const maxPageToShow = 5;
+ const startPage = Math.max(1, +page - Math.floor(maxPageToShow / 2));
+ const endPage = Math.min(totalPages, startPage + maxPageToShow - 1);
+
+ for (let i = startPage; i <= endPage; i++) {
+ pageNumers.push(i);
+ }
+
+ function handlePagination(number: number) {
+ if (number < pageNumers[0] || number > pageNumers[pageNumers.length - 1]) {
+ return;
+ }
+
+ onPaginate(number);
+ }
+
+ return (
+ <>
+
+ - handlePagination(+page - 1)}
+ >
+
+
+ {pageNumers.map(number => (
+ - handlePagination(number)}
+ >
+ {number}
+
+ ))}
+ - handlePagination(+page + 1)}
+ >
+
+
+
+ >
+ );
+};
diff --git a/src/pages/components/PopUp.tsx b/src/pages/components/PopUp.tsx
new file mode 100644
index 0000000000..2c304f61e7
--- /dev/null
+++ b/src/pages/components/PopUp.tsx
@@ -0,0 +1,28 @@
+import { FC } from 'react';
+
+type Props = {
+ setPopUpState: React.Dispatch>,
+};
+
+export const PopUp: FC = ({ setPopUpState }) => {
+ const handleClosePopUp = () => {
+ setPopUpState(false);
+ };
+
+ return (
+
+
+
+ We are sorry, but this feature is not implemented yet
+
+
+
+
+ );
+};
diff --git a/src/pages/components/PreviewSlider.tsx b/src/pages/components/PreviewSlider.tsx
new file mode 100644
index 0000000000..8765aea202
--- /dev/null
+++ b/src/pages/components/PreviewSlider.tsx
@@ -0,0 +1,129 @@
+/* eslint-disable jsx-a11y/control-has-associated-label */
+/* eslint-disable @typescript-eslint/no-use-before-define */
+import React, {
+ useState, useEffect, cloneElement, useRef,
+} from 'react';
+import classNames from 'classnames';
+import '../../styles/styles.scss';
+
+type Props = {
+ children: JSX.Element[];
+};
+
+const PICTURE_SIZE = 100;
+const INTERVAL_DELAY = 5000;
+
+export const PreviewSlider: React.FC = React.memo(({ children }) => {
+ const [pages, setPages] = useState(children);
+ const [offset, setOffset] = useState(0);
+ const [activeIndex, setActiveIndex] = useState(0); // Track the active slide index
+
+ const intervalRef = useRef(null);
+
+ function handleLeftClick() {
+ setOffset((prevOffset) => {
+ const newOffset = prevOffset + PICTURE_SIZE;
+ const minOffset = -(PICTURE_SIZE * (pages.length - 1));
+
+ if (newOffset > 0) {
+ setActiveIndex(pages.length - 1); // Set activeIndex to the last slide index
+
+ return minOffset;
+ }
+
+ setActiveIndex((prevIndex) => Math.max(prevIndex - 1, 0)); // Decrease activeIndex
+
+ return newOffset;
+ });
+
+ resetInterval();
+ }
+
+ function handleRightClick() {
+ setOffset((prevOffset) => {
+ const newOffset = prevOffset - PICTURE_SIZE;
+
+ if (newOffset < -(PICTURE_SIZE * (pages.length - 1))) {
+ setActiveIndex(0); // Set activeIndex to the first slide index
+
+ return 0;
+ }
+
+ setActiveIndex((prevIndex) => Math.min(prevIndex + 1, pages.length - 1)); // Increase activeIndex
+
+ return newOffset;
+ });
+
+ resetInterval();
+ }
+
+ function resetInterval() {
+ if (intervalRef.current) {
+ clearInterval(intervalRef.current);
+ }
+
+ intervalRef.current = window.setInterval(() => {
+ handleRightClick();
+ }, INTERVAL_DELAY);
+ }
+
+ // Function to handle pagination clicks
+ function handlePaginationClick(index: number) {
+ setActiveIndex(index); // Set the active slide index
+ setOffset(-PICTURE_SIZE * index); // Set the offset to display the selected slide
+ resetInterval(); // Reset the interval to pause auto-scrolling on manual navigation
+ }
+
+ useEffect(() => {
+ resetInterval();
+ }, []);
+
+ useEffect(() => {
+ setPages((prev) => {
+ return prev.map((child) => {
+ return cloneElement(child, {
+ key: child.key,
+ style: {
+ transition: '500ms',
+ transform: `translateX(${offset}%)`,
+ },
+ });
+ });
+ });
+ }, [offset]);
+
+ return (
+ <>
+
+
+
{pages}
+
+ {pages.map((page, index) => (
+
+
+
+ >
+ );
+});
diff --git a/src/pages/components/ProductCard.tsx b/src/pages/components/ProductCard.tsx
new file mode 100644
index 0000000000..2bb46533c9
--- /dev/null
+++ b/src/pages/components/ProductCard.tsx
@@ -0,0 +1,178 @@
+import { FC, useEffect, useState } from 'react';
+import { Link } from 'react-router-dom';
+import classNames from 'classnames';
+
+import { Product } from '../../types/Product';
+import '../../styles/styles.scss';
+import { useAppDispatch, useAppSelector } from '../../app/hooks';
+import { SelectedStatus } from '../../types/SelectedStatus';
+import {
+ setPhone,
+} from '../../features/selectedPhone/selectedPhoneSlice';
+import {
+ setFavoritePhone, unsetFavoritePhone,
+} from '../../features/PhonesFavorites/phonesFavoritesSlice';
+import {
+ setInCardPhone, unsetFromCardPhone,
+} from '../../features/PhonesInCard/phonesInCardSlice';
+import {
+ favoriteProductsSelector,
+ phoneCardSelector,
+ selectedPhoneSelector,
+ selectedPhoneStatusSelector,
+} from '../../app/selector';
+import { KeyJson } from '../../types/KeyJson';
+
+type Props = {
+ product: Product;
+};
+
+export const ProductCard: FC = ({ product }) => {
+ const dispatch = useAppDispatch();
+ const selectedPhoneId = useAppSelector(selectedPhoneSelector)?.id;
+ const statusSelectedPhone = useAppSelector(selectedPhoneStatusSelector);
+ const favoritesPhones = useAppSelector(favoriteProductsSelector);
+ const [isFavorite, setIsFavorite] = useState(() => {
+ if (favoritesPhones.some(p => product.id === p.id)) {
+ return true;
+ }
+
+ return false;
+ });
+ const cardedPhones = useAppSelector(phoneCardSelector);
+ const [isCarded, setIsCarded] = useState(() => {
+ if (cardedPhones.some(p => product.itemId === p.id)) {
+ return true;
+ }
+
+ if (cardedPhones.some(p => product.id === p.id)) {
+ return true;
+ }
+
+ return false;
+ });
+
+ const handleSelectingProduct = () => {
+ switch (statusSelectedPhone) {
+ case SelectedStatus.UNSELECTED:
+ dispatch(setPhone(product));
+ break;
+
+ default:
+ break;
+ }
+
+ if (selectedPhoneId !== product.id) {
+ dispatch(setPhone(product));
+ }
+ };
+
+ const handleFavoritesProducts = () => {
+ if (favoritesPhones.find(p => p.id === product.id)) {
+ dispatch(unsetFavoritePhone(product));
+ setIsFavorite(false);
+ } else {
+ dispatch(setFavoritePhone(product));
+ setIsFavorite(true);
+ }
+ };
+
+ const handleCardedProducts = () => {
+ if (
+ product.itemId !== undefined
+ && cardedPhones.find(card => card.id === product.itemId)
+ ) {
+ dispatch(unsetFromCardPhone(product));
+ setIsCarded(false);
+ } else if (
+ product.id.length !== undefined
+ && cardedPhones.find(card => card.id === product.id)
+ ) {
+ const oldApiProduct = {
+ ...product,
+ itemId: product.id,
+ };
+
+ dispatch(unsetFromCardPhone(oldApiProduct));
+ setIsCarded(false);
+ } else {
+ const productToSave = product.itemId === undefined ? {
+ ...product,
+ itemId: product.id,
+ } : product;
+
+ dispatch(setInCardPhone(productToSave));
+ setIsCarded(true);
+ }
+ };
+
+ useEffect(() => {
+ window.localStorage.setItem(KeyJson.CARD, JSON.stringify(cardedPhones));
+ }, [cardedPhones]);
+
+ const price = product.discount
+ ? product.price - product.discount
+ : product.price;
+ const fullPrice = product.fullPrice ? product.fullPrice : product.price;
+
+ return (
+
+
+
+
+
+ {product.name}
+
+
+
+
{`${price}$`}
+ {price !== fullPrice && (
+
+ {`${fullPrice}$`}
+
+ )}
+
+
+ - Screen
+ - {product.screen}
+ - Capacity
+ - {product.capacity}
+ - RAM
+ - {product.ram}
+
+
+
+
+
+
+ );
+};
diff --git a/src/pages/components/ProductsList.tsx b/src/pages/components/ProductsList.tsx
new file mode 100644
index 0000000000..89a25adce9
--- /dev/null
+++ b/src/pages/components/ProductsList.tsx
@@ -0,0 +1,53 @@
+import { FC } from 'react';
+import '../../styles/styles.scss';
+import { useLocation } from 'react-router-dom';
+import { Product } from '../../types/Product';
+import { ProductCard } from './ProductCard';
+import { useAppSelector } from '../../app/hooks';
+import {
+ favoriteProductsSelector,
+ searchBarSelector,
+} from '../../app/selector';
+
+type Props = {
+ products: Product[],
+};
+
+export const ProductsList: FC = ({ products }) => {
+ const location = useLocation();
+ const favolritesLenght = useAppSelector(favoriteProductsSelector).length;
+ const messageForZeroProducts = products.length > 0
+ ? null
+ : `All ${location.pathname.split('/')[1]} have been sold, try to come back latter, please.`;
+
+ const messageForZeroFavorites = favolritesLenght < 1
+ ? 'Add something to favorites card...'
+ : null;
+ const searchBarValue = useAppSelector(searchBarSelector);
+ const noSearchedResultMessage = 'No matches found';
+
+ return (
+ <>
+ {!searchBarValue
+ && messageForZeroProducts
+ && location.pathname !== '/favorites'
+ ? (
+ { messageForZeroProducts }
+ ) : (
+
+ {products.map(product => (
+ -
+
+
+ ))}
+
+ )}
+ {searchBarValue && messageForZeroProducts && (
+ {noSearchedResultMessage}
+ )}
+ {location.pathname === '/favorites' && !searchBarValue && (
+ {messageForZeroFavorites}
+ )}
+ >
+ );
+};
diff --git a/src/pages/components/ProductsSlider.tsx b/src/pages/components/ProductsSlider.tsx
new file mode 100644
index 0000000000..6816dc8efd
--- /dev/null
+++ b/src/pages/components/ProductsSlider.tsx
@@ -0,0 +1,82 @@
+// import { ProductCard } from './ProductCard';
+import {
+ FC, useEffect, useState,
+} from 'react';
+import '../../styles/styles.scss';
+import { Product } from '../../types/Product';
+import { ProductCard } from './ProductCard';
+
+type Props = {
+ phones: Product[];
+};
+
+const PICTURE_SIZE = 288; // 272px cardsize + 16px gap between product-cards
+const VISIBLE_SIZE_ROW = 1136; // 1136px visible length of Home-page;
+
+export const ProductsSlider: FC = ({ phones }) => {
+ const [products, setProducts] = useState([]);
+ const [offset, setOffset] = useState(0);
+
+ function handleLeftClick() {
+ setOffset((prevOffset) => {
+ const newOffset = prevOffset + PICTURE_SIZE;
+ const minOffset = 0;
+
+ if (newOffset > 0) {
+ return minOffset;
+ }
+
+ return newOffset;
+ });
+ }
+
+ function handleRightClick() {
+ setOffset((prevOffset) => {
+ const newOffset = prevOffset - PICTURE_SIZE;
+
+ if (newOffset < -((PICTURE_SIZE * products.length) - VISIBLE_SIZE_ROW)) {
+ return prevOffset;
+ }
+
+ return newOffset;
+ });
+ }
+
+ useEffect(() => {
+ setProducts(phones);
+ }, [phones]);
+
+ return (
+
+
+
+
+
+
+ {products.map(product => (
+
+ ))}
+
+
+ );
+};
diff --git a/src/pages/components/SearchBar.tsx b/src/pages/components/SearchBar.tsx
new file mode 100644
index 0000000000..793263d9c5
--- /dev/null
+++ b/src/pages/components/SearchBar.tsx
@@ -0,0 +1,60 @@
+/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { FC } from 'react';
+import { useLocation } from 'react-router-dom';
+import { useAppDispatch, useAppSelector } from '../../app/hooks';
+import {
+ setSearchingValue, unsetSearchingValue,
+} from '../../features/SearchBar/searchBarSlice';
+import '../../styles/styles.scss';
+import { searchBarSelector } from '../../app/selector';
+
+export const SearchBar: FC = () => {
+ const dispatch = useAppDispatch();
+ const location = useLocation();
+ const searchValue = useAppSelector(searchBarSelector);
+
+ const handleSearchCheange = (
+ event: React.ChangeEvent,
+ ) => {
+ dispatch(setSearchingValue(event.target.value));
+ };
+
+ const clearSearch = () => {
+ if (searchValue) {
+ dispatch(unsetSearchingValue());
+ }
+ };
+
+ const iconShowSearchClear = searchValue
+ ? 'images/icons/CloseButtonDark.svg'
+ : 'images/icons/Search.svg';
+
+ return (
+
+
+
+
+ );
+};
diff --git a/src/styles/blocks/breadcrumb.scss b/src/styles/blocks/breadcrumb.scss
new file mode 100644
index 0000000000..b00fcedcd3
--- /dev/null
+++ b/src/styles/blocks/breadcrumb.scss
@@ -0,0 +1,27 @@
+.breadcrumb {
+ &__breadcrumb-list {
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ list-style: none;
+ padding: 0;
+ }
+}
+
+.breadcrumb-item {
+
+ &__link {
+ height: 16px;
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ text-decoration: none;
+ color: var(--gray-secondary, #89939a);
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+
+ &::first-letter {
+ text-transform: capitalize;
+ }
+ }
+}
diff --git a/src/styles/blocks/burger-menu.scss b/src/styles/blocks/burger-menu.scss
new file mode 100644
index 0000000000..1aad39c020
--- /dev/null
+++ b/src/styles/blocks/burger-menu.scss
@@ -0,0 +1,68 @@
+.burger-menu {
+ display: none;
+ position: absolute;
+ padding-top: 40px;
+ top: 0;
+ left: 0;
+ z-index: 4;
+ height: 100vh;
+ width: 100%;
+ background-color: #ecececea;
+
+ &__nav-burger {
+ @include width($breakpoint__s);
+ margin: auto;
+ }
+
+ &--active {
+ display: block;
+ }
+}
+
+.hamburger-button {
+ display: none;
+ background-color: transparent;
+ border: 0;
+ padding: 0;
+}
+
+.nav-burger {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+
+ &__logo-containter {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: 64px;
+ }
+
+ &__logo {
+ width: 40px;
+ }
+
+ &__burger-menu-button {
+ justify-content: flex-end;
+ margin-left: auto;
+ background-color: transparent;
+ border: 0;
+ }
+ &__link {
+ text-decoration: none;
+ color: var(--gray-secondary, #89939A);
+
+ /* Uppercase */
+ font-family: Mont-Bold;
+ font-size: 14px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 22px; /* 91.667% */
+ letter-spacing: 0.48px;
+ text-transform: uppercase;
+
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+}
diff --git a/src/styles/blocks/card-page-media.scss b/src/styles/blocks/card-page-media.scss
new file mode 100644
index 0000000000..0c4e10c701
--- /dev/null
+++ b/src/styles/blocks/card-page-media.scss
@@ -0,0 +1,90 @@
+@include desktop() {
+ .card-page {
+ @include paddingInline($paddingLaptop);
+ width: $breakpoint__l;
+ }
+ .order-content__item-ored {
+ width: 602px;
+ }
+ .order-content__checkout-block {
+ width: 298px;
+ }
+ .checkout-block__checkout-button {
+ width: 90%;
+ }
+}
+@include laptop() {
+ .card-page {
+ @include paddingInline(0);
+ width: $breakpoint__m;
+ }
+ .order-content__item-ored {
+ width: 513px;
+ height: 100px;
+ }
+ .order-content__checkout-block {
+ width: 234px;
+ }
+ .checkout-block__checkout-button {
+ width: 90%;
+ }
+ .item-ored__product-img {
+ margin-left: 15px;
+ }
+ .item-ored__delete-button {
+ margin-left: 15px;
+ }
+ .item-ored__product-name {
+ margin-left: 20px;
+ width: 178px;
+ }
+ .item-ored__price-product {
+ margin-inline: 21px 20px;
+ }
+}
+@include tablet() {
+ .card-page {
+ @include paddingInline(10);
+ @include width($breakpoint__s);
+ }
+ .card-page__order-content {
+ flex-direction: column;
+ }
+ .order-content__item-ored {
+ width: 100%;
+ }
+ .order-content__checkout-block {
+ width: 100%;
+ }
+}
+@include mobile() {
+ .card-page {
+ @include paddingInline(10);
+ @include width($breakpoint__sx);
+ }
+ .order-content__item-ored {
+ flex-direction: column;
+ height: 328px;
+ justify-content: center;
+ gap: 10px;
+ padding-top: 10px;
+ }
+ .item-ored__product-name {
+ margin-left: 49px;
+ }
+ .img-container {
+ flex-direction: row-reverse;
+ gap: 10px;
+ }
+ .item-ored__product-img {
+ margin-top: 30px;
+ width: 100px;
+ height: 100px;
+ }
+ .item-ored__product-name {
+ margin-block: 5px;
+ }
+ .item-ored__price-product {
+ margin-block: 5px;
+ }
+}
diff --git a/src/styles/blocks/cart-page.scss b/src/styles/blocks/cart-page.scss
new file mode 100644
index 0000000000..b952cb57f0
--- /dev/null
+++ b/src/styles/blocks/cart-page.scss
@@ -0,0 +1,165 @@
+.card-page {
+ @include pageMaxWidth;
+ width: 1440px;
+ margin: auto;
+ padding-block: 40px 87px;
+ flex-grow: 1;
+ &__back-link {
+ display: flex;
+ align-items: center;
+ text-decoration: none;
+ color: var(--gray-secondary, #89939a);
+ margin-bottom: 16px;
+
+ /* Small text 12 */
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ font-style: normal;
+ line-height: normal;
+ }
+ &__title {
+ margin-bottom: 24px;
+ color: $gray-primary;
+
+ /* H1 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 32px;
+ font-style: normal;
+ line-height: normal;
+ letter-spacing: -0.32px;
+ }
+ &__order-content {
+ display: flex;
+ gap: 16px;
+ justify-content: flex-end;
+ }
+}
+
+.order-content {
+ &__list-ored {
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ }
+ &__item-ored {
+ display: flex;
+ align-items: center;
+ width: 752px;
+ height: 128px;
+ flex-shrink: 0;
+ margin-bottom: 16px;
+ }
+ &__checkout-block {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 368px;
+ height: 206px;
+ flex-shrink: 0;
+ border: 1px solid $gray-elements;
+ }
+}
+
+.item-ored {
+ border: 1px solid $gray-elements;
+ &__delete-button {
+ background-color: transparent;
+ border: 0;
+ cursor: pointer;
+ margin-left: 31px;
+ }
+ &__product-img {
+ width: 66px;
+ height: 66px;
+ flex-shrink: 0;
+ margin-left: 31px;
+
+ }
+ &__product-name {
+ width: 295px;
+ margin-left: 49px;
+
+ color: $gray-primary;
+
+ /* Body text 14 */
+ font-family: Mont-Regular, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ font-style: normal;
+ line-height: 21px; /* 150% */
+ }
+ &__product-count-inc,
+ &__product-count-deg {
+ width: 32px;
+ height: 32px;
+ flex-shrink: 0;
+ background-color: #fff;
+ border: 1px solid $gray-elements;
+ cursor: pointer;
+ &:hover {
+ border-color: $gray-primary;
+ }
+ }
+ &__count-value {
+ margin-inline: 13px;
+ }
+ &__price-product {
+ margin-inline: 43px 40px;
+ color: $gray-primary;
+
+ /* H2 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 22px;
+ font-style: normal;
+ line-height: 140%; /* 30.8px */
+ }
+}
+
+.count-container {
+ display: flex;
+ align-items: center;
+}
+.img-container {
+ display: flex;
+}
+.checkout-block {
+ &__total-price-amout {
+ margin: 0;
+ margin-top: 24px;
+ color: $gray-primary;
+
+ /* H1 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 32px;
+ font-style: normal;
+ line-height: normal;
+ letter-spacing: -0.32px;
+ }
+ &__total-items-amout {
+ box-sizing: border-box;
+ width: 90%;
+ margin: 0;
+ padding-bottom: 24px;
+ margin-bottom: 24px;
+ border-bottom: 1px solid #e2e6e9;
+ color: $gray-secondary;
+ text-align: center;
+
+ /* Body text 14 */
+ font-size: 14px;
+ font-style: normal;
+ line-height: 21px; /* 150% */
+ }
+ &__checkout-button {
+ width: 320px;
+ height: 48px;
+ flex-shrink: 0;
+ bottom: 0;
+ background-color: #313237;
+ color: #fff;
+
+ &:hover {
+ background-color: #fff;
+ color: #313237;
+ }
+ }
+}
diff --git a/src/styles/blocks/custom-select.scss b/src/styles/blocks/custom-select.scss
new file mode 100644
index 0000000000..651c4d126b
--- /dev/null
+++ b/src/styles/blocks/custom-select.scss
@@ -0,0 +1,54 @@
+/* Basic styling for the custom select container */
+.custom-select {
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ width: 176px;
+ height: 40px;
+ padding: 10px 0 9px 12px;
+ flex-shrink: 0;
+ position: relative;
+ border: 1px solid #ccc;
+ cursor: pointer;
+
+ color: $gray-primary;
+
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ font-style: normal;
+ line-height: 21px; /* 150% */
+}
+
+.options {
+ box-sizing: border-box;
+ width: 176px;
+
+ display: none;
+ position: absolute;
+ top: 100%;
+ left: 0;
+ margin: 0 4px 0 0;
+ background-color: #fff;
+ border: 1px solid #ccc;
+ list-style: none;
+ padding: 8px 1px;
+
+ &__option {
+ box-sizing: border-box;
+
+ color: #89939a;
+ width: 176px;
+ height: 32px;
+ padding: 6px 0 5px 12px;
+ cursor: pointer;
+
+ &:hover {
+ background-color: #fafbfc;
+ color: #313237;
+ }
+ }
+}
+
+.options--opened {
+ display: block;
+}
diff --git a/src/styles/blocks/favorites-page-media.scss b/src/styles/blocks/favorites-page-media.scss
new file mode 100644
index 0000000000..c5dc703bb3
--- /dev/null
+++ b/src/styles/blocks/favorites-page-media.scss
@@ -0,0 +1,24 @@
+@include desktop() {
+ .favorites-page {
+ @include paddingInline($paddingLaptop);
+ width: $breakpoint__l;
+ }
+}
+@include laptop() {
+ .favorites-page {
+ @include paddingInline(0);
+ width: $breakpoint__m;
+ }
+}
+@include tablet() {
+ .favorites-page {
+ @include paddingInline(10);
+ @include width($breakpoint__s);
+ }
+}
+@include mobile() {
+ .favorites-page {
+ @include paddingInline(10);
+ @include width($breakpoint__sx);
+ }
+}
diff --git a/src/styles/blocks/favorites-page.scss b/src/styles/blocks/favorites-page.scss
new file mode 100644
index 0000000000..fd107b116e
--- /dev/null
+++ b/src/styles/blocks/favorites-page.scss
@@ -0,0 +1,27 @@
+.favorites-page {
+ @include pageMaxWidth;
+ width: 1440px;
+ margin: auto;
+ flex-grow: 1;
+ padding-bottom: 80px;
+ display: flex;
+ flex-direction: column;
+ &__title {
+ margin-block: 40px 8px;
+ color: $gray-primary;
+
+ /* H1 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 32px;
+ font-style: normal;
+ line-height: normal;
+ letter-spacing: -0.32px;
+ }
+ &__items-amount {
+ color: $gray-secondary;
+
+ font-size: 14px;
+ font-style: normal;
+ line-height: 21px; /* 150% */
+ }
+}
diff --git a/src/styles/blocks/footer.scss b/src/styles/blocks/footer.scss
new file mode 100644
index 0000000000..6d6bbc0087
--- /dev/null
+++ b/src/styles/blocks/footer.scss
@@ -0,0 +1,54 @@
+.footer {
+ background: #fff;
+ box-shadow: 0 -1px 0 0 #e2e6e9;
+ &__content {
+ display: flex;
+ @include pageMaxWidth;
+ display: flex;
+ height: 96px;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ &__link {
+ text-decoration: none;
+ color: $gray-secondary;
+
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ font-style: normal;
+ font-weight: 700;
+ line-height: 11px; /* 91.667% */
+ letter-spacing: 0.48px;
+ text-transform: uppercase;
+ }
+ &__container {
+ display: flex;
+ gap: 64px;
+ }
+ &__backtotop-button {
+ position: relative;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 32px;
+ height: 32px;
+ background-color: #fff;
+ border: 1px solid #b4bdc3;
+ cursor: pointer;
+ &::before {
+ content: "Back to top";
+ position: absolute;
+ width: max-content;
+ right: 48px;
+
+ color: $gray-secondary;
+
+ /* Small text 12 */
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ font-style: normal;
+ line-height: normal;
+ }
+ }
+}
diff --git a/src/styles/blocks/header.scss b/src/styles/blocks/header.scss
new file mode 100644
index 0000000000..ac39ef7159
--- /dev/null
+++ b/src/styles/blocks/header.scss
@@ -0,0 +1,90 @@
+.header {
+ background: $gray-white;
+ box-shadow: 0 1px 0 0 #d2e6e9;
+ &__content {
+ @include pageMaxWidth;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ text-transform: uppercase;
+ height: 64px;
+ margin: auto;
+ }
+
+ &__favorites-card-buttons {
+ display: flex;
+ }
+}
+
+.nav {
+ display: flex;
+ gap: 64px;
+
+ &__link {
+ display: inline-block;
+ height: 100%;
+ padding-block: 27px 24px;
+ text-decoration: none;
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ line-height: 11px;
+ letter-spacing: 0.04em;
+
+ color: #89939a;
+ &--active {
+ color: $gray-primary;
+ border-bottom: 3px solid $gray-primary;
+ }
+ }
+ &__burger-menu-button {
+ opacity: 1;
+ &--is-active {
+ opacity: 0;
+ }
+ }
+}
+
+.favorites-card-buttons {
+ &__link {
+ width: 64px;
+ height: 64px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ background: $gray-white;
+ box-shadow: -1px 0 0 0 #e2e6e9;
+ border-right: 1px solid #e2e6e9;
+ &--active {
+ color: $gray-primary;
+ border-bottom: 3px solid $gray-primary;
+ }
+ position: relative;
+ }
+ &__icon {
+ width: 16px;
+ height: 16px;
+ }
+ &__amount-of-products {
+ position: absolute;
+ display: flex;
+ box-sizing: border-box;
+ align-items: center;
+ justify-content: center;
+ z-index: 3;
+ background-color: #eb5757;
+ width: 15px;
+ height: 15px;
+ border-radius: 50%;
+ border: 1px solid #fff;
+ text-decoration: none;
+ color: $gray-white;
+ text-align: center;
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 9px;
+ font-style: normal;
+ line-height: normal;
+ left: 33px;
+ top: 18px;
+ }
+}
diff --git a/src/styles/blocks/home-page-media.scss b/src/styles/blocks/home-page-media.scss
new file mode 100644
index 0000000000..26fa39e1cd
--- /dev/null
+++ b/src/styles/blocks/home-page-media.scss
@@ -0,0 +1,178 @@
+@include desktop() {
+ .header__content {
+ @include paddingInline($paddingLaptop);
+ @include width($breakpoint__l);
+ }
+ .nav {
+ gap: 32px;
+ }
+ .home-page {
+ @include paddingInline($paddingLaptop);
+ @include width($breakpoint__l);
+ }
+ .preview-slider {
+ height: 319px;
+ &__container {
+ @include paddingInline(0);
+ @include width($breakpoint__l)
+ }
+ }
+ .pictures__picture {
+ height: 319px;
+ }
+ .products-slider__container-buttons {
+ left: 843px;
+ }
+ .category__img {
+ width: 296px;
+ }
+ .footer__content {
+ @include paddingInline($paddingLaptop);
+ @include width($breakpoint__l);
+ }
+}
+
+@include laptop() {
+ .header__content {
+ @include paddingInline(0);
+ @include width($breakpoint__m);
+ }
+ .home-page {
+ @include paddingInline(0);
+ @include width($breakpoint__m);
+ }
+ .preview-slider {
+ height: 260px;
+ &__container {
+ @include paddingInline(0);
+ @include width($breakpoint__m)
+ }
+ }
+ .pictures__picture {
+ height: 260px;
+ }
+ .products-slider__container-buttons {
+ left: 687px;
+ }
+ .category__img {
+ width: 245px;
+ }
+ .footer__content {
+ @include paddingInline(0);
+ @include width($breakpoint__m);
+ }
+}
+
+@include tablet() {
+ .header {
+ &__content {
+ @include paddingInline(10);
+ @include width($breakpoint__s);
+ }
+ &__nav {
+ display: none;
+ }
+ }
+ .home-page {
+ @include paddingInline(10);
+ @include width($breakpoint__s);
+ }
+ .preview-slider {
+ height: 187px;
+ &__container {
+ @include paddingInline(10);
+ @include width($breakpoint__s)
+ }
+ }
+ .pictures__picture {
+ height: 187px;
+ }
+ .products-slider__container-products {
+ gap: 8px;
+
+ }
+ .product-card {
+ width: 280px;
+ }
+ .products-slider__container-buttons {
+ left: 497px;
+ }
+ .category__img {
+ width: 175px;
+ }
+ .footer__content {
+ @include width($breakpoint__s);
+ margin: auto;
+ }
+ .hamburger-button {
+ display: block;
+ }
+}
+
+@include mobile() {
+ .header {
+ &__content {
+ @include paddingInline(10);
+ @include width($breakpoint__sx);
+ }
+ &__nav {
+ display: none;
+ }
+ }
+ .home-page {
+ @include paddingInline(10);
+ @include width($breakpoint__sx);
+ }
+ .preview-slider {
+ height: 127px;
+ &__container {
+ @include paddingInline(10);
+ @include width($breakpoint__sx)
+ }
+ }
+ .pictures__picture {
+ height: 127px;
+ }
+ .products-slider__container-products {
+ gap: 8px;
+ }
+ .product-card {
+ width: 280px;
+ }
+ .products-slider__container-buttons {
+ left: 221px;
+ }
+ .shop-by-category__container {
+ flex-direction: column;
+ }
+ .category__img {
+ width: 100%;
+ }
+ .brand-new__title {
+ width: 190px;
+ }
+ .footer {
+ @include paddingInline(10);
+ padding-block: 30px;
+ display: flex;
+ justify-content: center;
+ padding-inline: 0;
+ margin: 0;
+ &__content {
+ @include width($breakpoint__sx);
+ display: flex;
+ flex-direction: column;
+ margin: auto;
+ }
+ &__container {
+ gap: 45px;
+ }
+ .footer__backtotop-button {
+ width: 40px;
+ }
+ }
+ .burger-menu__nav-burger {
+ @include paddingInline(10);
+ @include width($breakpoint__sx);
+ }
+}
diff --git a/src/styles/blocks/home-page.scss b/src/styles/blocks/home-page.scss
new file mode 100644
index 0000000000..61d74935bd
--- /dev/null
+++ b/src/styles/blocks/home-page.scss
@@ -0,0 +1,110 @@
+.home-page {
+ @include pageMaxWidth;
+ &__preview-slider {
+ margin-bottom: 72px;
+ }
+ &__hot-prices {
+ margin-bottom: 80px;
+ }
+ &__shop-by-category {
+ margin-bottom: 80px;
+ }
+
+ &__brand-new {
+ margin-bottom: 80px;
+ }
+}
+
+.preview-slider {
+ display: flex;
+ gap: 16px;
+ margin-top: 40px;
+ height: 400px;
+
+ &__button {
+ width: 32px;
+ background: $gray-white;
+ border: 1px solid #b4bdc3;
+ cursor: pointer;
+ }
+
+ &__pictures {
+ width: 100%;
+ background-color: #808080;
+ }
+ &__container {
+ width: 1040px;
+ }
+}
+
+.pictures {
+ display: flex;
+ overflow: hidden;
+ &__picture {
+ width: 100%;
+ object-fit: fill;
+ }
+}
+
+.pagination {
+ text-align: center;
+ &__indicator {
+ cursor: pointer;
+ width: 15px;
+ height: 5px;
+ background-color: #b4bdc3;
+ border: #b4bdc3;
+ margin-inline: 5px;
+
+ &--active {
+ background-color: black;
+ }
+ }
+}
+
+.hot-prices {
+ &__title {
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ }
+}
+
+.shop-by-category {
+ &__title {
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ }
+ &__container {
+ display: flex;
+ gap: 16px;
+ }
+}
+
+.category {
+ &__link {
+ display: inline-block;
+ box-sizing: border-box;
+ height: fit-content;
+ margin-bottom: 24px;
+ }
+ &__title {
+ margin: 0;
+
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ color: $gray-primary;
+
+ /* H3 */
+ font-size: 20px;
+ line-height: normal;
+ }
+ &__description {
+ margin: 0;
+ color: $gray-secondary;
+
+ /* Body text 14 */
+ font-size: 14px;
+ line-height: 21px; /* 150% */
+ }
+}
+
+.brand-new {
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+}
diff --git a/src/styles/blocks/info-messages.scss b/src/styles/blocks/info-messages.scss
new file mode 100644
index 0000000000..38ee3b41bd
--- /dev/null
+++ b/src/styles/blocks/info-messages.scss
@@ -0,0 +1,4 @@
+.info-messages {
+ font-family: Mont-Regular;
+ font-size: 21px;
+}
diff --git a/src/styles/blocks/pagination.scss b/src/styles/blocks/pagination.scss
new file mode 100644
index 0000000000..b0e6f1d251
--- /dev/null
+++ b/src/styles/blocks/pagination.scss
@@ -0,0 +1,26 @@
+.pagination {
+ display: flex;
+ gap: 8px;
+ justify-content: center;
+ margin-top: 18px;
+ list-style: none;
+ padding: 0;
+ &__items {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ box-sizing: border-box;
+ width: 32px;
+ height: 32px;
+ flex-shrink: 0;
+ border: 1px solid #e2e6e9;
+ cursor: pointer;
+ &:hover {
+ border: 1px solid $gray-primary;
+ }
+ &--isActive {
+ color: #fff;
+ background-color: #313237;
+ }
+ }
+}
diff --git a/src/styles/blocks/phones-page-media.scss b/src/styles/blocks/phones-page-media.scss
new file mode 100644
index 0000000000..58fd675abc
--- /dev/null
+++ b/src/styles/blocks/phones-page-media.scss
@@ -0,0 +1,93 @@
+@include desktop() {
+ .phones-page {
+ @include paddingInline($paddingLaptop);
+ @include width($breakpoint__l);
+ }
+ .search-bar {
+ width: 268px;
+ &__clear-button {
+ left: 231px;
+ }
+ }
+ .pagination {
+ @include width($breakpoint__l);
+ }
+ .products-list {
+ grid-template-columns: repeat(3, 1fr);
+ }
+ .product-card {
+ justify-content: center;
+ width: 100%;
+ }
+}
+
+@include laptop() {
+ .phones-page {
+ @include paddingInline(0);
+ width: $breakpoint__m;
+ }
+ .search-bar {
+ width: 188px;
+ &__clear-button {
+ left: 159px;
+ }
+ &__search-bar-input {
+ padding-left: 12px;
+ }
+ }
+ .pagination {
+ @include width($breakpoint__s);
+ gap: 9px;
+ }
+ .products-list {
+ grid-template-columns: repeat(2, 1fr);
+ }
+}
+
+@include tablet() {
+ .phones-page {
+ @include paddingInline(10);
+ @include width($breakpoint__s);
+ }
+ .search-bar {
+ width: 268px;
+ &__clear-button {
+ left: 231px;
+ }
+ &__search-bar-input {
+ padding-left: 24px;
+ }
+ }
+ .product-card {
+ padding-inline: 27px;
+ }
+}
+
+@include mobile() {
+ .phones-page {
+ @include paddingInline(10);
+ @include width($breakpoint__sx);
+ }
+ .search-bar {
+ width: 178px;
+ &__clear-button {
+ left: 149px;
+ }
+ &__search-bar-input {
+ padding-left: 12px;
+ font-size: 12px;
+ }
+ }
+ .custom-select {
+ width: 132px;
+ }
+ .favorites-card-buttons__link {
+ width: 43px;
+ }
+ .favorites-card-buttons__amount-of-products {
+ left: 23px;
+ }
+ .products-list {
+ grid-template-columns: 1fr;
+ }
+}
diff --git a/src/styles/blocks/phones-page.scss b/src/styles/blocks/phones-page.scss
new file mode 100644
index 0000000000..b0d99fb632
--- /dev/null
+++ b/src/styles/blocks/phones-page.scss
@@ -0,0 +1,70 @@
+.phones-page {
+ @include pageMaxWidth;
+ flex-grow: 1;
+ box-sizing: border-box;
+ margin: auto;
+ margin-bottom: 80px;
+ &__title {
+ margin: 0;
+ margin-bottom: 8px;
+ color: $gray-primary;
+
+ /* H1 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 32px;
+ line-height: normal;
+ letter-spacing: -0.32px;
+ }
+ &__amount-phone-text {
+ margin: 0;
+ margin-bottom: 40px;
+ color: $gray-secondary;
+
+ /* Body text 14 */
+ font-size: 14px;
+ line-height: 21px; /* 150% */
+ }
+
+ &__result-items {
+ margin-block: 24px;
+ color: $gray-secondary;
+
+ /* Body text 14 */
+ font-size: 14px;
+ font-style: normal;
+ line-height: 21px; /* 150% */
+ }
+}
+
+.filter {
+ display: flex;
+ gap: 16px;
+ &__title {
+ margin: 0;
+ margin-bottom: 4px;
+ color: $gray-secondary;
+
+ /* Small text 12 */
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ font-style: normal;
+ line-height: normal;
+ }
+ &__selector {
+ width: 174px;
+ height: 40px;
+ flex-shrink: 0;
+ padding: 10px 12px 9px;
+ font-family: Mont-Regular, Arial, Helvetica, sans-serif;
+ border: 1px solid #b4bdc3;
+
+ &--items {
+ padding: 10px 12px 9px;
+ height: 40px;
+
+ &:hover {
+ background-color: $gray-background;
+ }
+ }
+ }
+}
diff --git a/src/styles/blocks/pop-up.scss b/src/styles/blocks/pop-up.scss
new file mode 100644
index 0000000000..8f822b6c0c
--- /dev/null
+++ b/src/styles/blocks/pop-up.scss
@@ -0,0 +1,42 @@
+.pop-up {
+ position: absolute;
+ z-index: 2;
+ width: 100vw;
+ height: 100vh;
+ background-color: #ffffff80;
+ top: 0;
+ left: 0;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+}
+
+.content-pop-up {
+ display: flex;
+ background-color: #ffffffe2;
+ height: 150px;
+ border: 1px solid #eb5757;
+ padding-left: 20px;
+ &__title-message {
+ display: flex;
+ align-items: center;
+ color: $gray-secondary;
+ font-family: Mont-SemiBold;
+ font-size: 20px;
+ margin-right: 10px;
+ }
+ &__close-button {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ background-color: transparent;
+ height: 28px;
+ border: 0;
+ &:hover {
+ background-color: #fff;
+ }
+ }
+ &:hover {
+ border-color:#27ae60;
+ }
+}
diff --git a/src/styles/blocks/product-card.scss b/src/styles/blocks/product-card.scss
new file mode 100644
index 0000000000..8b81ce4905
--- /dev/null
+++ b/src/styles/blocks/product-card.scss
@@ -0,0 +1,122 @@
+.product-card {
+ display: grid;
+ justify-content: center;
+ box-sizing: border-box;
+ width: 272px;
+ height: 507px;
+ border: 1px solid $gray-elements;
+ background: $gray-white;
+ padding: 32px 32px 24px;
+
+ &__link {
+ text-decoration: none;
+ color: $gray-primary;
+ }
+
+ &__phone-img {
+ box-sizing: border-box;
+ width: 208px;
+ height: 208px;
+ padding-inline: 12px;
+ margin-bottom: 24px;
+ }
+
+ &__title {
+ display: block;
+ min-height: 42px;
+ margin: 0;
+ margin-bottom: 8px;
+ font-family: Mont-Regular, Arial, Helvetica, sans-serif;
+ color: $gray-primary;
+
+ font-size: 14px;
+ line-height: 21px; /* 150% */
+ &:hover {
+ text-decoration: underline;
+ }
+ }
+
+ &__price {
+ display: inline-block;
+ margin: 0;
+ color: $gray-primary;
+ margin-right: 8px;
+
+ /* H2 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 22px;
+ line-height: 140%; /* 30.8px */
+
+ &--discount {
+ color: $gray-secondary;
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ line-height: normal;
+ text-decoration-line: line-through;
+ }
+ }
+
+ &__container {
+ display: flex;
+ gap: 8px;
+ }
+
+ &__add-to-card {
+ box-sizing: border-box;
+ width: 176px;
+ height: 40px;
+ background-color: #313237;
+
+ color: $gray-white;
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ font-weight: 600;
+ line-height: 21px; /* 150% */
+ cursor: pointer;
+
+ &--is-added {
+ color: #27ae60;
+ background-color: #e2e6e9;
+ border: 1px solid $gray-elements;
+ }
+ &:hover {
+ box-shadow: 0 3px 13px 0 rgba(23, 32, 49, 0.26);
+ }
+ }
+
+ &__add-to-favorites {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ box-sizing: border-box;
+ gap: 8px;
+ width: 40px;
+ height: 40px;
+ border: 1px solid $gray-icons-placeholders;
+ background-color: #fff;
+
+ cursor: pointer;
+ &:hover {
+ border-color: #313237;
+ }
+ }
+}
+
+.description-phone {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ border-top: 1px solid #e2e6e9;
+ margin-block: 8px 16px;
+ padding-top: 16px;
+
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+
+ &--title {
+ color: $gray-secondary;
+ }
+}
+
+.add-to-favorites__icon {
+ width: 16px;
+ height: 16px;
+}
diff --git a/src/styles/blocks/product-details-page-media.scss b/src/styles/blocks/product-details-page-media.scss
new file mode 100644
index 0000000000..a758a7ba16
--- /dev/null
+++ b/src/styles/blocks/product-details-page-media.scss
@@ -0,0 +1,183 @@
+@include desktop() {
+ .product-details-page {
+ @include paddingInline($paddingLaptop);
+ width: $breakpoint__l;
+ }
+ .big-img-container {
+ margin-inline: 15px 27px;
+ }
+}
+@include laptop() {
+ .product-details-page {
+ @include paddingInline(0);
+ width: $breakpoint__m;
+ }
+ .product-details-page__product-section {
+ margin-bottom: 70px;
+ }
+ .big-img-container {
+ width: 337px;
+ margin: 0;
+ &__big-img {
+ height: 407px;
+ }
+ }
+ .choose-section__colors-picker {
+ padding-bottom: 12px;
+ }
+ .capasity-picker {
+ padding-bottom: 12px;
+ }
+ .prices-amount {
+ margin-block: 22px 12px;
+ }
+ .buttons-buy-like {
+ gap: 5%;
+ }
+ .product-details-page__product-articles {
+ margin-bottom: 70px;
+ }
+}
+@include tablet() {
+ .product-details-page {
+ @include paddingInline(10);
+ @include width($breakpoint__s);
+ }
+ .product-details-page__title {
+ font-size: 27px;
+ }
+ .product-details-page__product-section {
+ margin-bottom: 40px;
+ }
+ .big-img-container {
+ width: 274px;
+ margin: 0;
+ &__big-img {
+ height: 345px;
+ }
+ }
+ .choose-section {
+ width: 200px;
+ }
+ .capasity-picker__items {
+ width: 60px;
+ }
+ .buttons-buy-like__add-to-card {
+ width: 143px;
+ }
+ .product-details-page__product-articles {
+ margin-bottom: 40px;
+ }
+ .tech-specs__tech-specs-list {
+ grid-template-columns: 85px 170px;
+ }
+}
+@include mobile() {
+ .product-details-page {
+ @include paddingInline(10);
+ @include width($breakpoint__sx);
+ }
+ .breadcrumb-item__link {
+ gap: 4px;
+ font-size: 9px;
+ }
+ .product-details-page__title {
+ font-size: 15px;
+ }
+ .product-details-page__product-section {
+ margin-bottom: 0;
+ }
+ .big-img-container {
+ width: 150px;
+ margin: 0;
+ &__big-img {
+ height: 193px;
+ }
+ }
+ .small-img-container__img {
+ width: 40px;
+ height: 40px;
+
+ }
+ .gallery {
+ flex-direction: column;
+ }
+ .gallery__small-img-container {
+ flex-direction: row;
+ gap: 0;
+ flex-wrap: wrap;
+ }
+ .colors-picker__list {
+ gap: 4px;
+ }
+ .items-colors {
+ width: 25px;
+ height: 25px;
+ }
+ .items-colors__color {
+ width: 23px;
+ height: 23px;
+ }
+ .capasity-picker__items {
+ width: 35px;
+ font-size: 9px;
+
+ }
+ .choose-section {
+ width: 127px;
+ }
+ .buy-buttons__prices-amount {
+ gap: 6px;
+ }
+ .prices-amount {
+ margin-block: 12px 10px;
+ }
+ .prices-amount__price {
+ font-size: 22px;
+ }
+ .buttons-buy-like__add-to-card {
+ width: 80px;
+ height: 35px;
+ font-size: 10px;
+ }
+ .buttons-buy-like__add-to-favorites {
+ height: 35px;
+ width: 35px;
+ }
+ .description-product {
+ grid-template-columns: 27px 80px;
+ font-size: 8px;
+ gap: 4px;
+ }
+ .product-articles {
+ flex-direction: column;
+ gap: 30px;
+ }
+ .product-articles__article-about {
+ width: 300px;
+ }
+ .article-about__title {
+ font-size: 15px;
+ margin-block: 12px;
+ }
+ .article-about__sub-title {
+ margin-block: 12px 5px;
+ font-size: 13px;
+ }
+ .article-about__text {
+ margin-block: 4px;
+ font-size: 10px;
+ }
+ .tech-specs {
+ width: 300px;
+ &__tech-specs-list {
+ grid-template-columns: 100px 200px;
+ }
+ &__title {
+ font-size: 15px;
+ }
+ }
+ .you-may-like__title {
+ font-size: 15px;
+ }
+}
diff --git a/src/styles/blocks/product-details-page.scss b/src/styles/blocks/product-details-page.scss
new file mode 100644
index 0000000000..1f148326a8
--- /dev/null
+++ b/src/styles/blocks/product-details-page.scss
@@ -0,0 +1,375 @@
+.product-details-page {
+ @include pageMaxWidth;
+ &__link-move-back {
+ display: flex;
+ align-items: center;
+ gap: 4px;
+ text-decoration: none;
+ color: $gray-secondary;
+
+ /* Small text 12 */
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ font-style: normal;
+ line-height: normal;
+ }
+
+ &__title {
+ color: $gray-primary;
+
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 32px;
+ font-style: normal;
+ line-height: normal;
+ letter-spacing: -0.32px;
+ }
+
+ &__product-section {
+ margin-bottom: 80px;
+ }
+ &__product-articles {
+ margin-bottom: 80px;
+ }
+ &__you-may-like {
+ margin-bottom: 80px;
+ }
+}
+
+.link-move-back__arrow {
+ width: 16px;
+ height: 16px;
+ flex-shrink: 0;
+}
+
+.product-section {
+ display: flex;
+}
+
+.choose-section {
+ &__colors-picker {
+ padding-bottom: 24px;
+ border-bottom: 1px solid #e2e6e9;
+ }
+}
+
+.gallery {
+ display: flex;
+ &__small-img-container {
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ cursor: pointer;
+ }
+}
+
+.small-img-container {
+ &__img {
+ box-sizing: border-box;
+ width: 80px;
+ height: 80px;
+ flex-shrink: 0;
+ padding-inline: 10px;
+ }
+}
+
+.big-img-container {
+ display: flex;
+ justify-content: center;
+ padding: 11px;
+ margin-inline: 28px 75px;
+ width: 442px;
+
+ &__big-img {
+ height: 442px;
+ flex-shrink: 0;
+ }
+}
+
+.colors-picker {
+ &__title {
+ color: $gray-secondary;
+
+ /* Small text 12 */
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ font-style: normal;
+ line-height: normal;
+ }
+ &__items-colors {
+ list-style: none;
+ padding: 0;
+ }
+ &__list {
+ display: flex;
+ flex-wrap: wrap;
+ padding: 0;
+ gap: 8px;
+ margin: 0;
+ }
+}
+
+.items-colors {
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 100%;
+ width: 32px;
+ height: 32px;
+ padding: 0;
+ margin: 0;
+ background-color: #e2e6e9;
+ &:hover {
+ background-color: #313237;
+ }
+
+ &__color {
+ display: flex;
+ box-sizing: border-box;
+ justify-self: center;
+ align-items: center;
+ width: 29px;
+ height: 29px;
+ flex-shrink: 0;
+ border-radius: 50%;
+ margin: 0;
+ border: 2px solid #fff;
+
+ &--active {
+ transform: scale(1.1);
+ border-color: #313237;
+ }
+ }
+}
+
+.capasity-picker {
+ padding-bottom: 24px;
+ border-bottom: 1px solid #e2e6e9;
+ &__title {
+ color: $gray-secondary;
+
+ /* Small text 12 */
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+ font-style: normal;
+ line-height: normal;
+ }
+ &__list {
+ display: flex;
+ list-style: none;
+ padding: 0;
+ margin: 0;
+ gap: 8px;
+ }
+ &__items {
+ cursor: pointer;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 63px;
+ height: 32px;
+ flex-shrink: 0;
+ border: 1px solid $gray-icons-placeholders;
+ &:hover {
+ border-color: $gray-primary;
+ }
+ &--active {
+ background-color: $gray-primary;
+ color: #fff;
+ }
+ }
+}
+
+.buy-buttons {
+ &__prices-amount {
+ display: flex;
+ gap: 8px;
+ }
+}
+
+.prices-amount {
+ display: flex;
+ align-items: center;
+ margin-block: 32px 16px;
+ &__price {
+ margin: 0;
+ color: $gray-primary;
+
+ /* H1 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 32px;
+ font-style: normal;
+ line-height: normal;
+ letter-spacing: -0.32px;
+ &--discount {
+ color: $gray-secondary;
+ font-family: Mont-Regular, Arial, Helvetica, sans-serif;
+ font-size: 22px;
+ font-style: normal;
+ line-height: normal;
+ text-decoration: line-through;
+ }
+ }
+}
+
+.buttons-buy-like {
+ display: flex;
+ gap: 8%;
+ &__add-to-card {
+ width: 263px;
+ height: 48px;
+ flex-shrink: 0;
+ background-color: $gray-primary;
+ color: $gray-white;
+ text-align: center;
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ font-style: normal;
+ line-height: 21px; /* 150% */
+ cursor: pointer;
+ &--is-added {
+ color: #27ae60;
+ background-color: #e2e6e9;
+ border: 1px solid $gray-elements;
+ }
+ &:hover {
+ box-shadow: 0 3px 13px 0 rgba(23, 32, 49, 0.26);
+ }
+ }
+ &__add-to-favorites {
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ width: 48px;
+ height: 48px;
+ flex-shrink: 0;
+ background-color: #fff;
+ border: 1px solid #b4bdc3;
+ cursor: pointer;
+ &:hover {
+ border-color: #313237;
+ }
+ }
+}
+
+.details-product {
+ &__description-product {
+ margin-top: 32px;
+ }
+}
+
+.description-product {
+ display: grid;
+ grid-template-columns: repeat(2, 1fr);
+ margin-block: 8px 16px;
+ padding-top: 16px;
+ row-gap: 8px;
+
+ /* Small text 12 */
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 12px;
+
+ &--title {
+ color: $gray-secondary;
+
+ }
+}
+
+.product-articles {
+ display: flex;
+ gap: 64px;
+ &__article-about {
+ width: 559px;
+ }
+}
+
+.article-about {
+ &__title {
+ padding-bottom: 6px;
+ border-bottom: 1px solid #e2e6e9;
+ color: $gray-primary;
+ margin-bottom: 32px;
+
+ /* H2 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 22px;
+ font-style: normal;
+ line-height: 140%; /* 30.8px */
+ }
+ &__sub-title {
+ color: $gray-primary;
+ margin-block: 32px 16px;
+
+ /* H3 */
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 20px;
+ font-style: normal;
+ line-height: normal;
+ }
+ &__text {
+ color: #89939a;
+
+ /* Body text 14 */
+ font-family: Mont-Regular, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ font-style: normal;
+ line-height: 21px; /* 150% */
+ }
+}
+
+.tech-specs {
+ width: 512px;
+ &__title {
+ padding-bottom: 6px;
+ border-bottom: 1px solid #e2e6e9;
+ color: $gray-primary;
+ margin-bottom: 32px;
+
+ /* H2 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 22px;
+ font-style: normal;
+ line-height: 140%; /* 30.8px */
+ }
+ &__tech-specs-list {
+ display: grid;
+ grid-template-columns: 1fr 2fr;
+ row-gap: 8px;
+ }
+}
+
+.tech-specs-list {
+ &--title {
+ color: $gray-secondary;
+
+ /* Body text 14 */
+ font-family: Mont-Regular, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ font-style: normal;
+ line-height: 21px; /* 150% */
+ }
+ &--value {
+ text-align: end;
+ display: flex;
+ justify-content: end;
+ flex-wrap: wrap;
+ font-size: 14px;
+ &-cell {
+ margin: 1px;
+ font-size: 14px;
+ }
+ }
+}
+
+.you-may-like {
+ &__title {
+ color: $gray-primary;
+
+ /* H1 */
+ font-family: Mont-Bold, Arial, Helvetica, sans-serif;
+ font-size: 32px;
+ font-style: normal;
+ line-height: normal;
+ letter-spacing: -0.32px;
+ }
+}
diff --git a/src/styles/blocks/products-list.scss b/src/styles/blocks/products-list.scss
new file mode 100644
index 0000000000..5ac499e4b6
--- /dev/null
+++ b/src/styles/blocks/products-list.scss
@@ -0,0 +1,12 @@
+.products-list {
+ display: grid;
+ grid-template-columns: repeat(4, 1fr);
+ max-width: 1136px;
+
+ gap: 16px;
+ padding: 0;
+
+ &__item {
+ list-style: none;
+ }
+}
diff --git a/src/styles/blocks/products-slider.scss b/src/styles/blocks/products-slider.scss
new file mode 100644
index 0000000000..9c6e4cd338
--- /dev/null
+++ b/src/styles/blocks/products-slider.scss
@@ -0,0 +1,30 @@
+.products-slider {
+ // display: flex;
+ position: relative;
+
+ &__container-buttons {
+ display: flex;
+ gap: 16px;
+ position: absolute;
+ top: -60px;
+ left: 1054px;
+ z-index: 2;
+ }
+ &__buttons {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 32px;
+ height: 32px;
+ border: 1px solid #e2e6e9;
+ background-color: #fff;
+ padding: 0;
+ cursor: pointer;
+ }
+ &__container-products {
+ display: flex;
+ overflow: hidden;
+ width: 100%;
+ gap: 16px;
+ }
+}
diff --git a/src/styles/blocks/search-bar.scss b/src/styles/blocks/search-bar.scss
new file mode 100644
index 0000000000..ecb31d61e2
--- /dev/null
+++ b/src/styles/blocks/search-bar.scss
@@ -0,0 +1,38 @@
+.search-bar {
+ position: relative;
+ box-sizing: border-box;
+ width: 327px;
+ height: 64px;
+
+ flex-shrink: 0;
+ &__search-bar-input {
+ box-sizing: border-box;
+ padding-left: 24px;
+ height: 64px;
+ width: 99%;
+ border: 0;
+ background-color: $gray-white;
+ box-shadow: -1px 0 0 0 #e2e6e9;
+
+ color: $gray-icons-placeholders;
+ font-family: Mont-SemiBold, Arial, Helvetica, sans-serif;
+ font-size: 14px;
+ font-style: normal;
+ line-height: normal;
+
+ &:focus {
+ color: $gray-primary;
+ background-color: $gray-background;
+ box-shadow: -1px 0 0 0 #e2e6e9;
+ outline: 0;
+ }
+ }
+ &__clear-button {
+ cursor: pointer;
+ background-color: transparent;
+ border: 0;
+ position: absolute;
+ top: 24px;
+ left: 287px;
+ }
+}
diff --git a/src/styles/styles.scss b/src/styles/styles.scss
new file mode 100644
index 0000000000..5e122456ab
--- /dev/null
+++ b/src/styles/styles.scss
@@ -0,0 +1,26 @@
+@import "./utils/mixins";
+@import "./utils/media_breakpoints.scss";
+
+@import "./blocks/breadcrumb";
+@import "./blocks/header";
+@import "./blocks/home-page";
+@import "./blocks/phones-page";
+@import "./blocks/product-card";
+@import "./blocks/product-details-page";
+@import "./blocks/products-list";
+@import "./blocks/products-slider";
+@import "./blocks/footer";
+@import "./blocks/custom-select";
+@import "./blocks/pagination";
+@import "./blocks/favorites-page";
+@import "./blocks/search-bar";
+@import "./blocks/cart-page";
+@import "./blocks/info-messages";
+@import "./blocks/pop-up";
+@import "./blocks/burger-menu";
+
+@import "./blocks/home-page-media";
+@import "./blocks/phones-page-media";
+@import "./blocks/favorites-page-media";
+@import "./blocks/card-page-media";
+@import "./blocks/product-details-page-media";
diff --git a/src/styles/utils/media_breakpoints.scss b/src/styles/utils/media_breakpoints.scss
new file mode 100644
index 0000000000..d508735e09
--- /dev/null
+++ b/src/styles/utils/media_breakpoints.scss
@@ -0,0 +1,23 @@
+@mixin desktop() {
+ @media (max-width: $breakpoint__xl) {
+ @content;
+ }
+}
+
+@mixin laptop() {
+ @media (max-width: $breakpoint__l) {
+ @content;
+ }
+}
+
+@mixin tablet() {
+ @media (max-width: $breakpoint__m) {
+ @content;
+ }
+}
+
+@mixin mobile() {
+ @media (max-width: $breakpoint__s) {
+ @content;
+ }
+}
diff --git a/src/styles/utils/mixins.scss b/src/styles/utils/mixins.scss
new file mode 100644
index 0000000000..5eb90f3ffb
--- /dev/null
+++ b/src/styles/utils/mixins.scss
@@ -0,0 +1,31 @@
+@mixin pageMaxWidth() {
+ max-width: 1440px;
+ box-sizing: border-box;
+ padding-inline: 152px;
+ margin: auto;
+}
+
+@mixin paddingInline($value) {
+ padding-inline: $value;
+}
+@mixin width($value) {
+ max-width: $value;
+}
+@mixin hight($value) {
+ height: $value;
+}
+
+$gray-elements: #e2e6e9;
+$gray-primary: #313237;
+$gray-secondary: #89939a;
+$gray-white: #fff;
+$gray-background: #fafbfc;
+$gray-icons-placeholders: #b4bdc3;
+
+$breakpoint__xl: 1400px;
+$breakpoint__l: 1024px;
+$breakpoint__m: 767px;
+$breakpoint__s: 576px;
+$breakpoint__sx: 300px;
+
+$paddingLaptop: 52px;
diff --git a/src/types/AsyncStatus.ts b/src/types/AsyncStatus.ts
new file mode 100644
index 0000000000..bc6d40f47d
--- /dev/null
+++ b/src/types/AsyncStatus.ts
@@ -0,0 +1,5 @@
+export enum AsyncStatus {
+ IDLE = 'idle',
+ LOADING = 'loading',
+ FAILED = 'failed',
+}
diff --git a/src/types/KeyJson.ts b/src/types/KeyJson.ts
new file mode 100644
index 0000000000..ae482482dc
--- /dev/null
+++ b/src/types/KeyJson.ts
@@ -0,0 +1,4 @@
+export enum KeyJson {
+ CARD = 'cardedPhones',
+ DETAILS = 'productDetails',
+}
diff --git a/src/types/OldApiPhoneDetails.ts b/src/types/OldApiPhoneDetails.ts
new file mode 100644
index 0000000000..d3a189c0fc
--- /dev/null
+++ b/src/types/OldApiPhoneDetails.ts
@@ -0,0 +1,49 @@
+export type OldApiPhoneDetails = {
+ id: string,
+ additionalFeatures: string,
+ android: {
+ os: string,
+ ui: string,
+ },
+ camera: {
+ features: string[],
+ primary: string,
+ }
+ availability: string[],
+ battery: {
+ standbyTime: string,
+ talkTime: string,
+ type: string,
+ },
+ images: string[],
+ connectivity: {
+ bluetooth: string,
+ cell: string,
+ gps: string,
+ infrared: boolean,
+ wifi: string,
+ [key: string]: string | boolean,
+ },
+ display: {
+ screenResolution: string,
+ screenSize: string,
+ touchScreen: boolean,
+ },
+ hardware: {
+ accelerometer: boolean,
+ audioJack: string,
+ cpu: string,
+ fmRadio: boolean,
+ physicalKeyboard: boolean,
+ usb: string,
+ },
+ sizeAndWeight: {
+ dimensions: string[],
+ weight: string,
+ },
+ storage: {
+ flash: string,
+ ram: string,
+ },
+ description: string,
+};
diff --git a/src/types/PhoneDetails.ts b/src/types/PhoneDetails.ts
new file mode 100644
index 0000000000..bbb150175b
--- /dev/null
+++ b/src/types/PhoneDetails.ts
@@ -0,0 +1,24 @@
+export type PhoneDetails = {
+ id: string,
+ namespaceId: string,
+ name: string,
+ capacity: string,
+ capacityAvailable: string[],
+ priceRegular: number,
+ priceDiscount: number,
+ colorsAvailable: string[],
+ color: string,
+ images: string[],
+ description: [
+ { title: string, text: string[] },
+ { title: string, text: string[] },
+ { title: string, text: string[] },
+ ],
+ screen: string,
+ resolution: string,
+ processor: string,
+ ram: string,
+ camera: string,
+ zoom: string,
+ cell: string[],
+};
diff --git a/src/types/Product.ts b/src/types/Product.ts
new file mode 100644
index 0000000000..959c29b534
--- /dev/null
+++ b/src/types/Product.ts
@@ -0,0 +1,21 @@
+export type Product = {
+ id: string,
+ category: string,
+ phoneId: string,
+ itemId: string,
+ name: string,
+ fullPrice: number,
+ price: number,
+ screen: string,
+ capacity: string,
+ color: string,
+ ram: string
+ year: number,
+ age: number,
+ type: string
+ image: string,
+ imageUrl: string,
+ imgUrl: string,
+ snippet: string,
+ discount: number,
+};
diff --git a/src/types/SavedCard.ts b/src/types/SavedCard.ts
new file mode 100644
index 0000000000..eb7b54e607
--- /dev/null
+++ b/src/types/SavedCard.ts
@@ -0,0 +1,7 @@
+import { Product } from './Product';
+
+export type SavedCard = {
+ id: string,
+ amount: number,
+ value: Product,
+};
diff --git a/src/types/SelectAmountItems.ts b/src/types/SelectAmountItems.ts
new file mode 100644
index 0000000000..1a49044435
--- /dev/null
+++ b/src/types/SelectAmountItems.ts
@@ -0,0 +1,6 @@
+export enum SelectAmountItems {
+ FORTH = '4',
+ EIGHT = '8',
+ SIXTEEN = '16',
+ ALL = 'all',
+}
diff --git a/src/types/SelectOptionsArr.ts b/src/types/SelectOptionsArr.ts
new file mode 100644
index 0000000000..7c8c868cd5
--- /dev/null
+++ b/src/types/SelectOptionsArr.ts
@@ -0,0 +1,14 @@
+import { SelectAmountItems } from './SelectAmountItems';
+import { SortByOptions } from './SortByOptions';
+
+export const sortByOptions = [
+ { text: 'Newest', value: SortByOptions.AGE },
+ { text: 'Cheapest', value: SortByOptions.PRICE },
+ { text: 'Alphabetically', value: SortByOptions.NAME },
+];
+export const itemsOnPageOptions = [
+ { text: '4', value: SelectAmountItems.FORTH },
+ { text: '8', value: SelectAmountItems.EIGHT },
+ { text: '16', value: SelectAmountItems.SIXTEEN },
+ { text: 'ALL', value: SelectAmountItems.ALL },
+];
diff --git a/src/types/SelectedOptions.ts b/src/types/SelectedOptions.ts
new file mode 100644
index 0000000000..13f6e6a703
--- /dev/null
+++ b/src/types/SelectedOptions.ts
@@ -0,0 +1,7 @@
+import { SelectAmountItems } from './SelectAmountItems';
+import { SortByOptions } from './SortByOptions';
+
+export type SelectedOptions = {
+ sortBy: SortByOptions,
+ itemsShow: SelectAmountItems,
+};
diff --git a/src/types/SelectedStatus.ts b/src/types/SelectedStatus.ts
new file mode 100644
index 0000000000..3c3f479ec1
--- /dev/null
+++ b/src/types/SelectedStatus.ts
@@ -0,0 +1,4 @@
+export enum SelectedStatus {
+ SELECTED = 'selected',
+ UNSELECTED = 'unselected',
+}
diff --git a/src/types/SortByOptions.ts b/src/types/SortByOptions.ts
new file mode 100644
index 0000000000..dd55fe0f87
--- /dev/null
+++ b/src/types/SortByOptions.ts
@@ -0,0 +1,5 @@
+export enum SortByOptions {
+ AGE = 'age',
+ NAME = 'name',
+ PRICE = 'price',
+}
diff --git a/src/utils/fetchClient.ts b/src/utils/fetchClient.ts
new file mode 100644
index 0000000000..61934f8d2a
--- /dev/null
+++ b/src/utils/fetchClient.ts
@@ -0,0 +1,48 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
+// eslint-disable-next-line max-len
+const BASE_URL = 'https://mate-academy.github.io/react_phone-catalog';
+
+// returns a promise resolved after a given delay
+function wait(delay: number) {
+ return new Promise(resolve => {
+ setTimeout(resolve, delay);
+ });
+}
+
+// To have autocompletion and avoid mistypes
+type RequestMethod = 'GET';
+
+function request(
+ url: string,
+ method: RequestMethod = 'GET',
+ data: any = null, // we can send any data to the server
+): Promise {
+ const options: RequestInit = { method };
+
+ if (data) {
+ // We add body and Content-Type only for the requests with data
+ options.body = JSON.stringify(data);
+ options.headers = {
+ 'Content-Type': 'application/json; charset=UTF-8',
+ };
+ }
+
+ // we wait for testing purpose to see loaders
+ return wait(300)
+ .then(() => fetch(BASE_URL + url, options))
+ .then(response => {
+ if (!response.ok) {
+ throw new Error(`${response.status} - ${response.statusText}`);
+ }
+
+ if (!response.headers.get('content-type')?.includes('application/json')) {
+ return new Error('Content-type is not supported');
+ }
+
+ return response.json();
+ });
+}
+
+export const client = {
+ get: (url: string) => request(url),
+};