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 ( +
+ + +
+ +
+ ); +}; 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-button + 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 => ( + Banner + ))} + +
+ +
+

+ Hot prices +

+ {statusLadingPhones === AsyncStatus.LOADING ? ( + + ) : ( + + )} +
+ +
+

Shop by category

+
+
+ + Phone Category + +

+ Mobile phones +

+

{`${phones.length} models`}

+
+
+ + Phone Category + +

+ Tablets +

+

{`${tabletsAmount} models`}

+
+
+ + Phone Category + +

+ 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 button + 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 === '') ? ( + + Home icon + + ) : ( + <> + + Arrow right + {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 ( + <> +
+ + Phone +
+

+ {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) => ( + Phoduct onGalleryImg(index)} + /> + ))} +
+
+ Phoduct +
+
+
+
+

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 ( +
+
+ + Logo + +
+ + Github + + + Contacts + + rights +
+ +
+
+ ); +}; 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 }, + )} + > + Favorites + {productsFavoriteCount > 0 && ( +
+ {productsFavoriteCount} +
+ )} +
+ classNames( + 'favorites-card-buttons__link', + { 'favorites-card-buttons__link--active': isActive }, + )} + > + Shopping bag + {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 ( +
+ + + Phone +

+ {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), +};