diff --git a/package-lock.json b/package-lock.json index 0f4f8ea..83fa7d8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,11 @@ "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.2", + "notiflix": "^3.2.6", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-loader-spinner": "^5.4.5", "react-scripts": "5.0.1", "web-vitals": "^2.1.3" } @@ -217,11 +220,11 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -379,11 +382,11 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "dependencies": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.22.15" }, "engines": { "node": ">=6.9.0" @@ -419,9 +422,9 @@ } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", - "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", "engines": { "node": ">=6.9.0" } @@ -1028,11 +1031,11 @@ } }, "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.22.5" }, "engines": { "node": ">=6.9.0" @@ -2107,6 +2110,29 @@ "postcss": "^8.3" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "dependencies": { + "@emotion/memoize": "^0.8.1" + } + }, + "node_modules/@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "node_modules/@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "node_modules/@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "node_modules/@eslint/eslintrc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", @@ -4144,6 +4170,29 @@ "node": ">=12" } }, + "node_modules/axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -4308,6 +4357,21 @@ "@babel/core": "^7.0.0-0" } }, + "node_modules/babel-plugin-styled-components": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", + "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "lodash": "^4.17.21", + "picomatch": "^2.3.1" + }, + "peerDependencies": { + "styled-components": ">= 2" + } + }, "node_modules/babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -4626,6 +4690,14 @@ "node": ">= 6" } }, + "node_modules/camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -5161,6 +5233,14 @@ "postcss": "^8.4" } }, + "node_modules/css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==", + "engines": { + "node": ">=4" + } + }, "node_modules/css-declaration-sorter": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", @@ -5334,6 +5414,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "node_modules/css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "dependencies": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "node_modules/css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -7075,9 +7165,9 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" }, "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==", "funding": [ { "type": "individual", @@ -7562,6 +7652,19 @@ "he": "bin/he" } }, + "node_modules/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==", + "dependencies": { + "react-is": "^16.7.0" + } + }, + "node_modules/hoist-non-react-statics/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -9672,6 +9775,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/notiflix": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/notiflix/-/notiflix-3.2.6.tgz", + "integrity": "sha512-mUoaJ/s9E4r8o1O8XRqcuXEylpgOkfXtI5SEkBBlBK+LK7nq1c6qQaQcF7QzS/S018Ww7uUqSZotOs5cHENvVw==" + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -11425,6 +11533,11 @@ "node": ">= 0.10" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -11641,6 +11754,25 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-loader-spinner": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-5.4.5.tgz", + "integrity": "sha512-32f+sb/v2tnNfyvnCCOS4fpyVHsGXjSyNo6QLniHcaj1XjKLxx14L2z0h6szRugOL8IEJ+53GPwNAdbkDqmy4g==", + "dependencies": { + "react-is": "^18.2.0", + "styled-components": "^5.3.5", + "styled-tools": "^1.7.2" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/react-loader-spinner/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -12348,6 +12480,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -12739,6 +12876,59 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "dependencies": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0", + "react-is": ">= 16.8.0" + } + }, + "node_modules/styled-components/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/styled-components/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/styled-tools": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/styled-tools/-/styled-tools-1.7.2.tgz", + "integrity": "sha512-IjLxzM20RMwAsx8M1QoRlCG/Kmq8lKzCGyospjtSXt/BTIIcvgTonaxQAsKnBrsZNwhpHzO9ADx5te0h76ILVg==" + }, "node_modules/stylehacks": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", @@ -14534,11 +14724,11 @@ } }, "@babel/helper-annotate-as-pure": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz", - "integrity": "sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw==", + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", + "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.22.5" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { @@ -14652,11 +14842,11 @@ } }, "@babel/helper-module-imports": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz", - "integrity": "sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg==", + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", "requires": { - "@babel/types": "^7.16.7" + "@babel/types": "^7.22.15" } }, "@babel/helper-module-transforms": { @@ -14683,9 +14873,9 @@ } }, "@babel/helper-plugin-utils": { - "version": "7.17.12", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz", - "integrity": "sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==" + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==" }, "@babel/helper-remap-async-to-generator": { "version": "7.16.8", @@ -15090,11 +15280,11 @@ } }, "@babel/plugin-syntax-jsx": { - "version": "7.16.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz", - "integrity": "sha512-Esxmk7YjA8QysKeT3VhTXvF6y77f/a91SIs4pWb4H2eWGQkCKFgQaG6hdoEVZtGsrAcb2K5BW66XsOErD4WU3Q==", + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.23.3.tgz", + "integrity": "sha512-EB2MELswq55OHUoRZLGg/zC7QWUKfNLpE57m/S2yr1uEneIgsTgrSzXP3NXEsMkVn76OlaVVnzN+ugObuYGwhg==", "requires": { - "@babel/helper-plugin-utils": "^7.16.7" + "@babel/helper-plugin-utils": "^7.22.5" } }, "@babel/plugin-syntax-logical-assignment-operators": { @@ -15789,6 +15979,29 @@ "postcss-value-parser": "^4.2.0" } }, + "@emotion/is-prop-valid": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.1.tgz", + "integrity": "sha512-61Mf7Ufx4aDxx1xlDeOm8aFFigGHE4z+0sKCa+IHCeZKiyP9RLD0Mmx7m8b9/Cf37f7NAvQOOJAbQQGVr5uERw==", + "requires": { + "@emotion/memoize": "^0.8.1" + } + }, + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "@emotion/stylis": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/@emotion/stylis/-/stylis-0.8.5.tgz", + "integrity": "sha512-h6KtPihKFn3T9fuIrwvXXUOwlx3rfUvfZIcP5a6rh8Y7zjE3O06hT5Ss4S/YI1AYhuZ1kjaE/5EaOOI2NqSylQ==" + }, + "@emotion/unitless": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz", + "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==" + }, "@eslint/eslintrc": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.2.1.tgz", @@ -17297,6 +17510,28 @@ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.4.2.tgz", "integrity": "sha512-LVAaGp/wkkgYJcjmHsoKx4juT1aQvJyPcW09MLCjVTh3V2cc6PnyempiLMNH5iMdfIX/zdbjUx2KDjMLCTdPeA==" }, + "axios": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz", + "integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + }, + "dependencies": { + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + } + } + }, "axobject-query": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.2.0.tgz", @@ -17421,6 +17656,18 @@ "@babel/helper-define-polyfill-provider": "^0.3.1" } }, + "babel-plugin-styled-components": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/babel-plugin-styled-components/-/babel-plugin-styled-components-2.1.4.tgz", + "integrity": "sha512-Xgp9g+A/cG47sUyRwwYxGM4bR/jDRg5N6it/8+HxCnbT5XNKSKDT9xm4oag/osgqjC2It/vH0yXsomOG6k558g==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-module-imports": "^7.22.5", + "@babel/plugin-syntax-jsx": "^7.22.5", + "lodash": "^4.17.21", + "picomatch": "^2.3.1" + } + }, "babel-plugin-transform-react-remove-prop-types": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/babel-plugin-transform-react-remove-prop-types/-/babel-plugin-transform-react-remove-prop-types-0.4.24.tgz", @@ -17671,6 +17918,11 @@ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==" }, + "camelize": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz", + "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==" + }, "caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -18067,6 +18319,11 @@ "postcss-selector-parser": "^6.0.9" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, "css-declaration-sorter": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.2.2.tgz", @@ -18169,6 +18426,16 @@ "resolved": "https://registry.npmjs.org/css-select-base-adapter/-/css-select-base-adapter-0.1.1.tgz", "integrity": "sha512-jQVeeRG70QI08vSTwf1jHxp74JoZsr2XSgETae8/xC8ovSnL2WF87GTLO86Sbwdt2lK4Umg4HnnwMO4YF3Ce7w==" }, + "css-to-react-native": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz", + "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==", + "requires": { + "camelize": "^1.0.0", + "css-color-keywords": "^1.0.0", + "postcss-value-parser": "^4.0.2" + } + }, "css-tree": { "version": "1.0.0-alpha.37", "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-1.0.0-alpha.37.tgz", @@ -19481,9 +19748,9 @@ "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==" }, "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + "version": "1.15.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.3.tgz", + "integrity": "sha512-1VzOtuEM8pC9SFU1E+8KfTjZyMztRsgEfwQl44z8A25uy13jSzTj6dyK2Df52iV0vgHCfBwLhDWevLn95w5v6Q==" }, "fork-ts-checker-webpack-plugin": { "version": "6.5.2", @@ -19805,6 +20072,21 @@ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" }, + "hoist-non-react-statics": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", + "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==", + "requires": { + "react-is": "^16.7.0" + }, + "dependencies": { + "react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + } + } + }, "hoopy": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz", @@ -21344,6 +21626,11 @@ "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" }, + "notiflix": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/notiflix/-/notiflix-3.2.6.tgz", + "integrity": "sha512-mUoaJ/s9E4r8o1O8XRqcuXEylpgOkfXtI5SEkBBlBK+LK7nq1c6qQaQcF7QzS/S018Ww7uUqSZotOs5cHENvVw==" + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -22474,6 +22761,11 @@ } } }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, "psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", @@ -22634,6 +22926,23 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-loader-spinner": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/react-loader-spinner/-/react-loader-spinner-5.4.5.tgz", + "integrity": "sha512-32f+sb/v2tnNfyvnCCOS4fpyVHsGXjSyNo6QLniHcaj1XjKLxx14L2z0h6szRugOL8IEJ+53GPwNAdbkDqmy4g==", + "requires": { + "react-is": "^18.2.0", + "styled-components": "^5.3.5", + "styled-tools": "^1.7.2" + }, + "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.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -23153,6 +23462,11 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shallowequal": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz", + "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==" + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -23447,6 +23761,43 @@ "integrity": "sha512-GPcQ+LDJbrcxHORTRes6Jy2sfvK2kS6hpSfI/fXhPt+spVzxF6LJ1dHLN9zIGmVaaP044YKaIatFaufENRiDoQ==", "requires": {} }, + "styled-components": { + "version": "5.3.11", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-5.3.11.tgz", + "integrity": "sha512-uuzIIfnVkagcVHv9nE0VPlHPSCmXIUGKfJ42LNjxCCTDTL5sgnJ8Z7GZBq0EnLYGln77tPpEpExt2+qa+cZqSw==", + "requires": { + "@babel/helper-module-imports": "^7.0.0", + "@babel/traverse": "^7.4.5", + "@emotion/is-prop-valid": "^1.1.0", + "@emotion/stylis": "^0.8.4", + "@emotion/unitless": "^0.7.4", + "babel-plugin-styled-components": ">= 1.12.0", + "css-to-react-native": "^3.0.0", + "hoist-non-react-statics": "^3.0.0", + "shallowequal": "^1.1.0", + "supports-color": "^5.5.0" + }, + "dependencies": { + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "styled-tools": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/styled-tools/-/styled-tools-1.7.2.tgz", + "integrity": "sha512-IjLxzM20RMwAsx8M1QoRlCG/Kmq8lKzCGyospjtSXt/BTIIcvgTonaxQAsKnBrsZNwhpHzO9ADx5te0h76ILVg==" + }, "stylehacks": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.0.tgz", diff --git a/package.json b/package.json index 0fbb784..811b138 100644 --- a/package.json +++ b/package.json @@ -7,8 +7,11 @@ "@testing-library/jest-dom": "^5.16.3", "@testing-library/react": "^12.1.4", "@testing-library/user-event": "^13.5.0", + "axios": "^1.6.2", + "notiflix": "^3.2.6", "react": "^18.1.0", "react-dom": "^18.1.0", + "react-loader-spinner": "^5.4.5", "react-scripts": "5.0.1", "web-vitals": "^2.1.3" }, diff --git a/src/components/API/Api.jsx b/src/components/API/Api.jsx new file mode 100644 index 0000000..4edd734 --- /dev/null +++ b/src/components/API/Api.jsx @@ -0,0 +1,23 @@ +import axios from 'axios'; + +const BASE_URL = 'https://pixabay.com/api/'; +const API_KEY = '40728648-1b398202c5fa26f14327021d8'; + +const getImages = async (search, page) => { + const response = await axios.get(BASE_URL, { + method: 'get', + params: { + key: API_KEY, + q: search, + image_type: 'photo', + orientation: 'horizontal', + per_page: 12, + page: page, + }, + }); + console.log(response.data); + console.log(response.data.hits); + return response; +}; + +export default getImages; diff --git a/src/components/App.jsx b/src/components/App.jsx index 61bcfb7..5fc3578 100644 --- a/src/components/App.jsx +++ b/src/components/App.jsx @@ -1,16 +1,184 @@ -export const App = () => { - return ( -
- React homework template!!!! -
- ); -}; +import React, { Component } from 'react'; +import Notify from 'notiflix/build/notiflix-notify-aio'; +import getImages from './API/Api'; +import Searchbar from './Searchbar/Searchbar'; +import ImageGallery from './ImageGallery/ImageGallery'; +import Button from './Button/Button'; +import Modal from './Modal/Modal'; +import Loader from './Loader/Loader'; + +export default class App extends Component { + constructor(props) { + super(props); + this.state = { + images: [], + query: '', + page: 1, + loading: false, + openModal: false, + largeImageURL: '', + loadMore: false, + toTop: false, + }; + + // Ref for scrolling to bottom + this.bottomRef = React.createRef(); + } + + // Scroll to bottom with load more + scrollToBottom = () => { + this.bottomRef.current?.scrollIntoView({ behavior: 'smooth' }); + }; + + handleSubmit = query => { + this.setState({ + loading: true, + images: [], + query: query, + page: 1, + }); + }; + + handleOpenModal = url => { + this.setState({ + openModal: true, + largeImageURL: url, + }); + }; + + handleLoadMore = () => { + this.setState(prevState => ({ + page: prevState.page + 1, + loading: true, + loadMore: true, + toTop: true, + })); + }; + + backToTop = () => { + window.scrollTo({ + left: 0, + top: 0, + behavior: 'smooth', + }); + }; + + handleModalClose = () => { + this.setState({ + openModal: false, + largeImageURL: '', + }); + }; + + componentDidMount() { + this.getGallery(); + } + + componentDidUpdate(prevProps, prevState) { + if ( + prevState.query !== this.state.query || + prevState.page !== this.state.page + ) { + this.getGallery(); + } + } + + getGallery = async () => { + const { query, page } = this.state; + + if (!query) return; + + try { + const response = await getImages(query, page); + let imageData = response.data; + let imageCount = imageData.hits.length; + let imageTotal = imageData.totalHits; + let totalPages = Math.round(imageTotal / imageCount); + + if (imageCount === 0) { + this.setState({ + images: [], + loadMore: false, + toTop: false, + }); + Notify.failure( + `Sorry, there are no images matching your search query: ${query}. Please try again.` + ); + return; + } + + const newState = { images: [...this.state.images, ...imageData.hits] }; + + if (imageCount < 12) { + this.setState({ ...newState, toTop: true }); + if (page === 1) { + this.setState({ + loadMore: false, + toTop: false, + }); + Notify.success( + `Maximum search value found, there are ${imageCount} images.` + ); + } + } else { + this.setState({ + ...newState, + loadMore: page !== totalPages, + }); + if (page >= 2 && page <= 41) { + this.setState({ + loadMore: true, + toTop: true, + }); + } else if (page === 42) { + this.setState({ + toTop: true, + loadMore: false, + }); + } else if (imageTotal > 12) { + this.setState({ + loading: true, + loadMore: true, + toTop: true, + }); + } + } + } finally { + this.setState({ + loading: false, + }); + } + }; + + render() { + const { images, loading, loadMore, toTop, openModal, largeImageURL } = + this.state; + + return ( +
+ + + +
+ {''} + {loading && } +
+ {loadMore && ( +
+ {openModal && ( + + )} +
+ ); + } +} diff --git a/src/components/Button/Button.jsx b/src/components/Button/Button.jsx new file mode 100644 index 0000000..bf43ec7 --- /dev/null +++ b/src/components/Button/Button.jsx @@ -0,0 +1,31 @@ +import { string, func } from 'prop-types'; +import './Button.modules.css'; + +const Button = props => { + const { text, type, clickHandler } = props; + + return ( +
+ +
+ ); +}; + +Button.defaultProps = { + type: 'button', + text: 'click me', +}; + +Button.propTypes = { + type: string, + text: string, +}; + +export default Button; diff --git a/src/components/Button/Button.modules.css b/src/components/Button/Button.modules.css new file mode 100644 index 0000000..8897fc9 --- /dev/null +++ b/src/components/Button/Button.modules.css @@ -0,0 +1,31 @@ +.Button { + padding: 8px 16px; + border-radius: 2px; + background-color: #3f51b5; + transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1); + text-align: center; + display: inline-block; + color: #fff; + border: 0; + text-decoration: none; + cursor: pointer; + font-family: inherit; + font-size: 18px; + line-height: 24px; + font-style: normal; + font-weight: 500; + min-width: 180px; + box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), + 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); +} + +.Button:hover, +.Button:focus { + background-color: #303f9f; +} + +.Center-buttons { + display: flex; + flex-direction: column; + align-items: center; +} diff --git a/src/components/ImageGallery/ImageGallery.jsx b/src/components/ImageGallery/ImageGallery.jsx new file mode 100644 index 0000000..e8a43c8 --- /dev/null +++ b/src/components/ImageGallery/ImageGallery.jsx @@ -0,0 +1,27 @@ +import { array, func } from 'prop-types'; +import ImageGalleryItem from 'components/ImageGalleryItem/ImageGalleryItem'; +import './ImageGallery.modules.css'; + +const ImageGallery = props => { + const { images, openModal } = props; + return ( + + ); +}; + +ImageGallery.propTypes = { + images: array.isRequired, + openModal: func.isRequired, +}; + +export default ImageGallery; diff --git a/src/components/ImageGallery/ImageGallery.modules.css b/src/components/ImageGallery/ImageGallery.modules.css new file mode 100644 index 0000000..08f3e30 --- /dev/null +++ b/src/components/ImageGallery/ImageGallery.modules.css @@ -0,0 +1,12 @@ +.ImageGallery { + display: grid; + max-width: calc(100vw - 48px); + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + grid-gap: 16px; + margin-top: 0; + margin-bottom: 0; + padding: 0; + list-style: none; + margin-left: auto; + margin-right: auto; +} diff --git a/src/components/ImageGalleryItem/ImageGalleryItem.jsx b/src/components/ImageGalleryItem/ImageGalleryItem.jsx new file mode 100644 index 0000000..0fae559 --- /dev/null +++ b/src/components/ImageGalleryItem/ImageGalleryItem.jsx @@ -0,0 +1,32 @@ +import { string, func } from 'prop-types'; +import './ImageGalleryItem.modules.css'; + +const ImageGalleryItem = props => { + const { webformatURL, tags, largeImageURL, openModal } = props; + + return ( +
  • + {tags} openModal(largeImageURL)} + /> +
  • + ); +}; + +ImageGalleryItem.defaultProps = { + largeImageURL: 'https://picsum.photos/100%/260', + webformatURL: 'https://picsum.photos/100%/260', +}; + +ImageGalleryItem.propTypes = { + largeImageURL: string, + webformatURL: string, + tags: string.isRequired, + openModal: func.isRequired, +}; + +export default ImageGalleryItem; diff --git a/src/components/ImageGalleryItem/ImageGalleryItem.modules.css b/src/components/ImageGalleryItem/ImageGalleryItem.modules.css new file mode 100644 index 0000000..65d5a35 --- /dev/null +++ b/src/components/ImageGalleryItem/ImageGalleryItem.modules.css @@ -0,0 +1,17 @@ +.ImageGalleryItem { + border-radius: 2px; + box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), + 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12); +} + +.ImageGalleryItem-image { + width: 100%; + height: 260px; + object-fit: cover; + transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1); +} + +.ImageGalleryItem-image:hover { + transform: scale(1.03); + cursor: zoom-in; +} diff --git a/src/components/Loader/Loader.jsx b/src/components/Loader/Loader.jsx new file mode 100644 index 0000000..5e7fd44 --- /dev/null +++ b/src/components/Loader/Loader.jsx @@ -0,0 +1,11 @@ +import { InfinitySpin } from 'react-loader-spinner'; + +const Loader = () => { + return ( +
    + +
    + ); +}; + +export default Loader; diff --git a/src/components/Modal/Modal.jsx b/src/components/Modal/Modal.jsx new file mode 100644 index 0000000..2435eba --- /dev/null +++ b/src/components/Modal/Modal.jsx @@ -0,0 +1,49 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import './Modal.modules.css'; + +class Modal extends Component { + handleBackdropClose = e => { + if (e.target === e.currentTarget) { + this.props.modalClose(); + } + }; + + handleKeyDown = e => { + if (e.code === 'Escape') { + this.props.modalClose(); + } + }; + + componentDidMount() { + window.addEventListener('keydown', this.handleKeyDown); + } + + componentWillUnmount() { + window.removeEventListener('keydown', this.handleKeyDown); + } + + render() { + const { largeImageURL, tags } = this.props; + + return ( +
    +
    + {tags} +
    +
    + ); + } +} + +Modal.defaultProps = { + largeImageURL: 'https://picsum.photos/100%/260', + tags: 'This is a default image. I am sorry, the image you searched is not available.', +}; + +Modal.propTypes = { + largeImageURL: PropTypes.string, + tags: PropTypes.string, +}; + +export default Modal; diff --git a/src/components/Modal/Modal.modules.css b/src/components/Modal/Modal.modules.css new file mode 100644 index 0000000..1e0783d --- /dev/null +++ b/src/components/Modal/Modal.modules.css @@ -0,0 +1,17 @@ +.Overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, 0.8); + z-index: 1200; +} + +.Modal { + max-width: calc(100vw - 48px); + max-height: calc(100vh - 24px); +} diff --git a/src/components/Searchbar/Searchbar.jsx b/src/components/Searchbar/Searchbar.jsx new file mode 100644 index 0000000..1b7e64e --- /dev/null +++ b/src/components/Searchbar/Searchbar.jsx @@ -0,0 +1,50 @@ +import { Component } from 'react'; +import './Searchbar.modules.css'; + +export default class Searchbar extends Component { + constructor(props) { + super(props); + + this.state = { + query: '', + }; + } + + handleChange = e => { + const userInput = e.currentTarget.value.toLowerCase().trim(); + this.setState({ query: userInput }); + // console.log(this.state.query); + }; + + handleSubmit = e => { + e.preventDefault(); + const { query } = this.state; + this.props.onSubmit(query); + // console.log(this.state.query); + }; + + render() { + return ( +
    +
    + + + +
    +
    + ); + } +} diff --git a/src/components/Searchbar/Searchbar.modules.css b/src/components/Searchbar/Searchbar.modules.css new file mode 100644 index 0000000..9b1b62c --- /dev/null +++ b/src/components/Searchbar/Searchbar.modules.css @@ -0,0 +1,81 @@ +/* + * Стили компонента Searchbar + */ +.Searchbar { + top: 0; + left: 0; + position: sticky; + z-index: 1100; + display: flex; + justify-content: center; + align-items: center; + min-height: 64px; + padding-right: 24px; + padding-left: 24px; + padding-top: 12px; + padding-bottom: 12px; + color: #fff; + background-color: #3f51b5; + box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); +} + +/* + * Стили компонента SearchForm + */ +.SearchForm { + display: flex; + align-items: center; + width: 100%; + max-width: 600px; + background-color: #fff; + border-radius: 3px; + overflow: hidden; +} + +.SearchForm-button { + display: inline-block; + width: 48px; + height: 48px; + border: 0; + background-image: url('https://image.flaticon.com/icons/svg/149/149852.svg'); + background-size: 40%; + background-repeat: no-repeat; + background-position: center; + opacity: 0.6; + transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + outline: none; +} + +.SearchForm-button:hover { + opacity: 1; +} + +.SearchForm-button-label { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + clip-path: inset(50%); + border: 0; +} + +.SearchForm-input { + display: inline-block; + width: 100%; + font: inherit; + font-size: 20px; + border: none; + outline: none; + padding-left: 4px; + padding-right: 4px; +} + +.SearchForm-input::placeholder { + font: inherit; + font-size: 18px; +} diff --git a/src/index.css b/src/index.css index 1aac5f6..a8b12e9 100644 --- a/src/index.css +++ b/src/index.css @@ -1,15 +1,230 @@ -@import-normalize; /* bring in normalize.css styles */ +@import-normalize; + +html { + box-sizing: border-box; + width: 100vw; + overflow-x: hidden; +} + +*, +*::before, +*::after { + box-sizing: inherit; +} body { margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, + Oxygen-Sans, Ubuntu, Cantarell, 'Helvetica Neue', sans-serif; + color: #212121; + background-color: #fff; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; +img { + display: block; + max-width: 100%; + height: auto; +} + +.App { + display: grid; + grid-template-columns: 1fr; + grid-gap: 16px; + padding-bottom: 24px; +} + +/* + * Стили компонента Searchbar + */ +.Searchbar { + top: 0; + left: 0; + position: sticky; + z-index: 1100; + display: flex; + justify-content: center; + align-items: center; + min-height: 64px; + padding-right: 24px; + padding-left: 24px; + padding-top: 12px; + padding-bottom: 12px; + margin-bottom: 24px; + color: #fff; + background-color: #3f51b5; + box-shadow: 0px 2px 4px -1px rgba(0, 0, 0, 0.2), + 0px 4px 5px 0px rgba(0, 0, 0, 0.14), 0px 1px 10px 0px rgba(0, 0, 0, 0.12); +} + +/* + * Стили компонента SearchForm + */ +.SearchForm { + display: flex; + align-items: center; + width: 100%; + max-width: 600px; + background-color: #fff; + border-radius: 3px; + overflow: hidden; +} + +.SearchForm-button { + display: inline-block; + width: 48px; + height: 48px; + border: 0; + /* background-image: url('../src/images/magnifysvgrepo-min.svg'); */ + background-size: 40%; + background-repeat: no-repeat; + background-position: center; + opacity: 0.6; + transition: opacity 250ms cubic-bezier(0.4, 0, 0.2, 1); + cursor: pointer; + outline: none; +} + +.SearchForm-button:hover { + opacity: 1; +} + +.SearchForm-button-label { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + clip-path: inset(50%); + border: 0; +} + +.SearchForm-input { + display: inline-block; + width: 100%; + font: inherit; + font-size: 20px; + border: none; + outline: none; + padding-left: 4px; + padding-right: 4px; +} + +.SearchForm-input::placeholder { + font: inherit; + font-size: 18px; +} + +/* + * Стили компонента ImageGallery + */ +.ImageGallery { + display: grid; + max-width: calc(100vw - 48px); + grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)); + grid-gap: 16px; + margin-top: 0; + margin-bottom: 0; + padding: 0; + list-style: none; + margin-left: auto; + margin-right: auto; +} + +.ImageGalleryItem { + border-radius: 2px; + box-shadow: 0px 1px 3px 0px rgba(0, 0, 0, 0.2), + 0px 1px 1px 0px rgba(0, 0, 0, 0.14), 0px 2px 1px -1px rgba(0, 0, 0, 0.12); +} + +.ImageGalleryItem-image { + width: 100%; + height: 260px; + object-fit: cover; + transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1); +} + +.ImageGalleryItem-image:hover { + transform: scale(1.03); + cursor: zoom-in; +} + +.Overlay { + position: fixed; + top: 0; + left: 0; + width: 100vw; + height: 100vh; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, 0.8); + z-index: 1200; +} + +.Modal { + max-width: calc(100vw - 48px); + max-height: calc(100vh - 24px); +} + +.Button { + padding: 8px 16px; + margin: 12px 6px 12px 6px; + border-radius: 2px; + background-color: #3f51b5; + transition: all 250ms cubic-bezier(0.4, 0, 0.2, 1); + text-align: center; + display: inline-block; + color: #fff; + border: 0; + text-decoration: none; + cursor: pointer; + font-family: inherit; + font-size: 18px; + line-height: 24px; + font-style: normal; + font-weight: 500; + width: 180px; + box-shadow: 0px 3px 1px -2px rgba(0, 0, 0, 0.2), + 0px 2px 2px 0px rgba(0, 0, 0, 0.14), 0px 1px 5px 0px rgba(0, 0, 0, 0.12); + transition: background-color 0.9s; +} + +.Button:hover, +.Button:focus { + background-color: #303f9f; +} + +.Center-buttons { + display: flex; + flex-direction: column; + align-items: center; +} + +@media screen and (min-width: 625px) { + .Center-buttons { + flex-direction: row; + justify-content: center; + } +} + +.Center { + margin: auto; + width: 50%; + padding: 10px; +} + +@media screen and (min-width: 625px) and (max-width: 1024px) { + .Center { + width: 35%; + } +} + +@media screen and (min-width: 1025px) { + .Center { + width: 12%; + } } diff --git a/src/index.js b/src/index.js index 2bde91e..78f79d7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import { App } from 'components/App'; +import App from 'components/App'; import './index.css'; ReactDOM.createRoot(document.getElementById('root')).render(