diff --git a/README.md b/README.md index 9b90842c4..617ecd230 100644 --- a/README.md +++ b/README.md @@ -44,3 +44,15 @@ https://skypro-web-developer.github.io/react-memo/ Запускает eslint проверку кода, эта же команда запускается перед каждым коммитом. Если не получается закоммитить, попробуйте запустить эту команду и исправить все ошибки и предупреждения. + +### Время разработки упрощенного режима игры + +Планируемое время 12 часов — фактический 4 часа + +### Время разработки лидерборда + +Планируемое время 16 часа — фактический 10 часов + +### Время разработки "Прозрения" и ачивок + +Планируемое время 10 часа — фактический 8 часов \ No newline at end of file diff --git a/docs/mvp-spec.md b/docs/mvp-spec.md index fab47685e..e083d7788 100644 --- a/docs/mvp-spec.md +++ b/docs/mvp-spec.md @@ -14,9 +14,10 @@ Количество карточек для каждого уровня сложности можете назначать и свои или выбрать готовый пресет. Предлагаем следующее пресеты: - - Легкий уровень - 6 карточек (3 пары) - - Средний уровень - 12 карточек (6 пар) - - Сложный уровень - 18 карточек (9 пар) + +- Легкий уровень - 6 карточек (3 пары) +- Средний уровень - 12 карточек (6 пар) +- Сложный уровень - 18 карточек (9 пар) Как только уровень сложности выбран, игроку показывается на игровой поле. diff --git a/package-lock.json b/package-lock.json index edaf5083f..053a34ac1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,7 +17,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.15.0", - "react-scripts": "5.0.1" + "react-scripts": "5.0.1", + "styled-components": "^6.1.12" }, "devDependencies": { "@babel/plugin-proposal-private-property-in-object": "^7.21.11", @@ -2293,6 +2294,24 @@ "postcss-selector-parser": "^6.0.10" } }, + "node_modules/@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "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/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -4493,6 +4512,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "node_modules/@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + }, "node_modules/@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -5949,6 +5973,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", @@ -6389,6 +6421,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.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -6570,6 +6610,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", @@ -6763,9 +6813,9 @@ "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" }, "node_modules/csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -12962,9 +13012,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "funding": [ { "type": "github", @@ -13642,9 +13692,9 @@ } }, "node_modules/postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "funding": [ { "type": "opencollective", @@ -13660,9 +13710,9 @@ } ], "dependencies": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" }, "engines": { "node": "^10 || ^12 || >=14" @@ -16114,6 +16164,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", @@ -16196,9 +16251,9 @@ } }, "node_modules/source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "engines": { "node": ">=0.10.0" } @@ -16527,6 +16582,33 @@ "webpack": "^5.0.0" } }, + "node_modules/styled-components": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz", + "integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==", + "dependencies": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + }, + "engines": { + "node": ">= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/styled-components" + }, + "peerDependencies": { + "react": ">= 16.8.0", + "react-dom": ">= 16.8.0" + } + }, "node_modules/stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -16542,6 +16624,11 @@ "postcss": "^8.2.15" } }, + "node_modules/stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + }, "node_modules/sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", @@ -19841,6 +19928,24 @@ "integrity": "sha512-+OJ9konv95ClSTOJCmMZqpd5+YGsB2S+x6w3E1oaM8UuR5j8nTNHYSz8c9BEPGDOCMQYIEEGlVPj/VY64iTbGw==", "requires": {} }, + "@emotion/is-prop-valid": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz", + "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==", + "requires": { + "@emotion/memoize": "^0.8.1" + } + }, + "@emotion/memoize": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz", + "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==" + }, + "@emotion/unitless": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz", + "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==" + }, "@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -21484,6 +21589,11 @@ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.1.tgz", "integrity": "sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==" }, + "@types/stylis": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz", + "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==" + }, "@types/testing-library__jest-dom": { "version": "5.14.9", "resolved": "https://registry.npmjs.org/@types/testing-library__jest-dom/-/testing-library__jest-dom-5.14.9.tgz", @@ -22547,6 +22657,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", @@ -22872,6 +22987,11 @@ "postcss-selector-parser": "^6.0.9" } }, + "css-color-keywords": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", + "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + }, "css-declaration-sorter": { "version": "6.4.1", "resolved": "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.4.1.tgz", @@ -22979,6 +23099,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", @@ -23117,9 +23247,9 @@ } }, "csstype": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", - "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", + "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, "damerau-levenshtein": { "version": "1.0.8", @@ -27568,9 +27698,9 @@ } }, "nanoid": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", - "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==" }, "natural-compare": { "version": "1.4.0", @@ -28048,13 +28178,13 @@ } }, "postcss": { - "version": "8.4.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz", - "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==", + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", "requires": { - "nanoid": "^3.3.6", + "nanoid": "^3.3.7", "picocolors": "^1.0.0", - "source-map-js": "^1.0.2" + "source-map-js": "^1.2.0" } }, "postcss-attribute-case-insensitive": { @@ -29647,6 +29777,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", @@ -29711,9 +29846,9 @@ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==" }, "source-map-js": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", - "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==" }, "source-map-loader": { "version": "3.0.2", @@ -29958,6 +30093,22 @@ "integrity": "sha512-53BiGLXAcll9maCYtZi2RCQZKa8NQQai5C4horqKyRmHj9H7QmcUyucrH+4KW/gBQbXM2AsB0axoEcFZPlfPcw==", "requires": {} }, + "styled-components": { + "version": "6.1.12", + "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.12.tgz", + "integrity": "sha512-n/O4PzRPhbYI0k1vKKayfti3C/IGcPf+DqcrOB7O/ab9x4u/zjqraneT5N45+sIe87cxrCApXM8Bna7NYxwoTA==", + "requires": { + "@emotion/is-prop-valid": "1.2.2", + "@emotion/unitless": "0.8.1", + "@types/stylis": "4.2.5", + "css-to-react-native": "3.2.0", + "csstype": "3.1.3", + "postcss": "8.4.38", + "shallowequal": "1.1.0", + "stylis": "4.3.2", + "tslib": "2.6.2" + } + }, "stylehacks": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/stylehacks/-/stylehacks-5.1.1.tgz", @@ -29967,6 +30118,11 @@ "postcss-selector-parser": "^6.0.4" } }, + "stylis": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz", + "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==" + }, "sucrase": { "version": "3.34.0", "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.34.0.tgz", diff --git a/package.json b/package.json index e9b7a089e..16a48a015 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-router-dom": "^6.15.0", - "react-scripts": "5.0.1" + "react-scripts": "5.0.1", + "styled-components": "^6.1.12" }, "scripts": { "predeploy": "npm run build", diff --git a/src/components/Cards/Cards.jsx b/src/components/Cards/Cards.jsx index 7526a56c8..110d5cb14 100644 --- a/src/components/Cards/Cards.jsx +++ b/src/components/Cards/Cards.jsx @@ -5,10 +5,16 @@ import styles from "./Cards.module.css"; import { EndGameModal } from "../../components/EndGameModal/EndGameModal"; import { Button } from "../../components/Button/Button"; import { Card } from "../../components/Card/Card"; +import { useSimpleModeContext } from "../../context/hooks/useSimpleMode"; +import { getLeaders } from "../../utils/api"; +import { EndGameLeaderBoardModal } from "../EndGameLeaderBoardModal/EndGameLeaderBoardModal"; +import * as S from "../Cards/Cards.styled"; +import { useEpiphanyContext } from "../../context/hooks/useEpiphany"; // Игра закончилась const STATUS_LOST = "STATUS_LOST"; const STATUS_WON = "STATUS_WON"; +const STATUS_LEADERBOARD_WON = "STATUS_LEADERBOARD_WON"; // Идет игра: карты закрыты, игрок может их открыть const STATUS_IN_PROGRESS = "STATUS_IN_PROGRESS"; // Начало игры: игрок видит все карты в течении нескольких секунд @@ -41,15 +47,40 @@ function getTimerValue(startDate, endDate) { * previewSeconds - сколько секунд пользователь будет видеть все карты открытыми до начала игры */ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { + useEffect(() => { + getLeaders() + .then(data => { + const leaderList = data.leaders.sort((a, b) => a.time - b.time).slice(0, 10); + const leaderWithMaxTime = leaderList.reduce((acc, curr) => { + return acc.time > curr.time ? acc : curr; + }, {}); + setLastTime(leaderWithMaxTime.time); + return; + }) + .catch(error => { + console.log(error.message); + }); + }, []); // В cards лежит игровое поле - массив карт и их состояние открыта\закрыта const [cards, setCards] = useState([]); // Текущий статус игры const [status, setStatus] = useState(STATUS_PREVIEW); + // Получаем контекст упрощенного режима: включен он или нет + const { simpleMode } = useSimpleModeContext(); + // Количество оставщихся попыток в упрощенном режиме + const [countGame, setCountGame] = useState(3); + // Получаем наихудший результат в лидерборде + const [lastTime, setLastTime] = useState(null); + // Дата начала игры const [gameStartDate, setGameStartDate] = useState(null); // Дата конца игры const [gameEndDate, setGameEndDate] = useState(null); + // Стейт для паузы в игре + const [isPause, setIsPause] = useState(false); + // Получаем контекст прозрения: был включен он или нет + const { isEpiphany, setIsEpiphany } = useEpiphanyContext(); // Стейт для таймера, высчитывается в setInteval на основе gameStartDate и gameEndDate const [timer, setTimer] = useState({ @@ -67,12 +98,14 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { setGameStartDate(startDate); setTimer(getTimerValue(startDate, null)); setStatus(STATUS_IN_PROGRESS); + setIsEpiphany(false); } function resetGame() { setGameStartDate(null); setGameEndDate(null); setTimer(getTimerValue(null, null)); setStatus(STATUS_PREVIEW); + setIsEpiphany(false); } /** @@ -82,7 +115,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { * - "Игрок проиграл", если на поле есть две открытые карты без пары * - "Игра продолжается", если не случилось первых двух условий */ - const openCard = clickedCard => { + const openCard = async clickedCard => { // Если карта уже открыта, то ничего не делаем if (clickedCard.open) { return; @@ -105,6 +138,14 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { // Победа - все карты на поле открыты if (isPlayerWon) { + if (pairsCount === 9) { + const timeGame = timer.minutes * 60 + timer.seconds; + if (timeGame < lastTime) { + finishGame(STATUS_LEADERBOARD_WON); + return; + } + } + setCountGame(3); finishGame(STATUS_WON); return; } @@ -127,6 +168,18 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { // "Игрок проиграл", т.к на поле есть две открытые карты без пары if (playerLost) { + if (simpleMode) { + if (countGame > 1) { + setTimeout(() => { + openCardsWithoutPair.map(card => { + card.open = false; + }); + }, 500); + setCountGame(countGame - 1); + return; + } + } + setCountGame(3); finishGame(STATUS_LOST); return; } @@ -135,6 +188,7 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { }; const isGameEnded = status === STATUS_LOST || status === STATUS_WON; + const isGameEndedLeaderBoard = status === STATUS_LEADERBOARD_WON; // Игровой цикл useEffect(() => { @@ -164,14 +218,38 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { // Обновляем значение таймера в интервале useEffect(() => { - const intervalId = setInterval(() => { - setTimer(getTimerValue(gameStartDate, gameEndDate)); - }, 300); - return () => { - clearInterval(intervalId); - }; - }, [gameStartDate, gameEndDate]); + if (!isPause) { + const intervalId = setInterval(() => { + setTimer(getTimerValue(gameStartDate, gameEndDate)); + }, 200); + return () => { + clearInterval(intervalId); + }; + } + }, [gameStartDate, gameEndDate, isPause]); + + // Функция срабатывания паузы на 5 секунд + const enablePause = () => { + setIsPause(true); + const openedCards = cards; + setCards( + cards.map(card => { + return { + ...card, + open: true, + }; + }), + ); + setTimeout(() => { + const newStartGame = new Date(gameStartDate.getTime() + 5000); + setGameStartDate(newStartGame); + setIsPause(false); + setCards(openedCards); + setIsEpiphany(true); + }, 5000); + }; + const timeGame = timer.minutes * 60 + timer.seconds; return (
@@ -195,7 +273,66 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { )}
- {status === STATUS_IN_PROGRESS ? : null} + {status === STATUS_IN_PROGRESS ? ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) : null}
@@ -210,6 +347,8 @@ export function Cards({ pairsCount = 3, previewSeconds = 5 }) { ))}
+ {!simpleMode ? null :

Осталось попыток: {countGame}

} + {isGameEnded ? (
) : null} + + {isGameEndedLeaderBoard ? ( +
+ +
+ ) : null} ); } diff --git a/src/components/Cards/Cards.module.css b/src/components/Cards/Cards.module.css index 000c5006c..41e241d43 100644 --- a/src/components/Cards/Cards.module.css +++ b/src/components/Cards/Cards.module.css @@ -67,6 +67,16 @@ font-style: normal; font-weight: 400; line-height: 32px; - margin-bottom: -12px; } + +p { + color: #fff; + font-family: StratosSkyeng; + font-style: normal; + font-weight: 400; + line-height: 50px; + text-align: center; + font-size: 24px; + margin-top: 24px; +} diff --git a/src/components/Cards/Cards.styled.js b/src/components/Cards/Cards.styled.js new file mode 100644 index 000000000..06fd4c3c0 --- /dev/null +++ b/src/components/Cards/Cards.styled.js @@ -0,0 +1,7 @@ +import styled from "styled-components"; + +export const OpenAllCardsImages = styled.span` + cursor: pointer; + transition: opacity 0.3s; + opacity: ${props => (props.$isEpiphany ? "0.4" : "1")}; +`; diff --git a/src/components/EndGameLeaderBoardModal/EndGameLeaderBoardModal.jsx b/src/components/EndGameLeaderBoardModal/EndGameLeaderBoardModal.jsx new file mode 100644 index 000000000..f9dcf4f46 --- /dev/null +++ b/src/components/EndGameLeaderBoardModal/EndGameLeaderBoardModal.jsx @@ -0,0 +1,58 @@ +import styles from "./EndGameLeaderBoardModal.module.css"; +import { Button } from "../Button/Button"; +import celebrationImageUrl from "../EndGameModal/images/celebration.png"; +import { addLeaders } from "../../utils/api"; +import { useState } from "react"; +import { useLeaderBoardContext } from "../../context/hooks/useLeaderBoard"; +import { useNavigate } from "react-router-dom"; +import { useSimpleModeContext } from "../../context/hooks/useSimpleMode"; + +export function EndGameLeaderBoardModal({ gameDurationMinutes, gameDurationSeconds, timeGame, resetGame }) { + const navigate = useNavigate(); + const [nameInput, setNameInput] = useState(""); + const [errorName, setErrorName] = useState(false); + // Контекст лидерборда + const { setLeaderList } = useLeaderBoardContext(); + // Контекст упрощенного режима + const { simpleMode } = useSimpleModeContext(); + const addLeaderBoard = reset => { + let achievements = []; + if (!simpleMode) { + achievements.push(1); + } + addLeaders({ name: nameInput, time: timeGame, achievements: achievements }) + .then(data => { + setLeaderList(data.leaders.sort((a, b) => a.time - b.time).slice(0, 10)); + reset ? resetGame() : navigate("/leaderboard"); + }) + .catch(error => { + setErrorName(true); + }); + }; + return ( +
+ {"celebration +

Вы попали на Лидерборд!

+ { + setNameInput(e.target.value); + }} + /> +

Затраченное время:

+ {/*
+ {gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")} +
*/} +
+ {gameDurationMinutes.toString().padStart(2, "0")}.{gameDurationSeconds.toString().padStart(2, "0")} +
+ +
addLeaderBoard(false)}> + Перейти к лидерборду +
+ {errorName ? Заполни имя, чтобы продолжить : null} +
+ ); +} diff --git a/src/components/EndGameLeaderBoardModal/EndGameLeaderBoardModal.module.css b/src/components/EndGameLeaderBoardModal/EndGameLeaderBoardModal.module.css new file mode 100644 index 000000000..93c48d3cf --- /dev/null +++ b/src/components/EndGameLeaderBoardModal/EndGameLeaderBoardModal.module.css @@ -0,0 +1,85 @@ +.modal { + width: 480px; + height: 634px; + border-radius: 12px; + background: #c2f5ff; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + text-align: center; +} + +.image { + width: 96px; + height: 96px; + margin-bottom: 8px; +} + +.title { + color: #004980; + font-variant-numeric: lining-nums proportional-nums; + font-family: StratosSkyeng; + font-size: 40px; + font-style: normal; + font-weight: 400; + line-height: 48px; + + margin-bottom: 28px; +} + +.description { + color: #000; + font-variant-numeric: lining-nums proportional-nums; + font-family: StratosSkyeng; + font-size: 24px; + font-style: normal; + font-weight: 400; + line-height: 32px; + + margin-bottom: 10px; +} + +.time { + color: #000; + font-family: StratosSkyeng; + font-size: 64px; + font-style: normal; + font-weight: 400; + line-height: 72px; + + margin-bottom: 40px; +} + +.goLeaderBoard { + color: #004980; + font-family: StratosSkyeng; + font-style: normal; + font-weight: 400; + line-height: 50px; + display: flex; + justify-content: center; + cursor: pointer; +} + +.inputUser { + width: 276px; + height: 45px; + border-radius: 10px; + border: none; + outline: none; + text-align: center; + font-family: StratosSkyeng; + font-size: 24px; + margin-bottom: 28px; +} + +.inputUser::placeholder { + color: #999999; + text-align: center; +} + +.errorName { + font-family: StratosSkyeng; + color: red; +} diff --git a/src/components/EndGameModal/EndGameModal.jsx b/src/components/EndGameModal/EndGameModal.jsx index 722394833..905e3de44 100644 --- a/src/components/EndGameModal/EndGameModal.jsx +++ b/src/components/EndGameModal/EndGameModal.jsx @@ -20,7 +20,6 @@ export function EndGameModal({ isWon, gameDurationSeconds, gameDurationMinutes,
{gameDurationMinutes.toString().padStart("2", "0")}.{gameDurationSeconds.toString().padStart("2", "0")}
- ); diff --git a/src/context/EpiphanyContext.jsx b/src/context/EpiphanyContext.jsx new file mode 100644 index 000000000..d7bc528fd --- /dev/null +++ b/src/context/EpiphanyContext.jsx @@ -0,0 +1,10 @@ +import { createContext, useState } from "react"; + +export const EpiphanyContext = createContext(null); + +export const EpiphanyProvider = ({ children }) => { + // Стейт для определения пользовался ли пользователь прозрением + const [isEpiphany, setIsEpiphany] = useState(false); + + return {children}; +}; diff --git a/src/context/LeaderBoardContext.jsx b/src/context/LeaderBoardContext.jsx new file mode 100644 index 000000000..e06805da0 --- /dev/null +++ b/src/context/LeaderBoardContext.jsx @@ -0,0 +1,9 @@ +import { createContext, useState } from "react"; + +export const LeaderBoardContext = createContext(null); + +export function LeaderBoardProvider({ children }) { + const [leaderList, setLeaderList] = useState([]); + + return {children}; +} diff --git a/src/context/SimpleModeContext.jsx b/src/context/SimpleModeContext.jsx new file mode 100644 index 000000000..0d2ee33f8 --- /dev/null +++ b/src/context/SimpleModeContext.jsx @@ -0,0 +1,13 @@ +import { createContext, useState } from "react"; + +export const SimpleModeContext = createContext(null); + +export function SimpleModeProvider({ children }) { + const [simpleMode, setSimpleMode] = useState(false); + + const toggleSimpleMode = () => { + setSimpleMode(prevState => !prevState); + }; + + return {children}; +} diff --git a/src/context/hooks/useEpiphany.jsx b/src/context/hooks/useEpiphany.jsx new file mode 100644 index 000000000..8ee291d24 --- /dev/null +++ b/src/context/hooks/useEpiphany.jsx @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { EpiphanyContext } from "../EpiphanyContext"; + +export const useEpiphanyContext = () => { + return useContext(EpiphanyContext); +}; diff --git a/src/context/hooks/useLeaderBoard.jsx b/src/context/hooks/useLeaderBoard.jsx new file mode 100644 index 000000000..cfae16045 --- /dev/null +++ b/src/context/hooks/useLeaderBoard.jsx @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { LeaderBoardContext } from "../LeaderBoardContext"; + +export const useLeaderBoardContext = () => { + return useContext(LeaderBoardContext); +}; diff --git a/src/context/hooks/useSimpleMode.jsx b/src/context/hooks/useSimpleMode.jsx new file mode 100644 index 000000000..d5976e6f7 --- /dev/null +++ b/src/context/hooks/useSimpleMode.jsx @@ -0,0 +1,6 @@ +import { useContext } from "react"; +import { SimpleModeContext } from "../SimpleModeContext"; + +export const useSimpleModeContext = () => { + return useContext(SimpleModeContext); +}; diff --git a/src/index.js b/src/index.js index f689c5f0b..8081b8385 100644 --- a/src/index.js +++ b/src/index.js @@ -3,10 +3,19 @@ import ReactDOM from "react-dom/client"; import "./index.css"; import { RouterProvider } from "react-router-dom"; import { router } from "./router"; +import { SimpleModeProvider } from "./context/SimpleModeContext"; +import { LeaderBoardProvider } from "./context/LeaderBoardContext"; +import { EpiphanyProvider } from "./context/EpiphanyContext"; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( - + + + + + + + , ); diff --git a/src/pages/LeaderBoardPage/LeaderBoardPage.jsx b/src/pages/LeaderBoardPage/LeaderBoardPage.jsx new file mode 100644 index 000000000..379e27c2c --- /dev/null +++ b/src/pages/LeaderBoardPage/LeaderBoardPage.jsx @@ -0,0 +1,213 @@ +import { Link } from "react-router-dom"; +import { Button } from "../../components/Button/Button"; +import styles from "./LeaderBoardPage.module.css"; +import { useEffect } from "react"; +import { getLeaders } from "../../utils/api"; +import { useLeaderBoardContext } from "../../context/hooks/useLeaderBoard"; + +export const LeaderBoardPage = () => { + const { leaderList, setLeaderList } = useLeaderBoardContext(); + + useEffect(() => { + getLeaders() + .then(data => { + setLeaderList(data.leaders.sort((a, b) => a.time - b.time).slice(0, 10)); + }) + .catch(error => { + console.log(error.message); + }); + }, [setLeaderList]); + + const formatTime = timeInSeconds => { + const minutes = Math.floor(timeInSeconds / 60); + const seconds = Math.floor(timeInSeconds % 60); + return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`; + }; + + return ( +
+
+
Лидерборд
+ + + +
+
+
Позиция
+
Пользователь
+
Достижения
+
Время
+
+ {leaderList.map((leader, index) => ( +
+
# {index + 1}
+
{leader.name}
+
+ {leader.achievements?.includes(1) ? ( + + + + + + + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + + )} + + {leader.achievements?.includes(2) ? ( + + + + + + + + + + + + + + + ) : ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} +
+
{formatTime(leader.time)}
+
+ ))} +
+ ); +}; diff --git a/src/pages/LeaderBoardPage/LeaderBoardPage.module.css b/src/pages/LeaderBoardPage/LeaderBoardPage.module.css new file mode 100644 index 000000000..c72eedd7c --- /dev/null +++ b/src/pages/LeaderBoardPage/LeaderBoardPage.module.css @@ -0,0 +1,98 @@ +.container { + width: 1000px; + min-height: 100%; + justify-content: center; + margin: 0 auto; + padding-bottom: 20px; +} + +.header { + width: 100%; + height: 100px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + padding-top: 52px; +} + +.titlePage { + font-family: StratosSkyeng; + font-size: 24px; + color: white; +} + +.rowTable { + width: 100%; + height: 64px; + background-color: white; + border-radius: 12px; + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; + align-items: center; + padding: 0 20px; + box-sizing: border-box; + margin-bottom: 15px; +} + +.rowTable__positionTitle { + font-family: StratosSkyeng; + font-size: 24px; + color: #999999; + width: 24%; +} + +.rowTable__userTitle { + font-family: StratosSkyeng; + font-size: 24px; + color: #999999; + width: 35%; +} + +.rowTable__achiveTitle { + font-family: StratosSkyeng; + font-size: 24px; + color: #999999; + width: 28%; +} + +.rowTable__timeTitle { + font-family: StratosSkyeng; + font-size: 24px; + color: #999999; + width: 13%; +} + +.rowTable__position { + font-family: StratosSkyeng; + font-size: 24px; + color: black; + width: 24%; +} + +.rowTable__user { + font-family: StratosSkyeng; + font-size: 24px; + color: black; + width: 35%; +} + +.rowTable__achive { + font-family: StratosSkyeng; + font-size: 24px; + color: black; + width: 28%; +} + +.rowTable__time { + font-family: StratosSkyeng; + font-size: 24px; + color: black; + width: 13%; +} + +svg { + margin-right: 12px; +} diff --git a/src/pages/SelectLevelPage/SelectLevelPage.jsx b/src/pages/SelectLevelPage/SelectLevelPage.jsx index 758942e51..9df7900f9 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.jsx +++ b/src/pages/SelectLevelPage/SelectLevelPage.jsx @@ -1,28 +1,57 @@ -import { Link } from "react-router-dom"; +import { Link, useNavigate } from "react-router-dom"; import styles from "./SelectLevelPage.module.css"; +import { useSimpleModeContext } from "../../context/hooks/useSimpleMode"; +import { useState } from "react"; export function SelectLevelPage() { + const { simpleMode, toggleSimpleMode } = useSimpleModeContext(); + // Храним выбранную сложность + const [selectedLevel, setSelectedLevel] = useState(null); + // Хук для навигации + const navigate = useNavigate(); + + const handlePlay = () => { + if (selectedLevel !== null) { + navigate(`/game/${selectedLevel}`); + } else { + alert("Выбери уровень сложности"); + } + }; + return (

Выбери сложность

+
+ + +
+ + +
Перейти к лидерборду
+
); diff --git a/src/pages/SelectLevelPage/SelectLevelPage.module.css b/src/pages/SelectLevelPage/SelectLevelPage.module.css index 390ac0def..37b2f69c7 100644 --- a/src/pages/SelectLevelPage/SelectLevelPage.module.css +++ b/src/pages/SelectLevelPage/SelectLevelPage.module.css @@ -43,12 +43,8 @@ flex-direction: column; justify-content: center; flex-shrink: 0; - border-radius: 12px; background: #fff; -} - -.levelLink { color: #0080c1; text-align: center; font-family: StratosSkyeng; @@ -59,6 +55,92 @@ text-decoration: none; } -.levelLink:visited { +.level:visited { + color: #0080c1; +} + +.selected { + background-color: #004980; + color: #ffffff; +} + +/* Скрываем стандартный чекбокс */ +input[type="checkbox"] { + position: absolute; + opacity: 0; + cursor: pointer; +} + +/* Стиль для кастомного чекбокса */ +input[type="checkbox"] + label { + position: relative; + padding-left: 30px; + font-size: 20px; + line-height: 50px; + cursor: pointer; +} + +/* Кастомный квадрат */ +input[type="checkbox"] + label:before { + content: ""; + position: absolute; + left: 0; + top: 0; + width: 24px; + height: 24px; + background-color: #fff; + border-radius: 4px; +} + +/* Добавляем галочку при отмеченном чекбоксе */ +input[type="checkbox"]:checked + label:after { + content: ""; + position: absolute; + left: 7px; + top: 3px; + width: 8px; + height: 14px; + border: solid #4caf50; + border-width: 0 2px 2px 0; + transform: rotate(45deg); +} + +label { + color: #000; + font-family: StratosSkyeng; + font-style: normal; + font-weight: 100; + line-height: 50px; + font-size: 20px; +} + +.goLeaderBoard { color: #0080c1; + font-family: StratosSkyeng; + font-style: normal; + font-weight: 400; + line-height: 50px; + display: flex; + justify-content: center; +} + +.goLeaderBoard:hover { + color: #004d40; +} + +.playButton { + width: 276px; + background-color: #7ac100; + color: white; + border: none; + border-radius: 12px; + padding: 10px 20px; + font-size: 18px; + cursor: pointer; + transition: background-color 0.3s ease; + margin-top: 20px; +} + +.playButton:hover { + background-color: #7cb342; } diff --git a/src/router.js b/src/router.js index da6e94b51..c80542c76 100644 --- a/src/router.js +++ b/src/router.js @@ -1,6 +1,7 @@ import { createBrowserRouter } from "react-router-dom"; import { GamePage } from "./pages/GamePage/GamePage"; import { SelectLevelPage } from "./pages/SelectLevelPage/SelectLevelPage"; +import { LeaderBoardPage } from "./pages/LeaderBoardPage/LeaderBoardPage"; export const router = createBrowserRouter( [ @@ -12,6 +13,10 @@ export const router = createBrowserRouter( path: "/game/:pairsCount", element: , }, + { + path: "/leaderboard", + element: , + }, ], /** * basename нужен для корректной работы в gh pages diff --git a/src/utils/api.js b/src/utils/api.js new file mode 100644 index 000000000..c29bebb7d --- /dev/null +++ b/src/utils/api.js @@ -0,0 +1,20 @@ +export const getLeaders = async () => { + const response = await fetch("https://wedev-api.sky.pro/api/v2/leaderboard", { method: "GET" }); + if (!response.ok) { + throw new Error("Не получилось загрузить список, попробуй позже"); + } + const data = await response.json(); + return data; +}; + +export const addLeaders = async ({ name, time, achievements }) => { + const response = await fetch("https://wedev-api.sky.pro/api/v2/leaderboard", { + method: "POST", + body: JSON.stringify({ name, time, achievements }), + }); + if (!response.ok) { + throw new Error("Не получилось добавить лидера"); + } + const data = await response.json(); + return data; +};