From d6970d66080485862559d0469b7496c7c01927ae Mon Sep 17 00:00:00 2001 From: Yurabox Date: Sun, 15 Sep 2024 12:41:54 +0300 Subject: [PATCH 01/53] =?UTF-8?q?=D0=BE=20=D1=81=D0=B5=D0=B1=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 263 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 4 + src/app/App.css | 3 + src/app/App.tsx | 9 +- 4 files changed, 276 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5b1dfa392..77870b96b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -46,6 +46,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.8.0", "fork-ts-checker-webpack-plugin": "^8.0.0", + "gh-pages": "^6.1.1", "html-webpack-plugin": "^5.5.1", "husky": "^8.0.0", "jest": "^29.5.0", @@ -11021,6 +11022,13 @@ "integrity": "sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==", "dev": true }, + "node_modules/email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true, + "license": "MIT" + }, "node_modules/emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -12511,6 +12519,34 @@ "node": ">=10" } }, + "node_modules/filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -13147,6 +13183,101 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gh-pages": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.1.1.tgz", + "integrity": "sha512-upnohfjBwN5hBP9w2dPE7HO5JJTHzSGMV1JrLrHvNuqmjoYHg6TBrCcnEoorjG/e0ejbuvnwyKMdTyM40PEByw==", + "dev": true, + "license": "MIT", + "dependencies": { + "async": "^3.2.4", + "commander": "^11.0.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^11.1.1", + "globby": "^6.1.0" + }, + "bin": { + "gh-pages": "bin/gh-pages.js", + "gh-pages-clean": "bin/gh-pages-clean.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/gh-pages/node_modules/array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-uniq": "^1.0.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages/node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + } + }, + "node_modules/gh-pages/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gh-pages/node_modules/globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gh-pages/node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/giget": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", @@ -21335,6 +21466,19 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", @@ -21964,6 +22108,19 @@ "node": ">=12" } }, + "node_modules/trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "escape-string-regexp": "^1.0.2" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", @@ -31513,6 +31670,12 @@ "integrity": "sha512-8KR114CAYQ4/r5EIEsOmOMqQ9j0MRbJZR3aXD/KFA8RuKzyoUB4XrUCg+l8RUGqTVQgKNIgTpjaG8YHRPAbX2w==", "dev": true }, + "email-addresses": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-5.0.0.tgz", + "integrity": "sha512-4OIPYlA6JXqtVn8zpHpGiI7vE6EQOAg16aGnDMIAlZVinnoZ8208tW1hAbjWydgN/4PLTT9q+O1K6AH/vALJGw==", + "dev": true + }, "emittery": { "version": "0.13.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.13.1.tgz", @@ -32645,6 +32808,23 @@ } } }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha512-lc1bnsSr4L4Bdif8Xb/qrtokGbq5zlsms/CYH8PP+WtCkGNF65DPiQY8vG3SakEdRn8Dlnm+gW/qWKKjS5sZzQ==", + "dev": true + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -33105,6 +33285,71 @@ "get-intrinsic": "^1.1.1" } }, + "gh-pages": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-6.1.1.tgz", + "integrity": "sha512-upnohfjBwN5hBP9w2dPE7HO5JJTHzSGMV1JrLrHvNuqmjoYHg6TBrCcnEoorjG/e0ejbuvnwyKMdTyM40PEByw==", + "dev": true, + "requires": { + "async": "^3.2.4", + "commander": "^11.0.0", + "email-addresses": "^5.0.0", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^11.1.1", + "globby": "^6.1.0" + }, + "dependencies": { + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "dev": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "dev": true + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globby": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "dev": true, + "requires": { + "array-union": "^1.0.1", + "glob": "^7.0.3", + "object-assign": "^4.0.1", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + } + } + }, "giget": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.1.tgz", @@ -39034,6 +39279,15 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "style-loader": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.3.tgz", @@ -39512,6 +39766,15 @@ "punycode": "^2.1.1" } }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha512-pkonvlKk8/ZuR0D5tLW8ljt5I8kmxp2XKymhepUeOdCEfKpZaktSArkLHZt76OB1ZvO9bssUsDty4SWhLvZpLg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "trough": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/trough/-/trough-2.2.0.tgz", diff --git a/package.json b/package.json index 492664d1f..5fedda2bf 100644 --- a/package.json +++ b/package.json @@ -1,12 +1,15 @@ { "name": "react-start-template", "version": "1.0.0", + "homepage": "https://yurabox2017.github.io", "description": "Start repo with required configuration", "main": "src/index.tsx", "author": "Igor ", "scripts": { "start": "webpack serve --mode development", "build": "webpack --mode production", + "predeploy": "npm run build", + "deploy": "gh-pages -d dist", "test": "jest src", "lint": "eslint src --fix", "storybook": "storybook dev -p 6006", @@ -46,6 +49,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-storybook": "^0.8.0", "fork-ts-checker-webpack-plugin": "^8.0.0", + "gh-pages": "^6.1.1", "html-webpack-plugin": "^5.5.1", "husky": "^8.0.0", "jest": "^29.5.0", diff --git a/src/app/App.css b/src/app/App.css index 78b8850cf..4715c1bda 100644 --- a/src/app/App.css +++ b/src/app/App.css @@ -6,6 +6,9 @@ height: 40vmin; pointer-events: none; } +.text-left { + text-align: left !important; +} @media (prefers-reduced-motion: no-preference) { .App-logo { diff --git a/src/app/App.tsx b/src/app/App.tsx index dcc0ff8ad..7bef3af5e 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -7,9 +7,12 @@ function App() {
logo -

- Текст писать тут -

+
    +
  • В компании по импортозамещению необходимо переписать проект с Blazor на React
  • +
  • Владею Blazor, AspNet, net6
  • +
  • FullStack разработчик, разрабатываю информационно-аналитическую систему.
  • +
  • 2022-2023гг проходил обучение по курсу AspNet core Otus.
  • +
); From ebb7be15fdc18cead77963c8d722cf156bb884bc Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 18 Sep 2024 00:24:09 +0300 Subject: [PATCH 02/53] =?UTF-8?q?typescript=20=D0=B4=D0=BE=D0=BC=D0=B0?= =?UTF-8?q?=D1=88=D0=BD=D1=8F=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0?= =?UTF-8?q?=201?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 22 ++++++ package.json | 1 + src/homeworks/ts1/1_base.test.js | 48 ++++++------ src/homeworks/ts1/{1_base.js => 1_base.ts} | 39 ++++++---- src/homeworks/ts1/2_repair.ts | 90 +++++++++++----------- src/homeworks/ts1/3_write.test.js | 66 ++++++++-------- src/homeworks/ts1/3_write.ts | 67 +++++++++++++++- 7 files changed, 217 insertions(+), 116 deletions(-) rename src/homeworks/ts1/{1_base.js => 1_base.ts} (55%) diff --git a/package-lock.json b/package-lock.json index 77870b96b..6ca16be08 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@faker-js/faker": "^9.0.1", "clsx": "^1.2.1", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -2577,6 +2578,22 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@faker-js/faker": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.1.tgz", + "integrity": "sha512-4mDeYIgM3By7X6t5E6eYwLAa+2h4DeZDF7thhzIg6XB76jeEvMwadYAMCFJL/R4AnEBcAUO9+gL0vhy3s+qvZA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, "node_modules/@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", @@ -25363,6 +25380,11 @@ } } }, + "@faker-js/faker": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.1.tgz", + "integrity": "sha512-4mDeYIgM3By7X6t5E6eYwLAa+2h4DeZDF7thhzIg6XB76jeEvMwadYAMCFJL/R4AnEBcAUO9+gL0vhy3s+qvZA==" + }, "@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", diff --git a/package.json b/package.json index 5fedda2bf..dc6d471fb 100644 --- a/package.json +++ b/package.json @@ -68,6 +68,7 @@ "webpack-dev-server": "^4.15.0" }, "dependencies": { + "@faker-js/faker": "^9.0.1", "clsx": "^1.2.1", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/src/homeworks/ts1/1_base.test.js b/src/homeworks/ts1/1_base.test.js index ee68dc16f..ff77416da 100644 --- a/src/homeworks/ts1/1_base.test.js +++ b/src/homeworks/ts1/1_base.test.js @@ -1,26 +1,26 @@ // Этот блок кода удалить и раскомментировать код ниже -it('remove it', () => { - expect(true).toBe(true); -}); - -// import { transformCustomers } from './1_base'; -// -// describe('all', () => { -// it('transformCustomers', () => { -// const customers = [ -// { id: 1, name: 'John', age: 25, isSubscribed: true }, -// { id: 2, name: 'Mary', age: 40, isSubscribed: false }, -// { id: 3, name: 'Bob', age: 32, isSubscribed: true }, -// { id: 4, name: 'Alice', age: 22, isSubscribed: true }, -// { id: 5, name: 'David', age: 48, isSubscribed: false }, -// ]; -// -// expect(transformCustomers(customers)).toEqual({ -// 1: { name: 'John', age: 25, isSubscribed: true }, -// 2: { name: 'Mary', age: 40, isSubscribed: false }, -// 3: { name: 'Bob', age: 32, isSubscribed: true }, -// 4: { name: 'Alice', age: 22, isSubscribed: true }, -// 5: { name: 'David', age: 48, isSubscribed: false }, -// }); -// }); +// it('remove it', () => { +// expect(true).toBe(true); // }); + +import { transformCustomers } from './1_base'; + +describe('all', () => { + it('transformCustomers', () => { + const customers = [ + { id: 1, name: 'John', age: 25, isSubscribed: true }, + { id: 2, name: 'Mary', age: 40, isSubscribed: false }, + { id: 3, name: 'Bob', age: 32, isSubscribed: true }, + { id: 4, name: 'Alice', age: 22, isSubscribed: true }, + { id: 5, name: 'David', age: 48, isSubscribed: false }, + ]; + + expect(transformCustomers(customers)).toEqual({ + 1: { name: 'John', age: 25, isSubscribed: true }, + 2: { name: 'Mary', age: 40, isSubscribed: false }, + 3: { name: 'Bob', age: 32, isSubscribed: true }, + 4: { name: 'Alice', age: 22, isSubscribed: true }, + 5: { name: 'David', age: 48, isSubscribed: false }, + }); + }); +}); diff --git a/src/homeworks/ts1/1_base.js b/src/homeworks/ts1/1_base.ts similarity index 55% rename from src/homeworks/ts1/1_base.js rename to src/homeworks/ts1/1_base.ts index 611b3a92f..338e102bf 100644 --- a/src/homeworks/ts1/1_base.js +++ b/src/homeworks/ts1/1_base.ts @@ -1,16 +1,16 @@ /** * Нужно превратить файл в ts и указать типы аргументов и типы возвращаемого значения * */ -export const removePlus = (string) => string.replace(/^\+/, ''); +export const removePlus = (string: string): string => string.replace(/^\+/, ''); -export const addPlus = (string) => `+${string}`; +export const addPlus = (string: string): string => `+${string}`; -export const removeFirstZeros = (value) => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); +export const removeFirstZeros = (value: string): string => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); -export const getBeautifulNumber = (value, separator = ' ') => +export const getBeautifulNumber = (value: number, separator: string = ' '): string => value?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); -export const round = (value, accuracy = 2) => { +export const round = (value: number, accuracy: number = 2): number => { const d = 10 ** accuracy; return Math.round(value * d) / d; }; @@ -18,7 +18,7 @@ export const round = (value, accuracy = 2) => { const transformRegexp = /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; -export const getTransformFromCss = (transformCssString) => { +export const getTransformFromCss = (transformCssString: string): { x: number, y: number } => { const data = transformCssString.match(transformRegexp); if (!data) return { x: 0, y: 0 }; return { @@ -27,20 +27,20 @@ export const getTransformFromCss = (transformCssString) => { }; }; -export const getColorContrastValue = ([red, green, blue]) => +export const getColorContrastValue = ([red, green, blue]: [number, number, number]): number => // http://www.w3.org/TR/AERT#color-contrast Math.round((red * 299 + green * 587 + blue * 114) / 1000); -export const getContrastType = (contrastValue) => (contrastValue > 125 ? 'black' : 'white'); +export const getContrastType = (contrastValue: number): string => (contrastValue > 125 ? 'black' : 'white'); export const shortColorRegExp = /^#[0-9a-f]{3}$/i; export const longColorRegExp = /^#[0-9a-f]{6}$/i; -export const checkColor = (color) => { +export const checkColor = (color: string): void => { if (!longColorRegExp.test(color) && !shortColorRegExp.test(color)) throw new Error(`invalid hex color: ${color}`); }; -export const hex2rgb = (color) => { +export const hex2rgb = (color: string): [number, number, number] => { checkColor(color); if (shortColorRegExp.test(color)) { const red = parseInt(color.substring(1, 2), 16); @@ -54,12 +54,23 @@ export const hex2rgb = (color) => { return [red, green, blue]; }; -export const getNumberedArray = (arr) => arr.map((value, number) => ({ value, number })); -export const toStringArray = (arr) => arr.map(({ value, number }) => `${value}_${number}`); +export const getNumberedArray = (arr: [number]): any => arr.map((value, number) => ({ value, number })); +export const toStringArray = (arr: []) => arr.map(({ value, number }) => `${value}_${number}`); -export const transformCustomers = (customers) => { + +type Key = { + id: number; +} +type Customer = { + name: string, + age: number, + isSubscribed: boolean +} +type CustomerKey = Key & Customer; + +export const transformCustomers = (customers: CustomerKey[]) => { return customers.reduce((acc, customer) => { acc[customer.id] = { name: customer.name, age: customer.age, isSubscribed: customer.isSubscribed }; return acc; - }, {}); + }, {} as Customer[]); }; diff --git a/src/homeworks/ts1/2_repair.ts b/src/homeworks/ts1/2_repair.ts index 19e98c068..687b86e87 100644 --- a/src/homeworks/ts1/2_repair.ts +++ b/src/homeworks/ts1/2_repair.ts @@ -2,46 +2,50 @@ * Здесь код с ошибками типов. Нужно их устранить * */ -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export const getFakeApi = async (): void => { -// const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); -// console.log(result); -// }; -// -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export class SomeClass { -// constructor() { -// this.set = new Set([1]); -// this.channel = new BroadcastChannel('test-broadcast-channel'); -// } -// } -// -// export type Data = { -// type: 'Money' | 'Percent'; -// value: DataValue; -// }; -// -// export type DataValue = Money | Percent; -// -// export type Money = { -// currency: string; -// amount: number; -// }; -// -// export type Percent = { -// percent: number; -// }; -// -// // Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому -// const getDataAmount = (data: Data): number => { -// switch (data.type) { -// case 'Money': -// return data.value.amount; -// -// default: { -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const unhandled: never = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться -// throw new Error(`unknown type: ${data.type}`); -// } -// } -// }; +// Мы это не проходили, но по тексту ошибки можно понять, как это починить +export const getFakeApi = async (): Promise => { + const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); + console.log(result); +}; + +// Мы это не проходили, но по тексту ошибки можно понять, как это починить +export class SomeClass { + set: Set; + channel: BroadcastChannel; + + constructor() { + this.set = new Set([1]); + this.channel = new BroadcastChannel('test-broadcast-channel'); + } +} + +export type Data = { + type: 'Money' | 'Percent'; + value: DataValue; +}; + +export type DataValue = Money | Percent; + +export type Money = { + currency: string; + amount: number; +}; + +export type Percent = { + percent: number; +}; + +// Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому +const getDataAmount = (data: Data): number => { + switch (data.type) { + case 'Money': + return (data.value as Money).amount; + case 'Percent': + return (data.value as Percent).percent; + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const unhandled: never | Data = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться + throw new Error(`unknown type: ${data.type}`); + } + } +}; diff --git a/src/homeworks/ts1/3_write.test.js b/src/homeworks/ts1/3_write.test.js index 5b5173063..5a50badc4 100644 --- a/src/homeworks/ts1/3_write.test.js +++ b/src/homeworks/ts1/3_write.test.js @@ -1,35 +1,35 @@ // Этот блок кода удалить и раскомментировать код ниже -it('remove it', () => { - expect(true).toBe(true); -}); - -// import { createRandomOperation, createRandomProduct } from './3_write'; -// -// describe('all', () => { -// it('operation', () => { -// const createdAt = '2023-06-06T12:06:56.957Z'; -// const operation = createRandomOperation(createdAt); -// expect(operation).toHaveProperty('createdAt', createdAt); -// expect(operation).toHaveProperty('id'); -// expect(operation).toHaveProperty('name'); -// expect(operation).toHaveProperty('desc'); -// expect(operation).toHaveProperty('createdAt'); -// expect(operation).toHaveProperty('amount'); -// expect(operation).toHaveProperty('category'); -// expect(operation).toHaveProperty('type'); -// }); -// -// it('product', () => { -// const createdAt = '2023-06-06T12:06:56.957Z'; -// const product = createRandomProduct(createdAt); -// expect(product).toHaveProperty('createdAt', createdAt); -// expect(product).toHaveProperty('id'); -// expect(product).toHaveProperty('name'); -// expect(product).toHaveProperty('photo'); -// expect(product).toHaveProperty('desc'); -// expect(product).toHaveProperty('createdAt'); -// expect(product).toHaveProperty('oldPrice'); -// expect(product).toHaveProperty('price'); -// expect(product).toHaveProperty('category'); -// }); +// it('remove it', () => { +// expect(true).toBe(true); // }); + +import { createRandomOperation, createRandomProduct } from './3_write'; + +describe('all', () => { + it('operation', () => { + const createdAt = '2023-06-06T12:06:56.957Z'; + const operation = createRandomOperation(createdAt); + expect(operation).toHaveProperty('createdAt', createdAt); + expect(operation).toHaveProperty('id'); + expect(operation).toHaveProperty('name'); + expect(operation).toHaveProperty('desc'); + expect(operation).toHaveProperty('createdAt'); + expect(operation).toHaveProperty('amount'); + expect(operation).toHaveProperty('category'); + expect(operation).toHaveProperty('type'); + }); + + it('product', () => { + const createdAt = '2023-06-06T12:06:56.957Z'; + const product = createRandomProduct(createdAt); + expect(product).toHaveProperty('createdAt', createdAt); + expect(product).toHaveProperty('id'); + expect(product).toHaveProperty('name'); + expect(product).toHaveProperty('photo'); + expect(product).toHaveProperty('desc'); + expect(product).toHaveProperty('createdAt'); + expect(product).toHaveProperty('oldPrice'); + expect(product).toHaveProperty('price'); + expect(product).toHaveProperty('category'); + }); +}); diff --git a/src/homeworks/ts1/3_write.ts b/src/homeworks/ts1/3_write.ts index 15f9dcdf2..adf621c89 100644 --- a/src/homeworks/ts1/3_write.ts +++ b/src/homeworks/ts1/3_write.ts @@ -1,3 +1,4 @@ +import { faker } from "@faker-js/faker"; /** * Функции написанные здесь пригодятся на последующих уроках * С помощью этих функций мы будем добавлять элементы в список для проверки динамической загрузки @@ -42,15 +43,77 @@ * - category (Категория) * - type ('Profit') * */ +export type Category = { + id: number, + name: string, + photo?: string +}; +export type Product = { + id: number, + name: string, + photo: string, + desc?: string, + createdAt: string, + oldPrice?: number, + price: number + category: Category +}; + +export type Cost = { + id: string, + name: string, + desc?: string + createdAt: string, + amount: number, + category: Category, + type: Cost +}; + +export type Profit = { + id: string, + name: string, + desc?: string + createdAt: string, + amount: number, + category: Category, + type: Profit +}; /** * Создает случайный продукт (Product). * Принимает дату создания (строка) * */ -// export const createRandomProduct = (createdAt: string) => {}; +export const createRandomProduct = (createdAt: string): Product => ({ + id: faker.number.int(), + name: faker.person.fullName(), + photo: faker.image.avatar(), + desc: "desc", + createdAt: createdAt, + oldPrice: faker.number.float(), + price: faker.number.float(), + category: { + id: faker.number.int(), + name: faker.company.name(), + photo: faker.image.avatar() + } +}); /** * Создает случайную операцию (Operation). * Принимает дату создания (строка) * */ -// export const createRandomOperation = (createdAt: string) => {}; +export const createRandomOperation = (createdAt: string): Cost | Profit => ({ + id: faker.string.sample(), + createdAt: createdAt, + name: faker.person.fullName(), + desc: "desc", + amount: faker.number.int(), + category: { + id: faker.number.int(), + name: faker.company.name(), + photo: faker.image.avatar() + }, + type: this + +}); + From 367d4317317d2e69cc758752c7611c799620afd5 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 18 Sep 2024 20:50:00 +0300 Subject: [PATCH 03/53] =?UTF-8?q?Revert=20"typescript=20=D0=B4=D0=BE=D0=BC?= =?UTF-8?q?=D0=B0=D1=88=D0=BD=D1=8F=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0=201"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit ebb7be15fdc18cead77963c8d722cf156bb884bc. --- package-lock.json | 22 ------ package.json | 1 - src/homeworks/ts1/{1_base.ts => 1_base.js} | 39 ++++------ src/homeworks/ts1/1_base.test.js | 48 ++++++------ src/homeworks/ts1/2_repair.ts | 90 +++++++++++----------- src/homeworks/ts1/3_write.test.js | 66 ++++++++-------- src/homeworks/ts1/3_write.ts | 67 +--------------- 7 files changed, 116 insertions(+), 217 deletions(-) rename src/homeworks/ts1/{1_base.ts => 1_base.js} (55%) diff --git a/package-lock.json b/package-lock.json index 6ca16be08..77870b96b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,6 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@faker-js/faker": "^9.0.1", "clsx": "^1.2.1", "react": "^18.2.0", "react-dom": "^18.2.0" @@ -2578,22 +2577,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@faker-js/faker": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.1.tgz", - "integrity": "sha512-4mDeYIgM3By7X6t5E6eYwLAa+2h4DeZDF7thhzIg6XB76jeEvMwadYAMCFJL/R4AnEBcAUO9+gL0vhy3s+qvZA==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/fakerjs" - } - ], - "license": "MIT", - "engines": { - "node": ">=18.0.0", - "npm": ">=9.0.0" - } - }, "node_modules/@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", @@ -25380,11 +25363,6 @@ } } }, - "@faker-js/faker": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.1.tgz", - "integrity": "sha512-4mDeYIgM3By7X6t5E6eYwLAa+2h4DeZDF7thhzIg6XB76jeEvMwadYAMCFJL/R4AnEBcAUO9+gL0vhy3s+qvZA==" - }, "@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", diff --git a/package.json b/package.json index dc6d471fb..5fedda2bf 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,6 @@ "webpack-dev-server": "^4.15.0" }, "dependencies": { - "@faker-js/faker": "^9.0.1", "clsx": "^1.2.1", "react": "^18.2.0", "react-dom": "^18.2.0" diff --git a/src/homeworks/ts1/1_base.ts b/src/homeworks/ts1/1_base.js similarity index 55% rename from src/homeworks/ts1/1_base.ts rename to src/homeworks/ts1/1_base.js index 338e102bf..611b3a92f 100644 --- a/src/homeworks/ts1/1_base.ts +++ b/src/homeworks/ts1/1_base.js @@ -1,16 +1,16 @@ /** * Нужно превратить файл в ts и указать типы аргументов и типы возвращаемого значения * */ -export const removePlus = (string: string): string => string.replace(/^\+/, ''); +export const removePlus = (string) => string.replace(/^\+/, ''); -export const addPlus = (string: string): string => `+${string}`; +export const addPlus = (string) => `+${string}`; -export const removeFirstZeros = (value: string): string => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); +export const removeFirstZeros = (value) => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); -export const getBeautifulNumber = (value: number, separator: string = ' '): string => +export const getBeautifulNumber = (value, separator = ' ') => value?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); -export const round = (value: number, accuracy: number = 2): number => { +export const round = (value, accuracy = 2) => { const d = 10 ** accuracy; return Math.round(value * d) / d; }; @@ -18,7 +18,7 @@ export const round = (value: number, accuracy: number = 2): number => { const transformRegexp = /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; -export const getTransformFromCss = (transformCssString: string): { x: number, y: number } => { +export const getTransformFromCss = (transformCssString) => { const data = transformCssString.match(transformRegexp); if (!data) return { x: 0, y: 0 }; return { @@ -27,20 +27,20 @@ export const getTransformFromCss = (transformCssString: string): { x: number, y: }; }; -export const getColorContrastValue = ([red, green, blue]: [number, number, number]): number => +export const getColorContrastValue = ([red, green, blue]) => // http://www.w3.org/TR/AERT#color-contrast Math.round((red * 299 + green * 587 + blue * 114) / 1000); -export const getContrastType = (contrastValue: number): string => (contrastValue > 125 ? 'black' : 'white'); +export const getContrastType = (contrastValue) => (contrastValue > 125 ? 'black' : 'white'); export const shortColorRegExp = /^#[0-9a-f]{3}$/i; export const longColorRegExp = /^#[0-9a-f]{6}$/i; -export const checkColor = (color: string): void => { +export const checkColor = (color) => { if (!longColorRegExp.test(color) && !shortColorRegExp.test(color)) throw new Error(`invalid hex color: ${color}`); }; -export const hex2rgb = (color: string): [number, number, number] => { +export const hex2rgb = (color) => { checkColor(color); if (shortColorRegExp.test(color)) { const red = parseInt(color.substring(1, 2), 16); @@ -54,23 +54,12 @@ export const hex2rgb = (color: string): [number, number, number] => { return [red, green, blue]; }; -export const getNumberedArray = (arr: [number]): any => arr.map((value, number) => ({ value, number })); -export const toStringArray = (arr: []) => arr.map(({ value, number }) => `${value}_${number}`); +export const getNumberedArray = (arr) => arr.map((value, number) => ({ value, number })); +export const toStringArray = (arr) => arr.map(({ value, number }) => `${value}_${number}`); - -type Key = { - id: number; -} -type Customer = { - name: string, - age: number, - isSubscribed: boolean -} -type CustomerKey = Key & Customer; - -export const transformCustomers = (customers: CustomerKey[]) => { +export const transformCustomers = (customers) => { return customers.reduce((acc, customer) => { acc[customer.id] = { name: customer.name, age: customer.age, isSubscribed: customer.isSubscribed }; return acc; - }, {} as Customer[]); + }, {}); }; diff --git a/src/homeworks/ts1/1_base.test.js b/src/homeworks/ts1/1_base.test.js index ff77416da..ee68dc16f 100644 --- a/src/homeworks/ts1/1_base.test.js +++ b/src/homeworks/ts1/1_base.test.js @@ -1,26 +1,26 @@ // Этот блок кода удалить и раскомментировать код ниже -// it('remove it', () => { -// expect(true).toBe(true); -// }); - -import { transformCustomers } from './1_base'; - -describe('all', () => { - it('transformCustomers', () => { - const customers = [ - { id: 1, name: 'John', age: 25, isSubscribed: true }, - { id: 2, name: 'Mary', age: 40, isSubscribed: false }, - { id: 3, name: 'Bob', age: 32, isSubscribed: true }, - { id: 4, name: 'Alice', age: 22, isSubscribed: true }, - { id: 5, name: 'David', age: 48, isSubscribed: false }, - ]; - - expect(transformCustomers(customers)).toEqual({ - 1: { name: 'John', age: 25, isSubscribed: true }, - 2: { name: 'Mary', age: 40, isSubscribed: false }, - 3: { name: 'Bob', age: 32, isSubscribed: true }, - 4: { name: 'Alice', age: 22, isSubscribed: true }, - 5: { name: 'David', age: 48, isSubscribed: false }, - }); - }); +it('remove it', () => { + expect(true).toBe(true); }); + +// import { transformCustomers } from './1_base'; +// +// describe('all', () => { +// it('transformCustomers', () => { +// const customers = [ +// { id: 1, name: 'John', age: 25, isSubscribed: true }, +// { id: 2, name: 'Mary', age: 40, isSubscribed: false }, +// { id: 3, name: 'Bob', age: 32, isSubscribed: true }, +// { id: 4, name: 'Alice', age: 22, isSubscribed: true }, +// { id: 5, name: 'David', age: 48, isSubscribed: false }, +// ]; +// +// expect(transformCustomers(customers)).toEqual({ +// 1: { name: 'John', age: 25, isSubscribed: true }, +// 2: { name: 'Mary', age: 40, isSubscribed: false }, +// 3: { name: 'Bob', age: 32, isSubscribed: true }, +// 4: { name: 'Alice', age: 22, isSubscribed: true }, +// 5: { name: 'David', age: 48, isSubscribed: false }, +// }); +// }); +// }); diff --git a/src/homeworks/ts1/2_repair.ts b/src/homeworks/ts1/2_repair.ts index 687b86e87..19e98c068 100644 --- a/src/homeworks/ts1/2_repair.ts +++ b/src/homeworks/ts1/2_repair.ts @@ -2,50 +2,46 @@ * Здесь код с ошибками типов. Нужно их устранить * */ -// Мы это не проходили, но по тексту ошибки можно понять, как это починить -export const getFakeApi = async (): Promise => { - const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); - console.log(result); -}; - -// Мы это не проходили, но по тексту ошибки можно понять, как это починить -export class SomeClass { - set: Set; - channel: BroadcastChannel; - - constructor() { - this.set = new Set([1]); - this.channel = new BroadcastChannel('test-broadcast-channel'); - } -} - -export type Data = { - type: 'Money' | 'Percent'; - value: DataValue; -}; - -export type DataValue = Money | Percent; - -export type Money = { - currency: string; - amount: number; -}; - -export type Percent = { - percent: number; -}; - -// Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому -const getDataAmount = (data: Data): number => { - switch (data.type) { - case 'Money': - return (data.value as Money).amount; - case 'Percent': - return (data.value as Percent).percent; - default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const unhandled: never | Data = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться - throw new Error(`unknown type: ${data.type}`); - } - } -}; +// // Мы это не проходили, но по тексту ошибки можно понять, как это починить +// export const getFakeApi = async (): void => { +// const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); +// console.log(result); +// }; +// +// // Мы это не проходили, но по тексту ошибки можно понять, как это починить +// export class SomeClass { +// constructor() { +// this.set = new Set([1]); +// this.channel = new BroadcastChannel('test-broadcast-channel'); +// } +// } +// +// export type Data = { +// type: 'Money' | 'Percent'; +// value: DataValue; +// }; +// +// export type DataValue = Money | Percent; +// +// export type Money = { +// currency: string; +// amount: number; +// }; +// +// export type Percent = { +// percent: number; +// }; +// +// // Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому +// const getDataAmount = (data: Data): number => { +// switch (data.type) { +// case 'Money': +// return data.value.amount; +// +// default: { +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// const unhandled: never = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться +// throw new Error(`unknown type: ${data.type}`); +// } +// } +// }; diff --git a/src/homeworks/ts1/3_write.test.js b/src/homeworks/ts1/3_write.test.js index 5a50badc4..5b5173063 100644 --- a/src/homeworks/ts1/3_write.test.js +++ b/src/homeworks/ts1/3_write.test.js @@ -1,35 +1,35 @@ // Этот блок кода удалить и раскомментировать код ниже -// it('remove it', () => { -// expect(true).toBe(true); -// }); - -import { createRandomOperation, createRandomProduct } from './3_write'; - -describe('all', () => { - it('operation', () => { - const createdAt = '2023-06-06T12:06:56.957Z'; - const operation = createRandomOperation(createdAt); - expect(operation).toHaveProperty('createdAt', createdAt); - expect(operation).toHaveProperty('id'); - expect(operation).toHaveProperty('name'); - expect(operation).toHaveProperty('desc'); - expect(operation).toHaveProperty('createdAt'); - expect(operation).toHaveProperty('amount'); - expect(operation).toHaveProperty('category'); - expect(operation).toHaveProperty('type'); - }); - - it('product', () => { - const createdAt = '2023-06-06T12:06:56.957Z'; - const product = createRandomProduct(createdAt); - expect(product).toHaveProperty('createdAt', createdAt); - expect(product).toHaveProperty('id'); - expect(product).toHaveProperty('name'); - expect(product).toHaveProperty('photo'); - expect(product).toHaveProperty('desc'); - expect(product).toHaveProperty('createdAt'); - expect(product).toHaveProperty('oldPrice'); - expect(product).toHaveProperty('price'); - expect(product).toHaveProperty('category'); - }); +it('remove it', () => { + expect(true).toBe(true); }); + +// import { createRandomOperation, createRandomProduct } from './3_write'; +// +// describe('all', () => { +// it('operation', () => { +// const createdAt = '2023-06-06T12:06:56.957Z'; +// const operation = createRandomOperation(createdAt); +// expect(operation).toHaveProperty('createdAt', createdAt); +// expect(operation).toHaveProperty('id'); +// expect(operation).toHaveProperty('name'); +// expect(operation).toHaveProperty('desc'); +// expect(operation).toHaveProperty('createdAt'); +// expect(operation).toHaveProperty('amount'); +// expect(operation).toHaveProperty('category'); +// expect(operation).toHaveProperty('type'); +// }); +// +// it('product', () => { +// const createdAt = '2023-06-06T12:06:56.957Z'; +// const product = createRandomProduct(createdAt); +// expect(product).toHaveProperty('createdAt', createdAt); +// expect(product).toHaveProperty('id'); +// expect(product).toHaveProperty('name'); +// expect(product).toHaveProperty('photo'); +// expect(product).toHaveProperty('desc'); +// expect(product).toHaveProperty('createdAt'); +// expect(product).toHaveProperty('oldPrice'); +// expect(product).toHaveProperty('price'); +// expect(product).toHaveProperty('category'); +// }); +// }); diff --git a/src/homeworks/ts1/3_write.ts b/src/homeworks/ts1/3_write.ts index adf621c89..15f9dcdf2 100644 --- a/src/homeworks/ts1/3_write.ts +++ b/src/homeworks/ts1/3_write.ts @@ -1,4 +1,3 @@ -import { faker } from "@faker-js/faker"; /** * Функции написанные здесь пригодятся на последующих уроках * С помощью этих функций мы будем добавлять элементы в список для проверки динамической загрузки @@ -43,77 +42,15 @@ import { faker } from "@faker-js/faker"; * - category (Категория) * - type ('Profit') * */ -export type Category = { - id: number, - name: string, - photo?: string -}; -export type Product = { - id: number, - name: string, - photo: string, - desc?: string, - createdAt: string, - oldPrice?: number, - price: number - category: Category -}; - -export type Cost = { - id: string, - name: string, - desc?: string - createdAt: string, - amount: number, - category: Category, - type: Cost -}; - -export type Profit = { - id: string, - name: string, - desc?: string - createdAt: string, - amount: number, - category: Category, - type: Profit -}; /** * Создает случайный продукт (Product). * Принимает дату создания (строка) * */ -export const createRandomProduct = (createdAt: string): Product => ({ - id: faker.number.int(), - name: faker.person.fullName(), - photo: faker.image.avatar(), - desc: "desc", - createdAt: createdAt, - oldPrice: faker.number.float(), - price: faker.number.float(), - category: { - id: faker.number.int(), - name: faker.company.name(), - photo: faker.image.avatar() - } -}); +// export const createRandomProduct = (createdAt: string) => {}; /** * Создает случайную операцию (Operation). * Принимает дату создания (строка) * */ -export const createRandomOperation = (createdAt: string): Cost | Profit => ({ - id: faker.string.sample(), - createdAt: createdAt, - name: faker.person.fullName(), - desc: "desc", - amount: faker.number.int(), - category: { - id: faker.number.int(), - name: faker.company.name(), - photo: faker.image.avatar() - }, - type: this - -}); - +// export const createRandomOperation = (createdAt: string) => {}; From 657a7171d6df57b1736a0485f1f04fece831c96c Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 18 Sep 2024 21:01:41 +0300 Subject: [PATCH 04/53] =?UTF-8?q?=D0=BF=D1=8B=D1=82=D0=B0=D1=8E=D1=81?= =?UTF-8?q?=D1=8C=20=D0=BF=D0=BE=D1=87=D0=B8=D0=BD=D0=B8=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/homeworks/ts1/3_write.test.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/homeworks/ts1/3_write.test.js b/src/homeworks/ts1/3_write.test.js index 5a50badc4..1efc8ac1f 100644 --- a/src/homeworks/ts1/3_write.test.js +++ b/src/homeworks/ts1/3_write.test.js @@ -1,7 +1,4 @@ // Этот блок кода удалить и раскомментировать код ниже -// it('remove it', () => { -// expect(true).toBe(true); -// }); import { createRandomOperation, createRandomProduct } from './3_write'; From e7a18010aea8d7229914a2f89977f95ffda47e5c Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 18 Sep 2024 21:42:42 +0300 Subject: [PATCH 05/53] test --- src/homeworks/ts1/3_write.test.js | 39 +------------------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/src/homeworks/ts1/3_write.test.js b/src/homeworks/ts1/3_write.test.js index b8dc99ccf..4693a8762 100644 --- a/src/homeworks/ts1/3_write.test.js +++ b/src/homeworks/ts1/3_write.test.js @@ -1,9 +1,4 @@ // Этот блок кода удалить и раскомментировать код ниже -<<<<<<< HEAD -it('remove it', () => { - expect(true).toBe(true); -======= - import { createRandomOperation, createRandomProduct } from './3_write'; describe('all', () => { @@ -33,36 +28,4 @@ describe('all', () => { expect(product).toHaveProperty('price'); expect(product).toHaveProperty('category'); }); ->>>>>>> homework1 -}); - -// import { createRandomOperation, createRandomProduct } from './3_write'; -// -// describe('all', () => { -// it('operation', () => { -// const createdAt = '2023-06-06T12:06:56.957Z'; -// const operation = createRandomOperation(createdAt); -// expect(operation).toHaveProperty('createdAt', createdAt); -// expect(operation).toHaveProperty('id'); -// expect(operation).toHaveProperty('name'); -// expect(operation).toHaveProperty('desc'); -// expect(operation).toHaveProperty('createdAt'); -// expect(operation).toHaveProperty('amount'); -// expect(operation).toHaveProperty('category'); -// expect(operation).toHaveProperty('type'); -// }); -// -// it('product', () => { -// const createdAt = '2023-06-06T12:06:56.957Z'; -// const product = createRandomProduct(createdAt); -// expect(product).toHaveProperty('createdAt', createdAt); -// expect(product).toHaveProperty('id'); -// expect(product).toHaveProperty('name'); -// expect(product).toHaveProperty('photo'); -// expect(product).toHaveProperty('desc'); -// expect(product).toHaveProperty('createdAt'); -// expect(product).toHaveProperty('oldPrice'); -// expect(product).toHaveProperty('price'); -// expect(product).toHaveProperty('category'); -// }); -// }); +}); \ No newline at end of file From 6435beb42d8efaf5d240dc08d92d9e23f3c639b5 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 18 Sep 2024 21:54:51 +0300 Subject: [PATCH 06/53] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/homeworks/ts1/1_base.test.js | 48 ++++++------ src/homeworks/ts1/{1_base.js => 1_base.ts} | 39 ++++++---- src/homeworks/ts1/2_repair.ts | 90 +++++++++++----------- src/homeworks/ts1/3_write.test.js | 6 +- src/homeworks/ts1/3_write.ts | 67 +++++++++++++++- 5 files changed, 166 insertions(+), 84 deletions(-) rename src/homeworks/ts1/{1_base.js => 1_base.ts} (55%) diff --git a/src/homeworks/ts1/1_base.test.js b/src/homeworks/ts1/1_base.test.js index ee68dc16f..ff77416da 100644 --- a/src/homeworks/ts1/1_base.test.js +++ b/src/homeworks/ts1/1_base.test.js @@ -1,26 +1,26 @@ // Этот блок кода удалить и раскомментировать код ниже -it('remove it', () => { - expect(true).toBe(true); -}); - -// import { transformCustomers } from './1_base'; -// -// describe('all', () => { -// it('transformCustomers', () => { -// const customers = [ -// { id: 1, name: 'John', age: 25, isSubscribed: true }, -// { id: 2, name: 'Mary', age: 40, isSubscribed: false }, -// { id: 3, name: 'Bob', age: 32, isSubscribed: true }, -// { id: 4, name: 'Alice', age: 22, isSubscribed: true }, -// { id: 5, name: 'David', age: 48, isSubscribed: false }, -// ]; -// -// expect(transformCustomers(customers)).toEqual({ -// 1: { name: 'John', age: 25, isSubscribed: true }, -// 2: { name: 'Mary', age: 40, isSubscribed: false }, -// 3: { name: 'Bob', age: 32, isSubscribed: true }, -// 4: { name: 'Alice', age: 22, isSubscribed: true }, -// 5: { name: 'David', age: 48, isSubscribed: false }, -// }); -// }); +// it('remove it', () => { +// expect(true).toBe(true); // }); + +import { transformCustomers } from './1_base'; + +describe('all', () => { + it('transformCustomers', () => { + const customers = [ + { id: 1, name: 'John', age: 25, isSubscribed: true }, + { id: 2, name: 'Mary', age: 40, isSubscribed: false }, + { id: 3, name: 'Bob', age: 32, isSubscribed: true }, + { id: 4, name: 'Alice', age: 22, isSubscribed: true }, + { id: 5, name: 'David', age: 48, isSubscribed: false }, + ]; + + expect(transformCustomers(customers)).toEqual({ + 1: { name: 'John', age: 25, isSubscribed: true }, + 2: { name: 'Mary', age: 40, isSubscribed: false }, + 3: { name: 'Bob', age: 32, isSubscribed: true }, + 4: { name: 'Alice', age: 22, isSubscribed: true }, + 5: { name: 'David', age: 48, isSubscribed: false }, + }); + }); +}); diff --git a/src/homeworks/ts1/1_base.js b/src/homeworks/ts1/1_base.ts similarity index 55% rename from src/homeworks/ts1/1_base.js rename to src/homeworks/ts1/1_base.ts index 611b3a92f..338e102bf 100644 --- a/src/homeworks/ts1/1_base.js +++ b/src/homeworks/ts1/1_base.ts @@ -1,16 +1,16 @@ /** * Нужно превратить файл в ts и указать типы аргументов и типы возвращаемого значения * */ -export const removePlus = (string) => string.replace(/^\+/, ''); +export const removePlus = (string: string): string => string.replace(/^\+/, ''); -export const addPlus = (string) => `+${string}`; +export const addPlus = (string: string): string => `+${string}`; -export const removeFirstZeros = (value) => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); +export const removeFirstZeros = (value: string): string => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); -export const getBeautifulNumber = (value, separator = ' ') => +export const getBeautifulNumber = (value: number, separator: string = ' '): string => value?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); -export const round = (value, accuracy = 2) => { +export const round = (value: number, accuracy: number = 2): number => { const d = 10 ** accuracy; return Math.round(value * d) / d; }; @@ -18,7 +18,7 @@ export const round = (value, accuracy = 2) => { const transformRegexp = /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; -export const getTransformFromCss = (transformCssString) => { +export const getTransformFromCss = (transformCssString: string): { x: number, y: number } => { const data = transformCssString.match(transformRegexp); if (!data) return { x: 0, y: 0 }; return { @@ -27,20 +27,20 @@ export const getTransformFromCss = (transformCssString) => { }; }; -export const getColorContrastValue = ([red, green, blue]) => +export const getColorContrastValue = ([red, green, blue]: [number, number, number]): number => // http://www.w3.org/TR/AERT#color-contrast Math.round((red * 299 + green * 587 + blue * 114) / 1000); -export const getContrastType = (contrastValue) => (contrastValue > 125 ? 'black' : 'white'); +export const getContrastType = (contrastValue: number): string => (contrastValue > 125 ? 'black' : 'white'); export const shortColorRegExp = /^#[0-9a-f]{3}$/i; export const longColorRegExp = /^#[0-9a-f]{6}$/i; -export const checkColor = (color) => { +export const checkColor = (color: string): void => { if (!longColorRegExp.test(color) && !shortColorRegExp.test(color)) throw new Error(`invalid hex color: ${color}`); }; -export const hex2rgb = (color) => { +export const hex2rgb = (color: string): [number, number, number] => { checkColor(color); if (shortColorRegExp.test(color)) { const red = parseInt(color.substring(1, 2), 16); @@ -54,12 +54,23 @@ export const hex2rgb = (color) => { return [red, green, blue]; }; -export const getNumberedArray = (arr) => arr.map((value, number) => ({ value, number })); -export const toStringArray = (arr) => arr.map(({ value, number }) => `${value}_${number}`); +export const getNumberedArray = (arr: [number]): any => arr.map((value, number) => ({ value, number })); +export const toStringArray = (arr: []) => arr.map(({ value, number }) => `${value}_${number}`); -export const transformCustomers = (customers) => { + +type Key = { + id: number; +} +type Customer = { + name: string, + age: number, + isSubscribed: boolean +} +type CustomerKey = Key & Customer; + +export const transformCustomers = (customers: CustomerKey[]) => { return customers.reduce((acc, customer) => { acc[customer.id] = { name: customer.name, age: customer.age, isSubscribed: customer.isSubscribed }; return acc; - }, {}); + }, {} as Customer[]); }; diff --git a/src/homeworks/ts1/2_repair.ts b/src/homeworks/ts1/2_repair.ts index 19e98c068..687b86e87 100644 --- a/src/homeworks/ts1/2_repair.ts +++ b/src/homeworks/ts1/2_repair.ts @@ -2,46 +2,50 @@ * Здесь код с ошибками типов. Нужно их устранить * */ -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export const getFakeApi = async (): void => { -// const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); -// console.log(result); -// }; -// -// // Мы это не проходили, но по тексту ошибки можно понять, как это починить -// export class SomeClass { -// constructor() { -// this.set = new Set([1]); -// this.channel = new BroadcastChannel('test-broadcast-channel'); -// } -// } -// -// export type Data = { -// type: 'Money' | 'Percent'; -// value: DataValue; -// }; -// -// export type DataValue = Money | Percent; -// -// export type Money = { -// currency: string; -// amount: number; -// }; -// -// export type Percent = { -// percent: number; -// }; -// -// // Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому -// const getDataAmount = (data: Data): number => { -// switch (data.type) { -// case 'Money': -// return data.value.amount; -// -// default: { -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// const unhandled: never = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться -// throw new Error(`unknown type: ${data.type}`); -// } -// } -// }; +// Мы это не проходили, но по тексту ошибки можно понять, как это починить +export const getFakeApi = async (): Promise => { + const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); + console.log(result); +}; + +// Мы это не проходили, но по тексту ошибки можно понять, как это починить +export class SomeClass { + set: Set; + channel: BroadcastChannel; + + constructor() { + this.set = new Set([1]); + this.channel = new BroadcastChannel('test-broadcast-channel'); + } +} + +export type Data = { + type: 'Money' | 'Percent'; + value: DataValue; +}; + +export type DataValue = Money | Percent; + +export type Money = { + currency: string; + amount: number; +}; + +export type Percent = { + percent: number; +}; + +// Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому +const getDataAmount = (data: Data): number => { + switch (data.type) { + case 'Money': + return (data.value as Money).amount; + case 'Percent': + return (data.value as Percent).percent; + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const unhandled: never | Data = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться + throw new Error(`unknown type: ${data.type}`); + } + } +}; diff --git a/src/homeworks/ts1/3_write.test.js b/src/homeworks/ts1/3_write.test.js index 4693a8762..5a50badc4 100644 --- a/src/homeworks/ts1/3_write.test.js +++ b/src/homeworks/ts1/3_write.test.js @@ -1,4 +1,8 @@ // Этот блок кода удалить и раскомментировать код ниже +// it('remove it', () => { +// expect(true).toBe(true); +// }); + import { createRandomOperation, createRandomProduct } from './3_write'; describe('all', () => { @@ -28,4 +32,4 @@ describe('all', () => { expect(product).toHaveProperty('price'); expect(product).toHaveProperty('category'); }); -}); \ No newline at end of file +}); diff --git a/src/homeworks/ts1/3_write.ts b/src/homeworks/ts1/3_write.ts index 15f9dcdf2..adf621c89 100644 --- a/src/homeworks/ts1/3_write.ts +++ b/src/homeworks/ts1/3_write.ts @@ -1,3 +1,4 @@ +import { faker } from "@faker-js/faker"; /** * Функции написанные здесь пригодятся на последующих уроках * С помощью этих функций мы будем добавлять элементы в список для проверки динамической загрузки @@ -42,15 +43,77 @@ * - category (Категория) * - type ('Profit') * */ +export type Category = { + id: number, + name: string, + photo?: string +}; +export type Product = { + id: number, + name: string, + photo: string, + desc?: string, + createdAt: string, + oldPrice?: number, + price: number + category: Category +}; + +export type Cost = { + id: string, + name: string, + desc?: string + createdAt: string, + amount: number, + category: Category, + type: Cost +}; + +export type Profit = { + id: string, + name: string, + desc?: string + createdAt: string, + amount: number, + category: Category, + type: Profit +}; /** * Создает случайный продукт (Product). * Принимает дату создания (строка) * */ -// export const createRandomProduct = (createdAt: string) => {}; +export const createRandomProduct = (createdAt: string): Product => ({ + id: faker.number.int(), + name: faker.person.fullName(), + photo: faker.image.avatar(), + desc: "desc", + createdAt: createdAt, + oldPrice: faker.number.float(), + price: faker.number.float(), + category: { + id: faker.number.int(), + name: faker.company.name(), + photo: faker.image.avatar() + } +}); /** * Создает случайную операцию (Operation). * Принимает дату создания (строка) * */ -// export const createRandomOperation = (createdAt: string) => {}; +export const createRandomOperation = (createdAt: string): Cost | Profit => ({ + id: faker.string.sample(), + createdAt: createdAt, + name: faker.person.fullName(), + desc: "desc", + amount: faker.number.int(), + category: { + id: faker.number.int(), + name: faker.company.name(), + photo: faker.image.avatar() + }, + type: this + +}); + From 07b3deb49f0802413e26ab29101453c3f8471767 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Thu, 19 Sep 2024 23:46:24 +0300 Subject: [PATCH 07/53] =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BF=D0=BE=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/homeworks/ts1/1_base.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/homeworks/ts1/1_base.ts b/src/homeworks/ts1/1_base.ts index 338e102bf..6975f7c93 100644 --- a/src/homeworks/ts1/1_base.ts +++ b/src/homeworks/ts1/1_base.ts @@ -7,10 +7,10 @@ export const addPlus = (string: string): string => `+${string}`; export const removeFirstZeros = (value: string): string => value.replace(/^(-)?[0]+(-?\d+.*)$/, '$1$2'); -export const getBeautifulNumber = (value: number, separator: string = ' '): string => +export const getBeautifulNumber = (value: number, separator = ' '): string => value?.toString().replace(/\B(?=(\d{3})+(?!\d))/g, separator); -export const round = (value: number, accuracy: number = 2): number => { +export const round = (value: number, accuracy = 2): number => { const d = 10 ** accuracy; return Math.round(value * d) / d; }; @@ -18,7 +18,8 @@ export const round = (value: number, accuracy: number = 2): number => { const transformRegexp = /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; -export const getTransformFromCss = (transformCssString: string): { x: number, y: number } => { +type coordinate = { x: number, y: number }; +export const getTransformFromCss = (transformCssString: string): coordinate => { const data = transformCssString.match(transformRegexp); if (!data) return { x: 0, y: 0 }; return { @@ -36,7 +37,7 @@ export const getContrastType = (contrastValue: number): string => (contrastValue export const shortColorRegExp = /^#[0-9a-f]{3}$/i; export const longColorRegExp = /^#[0-9a-f]{6}$/i; -export const checkColor = (color: string): void => { +export const checkColor = (color: string): void | never => { if (!longColorRegExp.test(color) && !shortColorRegExp.test(color)) throw new Error(`invalid hex color: ${color}`); }; @@ -54,8 +55,8 @@ export const hex2rgb = (color: string): [number, number, number] => { return [red, green, blue]; }; -export const getNumberedArray = (arr: [number]): any => arr.map((value, number) => ({ value, number })); -export const toStringArray = (arr: []) => arr.map(({ value, number }) => `${value}_${number}`); +export const getNumberedArray = (arr: [T]): { value: T, number: number }[] => arr.map((value, number) => ({ value, number })); +export const toStringArray = (arr: [{ value: T, number: number }]) => arr.map(({ value, number }) => `${value}_${number}`); type Key = { From 7bb7b43575c373096b08541ff04976b35f62c04e Mon Sep 17 00:00:00 2001 From: Yurabox Date: Tue, 24 Sep 2024 23:01:27 +0300 Subject: [PATCH 08/53] =?UTF-8?q?=D0=B2=D1=82=D0=BE=D1=80=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D1=8F=D1=8F=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 40 ++++++++--------- .storybook/main.ts | 1 + .storybook/preview.ts | 1 + package-lock.json | 41 ++++++++++++++++++ package.json | 3 +- src/index.tsx | 2 + src/stories/{ => Examples}/Button.stories.ts | 0 src/stories/{ => Examples}/Button.tsx | 0 src/stories/{ => Examples}/Header.stories.ts | 0 src/stories/{ => Examples}/Header.tsx | 0 src/stories/{ => Examples}/Introduction.mdx | 0 src/stories/{ => Examples}/Page.stories.ts | 0 src/stories/{ => Examples}/Page.tsx | 0 .../{ => Examples}/assets/code-brackets.svg | 0 src/stories/{ => Examples}/assets/colors.svg | 0 .../{ => Examples}/assets/comments.svg | 0 .../{ => Examples}/assets/direction.svg | 0 src/stories/{ => Examples}/assets/flow.svg | 0 src/stories/{ => Examples}/assets/plugin.svg | 0 src/stories/{ => Examples}/assets/repo.svg | 0 .../{ => Examples}/assets/stackalt.svg | 0 src/stories/{ => Examples}/button.module.sass | 0 src/stories/{ => Examples}/header.css | 0 src/stories/{ => Examples}/page.css | 0 src/stories/Homework2/Logo.jsx | 9 ++++ src/stories/Homework2/Logo.stories.ts | 13 ++++++ src/stories/Homework2/assets/logo-image.png | Bin 0 -> 25705 bytes src/stories/Homework2/assets/logo-image64.png | Bin 0 -> 4926 bytes src/stories/Homework2/headers/Header.css | 0 src/stories/Homework2/headers/Header.jsx | 10 +++++ .../Homework2/headers/Header.stories.ts | 18 ++++++++ src/stories/Homework2/layouts/Layout.css | 9 ++++ src/stories/Homework2/layouts/Layout.jsx | 13 ++++++ .../Homework2/layouts/Layout.stories.ts | 19 ++++++++ 34 files changed, 158 insertions(+), 21 deletions(-) rename src/stories/{ => Examples}/Button.stories.ts (100%) rename src/stories/{ => Examples}/Button.tsx (100%) rename src/stories/{ => Examples}/Header.stories.ts (100%) rename src/stories/{ => Examples}/Header.tsx (100%) rename src/stories/{ => Examples}/Introduction.mdx (100%) rename src/stories/{ => Examples}/Page.stories.ts (100%) rename src/stories/{ => Examples}/Page.tsx (100%) rename src/stories/{ => Examples}/assets/code-brackets.svg (100%) rename src/stories/{ => Examples}/assets/colors.svg (100%) rename src/stories/{ => Examples}/assets/comments.svg (100%) rename src/stories/{ => Examples}/assets/direction.svg (100%) rename src/stories/{ => Examples}/assets/flow.svg (100%) rename src/stories/{ => Examples}/assets/plugin.svg (100%) rename src/stories/{ => Examples}/assets/repo.svg (100%) rename src/stories/{ => Examples}/assets/stackalt.svg (100%) rename src/stories/{ => Examples}/button.module.sass (100%) rename src/stories/{ => Examples}/header.css (100%) rename src/stories/{ => Examples}/page.css (100%) create mode 100644 src/stories/Homework2/Logo.jsx create mode 100644 src/stories/Homework2/Logo.stories.ts create mode 100644 src/stories/Homework2/assets/logo-image.png create mode 100644 src/stories/Homework2/assets/logo-image64.png create mode 100644 src/stories/Homework2/headers/Header.css create mode 100644 src/stories/Homework2/headers/Header.jsx create mode 100644 src/stories/Homework2/headers/Header.stories.ts create mode 100644 src/stories/Homework2/layouts/Layout.css create mode 100644 src/stories/Homework2/layouts/Layout.jsx create mode 100644 src/stories/Homework2/layouts/Layout.stories.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0f5ba90b0..19ffdec66 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,28 +34,28 @@ jobs: - name: Run tests and linter run: npm run lint && npm test - # Собираем приложение - - name: Build Application - run: npm run build - - # Публикуем приложение на Github Pages - - name: Deploy to Github Pages + # # Собираем приложение + # - name: Build Application + # run: npm run build + + # # Публикуем приложение на Github Pages + # - name: Deploy to Github Pages + # uses: JamesIves/github-pages-deploy-action@4.2.1 + # with: + # branch: gh-pages + # folder: dist + + # Собираем Storybook + - name: Build Storybook + run: npm run build-storybook + + # Публикуем Storybook на Github Pages + - name: Deploy Storybook to Github Pages uses: JamesIves/github-pages-deploy-action@4.2.1 with: - branch: gh-pages - folder: dist - - # # Собираем Storybook - # - name: Build Storybook - # run: npm run build-storybook - # - # # Публикуем Storybook на Github Pages - # - name: Deploy Storybook to Github Pages - # uses: JamesIves/github-pages-deploy-action@4.2.1 - # with: - # branch: gh-pages - # folder: storybook-static - # commit-message: "Automatically publish Storybook" + branch: gh-pages + folder: storybook-static + commit-message: "Automatically publish Storybook" # Останавливаем выполнение строго при неудачных тестах - name: Fail on failed tests diff --git a/.storybook/main.ts b/.storybook/main.ts index 3d1c9b2d5..2461b8577 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,5 +1,6 @@ const config = { stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + staticDirs: ['../src/stories/Homework2/assets'], addons: [ "@storybook/addon-links", "@storybook/addon-essentials", diff --git a/.storybook/preview.ts b/.storybook/preview.ts index 1c372b694..8da71f090 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,4 +1,5 @@ import type { Preview } from "@storybook/react"; +import 'bootstrap/dist/css/bootstrap.min.css'; const preview: Preview = { parameters: { diff --git a/package-lock.json b/package-lock.json index 77870b96b..5c888b113 100644 --- a/package-lock.json +++ b/package-lock.json @@ -34,6 +34,7 @@ "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "babel-loader": "^9.1.2", + "bootstrap": "5.1.3", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.8.1", "eslint": "8.22.0", @@ -3655,6 +3656,18 @@ } } }, + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "license": "MIT", + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, "node_modules/@radix-ui/number": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", @@ -9449,6 +9462,20 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, + "node_modules/bootstrap": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", + "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + }, + "peerDependencies": { + "@popperjs/core": "^2.10.2" + } + }, "node_modules/bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", @@ -26167,6 +26194,13 @@ "source-map": "^0.7.3" } }, + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "peer": true + }, "@radix-ui/number": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", @@ -30475,6 +30509,13 @@ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", "dev": true }, + "bootstrap": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", + "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "dev": true, + "requires": {} + }, "bplist-parser": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.2.0.tgz", diff --git a/package.json b/package.json index 5fedda2bf..4a6dfca18 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,8 @@ "typescript": "^5.1.3", "webpack": "^5.85.0", "webpack-cli": "^5.1.3", - "webpack-dev-server": "^4.15.0" + "webpack-dev-server": "^4.15.0", + "bootstrap": "5.1.3" }, "dependencies": { "clsx": "^1.2.1", diff --git a/src/index.tsx b/src/index.tsx index 26d2b1437..4297c6de6 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -2,6 +2,8 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import './app/index.css'; import App from './app/App'; +import 'bootstrap/dist/css/bootstrap.css'; +import 'bootstrap/dist/js/bootstrap.min.js'; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( diff --git a/src/stories/Button.stories.ts b/src/stories/Examples/Button.stories.ts similarity index 100% rename from src/stories/Button.stories.ts rename to src/stories/Examples/Button.stories.ts diff --git a/src/stories/Button.tsx b/src/stories/Examples/Button.tsx similarity index 100% rename from src/stories/Button.tsx rename to src/stories/Examples/Button.tsx diff --git a/src/stories/Header.stories.ts b/src/stories/Examples/Header.stories.ts similarity index 100% rename from src/stories/Header.stories.ts rename to src/stories/Examples/Header.stories.ts diff --git a/src/stories/Header.tsx b/src/stories/Examples/Header.tsx similarity index 100% rename from src/stories/Header.tsx rename to src/stories/Examples/Header.tsx diff --git a/src/stories/Introduction.mdx b/src/stories/Examples/Introduction.mdx similarity index 100% rename from src/stories/Introduction.mdx rename to src/stories/Examples/Introduction.mdx diff --git a/src/stories/Page.stories.ts b/src/stories/Examples/Page.stories.ts similarity index 100% rename from src/stories/Page.stories.ts rename to src/stories/Examples/Page.stories.ts diff --git a/src/stories/Page.tsx b/src/stories/Examples/Page.tsx similarity index 100% rename from src/stories/Page.tsx rename to src/stories/Examples/Page.tsx diff --git a/src/stories/assets/code-brackets.svg b/src/stories/Examples/assets/code-brackets.svg similarity index 100% rename from src/stories/assets/code-brackets.svg rename to src/stories/Examples/assets/code-brackets.svg diff --git a/src/stories/assets/colors.svg b/src/stories/Examples/assets/colors.svg similarity index 100% rename from src/stories/assets/colors.svg rename to src/stories/Examples/assets/colors.svg diff --git a/src/stories/assets/comments.svg b/src/stories/Examples/assets/comments.svg similarity index 100% rename from src/stories/assets/comments.svg rename to src/stories/Examples/assets/comments.svg diff --git a/src/stories/assets/direction.svg b/src/stories/Examples/assets/direction.svg similarity index 100% rename from src/stories/assets/direction.svg rename to src/stories/Examples/assets/direction.svg diff --git a/src/stories/assets/flow.svg b/src/stories/Examples/assets/flow.svg similarity index 100% rename from src/stories/assets/flow.svg rename to src/stories/Examples/assets/flow.svg diff --git a/src/stories/assets/plugin.svg b/src/stories/Examples/assets/plugin.svg similarity index 100% rename from src/stories/assets/plugin.svg rename to src/stories/Examples/assets/plugin.svg diff --git a/src/stories/assets/repo.svg b/src/stories/Examples/assets/repo.svg similarity index 100% rename from src/stories/assets/repo.svg rename to src/stories/Examples/assets/repo.svg diff --git a/src/stories/assets/stackalt.svg b/src/stories/Examples/assets/stackalt.svg similarity index 100% rename from src/stories/assets/stackalt.svg rename to src/stories/Examples/assets/stackalt.svg diff --git a/src/stories/button.module.sass b/src/stories/Examples/button.module.sass similarity index 100% rename from src/stories/button.module.sass rename to src/stories/Examples/button.module.sass diff --git a/src/stories/header.css b/src/stories/Examples/header.css similarity index 100% rename from src/stories/header.css rename to src/stories/Examples/header.css diff --git a/src/stories/page.css b/src/stories/Examples/page.css similarity index 100% rename from src/stories/page.css rename to src/stories/Examples/page.css diff --git a/src/stories/Homework2/Logo.jsx b/src/stories/Homework2/Logo.jsx new file mode 100644 index 000000000..49f77a4dd --- /dev/null +++ b/src/stories/Homework2/Logo.jsx @@ -0,0 +1,9 @@ + +function Logo() { + return ( +
+ +
+ ) +} +export default Logo; \ No newline at end of file diff --git a/src/stories/Homework2/Logo.stories.ts b/src/stories/Homework2/Logo.stories.ts new file mode 100644 index 000000000..4fd7a1373 --- /dev/null +++ b/src/stories/Homework2/Logo.stories.ts @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react'; + +import Logo from './Logo'; + +const meta: Meta = { + title: 'Homework2/Logo', + component: Logo, +}; + +export default meta; +type Story = StoryObj; + +export const WithAnImage: Story = {}; diff --git a/src/stories/Homework2/assets/logo-image.png b/src/stories/Homework2/assets/logo-image.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc41c902b4cbd9e3dc2bc65c386d94ad0c39ccd GIT binary patch literal 25705 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�l#)h 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 b?Th~h1UeQ^VFpst00000NkvXXu0mjf6u4$q literal 0 HcmV?d00001 diff --git a/src/stories/Homework2/assets/logo-image64.png b/src/stories/Homework2/assets/logo-image64.png new file mode 100644 index 0000000000000000000000000000000000000000..0037316ad4aef29b02eb2f66decd999b61819a84 GIT binary patch literal 4926 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 + + + ) +} +export default Header; \ No newline at end of file diff --git a/src/stories/Homework2/headers/Header.stories.ts b/src/stories/Homework2/headers/Header.stories.ts new file mode 100644 index 000000000..6bcd2937e --- /dev/null +++ b/src/stories/Homework2/headers/Header.stories.ts @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Header from './Header'; + +const meta: Meta = { + title: 'Homework2/Header', + component: Header, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + tags: ['autodocs'], + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { }; \ No newline at end of file diff --git a/src/stories/Homework2/layouts/Layout.css b/src/stories/Homework2/layouts/Layout.css new file mode 100644 index 000000000..2e7757075 --- /dev/null +++ b/src/stories/Homework2/layouts/Layout.css @@ -0,0 +1,9 @@ +.layout { + width: 1440px; + height: 1036px; + top: 162px; + left: 80px; + gap: 0px; + border-radius: 20px 20px 0px 0px; + opacity: 0px; +} \ No newline at end of file diff --git a/src/stories/Homework2/layouts/Layout.jsx b/src/stories/Homework2/layouts/Layout.jsx new file mode 100644 index 000000000..08357604b --- /dev/null +++ b/src/stories/Homework2/layouts/Layout.jsx @@ -0,0 +1,13 @@ +import Header from "../headers/Header"; +import "./Layout.css" + +function Layout() { + return ( +
+
+
+
+
+ ) +} +export default Layout; \ No newline at end of file diff --git a/src/stories/Homework2/layouts/Layout.stories.ts b/src/stories/Homework2/layouts/Layout.stories.ts new file mode 100644 index 000000000..bc423f07d --- /dev/null +++ b/src/stories/Homework2/layouts/Layout.stories.ts @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Layout from './Layout'; + + +const meta: Meta = { + title: 'Homework2/Layout', + component: Layout, + // This component will have an automatically generated Autodocs entry: https://storybook.js.org/docs/react/writing-docs/autodocs + tags: ['autodocs'], + parameters: { + // More on how to position stories at: https://storybook.js.org/docs/react/configure/story-layout + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +export const Primary: Story = { }; From a41c38cf7f94aa0d9c1591798b06a4565076f28a Mon Sep 17 00:00:00 2001 From: Yurabox Date: Tue, 24 Sep 2024 23:02:36 +0300 Subject: [PATCH 09/53] =?UTF-8?q?=D0=B2=D1=82=D0=BE=D1=80=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BD=D1=8F=D1=8F=20=D1=80=D0=B0?= =?UTF-8?q?=D0=B1=D0=BE=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/Homework2/Operation.jsx | 61 ++++++++++++++++++ src/stories/Homework2/Operation.stories.ts | 23 +++++++ src/stories/Homework2/Product.jsx | 16 +++++ src/stories/Homework2/Product.stories.ts | 23 +++++++ src/stories/Homework2/TrashProduct.jsx | 12 ++++ src/stories/Homework2/TrashProduct.stories.ts | 16 +++++ .../assets/free-icon-cleaning-9012135.png | Bin 0 -> 19844 bytes .../assets/free-icon-cleaning-9717764.png | Bin 0 -> 13981 bytes .../assets/free-icon-household-7029117.png | Bin 0 -> 8270 bytes src/stories/Homework2/buttons/TrashButton.jsx | 15 +++++ .../Homework2/buttons/TrashButton.stories.ts | 23 +++++++ src/stories/Homework2/modals/Modal.jsx | 35 ++++++++++ src/stories/Homework2/modals/Modal.stories.ts | 21 ++++++ src/stories/Homework2/modals/ModalOverlay.jsx | 11 ++++ .../Homework2/modals/ModalOverlay.stories.ts | 17 +++++ 15 files changed, 273 insertions(+) create mode 100644 src/stories/Homework2/Operation.jsx create mode 100644 src/stories/Homework2/Operation.stories.ts create mode 100644 src/stories/Homework2/Product.jsx create mode 100644 src/stories/Homework2/Product.stories.ts create mode 100644 src/stories/Homework2/TrashProduct.jsx create mode 100644 src/stories/Homework2/TrashProduct.stories.ts create mode 100644 src/stories/Homework2/assets/free-icon-cleaning-9012135.png create mode 100644 src/stories/Homework2/assets/free-icon-cleaning-9717764.png create mode 100644 src/stories/Homework2/assets/free-icon-household-7029117.png create mode 100644 src/stories/Homework2/buttons/TrashButton.jsx create mode 100644 src/stories/Homework2/buttons/TrashButton.stories.ts create mode 100644 src/stories/Homework2/modals/Modal.jsx create mode 100644 src/stories/Homework2/modals/Modal.stories.ts create mode 100644 src/stories/Homework2/modals/ModalOverlay.jsx create mode 100644 src/stories/Homework2/modals/ModalOverlay.stories.ts diff --git a/src/stories/Homework2/Operation.jsx b/src/stories/Homework2/Operation.jsx new file mode 100644 index 000000000..f0638c063 --- /dev/null +++ b/src/stories/Homework2/Operation.jsx @@ -0,0 +1,61 @@ +import TrashButton from "./buttons/TrashButton"; +function Operation({ price, images, category, title, description }) { + + return ( +
+ {images.map((x, i) => ( + ... + ))} + +
+
{title}
+
{category}
+

{description}

+

цена: {price}р

+ +
+
+ ) +} +export default Operation; + +{/*
+
+
+ + + +
+
+
+ ... +
+
First slide label
+

Some representative placeholder content for the first slide.

+
+
+
+ ... +
+
Second slide label
+

Some representative placeholder content for the second slide.

+
+
+
+ ... +
+
Third slide label
+

Some representative placeholder content for the third slide.

+
+
+
+ + +
+
*/} \ No newline at end of file diff --git a/src/stories/Homework2/Operation.stories.ts b/src/stories/Homework2/Operation.stories.ts new file mode 100644 index 000000000..9366003be --- /dev/null +++ b/src/stories/Homework2/Operation.stories.ts @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Operation from './Operation'; + + +const meta: Meta = { + title: 'Homework2/Operation', + component: Operation, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryObj; + +export const PrimaryOperation: Story = { + args: { + images: ['/free-icon-cleaning-9012135.png', '/free-icon-cleaning-9717764.png'], + price: 10000, + title: "Веник", + description: "Веник с длиной ручкой", + category:"товары для дома" + + } +}; diff --git a/src/stories/Homework2/Product.jsx b/src/stories/Homework2/Product.jsx new file mode 100644 index 000000000..836be6b68 --- /dev/null +++ b/src/stories/Homework2/Product.jsx @@ -0,0 +1,16 @@ +import TrashButton from "./buttons/TrashButton"; +function Product({ title, price, description }) { + return ( +
+ ... +
+
{title}
+
{price}
+

{description}

+ +
+
+ ); +} + +export default Product; \ No newline at end of file diff --git a/src/stories/Homework2/Product.stories.ts b/src/stories/Homework2/Product.stories.ts new file mode 100644 index 000000000..f1b4e4ecc --- /dev/null +++ b/src/stories/Homework2/Product.stories.ts @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Product from './Product'; + + +const meta: Meta = { + title: 'Homework2/Product', + component: Product, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +export const PrimaryProduct: Story = { + args: { + title: "Чистящие средства", + price: 5000, + description: "Хорошо очищает любые загрязнения" + } +}; diff --git a/src/stories/Homework2/TrashProduct.jsx b/src/stories/Homework2/TrashProduct.jsx new file mode 100644 index 000000000..ac3db71ec --- /dev/null +++ b/src/stories/Homework2/TrashProduct.jsx @@ -0,0 +1,12 @@ +function TrashProduct() { + return ( +
+ ... +
+ +
+
+ ) + +} +export default TrashProduct; \ No newline at end of file diff --git a/src/stories/Homework2/TrashProduct.stories.ts b/src/stories/Homework2/TrashProduct.stories.ts new file mode 100644 index 000000000..93589fef7 --- /dev/null +++ b/src/stories/Homework2/TrashProduct.stories.ts @@ -0,0 +1,16 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import TrashProduct from './TrashProduct'; + + +const meta: Meta = { + title: 'Homework2/TrashProduct', + component: TrashProduct, + tags: ['autodocs'], parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +export const PrimaryTrashProduct: Story = {}; diff --git a/src/stories/Homework2/assets/free-icon-cleaning-9012135.png b/src/stories/Homework2/assets/free-icon-cleaning-9012135.png new file mode 100644 index 0000000000000000000000000000000000000000..de7edd177366476fbebdbfcebaf761a7adbe1d0f GIT binary patch literal 19844 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 P0KjYLTZKwF(~tiH@6WUy literal 0 HcmV?d00001 diff --git a/src/stories/Homework2/buttons/TrashButton.jsx b/src/stories/Homework2/buttons/TrashButton.jsx new file mode 100644 index 000000000..df9381b74 --- /dev/null +++ b/src/stories/Homework2/buttons/TrashButton.jsx @@ -0,0 +1,15 @@ + + +function TrashButton({ counter }) { + return ( + (counter == 0 ? + : +
+ - + + + +
+ ) + ) +} +export default TrashButton; \ No newline at end of file diff --git a/src/stories/Homework2/buttons/TrashButton.stories.ts b/src/stories/Homework2/buttons/TrashButton.stories.ts new file mode 100644 index 000000000..3c19e52f8 --- /dev/null +++ b/src/stories/Homework2/buttons/TrashButton.stories.ts @@ -0,0 +1,23 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import TrashButton from './TrashButton'; + +const meta: Meta = { + title: 'Homework2/TrashButton', + component: TrashButton, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryObj; + +export const CounterZero: Story = { + args: { + counter: 0 + } +}; + +export const CounterMoreZero: Story = { + args: { + counter: 1 + } +}; \ No newline at end of file diff --git a/src/stories/Homework2/modals/Modal.jsx b/src/stories/Homework2/modals/Modal.jsx new file mode 100644 index 000000000..dc6871237 --- /dev/null +++ b/src/stories/Homework2/modals/Modal.jsx @@ -0,0 +1,35 @@ +import ModalOverlay from "./ModalOverlay"; +function Modal({ visible, children }) { + return ( + <> +
+
+
+
+
+

+
+

+ × +

+
+
{children}
+
+
+
+ + + ) +} +export default Modal; \ No newline at end of file diff --git a/src/stories/Homework2/modals/Modal.stories.ts b/src/stories/Homework2/modals/Modal.stories.ts new file mode 100644 index 000000000..dac2868da --- /dev/null +++ b/src/stories/Homework2/modals/Modal.stories.ts @@ -0,0 +1,21 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Modal from './Modal' + +const meta: Meta = { + title: 'Homework2/Modal', + component: Modal, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +export const PrimaryModal: Story = { + args: { + visible: true, + children: '' + } +}; \ No newline at end of file diff --git a/src/stories/Homework2/modals/ModalOverlay.jsx b/src/stories/Homework2/modals/ModalOverlay.jsx new file mode 100644 index 000000000..30cf488da --- /dev/null +++ b/src/stories/Homework2/modals/ModalOverlay.jsx @@ -0,0 +1,11 @@ +function ModalOverlay({ isOpen, setOpen }) { + return ( +
setOpen(false)} + style={{ opacity: 0.6 }} + >
+ ); +} + +export default ModalOverlay; \ No newline at end of file diff --git a/src/stories/Homework2/modals/ModalOverlay.stories.ts b/src/stories/Homework2/modals/ModalOverlay.stories.ts new file mode 100644 index 000000000..4d1026c20 --- /dev/null +++ b/src/stories/Homework2/modals/ModalOverlay.stories.ts @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import ModalOverlay from './ModalOverlay'; + + +const meta: Meta = { + title: 'Homework2/ModalOverlay', + component: ModalOverlay, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +export const PrimaryModalOverlay: Story = {}; From 35240431a4c4ced270947f36a9dc612a0514c58b Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 25 Sep 2024 00:02:13 +0300 Subject: [PATCH 10/53] 1 --- .github/workflows/main.yml | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 19ffdec66..0f5ba90b0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,28 +34,28 @@ jobs: - name: Run tests and linter run: npm run lint && npm test - # # Собираем приложение - # - name: Build Application - # run: npm run build - - # # Публикуем приложение на Github Pages - # - name: Deploy to Github Pages - # uses: JamesIves/github-pages-deploy-action@4.2.1 - # with: - # branch: gh-pages - # folder: dist - - # Собираем Storybook - - name: Build Storybook - run: npm run build-storybook - - # Публикуем Storybook на Github Pages - - name: Deploy Storybook to Github Pages + # Собираем приложение + - name: Build Application + run: npm run build + + # Публикуем приложение на Github Pages + - name: Deploy to Github Pages uses: JamesIves/github-pages-deploy-action@4.2.1 with: - branch: gh-pages - folder: storybook-static - commit-message: "Automatically publish Storybook" + branch: gh-pages + folder: dist + + # # Собираем Storybook + # - name: Build Storybook + # run: npm run build-storybook + # + # # Публикуем Storybook на Github Pages + # - name: Deploy Storybook to Github Pages + # uses: JamesIves/github-pages-deploy-action@4.2.1 + # with: + # branch: gh-pages + # folder: storybook-static + # commit-message: "Automatically publish Storybook" # Останавливаем выполнение строго при неудачных тестах - name: Fail on failed tests From e940a8172bfaba9c841b7645f55a539c981f205c Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 25 Sep 2024 21:00:38 +0300 Subject: [PATCH 11/53] =?UTF-8?q?=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BD?= =?UTF-8?q?=D1=8F=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=202?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 27 ++++++++++++++++++++++++++- package.json | 7 ++++--- 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5c888b113..faf527060 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,6 +18,7 @@ "@babel/preset-env": "^7.22.4", "@babel/preset-react": "^7.22.3", "@babel/preset-typescript": "^7.21.5", + "@faker-js/faker": "^9.0.2", "@storybook/addon-essentials": "^7.6.17", "@storybook/addon-interactions": "^7.6.17", "@storybook/addon-links": "^7.6.17", @@ -62,7 +63,7 @@ "ts-jest": "^29.1.0", "typescript": "^5.1.3", "webpack": "^5.85.0", - "webpack-cli": "^5.1.3", + "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.0" } }, @@ -2578,6 +2579,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@faker-js/faker": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.2.tgz", + "integrity": "sha512-nI/FP30ZGXb+UaR7yXawVTH40NVKXPIx0tA3GKjkKLjorqBoMAeq4iSEacl8mJmpVhOCDa0vYHwYDmOOcFMrYw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/fakerjs" + } + ], + "license": "MIT", + "engines": { + "node": ">=18.0.0", + "npm": ">=9.0.0" + } + }, "node_modules/@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", @@ -23000,6 +23018,7 @@ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-5.1.4.tgz", "integrity": "sha512-pIDJHIEI9LR0yxHXQ+Qh95k2EvXpWzZ5l+d+jIo+RdSm9MiHfzazIxwwni/p7+x4eJZuvG1AJwgC4TNQ7NRgsg==", "dev": true, + "license": "MIT", "dependencies": { "@discoveryjs/json-ext": "^0.5.0", "@webpack-cli/configtest": "^2.1.1", @@ -25390,6 +25409,12 @@ } } }, + "@faker-js/faker": { + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/@faker-js/faker/-/faker-9.0.2.tgz", + "integrity": "sha512-nI/FP30ZGXb+UaR7yXawVTH40NVKXPIx0tA3GKjkKLjorqBoMAeq4iSEacl8mJmpVhOCDa0vYHwYDmOOcFMrYw==", + "dev": true + }, "@fal-works/esbuild-plugin-global-externals": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/@fal-works/esbuild-plugin-global-externals/-/esbuild-plugin-global-externals-2.1.2.tgz", diff --git a/package.json b/package.json index 4a6dfca18..d67426d78 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "@babel/preset-env": "^7.22.4", "@babel/preset-react": "^7.22.3", "@babel/preset-typescript": "^7.21.5", + "@faker-js/faker": "^9.0.2", "@storybook/addon-essentials": "^7.6.17", "@storybook/addon-interactions": "^7.6.17", "@storybook/addon-links": "^7.6.17", @@ -37,6 +38,7 @@ "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "babel-loader": "^9.1.2", + "bootstrap": "5.1.3", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.8.1", "eslint": "8.22.0", @@ -64,9 +66,8 @@ "ts-jest": "^29.1.0", "typescript": "^5.1.3", "webpack": "^5.85.0", - "webpack-cli": "^5.1.3", - "webpack-dev-server": "^4.15.0", - "bootstrap": "5.1.3" + "webpack-cli": "^5.1.4", + "webpack-dev-server": "^4.15.0" }, "dependencies": { "clsx": "^1.2.1", From 901fccbbc37fab17bb2f2ddee16973555679eaa4 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 25 Sep 2024 21:04:55 +0300 Subject: [PATCH 12/53] storybook --- .github/workflows/main.yml | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0f5ba90b0..aa6818789 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,28 +34,28 @@ jobs: - name: Run tests and linter run: npm run lint && npm test - # Собираем приложение - - name: Build Application - run: npm run build - - # Публикуем приложение на Github Pages - - name: Deploy to Github Pages + # # Собираем приложение + # - name: Build Application + # run: npm run build + + # # Публикуем приложение на Github Pages + # - name: Deploy to Github Pages + # uses: JamesIves/github-pages-deploy-action@4.2.1 + # with: + # branch: gh-pages + # folder: dist + + # Собираем Storybook + - name: Build Storybook + run: npm run build-storybook + + # Публикуем Storybook на Github Pages + - name: Deploy Storybook to Github Pages uses: JamesIves/github-pages-deploy-action@4.2.1 with: - branch: gh-pages - folder: dist - - # # Собираем Storybook - # - name: Build Storybook - # run: npm run build-storybook - # - # # Публикуем Storybook на Github Pages - # - name: Deploy Storybook to Github Pages - # uses: JamesIves/github-pages-deploy-action@4.2.1 - # with: - # branch: gh-pages - # folder: storybook-static - # commit-message: "Automatically publish Storybook" + branch: gh-pages + folder: storybook-static + commit-message: "Automatically publish Storybook" # Останавливаем выполнение строго при неудачных тестах - name: Fail on failed tests From 30dda289ce375ea301993b483e07b83afdfbfb55 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 25 Sep 2024 21:35:46 +0300 Subject: [PATCH 13/53] =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index 7bef3af5e..6e03e9783 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -8,7 +8,7 @@ function App() {
logo
    -
  • В компании по импортозамещению необходимо переписать проект с Blazor на React
  • +
  • В компании по импортозамещению необходимо переписать проект с Blazor на React.
  • Владею Blazor, AspNet, net6
  • FullStack разработчик, разрабатываю информационно-аналитическую систему.
  • 2022-2023гг проходил обучение по курсу AspNet core Otus.
  • From d6ae984aa492e1bb3feb950b11dacdb8f9b8d87f Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 25 Sep 2024 21:49:02 +0300 Subject: [PATCH 14/53] . --- src/app/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index 6e03e9783..8d3ebca4c 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -10,7 +10,7 @@ function App() {
    • В компании по импортозамещению необходимо переписать проект с Blazor на React.
    • Владею Blazor, AspNet, net6
    • -
    • FullStack разработчик, разрабатываю информационно-аналитическую систему.
    • +
    • FullStack разработчик, разрабатываю информационно-аналитическую систему.
    • 2022-2023гг проходил обучение по курсу AspNet core Otus.
From d9a5c5d977663db0d6d9c20a89183eb989ec9887 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Thu, 26 Sep 2024 23:10:19 +0300 Subject: [PATCH 15/53] =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BB=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20?= =?UTF-8?q?=D0=BF=D0=BE=202=D0=B9=20=D0=B4=D0=BE=D0=BC=D0=B0=D1=88=D0=BD?= =?UTF-8?q?=D0=B5=D0=B9=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/Homework2/{ => Logo}/Logo.jsx | 0 .../Homework2/{ => Logo}/Logo.stories.ts | 0 src/stories/Homework2/Operation.jsx | 61 ------------------- src/stories/Homework2/Operation/Operation.jsx | 21 +++++++ .../{ => Operation}/Operation.stories.ts | 0 .../Homework2/{ => Product}/Product.jsx | 3 +- .../{ => Product}/Product.stories.ts | 0 .../Homework2/TrashButton/TrashButton.jsx | 18 ++++++ .../TrashButton.stories.ts | 0 .../{ => TrashProduct}/TrashProduct.jsx | 0 .../TrashProduct.stories.ts | 0 src/stories/Homework2/buttons/TrashButton.jsx | 15 ----- src/stories/Homework2/headers/Header.jsx | 4 +- src/stories/Homework2/modals/Modal.css | 7 +++ src/stories/Homework2/modals/Modal.jsx | 11 +--- src/stories/Homework2/modals/ModalOverlay.css | 3 + src/stories/Homework2/modals/ModalOverlay.jsx | 9 +-- 17 files changed, 60 insertions(+), 92 deletions(-) rename src/stories/Homework2/{ => Logo}/Logo.jsx (100%) rename src/stories/Homework2/{ => Logo}/Logo.stories.ts (100%) delete mode 100644 src/stories/Homework2/Operation.jsx create mode 100644 src/stories/Homework2/Operation/Operation.jsx rename src/stories/Homework2/{ => Operation}/Operation.stories.ts (100%) rename src/stories/Homework2/{ => Product}/Product.jsx (91%) rename src/stories/Homework2/{ => Product}/Product.stories.ts (100%) create mode 100644 src/stories/Homework2/TrashButton/TrashButton.jsx rename src/stories/Homework2/{buttons => TrashButton}/TrashButton.stories.ts (100%) rename src/stories/Homework2/{ => TrashProduct}/TrashProduct.jsx (100%) rename src/stories/Homework2/{ => TrashProduct}/TrashProduct.stories.ts (100%) delete mode 100644 src/stories/Homework2/buttons/TrashButton.jsx create mode 100644 src/stories/Homework2/modals/Modal.css create mode 100644 src/stories/Homework2/modals/ModalOverlay.css diff --git a/src/stories/Homework2/Logo.jsx b/src/stories/Homework2/Logo/Logo.jsx similarity index 100% rename from src/stories/Homework2/Logo.jsx rename to src/stories/Homework2/Logo/Logo.jsx diff --git a/src/stories/Homework2/Logo.stories.ts b/src/stories/Homework2/Logo/Logo.stories.ts similarity index 100% rename from src/stories/Homework2/Logo.stories.ts rename to src/stories/Homework2/Logo/Logo.stories.ts diff --git a/src/stories/Homework2/Operation.jsx b/src/stories/Homework2/Operation.jsx deleted file mode 100644 index f0638c063..000000000 --- a/src/stories/Homework2/Operation.jsx +++ /dev/null @@ -1,61 +0,0 @@ -import TrashButton from "./buttons/TrashButton"; -function Operation({ price, images, category, title, description }) { - - return ( -
- {images.map((x, i) => ( - ... - ))} - -
-
{title}
-
{category}
-

{description}

-

цена: {price}р

- -
-
- ) -} -export default Operation; - -{/*
-
-
- - - -
-
-
- ... -
-
First slide label
-

Some representative placeholder content for the first slide.

-
-
-
- ... -
-
Second slide label
-

Some representative placeholder content for the second slide.

-
-
-
- ... -
-
Third slide label
-

Some representative placeholder content for the third slide.

-
-
-
- - -
-
*/} \ No newline at end of file diff --git a/src/stories/Homework2/Operation/Operation.jsx b/src/stories/Homework2/Operation/Operation.jsx new file mode 100644 index 000000000..4e1cafc80 --- /dev/null +++ b/src/stories/Homework2/Operation/Operation.jsx @@ -0,0 +1,21 @@ +import TrashButton from "../TrashButton/TrashButton"; + +function Operation({ price, images, category, title, description }) { + + return ( +
+ {images.map((x, i) => ( + ... + ))} + +
+
{title}
+
{category}
+

{description}

+

цена: {price}р

+ +
+
+ ) +} +export default Operation; \ No newline at end of file diff --git a/src/stories/Homework2/Operation.stories.ts b/src/stories/Homework2/Operation/Operation.stories.ts similarity index 100% rename from src/stories/Homework2/Operation.stories.ts rename to src/stories/Homework2/Operation/Operation.stories.ts diff --git a/src/stories/Homework2/Product.jsx b/src/stories/Homework2/Product/Product.jsx similarity index 91% rename from src/stories/Homework2/Product.jsx rename to src/stories/Homework2/Product/Product.jsx index 836be6b68..ac73c0cba 100644 --- a/src/stories/Homework2/Product.jsx +++ b/src/stories/Homework2/Product/Product.jsx @@ -1,4 +1,5 @@ -import TrashButton from "./buttons/TrashButton"; +import TrashButton from "../TrashButton/TrashButton"; + function Product({ title, price, description }) { return (
diff --git a/src/stories/Homework2/Product.stories.ts b/src/stories/Homework2/Product/Product.stories.ts similarity index 100% rename from src/stories/Homework2/Product.stories.ts rename to src/stories/Homework2/Product/Product.stories.ts diff --git a/src/stories/Homework2/TrashButton/TrashButton.jsx b/src/stories/Homework2/TrashButton/TrashButton.jsx new file mode 100644 index 000000000..83486895a --- /dev/null +++ b/src/stories/Homework2/TrashButton/TrashButton.jsx @@ -0,0 +1,18 @@ + +import PropTypes from 'prop-types'; +function TrashButton({ counter }) { + return ( + (counter == 0 ? + : +
+ - + + + +
+ ) + ); +} +TrashButton.propTypes = { + counter: PropTypes.number +}; +export default TrashButton; \ No newline at end of file diff --git a/src/stories/Homework2/buttons/TrashButton.stories.ts b/src/stories/Homework2/TrashButton/TrashButton.stories.ts similarity index 100% rename from src/stories/Homework2/buttons/TrashButton.stories.ts rename to src/stories/Homework2/TrashButton/TrashButton.stories.ts diff --git a/src/stories/Homework2/TrashProduct.jsx b/src/stories/Homework2/TrashProduct/TrashProduct.jsx similarity index 100% rename from src/stories/Homework2/TrashProduct.jsx rename to src/stories/Homework2/TrashProduct/TrashProduct.jsx diff --git a/src/stories/Homework2/TrashProduct.stories.ts b/src/stories/Homework2/TrashProduct/TrashProduct.stories.ts similarity index 100% rename from src/stories/Homework2/TrashProduct.stories.ts rename to src/stories/Homework2/TrashProduct/TrashProduct.stories.ts diff --git a/src/stories/Homework2/buttons/TrashButton.jsx b/src/stories/Homework2/buttons/TrashButton.jsx deleted file mode 100644 index df9381b74..000000000 --- a/src/stories/Homework2/buttons/TrashButton.jsx +++ /dev/null @@ -1,15 +0,0 @@ - - -function TrashButton({ counter }) { - return ( - (counter == 0 ? - : -
- - - - + -
- ) - ) -} -export default TrashButton; \ No newline at end of file diff --git a/src/stories/Homework2/headers/Header.jsx b/src/stories/Homework2/headers/Header.jsx index 288c7491f..3ff3aca51 100644 --- a/src/stories/Homework2/headers/Header.jsx +++ b/src/stories/Homework2/headers/Header.jsx @@ -1,9 +1,9 @@ -import Logo from "../Logo"; +import Logo from "../Logo/Logo"; function Header() { return (
- +
) } diff --git a/src/stories/Homework2/modals/Modal.css b/src/stories/Homework2/modals/Modal.css new file mode 100644 index 000000000..0cfeac6ca --- /dev/null +++ b/src/stories/Homework2/modals/Modal.css @@ -0,0 +1,7 @@ +.modal-container { + display: block; + width: 39%; + height: 88%; + left: 30%; + overflow: hidden; +} \ No newline at end of file diff --git a/src/stories/Homework2/modals/Modal.jsx b/src/stories/Homework2/modals/Modal.jsx index dc6871237..e4a798a20 100644 --- a/src/stories/Homework2/modals/Modal.jsx +++ b/src/stories/Homework2/modals/Modal.jsx @@ -1,19 +1,12 @@ import ModalOverlay from "./ModalOverlay"; +import './Modal.css' function Modal({ visible, children }) { return ( <>
+ role="dialog">
diff --git a/src/stories/Homework2/modals/ModalOverlay.css b/src/stories/Homework2/modals/ModalOverlay.css new file mode 100644 index 000000000..9f5127f86 --- /dev/null +++ b/src/stories/Homework2/modals/ModalOverlay.css @@ -0,0 +1,3 @@ +.modal-overlay { + opacity: 0.6 +} \ No newline at end of file diff --git a/src/stories/Homework2/modals/ModalOverlay.jsx b/src/stories/Homework2/modals/ModalOverlay.jsx index 30cf488da..3a148481b 100644 --- a/src/stories/Homework2/modals/ModalOverlay.jsx +++ b/src/stories/Homework2/modals/ModalOverlay.jsx @@ -1,10 +1,11 @@ +import './ModalOverlay.css' function ModalOverlay({ isOpen, setOpen }) { return (
setOpen(false)} - style={{ opacity: 0.6 }} - >
+ className="modal-backdrop fade show modal-overlay" + onClick={() => setOpen(false)}> + +
); } From 43b41883212186084212a8b9a7604bafd2535753 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Thu, 26 Sep 2024 23:32:13 +0300 Subject: [PATCH 16/53] =?UTF-8?q?=D0=BD=D0=B5=20=D0=BE=D1=82=D0=BE=D0=B1?= =?UTF-8?q?=D1=80=D0=B0=D0=B6=D0=B0=D0=BB=D0=BE=D1=81=D1=8C=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BE=D0=BA=D0=BD?= =?UTF-8?q?=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/Homework2/modals/Modal.jsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/stories/Homework2/modals/Modal.jsx b/src/stories/Homework2/modals/Modal.jsx index e4a798a20..327421f2c 100644 --- a/src/stories/Homework2/modals/Modal.jsx +++ b/src/stories/Homework2/modals/Modal.jsx @@ -1,10 +1,11 @@ +import PropTypes from 'prop-types' import ModalOverlay from "./ModalOverlay"; import './Modal.css' function Modal({ visible, children }) { return ( <>
@@ -25,4 +26,8 @@ function Modal({ visible, children }) { ) } +Modal.propTypes = { + visible: PropTypes.bool, + children: PropTypes.element +}; export default Modal; \ No newline at end of file From 3ae424ab28f56db0ebee5edd467712615b4d8cfd Mon Sep 17 00:00:00 2001 From: Yurabox Date: Tue, 1 Oct 2024 22:05:06 +0300 Subject: [PATCH 17/53] =?UTF-8?q?=D1=82=D0=B8=D0=BF=D0=B8=D0=B7=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20=D0=BF=D1=80=D0=BE=D0=BF=D1=81?= =?UTF-8?q?=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/Homework2/Interfaces/IModal.ts | 5 +++++ src/stories/Homework2/Interfaces/IOperation.ts | 7 +++++++ src/stories/Homework2/Interfaces/IProduct.ts | 5 +++++ src/stories/Homework2/Interfaces/ITrashButton.ts | 3 +++ .../Homework2/Operation/Operation.stories.ts | 2 +- .../Operation/{Operation.jsx => Operation.tsx} | 6 ++++-- .../Homework2/Product/{Product.jsx => Product.tsx} | 8 +++++--- .../{TrashButton.jsx => TrashButton.tsx} | 9 ++++----- .../{TrashProduct.jsx => TrashProduct.tsx} | 1 + .../Homework2/layouts/{Layout.jsx => Layout.tsx} | 1 + .../Homework2/modals/{Modal.jsx => Modal.tsx} | 14 ++++++-------- src/stories/Homework2/modals/ModalOverlay.jsx | 12 ------------ src/stories/Homework2/modals/ModalOverlay.tsx | 11 +++++++++++ 13 files changed, 53 insertions(+), 31 deletions(-) create mode 100644 src/stories/Homework2/Interfaces/IModal.ts create mode 100644 src/stories/Homework2/Interfaces/IOperation.ts create mode 100644 src/stories/Homework2/Interfaces/IProduct.ts create mode 100644 src/stories/Homework2/Interfaces/ITrashButton.ts rename src/stories/Homework2/Operation/{Operation.jsx => Operation.tsx} (84%) rename src/stories/Homework2/Product/{Product.jsx => Product.tsx} (75%) rename src/stories/Homework2/TrashButton/{TrashButton.jsx => TrashButton.tsx} (78%) rename src/stories/Homework2/TrashProduct/{TrashProduct.jsx => TrashProduct.tsx} (94%) rename src/stories/Homework2/layouts/{Layout.jsx => Layout.tsx} (90%) rename src/stories/Homework2/modals/{Modal.jsx => Modal.tsx} (82%) delete mode 100644 src/stories/Homework2/modals/ModalOverlay.jsx create mode 100644 src/stories/Homework2/modals/ModalOverlay.tsx diff --git a/src/stories/Homework2/Interfaces/IModal.ts b/src/stories/Homework2/Interfaces/IModal.ts new file mode 100644 index 000000000..486b4236c --- /dev/null +++ b/src/stories/Homework2/Interfaces/IModal.ts @@ -0,0 +1,5 @@ +import { ReactElement, ReactNode } from "react" +export default interface IModal extends ReactElement { + visible: boolean, + children: ReactNode +} \ No newline at end of file diff --git a/src/stories/Homework2/Interfaces/IOperation.ts b/src/stories/Homework2/Interfaces/IOperation.ts new file mode 100644 index 000000000..539aa80ac --- /dev/null +++ b/src/stories/Homework2/Interfaces/IOperation.ts @@ -0,0 +1,7 @@ +export default interface IOperation { + price: number, + images: string[], + category: string, + title: string, + description: string +} \ No newline at end of file diff --git a/src/stories/Homework2/Interfaces/IProduct.ts b/src/stories/Homework2/Interfaces/IProduct.ts new file mode 100644 index 000000000..bee2080bc --- /dev/null +++ b/src/stories/Homework2/Interfaces/IProduct.ts @@ -0,0 +1,5 @@ +export default interface IProduct { + title: string, + price: number, + description: string +} \ No newline at end of file diff --git a/src/stories/Homework2/Interfaces/ITrashButton.ts b/src/stories/Homework2/Interfaces/ITrashButton.ts new file mode 100644 index 000000000..f449ce38b --- /dev/null +++ b/src/stories/Homework2/Interfaces/ITrashButton.ts @@ -0,0 +1,3 @@ +export default interface nITrashButton { + counter: number +} \ No newline at end of file diff --git a/src/stories/Homework2/Operation/Operation.stories.ts b/src/stories/Homework2/Operation/Operation.stories.ts index 9366003be..d94d79c4e 100644 --- a/src/stories/Homework2/Operation/Operation.stories.ts +++ b/src/stories/Homework2/Operation/Operation.stories.ts @@ -17,7 +17,7 @@ export const PrimaryOperation: Story = { price: 10000, title: "Веник", description: "Веник с длиной ручкой", - category:"товары для дома" + category: "товары для дома" } }; diff --git a/src/stories/Homework2/Operation/Operation.jsx b/src/stories/Homework2/Operation/Operation.tsx similarity index 84% rename from src/stories/Homework2/Operation/Operation.jsx rename to src/stories/Homework2/Operation/Operation.tsx index 4e1cafc80..c43b2e981 100644 --- a/src/stories/Homework2/Operation/Operation.jsx +++ b/src/stories/Homework2/Operation/Operation.tsx @@ -1,10 +1,12 @@ +import React from "react"; import TrashButton from "../TrashButton/TrashButton"; +import IOperation from "../Interfaces/IOperation"; -function Operation({ price, images, category, title, description }) { +function Operation({ price, images, category, title, description }: IOperation) { return (
- {images.map((x, i) => ( + {images?.map((x, i) => ( ... ))} diff --git a/src/stories/Homework2/Product/Product.jsx b/src/stories/Homework2/Product/Product.tsx similarity index 75% rename from src/stories/Homework2/Product/Product.jsx rename to src/stories/Homework2/Product/Product.tsx index ac73c0cba..d40cefca0 100644 --- a/src/stories/Homework2/Product/Product.jsx +++ b/src/stories/Homework2/Product/Product.tsx @@ -1,10 +1,12 @@ +import IProduct from "../Interfaces/IProduct"; import TrashButton from "../TrashButton/TrashButton"; +import React from "react"; -function Product({ title, price, description }) { +function Product({ title, price, description }: IProduct) { return ( -
+
... -
+
{title}
{price}

{description}

diff --git a/src/stories/Homework2/TrashButton/TrashButton.jsx b/src/stories/Homework2/TrashButton/TrashButton.tsx similarity index 78% rename from src/stories/Homework2/TrashButton/TrashButton.jsx rename to src/stories/Homework2/TrashButton/TrashButton.tsx index 83486895a..3174491f3 100644 --- a/src/stories/Homework2/TrashButton/TrashButton.jsx +++ b/src/stories/Homework2/TrashButton/TrashButton.tsx @@ -1,6 +1,8 @@ - +import React from 'react'; import PropTypes from 'prop-types'; -function TrashButton({ counter }) { +import ITrashButton from '../Interfaces/ITrashButton'; + +function TrashButton({ counter }: ITrashButton) { return ( (counter == 0 ? : @@ -12,7 +14,4 @@ function TrashButton({ counter }) { ) ); } -TrashButton.propTypes = { - counter: PropTypes.number -}; export default TrashButton; \ No newline at end of file diff --git a/src/stories/Homework2/TrashProduct/TrashProduct.jsx b/src/stories/Homework2/TrashProduct/TrashProduct.tsx similarity index 94% rename from src/stories/Homework2/TrashProduct/TrashProduct.jsx rename to src/stories/Homework2/TrashProduct/TrashProduct.tsx index ac3db71ec..d2e247a10 100644 --- a/src/stories/Homework2/TrashProduct/TrashProduct.jsx +++ b/src/stories/Homework2/TrashProduct/TrashProduct.tsx @@ -1,3 +1,4 @@ +import React from "react"; function TrashProduct() { return (
diff --git a/src/stories/Homework2/layouts/Layout.jsx b/src/stories/Homework2/layouts/Layout.tsx similarity index 90% rename from src/stories/Homework2/layouts/Layout.jsx rename to src/stories/Homework2/layouts/Layout.tsx index 08357604b..c8b975d26 100644 --- a/src/stories/Homework2/layouts/Layout.jsx +++ b/src/stories/Homework2/layouts/Layout.tsx @@ -1,3 +1,4 @@ +import React from "react"; import Header from "../headers/Header"; import "./Layout.css" diff --git a/src/stories/Homework2/modals/Modal.jsx b/src/stories/Homework2/modals/Modal.tsx similarity index 82% rename from src/stories/Homework2/modals/Modal.jsx rename to src/stories/Homework2/modals/Modal.tsx index 327421f2c..7098d3152 100644 --- a/src/stories/Homework2/modals/Modal.jsx +++ b/src/stories/Homework2/modals/Modal.tsx @@ -1,12 +1,13 @@ -import PropTypes from 'prop-types' +import React from 'react'; import ModalOverlay from "./ModalOverlay"; -import './Modal.css' -function Modal({ visible, children }) { +import './Modal.css'; +import IModal from '../Interfaces/IModal'; + +function Modal({ visible, children }: IModal) { return ( <>
@@ -26,8 +27,5 @@ function Modal({ visible, children }) { ) } -Modal.propTypes = { - visible: PropTypes.bool, - children: PropTypes.element -}; + export default Modal; \ No newline at end of file diff --git a/src/stories/Homework2/modals/ModalOverlay.jsx b/src/stories/Homework2/modals/ModalOverlay.jsx deleted file mode 100644 index 3a148481b..000000000 --- a/src/stories/Homework2/modals/ModalOverlay.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import './ModalOverlay.css' -function ModalOverlay({ isOpen, setOpen }) { - return ( -
setOpen(false)}> - -
- ); -} - -export default ModalOverlay; \ No newline at end of file diff --git a/src/stories/Homework2/modals/ModalOverlay.tsx b/src/stories/Homework2/modals/ModalOverlay.tsx new file mode 100644 index 000000000..983a3b2d2 --- /dev/null +++ b/src/stories/Homework2/modals/ModalOverlay.tsx @@ -0,0 +1,11 @@ +import './ModalOverlay.css' +import React from 'react'; +function ModalOverlay() { + return ( +
+
+ ); +} + +export default ModalOverlay; \ No newline at end of file From 9a1042084d39ef04bcce2f106d97af6aa2bcaac8 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Tue, 1 Oct 2024 22:08:34 +0300 Subject: [PATCH 18/53] =?UTF-8?q?=D1=82=D0=B8=D0=BF=D0=B8=D0=B7=D0=B8?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=B0=D0=BB=20=D0=BF=D1=80=D0=BE=D0=BF=D1=81?= =?UTF-8?q?=D1=8B=20=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=B8=D0=BB=20?= =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/stories/Homework2/Operation/Operation.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stories/Homework2/Operation/Operation.tsx b/src/stories/Homework2/Operation/Operation.tsx index c43b2e981..ee38cc10d 100644 --- a/src/stories/Homework2/Operation/Operation.tsx +++ b/src/stories/Homework2/Operation/Operation.tsx @@ -7,7 +7,7 @@ function Operation({ price, images, category, title, description }: IOperation) return (
{images?.map((x, i) => ( - ... + ... ))}
From b8bb3a8c57ead823589851b303a74ae7efa9333c Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 2 Oct 2024 23:13:48 +0300 Subject: [PATCH 19/53] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B8=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=D0=B2=D0=B0=D0=BB=20=D0=BA=D0=BE=D0=BC=D0=BF?= =?UTF-8?q?=D0=BE=D0=BD=D0=B5=D0=BD=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FullProduct.stories.ts} | 10 +++++----- .../Operation.tsx => FullProduct/FullProduct.tsx} | 6 +++--- .../Interfaces/{IOperation.ts => IFullProduct.ts} | 2 +- src/stories/Homework2/Interfaces/IProduct.ts | 5 ----- src/stories/Homework2/Interfaces/IShortProduct.ts | 6 ++++++ .../ShortProduct.stories.ts} | 11 ++++++----- .../Product.tsx => ShortProduct/ShortProduct.tsx} | 9 +++++---- .../modals/{Modal.stories.ts => Modal.stories.tsx} | 4 ++-- 8 files changed, 28 insertions(+), 25 deletions(-) rename src/stories/Homework2/{Operation/Operation.stories.ts => FullProduct/FullProduct.stories.ts} (68%) rename src/stories/Homework2/{Operation/Operation.tsx => FullProduct/FullProduct.tsx} (81%) rename src/stories/Homework2/Interfaces/{IOperation.ts => IFullProduct.ts} (72%) delete mode 100644 src/stories/Homework2/Interfaces/IProduct.ts create mode 100644 src/stories/Homework2/Interfaces/IShortProduct.ts rename src/stories/Homework2/{Product/Product.stories.ts => ShortProduct/ShortProduct.stories.ts} (63%) rename src/stories/Homework2/{Product/Product.tsx => ShortProduct/ShortProduct.tsx} (65%) rename src/stories/Homework2/modals/{Modal.stories.ts => Modal.stories.tsx} (87%) diff --git a/src/stories/Homework2/Operation/Operation.stories.ts b/src/stories/Homework2/FullProduct/FullProduct.stories.ts similarity index 68% rename from src/stories/Homework2/Operation/Operation.stories.ts rename to src/stories/Homework2/FullProduct/FullProduct.stories.ts index d94d79c4e..b4199dece 100644 --- a/src/stories/Homework2/Operation/Operation.stories.ts +++ b/src/stories/Homework2/FullProduct/FullProduct.stories.ts @@ -1,17 +1,17 @@ import type { Meta, StoryObj } from '@storybook/react'; -import Operation from './Operation'; +import FullProduct from './FullProduct'; -const meta: Meta = { - title: 'Homework2/Operation', - component: Operation, +const meta: Meta = { + title: 'Homework2/FullProduct', + component: FullProduct, tags: ['autodocs'] }; export default meta; type Story = StoryObj; -export const PrimaryOperation: Story = { +export const PrimaryFullProduct: Story = { args: { images: ['/free-icon-cleaning-9012135.png', '/free-icon-cleaning-9717764.png'], price: 10000, diff --git a/src/stories/Homework2/Operation/Operation.tsx b/src/stories/Homework2/FullProduct/FullProduct.tsx similarity index 81% rename from src/stories/Homework2/Operation/Operation.tsx rename to src/stories/Homework2/FullProduct/FullProduct.tsx index ee38cc10d..17c34d061 100644 --- a/src/stories/Homework2/Operation/Operation.tsx +++ b/src/stories/Homework2/FullProduct/FullProduct.tsx @@ -1,8 +1,8 @@ import React from "react"; import TrashButton from "../TrashButton/TrashButton"; -import IOperation from "../Interfaces/IOperation"; +import IFullProduct from "../Interfaces/IFullProduct"; -function Operation({ price, images, category, title, description }: IOperation) { +function FullProduct({ price, images, category, title, description }: IFullProduct) { return (
@@ -20,4 +20,4 @@ function Operation({ price, images, category, title, description }: IOperation)
) } -export default Operation; \ No newline at end of file +export default FullProduct; \ No newline at end of file diff --git a/src/stories/Homework2/Interfaces/IOperation.ts b/src/stories/Homework2/Interfaces/IFullProduct.ts similarity index 72% rename from src/stories/Homework2/Interfaces/IOperation.ts rename to src/stories/Homework2/Interfaces/IFullProduct.ts index 539aa80ac..58f748c2b 100644 --- a/src/stories/Homework2/Interfaces/IOperation.ts +++ b/src/stories/Homework2/Interfaces/IFullProduct.ts @@ -1,4 +1,4 @@ -export default interface IOperation { +export default interface IFullProduct { price: number, images: string[], category: string, diff --git a/src/stories/Homework2/Interfaces/IProduct.ts b/src/stories/Homework2/Interfaces/IProduct.ts deleted file mode 100644 index bee2080bc..000000000 --- a/src/stories/Homework2/Interfaces/IProduct.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default interface IProduct { - title: string, - price: number, - description: string -} \ No newline at end of file diff --git a/src/stories/Homework2/Interfaces/IShortProduct.ts b/src/stories/Homework2/Interfaces/IShortProduct.ts new file mode 100644 index 000000000..99c745f21 --- /dev/null +++ b/src/stories/Homework2/Interfaces/IShortProduct.ts @@ -0,0 +1,6 @@ +export default interface IShortProduct { + title: string, + price: number, + description: string, + image: string +} \ No newline at end of file diff --git a/src/stories/Homework2/Product/Product.stories.ts b/src/stories/Homework2/ShortProduct/ShortProduct.stories.ts similarity index 63% rename from src/stories/Homework2/Product/Product.stories.ts rename to src/stories/Homework2/ShortProduct/ShortProduct.stories.ts index f1b4e4ecc..7056387e5 100644 --- a/src/stories/Homework2/Product/Product.stories.ts +++ b/src/stories/Homework2/ShortProduct/ShortProduct.stories.ts @@ -1,10 +1,10 @@ import type { Meta, StoryObj } from '@storybook/react'; -import Product from './Product'; +import ShortProduct from './ShortProduct'; -const meta: Meta = { - title: 'Homework2/Product', - component: Product, +const meta: Meta = { + title: 'Homework2/ShortProduct', + component: ShortProduct, tags: ['autodocs'], parameters: { layout: 'fullscreen', @@ -18,6 +18,7 @@ export const PrimaryProduct: Story = { args: { title: "Чистящие средства", price: 5000, - description: "Хорошо очищает любые загрязнения" + description: "Хорошо очищает любые загрязнения", + image: "/free-icon-household-7029117.png" } }; diff --git a/src/stories/Homework2/Product/Product.tsx b/src/stories/Homework2/ShortProduct/ShortProduct.tsx similarity index 65% rename from src/stories/Homework2/Product/Product.tsx rename to src/stories/Homework2/ShortProduct/ShortProduct.tsx index d40cefca0..3b38ba821 100644 --- a/src/stories/Homework2/Product/Product.tsx +++ b/src/stories/Homework2/ShortProduct/ShortProduct.tsx @@ -1,11 +1,12 @@ -import IProduct from "../Interfaces/IProduct"; + +import IShortProduct from "../Interfaces/IShortProduct"; import TrashButton from "../TrashButton/TrashButton"; import React from "react"; -function Product({ title, price, description }: IProduct) { +function ShortProduct({ title, price, description, image }: IShortProduct) { return (
- ... + ...
{title}
{price}
@@ -16,4 +17,4 @@ function Product({ title, price, description }: IProduct) { ); } -export default Product; \ No newline at end of file +export default ShortProduct; \ No newline at end of file diff --git a/src/stories/Homework2/modals/Modal.stories.ts b/src/stories/Homework2/modals/Modal.stories.tsx similarity index 87% rename from src/stories/Homework2/modals/Modal.stories.ts rename to src/stories/Homework2/modals/Modal.stories.tsx index dac2868da..7c63e7e95 100644 --- a/src/stories/Homework2/modals/Modal.stories.ts +++ b/src/stories/Homework2/modals/Modal.stories.tsx @@ -1,6 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; import Modal from './Modal' - +import React from 'react'; const meta: Meta = { title: 'Homework2/Modal', component: Modal, @@ -16,6 +16,6 @@ type Story = StoryObj; export const PrimaryModal: Story = { args: { visible: true, - children: '' + children:
} }; \ No newline at end of file From e8992c59808b877e66556c37a2454e86db93c23e Mon Sep 17 00:00:00 2001 From: Yurabox Date: Sat, 12 Oct 2024 23:16:22 +0300 Subject: [PATCH 20/53] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D1=8F=D0=B5=D0=BC=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B2=20=D0=BF=D1=80=D0=B8=D0=BB=D0=BE=D0=B6?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20react=20hooks(2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/main.ts | 2 +- .storybook/preview.ts | 16 - .storybook/preview.tsx | 57 +++ package-lock.json | 200 ++++++++-- package.json | 6 +- src/app/App.css | 15 +- src/app/App.tsx | 41 +- src/assets/free-icon-cleaning-9012135.png | Bin 0 -> 19844 bytes src/assets/free-icon-cleaning-9717764.png | Bin 0 -> 13981 bytes src/assets/free-icon-household-7029117.png | Bin 0 -> 8270 bytes src/assets/logo-image.png | Bin 0 -> 25705 bytes src/assets/logo-image64.png | Bin 0 -> 4926 bytes src/components/buttons/ChangeThemeButton.tsx | 12 + src/components/headers/Header.tsx | 24 ++ src/components/layouts/Layout.css | 9 + src/components/layouts/Layout.tsx | 10 + src/components/logo/Logo.tsx | 10 + src/context/themeContext.ts | 13 + src/features/LangSwitcher.sass | 6 + src/features/LangSwitcher.tsx | 20 + src/{app => }/index.css | 0 src/index.tsx | 2 +- src/localization/LocalizationInitiator.tsx | 15 + src/localization/resources.ts | 362 ++++++++++++++++++ src/localization/settings.ts | 28 ++ src/localization/translation.ts | 20 + src/stories/Homework2/Interfaces/IModal.ts | 9 +- src/stories/Homework2/Logo/Logo.jsx | 9 - src/stories/Homework2/Logo/Logo.stories.ts | 2 +- src/stories/Homework2/Logo/Logo.tsx | 10 + .../Homework2/ShowModalButton.stories.ts | 19 + src/stories/Homework2/ShowModalButton.tsx | 29 ++ .../changeLang/ChangeLangComponent.tsx | 21 + .../changeLang/changeLangComponent.stories.ts | 18 + .../changeThemeButton/ChangeThemeButton.tsx | 15 + .../changeThemeButton.stories.ts | 19 + src/stories/Homework2/headers/Header.css | 0 src/stories/Homework2/headers/Header.jsx | 10 - .../Homework2/headers/Header.stories.ts | 5 +- src/stories/Homework2/headers/Header.tsx | 15 + .../Homework2/layouts/Layout.stories.ts | 1 + src/stories/Homework2/layouts/Layout.tsx | 8 +- .../Homework2/modals/Modal.stories.tsx | 8 +- src/stories/Homework2/modals/Modal.tsx | 25 +- src/stories/Homework2/modals/ModalOverlay.tsx | 1 + src/widgets/main/Main.tsx | 21 + webpack.config.js | 4 + 47 files changed, 1013 insertions(+), 104 deletions(-) delete mode 100644 .storybook/preview.ts create mode 100644 .storybook/preview.tsx create mode 100644 src/assets/free-icon-cleaning-9012135.png create mode 100644 src/assets/free-icon-cleaning-9717764.png create mode 100644 src/assets/free-icon-household-7029117.png create mode 100644 src/assets/logo-image.png create mode 100644 src/assets/logo-image64.png create mode 100644 src/components/buttons/ChangeThemeButton.tsx create mode 100644 src/components/headers/Header.tsx create mode 100644 src/components/layouts/Layout.css create mode 100644 src/components/layouts/Layout.tsx create mode 100644 src/components/logo/Logo.tsx create mode 100644 src/context/themeContext.ts create mode 100644 src/features/LangSwitcher.sass create mode 100644 src/features/LangSwitcher.tsx rename src/{app => }/index.css (100%) create mode 100644 src/localization/LocalizationInitiator.tsx create mode 100644 src/localization/resources.ts create mode 100644 src/localization/settings.ts create mode 100644 src/localization/translation.ts delete mode 100644 src/stories/Homework2/Logo/Logo.jsx create mode 100644 src/stories/Homework2/Logo/Logo.tsx create mode 100644 src/stories/Homework2/ShowModalButton.stories.ts create mode 100644 src/stories/Homework2/ShowModalButton.tsx create mode 100644 src/stories/Homework2/changeLang/ChangeLangComponent.tsx create mode 100644 src/stories/Homework2/changeLang/changeLangComponent.stories.ts create mode 100644 src/stories/Homework2/changeThemeButton/ChangeThemeButton.tsx create mode 100644 src/stories/Homework2/changeThemeButton/changeThemeButton.stories.ts delete mode 100644 src/stories/Homework2/headers/Header.css delete mode 100644 src/stories/Homework2/headers/Header.jsx create mode 100644 src/stories/Homework2/headers/Header.tsx create mode 100644 src/widgets/main/Main.tsx diff --git a/.storybook/main.ts b/.storybook/main.ts index 2461b8577..ff76e46b1 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,6 +1,6 @@ const config = { stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], - staticDirs: ['../src/stories/Homework2/assets'], + staticDirs: ['../src/assets'], addons: [ "@storybook/addon-links", "@storybook/addon-essentials", diff --git a/.storybook/preview.ts b/.storybook/preview.ts deleted file mode 100644 index 8da71f090..000000000 --- a/.storybook/preview.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Preview } from "@storybook/react"; -import 'bootstrap/dist/css/bootstrap.min.css'; - -const preview: Preview = { - parameters: { - actions: { argTypesRegex: "^on[A-Z].*" }, - controls: { - matchers: { - color: /(background|color)$/i, - date: /Date$/, - }, - }, - }, -}; - -export default preview; diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx new file mode 100644 index 000000000..cfc60e8fc --- /dev/null +++ b/.storybook/preview.tsx @@ -0,0 +1,57 @@ +import type { Preview } from '@storybook/react'; +import 'bootstrap/dist/css/bootstrap.min.css'; +import i18n from '../src/localization/settings'; +import React, { Suspense } from 'react'; +import { I18nextProvider } from 'react-i18next'; + +const preview: Preview = { + parameters: { + actions: { argTypesRegex: '^on[A-Z].*' }, + controls: { + matchers: { + color: /(background|color)$/i, + date: /Date$/, + }, + }, + }, +}; + +export default preview; + + +// Wrap your stories in the I18nextProvider component +const withI18next = (Story) => { + return ( + // This catches the suspense from components not yet ready (still loading translations) + // Alternative: set useSuspense to false on i18next.options.react when initializing i18next + loading translations...
}> + + + + + ); +}; + +// export decorators for storybook to wrap your stories in +export const decorators = [withI18next]; + + +/* Snipped for brevity */ + +// Create a global variable called locale in storybook +// and add a menu in the toolbar to change your locale +export const globalTypes = { + locale: { + name: 'Locale', + description: 'Internationalization locale', + toolbar: { + icon: 'globe', + items: [ + { value: 'en', title: 'English' }, + { value: 'de', title: 'Deutsch' }, + ], + showName: true, + }, + }, +}; + diff --git a/package-lock.json b/package-lock.json index faf527060..285a0063e 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,12 @@ "license": "MIT", "dependencies": { "clsx": "^1.2.1", + "i18next": "^23.15.2", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^2.6.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-i18next": "^15.0.2" }, "devDependencies": { "@babel/core": "^7.22.1", @@ -2035,12 +2039,12 @@ "dev": true }, "node_modules/@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", - "dev": true, + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", + "license": "MIT", "dependencies": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" }, "engines": { "node": ">=6.9.0" @@ -10306,6 +10310,15 @@ "node": ">=10" } }, + "node_modules/cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.12" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -13722,6 +13735,15 @@ "node": ">=12" } }, + "node_modules/html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "license": "MIT", + "dependencies": { + "void-elements": "3.1.0" + } + }, "node_modules/html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -13902,6 +13924,47 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/i18next": { + "version": "23.15.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.15.2.tgz", + "integrity": "sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==", + "funding": [ + { + "type": "individual", + "url": "https://locize.com" + }, + { + "type": "individual", + "url": "https://locize.com/i18next.html" + }, + { + "type": "individual", + "url": "https://www.i18next.com/how-to/faq#i18next-is-awesome.-how-can-i-support-the-project" + } + ], + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-browser-languagedetector": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", + "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.23.2" + } + }, + "node_modules/i18next-http-backend": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.6.2.tgz", + "integrity": "sha512-Hp/kd8/VuoxIHmxsknJXjkTYYHzivAyAF15pzliKzk2TiXC25rZCEerb1pUFoxz4IVrG3fCvQSY51/Lu4ECV4A==", + "license": "MIT", + "dependencies": { + "cross-fetch": "4.0.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -18658,7 +18721,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -18683,20 +18745,17 @@ "node_modules/node-fetch/node_modules/tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "node_modules/node-fetch/node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "node_modules/node-fetch/node_modules/whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "dependencies": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -20117,6 +20176,28 @@ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", "dev": true }, + "node_modules/react-i18next": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", + "integrity": "sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + }, + "peerDependencies": { + "i18next": ">= 23.2.3", + "react": ">= 16.8.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -20374,10 +20455,10 @@ } }, "node_modules/regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==", + "license": "MIT" }, "node_modules/regenerator-transform": { "version": "0.15.2", @@ -22905,6 +22986,15 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", @@ -25111,12 +25201,11 @@ "dev": true }, "@babel/runtime": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.22.5.tgz", - "integrity": "sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==", - "dev": true, + "version": "7.25.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.25.7.tgz", + "integrity": "sha512-FjoyLe754PMiYsFaN5C94ttGiOmBNYTf6pLr4xXHAT5uctHb092PBszndLDR5XA/jghQvn4n7JMHl7dmTgbm9w==", "requires": { - "regenerator-runtime": "^0.13.11" + "regenerator-runtime": "^0.14.0" } }, "@babel/template": { @@ -31146,6 +31235,14 @@ "yaml": "^1.10.0" } }, + "cross-fetch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", + "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "requires": { + "node-fetch": "^2.6.12" + } + }, "cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -33726,6 +33823,14 @@ "terser": "^5.10.0" } }, + "html-parse-stringify": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz", + "integrity": "sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==", + "requires": { + "void-elements": "3.1.0" + } + }, "html-tags": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/html-tags/-/html-tags-3.3.1.tgz", @@ -33847,6 +33952,30 @@ "integrity": "sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==", "dev": true }, + "i18next": { + "version": "23.15.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-23.15.2.tgz", + "integrity": "sha512-zcPSWzCvw6uKnuYHIqs4W7hTuB9e3AFcSdZgvCWoPXIZsBjBd4djN2/2uOHIB+1DFFkQnMBXvhNg7J3WyCuywQ==", + "requires": { + "@babel/runtime": "^7.23.2" + } + }, + "i18next-browser-languagedetector": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/i18next-browser-languagedetector/-/i18next-browser-languagedetector-8.0.0.tgz", + "integrity": "sha512-zhXdJXTTCoG39QsrOCiOabnWj2jecouOqbchu3EfhtSHxIB5Uugnm9JaizenOy39h7ne3+fLikIjeW88+rgszw==", + "requires": { + "@babel/runtime": "^7.23.2" + } + }, + "i18next-http-backend": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/i18next-http-backend/-/i18next-http-backend-2.6.2.tgz", + "integrity": "sha512-Hp/kd8/VuoxIHmxsknJXjkTYYHzivAyAF15pzliKzk2TiXC25rZCEerb1pUFoxz4IVrG3fCvQSY51/Lu4ECV4A==", + "requires": { + "cross-fetch": "4.0.0" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -37228,7 +37357,6 @@ "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, "requires": { "whatwg-url": "^5.0.0" }, @@ -37236,20 +37364,17 @@ "tr46": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, "webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" }, "whatwg-url": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "dev": true, "requires": { "tr46": "~0.0.3", "webidl-conversions": "^3.0.0" @@ -38297,6 +38422,15 @@ } } }, + "react-i18next": { + "version": "15.0.2", + "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", + "integrity": "sha512-z0W3/RES9Idv3MmJUcf0mDNeeMOUXe+xoL0kPfQPbDoZHmni/XsIoq5zgT2MCFUiau283GuBUK578uD/mkAbLQ==", + "requires": { + "@babel/runtime": "^7.25.0", + "html-parse-stringify": "^3.0.1" + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -38476,10 +38610,9 @@ } }, "regenerator-runtime": { - "version": "0.13.11", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", - "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", - "dev": true + "version": "0.14.1", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz", + "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==" }, "regenerator-transform": { "version": "0.15.2", @@ -40365,6 +40498,11 @@ "unist-util-stringify-position": "^3.0.0" } }, + "void-elements": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-3.1.0.tgz", + "integrity": "sha512-Dhxzh5HZuiHQhbvTW9AMetFfBHDMYpo23Uo9btPXgdYP+3T5S+p+jgNy7spra+veYhBP2dCSgxR/i2Y02h5/6w==" + }, "w3c-xmlserializer": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-4.0.0.tgz", diff --git a/package.json b/package.json index d67426d78..2681b403c 100644 --- a/package.json +++ b/package.json @@ -71,7 +71,11 @@ }, "dependencies": { "clsx": "^1.2.1", + "i18next": "^23.15.2", + "i18next-browser-languagedetector": "^8.0.0", + "i18next-http-backend": "^2.6.2", "react": "^18.2.0", - "react-dom": "^18.2.0" + "react-dom": "^18.2.0", + "react-i18next": "^15.0.2" } } diff --git a/src/app/App.css b/src/app/App.css index 4715c1bda..09b467829 100644 --- a/src/app/App.css +++ b/src/app/App.css @@ -1,11 +1,13 @@ .App { text-align: center; + height: 100vh; } .App-logo { height: 40vmin; pointer-events: none; } + .text-left { text-align: left !important; } @@ -27,6 +29,16 @@ color: white; } +.App.light { + background-color: #f0f0f0 !important; + color: #282c34; +} + +.App.dark { + background-color: #282c34 !important; + color: white; +} + .App-link { color: #61dafb; } @@ -35,7 +47,8 @@ from { transform: rotate(0deg); } + to { transform: rotate(360deg); } -} +} \ No newline at end of file diff --git a/src/app/App.tsx b/src/app/App.tsx index 8d3ebca4c..d8edf5811 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,20 +1,35 @@ -import React from 'react'; -import logo from './logo.svg'; +import React, { useState } from 'react'; + import './App.css'; +import { Theme, ThemeContext } from 'src/context/themeContext'; +import { LocalizationInitiator } from 'src/localization/LocalizationInitiator'; +import Layout from 'src/components/layouts/Layout'; +import { I18nextProvider, useTranslation } from 'react-i18next'; +import i18n, { Locale } from 'src/localization/settings'; +import { translation } from 'src/localization/translation'; +import Main from 'src/widgets/main/Main'; + + function App() { + let [theme, setTheme] = useState("light"); + + const toggleTheme = () => { + setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light")); + }; + return ( -
-
- logo -
    -
  • В компании по импортозамещению необходимо переписать проект с Blazor на React.
  • -
  • Владею Blazor, AspNet, net6
  • -
  • FullStack разработчик, разрабатываю информационно-аналитическую систему.
  • -
  • 2022-2023гг проходил обучение по курсу AspNet core Otus.
  • -
-
-
+ <> + + + +
+ +
+
+
+
+ ); } diff --git a/src/assets/free-icon-cleaning-9012135.png b/src/assets/free-icon-cleaning-9012135.png new file mode 100644 index 0000000000000000000000000000000000000000..de7edd177366476fbebdbfcebaf761a7adbe1d0f GIT binary patch literal 19844 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 P0KjYLTZKwF(~tiH@6WUy literal 0 HcmV?d00001 diff --git a/src/assets/logo-image.png b/src/assets/logo-image.png new file mode 100644 index 0000000000000000000000000000000000000000..6fc41c902b4cbd9e3dc2bc65c386d94ad0c39ccd GIT binary patch literal 25705 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�l#)h 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 b?Th~h1UeQ^VFpst00000NkvXXu0mjf6u4$q literal 0 HcmV?d00001 diff --git a/src/assets/logo-image64.png b/src/assets/logo-image64.png new file mode 100644 index 0000000000000000000000000000000000000000..0037316ad4aef29b02eb2f66decd999b61819a84 GIT binary patch literal 4926 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 { + const { theme, toggleTheme } = useContext(ThemeContext); + return ( + + + + ) +} +export default ChangeThemeButton; \ No newline at end of file diff --git a/src/components/headers/Header.tsx b/src/components/headers/Header.tsx new file mode 100644 index 000000000..7f1d5eeb3 --- /dev/null +++ b/src/components/headers/Header.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react'; +import { withTranslation } from 'react-i18next'; + +import Logo from '../logo/Logo'; +import { LangSwitcher } from 'src/features/LangSwitcher'; +import ChangeThemeButton from '../buttons/ChangeThemeButton'; + +export const HeaderOrigin: FC = () => { + return ( +
+
+
+ +
+
+ + +
+
+
+ ); +}; +HeaderOrigin.displayName = 'HeaderOrigin'; +export const Header = withTranslation()(HeaderOrigin); diff --git a/src/components/layouts/Layout.css b/src/components/layouts/Layout.css new file mode 100644 index 000000000..2e7757075 --- /dev/null +++ b/src/components/layouts/Layout.css @@ -0,0 +1,9 @@ +.layout { + width: 1440px; + height: 1036px; + top: 162px; + left: 80px; + gap: 0px; + border-radius: 20px 20px 0px 0px; + opacity: 0px; +} \ No newline at end of file diff --git a/src/components/layouts/Layout.tsx b/src/components/layouts/Layout.tsx new file mode 100644 index 000000000..406c95a62 --- /dev/null +++ b/src/components/layouts/Layout.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import "./Layout.css" +import { Header } from "../headers/Header"; + +function Layout() { + return ( +
+ ) +} +export default Layout; \ No newline at end of file diff --git a/src/components/logo/Logo.tsx b/src/components/logo/Logo.tsx new file mode 100644 index 000000000..7f7309ae4 --- /dev/null +++ b/src/components/logo/Logo.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import logo from '../../assets/logo-image64.png'; +function Logo() { + return ( +
+ +
+ ) +} +export default Logo; \ No newline at end of file diff --git a/src/context/themeContext.ts b/src/context/themeContext.ts new file mode 100644 index 000000000..f49a72444 --- /dev/null +++ b/src/context/themeContext.ts @@ -0,0 +1,13 @@ +import { createContext } from "react"; + +export type Theme = "light" | "dark"; + +export interface ThemeContextType { + theme: Theme; + toggleTheme: () => void; +} + +export const ThemeContext = createContext({ + theme: "dark", + toggleTheme: () => { }, +}); \ No newline at end of file diff --git a/src/features/LangSwitcher.sass b/src/features/LangSwitcher.sass new file mode 100644 index 000000000..75208d4b2 --- /dev/null +++ b/src/features/LangSwitcher.sass @@ -0,0 +1,6 @@ +.root + border: 0 + padding: 4px + background: none + outline: none + cursor: pointer \ No newline at end of file diff --git a/src/features/LangSwitcher.tsx b/src/features/LangSwitcher.tsx new file mode 100644 index 000000000..1c3e05018 --- /dev/null +++ b/src/features/LangSwitcher.tsx @@ -0,0 +1,20 @@ +import React, { FC } from 'react'; +import cn from 'clsx'; +import { useTranslation } from 'react-i18next'; + +import s from './LangSwitcher.sass'; +import { Locale } from 'src/localization/settings'; + +export type ThemeSwitcherProps = { + className?: string; +}; + +export const LangSwitcher: FC = ({ className }) => { + const { i18n } = useTranslation(); + const lang = (i18n.language as Locale) === Locale.ru ? Locale.en : Locale.ru; + return ( + + ); +}; diff --git a/src/app/index.css b/src/index.css similarity index 100% rename from src/app/index.css rename to src/index.css diff --git a/src/index.tsx b/src/index.tsx index 4297c6de6..879481a45 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,6 +1,6 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; -import './app/index.css'; +import './index.css'; import App from './app/App'; import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/js/bootstrap.min.js'; diff --git a/src/localization/LocalizationInitiator.tsx b/src/localization/LocalizationInitiator.tsx new file mode 100644 index 000000000..b0665a2f0 --- /dev/null +++ b/src/localization/LocalizationInitiator.tsx @@ -0,0 +1,15 @@ +import { FC, useInsertionEffect } from 'react'; +import { useTranslation } from 'react-i18next'; +import { LANG_STORAGE_KEY } from './settings'; + +export const LocalizationInitiator: FC = () => { + const { i18n } = useTranslation(); + + useInsertionEffect(() => { + localStorage.setItem(LANG_STORAGE_KEY, i18n.language); + const html = document.body.parentElement; + html.lang = i18n.language; + }, [i18n.language]); + + return null; +}; diff --git a/src/localization/resources.ts b/src/localization/resources.ts new file mode 100644 index 000000000..b448049f4 --- /dev/null +++ b/src/localization/resources.ts @@ -0,0 +1,362 @@ +// the translations +// (tip move them in a JSON file and import them, +// or even better, manage them separated from your code: https://react.i18next.com/guides/multiple-translation-files) +export const resources = { + en: { + translation: { + errors: { + unexpected_error: 'Unexpected error. We automatically register errors and will fix everything soon', + 'Failed to fetch': 'Connection error. Go to the server directory and start the server using the start script', + is_required: 'Required field', + invalid_email_address: 'Invalid email address', + too_short_password: 'The password is too short', + not_same_password: "Passwords don't match", + ERR_INCORRECT_EMAIL_OR_PASSWORD: 'Incorrect password or email', + ERR_NOT_FOUND: 'An entity with this id was not found', + ERR_USER_NOT_REGISTER: 'Register to access this feature', + ERR_INCORRECT_PASSWORD: 'Invalid password', + ERR_ACCOUNT_ALREADY_EXIST: 'An account with this email already exists', + ERR_INVALID_PASSWORD: 'The password must contain at least 8 characters', + ERR_INVALID_EMAIL: 'Invalid email', + ERR_TOKEN_REQUIRED_ERROR: + 'Server token error. We automatically register all errors and will fix everything soon', + ERR_AUTH_ERROR: 'You are not logged in, log in to your account and try again', + ERR_DATA_BASE_ERROR: 'Database server error. We automatically register all errors and will fix everything soon', + INTERNAL_SERVER_ERROR: 'Server error. We automatically register all errors and will fix everything soon', + ERR_INVALID_NICKNAME: + 'The alias must be at least 7 characters and can contain only numbers, letters and an underscore', + }, + customText: { + desc: "An import substitution company needs to rewrite a project from Blazor to React." + }, + screens: { + ProfileScreen: { + title: 'Profile', + updateProfile: { + title: 'Change profile', + success: 'Profile changed successfully', + save: 'Save', + }, + changePassword: { + title: 'Change password', + success: 'Password changed successfully', + save: 'Change', + }, + }, + ExamplesScreen: { + title: 'Examples', + }, + LessonsScreen: { + title: 'Lessons', + }, + auth: { + title: 'Authentication', + signIn: { + title: 'Sign in', + submit: 'Sign in', + }, + signUp: { + title: 'Sign out', + submit: 'Sign out', + }, + }, + HomeScreen: { + title: 'Home', + desc: `Welcome to the react course from otus!\n\nThis is a training project of the course. Here you will find materials for lectures, homework, as well as examples of some complex components.\n\nThe project is written in **typescript**, **redux**, **redux-saga** using **webpack**. In it , you can spy on the implementation of the dark/light theme, locale switching, navigation, token exchange between browser tabs.\n\nThe project also has its own server implemented on **graphql**, **apollo**, **express** and using a json file as a database (in real development, do not do this, it's just to save you from installing the database). Interaction with the server is implemented, namely the ability to register a user, log in to an account, but do not forget to start the server locally - use the _start:client_ and _start:server_ commands.\n\nWelcome and good luck in learning!`, + }, + TeachersScreen: { + title: 'Teachers', + desc: "Someday there will be teachers' cards here, but that's not for sure", + }, + }, + forms: { + AuthForm: { + email: { + title: 'Email', + placeholder: 'Enter email', + }, + password: { + title: 'Password', + placeholder: 'Enter password', + }, + }, + ChangePasswordForm: { + password: { + title: 'Password', + placeholder: 'Enter password', + }, + newPassword: { + title: 'New password', + placeholder: 'Enter new password', + }, + repeatPassword: { + title: 'Repeat password', + placeholder: 'Repeat password', + }, + }, + EmailForm: { + email: { + title: 'Email', + placeholder: 'Enter email', + }, + }, + ProfileForm: { + name: { + title: 'Nickname', + placeholder: 'Come up with a pseudonym for yourself', + }, + about: { + title: 'About', + placeholder: 'Write something about yourself', + }, + }, + RepeatPasswordForm: { + password: { + title: 'Password', + placeholder: 'Enter password', + }, + repeatPassword: { + title: 'Repeat password', + placeholder: 'Repeat password', + }, + }, + }, + fields: { + string: 'String value', + }, + components: { + RemoveButton: { + title: 'Data will be lost, delete?', + ok: 'Remove', + cancel: 'Cancel', + }, + RangeInputs: { + from: 'From', + to: 'To', + }, + InputIntRangeList: { + title: 'Range', + }, + NumberInput: { + float: { + placeholder: 'Fractional number', + }, + integer: { + placeholder: 'Integer', + }, + }, + login: { + enter: 'Login', + leave: 'Logout', + }, + header: { + nav: 'Navigation', + root: 'Home', + profile: 'Profile', + 'home-works': 'Home works', + examples: 'Examples', + lessons: 'Lessons', + teachers: 'Teachers', + }, + }, + enums: { + ExampleKey: { + modal: 'Modal', + movable: 'Movable', + sortableList: 'Sortable List', + waveSlider: 'Wave Slider', + inputs: 'Inputs', + }, + LessonKey: { + generator: 'Generators iterators', + patterns: 'Patterns', + solid: 'Solid', + functionalProgramming: 'Functional Programming', + babel: 'Babel + typescript', + socketsAndWorkers: 'Web sockets & Web workers', + graphql: 'Graphql', + }, + }, + }, + }, + ru: { + translation: { + errors: { + unexpected_error: 'Неожиданная ошибка. Мы автоматически регистрируем ошибки и скоро все исправим', + 'Failed to fetch': + 'Ошибка соединения. Перейдите в директорию server и запустите сервер с помощью скрипта start', + is_required: 'Обязательное поле', + invalid_email_address: 'Некорректный email адрес', + too_short_password: 'Слишком короткий пароль', + not_same_password: 'Пароли не совпадают', + ERR_INCORRECT_EMAIL_OR_PASSWORD: 'Некорректный пароль или email', + ERR_NOT_FOUND: 'Сущность с таким id не найдена', + ERR_USER_NOT_REGISTER: 'Зарегистрируйтесь, чтобы получить доступ к этой функции', + ERR_INCORRECT_PASSWORD: 'Некорректный пароль', + ERR_ACCOUNT_ALREADY_EXIST: 'Аккаунт с таким email уже существует', + ERR_INVALID_PASSWORD: 'Пароль должен содержать от 8 символов', + ERR_INVALID_EMAIL: 'Некорректный email', + ERR_TOKEN_REQUIRED_ERROR: + 'Серверная ошибка токена. Мы автоматически регистрируем все ошибки и скоро все исправим', + ERR_AUTH_ERROR: 'Вы не авторизованы, войдите в учетную запись и повторите попытку', + ERR_DATA_BASE_ERROR: + 'Серверная ошибка базы данный. Мы автоматически регистрируем все ошибки и скоро все исправим', + INTERNAL_SERVER_ERROR: 'Серверная ошибка. Мы автоматически регистрируем все ошибки и скоро все исправим', + ERR_INVALID_NICKNAME: + 'Псевдоним должен быть от 7 символов и может содержать только числа, буквы и символ нижнего подчеркивания', + }, + customText: { + desc: "An import substitution company needs to rewrite a project from Blazor to React." + }, + screens: { + ProfileScreen: { + title: 'Профиль', + updateProfile: { + title: 'Изменить профиль', + success: 'Профиль успешно изменен', + save: 'Сохранить', + }, + changePassword: { + title: 'Изменить пароль', + success: 'Пароль успешно изменен', + save: 'Изменить', + }, + }, + ExamplesScreen: { + title: 'Примеры', + }, + LessonsScreen: { + title: 'Уроки', + }, + auth: { + title: 'Аутентификация', + signIn: { + title: 'Войти', + submit: 'Войти', + }, + signUp: { + title: 'Зарегистрироваться', + submit: 'Зарегистрироваться', + }, + }, + HomeScreen: { + title: 'Главная', + desc: `Приветствую на курсе по react от otus!\n\nЭто учебный проект курса. Здесь вы найдете материалы к лекциям, домашние задания, а так же примеры некоторых сложных компонентов.\n\nПроект написан на **typescript**, **redux**, **redux-saga** с использованием **webpack**. В нем можно подсмотреть реализацию темной/светлой темы, переключение локали, навигацию, обмен токен между вкладками браузера.\n\nТак же в проекте есть свой сервер, реализованный на **graphql**, **apollo**, **express** и использующий в качестве базы данных json файл (в реальной разработке не делайте так, это только чтобы избавить вас от установки базы данных). Реализовано взаимодействие с сервером, а именно возможность зарегистрировать пользователя, войти в учетную запись, но не забудьте запустить сервер локально - используйте команды _start:client_ и _start:server_.\n\nДобро пожаловать и удачи в обучении!`, + }, + TeachersScreen: { + title: 'Преподаватели', + desc: 'Когда-нибудь здесь появятся карточки преподавателей, но это не точно', + }, + }, + forms: { + AuthForm: { + email: { + title: 'Email', + placeholder: 'Укажите email', + }, + password: { + title: 'Пароль', + placeholder: 'Укажите пароль', + }, + }, + ChangePasswordForm: { + password: { + title: 'Пароль', + placeholder: 'Укажите пароль', + }, + newPassword: { + title: 'Новый пароль', + placeholder: 'Укажите новый пароль', + }, + repeatPassword: { + title: 'Повторите пароль', + placeholder: 'Повторите пароль', + }, + }, + EmailForm: { + email: { + title: 'Email', + placeholder: 'Укажите email', + }, + }, + ProfileForm: { + name: { + title: 'Псевдоним', + placeholder: 'Придумайте себе псевдоним', + }, + about: { + title: 'О себе', + placeholder: 'Напишите что-нибудь о себе', + }, + }, + RepeatPasswordForm: { + password: { + title: 'Пароль', + placeholder: 'Укажите пароль', + }, + repeatPassword: { + title: 'Повторите пароль', + placeholder: 'Повторите пароль', + }, + }, + }, + fields: { + string: 'Строковое значение', + }, + components: { + RemoveButton: { + title: 'Данные будут потеряны, удалить?', + ok: 'Удалить', + cancel: 'Отмена', + }, + RangeInputs: { + from: 'От', + to: 'До', + }, + InputIntRangeList: { + title: 'Диапазон', + }, + NumberInput: { + float: { + placeholder: 'Дробное число', + }, + integer: { + placeholder: 'Целое число', + }, + }, + login: { + enter: 'Вход', + leave: 'Выход', + }, + header: { + nav: 'Навигация', + root: 'Главная', + profile: 'Профиль', + 'home-works': 'Домашние работы', + examples: 'Примеры', + lessons: 'Уроки', + teachers: 'Преподаватели', + }, + }, + enums: { + ExampleKey: { + modal: 'Модальное окно', + movable: 'Перемещаемый', + sortableList: 'Сортируемый список', + waveSlider: 'Слайдер', + inputs: 'Инпуты', + }, + LessonKey: { + generator: 'Генераторы итераторы', + patterns: 'Паттерны', + restApi: 'Rest Api', + solid: 'Solid', + functionalProgramming: 'Функциональное программирование', + babel: 'Babel + typescript', + socketsAndWorkers: 'Web sockets & Web workers', + graphql: 'Graphql', + }, + }, + }, + }, +}; diff --git a/src/localization/settings.ts b/src/localization/settings.ts new file mode 100644 index 000000000..627cc3824 --- /dev/null +++ b/src/localization/settings.ts @@ -0,0 +1,28 @@ +import i18n, { InitOptions } from 'i18next'; +import { initReactI18next } from 'react-i18next'; +import { translation } from './translation'; +import LanguageDetector from 'i18next-browser-languagedetector'; +export const LANG_STORAGE_KEY = 'lang'; + +export enum Locale { + ru = 'ru', + en = 'en', +} + +i18n + .use(initReactI18next) // passes i18n down to react-i18next + .use(LanguageDetector) + .init({ + resources: translation, + // translation, + lng: localStorage.getItem(LANG_STORAGE_KEY) || Locale.ru, + // // language to use, more information here: https://www.i18next.com/overview/configuration-options#languages-namespaces-resources + // // you can use the i18n.changeLanguage function to change the language manually: https://www.i18next.com/overview/api#changelanguage + // // if you're using a language detector, do not define the lng option + + // interpolation: { + // escapeValue: false, // react already safes from xss + // }, + } as InitOptions); + +export default i18n; diff --git a/src/localization/translation.ts b/src/localization/translation.ts new file mode 100644 index 000000000..5f8842eb3 --- /dev/null +++ b/src/localization/translation.ts @@ -0,0 +1,20 @@ +export const translation = { + ru: { + translation: { + key1: 'В компании по импортозамещению необходимо переписать проект с Blazor на React.', + key2: 'Владею Blazor, AspNet, net.6', + key3: 'FullStack разработчик, разрабатываю информационно-аналитическую систему.', + key4: '2022-2023гг проходил обучение по курсу AspNet core Otus.', + teststorybook: 'Привет мир!', + }, + }, + en: { + translation: { + key1: 'An import substitution company needs to rewrite a project from Blazor to React.', + key2: 'I speak Blazor, AspNet, net.6', + key3: 'FullStack developer, developing an information and analytical system.', + key4: '2022-2023 studied at the AspNet core Otus course.', + teststorybook: 'Hello world!', + }, + }, +}; diff --git a/src/stories/Homework2/Interfaces/IModal.ts b/src/stories/Homework2/Interfaces/IModal.ts index 486b4236c..cb766b8df 100644 --- a/src/stories/Homework2/Interfaces/IModal.ts +++ b/src/stories/Homework2/Interfaces/IModal.ts @@ -1,5 +1,8 @@ -import { ReactElement, ReactNode } from "react" -export default interface IModal extends ReactElement { +import React from "react" + +export default interface IModal { + children?: React.ReactNode, visible: boolean, - children: ReactNode + header: string, + onClose: () => void } \ No newline at end of file diff --git a/src/stories/Homework2/Logo/Logo.jsx b/src/stories/Homework2/Logo/Logo.jsx deleted file mode 100644 index 49f77a4dd..000000000 --- a/src/stories/Homework2/Logo/Logo.jsx +++ /dev/null @@ -1,9 +0,0 @@ - -function Logo() { - return ( -
- -
- ) -} -export default Logo; \ No newline at end of file diff --git a/src/stories/Homework2/Logo/Logo.stories.ts b/src/stories/Homework2/Logo/Logo.stories.ts index 4fd7a1373..82ae72552 100644 --- a/src/stories/Homework2/Logo/Logo.stories.ts +++ b/src/stories/Homework2/Logo/Logo.stories.ts @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; - import Logo from './Logo'; + const meta: Meta = { title: 'Homework2/Logo', component: Logo, diff --git a/src/stories/Homework2/Logo/Logo.tsx b/src/stories/Homework2/Logo/Logo.tsx new file mode 100644 index 000000000..430d03d3a --- /dev/null +++ b/src/stories/Homework2/Logo/Logo.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import logo from '../assets/logo-image64.png'; +function Logo() { + return ( +
+ +
+ ) +} +export default Logo; \ No newline at end of file diff --git a/src/stories/Homework2/ShowModalButton.stories.ts b/src/stories/Homework2/ShowModalButton.stories.ts new file mode 100644 index 000000000..e1198c311 --- /dev/null +++ b/src/stories/Homework2/ShowModalButton.stories.ts @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import ShowModalButton from './ShowModalButton'; +import { action } from '@storybook/addon-actions'; + +const meta: Meta = { + title: 'Homework2/ShowModalButton', + component: ShowModalButton, + tags: ['autodocs'], + +}; + +export default meta; +type Story = StoryObj; + +export const ShowModal: Story = { + args: { + openModal: action('clicked'), + }, +}; diff --git a/src/stories/Homework2/ShowModalButton.tsx b/src/stories/Homework2/ShowModalButton.tsx new file mode 100644 index 000000000..0192f02ed --- /dev/null +++ b/src/stories/Homework2/ShowModalButton.tsx @@ -0,0 +1,29 @@ +import React, { useState } from "react"; +import Modal from "./modals/Modal"; + +function ShowModalButton() { + const [visible, setVisible] = useState(false); + const [inputText, setInputText] = useState(''); + + const openModal = () => { + setVisible(true); + } + const closeModal = () => { + setVisible(false); + } + const modal = ( + +
aaaaa
+
+ ) + + return ( +
+ setInputText(e.target.value)} /> + + {visible && modal} +
+ ) +} + +export default ShowModalButton; \ No newline at end of file diff --git a/src/stories/Homework2/changeLang/ChangeLangComponent.tsx b/src/stories/Homework2/changeLang/ChangeLangComponent.tsx new file mode 100644 index 000000000..d0b03fba8 --- /dev/null +++ b/src/stories/Homework2/changeLang/ChangeLangComponent.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import { I18nextProvider, useTranslation } from 'react-i18next'; + +function ChangeLangComponent() { + const { t, i18n } = useTranslation(); + const lang = i18n.language === 'ru' ? 'en' : 'ru'; + + return ( + <> + +
+ {t(`teststorybook`)} + +
+
+ + ); +} +export default ChangeLangComponent; diff --git a/src/stories/Homework2/changeLang/changeLangComponent.stories.ts b/src/stories/Homework2/changeLang/changeLangComponent.stories.ts new file mode 100644 index 000000000..241026660 --- /dev/null +++ b/src/stories/Homework2/changeLang/changeLangComponent.stories.ts @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import ChangeLangComponent from './ChangeLangComponent'; + +const meta: Meta = { + title: 'Homework2/ChangeLangComponent', + component: ChangeLangComponent, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const ChangeLangPrimary: Story = { + args: { + openModal: action('clicked'), + }, +}; diff --git a/src/stories/Homework2/changeThemeButton/ChangeThemeButton.tsx b/src/stories/Homework2/changeThemeButton/ChangeThemeButton.tsx new file mode 100644 index 000000000..39f9bbc97 --- /dev/null +++ b/src/stories/Homework2/changeThemeButton/ChangeThemeButton.tsx @@ -0,0 +1,15 @@ +import React, { FC, useState } from 'react'; + +const ChangeThemeButton: FC = () => { + const [theme, setTheme] = useState('primary'); + + const hadleTheme = () => { + setTheme((prevTheme) => (prevTheme === 'primary' ? 'danger' : 'primary')); + }; + return ( + + ); +}; +export default ChangeThemeButton; diff --git a/src/stories/Homework2/changeThemeButton/changeThemeButton.stories.ts b/src/stories/Homework2/changeThemeButton/changeThemeButton.stories.ts new file mode 100644 index 000000000..3e66e7869 --- /dev/null +++ b/src/stories/Homework2/changeThemeButton/changeThemeButton.stories.ts @@ -0,0 +1,19 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { action } from '@storybook/addon-actions'; +import ChangeThemeButton from './ChangeThemeButton'; + + +const meta: Meta = { + title: 'Homework2/ChangeThemeButton', + component: ChangeThemeButton, + tags: ['autodocs'] +}; + +export default meta; +type Story = StoryObj; + +export const ChangeThemePrimary: Story = { + args: { + openModal: action('clicked'), + } +}; diff --git a/src/stories/Homework2/headers/Header.css b/src/stories/Homework2/headers/Header.css deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/stories/Homework2/headers/Header.jsx b/src/stories/Homework2/headers/Header.jsx deleted file mode 100644 index 3ff3aca51..000000000 --- a/src/stories/Homework2/headers/Header.jsx +++ /dev/null @@ -1,10 +0,0 @@ -import Logo from "../Logo/Logo"; - -function Header() { - return ( -
- -
- ) -} -export default Header; \ No newline at end of file diff --git a/src/stories/Homework2/headers/Header.stories.ts b/src/stories/Homework2/headers/Header.stories.ts index 6bcd2937e..5785e8f6f 100644 --- a/src/stories/Homework2/headers/Header.stories.ts +++ b/src/stories/Homework2/headers/Header.stories.ts @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; -import Header from './Header'; +import { Header } from './Header'; + const meta: Meta = { title: 'Homework2/Header', @@ -15,4 +16,4 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Primary: Story = { }; \ No newline at end of file +export const Primary: Story = {}; \ No newline at end of file diff --git a/src/stories/Homework2/headers/Header.tsx b/src/stories/Homework2/headers/Header.tsx new file mode 100644 index 000000000..fdea93161 --- /dev/null +++ b/src/stories/Homework2/headers/Header.tsx @@ -0,0 +1,15 @@ +import React, { FC } from 'react'; +import Logo from '../Logo/Logo'; + + +export const Header: FC = () => { + return ( +
+
+
+ +
+
+
+ ); +}; diff --git a/src/stories/Homework2/layouts/Layout.stories.ts b/src/stories/Homework2/layouts/Layout.stories.ts index bc423f07d..410d5a374 100644 --- a/src/stories/Homework2/layouts/Layout.stories.ts +++ b/src/stories/Homework2/layouts/Layout.stories.ts @@ -2,6 +2,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import Layout from './Layout'; + const meta: Meta = { title: 'Homework2/Layout', component: Layout, diff --git a/src/stories/Homework2/layouts/Layout.tsx b/src/stories/Homework2/layouts/Layout.tsx index c8b975d26..406c95a62 100644 --- a/src/stories/Homework2/layouts/Layout.tsx +++ b/src/stories/Homework2/layouts/Layout.tsx @@ -1,14 +1,10 @@ import React from "react"; -import Header from "../headers/Header"; import "./Layout.css" +import { Header } from "../headers/Header"; function Layout() { return ( -
-
-
-
-
+
) } export default Layout; \ No newline at end of file diff --git a/src/stories/Homework2/modals/Modal.stories.tsx b/src/stories/Homework2/modals/Modal.stories.tsx index 7c63e7e95..3c83fa6f3 100644 --- a/src/stories/Homework2/modals/Modal.stories.tsx +++ b/src/stories/Homework2/modals/Modal.stories.tsx @@ -1,6 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react'; import Modal from './Modal' import React from 'react'; +import { action } from '@storybook/addon-actions'; const meta: Meta = { title: 'Homework2/Modal', component: Modal, @@ -15,7 +16,8 @@ type Story = StoryObj; export const PrimaryModal: Story = { args: { - visible: true, - children:
+ children:
, + onClose: action('clicked') } -}; \ No newline at end of file +}; + diff --git a/src/stories/Homework2/modals/Modal.tsx b/src/stories/Homework2/modals/Modal.tsx index 7098d3152..9db134a63 100644 --- a/src/stories/Homework2/modals/Modal.tsx +++ b/src/stories/Homework2/modals/Modal.tsx @@ -1,21 +1,31 @@ -import React from 'react'; +import React, { useState } from 'react'; +import ReactDOM from 'react-dom'; import ModalOverlay from "./ModalOverlay"; import './Modal.css'; import IModal from '../Interfaces/IModal'; -function Modal({ visible, children }: IModal) { +// const modalRoot = document.getElementById('root-modal'); + +function Modal({ visible, header = '', children, onClose }: IModal) { + + // return ReactDOM.createPortal( return ( <>
+ role="dialog" + >
-
-

+
+

{header}

-

+

×

@@ -26,6 +36,9 @@ function Modal({ visible, children }: IModal) { ) + // , + // modalRoot + // ); } export default Modal; \ No newline at end of file diff --git a/src/stories/Homework2/modals/ModalOverlay.tsx b/src/stories/Homework2/modals/ModalOverlay.tsx index 983a3b2d2..7fa6ea84c 100644 --- a/src/stories/Homework2/modals/ModalOverlay.tsx +++ b/src/stories/Homework2/modals/ModalOverlay.tsx @@ -1,5 +1,6 @@ import './ModalOverlay.css' import React from 'react'; + function ModalOverlay() { return (
+
+ logo +
    +
  • {t(`key1`)}
  • +
  • {t(`key2`)}
  • +
  • {t(`key3`)}
  • +
  • {t(`key4`)}
  • +
+
+ + ); +} diff --git a/webpack.config.js b/webpack.config.js index 583dfd1ed..ccda5187f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -62,6 +62,10 @@ module.exports = (_, args) => { 'css-loader', ], }, + { + test: /\.(png|svg|jpg|jpeg|gif)$/i, + type: 'asset/resource', + }, { test: /\.svg/, type: 'asset/inline', From 3ca3759fb5702e06c2dad662edae982132e5ba15 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Sun, 13 Oct 2024 23:28:17 +0300 Subject: [PATCH 21/53] no message --- src/localization/settings.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/localization/settings.ts b/src/localization/settings.ts index 627cc3824..a7f27b060 100644 --- a/src/localization/settings.ts +++ b/src/localization/settings.ts @@ -25,4 +25,4 @@ i18n // }, } as InitOptions); -export default i18n; +export default i18n; From b1e1d2e9b62d277c230cf41f99af0fc0267a3d81 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Sun, 13 Oct 2024 23:30:54 +0300 Subject: [PATCH 22/53] a --- src/localization/settings.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/localization/settings.ts b/src/localization/settings.ts index a7f27b060..d19b2fd51 100644 --- a/src/localization/settings.ts +++ b/src/localization/settings.ts @@ -1,4 +1,4 @@ -import i18n, { InitOptions } from 'i18next'; +import i18n from 'i18next'; import { initReactI18next } from 'react-i18next'; import { translation } from './translation'; import LanguageDetector from 'i18next-browser-languagedetector'; @@ -23,6 +23,6 @@ i18n // interpolation: { // escapeValue: false, // react already safes from xss // }, - } as InitOptions); + }); export default i18n; From 3d571a5504b53cf8b4f03c083fa4bd02a00681ba Mon Sep 17 00:00:00 2001 From: Yurabox Date: Sun, 13 Oct 2024 23:38:45 +0300 Subject: [PATCH 23/53] react hooks(2) --- src/context/themeContext.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/context/themeContext.ts b/src/context/themeContext.ts index f49a72444..53c0e9252 100644 --- a/src/context/themeContext.ts +++ b/src/context/themeContext.ts @@ -7,7 +7,4 @@ export interface ThemeContextType { toggleTheme: () => void; } -export const ThemeContext = createContext({ - theme: "dark", - toggleTheme: () => { }, -}); \ No newline at end of file +export const ThemeContext = createContext({} as ThemeContextType); \ No newline at end of file From c9a984f17292e526206b5f8461de172aee9e2b64 Mon Sep 17 00:00:00 2001 From: Yurabox Date: Wed, 16 Oct 2024 20:39:23 +0300 Subject: [PATCH 24/53] hooks(2) --- package-lock.json | 132 ++++++++++++++++-- package.json | 5 + .../Homework2 => }/Interfaces/IFullProduct.ts | 0 .../Homework2 => }/Interfaces/IModal.ts | 0 .../Interfaces/IShortProduct.ts | 0 .../Homework2 => }/Interfaces/ITrashButton.ts | 0 src/components/buttons/ChangeThemeButton.tsx | 4 +- src/components/headers/Header.tsx | 2 +- .../{ => LangSwitcher}/LangSwitcher.sass | 0 .../{ => LangSwitcher}/LangSwitcher.tsx | 0 src/index.html | 1 + .../Homework2/FullProduct/FullProduct.tsx | 2 +- .../Homework2/ShortProduct/ShortProduct.tsx | 2 +- .../Homework2/TrashButton/TrashButton.tsx | 2 +- src/stories/Homework2/modals/Modal.tsx | 2 +- src/widgets/main/Main.tsx | 16 ++- src/widgets/modal/Modal.css | 7 + src/widgets/modal/Modal.tsx | 35 +++++ src/widgets/modal/ModalOverlay.css | 3 + src/widgets/modal/ModalOverlay.tsx | 12 ++ 20 files changed, 209 insertions(+), 16 deletions(-) rename src/{stories/Homework2 => }/Interfaces/IFullProduct.ts (100%) rename src/{stories/Homework2 => }/Interfaces/IModal.ts (100%) rename src/{stories/Homework2 => }/Interfaces/IShortProduct.ts (100%) rename src/{stories/Homework2 => }/Interfaces/ITrashButton.ts (100%) rename src/features/{ => LangSwitcher}/LangSwitcher.sass (100%) rename src/features/{ => LangSwitcher}/LangSwitcher.tsx (100%) create mode 100644 src/widgets/modal/Modal.css create mode 100644 src/widgets/modal/Modal.tsx create mode 100644 src/widgets/modal/ModalOverlay.css create mode 100644 src/widgets/modal/ModalOverlay.tsx diff --git a/package-lock.json b/package-lock.json index 285a0063e..0caa380a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,11 @@ "version": "1.0.0", "license": "MIT", "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.6.0", + "@fortawesome/free-brands-svg-icons": "^6.6.0", + "@fortawesome/free-regular-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@fortawesome/react-fontawesome": "^0.2.2", "clsx": "^1.2.1", "i18next": "^23.15.2", "i18next-browser-languagedetector": "^8.0.0", @@ -2644,6 +2649,76 @@ "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==", "dev": true }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", + "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "license": "MIT", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz", + "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-regular-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz", + "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "license": "(CC-BY-4.0 AND MIT)", + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "license": "MIT", + "dependencies": { + "prop-types": "^15.8.1" + }, + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "react": ">=16.3" + } + }, "node_modules/@humanwhocodes/config-array": { "version": "0.10.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz", @@ -18998,7 +19073,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -19836,7 +19910,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "dependencies": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -19846,8 +19919,7 @@ "node_modules/prop-types/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==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, "node_modules/proxy-addr": { "version": "2.0.7", @@ -25544,6 +25616,51 @@ "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==", "dev": true }, + "@fortawesome/fontawesome-common-types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.6.0.tgz", + "integrity": "sha512-xyX0X9mc0kyz9plIyryrRbl7ngsA9jz77mCZJsUkLl+ZKs0KWObgaEBoSgQiYWAsSmjz/yjl0F++Got0Mdp4Rw==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.6.0.tgz", + "integrity": "sha512-KHwPkCk6oRT4HADE7smhfsKudt9N/9lm6EJ5BVg0tD1yPA5hht837fB87F8pn15D8JfTqQOjhKTktwmLMiD7Kg==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.6.0" + } + }, + "@fortawesome/free-brands-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.6.0.tgz", + "integrity": "sha512-1MPD8lMNW/earme4OQi1IFHtmHUwAKgghXlNwWi9GO7QkTfD+IIaYpIai4m2YJEzqfEji3jFHX1DZI5pbY/biQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.6.0" + } + }, + "@fortawesome/free-regular-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.6.0.tgz", + "integrity": "sha512-Yv9hDzL4aI73BEwSEh20clrY8q/uLxawaQ98lekBx6t9dQKDHcDzzV1p2YtBGTtolYtNqcWdniOnhzB+JPnQEQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.6.0" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.6.0.tgz", + "integrity": "sha512-IYv/2skhEDFc2WGUcqvFJkeK39Q+HyPf5GHUrT/l2pKbtgEIv1al1TKd6qStR5OIwQdN1GZP54ci3y4mroJWjA==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.6.0" + } + }, + "@fortawesome/react-fontawesome": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.2.tgz", + "integrity": "sha512-EnkrprPNqI6SXJl//m29hpaNzOp1bruISWaOiRtkMi/xSvHJlzc2j2JAYS7egxt/EbjSNV/k6Xy0AQI6vB2+1g==", + "requires": { + "prop-types": "^15.8.1" + } + }, "@humanwhocodes/config-array": { "version": "0.10.7", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.10.7.tgz", @@ -37550,8 +37667,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { "version": "1.12.3", @@ -38151,7 +38267,6 @@ "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", - "dev": true, "requires": { "loose-envify": "^1.4.0", "object-assign": "^4.1.1", @@ -38161,8 +38276,7 @@ "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==", - "dev": true + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" } } }, diff --git a/package.json b/package.json index 2681b403c..fc28dce5a 100644 --- a/package.json +++ b/package.json @@ -70,6 +70,11 @@ "webpack-dev-server": "^4.15.0" }, "dependencies": { + "@fortawesome/fontawesome-svg-core": "^6.6.0", + "@fortawesome/free-brands-svg-icons": "^6.6.0", + "@fortawesome/free-regular-svg-icons": "^6.6.0", + "@fortawesome/free-solid-svg-icons": "^6.6.0", + "@fortawesome/react-fontawesome": "^0.2.2", "clsx": "^1.2.1", "i18next": "^23.15.2", "i18next-browser-languagedetector": "^8.0.0", diff --git a/src/stories/Homework2/Interfaces/IFullProduct.ts b/src/Interfaces/IFullProduct.ts similarity index 100% rename from src/stories/Homework2/Interfaces/IFullProduct.ts rename to src/Interfaces/IFullProduct.ts diff --git a/src/stories/Homework2/Interfaces/IModal.ts b/src/Interfaces/IModal.ts similarity index 100% rename from src/stories/Homework2/Interfaces/IModal.ts rename to src/Interfaces/IModal.ts diff --git a/src/stories/Homework2/Interfaces/IShortProduct.ts b/src/Interfaces/IShortProduct.ts similarity index 100% rename from src/stories/Homework2/Interfaces/IShortProduct.ts rename to src/Interfaces/IShortProduct.ts diff --git a/src/stories/Homework2/Interfaces/ITrashButton.ts b/src/Interfaces/ITrashButton.ts similarity index 100% rename from src/stories/Homework2/Interfaces/ITrashButton.ts rename to src/Interfaces/ITrashButton.ts diff --git a/src/components/buttons/ChangeThemeButton.tsx b/src/components/buttons/ChangeThemeButton.tsx index 3730f765d..e7ae17448 100644 --- a/src/components/buttons/ChangeThemeButton.tsx +++ b/src/components/buttons/ChangeThemeButton.tsx @@ -1,3 +1,5 @@ +import { faMoneyBill } from "@fortawesome/free-solid-svg-icons"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import React, { FC, useContext } from "react"; import { ThemeContext } from "src/context/themeContext"; @@ -5,7 +7,7 @@ const ChangeThemeButton: FC = () => { const { theme, toggleTheme } = useContext(ThemeContext); return ( - + ) } diff --git a/src/components/headers/Header.tsx b/src/components/headers/Header.tsx index 7f1d5eeb3..aa1d9be49 100644 --- a/src/components/headers/Header.tsx +++ b/src/components/headers/Header.tsx @@ -2,7 +2,7 @@ import React, { FC } from 'react'; import { withTranslation } from 'react-i18next'; import Logo from '../logo/Logo'; -import { LangSwitcher } from 'src/features/LangSwitcher'; +import { LangSwitcher } from 'src/features/LangSwitcher/LangSwitcher'; import ChangeThemeButton from '../buttons/ChangeThemeButton'; export const HeaderOrigin: FC = () => { diff --git a/src/features/LangSwitcher.sass b/src/features/LangSwitcher/LangSwitcher.sass similarity index 100% rename from src/features/LangSwitcher.sass rename to src/features/LangSwitcher/LangSwitcher.sass diff --git a/src/features/LangSwitcher.tsx b/src/features/LangSwitcher/LangSwitcher.tsx similarity index 100% rename from src/features/LangSwitcher.tsx rename to src/features/LangSwitcher/LangSwitcher.tsx diff --git a/src/index.html b/src/index.html index c8a8e9634..372160e9f 100644 --- a/src/index.html +++ b/src/index.html @@ -7,5 +7,6 @@
+
diff --git a/src/stories/Homework2/FullProduct/FullProduct.tsx b/src/stories/Homework2/FullProduct/FullProduct.tsx index 17c34d061..3763784b5 100644 --- a/src/stories/Homework2/FullProduct/FullProduct.tsx +++ b/src/stories/Homework2/FullProduct/FullProduct.tsx @@ -1,6 +1,6 @@ import React from "react"; import TrashButton from "../TrashButton/TrashButton"; -import IFullProduct from "../Interfaces/IFullProduct"; +import IFullProduct from "../../../Interfaces/IFullProduct"; function FullProduct({ price, images, category, title, description }: IFullProduct) { diff --git a/src/stories/Homework2/ShortProduct/ShortProduct.tsx b/src/stories/Homework2/ShortProduct/ShortProduct.tsx index 3b38ba821..42fd63e56 100644 --- a/src/stories/Homework2/ShortProduct/ShortProduct.tsx +++ b/src/stories/Homework2/ShortProduct/ShortProduct.tsx @@ -1,5 +1,5 @@ -import IShortProduct from "../Interfaces/IShortProduct"; +import IShortProduct from "../../../Interfaces/IShortProduct"; import TrashButton from "../TrashButton/TrashButton"; import React from "react"; diff --git a/src/stories/Homework2/TrashButton/TrashButton.tsx b/src/stories/Homework2/TrashButton/TrashButton.tsx index 3174491f3..d3c3f616e 100644 --- a/src/stories/Homework2/TrashButton/TrashButton.tsx +++ b/src/stories/Homework2/TrashButton/TrashButton.tsx @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import ITrashButton from '../Interfaces/ITrashButton'; +import ITrashButton from '../../../Interfaces/ITrashButton'; function TrashButton({ counter }: ITrashButton) { return ( diff --git a/src/stories/Homework2/modals/Modal.tsx b/src/stories/Homework2/modals/Modal.tsx index 9db134a63..07d0b8d6a 100644 --- a/src/stories/Homework2/modals/Modal.tsx +++ b/src/stories/Homework2/modals/Modal.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import ModalOverlay from "./ModalOverlay"; import './Modal.css'; -import IModal from '../Interfaces/IModal'; +import IModal from '../../../Interfaces/IModal'; // const modalRoot = document.getElementById('root-modal'); diff --git a/src/widgets/main/Main.tsx b/src/widgets/main/Main.tsx index d2ca5c176..393bca2fe 100644 --- a/src/widgets/main/Main.tsx +++ b/src/widgets/main/Main.tsx @@ -1,9 +1,17 @@ -import React from 'react'; +import React, { useState } from 'react'; import logo from '../../app/logo.svg'; import { useTranslation } from 'react-i18next'; +import Modal from '../modal/Modal'; export default function Main() { const { t } = useTranslation(); + const [visible, setVisible] = useState(false); + const onOpen = () => { + setVisible(true); + }; + const onClosed = () => { + setVisible(false); + }; return (
@@ -15,6 +23,12 @@ export default function Main() {
  • {t(`key3`)}
  • {t(`key4`)}
  • + + +

    Привет мир!

    +
    ); diff --git a/src/widgets/modal/Modal.css b/src/widgets/modal/Modal.css new file mode 100644 index 000000000..8c2731d40 --- /dev/null +++ b/src/widgets/modal/Modal.css @@ -0,0 +1,7 @@ +.modal-container { + display: block !important; + width: 39%; + height: 88%; + left: 30%; + overflow: hidden; +} \ No newline at end of file diff --git a/src/widgets/modal/Modal.tsx b/src/widgets/modal/Modal.tsx new file mode 100644 index 000000000..0d557451c --- /dev/null +++ b/src/widgets/modal/Modal.tsx @@ -0,0 +1,35 @@ +import React from 'react'; +import ReactDOM from 'react-dom'; +import ModalOverlay from './ModalOverlay'; +import './Modal.css'; +import IModal from 'src/Interfaces/IModal'; + +const modalRoot = document.getElementById('root-modal'); + +function Modal({ visible, header = '', children, onClose }: IModal) { + return visible + ? ReactDOM.createPortal( + <> +
    +
    +
    +
    +
    +

    {header}

    +
    +

    + × +

    +
    +
    {children}
    +
    +
    +
    + + , + modalRoot + ) + : null; +} + +export default Modal; diff --git a/src/widgets/modal/ModalOverlay.css b/src/widgets/modal/ModalOverlay.css new file mode 100644 index 000000000..9f5127f86 --- /dev/null +++ b/src/widgets/modal/ModalOverlay.css @@ -0,0 +1,3 @@ +.modal-overlay { + opacity: 0.6 +} \ No newline at end of file diff --git a/src/widgets/modal/ModalOverlay.tsx b/src/widgets/modal/ModalOverlay.tsx new file mode 100644 index 000000000..7fa6ea84c --- /dev/null +++ b/src/widgets/modal/ModalOverlay.tsx @@ -0,0 +1,12 @@ +import './ModalOverlay.css' +import React from 'react'; + +function ModalOverlay() { + return ( +
    +
    + ); +} + +export default ModalOverlay; \ No newline at end of file From 9f896e46fecb7a51a2693e01756a877978bb49db Mon Sep 17 00:00:00 2001 From: Yurabox Date: Sun, 20 Oct 2024 00:18:07 +0300 Subject: [PATCH 25/53] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/main.ts | 16 ++-- .vscode/launch.json | 15 +++ package-lock.json | 36 ++++++- package.json | 4 +- src/Interfaces/IModal.ts | 12 +-- src/app/App.tsx | 13 +-- .../ChangeThemeButton/ChangeThemeButton.tsx | 14 +++ src/components/ChangeThemeButton/index.ts | 1 + src/components/FullProduct/FullProduct.tsx | 25 +++++ src/components/{headers => Header}/Header.tsx | 3 +- src/components/Header/index.ts | 1 + src/components/ListProduct/ListProduct.tsx | 21 ++++ src/components/ShortProduct/ShortProduct.tsx | 20 ++++ src/components/TrashButton/TrashButton.tsx | 16 ++++ src/components/TrashButton/index.ts | 1 + src/components/TrashProduct/TrashProduct.tsx | 13 +++ src/components/buttons/ChangeThemeButton.tsx | 14 --- src/components/layouts/Layout.tsx | 2 +- src/homeworks/ts1/3_write.ts | 96 +++++++++---------- .../Homework2/FullProduct/FullProduct.tsx | 3 +- .../Homework2/ShortProduct/ShortProduct.tsx | 30 +++--- .../TrashButton/TrashButton.stories.ts | 22 ++--- .../Homework2/TrashButton/TrashButton.tsx | 27 +++--- src/widgets/main/Main.tsx | 54 ++++++++++- src/widgets/modal/Modal.tsx | 2 +- tsconfig.json | 43 +++++---- 26 files changed, 345 insertions(+), 159 deletions(-) create mode 100644 .vscode/launch.json create mode 100644 src/components/ChangeThemeButton/ChangeThemeButton.tsx create mode 100644 src/components/ChangeThemeButton/index.ts create mode 100644 src/components/FullProduct/FullProduct.tsx rename src/components/{headers => Header}/Header.tsx (91%) create mode 100644 src/components/Header/index.ts create mode 100644 src/components/ListProduct/ListProduct.tsx create mode 100644 src/components/ShortProduct/ShortProduct.tsx create mode 100644 src/components/TrashButton/TrashButton.tsx create mode 100644 src/components/TrashButton/index.ts create mode 100644 src/components/TrashProduct/TrashProduct.tsx delete mode 100644 src/components/buttons/ChangeThemeButton.tsx diff --git a/.storybook/main.ts b/.storybook/main.ts index ff76e46b1..b9786a929 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,19 +1,19 @@ const config = { - stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)"], + stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], staticDirs: ['../src/assets'], addons: [ - "@storybook/addon-links", - "@storybook/addon-essentials", - "@storybook/addon-interactions", - "@storybook/preset-scss", - "@storybook/addon-mdx-gfm" + '@storybook/addon-links', + '@storybook/addon-essentials', + '@storybook/addon-interactions', + '@storybook/preset-scss', + '@storybook/addon-mdx-gfm', ], framework: { - name: "@storybook/react-webpack5", + name: '@storybook/react-webpack5', options: {}, }, docs: { - autodocs: "tag", + autodocs: 'tag', }, }; export default config; diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..2ba986f6f --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,15 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "chrome", + "request": "launch", + "name": "Launch Chrome against localhost", + "url": "http://localhost:8080", + "webRoot": "${workspaceFolder}" + } + ] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0caa380a3..5a8e51fe1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,9 +18,11 @@ "i18next": "^23.15.2", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", + "intersection-observer": "^0.12.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-i18next": "^15.0.2" + "react-i18next": "^15.0.2", + "react-intersection-observer": "^9.13.1" }, "devDependencies": { "@babel/core": "^7.22.1", @@ -14269,6 +14271,12 @@ "node": ">= 0.4" } }, + "node_modules/intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==", + "license": "Apache-2.0" + }, "node_modules/invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -20270,6 +20278,21 @@ } } }, + "node_modules/react-intersection-observer": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.1.tgz", + "integrity": "sha512-tSzDaTy0qwNPLJHg8XZhlyHTgGW6drFKTtvjdL+p6um12rcnp8Z5XstE+QNBJ7c64n5o0Lj4ilUleA41bmDoMw==", + "license": "MIT", + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -34250,6 +34273,11 @@ "side-channel": "^1.0.4" } }, + "intersection-observer": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/intersection-observer/-/intersection-observer-0.12.2.tgz", + "integrity": "sha512-7m1vEcPCxXYI8HqnL8CKI6siDyD+eIWSwgB3DZA+ZTogxk9I4CDnj4wilt9x/+/QbHI4YG5YZNmC6458/e9Ktg==" + }, "invariant": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", @@ -38545,6 +38573,12 @@ "html-parse-stringify": "^3.0.1" } }, + "react-intersection-observer": { + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.1.tgz", + "integrity": "sha512-tSzDaTy0qwNPLJHg8XZhlyHTgGW6drFKTtvjdL+p6um12rcnp8Z5XstE+QNBJ7c64n5o0Lj4ilUleA41bmDoMw==", + "requires": {} + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", diff --git a/package.json b/package.json index fc28dce5a..b7e66cd97 100644 --- a/package.json +++ b/package.json @@ -79,8 +79,10 @@ "i18next": "^23.15.2", "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", + "intersection-observer": "^0.12.2", "react": "^18.2.0", "react-dom": "^18.2.0", - "react-i18next": "^15.0.2" + "react-i18next": "^15.0.2", + "react-intersection-observer": "^9.13.1" } } diff --git a/src/Interfaces/IModal.ts b/src/Interfaces/IModal.ts index cb766b8df..77a26e687 100644 --- a/src/Interfaces/IModal.ts +++ b/src/Interfaces/IModal.ts @@ -1,8 +1,8 @@ -import React from "react" +import React from 'react'; export default interface IModal { - children?: React.ReactNode, - visible: boolean, - header: string, - onClose: () => void -} \ No newline at end of file + children?: React.ReactNode; + visible: boolean; + header: string; + onClose: () => void; +} diff --git a/src/app/App.tsx b/src/app/App.tsx index d8edf5811..5b0e47639 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -4,18 +4,15 @@ import './App.css'; import { Theme, ThemeContext } from 'src/context/themeContext'; import { LocalizationInitiator } from 'src/localization/LocalizationInitiator'; import Layout from 'src/components/layouts/Layout'; -import { I18nextProvider, useTranslation } from 'react-i18next'; -import i18n, { Locale } from 'src/localization/settings'; -import { translation } from 'src/localization/translation'; +import { I18nextProvider } from 'react-i18next'; +import i18n from 'src/localization/settings'; import Main from 'src/widgets/main/Main'; - - function App() { - let [theme, setTheme] = useState("light"); + let [theme, setTheme] = useState('light'); const toggleTheme = () => { - setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light")); + setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light')); }; return ( @@ -27,7 +24,7 @@ function App() {
    - + ); diff --git a/src/components/ChangeThemeButton/ChangeThemeButton.tsx b/src/components/ChangeThemeButton/ChangeThemeButton.tsx new file mode 100644 index 000000000..6e8e20b7e --- /dev/null +++ b/src/components/ChangeThemeButton/ChangeThemeButton.tsx @@ -0,0 +1,14 @@ +import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import React, { FC, useContext } from 'react'; +import { ThemeContext } from 'src/context/themeContext'; + +export const ChangeThemeButton: FC = () => { + const { theme, toggleTheme } = useContext(ThemeContext); + return ( + + {theme === 'light' ? : } + + ); +}; + diff --git a/src/components/ChangeThemeButton/index.ts b/src/components/ChangeThemeButton/index.ts new file mode 100644 index 000000000..1af308dfb --- /dev/null +++ b/src/components/ChangeThemeButton/index.ts @@ -0,0 +1 @@ +export * from './ChangeThemeButton'; \ No newline at end of file diff --git a/src/components/FullProduct/FullProduct.tsx b/src/components/FullProduct/FullProduct.tsx new file mode 100644 index 000000000..8771df34e --- /dev/null +++ b/src/components/FullProduct/FullProduct.tsx @@ -0,0 +1,25 @@ +import React from 'react'; + +import { TrashButton } from '../TrashButton/TrashButton'; +import IFullProduct from 'src/Interfaces/IFullProduct'; + + +function FullProduct({ price, images, category, title, description }: IFullProduct) { + return ( +
    + + {images?.map((image, i) => ( + ... + ))} + +
    +
    {title}
    +
    {category}
    +

    {description}

    +

    цена: {price}р

    + +
    +
    + ); +} +export default FullProduct; diff --git a/src/components/headers/Header.tsx b/src/components/Header/Header.tsx similarity index 91% rename from src/components/headers/Header.tsx rename to src/components/Header/Header.tsx index aa1d9be49..39f55d3fa 100644 --- a/src/components/headers/Header.tsx +++ b/src/components/Header/Header.tsx @@ -1,9 +1,8 @@ import React, { FC } from 'react'; import { withTranslation } from 'react-i18next'; - import Logo from '../logo/Logo'; import { LangSwitcher } from 'src/features/LangSwitcher/LangSwitcher'; -import ChangeThemeButton from '../buttons/ChangeThemeButton'; +import { ChangeThemeButton } from '../ChangeThemeButton'; export const HeaderOrigin: FC = () => { return ( diff --git a/src/components/Header/index.ts b/src/components/Header/index.ts new file mode 100644 index 000000000..64f7c8742 --- /dev/null +++ b/src/components/Header/index.ts @@ -0,0 +1 @@ +export * from './Header'; \ No newline at end of file diff --git a/src/components/ListProduct/ListProduct.tsx b/src/components/ListProduct/ListProduct.tsx new file mode 100644 index 000000000..b70016a13 --- /dev/null +++ b/src/components/ListProduct/ListProduct.tsx @@ -0,0 +1,21 @@ +import React, { FC } from 'react'; +import IFullProduct from 'src/Interfaces/IFullProduct'; +import ShortProduct from '../ShortProduct/ShortProduct'; +import FullProduct from '../FullProduct/FullProduct'; + +export const ListProduct: FC<{ products: IFullProduct[] }> = ({ products }) => { + return ( + <> + {products.map((product, i: number) => ( + + ))} + + ); +}; diff --git a/src/components/ShortProduct/ShortProduct.tsx b/src/components/ShortProduct/ShortProduct.tsx new file mode 100644 index 000000000..5ceed73e7 --- /dev/null +++ b/src/components/ShortProduct/ShortProduct.tsx @@ -0,0 +1,20 @@ +import IShortProduct from 'src/Interfaces/IShortProduct'; +import { TrashButton } from '../TrashButton/TrashButton'; + +import React from 'react'; + +function ShortProduct({ title, price, description, image }: IShortProduct) { + return ( +
    + ... +
    +
    {title}
    +
    {price}
    +

    {description}

    + +
    +
    + ); +} + +export default ShortProduct; diff --git a/src/components/TrashButton/TrashButton.tsx b/src/components/TrashButton/TrashButton.tsx new file mode 100644 index 000000000..f8330ab3b --- /dev/null +++ b/src/components/TrashButton/TrashButton.tsx @@ -0,0 +1,16 @@ +import React from 'react'; +import ITrashButton from '../../Interfaces/ITrashButton'; + +export const TrashButton = ({ counter }: ITrashButton) => { + return counter == 0 ? ( + + ) : ( +
    + - + + + +
    + ); +}; diff --git a/src/components/TrashButton/index.ts b/src/components/TrashButton/index.ts new file mode 100644 index 000000000..2b962cd2b --- /dev/null +++ b/src/components/TrashButton/index.ts @@ -0,0 +1 @@ +export * from './TrashButton'; \ No newline at end of file diff --git a/src/components/TrashProduct/TrashProduct.tsx b/src/components/TrashProduct/TrashProduct.tsx new file mode 100644 index 000000000..d2e247a10 --- /dev/null +++ b/src/components/TrashProduct/TrashProduct.tsx @@ -0,0 +1,13 @@ +import React from "react"; +function TrashProduct() { + return ( +
    + ... +
    + +
    +
    + ) + +} +export default TrashProduct; \ No newline at end of file diff --git a/src/components/buttons/ChangeThemeButton.tsx b/src/components/buttons/ChangeThemeButton.tsx deleted file mode 100644 index e7ae17448..000000000 --- a/src/components/buttons/ChangeThemeButton.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { faMoneyBill } from "@fortawesome/free-solid-svg-icons"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import React, { FC, useContext } from "react"; -import { ThemeContext } from "src/context/themeContext"; - -const ChangeThemeButton: FC = () => { - const { theme, toggleTheme } = useContext(ThemeContext); - return ( - - - - ) -} -export default ChangeThemeButton; \ No newline at end of file diff --git a/src/components/layouts/Layout.tsx b/src/components/layouts/Layout.tsx index 406c95a62..386826407 100644 --- a/src/components/layouts/Layout.tsx +++ b/src/components/layouts/Layout.tsx @@ -1,6 +1,6 @@ import React from "react"; import "./Layout.css" -import { Header } from "../headers/Header"; +import { Header } from "../Header"; function Layout() { return ( diff --git a/src/homeworks/ts1/3_write.ts b/src/homeworks/ts1/3_write.ts index adf621c89..3497ffdb6 100644 --- a/src/homeworks/ts1/3_write.ts +++ b/src/homeworks/ts1/3_write.ts @@ -1,4 +1,4 @@ -import { faker } from "@faker-js/faker"; +import { faker } from '@faker-js/faker'; /** * Функции написанные здесь пригодятся на последующих уроках * С помощью этих функций мы будем добавлять элементы в список для проверки динамической загрузки @@ -44,58 +44,58 @@ import { faker } from "@faker-js/faker"; * - type ('Profit') * */ export type Category = { - id: number, - name: string, - photo?: string + id: number; + name: string; + photo?: string; }; export type Product = { - id: number, - name: string, - photo: string, - desc?: string, - createdAt: string, - oldPrice?: number, - price: number - category: Category + id: number; + name: string; + photo: string; + desc?: string; + createdAt: string; + oldPrice?: number; + price: number; + category: Category; }; export type Cost = { - id: string, - name: string, - desc?: string - createdAt: string, - amount: number, - category: Category, - type: Cost + id: string; + name: string; + desc?: string; + createdAt: string; + amount: number; + category: Category; + type: Cost; }; export type Profit = { - id: string, - name: string, - desc?: string - createdAt: string, - amount: number, - category: Category, - type: Profit + id: string; + name: string; + desc?: string; + createdAt: string; + amount: number; + category: Category; + type: Profit; }; /** * Создает случайный продукт (Product). * Принимает дату создания (строка) * */ export const createRandomProduct = (createdAt: string): Product => ({ + id: faker.number.int(), + name: faker.commerce.product.name, + photo: faker.image.avatar(), + desc: 'desc', + createdAt: createdAt, + oldPrice: +faker.commerce.price({ min: 1000, max: 5000 }), + price: +faker.commerce.price({ min: 1000, max: 5000 }), + category: { id: faker.number.int(), - name: faker.person.fullName(), + name: faker.company.name(), photo: faker.image.avatar(), - desc: "desc", - createdAt: createdAt, - oldPrice: faker.number.float(), - price: faker.number.float(), - category: { - id: faker.number.int(), - name: faker.company.name(), - photo: faker.image.avatar() - } + }, }); /** @@ -103,17 +103,15 @@ export const createRandomProduct = (createdAt: string): Product => ({ * Принимает дату создания (строка) * */ export const createRandomOperation = (createdAt: string): Cost | Profit => ({ - id: faker.string.sample(), - createdAt: createdAt, - name: faker.person.fullName(), - desc: "desc", - amount: faker.number.int(), - category: { - id: faker.number.int(), - name: faker.company.name(), - photo: faker.image.avatar() - }, - type: this - + id: faker.string.sample(), + createdAt: createdAt, + name: faker.person.fullName(), + desc: 'desc', + amount: faker.number.int(), + category: { + id: faker.number.int(), + name: faker.company.name(), + photo: faker.image.avatar(), + }, + type: this, }); - diff --git a/src/stories/Homework2/FullProduct/FullProduct.tsx b/src/stories/Homework2/FullProduct/FullProduct.tsx index 3763784b5..2fdd1be8a 100644 --- a/src/stories/Homework2/FullProduct/FullProduct.tsx +++ b/src/stories/Homework2/FullProduct/FullProduct.tsx @@ -1,7 +1,6 @@ import React from "react"; -import TrashButton from "../TrashButton/TrashButton"; import IFullProduct from "../../../Interfaces/IFullProduct"; - +import { TrashButton } from "../TrashButton/TrashButton"; function FullProduct({ price, images, category, title, description }: IFullProduct) { return ( diff --git a/src/stories/Homework2/ShortProduct/ShortProduct.tsx b/src/stories/Homework2/ShortProduct/ShortProduct.tsx index 42fd63e56..ab1b8426c 100644 --- a/src/stories/Homework2/ShortProduct/ShortProduct.tsx +++ b/src/stories/Homework2/ShortProduct/ShortProduct.tsx @@ -1,20 +1,20 @@ +import { TrashButton } from "../TrashButton/TrashButton"; +import IShortProduct from '../../../Interfaces/IShortProduct'; -import IShortProduct from "../../../Interfaces/IShortProduct"; -import TrashButton from "../TrashButton/TrashButton"; -import React from "react"; +import React from 'react'; function ShortProduct({ title, price, description, image }: IShortProduct) { - return ( -
    - ... -
    -
    {title}
    -
    {price}
    -

    {description}

    - -
    -
    - ); + return ( +
    + ... +
    +
    {title}
    +
    {price}
    +

    {description}

    + +
    +
    + ); } -export default ShortProduct; \ No newline at end of file +export default ShortProduct; diff --git a/src/stories/Homework2/TrashButton/TrashButton.stories.ts b/src/stories/Homework2/TrashButton/TrashButton.stories.ts index 3c19e52f8..20c8c93ac 100644 --- a/src/stories/Homework2/TrashButton/TrashButton.stories.ts +++ b/src/stories/Homework2/TrashButton/TrashButton.stories.ts @@ -1,23 +1,23 @@ import type { Meta, StoryObj } from '@storybook/react'; -import TrashButton from './TrashButton'; +import { TrashButton } from './TrashButton'; const meta: Meta = { - title: 'Homework2/TrashButton', - component: TrashButton, - tags: ['autodocs'] + title: 'Homework2/TrashButton', + component: TrashButton, + tags: ['autodocs'], }; export default meta; type Story = StoryObj; export const CounterZero: Story = { - args: { - counter: 0 - } + args: { + counter: 0, + }, }; export const CounterMoreZero: Story = { - args: { - counter: 1 - } -}; \ No newline at end of file + args: { + counter: 1, + }, +}; diff --git a/src/stories/Homework2/TrashButton/TrashButton.tsx b/src/stories/Homework2/TrashButton/TrashButton.tsx index d3c3f616e..a995a9868 100644 --- a/src/stories/Homework2/TrashButton/TrashButton.tsx +++ b/src/stories/Homework2/TrashButton/TrashButton.tsx @@ -1,17 +1,16 @@ import React from 'react'; -import PropTypes from 'prop-types'; import ITrashButton from '../../../Interfaces/ITrashButton'; -function TrashButton({ counter }: ITrashButton) { - return ( - (counter == 0 ? - : -
    - - - - + -
    - ) - ); -} -export default TrashButton; \ No newline at end of file +export const TrashButton = ({ counter }: ITrashButton) => { + return counter == 0 ? ( + + ) : ( +
    + - + + + +
    + ); +}; diff --git a/src/widgets/main/Main.tsx b/src/widgets/main/Main.tsx index 393bca2fe..a2d24102f 100644 --- a/src/widgets/main/Main.tsx +++ b/src/widgets/main/Main.tsx @@ -1,18 +1,64 @@ -import React, { useState } from 'react'; +import React, { useEffect, useState } from 'react'; import logo from '../../app/logo.svg'; import { useTranslation } from 'react-i18next'; import Modal from '../modal/Modal'; +import { ListProduct } from 'src/components/ListProduct/ListProduct'; +import { createRandomProduct } from 'src/homeworks/ts1/3_write'; +import { useInView } from 'react-intersection-observer'; export default function Main() { - const { t } = useTranslation(); const [visible, setVisible] = useState(false); + const [products, setProducts] = useState([]); + const { t } = useTranslation(); + const { ref, inView } = useInView({ threshold: 0.7 }); + + function addProduct() { + const createdAt = '2023-06-06T12:06:56.957Z'; + const product = createRandomProduct(createdAt); + + setProducts([ + ...products, + { + price: product.price, + category: product.category.name, + title: product.name, + description: product.desc, + images: [product.photo], + }, + ]); + } + + useEffect(() => { + addProduct(); + }, []); + + useEffect(() => { + if (inView) { + addProduct(); + } + }, [addProduct,inView]); + const onOpen = () => { setVisible(true); }; const onClosed = () => { + setProducts([]); setVisible(false); }; + const modal = ( + +
    + +
    +
    + +
    +
    + ); + return (
    @@ -26,9 +72,7 @@ export default function Main() { - -

    Привет мир!

    -
    + {modal}
    ); diff --git a/src/widgets/modal/Modal.tsx b/src/widgets/modal/Modal.tsx index 0d557451c..da80b47c3 100644 --- a/src/widgets/modal/Modal.tsx +++ b/src/widgets/modal/Modal.tsx @@ -11,7 +11,7 @@ function Modal({ visible, header = '', children, onClose }: IModal) { ? ReactDOM.createPortal( <>
    -
    +
    diff --git a/tsconfig.json b/tsconfig.json index 1fd6f7887..94a8c636d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,23 +1,24 @@ { - "compilerOptions": { - "outDir": "./dist", - "baseUrl": ".", - "paths": { - "src/*": ["src/*"] - }, - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "allowSyntheticDefaultImports": true, - "skipLibCheck": true, - "esModuleInterop": true, - "target": "ES2015", - "module": "esnext", - "sourceMap": true, - "noImplicitAny": true, - "downlevelIteration": true, - "moduleResolution": "node", - "resolveJsonModule": true, - "jsx": "react" - } + "compilerOptions": { + "outDir": "./dist", + "baseUrl": ".", + "paths": { + "src/*": ["src/*"], + "@components/*": ["src/components/*"], + "@images/*": ["scr/assets/*"] + }, + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "allowSyntheticDefaultImports": true, + "skipLibCheck": true, + "esModuleInterop": true, + "target": "ES2015", + "module": "esnext", + "sourceMap": true, + "noImplicitAny": true, + "downlevelIteration": true, + "moduleResolution": "node", + "resolveJsonModule": true, + "jsx": "react" + } } - \ No newline at end of file From 5e1c30abb9b3ee65ac2d92c89a4072694cd2cb53 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Sun, 27 Oct 2024 23:24:17 +0300 Subject: [PATCH 26/53] react patterns --- package-lock.json | 12 +++++++++ package.json | 1 + src/app/App.tsx | 3 +-- src/components/FullProduct/FullProduct.tsx | 25 ------------------ src/components/ListProduct/ListProduct.tsx | 21 --------------- src/components/ShortProduct/ShortProduct.tsx | 20 -------------- src/components/TrashButton/TrashButton.tsx | 16 ----------- src/components/TrashButton/index.ts | 1 - .../interfaces}/IFullProduct.ts | 0 .../interfaces}/IModal.ts | 0 .../interfaces}/IShortProduct.ts | 0 .../interfaces}/ITrashButton.ts | 0 src/shared/.DS_Store | Bin 0 -> 6148 bytes src/shared/ui/.DS_Store | Bin 0 -> 6148 bytes src/shared/ui/ChangeThemeButton/.DS_Store | Bin 0 -> 6148 bytes .../ChangeThemeButton/ChangeThemeButton.tsx | 0 .../ui}/ChangeThemeButton/index.ts | 0 src/shared/ui/fullProduct/FullProduct.tsx | 24 +++++++++++++++++ src/shared/ui/fullProduct/index.ts | 1 + .../Header => shared/ui/header}/Header.tsx | 0 .../Header => shared/ui/header}/index.ts | 0 .../ui}/layouts/Layout.css | 0 .../ui}/layouts/Layout.tsx | 2 +- src/shared/ui/listProduct/ListProduct.tsx | 14 ++++++++++ src/shared/ui/listProduct/index.ts | 1 + src/{components => shared/ui}/logo/Logo.tsx | 2 +- src/shared/ui/shortProduct/ShortProduct.tsx | 20 ++++++++++++++ src/shared/ui/trashButton/TrashButton.tsx | 21 +++++++++++++++ src/shared/ui/trashButton/index.ts | 1 + .../ui/trashProduct}/TrashProduct.tsx | 0 .../Homework2/FullProduct/FullProduct.tsx | 2 +- .../Homework2/ShortProduct/ShortProduct.tsx | 2 +- .../Homework2/TrashButton/TrashButton.tsx | 2 +- src/stories/Homework2/modals/Modal.tsx | 2 +- src/widgets/main/Main.tsx | 3 ++- src/widgets/modal/Modal.tsx | 2 +- tsconfig.json | 3 ++- 37 files changed, 107 insertions(+), 94 deletions(-) delete mode 100644 src/components/FullProduct/FullProduct.tsx delete mode 100644 src/components/ListProduct/ListProduct.tsx delete mode 100644 src/components/ShortProduct/ShortProduct.tsx delete mode 100644 src/components/TrashButton/TrashButton.tsx delete mode 100644 src/components/TrashButton/index.ts rename src/{Interfaces => entities/interfaces}/IFullProduct.ts (100%) rename src/{Interfaces => entities/interfaces}/IModal.ts (100%) rename src/{Interfaces => entities/interfaces}/IShortProduct.ts (100%) rename src/{Interfaces => entities/interfaces}/ITrashButton.ts (100%) create mode 100644 src/shared/.DS_Store create mode 100644 src/shared/ui/.DS_Store create mode 100644 src/shared/ui/ChangeThemeButton/.DS_Store rename src/{components => shared/ui}/ChangeThemeButton/ChangeThemeButton.tsx (100%) rename src/{components => shared/ui}/ChangeThemeButton/index.ts (100%) create mode 100644 src/shared/ui/fullProduct/FullProduct.tsx create mode 100644 src/shared/ui/fullProduct/index.ts rename src/{components/Header => shared/ui/header}/Header.tsx (100%) rename src/{components/Header => shared/ui/header}/index.ts (100%) rename src/{components => shared/ui}/layouts/Layout.css (100%) rename src/{components => shared/ui}/layouts/Layout.tsx (78%) create mode 100644 src/shared/ui/listProduct/ListProduct.tsx create mode 100644 src/shared/ui/listProduct/index.ts rename src/{components => shared/ui}/logo/Logo.tsx (75%) create mode 100644 src/shared/ui/shortProduct/ShortProduct.tsx create mode 100644 src/shared/ui/trashButton/TrashButton.tsx create mode 100644 src/shared/ui/trashButton/index.ts rename src/{components/TrashProduct => shared/ui/trashProduct}/TrashProduct.tsx (100%) diff --git a/package-lock.json b/package-lock.json index 5a8e51fe1..115266f0d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,6 +19,7 @@ "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", "intersection-observer": "^0.12.2", + "module-alias": "^2.2.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^15.0.2", @@ -18655,6 +18656,12 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "node_modules/module-alias": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", + "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==", + "license": "MIT" + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -37384,6 +37391,11 @@ "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", "dev": true }, + "module-alias": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/module-alias/-/module-alias-2.2.3.tgz", + "integrity": "sha512-23g5BFj4zdQL/b6tor7Ji+QY4pEfNH784BMslY9Qb0UnJWRAt+lQGLYmRaM0KDBwIG23ffEBELhZDP2rhi9f/Q==" + }, "mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", diff --git a/package.json b/package.json index b7e66cd97..0a4792594 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "i18next-browser-languagedetector": "^8.0.0", "i18next-http-backend": "^2.6.2", "intersection-observer": "^0.12.2", + "module-alias": "^2.2.3", "react": "^18.2.0", "react-dom": "^18.2.0", "react-i18next": "^15.0.2", diff --git a/src/app/App.tsx b/src/app/App.tsx index 5b0e47639..979565c32 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -1,12 +1,11 @@ import React, { useState } from 'react'; - import './App.css'; import { Theme, ThemeContext } from 'src/context/themeContext'; import { LocalizationInitiator } from 'src/localization/LocalizationInitiator'; -import Layout from 'src/components/layouts/Layout'; import { I18nextProvider } from 'react-i18next'; import i18n from 'src/localization/settings'; import Main from 'src/widgets/main/Main'; +import Layout from 'src/shared/ui/layouts/Layout'; function App() { let [theme, setTheme] = useState('light'); diff --git a/src/components/FullProduct/FullProduct.tsx b/src/components/FullProduct/FullProduct.tsx deleted file mode 100644 index 8771df34e..000000000 --- a/src/components/FullProduct/FullProduct.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React from 'react'; - -import { TrashButton } from '../TrashButton/TrashButton'; -import IFullProduct from 'src/Interfaces/IFullProduct'; - - -function FullProduct({ price, images, category, title, description }: IFullProduct) { - return ( -
    - - {images?.map((image, i) => ( - ... - ))} - -
    -
    {title}
    -
    {category}
    -

    {description}

    -

    цена: {price}р

    - -
    -
    - ); -} -export default FullProduct; diff --git a/src/components/ListProduct/ListProduct.tsx b/src/components/ListProduct/ListProduct.tsx deleted file mode 100644 index b70016a13..000000000 --- a/src/components/ListProduct/ListProduct.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import React, { FC } from 'react'; -import IFullProduct from 'src/Interfaces/IFullProduct'; -import ShortProduct from '../ShortProduct/ShortProduct'; -import FullProduct from '../FullProduct/FullProduct'; - -export const ListProduct: FC<{ products: IFullProduct[] }> = ({ products }) => { - return ( - <> - {products.map((product, i: number) => ( - - ))} - - ); -}; diff --git a/src/components/ShortProduct/ShortProduct.tsx b/src/components/ShortProduct/ShortProduct.tsx deleted file mode 100644 index 5ceed73e7..000000000 --- a/src/components/ShortProduct/ShortProduct.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import IShortProduct from 'src/Interfaces/IShortProduct'; -import { TrashButton } from '../TrashButton/TrashButton'; - -import React from 'react'; - -function ShortProduct({ title, price, description, image }: IShortProduct) { - return ( -
    - ... -
    -
    {title}
    -
    {price}
    -

    {description}

    - -
    -
    - ); -} - -export default ShortProduct; diff --git a/src/components/TrashButton/TrashButton.tsx b/src/components/TrashButton/TrashButton.tsx deleted file mode 100644 index f8330ab3b..000000000 --- a/src/components/TrashButton/TrashButton.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import ITrashButton from '../../Interfaces/ITrashButton'; - -export const TrashButton = ({ counter }: ITrashButton) => { - return counter == 0 ? ( - - ) : ( -
    - - - - + -
    - ); -}; diff --git a/src/components/TrashButton/index.ts b/src/components/TrashButton/index.ts deleted file mode 100644 index 2b962cd2b..000000000 --- a/src/components/TrashButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TrashButton'; \ No newline at end of file diff --git a/src/Interfaces/IFullProduct.ts b/src/entities/interfaces/IFullProduct.ts similarity index 100% rename from src/Interfaces/IFullProduct.ts rename to src/entities/interfaces/IFullProduct.ts diff --git a/src/Interfaces/IModal.ts b/src/entities/interfaces/IModal.ts similarity index 100% rename from src/Interfaces/IModal.ts rename to src/entities/interfaces/IModal.ts diff --git a/src/Interfaces/IShortProduct.ts b/src/entities/interfaces/IShortProduct.ts similarity index 100% rename from src/Interfaces/IShortProduct.ts rename to src/entities/interfaces/IShortProduct.ts diff --git a/src/Interfaces/ITrashButton.ts b/src/entities/interfaces/ITrashButton.ts similarity index 100% rename from src/Interfaces/ITrashButton.ts rename to src/entities/interfaces/ITrashButton.ts diff --git a/src/shared/.DS_Store b/src/shared/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..0bccecaca87f8dff9003f20688cfbecef0de6267 GIT binary patch literal 6148 zcmeHKUrWO<5Kp%0GKSCxg*^s*9XS8YKloDW`~p_=L1ngdXt6e9ZSBJt^jW`;WUY@ z@2>-XdxI4$4$Ex%_4~7YF!?GT|TlGpGL z`~q9PgnwZL=gv$eC!tYS2%)*w%$diz*Pi3?jE9IsI2qj`>JyQR#OPc`_6OsBPAk?i zEgn#)7)O-S0gaKLtgvl^Rlq9nuPMOa?p4~uHycwy?e90GkS0{)l2km;>2%n?wkL}^&Wf~D4%sLLo;#dc{x1JPYU^2jfDi(GG$i`p1~IsE}+%Ns4Q|>9D&V|Jo*3~ zb6hW|gq9=J4yj&J^c$FMMK{$d8iSouxyThambhKSj_DI(^ZTln*!;pi9n;xtpZkoMFlo=wa@$iv-8jY zi%zy?6|f5YR|*JcKiUs5B(t}!4376&AL%_38}k~C$_1I(j#YuT;%y{l=<~S%3=B3J S(E_tS0!jv(Sq1*80>1&Y{nD%e literal 0 HcmV?d00001 diff --git a/src/shared/ui/ChangeThemeButton/.DS_Store b/src/shared/ui/ChangeThemeButton/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5b01954259390a38729a6cf8bc5ba67bf65f7741 GIT binary patch literal 6148 zcmeHKJ5B>Z47DLeB$_mlDCY!`xWTd{1vMN1LLxv)*a9M@oEvdOc+WP74O>SD*{kvM zjP3b6Y3Cy%+U`~tA`=lAqY#zGfUtScbrQiuwLTtYEw}Ony&dT2FE(-RQ@O&N@8v~) z&VMbf?Boq!2hR-nD*NkA_psb_OJtF{zxRxT_{)QMOG=dtBm>Dn zGLQ@$l>waDl<}Ek>SQ1pNCv(c;Qmlhh)rO1w5#ybUtSu;aC=|w38W7mMO8_d|N6yXZ{z4mZO<;9oFKoAT PU|s}_kW|UQ5g7Oc{K+fk literal 0 HcmV?d00001 diff --git a/src/components/ChangeThemeButton/ChangeThemeButton.tsx b/src/shared/ui/ChangeThemeButton/ChangeThemeButton.tsx similarity index 100% rename from src/components/ChangeThemeButton/ChangeThemeButton.tsx rename to src/shared/ui/ChangeThemeButton/ChangeThemeButton.tsx diff --git a/src/components/ChangeThemeButton/index.ts b/src/shared/ui/ChangeThemeButton/index.ts similarity index 100% rename from src/components/ChangeThemeButton/index.ts rename to src/shared/ui/ChangeThemeButton/index.ts diff --git a/src/shared/ui/fullProduct/FullProduct.tsx b/src/shared/ui/fullProduct/FullProduct.tsx new file mode 100644 index 000000000..adb7131c4 --- /dev/null +++ b/src/shared/ui/fullProduct/FullProduct.tsx @@ -0,0 +1,24 @@ +import React, { FC } from 'react'; +import { TrashButton } from '../trashButton'; +import IFullProduct from 'src/entities/interfaces/IFullProduct'; + + +export const FullProduct: FC = ({ ...product }) => { + return ( +
    + + {product.images?.map((image, i) => ( + ... + ))} + +
    +
    {product.title}
    +
    {product.category}
    +

    {product.description}

    +

    цена: {product.price}р

    + +
    +
    + ); +} + diff --git a/src/shared/ui/fullProduct/index.ts b/src/shared/ui/fullProduct/index.ts new file mode 100644 index 000000000..7b0554564 --- /dev/null +++ b/src/shared/ui/fullProduct/index.ts @@ -0,0 +1 @@ +export * from './FullProduct'; \ No newline at end of file diff --git a/src/components/Header/Header.tsx b/src/shared/ui/header/Header.tsx similarity index 100% rename from src/components/Header/Header.tsx rename to src/shared/ui/header/Header.tsx diff --git a/src/components/Header/index.ts b/src/shared/ui/header/index.ts similarity index 100% rename from src/components/Header/index.ts rename to src/shared/ui/header/index.ts diff --git a/src/components/layouts/Layout.css b/src/shared/ui/layouts/Layout.css similarity index 100% rename from src/components/layouts/Layout.css rename to src/shared/ui/layouts/Layout.css diff --git a/src/components/layouts/Layout.tsx b/src/shared/ui/layouts/Layout.tsx similarity index 78% rename from src/components/layouts/Layout.tsx rename to src/shared/ui/layouts/Layout.tsx index 386826407..9e0a4db0f 100644 --- a/src/components/layouts/Layout.tsx +++ b/src/shared/ui/layouts/Layout.tsx @@ -1,6 +1,6 @@ import React from "react"; import "./Layout.css" -import { Header } from "../Header"; +import { Header } from "../header"; function Layout() { return ( diff --git a/src/shared/ui/listProduct/ListProduct.tsx b/src/shared/ui/listProduct/ListProduct.tsx new file mode 100644 index 000000000..d1d88687c --- /dev/null +++ b/src/shared/ui/listProduct/ListProduct.tsx @@ -0,0 +1,14 @@ +import React, { FC } from 'react'; +import IFullProduct from 'src/entities/interfaces/IFullProduct'; +import { FullProduct } from '../fullProduct/FullProduct'; + + +export const ListProduct: FC<{ products: IFullProduct[] }> = ({ products }) => { + return ( + <> + {products.map((product, i: number) => ( + + ))} + + ); +}; diff --git a/src/shared/ui/listProduct/index.ts b/src/shared/ui/listProduct/index.ts new file mode 100644 index 000000000..3deb6deff --- /dev/null +++ b/src/shared/ui/listProduct/index.ts @@ -0,0 +1 @@ +export * from './ListProduct'; \ No newline at end of file diff --git a/src/components/logo/Logo.tsx b/src/shared/ui/logo/Logo.tsx similarity index 75% rename from src/components/logo/Logo.tsx rename to src/shared/ui/logo/Logo.tsx index 7f7309ae4..b30851d0d 100644 --- a/src/components/logo/Logo.tsx +++ b/src/shared/ui/logo/Logo.tsx @@ -1,5 +1,5 @@ import React from "react"; -import logo from '../../assets/logo-image64.png'; +import logo from '../../../assets/logo-image64.png'; function Logo() { return (
    diff --git a/src/shared/ui/shortProduct/ShortProduct.tsx b/src/shared/ui/shortProduct/ShortProduct.tsx new file mode 100644 index 000000000..edb83668d --- /dev/null +++ b/src/shared/ui/shortProduct/ShortProduct.tsx @@ -0,0 +1,20 @@ + +import React from 'react'; +import { TrashButton } from '../trashButton'; +import IShortProduct from 'src/entities/interfaces/IShortProduct'; + +function ShortProduct({ ...product }: IShortProduct) { + return ( +
    + ... +
    +
    {product.title}
    +
    {product.price}
    +

    {product.description}

    + +
    +
    + ); +} + +export default ShortProduct; diff --git a/src/shared/ui/trashButton/TrashButton.tsx b/src/shared/ui/trashButton/TrashButton.tsx new file mode 100644 index 000000000..050aa6169 --- /dev/null +++ b/src/shared/ui/trashButton/TrashButton.tsx @@ -0,0 +1,21 @@ +import React from 'react'; +import ITrashButton from 'src/entities/interfaces/ITrashButton'; + + +export const TrashButton = ({ counter }: ITrashButton) => { + + const TrashButtonComponent = ( + < button disabled className="btn btn-primary" > + в корзину + + ) + const CounterComponent = ( +
    + - + + + +
    + ) + return counter == 0 ? TrashButtonComponent:CounterComponent + +}; diff --git a/src/shared/ui/trashButton/index.ts b/src/shared/ui/trashButton/index.ts new file mode 100644 index 000000000..efb806a70 --- /dev/null +++ b/src/shared/ui/trashButton/index.ts @@ -0,0 +1 @@ +export * from './TrashButton';  \ No newline at end of file diff --git a/src/components/TrashProduct/TrashProduct.tsx b/src/shared/ui/trashProduct/TrashProduct.tsx similarity index 100% rename from src/components/TrashProduct/TrashProduct.tsx rename to src/shared/ui/trashProduct/TrashProduct.tsx diff --git a/src/stories/Homework2/FullProduct/FullProduct.tsx b/src/stories/Homework2/FullProduct/FullProduct.tsx index 2fdd1be8a..3d5923519 100644 --- a/src/stories/Homework2/FullProduct/FullProduct.tsx +++ b/src/stories/Homework2/FullProduct/FullProduct.tsx @@ -1,5 +1,5 @@ import React from "react"; -import IFullProduct from "../../../Interfaces/IFullProduct"; +import IFullProduct from "../../../entities/interfaces/IFullProduct"; import { TrashButton } from "../TrashButton/TrashButton"; function FullProduct({ price, images, category, title, description }: IFullProduct) { diff --git a/src/stories/Homework2/ShortProduct/ShortProduct.tsx b/src/stories/Homework2/ShortProduct/ShortProduct.tsx index ab1b8426c..deaa18c62 100644 --- a/src/stories/Homework2/ShortProduct/ShortProduct.tsx +++ b/src/stories/Homework2/ShortProduct/ShortProduct.tsx @@ -1,5 +1,5 @@ import { TrashButton } from "../TrashButton/TrashButton"; -import IShortProduct from '../../../Interfaces/IShortProduct'; +import IShortProduct from '../../../entities/interfaces/IShortProduct'; import React from 'react'; diff --git a/src/stories/Homework2/TrashButton/TrashButton.tsx b/src/stories/Homework2/TrashButton/TrashButton.tsx index a995a9868..3f6c9b0e1 100644 --- a/src/stories/Homework2/TrashButton/TrashButton.tsx +++ b/src/stories/Homework2/TrashButton/TrashButton.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import ITrashButton from '../../../Interfaces/ITrashButton'; +import ITrashButton from '../../../entities/interfaces/ITrashButton'; export const TrashButton = ({ counter }: ITrashButton) => { return counter == 0 ? ( diff --git a/src/stories/Homework2/modals/Modal.tsx b/src/stories/Homework2/modals/Modal.tsx index 07d0b8d6a..cf31aab11 100644 --- a/src/stories/Homework2/modals/Modal.tsx +++ b/src/stories/Homework2/modals/Modal.tsx @@ -2,7 +2,7 @@ import React, { useState } from 'react'; import ReactDOM from 'react-dom'; import ModalOverlay from "./ModalOverlay"; import './Modal.css'; -import IModal from '../../../Interfaces/IModal'; +import IModal from '../../../entities/interfaces/IModal'; // const modalRoot = document.getElementById('root-modal'); diff --git a/src/widgets/main/Main.tsx b/src/widgets/main/Main.tsx index a2d24102f..fbe7a9716 100644 --- a/src/widgets/main/Main.tsx +++ b/src/widgets/main/Main.tsx @@ -2,9 +2,10 @@ import React, { useEffect, useState } from 'react'; import logo from '../../app/logo.svg'; import { useTranslation } from 'react-i18next'; import Modal from '../modal/Modal'; -import { ListProduct } from 'src/components/ListProduct/ListProduct'; import { createRandomProduct } from 'src/homeworks/ts1/3_write'; import { useInView } from 'react-intersection-observer'; +import { ListProduct } from 'src/shared/ui/listProduct'; + export default function Main() { const [visible, setVisible] = useState(false); diff --git a/src/widgets/modal/Modal.tsx b/src/widgets/modal/Modal.tsx index da80b47c3..613225649 100644 --- a/src/widgets/modal/Modal.tsx +++ b/src/widgets/modal/Modal.tsx @@ -2,7 +2,7 @@ import React from 'react'; import ReactDOM from 'react-dom'; import ModalOverlay from './ModalOverlay'; import './Modal.css'; -import IModal from 'src/Interfaces/IModal'; +import IModal from 'src/entities/interfaces/IModal'; const modalRoot = document.getElementById('root-modal'); diff --git a/tsconfig.json b/tsconfig.json index 94a8c636d..9885eb596 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,7 +5,8 @@ "paths": { "src/*": ["src/*"], "@components/*": ["src/components/*"], - "@images/*": ["scr/assets/*"] + "@images/*": ["scr/assets/*"], + "@interfaces/*": ["scr/entities/interfaces/*"] }, "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, From 19a5cce291b865de787eae9b40c303532542ad3f Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Thu, 14 Nov 2024 23:53:53 +0300 Subject: [PATCH 27/53] Features react patterns review --- .vscode/launch.json | 3 ++- .vscode/settings.json | 3 +++ src/entities/interfaces/IFuncProps.ts | 5 +++++ src/shared/ui/fullProduct/FullProduct.tsx | 13 +++++------ src/shared/ui/listProduct/ListProduct.tsx | 7 +++--- src/shared/ui/shortProduct/ShortProduct.tsx | 15 +++++++------ src/shared/ui/trashButton/TrashButton.tsx | 25 ++++++++++----------- webpack.config.js | 7 +++++- 8 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 src/entities/interfaces/IFuncProps.ts diff --git a/.vscode/launch.json b/.vscode/launch.json index 2ba986f6f..ff984fe69 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -9,7 +9,8 @@ "request": "launch", "name": "Launch Chrome against localhost", "url": "http://localhost:8080", - "webRoot": "${workspaceFolder}" + "webRoot": "${workspaceFolder}", + // "runtimeExecutable": "/Applications/Сhrome.app", } ] } \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..e8df7cb4c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "workbench.externalBrowser": "chrome" +} \ No newline at end of file diff --git a/src/entities/interfaces/IFuncProps.ts b/src/entities/interfaces/IFuncProps.ts new file mode 100644 index 000000000..70c7482f6 --- /dev/null +++ b/src/entities/interfaces/IFuncProps.ts @@ -0,0 +1,5 @@ +import IFullProduct from './IFullProduct'; + +export default interface FuncProps { + products: IFullProduct[]; +} diff --git a/src/shared/ui/fullProduct/FullProduct.tsx b/src/shared/ui/fullProduct/FullProduct.tsx index adb7131c4..cc08b3d1c 100644 --- a/src/shared/ui/fullProduct/FullProduct.tsx +++ b/src/shared/ui/fullProduct/FullProduct.tsx @@ -3,22 +3,21 @@ import { TrashButton } from '../trashButton'; import IFullProduct from 'src/entities/interfaces/IFullProduct'; -export const FullProduct: FC = ({ ...product }) => { +export const FullProduct: FC = ({ price, images, category, title, description }) => { return (
    - {product.images?.map((image, i) => ( + {images?.map((image, i) => ( ... ))}
    -
    {product.title}
    -
    {product.category}
    -

    {product.description}

    -

    цена: {product.price}р

    +
    {title}
    +
    {category}
    +

    {description}

    +

    цена: {price}р

    ); } - diff --git a/src/shared/ui/listProduct/ListProduct.tsx b/src/shared/ui/listProduct/ListProduct.tsx index d1d88687c..36267ed95 100644 --- a/src/shared/ui/listProduct/ListProduct.tsx +++ b/src/shared/ui/listProduct/ListProduct.tsx @@ -1,9 +1,8 @@ import React, { FC } from 'react'; -import IFullProduct from 'src/entities/interfaces/IFullProduct'; -import { FullProduct } from '../fullProduct/FullProduct'; +import { FullProduct } from '../FullProduct'; +import FuncProps from 'src/entities/interfaces/IFuncProps'; - -export const ListProduct: FC<{ products: IFullProduct[] }> = ({ products }) => { +export const ListProduct: FC = ({ products }) => { return ( <> {products.map((product, i: number) => ( diff --git a/src/shared/ui/shortProduct/ShortProduct.tsx b/src/shared/ui/shortProduct/ShortProduct.tsx index edb83668d..4505e69f2 100644 --- a/src/shared/ui/shortProduct/ShortProduct.tsx +++ b/src/shared/ui/shortProduct/ShortProduct.tsx @@ -1,20 +1,21 @@ -import React from 'react'; +import React, { memo } from 'react'; import { TrashButton } from '../trashButton'; import IShortProduct from 'src/entities/interfaces/IShortProduct'; -function ShortProduct({ ...product }: IShortProduct) { +const ShortProduct = memo(function ShortProduct({ title, price, description, image }: IShortProduct) { return (
    - ... + ...
    -
    {product.title}
    -
    {product.price}
    -

    {product.description}

    +
    {title}
    +
    {price}
    +

    {description}

    ); -} +}) + export default ShortProduct; diff --git a/src/shared/ui/trashButton/TrashButton.tsx b/src/shared/ui/trashButton/TrashButton.tsx index 050aa6169..cb33a6193 100644 --- a/src/shared/ui/trashButton/TrashButton.tsx +++ b/src/shared/ui/trashButton/TrashButton.tsx @@ -1,21 +1,20 @@ import React from 'react'; import ITrashButton from 'src/entities/interfaces/ITrashButton'; - export const TrashButton = ({ counter }: ITrashButton) => { + if (!counter) { + return ( + + ); + } - const TrashButtonComponent = ( - < button disabled className="btn btn-primary" > - в корзину - - ) - const CounterComponent = ( + return (
    - - - + - + + -
    - ) - return counter == 0 ? TrashButtonComponent:CounterComponent - +
    + ); }; diff --git a/webpack.config.js b/webpack.config.js index ccda5187f..dc6e3b400 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,7 +14,12 @@ module.exports = (_, args) => { devtool: 'source-map', context: src, devServer: { - open: true, + open:true, + // open: { + // app: { + // name: 'chrome', + // }, + // }, port, hot: true, historyApiFallback: true, From 34a73c914d8c72767fff9c09d373fc485a046642 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Sun, 17 Nov 2024 23:16:09 +0300 Subject: [PATCH 28/53] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB?= =?UTF-8?q?=E2=80=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/listProduct/ListProduct.tsx | 3 ++- .../Homework2/Collapse/Collapse.module.sass | 9 +++++++++ .../Homework2/Collapse/Collapse.stories.ts | 18 +++++++++++++++++ src/stories/Homework2/Collapse/Collapse.tsx | 20 +++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/stories/Homework2/Collapse/Collapse.module.sass create mode 100644 src/stories/Homework2/Collapse/Collapse.stories.ts create mode 100644 src/stories/Homework2/Collapse/Collapse.tsx diff --git a/src/shared/ui/listProduct/ListProduct.tsx b/src/shared/ui/listProduct/ListProduct.tsx index 36267ed95..7db67516d 100644 --- a/src/shared/ui/listProduct/ListProduct.tsx +++ b/src/shared/ui/listProduct/ListProduct.tsx @@ -1,6 +1,7 @@ import React, { FC } from 'react'; -import { FullProduct } from '../FullProduct'; + import FuncProps from 'src/entities/interfaces/IFuncProps'; +import { FullProduct } from '../fullProduct'; export const ListProduct: FC = ({ products }) => { return ( diff --git a/src/stories/Homework2/Collapse/Collapse.module.sass b/src/stories/Homework2/Collapse/Collapse.module.sass new file mode 100644 index 000000000..89ef38b09 --- /dev/null +++ b/src/stories/Homework2/Collapse/Collapse.module.sass @@ -0,0 +1,9 @@ + +.collapse + opacity:0 + transition: 0.3s + +.collapsing + opacity: 1 + overflow: hidden + transition: 0.3s \ No newline at end of file diff --git a/src/stories/Homework2/Collapse/Collapse.stories.ts b/src/stories/Homework2/Collapse/Collapse.stories.ts new file mode 100644 index 000000000..e55dfc19a --- /dev/null +++ b/src/stories/Homework2/Collapse/Collapse.stories.ts @@ -0,0 +1,18 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Collapse } from './Collapse'; + +const meta: Meta = { + title: 'Homework2/Collapse', + component: Collapse, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + args: { + opened: true, + children: `Hello Word`, + }, +}; diff --git a/src/stories/Homework2/Collapse/Collapse.tsx b/src/stories/Homework2/Collapse/Collapse.tsx new file mode 100644 index 000000000..3846ea65d --- /dev/null +++ b/src/stories/Homework2/Collapse/Collapse.tsx @@ -0,0 +1,20 @@ +import { FC } from 'react'; +import React from 'react'; +import s from './Collapse.module.sass'; +import cn from 'clsx'; + +export type CollapseProps = { + className?: string; + children: React.ReactNode; + opened: boolean; +}; + +export const Collapse: FC = ({ className, opened, children }) => { + return ( + <> +
    +
    {children}
    +
    + + ); +}; From 27b1ada28aeb58231878ee6e7c9d826ddeb7c4f2 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Sun, 17 Nov 2024 23:24:22 +0300 Subject: [PATCH 29/53] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83=20=20key?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/listProduct/ListProduct.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/ui/listProduct/ListProduct.tsx b/src/shared/ui/listProduct/ListProduct.tsx index 7db67516d..4d85100a0 100644 --- a/src/shared/ui/listProduct/ListProduct.tsx +++ b/src/shared/ui/listProduct/ListProduct.tsx @@ -7,7 +7,7 @@ export const ListProduct: FC = ({ products }) => { return ( <> {products.map((product, i: number) => ( - + ))} ); From 8a39dc6aaec29eb80917ebbe535614ce695a36e2 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Thu, 21 Nov 2024 23:12:59 +0300 Subject: [PATCH 30/53] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 23 ++++++ package.json | 1 + .../AuthScreen/SingInBlock/Login.stories.ts | 15 ++++ src/pages/AuthScreen/SingInBlock/Login.tsx | 73 +++++++++++++++++++ .../ui/formProduct/FormProduct.stories.ts | 13 ++++ src/shared/ui/formProduct/FormProduct.tsx | 62 ++++++++++++++++ webpack.config.js | 12 +-- 7 files changed, 193 insertions(+), 6 deletions(-) create mode 100644 src/pages/AuthScreen/SingInBlock/Login.stories.ts create mode 100644 src/pages/AuthScreen/SingInBlock/Login.tsx create mode 100644 src/shared/ui/formProduct/FormProduct.stories.ts create mode 100644 src/shared/ui/formProduct/FormProduct.tsx diff --git a/package-lock.json b/package-lock.json index 115266f0d..375ffd18f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,6 +22,7 @@ "module-alias": "^2.2.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.53.2", "react-i18next": "^15.0.2", "react-intersection-observer": "^9.13.1" }, @@ -20263,6 +20264,22 @@ "integrity": "sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==", "dev": true }, + "node_modules/react-hook-form": { + "version": "7.53.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.2.tgz", + "integrity": "sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19" + } + }, "node_modules/react-i18next": { "version": "15.0.2", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", @@ -38576,6 +38593,12 @@ } } }, + "react-hook-form": { + "version": "7.53.2", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.53.2.tgz", + "integrity": "sha512-YVel6fW5sOeedd1524pltpHX+jgU2u3DSDtXEaBORNdqiNrsX/nUI/iGXONegttg0mJVnfrIkiV0cmTU6Oo2xw==", + "requires": {} + }, "react-i18next": { "version": "15.0.2", "resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.0.2.tgz", diff --git a/package.json b/package.json index 0a4792594..365ba361e 100644 --- a/package.json +++ b/package.json @@ -83,6 +83,7 @@ "module-alias": "^2.2.3", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.53.2", "react-i18next": "^15.0.2", "react-intersection-observer": "^9.13.1" } diff --git a/src/pages/AuthScreen/SingInBlock/Login.stories.ts b/src/pages/AuthScreen/SingInBlock/Login.stories.ts new file mode 100644 index 000000000..c8aa21b68 --- /dev/null +++ b/src/pages/AuthScreen/SingInBlock/Login.stories.ts @@ -0,0 +1,15 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import Login from './Login'; + +const meta: Meta = { + title: 'Homework2/Login', + component: Login, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = { + +}; diff --git a/src/pages/AuthScreen/SingInBlock/Login.tsx b/src/pages/AuthScreen/SingInBlock/Login.tsx new file mode 100644 index 000000000..545f9a3e2 --- /dev/null +++ b/src/pages/AuthScreen/SingInBlock/Login.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import cn from 'clsx'; + +interface IFormLogin { + email: string; + password: string; +} + +function Login() { + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + defaultValues: { + email: '', + password: '', + }, + mode: 'onChange', + }); + + const onSubmit: SubmitHandler = (data) => { + alert(`Добро пожаловать ${data.email}`); + reset(); + }; + + const passwordError = ( +
      +
    • Минимальная длина пароля 10 символов
    • +
    • Пароль должен содержать хотя бы одну цифру
    • +
    • Пароль должен содержать хотя бы одну латинскую букву в нижнем регистре
    • +
    • Пароль должен содержать хотя бы одну латинскую букву в верхнем регистре
    • +
    + ); + + return ( +
    +
    + + +
    +
    + + +
    {errors.password && passwordError}
    +
    + +
    + ); +} + +export default Login; diff --git a/src/shared/ui/formProduct/FormProduct.stories.ts b/src/shared/ui/formProduct/FormProduct.stories.ts new file mode 100644 index 000000000..54beb6652 --- /dev/null +++ b/src/shared/ui/formProduct/FormProduct.stories.ts @@ -0,0 +1,13 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import FormProduct from './FormProduct'; + +const meta: Meta = { + title: 'Homework2/FormProduct', + component: FormProduct, + tags: ['autodocs'], +}; + +export default meta; +type Story = StoryObj; + +export const Default: Story = {}; diff --git a/src/shared/ui/formProduct/FormProduct.tsx b/src/shared/ui/formProduct/FormProduct.tsx new file mode 100644 index 000000000..9b26a7f8c --- /dev/null +++ b/src/shared/ui/formProduct/FormProduct.tsx @@ -0,0 +1,62 @@ +import React, { FC } from 'react'; +import IFullProduct from 'src/entities/interfaces/IFullProduct'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import cn from 'clsx'; + +const FormProduct = () => { + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + mode: 'onChange', + }); + + const onSubmit: SubmitHandler = (data) => { + console.log(data); + reset(); + }; + + return ( +
    +
    + + +
    +
    + + +
    +
    + + +
    + +
    + ); +}; +export default FormProduct; diff --git a/webpack.config.js b/webpack.config.js index dc6e3b400..ffcc5675f 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -14,12 +14,12 @@ module.exports = (_, args) => { devtool: 'source-map', context: src, devServer: { - open:true, - // open: { - // app: { - // name: 'chrome', - // }, - // }, + open: true, + open: { + app: { + name: 'google chrome', + }, + }, port, hot: true, historyApiFallback: true, From ac015798f5a19babd6e5c78541be9f7c420e1a43 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Thu, 21 Nov 2024 23:33:50 +0300 Subject: [PATCH 31/53] 1 --- src/pages/AuthScreen/SingInBlock/Login.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/pages/AuthScreen/SingInBlock/Login.tsx b/src/pages/AuthScreen/SingInBlock/Login.tsx index 545f9a3e2..466aa9a9f 100644 --- a/src/pages/AuthScreen/SingInBlock/Login.tsx +++ b/src/pages/AuthScreen/SingInBlock/Login.tsx @@ -44,7 +44,7 @@ function Login() { className={cn(['form-control', errors.email && 'is-invalid'])} id="exampleInputEmail1" aria-describedby="emailHelp" - {...register('email', { required: true, pattern: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/g })} + {...register('email', { required: true, pattern: /^[\w-.]+@([\w-]+\.)+[\w-]{2,4}$/g })} required />
    From c4ccdc9faae4f8c669c5195a27b91f6842d1c23c Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Sat, 23 Nov 2024 12:56:51 +0300 Subject: [PATCH 32/53] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=20=D0=BA=D0=BE=D0=BC=D0=BF=D0=BE=D0=BD=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D1=81=D1=82=D0=BE=D1=80=D0=B8=D0=B1=D1=83=D0=BA?= =?UTF-8?q?=20=D0=B2=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B5=D0=BA=D1=82,=20=D1=80=D0=B0=D0=BD?= =?UTF-8?q?=D1=8C=D1=88=D0=B5=20=D0=B4=D1=83=D0=B1=D0=BB=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BB=20=D0=B2=20=D1=80=D0=B0=D0=B7=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=B0=D1=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .storybook/main.ts | 3 +- .storybook/preview-body.html | 1 + .../ui/fullProduct}/FullProduct.stories.ts | 3 +- src/shared/ui/modalButton/ShowModalButton.tsx | 31 +++++++++++++++++++ .../modalButton/showModalButton.stories.ts} | 16 +++++----- .../ui/modals}/modal/Modal.css | 0 .../ui/modals}/modal/Modal.tsx | 0 .../ui/modals}/modal/ModalOverlay.css | 0 .../ui/modals}/modal/ModalOverlay.tsx | 0 .../ui/shortProduct}/ShortProduct.stories.ts | 0 src/shared/ui/shortProduct/ShortProduct.tsx | 7 ++--- .../ui/trashButton}/TrashButton.stories.ts | 0 .../ui/trashProduct}/TrashProduct.stories.ts | 0 .../Homework2/FullProduct/FullProduct.tsx | 22 ------------- .../Homework2/ShortProduct/ShortProduct.tsx | 20 ------------ src/stories/Homework2/ShowModalButton.tsx | 29 ----------------- .../Homework2/TrashButton/TrashButton.tsx | 16 ---------- .../Homework2/TrashProduct/TrashProduct.tsx | 13 -------- src/widgets/main/Main.tsx | 5 ++- 19 files changed, 49 insertions(+), 117 deletions(-) create mode 100644 .storybook/preview-body.html rename src/{stories/Homework2/FullProduct => shared/ui/fullProduct}/FullProduct.stories.ts (92%) create mode 100644 src/shared/ui/modalButton/ShowModalButton.tsx rename src/{stories/Homework2/ShowModalButton.stories.ts => shared/ui/modalButton/showModalButton.stories.ts} (67%) rename src/{widgets => shared/ui/modals}/modal/Modal.css (100%) rename src/{widgets => shared/ui/modals}/modal/Modal.tsx (100%) rename src/{widgets => shared/ui/modals}/modal/ModalOverlay.css (100%) rename src/{widgets => shared/ui/modals}/modal/ModalOverlay.tsx (100%) rename src/{stories/Homework2/ShortProduct => shared/ui/shortProduct}/ShortProduct.stories.ts (100%) rename src/{stories/Homework2/TrashButton => shared/ui/trashButton}/TrashButton.stories.ts (100%) rename src/{stories/Homework2/TrashProduct => shared/ui/trashProduct}/TrashProduct.stories.ts (100%) delete mode 100644 src/stories/Homework2/FullProduct/FullProduct.tsx delete mode 100644 src/stories/Homework2/ShortProduct/ShortProduct.tsx delete mode 100644 src/stories/Homework2/ShowModalButton.tsx delete mode 100644 src/stories/Homework2/TrashButton/TrashButton.tsx delete mode 100644 src/stories/Homework2/TrashProduct/TrashProduct.tsx diff --git a/.storybook/main.ts b/.storybook/main.ts index b9786a929..953c50705 100644 --- a/.storybook/main.ts +++ b/.storybook/main.ts @@ -1,6 +1,7 @@ const config = { stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], - staticDirs: ['../src/assets'], + // staticDirs: ['../src/assets'], + staticDirs: [{ from: '../src/assets', to: '../../assets' }], addons: [ '@storybook/addon-links', '@storybook/addon-essentials', diff --git a/.storybook/preview-body.html b/.storybook/preview-body.html new file mode 100644 index 000000000..8b3c0a972 --- /dev/null +++ b/.storybook/preview-body.html @@ -0,0 +1 @@ +
    \ No newline at end of file diff --git a/src/stories/Homework2/FullProduct/FullProduct.stories.ts b/src/shared/ui/fullProduct/FullProduct.stories.ts similarity index 92% rename from src/stories/Homework2/FullProduct/FullProduct.stories.ts rename to src/shared/ui/fullProduct/FullProduct.stories.ts index b4199dece..4afae3902 100644 --- a/src/stories/Homework2/FullProduct/FullProduct.stories.ts +++ b/src/shared/ui/fullProduct/FullProduct.stories.ts @@ -1,5 +1,6 @@ import type { Meta, StoryObj } from '@storybook/react'; -import FullProduct from './FullProduct'; +import { FullProduct } from './FullProduct'; + const meta: Meta = { diff --git a/src/shared/ui/modalButton/ShowModalButton.tsx b/src/shared/ui/modalButton/ShowModalButton.tsx new file mode 100644 index 000000000..ade7e4856 --- /dev/null +++ b/src/shared/ui/modalButton/ShowModalButton.tsx @@ -0,0 +1,31 @@ +import React, { useState } from 'react'; +import Modal from '../modals/modal/Modal'; + +function ShowModalButton() { + const [visible, setVisible] = useState(false); + const [inputText, setInputText] = useState(''); + + const openModal = () => { + setVisible(true); + }; + const closeModal = () => { + setVisible(false); + }; + const modal = ( + +
    aaaaa
    +
    + ); + + return ( +
    + setInputText(e.target.value)} /> + + {visible && modal} +
    + ); +} + +export default ShowModalButton; diff --git a/src/stories/Homework2/ShowModalButton.stories.ts b/src/shared/ui/modalButton/showModalButton.stories.ts similarity index 67% rename from src/stories/Homework2/ShowModalButton.stories.ts rename to src/shared/ui/modalButton/showModalButton.stories.ts index e1198c311..e4bffcd22 100644 --- a/src/stories/Homework2/ShowModalButton.stories.ts +++ b/src/shared/ui/modalButton/showModalButton.stories.ts @@ -1,19 +1,19 @@ import type { Meta, StoryObj } from '@storybook/react'; -import ShowModalButton from './ShowModalButton'; + import { action } from '@storybook/addon-actions'; +import ShowModalButton from './ShowModalButton'; const meta: Meta = { - title: 'Homework2/ShowModalButton', - component: ShowModalButton, - tags: ['autodocs'], - + title: 'Homework2/ShowModalButton', + component: ShowModalButton, + tags: ['autodocs'], }; export default meta; type Story = StoryObj; export const ShowModal: Story = { - args: { - openModal: action('clicked'), - }, + args: { + openModal: action('clicked'), + }, }; diff --git a/src/widgets/modal/Modal.css b/src/shared/ui/modals/modal/Modal.css similarity index 100% rename from src/widgets/modal/Modal.css rename to src/shared/ui/modals/modal/Modal.css diff --git a/src/widgets/modal/Modal.tsx b/src/shared/ui/modals/modal/Modal.tsx similarity index 100% rename from src/widgets/modal/Modal.tsx rename to src/shared/ui/modals/modal/Modal.tsx diff --git a/src/widgets/modal/ModalOverlay.css b/src/shared/ui/modals/modal/ModalOverlay.css similarity index 100% rename from src/widgets/modal/ModalOverlay.css rename to src/shared/ui/modals/modal/ModalOverlay.css diff --git a/src/widgets/modal/ModalOverlay.tsx b/src/shared/ui/modals/modal/ModalOverlay.tsx similarity index 100% rename from src/widgets/modal/ModalOverlay.tsx rename to src/shared/ui/modals/modal/ModalOverlay.tsx diff --git a/src/stories/Homework2/ShortProduct/ShortProduct.stories.ts b/src/shared/ui/shortProduct/ShortProduct.stories.ts similarity index 100% rename from src/stories/Homework2/ShortProduct/ShortProduct.stories.ts rename to src/shared/ui/shortProduct/ShortProduct.stories.ts diff --git a/src/shared/ui/shortProduct/ShortProduct.tsx b/src/shared/ui/shortProduct/ShortProduct.tsx index 4505e69f2..f337c501f 100644 --- a/src/shared/ui/shortProduct/ShortProduct.tsx +++ b/src/shared/ui/shortProduct/ShortProduct.tsx @@ -1,7 +1,7 @@ - import React, { memo } from 'react'; -import { TrashButton } from '../trashButton'; + import IShortProduct from 'src/entities/interfaces/IShortProduct'; +import { TrashButton } from '../trashButton'; const ShortProduct = memo(function ShortProduct({ title, price, description, image }: IShortProduct) { return ( @@ -15,7 +15,6 @@ const ShortProduct = memo(function ShortProduct({ title, price, description, ima
    ); -}) - +}); export default ShortProduct; diff --git a/src/stories/Homework2/TrashButton/TrashButton.stories.ts b/src/shared/ui/trashButton/TrashButton.stories.ts similarity index 100% rename from src/stories/Homework2/TrashButton/TrashButton.stories.ts rename to src/shared/ui/trashButton/TrashButton.stories.ts diff --git a/src/stories/Homework2/TrashProduct/TrashProduct.stories.ts b/src/shared/ui/trashProduct/TrashProduct.stories.ts similarity index 100% rename from src/stories/Homework2/TrashProduct/TrashProduct.stories.ts rename to src/shared/ui/trashProduct/TrashProduct.stories.ts diff --git a/src/stories/Homework2/FullProduct/FullProduct.tsx b/src/stories/Homework2/FullProduct/FullProduct.tsx deleted file mode 100644 index 3d5923519..000000000 --- a/src/stories/Homework2/FullProduct/FullProduct.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import React from "react"; -import IFullProduct from "../../../entities/interfaces/IFullProduct"; -import { TrashButton } from "../TrashButton/TrashButton"; -function FullProduct({ price, images, category, title, description }: IFullProduct) { - - return ( -
    - {images?.map((x, i) => ( - ... - ))} - -
    -
    {title}
    -
    {category}
    -

    {description}

    -

    цена: {price}р

    - -
    -
    - ) -} -export default FullProduct; \ No newline at end of file diff --git a/src/stories/Homework2/ShortProduct/ShortProduct.tsx b/src/stories/Homework2/ShortProduct/ShortProduct.tsx deleted file mode 100644 index deaa18c62..000000000 --- a/src/stories/Homework2/ShortProduct/ShortProduct.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import { TrashButton } from "../TrashButton/TrashButton"; -import IShortProduct from '../../../entities/interfaces/IShortProduct'; - -import React from 'react'; - -function ShortProduct({ title, price, description, image }: IShortProduct) { - return ( -
    - ... -
    -
    {title}
    -
    {price}
    -

    {description}

    - -
    -
    - ); -} - -export default ShortProduct; diff --git a/src/stories/Homework2/ShowModalButton.tsx b/src/stories/Homework2/ShowModalButton.tsx deleted file mode 100644 index 0192f02ed..000000000 --- a/src/stories/Homework2/ShowModalButton.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import React, { useState } from "react"; -import Modal from "./modals/Modal"; - -function ShowModalButton() { - const [visible, setVisible] = useState(false); - const [inputText, setInputText] = useState(''); - - const openModal = () => { - setVisible(true); - } - const closeModal = () => { - setVisible(false); - } - const modal = ( - -
    aaaaa
    -
    - ) - - return ( -
    - setInputText(e.target.value)} /> - - {visible && modal} -
    - ) -} - -export default ShowModalButton; \ No newline at end of file diff --git a/src/stories/Homework2/TrashButton/TrashButton.tsx b/src/stories/Homework2/TrashButton/TrashButton.tsx deleted file mode 100644 index 3f6c9b0e1..000000000 --- a/src/stories/Homework2/TrashButton/TrashButton.tsx +++ /dev/null @@ -1,16 +0,0 @@ -import React from 'react'; -import ITrashButton from '../../../entities/interfaces/ITrashButton'; - -export const TrashButton = ({ counter }: ITrashButton) => { - return counter == 0 ? ( - - ) : ( -
    - - - - + -
    - ); -}; diff --git a/src/stories/Homework2/TrashProduct/TrashProduct.tsx b/src/stories/Homework2/TrashProduct/TrashProduct.tsx deleted file mode 100644 index d2e247a10..000000000 --- a/src/stories/Homework2/TrashProduct/TrashProduct.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import React from "react"; -function TrashProduct() { - return ( -
    - ... -
    - -
    -
    - ) - -} -export default TrashProduct; \ No newline at end of file diff --git a/src/widgets/main/Main.tsx b/src/widgets/main/Main.tsx index fbe7a9716..8bc8babda 100644 --- a/src/widgets/main/Main.tsx +++ b/src/widgets/main/Main.tsx @@ -1,11 +1,10 @@ import React, { useEffect, useState } from 'react'; import logo from '../../app/logo.svg'; import { useTranslation } from 'react-i18next'; -import Modal from '../modal/Modal'; import { createRandomProduct } from 'src/homeworks/ts1/3_write'; import { useInView } from 'react-intersection-observer'; import { ListProduct } from 'src/shared/ui/listProduct'; - +import Modal from 'src/shared/ui/modals/modal/Modal'; export default function Main() { const [visible, setVisible] = useState(false); @@ -37,7 +36,7 @@ export default function Main() { if (inView) { addProduct(); } - }, [addProduct,inView]); + }, [addProduct, inView]); const onOpen = () => { setVisible(true); From 39d99c2263d8e51394aafae2162f0c60c04bffcc Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Tue, 26 Nov 2024 22:25:05 +0300 Subject: [PATCH 33/53] react routing --- .github/workflows/main.yml | 26 +-- package-lock.json | 152 ++++++++++++++++-- package.json | 4 +- src/app/App.css | 74 +++++---- src/app/App.tsx | 9 +- src/assets/logo-clean.png | Bin 0 -> 2680 bytes src/assets/logo-image32.png | Bin 0 -> 2942 bytes src/assets/mop and broom.png | Bin 0 -> 793 bytes src/features/LangSwitcher/LangSwitcher.tsx | 11 +- src/pages/ListProduct/ListProductPage.tsx | 54 +++++++ src/pages/error/Error.tsx | 5 + src/routes/routes.data.ts | 45 ++++++ src/shared/ui/fullProduct/FullProduct.tsx | 7 +- src/shared/ui/header/Header.tsx | 82 +++++++++- src/shared/ui/layouts/Layout.tsx | 20 ++- src/shared/ui/listProduct/ListProduct.tsx | 3 +- src/shared/ui/logo/Logo.tsx | 2 +- .../ui/modals/modal/AddEditProductModal.tsx | 23 +++ src/shared/ui/modals/modal/Modal.tsx | 16 +- src/shared/ui/profile/UserProfile.tsx | 55 +++++++ src/shared/ui/trashProduct/TrashProduct.tsx | 2 +- 21 files changed, 502 insertions(+), 88 deletions(-) create mode 100644 src/assets/logo-clean.png create mode 100644 src/assets/logo-image32.png create mode 100644 src/assets/mop and broom.png create mode 100644 src/pages/ListProduct/ListProductPage.tsx create mode 100644 src/pages/error/Error.tsx create mode 100644 src/routes/routes.data.ts create mode 100644 src/shared/ui/modals/modal/AddEditProductModal.tsx create mode 100644 src/shared/ui/profile/UserProfile.tsx diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index aa6818789..e964cf1b1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -2,7 +2,7 @@ name: Test, Lint, Build and Deploy on Github Pages on: push: - branches: ["master", "main"] + branches: ['master', 'main'] # Позволяет запустить этот рабочий процесс вручную на вкладке Actions workflow_dispatch: @@ -39,23 +39,23 @@ jobs: # run: npm run build # # Публикуем приложение на Github Pages - # - name: Deploy to Github Pages - # uses: JamesIves/github-pages-deploy-action@4.2.1 - # with: - # branch: gh-pages - # folder: dist + - name: Deploy to Github Pages + uses: JamesIves/github-pages-deploy-action@4.2.1 + with: + branch: gh-pages + folder: dist # Собираем Storybook - name: Build Storybook run: npm run build-storybook - + # Публикуем Storybook на Github Pages - - name: Deploy Storybook to Github Pages - uses: JamesIves/github-pages-deploy-action@4.2.1 - with: - branch: gh-pages - folder: storybook-static - commit-message: "Automatically publish Storybook" + # - name: Deploy Storybook to Github Pages + # uses: JamesIves/github-pages-deploy-action@4.2.1 + # with: + # branch: gh-pages + # folder: storybook-static + # commit-message: "Automatically publish Storybook" # Останавливаем выполнение строго при неудачных тестах - name: Fail on failed tests diff --git a/package-lock.json b/package-lock.json index 375ffd18f..6d586b07b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -48,7 +48,7 @@ "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "babel-loader": "^9.1.2", - "bootstrap": "5.1.3", + "bootstrap": "^5.3.3", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.8.1", "eslint": "8.22.0", @@ -69,6 +69,8 @@ "less-loader": "^11.1.2", "mini-css-extract-plugin": "^2.7.6", "prettier": "2.8.8", + "react-router": "^7.0.1", + "react-router-dom": "^7.0.1", "sass": "^1.71.1", "sass-loader": "^13.3.1", "storybook": "^7.6.17", @@ -7635,6 +7637,13 @@ "@types/node": "*" } }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/cross-spawn": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", @@ -9564,17 +9573,23 @@ "dev": true }, "node_modules/bootstrap": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", - "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/twbs" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/bootstrap" + } + ], "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/bootstrap" - }, "peerDependencies": { - "@popperjs/core": "^2.10.2" + "@popperjs/core": "^2.11.8" } }, "node_modules/bplist-parser": { @@ -20379,6 +20394,58 @@ } } }, + "node_modules/react-router": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.0.1.tgz", + "integrity": "sha512-WVAhv9oWCNsja5AkK6KLpXJDSJCQizOIyOd4vvB/+eHGbYx5vkhcmcmwWjQ9yqkRClogi+xjEg9fNEOd5EX/tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.0.1.tgz", + "integrity": "sha512-duBzwAAiIabhFPZfDjcYpJ+f08TMbPMETgq254GWne2NW1ZwRHhZLj7tpSp8KGb7JvZzlLcjGUnqLxpZQVEPng==", + "dev": true, + "license": "MIT", + "dependencies": { + "react-router": "7.0.1" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -21219,6 +21286,13 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "dev": true, + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -22530,6 +22604,13 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "dev": true, + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -29221,6 +29302,12 @@ "@types/node": "*" } }, + "@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true + }, "@types/cross-spawn": { "version": "6.0.6", "resolved": "https://registry.npmjs.org/@types/cross-spawn/-/cross-spawn-6.0.6.tgz", @@ -30788,9 +30875,9 @@ "dev": true }, "bootstrap": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", - "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==", + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.3.tgz", + "integrity": "sha512-8HLCdWgyoMguSO9o+aH+iuZ+aht+mzW0u3HIMzVu7Srrpv7EBBxTnrFlSCskwdY1+EOFQSm7uMJhNQHkdPcmjg==", "dev": true, "requires": {} }, @@ -38649,6 +38736,35 @@ "tslib": "^2.0.0" } }, + "react-router": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.0.1.tgz", + "integrity": "sha512-WVAhv9oWCNsja5AkK6KLpXJDSJCQizOIyOd4vvB/+eHGbYx5vkhcmcmwWjQ9yqkRClogi+xjEg9fNEOd5EX/tw==", + "dev": true, + "requires": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "dependencies": { + "cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "dev": true + } + } + }, + "react-router-dom": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.0.1.tgz", + "integrity": "sha512-duBzwAAiIabhFPZfDjcYpJ+f08TMbPMETgq254GWne2NW1ZwRHhZLj7tpSp8KGb7JvZzlLcjGUnqLxpZQVEPng==", + "dev": true, + "requires": { + "react-router": "7.0.1" + } + }, "react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -39266,6 +39382,12 @@ "send": "0.18.0" } }, + "set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "dev": true + }, "setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -40269,6 +40391,12 @@ } } }, + "turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "dev": true + }, "type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", diff --git a/package.json b/package.json index 365ba361e..45cfbb488 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "babel-loader": "^9.1.2", - "bootstrap": "5.1.3", + "bootstrap": "^5.3.3", "clean-webpack-plugin": "^4.0.0", "css-loader": "^6.8.1", "eslint": "8.22.0", @@ -59,6 +59,8 @@ "less-loader": "^11.1.2", "mini-css-extract-plugin": "^2.7.6", "prettier": "2.8.8", + "react-router": "^7.0.1", + "react-router-dom": "^7.0.1", "sass": "^1.71.1", "sass-loader": "^13.3.1", "storybook": "^7.6.17", diff --git a/src/app/App.css b/src/app/App.css index 09b467829..1d04523ef 100644 --- a/src/app/App.css +++ b/src/app/App.css @@ -1,54 +1,66 @@ .App { - text-align: center; - height: 100vh; + text-align: center; + height: 100vh; } .App-logo { - height: 40vmin; - pointer-events: none; + height: 40vmin; + pointer-events: none; } .text-left { - text-align: left !important; + text-align: left !important; } @media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } + .App-logo { + animation: App-logo-spin infinite 20s linear; + } } .App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; + background-color: #282c34; + min-height: 100vh; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + font-size: calc(10px + 2vmin); + color: white; } -.App.light { - background-color: #f0f0f0 !important; - color: #282c34; +.bg-light { + background-color: #f0f0f0 !important; + color: #282c34 !important; } -.App.dark { - background-color: #282c34 !important; - color: white; +.bg-dark { + background-color: #282c34 !important; + color: white !important; } +.navbar.bg-dark { + border-bottom: 1px solid white; +} +.navbar.bg-light { + border-bottom: 1px solid black; +} +.navbar.bg-dark .nav-link { + color: white !important; +} +.navbar.bg-light .nav-link { + color: black !important; +} .App-link { - color: #61dafb; + color: #61dafb; } @keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - - to { - transform: rotate(360deg); - } -} \ No newline at end of file + from { + transform: rotate(0deg); + } + + to { + transform: rotate(360deg); + } +} diff --git a/src/app/App.tsx b/src/app/App.tsx index 979565c32..c8692bc99 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -4,8 +4,8 @@ import { Theme, ThemeContext } from 'src/context/themeContext'; import { LocalizationInitiator } from 'src/localization/LocalizationInitiator'; import { I18nextProvider } from 'react-i18next'; import i18n from 'src/localization/settings'; -import Main from 'src/widgets/main/Main'; -import Layout from 'src/shared/ui/layouts/Layout'; +import { RouterProvider } from 'react-router'; +import { routes } from 'src/routes/routes.data'; function App() { let [theme, setTheme] = useState('light'); @@ -19,9 +19,8 @@ function App() { -
    - -
    +
    +
    diff --git a/src/assets/logo-clean.png b/src/assets/logo-clean.png new file mode 100644 index 0000000000000000000000000000000000000000..100fe0a13da22fbefcdee1ba53928a5d7098ac47 GIT binary patch literal 2680 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 JSF74b{tF|>QeprA literal 0 HcmV?d00001 diff --git a/src/assets/mop and broom.png b/src/assets/mop and broom.png new file mode 100644 index 0000000000000000000000000000000000000000..721c39c793bcb0ca153756640be7437877a8d896 GIT binary patch literal 793 zcmZ{ge@GK?7{*NP&2hKu(Vc?L0E)QL=jX} zMrbXI3<-kBsiA3>S<_p(-_zZ8zqj4(ZoAtO$u5E*qCejE;d$W0!~0&X(C^C1%+CY> zWa)Nm_KLdd*DOjGx5jd9=0y|)~IW(5E(mjRd+qZR^yGfDs`69DRa0LU62S}V5! zkhZF#%%DxC7$L>*NjmjCMzJ^?f#X61XHYlpvxi(5gVJ$|<0B(}sL%QN6*OdZj3RU> zm13q)Prqx-GTQsvKFsoAFFE}sFiFO+gb(ntko+Et3sELPF>xvyCzGj| zAX*w>S)7iB5{WR!k%+Z^CICbWIE;_^{70>V}31#O)j(^i6#Vc*lh#9rW9SK8QrU zK|d5G{3PbZ5qD_%8-aSFVHgj&cp-|yjtCV%;i=HHofje;OK>bcKR>_k1#(P$V%m{C zyEKPSLzM-h$uR56wHd@>dCt-$J)U5v7}<525<~G=jY%%4P1~uM>%EdwF+m?>AWP@6VIx z8uD@t9o%t>%70+=sIAS^?84TaBdv;jMW^DZLSdb4HA$K=Y}wrdj!kdQ3fVV|dfP@@ z_X*nOu&sP_Xxbz(Zhv>-N7}`b!POm9^~UT2u(U*atCE&Vt07gz=I0Bd8OEow<+A0^ zp2=igvh1e&>s}n}AA$$k=02^Eyak{ref6U8&K-L3x;18PmASUsT&J!%RVNB4R2CK$ zs8j`oMF!O-wW?UHRIOJk)k = ({ className }) => { const { i18n } = useTranslation(); + const theme = useContext(ThemeContext); + const lang = (i18n.language as Locale) === Locale.ru ? Locale.en : Locale.ru; return ( - ); diff --git a/src/pages/ListProduct/ListProductPage.tsx b/src/pages/ListProduct/ListProductPage.tsx new file mode 100644 index 000000000..959289ea7 --- /dev/null +++ b/src/pages/ListProduct/ListProductPage.tsx @@ -0,0 +1,54 @@ +import React, { useEffect, useState } from 'react'; +import { ListProduct } from 'src/shared/ui/listProduct'; +import { useInView } from 'react-intersection-observer'; +import { createRandomProduct } from 'src/homeworks/ts1/3_write'; +import Modal from 'src/stories/Homework2/modals/Modal'; +import FormProduct from 'src/shared/ui/formProduct/FormProduct'; + +const ListProductPage = () => { + const { ref, inView } = useInView({ threshold: 0.7 }); + const [products, setProducts] = useState([]); + + function addProduct() { + const createdAt = '2023-06-06T12:06:56.957Z'; + const product = createRandomProduct(createdAt); + + setProducts([ + ...products, + { + price: product.price, + category: product.category.name, + title: product.name, + description: product.desc, + images: [product.photo], + }, + ]); + } + + useEffect(() => { + addProduct(); + }, []); + + useEffect(() => { + if (inView) { + addProduct(); + } + }, [addProduct, inView]); + + + + return ( + <> +
    + +
    +
    + +
    + + ); +}; + +export default ListProductPage; diff --git a/src/pages/error/Error.tsx b/src/pages/error/Error.tsx new file mode 100644 index 000000000..3c2b2d186 --- /dev/null +++ b/src/pages/error/Error.tsx @@ -0,0 +1,5 @@ +import React from "react" +export default function Error() { + + return
    Error
    +} diff --git a/src/routes/routes.data.ts b/src/routes/routes.data.ts new file mode 100644 index 000000000..cdd6f15b6 --- /dev/null +++ b/src/routes/routes.data.ts @@ -0,0 +1,45 @@ +import { createBrowserRouter } from 'react-router-dom'; +import Layout from 'src/shared/ui/layouts/Layout'; +import Main from 'src/widgets/main/Main'; +import UserProfile from 'src/shared/ui/profile/UserProfile'; +import Error from 'src/pages/error/Error'; +import ListProductPage from 'src/pages/ListProduct/ListProductPage'; +import TrashProduct from 'src/shared/ui/trashProduct/TrashProduct'; +import { AddEditProductModal } from 'src/shared/ui/modals/modal/AddEditProductModal'; + +export const routes = createBrowserRouter([ + { + path: '/', + Component: Layout, + children: [ + { + path: '/', + Component: Main, + }, + { + path: '/userProfile', + Component: UserProfile, + }, + { + path: '/trash', + Component: TrashProduct, + }, + { + path: '/listProduct', + Component: ListProductPage, + }, + { + path: '/listProduct/edit', + Component: () => AddEditProductModal('edit'), + }, + { + path: '/listProduct/add', + Component: () => AddEditProductModal('add'), + }, + ], + }, + { + path: '*', + Component: Error, + }, +]); diff --git a/src/shared/ui/fullProduct/FullProduct.tsx b/src/shared/ui/fullProduct/FullProduct.tsx index cc08b3d1c..564514b82 100644 --- a/src/shared/ui/fullProduct/FullProduct.tsx +++ b/src/shared/ui/fullProduct/FullProduct.tsx @@ -1,12 +1,11 @@ -import React, { FC } from 'react'; +import React, { FC, memo, useState } from 'react'; import { TrashButton } from '../trashButton'; import IFullProduct from 'src/entities/interfaces/IFullProduct'; +export const FullProduct = memo(function FullProduct({ price, images, category, title, description }: IFullProduct) { -export const FullProduct: FC = ({ price, images, category, title, description }) => { return (
    - {images?.map((image, i) => ( ... ))} @@ -20,4 +19,4 @@ export const FullProduct: FC = ({ price, images, category, title,
    ); -} +}); diff --git a/src/shared/ui/header/Header.tsx b/src/shared/ui/header/Header.tsx index 39f55d3fa..0e178386a 100644 --- a/src/shared/ui/header/Header.tsx +++ b/src/shared/ui/header/Header.tsx @@ -1,21 +1,97 @@ -import React, { FC } from 'react'; +import React, { FC, useContext, useEffect } from 'react'; import { withTranslation } from 'react-i18next'; import Logo from '../logo/Logo'; import { LangSwitcher } from 'src/features/LangSwitcher/LangSwitcher'; import { ChangeThemeButton } from '../ChangeThemeButton'; +import UserProfile from '../profile/UserProfile'; +import { NavLink } from 'react-router'; +import { ThemeContext } from 'src/context/themeContext'; +import { clsx as cn } from 'clsx'; export const HeaderOrigin: FC = () => { + const theme = useContext(ThemeContext); + return (
    -
    + + + {/* */} + {/*
    + Профиль
    -
    +
    */}
    ); }; diff --git a/src/shared/ui/layouts/Layout.tsx b/src/shared/ui/layouts/Layout.tsx index 9e0a4db0f..c956bbfc6 100644 --- a/src/shared/ui/layouts/Layout.tsx +++ b/src/shared/ui/layouts/Layout.tsx @@ -1,10 +1,16 @@ -import React from "react"; -import "./Layout.css" -import { Header } from "../header"; +import React from 'react'; +import { Outlet } from 'react-router'; +import './Layout.css'; +import { Header } from '../header'; function Layout() { - return ( -
    - ) + return ( + <> +
    +
    + +
    + + ); } -export default Layout; \ No newline at end of file +export default Layout; diff --git a/src/shared/ui/listProduct/ListProduct.tsx b/src/shared/ui/listProduct/ListProduct.tsx index 4d85100a0..a99d2895d 100644 --- a/src/shared/ui/listProduct/ListProduct.tsx +++ b/src/shared/ui/listProduct/ListProduct.tsx @@ -1,9 +1,10 @@ -import React, { FC } from 'react'; +import React, { FC, useEffect } from 'react'; import FuncProps from 'src/entities/interfaces/IFuncProps'; import { FullProduct } from '../fullProduct'; export const ListProduct: FC = ({ products }) => { + return ( <> {products.map((product, i: number) => ( diff --git a/src/shared/ui/logo/Logo.tsx b/src/shared/ui/logo/Logo.tsx index b30851d0d..13d4109ff 100644 --- a/src/shared/ui/logo/Logo.tsx +++ b/src/shared/ui/logo/Logo.tsx @@ -1,5 +1,5 @@ import React from "react"; -import logo from '../../../assets/logo-image64.png'; +import logo from '../../../assets/logo-clean.png'; function Logo() { return (
    diff --git a/src/shared/ui/modals/modal/AddEditProductModal.tsx b/src/shared/ui/modals/modal/AddEditProductModal.tsx new file mode 100644 index 000000000..72293729f --- /dev/null +++ b/src/shared/ui/modals/modal/AddEditProductModal.tsx @@ -0,0 +1,23 @@ +import React, { useEffect, useState } from 'react'; +import FormProduct from '../../formProduct/FormProduct'; +import Modal from './Modal'; + +export const AddEditProductModal = (url: string) => { + const [visible, setVisible] = useState(false); + + useEffect(() => { + console.log('EditProductModal'); + setVisible(true); + return () => setVisible(false); + }, []); + + const onClosed = () => { + setVisible(false); + }; + + return ( + + {} + + ); +}; diff --git a/src/shared/ui/modals/modal/Modal.tsx b/src/shared/ui/modals/modal/Modal.tsx index 613225649..93c1904a7 100644 --- a/src/shared/ui/modals/modal/Modal.tsx +++ b/src/shared/ui/modals/modal/Modal.tsx @@ -13,13 +13,15 @@ function Modal({ visible, header = '', children, onClose }: IModal) {
    -
    -
    -

    {header}

    -
    -

    - × -

    +
    +
    {header}
    +
    {children}
    diff --git a/src/shared/ui/profile/UserProfile.tsx b/src/shared/ui/profile/UserProfile.tsx new file mode 100644 index 000000000..95af599e8 --- /dev/null +++ b/src/shared/ui/profile/UserProfile.tsx @@ -0,0 +1,55 @@ +import React from 'react'; + +const UserProfile = () => { + return ( +
    +
    +
    +
    + + + + +
    +
    +
    +
    +
    +

    Профиль

    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    + +
    +
    +
    +
    +
    + ); +}; +export default UserProfile; diff --git a/src/shared/ui/trashProduct/TrashProduct.tsx b/src/shared/ui/trashProduct/TrashProduct.tsx index d2e247a10..c576af4b9 100644 --- a/src/shared/ui/trashProduct/TrashProduct.tsx +++ b/src/shared/ui/trashProduct/TrashProduct.tsx @@ -4,7 +4,7 @@ function TrashProduct() {
    ...
    - +
    ) From 69bae513750b79768f18963c332be230cf174dce Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Tue, 26 Nov 2024 22:30:36 +0300 Subject: [PATCH 34/53] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83=20=D1=81?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e964cf1b1..572e52644 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -34,11 +34,11 @@ jobs: - name: Run tests and linter run: npm run lint && npm test - # # Собираем приложение - # - name: Build Application - # run: npm run build + # Собираем приложение + - name: Build Application + run: npm run build - # # Публикуем приложение на Github Pages + # Публикуем приложение на Github Pages - name: Deploy to Github Pages uses: JamesIves/github-pages-deploy-action@4.2.1 with: From bc224b3e6658fbc29b5432e9bf49d7d6ddbc46fc Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Wed, 27 Nov 2024 22:20:41 +0300 Subject: [PATCH 35/53] =?UTF-8?q?=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0=20?= =?UTF-8?q?=D1=80=D0=BE=D1=83=D1=82=D0=B8=D0=BD=D0=B3=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/routes.data.ts | 7 +++--- ...itProductModal.tsx => AddProductModal.tsx} | 5 ++--- .../ui/modals/modal/EditProductModal.tsx | 22 +++++++++++++++++++ 3 files changed, 28 insertions(+), 6 deletions(-) rename src/shared/ui/modals/modal/{AddEditProductModal.tsx => AddProductModal.tsx} (62%) create mode 100644 src/shared/ui/modals/modal/EditProductModal.tsx diff --git a/src/routes/routes.data.ts b/src/routes/routes.data.ts index cdd6f15b6..c922dbf50 100644 --- a/src/routes/routes.data.ts +++ b/src/routes/routes.data.ts @@ -5,7 +5,8 @@ import UserProfile from 'src/shared/ui/profile/UserProfile'; import Error from 'src/pages/error/Error'; import ListProductPage from 'src/pages/ListProduct/ListProductPage'; import TrashProduct from 'src/shared/ui/trashProduct/TrashProduct'; -import { AddEditProductModal } from 'src/shared/ui/modals/modal/AddEditProductModal'; +import { AddProductModal } from 'src/shared/ui/modals/modal/AddProductModal'; +import { EditProductModal } from 'src/shared/ui/modals/modal/EditProductModal'; export const routes = createBrowserRouter([ { @@ -30,11 +31,11 @@ export const routes = createBrowserRouter([ }, { path: '/listProduct/edit', - Component: () => AddEditProductModal('edit'), + Component: EditProductModal, }, { path: '/listProduct/add', - Component: () => AddEditProductModal('add'), + Component: AddProductModal, }, ], }, diff --git a/src/shared/ui/modals/modal/AddEditProductModal.tsx b/src/shared/ui/modals/modal/AddProductModal.tsx similarity index 62% rename from src/shared/ui/modals/modal/AddEditProductModal.tsx rename to src/shared/ui/modals/modal/AddProductModal.tsx index 72293729f..25fc0d470 100644 --- a/src/shared/ui/modals/modal/AddEditProductModal.tsx +++ b/src/shared/ui/modals/modal/AddProductModal.tsx @@ -2,11 +2,10 @@ import React, { useEffect, useState } from 'react'; import FormProduct from '../../formProduct/FormProduct'; import Modal from './Modal'; -export const AddEditProductModal = (url: string) => { +export const AddProductModal = () => { const [visible, setVisible] = useState(false); useEffect(() => { - console.log('EditProductModal'); setVisible(true); return () => setVisible(false); }, []); @@ -16,7 +15,7 @@ export const AddEditProductModal = (url: string) => { }; return ( - + {} ); diff --git a/src/shared/ui/modals/modal/EditProductModal.tsx b/src/shared/ui/modals/modal/EditProductModal.tsx new file mode 100644 index 000000000..848c3e657 --- /dev/null +++ b/src/shared/ui/modals/modal/EditProductModal.tsx @@ -0,0 +1,22 @@ +import React, { useEffect, useState } from 'react'; +import FormProduct from '../../formProduct/FormProduct'; +import Modal from './Modal'; + +export const EditProductModal = () => { + const [visible, setVisible] = useState(false); + + useEffect(() => { + setVisible(true); + return () => setVisible(false); + }, []); + + const onClosed = () => { + setVisible(false); + }; + + return ( + + {} + + ); +}; From b8685d52be31e7203542f1d7409a52b63360d92d Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Thu, 28 Nov 2024 19:45:29 +0300 Subject: [PATCH 36/53] add redux persist --- package-lock.json | 282 ++++++++++++++++++++++++++++++---- package.json | 6 +- src/features/store/storage.ts | 0 3 files changed, 260 insertions(+), 28 deletions(-) create mode 100644 src/features/store/storage.ts diff --git a/package-lock.json b/package-lock.json index 6d586b07b..ced7abfa4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", + "@reduxjs/toolkit": "^2.3.0", "clsx": "^1.2.1", "i18next": "^23.15.2", "i18next-browser-languagedetector": "^8.0.0", @@ -24,7 +25,8 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.53.2", "react-i18next": "^15.0.2", - "react-intersection-observer": "^9.13.1" + "react-intersection-observer": "^9.13.1", + "react-redux": "^9.1.2" }, "devDependencies": { "@babel/core": "^7.22.1", @@ -45,6 +47,7 @@ "@types/node": "^20.2.5", "@types/react": "^18.2.8", "@types/react-dom": "^18.2.4", + "@types/redux-persist": "^4.0.0", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "babel-loader": "^9.1.2", @@ -71,6 +74,7 @@ "prettier": "2.8.8", "react-router": "^7.0.1", "react-router-dom": "^7.0.1", + "redux-persist": "^6.0.0", "sass": "^1.71.1", "sass-loader": "^13.3.1", "storybook": "^7.6.17", @@ -4434,6 +4438,30 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.3.0.tgz", + "integrity": "sha512-WC7Yd6cNGfHx8zf+iu+Q1UPTfEcXhQ+ATi7CV1hlrSAaQBdlPzg7Ww/wJHNQem7qG9rxmWoFCDCPubSvFObGzA==", + "license": "MIT", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, "node_modules/@sinclair/typebox": { "version": "0.25.24", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", @@ -7960,7 +7988,7 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "devOptional": true }, "node_modules/@types/qs": { "version": "6.9.7", @@ -7975,13 +8003,13 @@ "dev": true }, "node_modules/@types/react": { - "version": "18.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz", - "integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==", - "dev": true, + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "devOptional": true, + "license": "MIT", "dependencies": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, @@ -7994,6 +8022,29 @@ "@types/react": "*" } }, + "node_modules/@types/redux-persist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/redux-persist/-/redux-persist-4.0.0.tgz", + "integrity": "sha512-VyDn75G7+Xkebp5Lxowk2t7HeT4VLSy92nyoQu1dZxk+ySBBdvV0yVR/upE4z24eVFxMp6Fo3CisQpkl4v1k3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "redux": "^3.6.0" + } + }, + "node_modules/@types/redux-persist/node_modules/redux": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "dev": true, + "license": "MIT", + "dependencies": { + "lodash": "^4.2.1", + "lodash-es": "^4.2.1", + "loose-envify": "^1.1.0", + "symbol-observable": "^1.0.3" + } + }, "node_modules/@types/resolve": { "version": "1.20.6", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", @@ -8006,12 +8057,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, - "node_modules/@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", - "dev": true - }, "node_modules/@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", @@ -8074,6 +8119,12 @@ "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", "dev": true }, + "node_modules/@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==", + "license": "MIT" + }, "node_modules/@types/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -10563,7 +10614,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true + "devOptional": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -14126,6 +14177,16 @@ "node": ">=0.10.0" } }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/immutable": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", @@ -17313,6 +17374,13 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true, + "license": "MIT" + }, "node_modules/lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -20338,6 +20406,29 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "node_modules/react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25", + "react": "^18.0", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -20622,6 +20713,31 @@ "node": ">=0.10.0" } }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "redux": ">4.0.0" + } + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -20825,6 +20941,12 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -21851,6 +21973,16 @@ "webpack": ">=2" } }, + "node_modules/symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -23043,6 +23175,15 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", @@ -26910,6 +27051,17 @@ "@babel/runtime": "^7.13.10" } }, + "@reduxjs/toolkit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.3.0.tgz", + "integrity": "sha512-WC7Yd6cNGfHx8zf+iu+Q1UPTfEcXhQ+ATi7CV1hlrSAaQBdlPzg7Ww/wJHNQem7qG9rxmWoFCDCPubSvFObGzA==", + "requires": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + } + }, "@sinclair/typebox": { "version": "0.25.24", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.25.24.tgz", @@ -29617,7 +29769,7 @@ "version": "15.7.5", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.5.tgz", "integrity": "sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==", - "dev": true + "devOptional": true }, "@types/qs": { "version": "6.9.7", @@ -29632,13 +29784,12 @@ "dev": true }, "@types/react": { - "version": "18.2.9", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.2.9.tgz", - "integrity": "sha512-pL3JAesUkF7PEQGxh5XOwdXGV907te6m1/Qe1ERJLgomojS6Ne790QiA7GUl434JEkFA2aAaB6qJ5z4e1zJn/w==", - "dev": true, + "version": "18.3.12", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.12.tgz", + "integrity": "sha512-D2wOSq/d6Agt28q7rSI3jhU7G6aiuzljDGZ2hTZHIkrTLUI+AF3WMeKkEZ9nN2fkBAlcktT6vcZjDFiIhMYEQw==", + "devOptional": true, "requires": { "@types/prop-types": "*", - "@types/scheduler": "*", "csstype": "^3.0.2" } }, @@ -29651,6 +29802,29 @@ "@types/react": "*" } }, + "@types/redux-persist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/redux-persist/-/redux-persist-4.0.0.tgz", + "integrity": "sha512-VyDn75G7+Xkebp5Lxowk2t7HeT4VLSy92nyoQu1dZxk+ySBBdvV0yVR/upE4z24eVFxMp6Fo3CisQpkl4v1k3w==", + "dev": true, + "requires": { + "redux": "^3.6.0" + }, + "dependencies": { + "redux": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/redux/-/redux-3.7.2.tgz", + "integrity": "sha512-pNqnf9q1hI5HHZRBkj3bAngGZW/JMCmexDlOxw4XagXY2o1327nHH54LoTjiPJ0gizoqPDRqWyX/00g0hD6w+A==", + "dev": true, + "requires": { + "lodash": "^4.2.1", + "lodash-es": "^4.2.1", + "loose-envify": "^1.1.0", + "symbol-observable": "^1.0.3" + } + } + } + }, "@types/resolve": { "version": "1.20.6", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.6.tgz", @@ -29663,12 +29837,6 @@ "integrity": "sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA==", "dev": true }, - "@types/scheduler": { - "version": "0.16.3", - "resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz", - "integrity": "sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==", - "dev": true - }, "@types/semver": { "version": "7.5.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.0.tgz", @@ -29731,6 +29899,11 @@ "integrity": "sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==", "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/uuid": { "version": "9.0.8", "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.8.tgz", @@ -31605,7 +31778,7 @@ "version": "3.1.2", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==", - "dev": true + "devOptional": true }, "damerau-levenshtein": { "version": "1.0.8", @@ -34263,6 +34436,11 @@ "optional": true, "peer": true }, + "immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==" + }, "immutable": { "version": "4.3.4", "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", @@ -36573,6 +36751,12 @@ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==", + "dev": true + }, "lodash.debounce": { "version": "4.0.8", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", @@ -38707,6 +38891,15 @@ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==", "dev": true }, + "react-redux": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.1.2.tgz", + "integrity": "sha512-0OA4dhM1W48l3uzmv6B7TXPCGmokUU4p1M44DGN2/D9a1FjVPukVjER1PcPX97jIg6aUeLq1XJo1IpfbgULn0w==", + "requires": { + "@types/use-sync-external-store": "^0.0.3", + "use-sync-external-store": "^1.0.0" + } + }, "react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", @@ -38893,6 +39086,24 @@ } } }, + "redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" + }, + "redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "dev": true, + "requires": {} + }, + "redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "requires": {} + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -39049,6 +39260,11 @@ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", "dev": true }, + "reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==" + }, "resolve": { "version": "1.22.2", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", @@ -39823,6 +40039,12 @@ "@swc/counter": "^0.1.3" } }, + "symbol-observable": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.2.0.tgz", + "integrity": "sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==", + "dev": true + }, "symbol-tree": { "version": "3.2.4", "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", @@ -40697,6 +40919,12 @@ "tslib": "^2.0.0" } }, + "use-sync-external-store": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.2.tgz", + "integrity": "sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==", + "requires": {} + }, "util": { "version": "0.12.5", "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", diff --git a/package.json b/package.json index 45cfbb488..bf38d53ac 100644 --- a/package.json +++ b/package.json @@ -35,6 +35,7 @@ "@types/node": "^20.2.5", "@types/react": "^18.2.8", "@types/react-dom": "^18.2.4", + "@types/redux-persist": "^4.0.0", "@typescript-eslint/eslint-plugin": "^5.59.9", "@typescript-eslint/parser": "^5.59.9", "babel-loader": "^9.1.2", @@ -61,6 +62,7 @@ "prettier": "2.8.8", "react-router": "^7.0.1", "react-router-dom": "^7.0.1", + "redux-persist": "^6.0.0", "sass": "^1.71.1", "sass-loader": "^13.3.1", "storybook": "^7.6.17", @@ -77,6 +79,7 @@ "@fortawesome/free-regular-svg-icons": "^6.6.0", "@fortawesome/free-solid-svg-icons": "^6.6.0", "@fortawesome/react-fontawesome": "^0.2.2", + "@reduxjs/toolkit": "^2.3.0", "clsx": "^1.2.1", "i18next": "^23.15.2", "i18next-browser-languagedetector": "^8.0.0", @@ -87,6 +90,7 @@ "react-dom": "^18.2.0", "react-hook-form": "^7.53.2", "react-i18next": "^15.0.2", - "react-intersection-observer": "^9.13.1" + "react-intersection-observer": "^9.13.1", + "react-redux": "^9.1.2" } } diff --git a/src/features/store/storage.ts b/src/features/store/storage.ts new file mode 100644 index 000000000..e69de29bb From f01d61f32f226cc04957bde93298c1f961c0b155 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Thu, 28 Nov 2024 20:03:45 +0300 Subject: [PATCH 37/53] =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D0=B0=D0=BB=20=D1=80=D0=BE=D1=83=D1=82=D0=B8=D0=BD=D0=B3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/App.tsx | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index c8692bc99..e5bf59f58 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -4,8 +4,16 @@ import { Theme, ThemeContext } from 'src/context/themeContext'; import { LocalizationInitiator } from 'src/localization/LocalizationInitiator'; import { I18nextProvider } from 'react-i18next'; import i18n from 'src/localization/settings'; -import { RouterProvider } from 'react-router'; +import { BrowserRouter, Route, RouterProvider, Routes } from 'react-router'; import { routes } from 'src/routes/routes.data'; +import Layout from 'src/shared/ui/layouts/Layout'; +import UserProfile from 'src/shared/ui/profile/UserProfile'; +import TrashProduct from 'src/shared/ui/trashProduct/TrashProduct'; +import ListProductPage from 'src/pages/ListProduct/ListProductPage'; +import { EditProductModal } from 'src/shared/ui/modals/modal/EditProductModal'; +import { AddProductModal } from 'src/shared/ui/modals/modal/AddProductModal'; +import Error from 'src/pages/error/Error'; +import Main from 'src/widgets/main/Main'; function App() { let [theme, setTheme] = useState('light'); @@ -20,7 +28,19 @@ function App() {
    - + + + + + + + + + + + + +
    From bafaa791856c27a38d2dbaf51833c574c13fe8fd Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Thu, 28 Nov 2024 20:13:40 +0300 Subject: [PATCH 38/53] 1 --- src/app/App.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index e5bf59f58..eee8cc69a 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -30,7 +30,7 @@ function App() {
    - + From 077b40ac17cd4a167e9e7a4891c26361c9f44986 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Thu, 28 Nov 2024 20:57:22 +0300 Subject: [PATCH 39/53] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D0=BB=20=D0=BA=D0=BD=D0=BE=D0=BF=D0=BA=D0=B8=20=D0=B2=20=D1=85?= =?UTF-8?q?=D0=B5=D0=B4=D0=B5=D1=80=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=80=D0=B5=D0=B4=D0=B0=D0=BA=D1=82=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D1=82=D0=BE=D0=B2=D0=B0=D1=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/App.tsx | 14 +---- src/shared/ui/formProduct/FormProduct.tsx | 2 +- src/shared/ui/header/Header.tsx | 73 +++++++---------------- src/shared/ui/profile/UserProfile.tsx | 2 +- 4 files changed, 23 insertions(+), 68 deletions(-) diff --git a/src/app/App.tsx b/src/app/App.tsx index eee8cc69a..89eb88b43 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -28,19 +28,7 @@ function App() {
    - - - - - - - - - - - - - +
    diff --git a/src/shared/ui/formProduct/FormProduct.tsx b/src/shared/ui/formProduct/FormProduct.tsx index 9b26a7f8c..5dee99fe3 100644 --- a/src/shared/ui/formProduct/FormProduct.tsx +++ b/src/shared/ui/formProduct/FormProduct.tsx @@ -42,7 +42,7 @@ const FormProduct = () => { />
    - + { return (
    -
    ); }; diff --git a/src/shared/ui/profile/UserProfile.tsx b/src/shared/ui/profile/UserProfile.tsx index 95af599e8..6b1b48151 100644 --- a/src/shared/ui/profile/UserProfile.tsx +++ b/src/shared/ui/profile/UserProfile.tsx @@ -3,7 +3,7 @@ import React from 'react'; const UserProfile = () => { return (
    -
    +
    Date: Mon, 2 Dec 2024 23:04:05 +0300 Subject: [PATCH 40/53] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=81=D0=BB=D0=B0=D0=B9=D0=B4=D0=B5=D1=80=20=D0=BA=D0=B0=D1=80?= =?UTF-8?q?=D1=83=D1=81=D0=B5=D0=BB=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 28 +- package-lock.json | 288 ++++++++++++++++++++- package.json | 1 + src/assets/cleaner.png | Bin 0 -> 12474 bytes src/assets/yoga-mat-cleaning-kit.png | Bin 0 -> 17861 bytes src/shared/ui/carousel/Carousel.stories.ts | 22 ++ src/shared/ui/carousel/Carousel.tsx | 52 ++++ src/shared/ui/carousel/CarouselImage.tsx | 17 ++ src/shared/ui/header/Header.tsx | 2 +- webpack.config.js | 5 + 10 files changed, 388 insertions(+), 27 deletions(-) create mode 100644 src/assets/cleaner.png create mode 100644 src/assets/yoga-mat-cleaning-kit.png create mode 100644 src/shared/ui/carousel/Carousel.stories.ts create mode 100644 src/shared/ui/carousel/Carousel.tsx create mode 100644 src/shared/ui/carousel/CarouselImage.tsx diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 572e52644..f7e89891d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,27 +35,27 @@ jobs: run: npm run lint && npm test # Собираем приложение - - name: Build Application - run: npm run build + # - name: Build Application + # run: npm run build - # Публикуем приложение на Github Pages - - name: Deploy to Github Pages - uses: JamesIves/github-pages-deploy-action@4.2.1 - with: - branch: gh-pages - folder: dist + # # Публикуем приложение на Github Pages + # - name: Deploy to Github Pages + # uses: JamesIves/github-pages-deploy-action@4.2.1 + # with: + # branch: gh-pages + # folder: dist # Собираем Storybook - name: Build Storybook run: npm run build-storybook # Публикуем Storybook на Github Pages - # - name: Deploy Storybook to Github Pages - # uses: JamesIves/github-pages-deploy-action@4.2.1 - # with: - # branch: gh-pages - # folder: storybook-static - # commit-message: "Automatically publish Storybook" + - name: Deploy Storybook to Github Pages + uses: JamesIves/github-pages-deploy-action@4.2.1 + with: + branch: gh-pages + folder: storybook-static + commit-message: 'Automatically publish Storybook' # Останавливаем выполнение строго при неудачных тестах - name: Fail on failed tests diff --git a/package-lock.json b/package-lock.json index ced7abfa4..0a8c49884 100644 --- a/package-lock.json +++ b/package-lock.json @@ -53,6 +53,7 @@ "babel-loader": "^9.1.2", "bootstrap": "^5.3.3", "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.8.1", "eslint": "8.22.0", "eslint-config-airbnb": "^19.0.4", @@ -4468,6 +4469,19 @@ "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", "dev": true }, + "node_modules/@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", @@ -10409,6 +10423,135 @@ "url": "https://github.com/sponsors/mesqueeb" } }, + "node_modules/copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "engines": { + "node": ">= 18.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^5.1.0" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/copy-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/copy-webpack-plugin/node_modules/globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-webpack-plugin/node_modules/path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/copy-webpack-plugin/node_modules/schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/copy-webpack-plugin/node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/core-js-compat": { "version": "3.33.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.1.tgz", @@ -12571,10 +12714,11 @@ "dev": true }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -12616,6 +12760,13 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true, + "license": "BSD-3-Clause" + }, "node_modules/fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -21307,10 +21458,11 @@ "dev": true }, "node_modules/serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, + "license": "BSD-3-Clause", "dependencies": { "randombytes": "^2.1.0" } @@ -22902,6 +23054,19 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -27068,6 +27233,12 @@ "integrity": "sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ==", "dev": true }, + "@sindresorhus/merge-streams": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-2.3.0.tgz", + "integrity": "sha512-LtoMMhxAlorcGhmFYI+LhPgbPZCkgP6ra1YL604EeF6U98pLlQ3iWIGMdWSC+vWmPBWBNgmDBAhnAobLROJmwg==", + "dev": true + }, "@sinonjs/commons": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", @@ -31625,6 +31796,87 @@ "is-what": "^3.14.1" } }, + "copy-webpack-plugin": { + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-12.0.2.tgz", + "integrity": "sha512-SNwdBeHyII+rWvee/bTnAYyO8vfVdcSTud4EIb6jcZ8inLeWucJE0DnxXQBjlQ5zlteuuvooGQy3LIyGxhvlOA==", + "dev": true, + "requires": { + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.1", + "globby": "^14.0.0", + "normalize-path": "^3.0.0", + "schema-utils": "^4.2.0", + "serialize-javascript": "^6.0.2" + }, + "dependencies": { + "ajv": { + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", + "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + } + }, + "ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.3" + } + }, + "globby": { + "version": "14.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-14.0.2.tgz", + "integrity": "sha512-s3Fq41ZVh7vbbe2PN3nrW7yC7U7MFVc5c98/iTl9c2GawNMKx/J648KQRW6WKkuU8GIbbh2IXfIRQjOZnXcTnw==", + "dev": true, + "requires": { + "@sindresorhus/merge-streams": "^2.1.0", + "fast-glob": "^3.3.2", + "ignore": "^5.2.4", + "path-type": "^5.0.0", + "slash": "^5.1.0", + "unicorn-magic": "^0.1.0" + } + }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "path-type": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-5.0.0.tgz", + "integrity": "sha512-5HviZNaZcfqP95rwpv+1HDgUamezbqdSYTyzjTvwtJSnIH+3vnbmWsItli8OFEndS984VT55M3jduxZbX351gg==", + "dev": true + }, + "schema-utils": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.2.0.tgz", + "integrity": "sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.9.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.1.0" + } + }, + "slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true + } + } + }, "core-js-compat": { "version": "3.33.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.33.1.tgz", @@ -33258,9 +33510,9 @@ "dev": true }, "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", "dev": true, "requires": { "@nodelib/fs.stat": "^2.0.2", @@ -33299,6 +33551,12 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-uri": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.0.3.tgz", + "integrity": "sha512-aLrHthzCjH5He4Z2H9YZ+v6Ujb9ocRuW6ZzkJQOrTxleEijANq4v1TsaPaVG1PZcuurEzrLcWRyYBYXD5cEiaw==", + "dev": true + }, "fastest-levenshtein": { "version": "1.0.16", "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.16.tgz", @@ -39510,9 +39768,9 @@ } }, "serialize-javascript": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", - "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -40732,6 +40990,12 @@ "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==", "dev": true }, + "unicorn-magic": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.1.0.tgz", + "integrity": "sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==", + "dev": true + }, "unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", diff --git a/package.json b/package.json index bf38d53ac..0c799237c 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "babel-loader": "^9.1.2", "bootstrap": "^5.3.3", "clean-webpack-plugin": "^4.0.0", + "copy-webpack-plugin": "^12.0.2", "css-loader": "^6.8.1", "eslint": "8.22.0", "eslint-config-airbnb": "^19.0.4", diff --git a/src/assets/cleaner.png b/src/assets/cleaner.png new file mode 100644 index 0000000000000000000000000000000000000000..3c0f06fe0ca5d761ccbca52f5f52697432e36ef2 GIT binary patch literal 12474 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 literal 0 HcmV?d00001 diff --git a/src/assets/yoga-mat-cleaning-kit.png b/src/assets/yoga-mat-cleaning-kit.png new file mode 100644 index 0000000000000000000000000000000000000000..a824d7a6d91ec429704809a1f2e7e9bbd8c4ea3a GIT binary patch literal 17861 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 literal 0 HcmV?d00001 diff --git a/src/shared/ui/carousel/Carousel.stories.ts b/src/shared/ui/carousel/Carousel.stories.ts new file mode 100644 index 000000000..a73568684 --- /dev/null +++ b/src/shared/ui/carousel/Carousel.stories.ts @@ -0,0 +1,22 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { Carousel } from './Carousel'; +import { action } from '@storybook/addon-actions'; + +const meta: Meta = { + title: 'Homework2/Carousel', + component: Carousel, + tags: ['autodocs'], + +}; + +export default meta; +type Story = StoryObj; + +export const DefaultCarousel: Story = { + args: { + // openModal: action('clicked'), + // handleNext: action('clicked'), + images: ['/free-icon-cleaning-9012135.png', '/free-icon-cleaning-9717764.png','/yoga-mat-cleaning-kit.png','/cleaner.png'], + }, + +}; diff --git a/src/shared/ui/carousel/Carousel.tsx b/src/shared/ui/carousel/Carousel.tsx new file mode 100644 index 000000000..780b19545 --- /dev/null +++ b/src/shared/ui/carousel/Carousel.tsx @@ -0,0 +1,52 @@ +import React, { FC, useEffect, useState } from 'react'; +import CarouselImage from './CarouselImage'; + +export const Carousel: FC<{ images: string[] }> = ({ images }) => { + const [index, setIndex] = useState(0); + + useEffect(() => { + console.log(index); + }, [index]); + + const handlePrev = () => { + if (index === 0) setIndex(images.length - 1); + else setIndex(index - 1); + }; + + const handleNext = () => { + if (index === images.length - 1) setIndex(0); + else setIndex(index + 1); + }; + + return ( +
    +
    +
    + {images.map((img: string, i: number) => ( + + ))} +
    + + +
    +
    + ); +}; diff --git a/src/shared/ui/carousel/CarouselImage.tsx b/src/shared/ui/carousel/CarouselImage.tsx new file mode 100644 index 000000000..7c2aa6828 --- /dev/null +++ b/src/shared/ui/carousel/CarouselImage.tsx @@ -0,0 +1,17 @@ +import React, { FC, memo } from 'react'; +import { clsx as cn } from 'clsx'; + +interface ICarouselImage { + index: number; + image: string; + active: string; +} + +const CarouselImage: FC = memo((carouseImage) => { + return ( +
    + ... +
    + ); +}); +export default CarouselImage; diff --git a/src/shared/ui/header/Header.tsx b/src/shared/ui/header/Header.tsx index 192a89fb1..d4d975d28 100644 --- a/src/shared/ui/header/Header.tsx +++ b/src/shared/ui/header/Header.tsx @@ -2,7 +2,7 @@ import React, { FC, useContext, useEffect } from 'react'; import { withTranslation } from 'react-i18next'; import Logo from '../logo/Logo'; import { LangSwitcher } from 'src/features/LangSwitcher/LangSwitcher'; -import { ChangeThemeButton } from '../ChangeThemeButton'; +import { ChangeThemeButton } from '../changeThemeButton'; import UserProfile from '../profile/UserProfile'; import { NavLink } from 'react-router'; import { ThemeContext } from 'src/context/themeContext'; diff --git a/webpack.config.js b/webpack.config.js index ffcc5675f..da14a8407 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -3,10 +3,12 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const ForkTsCheckerWebpackPlugin = require('fork-ts-checker-webpack-plugin'); const { CleanWebpackPlugin } = require('clean-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const CopyWebPackPlugin = require('copy-webpack-plugin'); const port = 2233; const dist = path.join(__dirname, 'dist'); const src = path.join(__dirname, 'src'); +const assets = path.join(src, 'assets'); const host = 'localhost'; module.exports = (_, args) => { @@ -109,6 +111,9 @@ module.exports = (_, args) => { configFile: path.join(__dirname, 'tsconfig.json'), }, }), + new CopyWebPackPlugin({ + patterns: [{ from: assets, to: dist, force: true }], + }), ], }; }; From 9e0573b283624e1cbe80d9473895bc3a510dad9e Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Mon, 2 Dec 2024 23:21:33 +0300 Subject: [PATCH 41/53] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D1=83=20=D1=81?= =?UTF-8?q?=D0=B1=D0=BE=D1=80=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/shared/ui/ChangeThemeButton/index.ts | 1 - src/shared/ui/carousel/CarouselImage.tsx | 8 ++++---- src/shared/ui/header/Header.tsx | 4 ++-- 3 files changed, 6 insertions(+), 7 deletions(-) delete mode 100644 src/shared/ui/ChangeThemeButton/index.ts diff --git a/src/shared/ui/ChangeThemeButton/index.ts b/src/shared/ui/ChangeThemeButton/index.ts deleted file mode 100644 index 1af308dfb..000000000 --- a/src/shared/ui/ChangeThemeButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ChangeThemeButton'; \ No newline at end of file diff --git a/src/shared/ui/carousel/CarouselImage.tsx b/src/shared/ui/carousel/CarouselImage.tsx index 7c2aa6828..d0ca715d5 100644 --- a/src/shared/ui/carousel/CarouselImage.tsx +++ b/src/shared/ui/carousel/CarouselImage.tsx @@ -1,16 +1,16 @@ import React, { FC, memo } from 'react'; import { clsx as cn } from 'clsx'; -interface ICarouselImage { +interface IImageCarousel { index: number; image: string; active: string; } -const CarouselImage: FC = memo((carouseImage) => { +const CarouselImage: FC = memo((image) => { return ( -
    - ... +
    + ...
    ); }); diff --git a/src/shared/ui/header/Header.tsx b/src/shared/ui/header/Header.tsx index d4d975d28..18002a9fe 100644 --- a/src/shared/ui/header/Header.tsx +++ b/src/shared/ui/header/Header.tsx @@ -2,11 +2,11 @@ import React, { FC, useContext, useEffect } from 'react'; import { withTranslation } from 'react-i18next'; import Logo from '../logo/Logo'; import { LangSwitcher } from 'src/features/LangSwitcher/LangSwitcher'; -import { ChangeThemeButton } from '../changeThemeButton'; -import UserProfile from '../profile/UserProfile'; + import { NavLink } from 'react-router'; import { ThemeContext } from 'src/context/themeContext'; import { clsx as cn } from 'clsx'; +import { ChangeThemeButton } from '../changeThemeButton/ChangeThemeButton'; export const HeaderOrigin: FC = () => { const theme = useContext(ThemeContext); From 5bd464c3f69d7bdefc0533d54618b79657cd2e71 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Mon, 2 Dec 2024 23:28:45 +0300 Subject: [PATCH 42/53] 1 --- src/shared/ui/.DS_Store | Bin 6148 -> 6148 bytes src/shared/ui/ChangeThemeButton/.DS_Store | Bin 6148 -> 0 bytes src/shared/ui/header/Header.tsx | 2 +- .../ChangeThemeButton.tsx" | 0 .../ui/\320\241hangeThemeButton/index.ts" | 1 + 5 files changed, 2 insertions(+), 1 deletion(-) delete mode 100644 src/shared/ui/ChangeThemeButton/.DS_Store rename src/shared/ui/ChangeThemeButton/ChangeThemeButton.tsx => "src/shared/ui/\320\241hangeThemeButton/ChangeThemeButton.tsx" (100%) create mode 100644 "src/shared/ui/\320\241hangeThemeButton/index.ts" diff --git a/src/shared/ui/.DS_Store b/src/shared/ui/.DS_Store index 529f6fe17ac1edc66e9444e5af913643c0b79277..8cea028201351804521bb2d1d63bc3bd7c41cd9d 100644 GIT binary patch delta 105 zcmZoMXfc=|#>B)qu~2NHo}wrx0|Nsi1A_nqLjXe&Lq0B!ku~2NHo}wr>0|Nsi1A_nqLpnn$LkL3#kj&kDk$E{YJ4i~9!FlpB zW`)T<%>2TRK)&98FaWaPa`j2&#RW+@`AI;b9Z3Z_nZ+dr2G;i=7gSqCvcn{QR6G7&|d3EHkw{UO>b-Kd&S)GcUCWtRXWc6{sX8JTosP zzuYOmG%uwXtTz}U!NJMF880AF?OknQWT2y9ZfaVqqfl)LVw)J7)z)%yh$`z_2gPUS zZ47DLeB$_mlDCY!`xWTd{1vMN1LLxv)*a9M@oEvdOc+WP74O>SD*{kvM zjP3b6Y3Cy%+U`~tA`=lAqY#zGfUtScbrQiuwLTtYEw}Ony&dT2FE(-RQ@O&N@8v~) z&VMbf?Boq!2hR-nD*NkA_psb_OJtF{zxRxT_{)QMOG=dtBm>Dn zGLQ@$l>waDl<}Ek>SQ1pNCv(c;Qmlhh)rO1w5#ybUtSu;aC=|w38W7mMO8_d|N6yXZ{z4mZO<;9oFKoAT PU|s}_kW|UQ5g7Oc{K+fk diff --git a/src/shared/ui/header/Header.tsx b/src/shared/ui/header/Header.tsx index 18002a9fe..d6028cb9a 100644 --- a/src/shared/ui/header/Header.tsx +++ b/src/shared/ui/header/Header.tsx @@ -6,7 +6,7 @@ import { LangSwitcher } from 'src/features/LangSwitcher/LangSwitcher'; import { NavLink } from 'react-router'; import { ThemeContext } from 'src/context/themeContext'; import { clsx as cn } from 'clsx'; -import { ChangeThemeButton } from '../changeThemeButton/ChangeThemeButton'; +import { ChangeThemeButton } from '../СhangeThemeButton/ChangeThemeButton'; export const HeaderOrigin: FC = () => { const theme = useContext(ThemeContext); diff --git a/src/shared/ui/ChangeThemeButton/ChangeThemeButton.tsx "b/src/shared/ui/\320\241hangeThemeButton/ChangeThemeButton.tsx" similarity index 100% rename from src/shared/ui/ChangeThemeButton/ChangeThemeButton.tsx rename to "src/shared/ui/\320\241hangeThemeButton/ChangeThemeButton.tsx" diff --git "a/src/shared/ui/\320\241hangeThemeButton/index.ts" "b/src/shared/ui/\320\241hangeThemeButton/index.ts" new file mode 100644 index 000000000..7dbad7e63 --- /dev/null +++ "b/src/shared/ui/\320\241hangeThemeButton/index.ts" @@ -0,0 +1 @@ +export * from './ChangeThemeButton'; From a82f2ff5644bcd2b95e5b5d246a517a865ec6184 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Mon, 2 Dec 2024 23:35:15 +0300 Subject: [PATCH 43/53] 2 --- src/shared/ui/carousel/Carousel.tsx | 4 ++-- src/shared/ui/carousel/CarouselImage.tsx | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/shared/ui/carousel/Carousel.tsx b/src/shared/ui/carousel/Carousel.tsx index 780b19545..f1806eba9 100644 --- a/src/shared/ui/carousel/Carousel.tsx +++ b/src/shared/ui/carousel/Carousel.tsx @@ -1,5 +1,5 @@ import React, { FC, useEffect, useState } from 'react'; -import CarouselImage from './CarouselImage'; +import { CarouselImage } from './CarouselImage'; export const Carousel: FC<{ images: string[] }> = ({ images }) => { const [index, setIndex] = useState(0); @@ -19,7 +19,7 @@ export const Carousel: FC<{ images: string[] }> = ({ images }) => { }; return ( -
    +
    {images.map((img: string, i: number) => ( diff --git a/src/shared/ui/carousel/CarouselImage.tsx b/src/shared/ui/carousel/CarouselImage.tsx index d0ca715d5..831d6dd3e 100644 --- a/src/shared/ui/carousel/CarouselImage.tsx +++ b/src/shared/ui/carousel/CarouselImage.tsx @@ -7,11 +7,10 @@ interface IImageCarousel { active: string; } -const CarouselImage: FC = memo((image) => { +export const CarouselImage: FC = memo((image) => { return (
    ...
    ); }); -export default CarouselImage; From 57530b582b11af357c89a37a8b002fab77410f0e Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Mon, 2 Dec 2024 23:40:42 +0300 Subject: [PATCH 44/53] 3 --- src/shared/ui/carousel/Carousel.tsx | 2 +- src/shared/ui/carousel/CarouselImage.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/shared/ui/carousel/Carousel.tsx b/src/shared/ui/carousel/Carousel.tsx index f1806eba9..6b37f91b2 100644 --- a/src/shared/ui/carousel/Carousel.tsx +++ b/src/shared/ui/carousel/Carousel.tsx @@ -1,5 +1,5 @@ import React, { FC, useEffect, useState } from 'react'; -import { CarouselImage } from './CarouselImage'; +import CarouselImage from './CarouselImage'; export const Carousel: FC<{ images: string[] }> = ({ images }) => { const [index, setIndex] = useState(0); diff --git a/src/shared/ui/carousel/CarouselImage.tsx b/src/shared/ui/carousel/CarouselImage.tsx index 831d6dd3e..0408c0c30 100644 --- a/src/shared/ui/carousel/CarouselImage.tsx +++ b/src/shared/ui/carousel/CarouselImage.tsx @@ -7,10 +7,11 @@ interface IImageCarousel { active: string; } -export const CarouselImage: FC = memo((image) => { +const CarouselImage = memo(function (image: IImageCarousel) { return (
    ...
    ); }); +export default CarouselImage; From e7323f822176594a47845075c3f7fc10c34f3d28 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Mon, 2 Dec 2024 23:43:01 +0300 Subject: [PATCH 45/53] 4 --- src/shared/ui/carousel/CarouselImage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/shared/ui/carousel/CarouselImage.tsx b/src/shared/ui/carousel/CarouselImage.tsx index 0408c0c30..815fa9624 100644 --- a/src/shared/ui/carousel/CarouselImage.tsx +++ b/src/shared/ui/carousel/CarouselImage.tsx @@ -14,4 +14,5 @@ const CarouselImage = memo(function (image: IImageCarousel) {
    ); }); +CarouselImage.displayName = 'CarouselImage'; export default CarouselImage; From 21734708c51f22f166531d87a974d8456a967612 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Tue, 3 Dec 2024 22:59:14 +0300 Subject: [PATCH 46/53] =?UTF-8?q?=D0=BF=D0=BE=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D0=BB=20=D0=B7=D0=B0=D0=BC=D0=B5=D1=87=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=20=D1=80=D0=B5=D0=B2=D1=8C=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/App.tsx | 2 +- src/context/themeContext.ts | 10 +- src/entities/interfaces/IFullProduct.ts | 12 +- src/entities/interfaces/IShortProduct.ts | 10 +- src/entities/interfaces/ITrashButton.ts | 4 +- src/homeworks/ts1/1_base.ts | 19 +-- src/homeworks/ts1/2_repair.ts | 46 +++--- src/localization/resources.ts | 4 +- src/localization/settings.ts | 2 +- .../AuthScreen/SingInBlock/Login.stories.ts | 4 +- src/pages/AuthScreen/SingInBlock/Login.tsx | 146 +++++++++--------- src/pages/ListProduct/ListProductPage.tsx | 2 - src/pages/error/Error.tsx | 5 +- src/shared/ui/carousel/Carousel.stories.ts | 9 +- src/shared/ui/carousel/Carousel.tsx | 8 +- src/shared/ui/carousel/CarouselImage.tsx | 3 +- .../changeThemeButton/ChangeThemeButton.tsx | 1 - .../ui/fullProduct/FullProduct.stories.ts | 23 ++- src/shared/ui/fullProduct/FullProduct.tsx | 1 - src/shared/ui/fullProduct/index.ts | 2 +- src/shared/ui/header/Header.tsx | 2 +- src/shared/ui/header/index.ts | 2 +- src/shared/ui/listProduct/ListProduct.tsx | 1 - src/shared/ui/listProduct/index.ts | 2 +- src/shared/ui/logo/Logo.tsx | 14 +- src/shared/ui/modals/modal/ModalOverlay.tsx | 10 +- .../ui/shortProduct/ShortProduct.stories.ts | 25 ++- src/shared/ui/trashButton/index.ts | 2 +- .../ui/trashProduct/TrashProduct.stories.ts | 12 +- src/shared/ui/trashProduct/TrashProduct.tsx | 26 ++-- .../ui/\320\241hangeThemeButton/index.ts" | 1 - src/stories/Examples/Button.tsx | 7 +- src/stories/Homework2/Collapse/Collapse.tsx | 3 +- src/stories/Homework2/Logo/Logo.stories.ts | 5 +- src/stories/Homework2/Logo/Logo.tsx | 14 +- .../changeLang/ChangeLangComponent.tsx | 2 +- .../changeThemeButton.stories.ts | 5 +- .../Homework2/headers/Header.stories.ts | 19 ++- src/stories/Homework2/headers/Header.tsx | 1 - .../Homework2/layouts/Layout.stories.ts | 20 ++- src/stories/Homework2/layouts/Layout.tsx | 12 +- .../Homework2/modals/Modal.stories.tsx | 23 ++- src/stories/Homework2/modals/Modal.tsx | 58 +++---- .../Homework2/modals/ModalOverlay.stories.ts | 13 +- src/stories/Homework2/modals/ModalOverlay.tsx | 10 +- 45 files changed, 280 insertions(+), 322 deletions(-) rename "src/shared/ui/\320\241hangeThemeButton/ChangeThemeButton.tsx" => src/shared/ui/changeThemeButton/ChangeThemeButton.tsx (99%) delete mode 100644 "src/shared/ui/\320\241hangeThemeButton/index.ts" diff --git a/src/app/App.tsx b/src/app/App.tsx index 89eb88b43..ef9b31f7f 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -16,7 +16,7 @@ import Error from 'src/pages/error/Error'; import Main from 'src/widgets/main/Main'; function App() { - let [theme, setTheme] = useState('light'); + const [theme, setTheme] = useState('light'); const toggleTheme = () => { setTheme((prevTheme) => (prevTheme === 'light' ? 'dark' : 'light')); diff --git a/src/context/themeContext.ts b/src/context/themeContext.ts index 53c0e9252..2feca76fb 100644 --- a/src/context/themeContext.ts +++ b/src/context/themeContext.ts @@ -1,10 +1,10 @@ -import { createContext } from "react"; +import { createContext } from 'react'; -export type Theme = "light" | "dark"; +export type Theme = 'light' | 'dark'; export interface ThemeContextType { - theme: Theme; - toggleTheme: () => void; + theme: Theme; + toggleTheme: () => void; } -export const ThemeContext = createContext({} as ThemeContextType); \ No newline at end of file +export const ThemeContext = createContext({} as ThemeContextType); diff --git a/src/entities/interfaces/IFullProduct.ts b/src/entities/interfaces/IFullProduct.ts index 58f748c2b..d19182f2f 100644 --- a/src/entities/interfaces/IFullProduct.ts +++ b/src/entities/interfaces/IFullProduct.ts @@ -1,7 +1,7 @@ export default interface IFullProduct { - price: number, - images: string[], - category: string, - title: string, - description: string -} \ No newline at end of file + price: number; + images: string[]; + category: string; + title: string; + description: string; +} diff --git a/src/entities/interfaces/IShortProduct.ts b/src/entities/interfaces/IShortProduct.ts index 99c745f21..c07317eb1 100644 --- a/src/entities/interfaces/IShortProduct.ts +++ b/src/entities/interfaces/IShortProduct.ts @@ -1,6 +1,6 @@ export default interface IShortProduct { - title: string, - price: number, - description: string, - image: string -} \ No newline at end of file + title: string; + price: number; + description: string; + image: string; +} diff --git a/src/entities/interfaces/ITrashButton.ts b/src/entities/interfaces/ITrashButton.ts index f449ce38b..07c5df6a2 100644 --- a/src/entities/interfaces/ITrashButton.ts +++ b/src/entities/interfaces/ITrashButton.ts @@ -1,3 +1,3 @@ export default interface nITrashButton { - counter: number -} \ No newline at end of file + counter: number; +} diff --git a/src/homeworks/ts1/1_base.ts b/src/homeworks/ts1/1_base.ts index 6975f7c93..2bc11f275 100644 --- a/src/homeworks/ts1/1_base.ts +++ b/src/homeworks/ts1/1_base.ts @@ -18,7 +18,7 @@ export const round = (value: number, accuracy = 2): number => { const transformRegexp = /(matrix\(-?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, -?\d+(\.\d+)?, )(-?\d+(\.\d+)?), (-?\d+(\.\d+)?)\)/; -type coordinate = { x: number, y: number }; +type coordinate = { x: number; y: number }; export const getTransformFromCss = (transformCssString: string): coordinate => { const data = transformCssString.match(transformRegexp); if (!data) return { x: 0, y: 0 }; @@ -55,18 +55,19 @@ export const hex2rgb = (color: string): [number, number, number] => { return [red, green, blue]; }; -export const getNumberedArray = (arr: [T]): { value: T, number: number }[] => arr.map((value, number) => ({ value, number })); -export const toStringArray = (arr: [{ value: T, number: number }]) => arr.map(({ value, number }) => `${value}_${number}`); - +export const getNumberedArray = (arr: [T]): { value: T; number: number }[] => + arr.map((value, number) => ({ value, number })); +export const toStringArray = (arr: [{ value: T; number: number }]) => + arr.map(({ value, number }) => `${value}_${number}`); type Key = { id: number; -} +}; type Customer = { - name: string, - age: number, - isSubscribed: boolean -} + name: string; + age: number; + isSubscribed: boolean; +}; type CustomerKey = Key & Customer; export const transformCustomers = (customers: CustomerKey[]) => { diff --git a/src/homeworks/ts1/2_repair.ts b/src/homeworks/ts1/2_repair.ts index 687b86e87..86ead12eb 100644 --- a/src/homeworks/ts1/2_repair.ts +++ b/src/homeworks/ts1/2_repair.ts @@ -4,48 +4,48 @@ // Мы это не проходили, но по тексту ошибки можно понять, как это починить export const getFakeApi = async (): Promise => { - const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); - console.log(result); + const result = await fetch('https://jsonplaceholder.typicode.com/todos/1').then((response) => response.json()); + console.log(result); }; // Мы это не проходили, но по тексту ошибки можно понять, как это починить export class SomeClass { - set: Set; - channel: BroadcastChannel; + set: Set; + channel: BroadcastChannel; - constructor() { - this.set = new Set([1]); - this.channel = new BroadcastChannel('test-broadcast-channel'); - } + constructor() { + this.set = new Set([1]); + this.channel = new BroadcastChannel('test-broadcast-channel'); + } } export type Data = { - type: 'Money' | 'Percent'; - value: DataValue; + type: 'Money' | 'Percent'; + value: DataValue; }; export type DataValue = Money | Percent; export type Money = { - currency: string; - amount: number; + currency: string; + amount: number; }; export type Percent = { - percent: number; + percent: number; }; // Здесь, возможно, нужно использовать as, возможно в switch передавать немного по-другому const getDataAmount = (data: Data): number => { - switch (data.type) { - case 'Money': - return (data.value as Money).amount; - case 'Percent': - return (data.value as Percent).percent; - default: { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const unhandled: never | Data = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться - throw new Error(`unknown type: ${data.type}`); - } + switch (data.type) { + case 'Money': + return (data.value as Money).amount; + case 'Percent': + return (data.value as Percent).percent; + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const unhandled: never | Data = data; // здесь, возможно, нужно использовать нечто другое. :never должен остаться + throw new Error(`unknown type: ${data.type}`); } + } }; diff --git a/src/localization/resources.ts b/src/localization/resources.ts index b448049f4..2d34fc45e 100644 --- a/src/localization/resources.ts +++ b/src/localization/resources.ts @@ -27,7 +27,7 @@ export const resources = { 'The alias must be at least 7 characters and can contain only numbers, letters and an underscore', }, customText: { - desc: "An import substitution company needs to rewrite a project from Blazor to React." + desc: 'An import substitution company needs to rewrite a project from Blazor to React.', }, screens: { ProfileScreen: { @@ -206,7 +206,7 @@ export const resources = { 'Псевдоним должен быть от 7 символов и может содержать только числа, буквы и символ нижнего подчеркивания', }, customText: { - desc: "An import substitution company needs to rewrite a project from Blazor to React." + desc: 'An import substitution company needs to rewrite a project from Blazor to React.', }, screens: { ProfileScreen: { diff --git a/src/localization/settings.ts b/src/localization/settings.ts index d19b2fd51..d30c07b46 100644 --- a/src/localization/settings.ts +++ b/src/localization/settings.ts @@ -25,4 +25,4 @@ i18n // }, }); -export default i18n; +export default i18n; diff --git a/src/pages/AuthScreen/SingInBlock/Login.stories.ts b/src/pages/AuthScreen/SingInBlock/Login.stories.ts index c8aa21b68..c0619f2c2 100644 --- a/src/pages/AuthScreen/SingInBlock/Login.stories.ts +++ b/src/pages/AuthScreen/SingInBlock/Login.stories.ts @@ -10,6 +10,4 @@ const meta: Meta = { export default meta; type Story = StoryObj; -export const Default: Story = { - -}; +export const Default: Story = {}; diff --git a/src/pages/AuthScreen/SingInBlock/Login.tsx b/src/pages/AuthScreen/SingInBlock/Login.tsx index 466aa9a9f..635bbe505 100644 --- a/src/pages/AuthScreen/SingInBlock/Login.tsx +++ b/src/pages/AuthScreen/SingInBlock/Login.tsx @@ -1,73 +1,73 @@ -import React from 'react'; -import { useForm, SubmitHandler } from 'react-hook-form'; -import cn from 'clsx'; - -interface IFormLogin { - email: string; - password: string; -} - -function Login() { - const { - register, - handleSubmit, - reset, - formState: { errors }, - } = useForm({ - defaultValues: { - email: '', - password: '', - }, - mode: 'onChange', - }); - - const onSubmit: SubmitHandler = (data) => { - alert(`Добро пожаловать ${data.email}`); - reset(); - }; - - const passwordError = ( -
      -
    • Минимальная длина пароля 10 символов
    • -
    • Пароль должен содержать хотя бы одну цифру
    • -
    • Пароль должен содержать хотя бы одну латинскую букву в нижнем регистре
    • -
    • Пароль должен содержать хотя бы одну латинскую букву в верхнем регистре
    • -
    - ); - - return ( -
    -
    - - -
    -
    - - -
    {errors.password && passwordError}
    -
    - -
    - ); -} - -export default Login; +import React from 'react'; +import { useForm, SubmitHandler } from 'react-hook-form'; +import cn from 'clsx'; + +interface IFormLogin { + email: string; + password: string; +} + +function Login() { + const { + register, + handleSubmit, + reset, + formState: { errors }, + } = useForm({ + defaultValues: { + email: '', + password: '', + }, + mode: 'onChange', + }); + + const onSubmit: SubmitHandler = (data) => { + alert(`Добро пожаловать ${data.email}`); + reset(); + }; + + const passwordError = ( +
      +
    • Минимальная длина пароля 10 символов
    • +
    • Пароль должен содержать хотя бы одну цифру
    • +
    • Пароль должен содержать хотя бы одну латинскую букву в нижнем регистре
    • +
    • Пароль должен содержать хотя бы одну латинскую букву в верхнем регистре
    • +
    + ); + + return ( +
    +
    + + +
    +
    + + +
    {errors.password && passwordError}
    +
    + +
    + ); +} + +export default Login; diff --git a/src/pages/ListProduct/ListProductPage.tsx b/src/pages/ListProduct/ListProductPage.tsx index 959289ea7..9a94d95b2 100644 --- a/src/pages/ListProduct/ListProductPage.tsx +++ b/src/pages/ListProduct/ListProductPage.tsx @@ -35,8 +35,6 @@ const ListProductPage = () => { } }, [addProduct, inView]); - - return ( <>
    diff --git a/src/pages/error/Error.tsx b/src/pages/error/Error.tsx index 3c2b2d186..eb1128a86 100644 --- a/src/pages/error/Error.tsx +++ b/src/pages/error/Error.tsx @@ -1,5 +1,4 @@ -import React from "react" +import React from 'react'; export default function Error() { - - return
    Error
    + return
    Error
    ; } diff --git a/src/shared/ui/carousel/Carousel.stories.ts b/src/shared/ui/carousel/Carousel.stories.ts index a73568684..15fe8d36b 100644 --- a/src/shared/ui/carousel/Carousel.stories.ts +++ b/src/shared/ui/carousel/Carousel.stories.ts @@ -6,7 +6,6 @@ const meta: Meta = { title: 'Homework2/Carousel', component: Carousel, tags: ['autodocs'], - }; export default meta; @@ -16,7 +15,11 @@ export const DefaultCarousel: Story = { args: { // openModal: action('clicked'), // handleNext: action('clicked'), - images: ['/free-icon-cleaning-9012135.png', '/free-icon-cleaning-9717764.png','/yoga-mat-cleaning-kit.png','/cleaner.png'], + images: [ + '/free-icon-cleaning-9012135.png', + '/free-icon-cleaning-9717764.png', + '/yoga-mat-cleaning-kit.png', + '/cleaner.png', + ], }, - }; diff --git a/src/shared/ui/carousel/Carousel.tsx b/src/shared/ui/carousel/Carousel.tsx index 6b37f91b2..957f742a1 100644 --- a/src/shared/ui/carousel/Carousel.tsx +++ b/src/shared/ui/carousel/Carousel.tsx @@ -1,13 +1,9 @@ -import React, { FC, useEffect, useState } from 'react'; +import React, { FC, useEffect, useLayoutEffect, useRef, useState } from 'react'; import CarouselImage from './CarouselImage'; export const Carousel: FC<{ images: string[] }> = ({ images }) => { const [index, setIndex] = useState(0); - useEffect(() => { - console.log(index); - }, [index]); - const handlePrev = () => { if (index === 0) setIndex(images.length - 1); else setIndex(index - 1); @@ -23,7 +19,7 @@ export const Carousel: FC<{ images: string[] }> = ({ images }) => {
    {images.map((img: string, i: number) => ( - + ))}
    -
    -
    - ) - + return ( +
    + ... +
    + +
    +
    + ); } -export default TrashProduct; \ No newline at end of file +export default TrashProduct; diff --git "a/src/shared/ui/\320\241hangeThemeButton/index.ts" "b/src/shared/ui/\320\241hangeThemeButton/index.ts" deleted file mode 100644 index 7dbad7e63..000000000 --- "a/src/shared/ui/\320\241hangeThemeButton/index.ts" +++ /dev/null @@ -1 +0,0 @@ -export * from './ChangeThemeButton'; diff --git a/src/stories/Examples/Button.tsx b/src/stories/Examples/Button.tsx index 244ff033e..57c74e5b0 100644 --- a/src/stories/Examples/Button.tsx +++ b/src/stories/Examples/Button.tsx @@ -30,12 +30,7 @@ interface ButtonProps { export function Button({ primary = false, size = 'medium', backgroundColor, label, ...props }: ButtonProps) { const mode = primary ? s.primary : s.secondary; return ( - ); diff --git a/src/stories/Homework2/Collapse/Collapse.tsx b/src/stories/Homework2/Collapse/Collapse.tsx index 3846ea65d..ac765c71b 100644 --- a/src/stories/Homework2/Collapse/Collapse.tsx +++ b/src/stories/Homework2/Collapse/Collapse.tsx @@ -1,5 +1,4 @@ -import { FC } from 'react'; -import React from 'react'; +import React, { FC } from 'react'; import s from './Collapse.module.sass'; import cn from 'clsx'; diff --git a/src/stories/Homework2/Logo/Logo.stories.ts b/src/stories/Homework2/Logo/Logo.stories.ts index 82ae72552..7b0caa7be 100644 --- a/src/stories/Homework2/Logo/Logo.stories.ts +++ b/src/stories/Homework2/Logo/Logo.stories.ts @@ -1,10 +1,9 @@ import type { Meta, StoryObj } from '@storybook/react'; import Logo from './Logo'; - const meta: Meta = { - title: 'Homework2/Logo', - component: Logo, + title: 'Homework2/Logo', + component: Logo, }; export default meta; diff --git a/src/stories/Homework2/Logo/Logo.tsx b/src/stories/Homework2/Logo/Logo.tsx index 430d03d3a..7b8aa025f 100644 --- a/src/stories/Homework2/Logo/Logo.tsx +++ b/src/stories/Homework2/Logo/Logo.tsx @@ -1,10 +1,10 @@ -import React from "react"; +import React from 'react'; import logo from '../assets/logo-image64.png'; function Logo() { - return ( -
    - -
    - ) + return ( +
    + +
    + ); } -export default Logo; \ No newline at end of file +export default Logo; diff --git a/src/stories/Homework2/changeLang/ChangeLangComponent.tsx b/src/stories/Homework2/changeLang/ChangeLangComponent.tsx index d0b03fba8..7fe7555bf 100644 --- a/src/stories/Homework2/changeLang/ChangeLangComponent.tsx +++ b/src/stories/Homework2/changeLang/ChangeLangComponent.tsx @@ -8,7 +8,7 @@ function ChangeLangComponent() { return ( <> -
    +
    {t(`teststorybook`)} + ); }; diff --git a/src/features/store/cart.slice.ts b/src/features/store/cart.slice.ts new file mode 100644 index 000000000..3aa9e87f1 --- /dev/null +++ b/src/features/store/cart.slice.ts @@ -0,0 +1,42 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { loadState } from './storage'; +import { ICartProduct } from 'src/entities/interfaces/ICartProduct'; + +export const CART_PERSISTENT_STATE = 'cartData'; + +export interface ICartState { + items: ICartProduct[]; +} + +const initialState: ICartState = loadState(CART_PERSISTENT_STATE) ?? { + items: [], +}; + +const cartSlice = createSlice({ + name: 'cart', + initialState, + reducers: { + clear: (state) => { + state.items = []; + }, + delete: (state, action: PayloadAction) => { + state.items = state.items.filter((i) => i.id !== action.payload); + }, + add: (state, action: PayloadAction) => { + const existed = state.items.find((i) => i === action.payload); + // if (!existed) { + state.items.push(action.payload); + return; + // } + // state.items.map((i) => { + // if (i === action.payload) { + // i.count += 1; + // } + // return i; + // }); + }, + }, +}); + +export default cartSlice.reducer; +export const cartActions = cartSlice.actions; diff --git a/src/features/store/product.slice.ts b/src/features/store/product.slice.ts new file mode 100644 index 000000000..83ec8690f --- /dev/null +++ b/src/features/store/product.slice.ts @@ -0,0 +1,43 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { loadState } from './storage'; +import IShortProduct from 'src/entities/interfaces/IShortProduct'; + + +export const PRODUCT_PERSISTENT_STATE = 'productData'; + +export interface IProductState { + items: IShortProduct[]; +} + +const initialState: IProductState = loadState(PRODUCT_PERSISTENT_STATE) ?? { + items: [], +}; + +const productSlice = createSlice({ + name: 'product', + initialState, + reducers: { + clear: (state) => { + state.items = []; + }, + delete: (state, action: PayloadAction) => { + state.items = state.items.filter((i) => i.id !== action.payload); + }, + add: (state, action: PayloadAction) => { + const existed = state.items.find((i) => i === action.payload); + // if (!existed) { + state.items.push(action.payload); + return; + // } + // state.items.map((i) => { + // if (i === action.payload) { + // i.count += 1; + // } + // return i; + // }); + }, + }, +}); + +export default productSlice.reducer; +export const cartActions = productSlice.actions; diff --git a/src/features/store/storage.ts b/src/features/store/storage.ts index e69de29bb..7ff3db3a3 100644 --- a/src/features/store/storage.ts +++ b/src/features/store/storage.ts @@ -0,0 +1,17 @@ +export function loadState(key: string): T | undefined { + try { + const jsonState = localStorage.getItem(key); + if (!jsonState) { + return undefined; + } + return JSON.parse(jsonState); + } catch (e) { + console.error(e); + return undefined; + } +} + +export function saveState(state: T, key: string) { + const stringState = JSON.stringify(state); + localStorage.setItem(key, stringState); +} diff --git a/src/features/store/store.ts b/src/features/store/store.ts new file mode 100644 index 000000000..ef507a2ea --- /dev/null +++ b/src/features/store/store.ts @@ -0,0 +1,37 @@ +import { combineReducers, configureStore } from '@reduxjs/toolkit'; +import storage from 'redux-persist/lib/storage'; +import userSlice, { JWT_PERSISTENT_STATE } from './user.slice'; +import cartSlice from './cart.slice'; +import productSlice from './product.slice'; +import persistReducer from 'redux-persist/es/persistReducer'; +import persistStore from 'redux-persist/es/persistStore'; + +const persistConfig = { + key: 'root', + storage, + debug: true, +}; + +const rootReducer = combineReducers({ userData: userSlice, cartData: cartSlice, productData: productSlice }); + +const persistedReducer = persistReducer(persistConfig, rootReducer); + +export const store = configureStore({ + reducer: { + user: persistedReducer, + }, + middleware: (getDefaultMiddleware) => + getDefaultMiddleware({ + serializableCheck: { + ignoredActions: ['persist/PERSIST', 'persist/REHYDRATE'], + }, + }), +}); + +// store.subscribe(() => { +// saveState({ userData: store.getState().user }, JWT_PERSISTENT_STATE); +// }); + +export type RootState = ReturnType; +export type AppDispath = typeof store.dispatch; +export const persistor = persistStore(store); diff --git a/src/features/store/user.slice.ts b/src/features/store/user.slice.ts new file mode 100644 index 000000000..a04f27fd3 --- /dev/null +++ b/src/features/store/user.slice.ts @@ -0,0 +1,34 @@ +import { createSlice, PayloadAction } from '@reduxjs/toolkit'; +import { loadState } from './storage'; +import { IUserProfile } from 'src/entities/interfaces/IUserProfile'; + +export const JWT_PERSISTENT_STATE = 'userData'; + +export interface IUserState { + jwt: string | null; + profile?: IUserProfile; +} + +const initialState: IUserState = { + jwt: loadState(JWT_PERSISTENT_STATE)?.jwt ?? null, +}; + +const userSlice = createSlice({ + name: 'user', + initialState, + reducers: { + addJwt: (state, action: PayloadAction) => { + state.jwt = action.payload; + }, + addProfile: (state, action: PayloadAction) => { + state.profile = action.payload; + }, + logout: (state) => { + state.jwt = null; + state.profile = null; + }, + }, +}); + +export default userSlice.reducer; +export const userActions = userSlice.actions; diff --git a/src/homeworks/ts1/3_write.ts b/src/homeworks/ts1/3_write.ts index 3497ffdb6..92de62478 100644 --- a/src/homeworks/ts1/3_write.ts +++ b/src/homeworks/ts1/3_write.ts @@ -85,9 +85,9 @@ export type Profit = { * */ export const createRandomProduct = (createdAt: string): Product => ({ id: faker.number.int(), - name: faker.commerce.product.name, + name: faker.commerce.product(), photo: faker.image.avatar(), - desc: 'desc', + desc: faker.commerce.productDescription(), createdAt: createdAt, oldPrice: +faker.commerce.price({ min: 1000, max: 5000 }), price: +faker.commerce.price({ min: 1000, max: 5000 }), diff --git a/src/index.tsx b/src/index.tsx index 879481a45..38c358e5f 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -4,10 +4,20 @@ import './index.css'; import App from './app/App'; import 'bootstrap/dist/css/bootstrap.css'; import 'bootstrap/dist/js/bootstrap.min.js'; +import { persistor, store } from './features/store/store'; +import { Provider } from 'react-redux'; +import { PersistGate } from 'redux-persist/integration/react'; +import { BrowserRouter } from 'react-router'; const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( - + + + + + + + ); diff --git a/src/pages/AuthScreen/SingInBlock/Login.tsx b/src/pages/AuthScreen/SingInBlock/Login.tsx index 635bbe505..04cec7437 100644 --- a/src/pages/AuthScreen/SingInBlock/Login.tsx +++ b/src/pages/AuthScreen/SingInBlock/Login.tsx @@ -1,6 +1,11 @@ -import React from 'react'; +import React, { useEffect } from 'react'; import { useForm, SubmitHandler } from 'react-hook-form'; import cn from 'clsx'; +import { useDispatch, useSelector } from 'react-redux'; +import { AppDispath, RootState } from 'src/features/store/store'; +import { userActions } from 'src/features/store/user.slice'; +import { useNavigate } from 'react-router'; +import { IUserProfile } from 'src/entities/interfaces/IUserProfile'; interface IFormLogin { email: string; @@ -11,24 +16,42 @@ function Login() { const { register, handleSubmit, - reset, formState: { errors }, } = useForm({ defaultValues: { - email: '', - password: '', + email: 'test@mail.ru', + password: 'qazQ1@', }, mode: 'onChange', }); + const dispatch = useDispatch(); + const navigate = useNavigate(); + const jwt = useSelector((s: RootState) => s.user.userData?.jwt); + + const profile: IUserProfile = { + id: 123, + lastName: 'Ivanov', + firstName: 'Ivan', + phone: '89260010101', + email: 'test@mail.ru', + role: 'Admin', + }; + + useEffect(() => { + if (jwt) { + dispatch(userActions.addProfile(profile)); + navigate('/'); + } + }, [jwt, navigate]); const onSubmit: SubmitHandler = (data) => { - alert(`Добро пожаловать ${data.email}`); - reset(); + const jwt = Math.random().toString(16); + dispatch(userActions.addJwt(jwt)); }; const passwordError = (
      -
    • Минимальная длина пароля 10 символов
    • +
    • Минимальная длина пароля 5 символов
    • Пароль должен содержать хотя бы одну цифру
    • Пароль должен содержать хотя бы одну латинскую букву в нижнем регистре
    • Пароль должен содержать хотя бы одну латинскую букву в верхнем регистре
    • @@ -36,7 +59,7 @@ function Login() { ); return ( -
      +
      {errors.password && passwordError}
    ); diff --git a/src/pages/ListProduct/ListProductPage.tsx b/src/pages/ListProduct/ListProductPage.tsx index 9a94d95b2..cb7063dd8 100644 --- a/src/pages/ListProduct/ListProductPage.tsx +++ b/src/pages/ListProduct/ListProductPage.tsx @@ -4,30 +4,31 @@ import { useInView } from 'react-intersection-observer'; import { createRandomProduct } from 'src/homeworks/ts1/3_write'; import Modal from 'src/stories/Homework2/modals/Modal'; import FormProduct from 'src/shared/ui/formProduct/FormProduct'; +import IShortProduct from 'src/entities/interfaces/IShortProduct'; const ListProductPage = () => { const { ref, inView } = useInView({ threshold: 0.7 }); - const [products, setProducts] = useState([]); + const [products, setProducts] = useState([]); function addProduct() { const createdAt = '2023-06-06T12:06:56.957Z'; const product = createRandomProduct(createdAt); - setProducts([ ...products, { + id: product.id, price: product.price, - category: product.category.name, + // category: product.category.name, title: product.name, description: product.desc, - images: [product.photo], + image: product.photo, }, ]); } - useEffect(() => { - addProduct(); - }, []); + // useEffect(() => { + // addProduct(); + // }, []); useEffect(() => { if (inView) { diff --git a/src/shared/ui/profile/UserProfile.tsx b/src/pages/profile/UserProfile.tsx similarity index 78% rename from src/shared/ui/profile/UserProfile.tsx rename to src/pages/profile/UserProfile.tsx index 6b1b48151..19fa30fb3 100644 --- a/src/shared/ui/profile/UserProfile.tsx +++ b/src/pages/profile/UserProfile.tsx @@ -1,8 +1,12 @@ import React from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from 'src/features/store/store'; const UserProfile = () => { + const profile = useSelector((s: RootState) => s.user.userData); + return ( -
    +
    @@ -24,21 +28,21 @@ const UserProfile = () => {
    - +
    - +
    - +
    - +
    diff --git a/src/routes/helpers/ProtectedRouteAdmin.tsx b/src/routes/helpers/ProtectedRouteAdmin.tsx new file mode 100644 index 000000000..10c2b3747 --- /dev/null +++ b/src/routes/helpers/ProtectedRouteAdmin.tsx @@ -0,0 +1,20 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { Navigate } from 'react-router'; +import { RootState } from 'src/features/store/store'; + +interface IProtectedRouteAdmin { + children: React.ReactElement; + role: string; +} + +export const ProtectedRouteAdmin = ({ role, children }: IProtectedRouteAdmin) => { + const user = useSelector((s: RootState) => s.user); + + if (!user.userData?.jwt) return ; + + if (user.userData?.profile?.role !== role) return ; + + return children; +}; + diff --git a/src/routes/helpers/RequireAuth.tsx b/src/routes/helpers/RequireAuth.tsx new file mode 100644 index 000000000..41d95af93 --- /dev/null +++ b/src/routes/helpers/RequireAuth.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { useSelector } from 'react-redux'; +import { RootState } from '../../features/store/store'; +import { Navigate } from 'react-router'; + +interface IProtectedRoute { + children: React.ReactElement; +} + +export const RequireAuth = ({ children }: IProtectedRoute) => { + const jwt = useSelector((s: RootState) => s.user.userData?.jwt); + if (!jwt) return ; + + return children; +}; diff --git a/src/routes/routes.data.ts b/src/routes/routes.data.tsx similarity index 53% rename from src/routes/routes.data.ts rename to src/routes/routes.data.tsx index c922dbf50..d217883a8 100644 --- a/src/routes/routes.data.ts +++ b/src/routes/routes.data.tsx @@ -1,12 +1,16 @@ import { createBrowserRouter } from 'react-router-dom'; import Layout from 'src/shared/ui/layouts/Layout'; import Main from 'src/widgets/main/Main'; -import UserProfile from 'src/shared/ui/profile/UserProfile'; +import UserProfile from 'src/pages/profile/UserProfile'; import Error from 'src/pages/error/Error'; import ListProductPage from 'src/pages/ListProduct/ListProductPage'; -import TrashProduct from 'src/shared/ui/trashProduct/TrashProduct'; +import CartProduct from 'src/shared/ui/cartProduct/CartProduct'; import { AddProductModal } from 'src/shared/ui/modals/modal/AddProductModal'; import { EditProductModal } from 'src/shared/ui/modals/modal/EditProductModal'; +import { RequireAuth } from 'src/routes/helpers/RequireAuth'; +import React from 'react'; +import Login from 'src/pages/AuthScreen/SingInBlock/Login'; +import { ProtectedRouteAdmin } from './helpers/ProtectedRouteAdmin'; export const routes = createBrowserRouter([ { @@ -19,11 +23,15 @@ export const routes = createBrowserRouter([ }, { path: '/userProfile', - Component: UserProfile, + element: ( + + + + ), }, { path: '/trash', - Component: TrashProduct, + Component: CartProduct, }, { path: '/listProduct', @@ -31,14 +39,23 @@ export const routes = createBrowserRouter([ }, { path: '/listProduct/edit', - Component: EditProductModal, + element: ( + + + + ), }, { path: '/listProduct/add', - Component: AddProductModal, + element: ( + + + + ), }, ], }, + { path: '/login', Component: Login }, { path: '*', Component: Error, diff --git a/src/shared/ui/.DS_Store b/src/shared/ui/.DS_Store index 8cea028201351804521bb2d1d63bc3bd7c41cd9d..78f8b0ed99dc57547eed94d596225afc39decda7 100644 GIT binary patch delta 32 ocmZoMXfc@J&&aVcU^gQp$7UX;>5QANF&nZ>Y%t!;&heKY0H>D5MvpEQ$;n42cYR4CxH13?V>vDnl+1J28|plmKBq aLtaX8a!yiyeh%a2h0GQ#o7p-3@&f>*8xr~e diff --git a/src/shared/ui/trashButton/TrashButton.stories.ts b/src/shared/ui/cartButton/CartButton.stories.ts similarity index 66% rename from src/shared/ui/trashButton/TrashButton.stories.ts rename to src/shared/ui/cartButton/CartButton.stories.ts index 20c8c93ac..6f8d1c675 100644 --- a/src/shared/ui/trashButton/TrashButton.stories.ts +++ b/src/shared/ui/cartButton/CartButton.stories.ts @@ -1,9 +1,9 @@ import type { Meta, StoryObj } from '@storybook/react'; -import { TrashButton } from './TrashButton'; +import { CartButton } from './CartButton'; -const meta: Meta = { - title: 'Homework2/TrashButton', - component: TrashButton, +const meta: Meta = { + title: 'Homework2/CartButton', + component: CartButton, tags: ['autodocs'], }; diff --git a/src/shared/ui/cartButton/CartButton.tsx b/src/shared/ui/cartButton/CartButton.tsx new file mode 100644 index 000000000..9ced6fa20 --- /dev/null +++ b/src/shared/ui/cartButton/CartButton.tsx @@ -0,0 +1,30 @@ +import React, { useEffect } from 'react'; +import { useDispatch } from 'react-redux'; +import ITrashButton from 'src/entities/interfaces/ICartButton'; +import { AppDispath } from 'src/features/store/store'; + +export const CartButton = ({ counter, addProduct, increment, decrement }: ITrashButton) => { + const handleClick = () => { + addProduct(); + }; + + if (!counter) { + return ( + + ); + } + + return ( +
    + + - + + + + + + +
    + ); +}; diff --git a/src/shared/ui/cartButton/index.ts b/src/shared/ui/cartButton/index.ts new file mode 100644 index 000000000..5304ae5a1 --- /dev/null +++ b/src/shared/ui/cartButton/index.ts @@ -0,0 +1 @@ +export * from './CartButton'; diff --git a/src/shared/ui/cartProduct/CartProduct.stories.ts b/src/shared/ui/cartProduct/CartProduct.stories.ts new file mode 100644 index 000000000..a5b65c0d0 --- /dev/null +++ b/src/shared/ui/cartProduct/CartProduct.stories.ts @@ -0,0 +1,16 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import CartProduct from './CartProduct'; + +const meta: Meta = { + title: 'Homework2/CartProduct', + component: CartProduct, + tags: ['autodocs'], + parameters: { + layout: 'fullscreen', + }, +}; + +export default meta; +type Story = StoryObj; + +export const PrimaryCartProduct: Story = {}; diff --git a/src/shared/ui/cartProduct/CartProduct.tsx b/src/shared/ui/cartProduct/CartProduct.tsx new file mode 100644 index 000000000..15f7f0e7f --- /dev/null +++ b/src/shared/ui/cartProduct/CartProduct.tsx @@ -0,0 +1,28 @@ +import React, { FC } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { cartActions } from 'src/features/store/cart.slice'; +import { AppDispath, RootState } from 'src/features/store/store'; + +const CartProduct = () => { + const products = useSelector((s: RootState) => s.user.cartData.items); + const dispatch = useDispatch(); + + return ( +
    + {products.map((product) => ( +
    + ... +
    +
    {product.title}
    +
    {product.price}
    +

    {product.description}

    + +
    +
    + ))} +
    + ); +}; +export default CartProduct; diff --git a/src/shared/ui/changeThemeButton/ChangeThemeButton.tsx b/src/shared/ui/changeThemeButton/ChangeThemeButton.tsx index 9dbe11918..fe5419958 100644 --- a/src/shared/ui/changeThemeButton/ChangeThemeButton.tsx +++ b/src/shared/ui/changeThemeButton/ChangeThemeButton.tsx @@ -1,13 +1,14 @@ import { faMoon, faSun } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import React, { FC, useContext } from 'react'; +import { Link } from 'react-router'; import { ThemeContext } from 'src/context/themeContext'; export const ChangeThemeButton: FC = () => { const { theme, toggleTheme } = useContext(ThemeContext); return ( - + {theme === 'light' ? : } - + ); }; diff --git a/src/shared/ui/formProduct/FormProduct.tsx b/src/shared/ui/formProduct/FormProduct.tsx index 5dee99fe3..4976f4d00 100644 --- a/src/shared/ui/formProduct/FormProduct.tsx +++ b/src/shared/ui/formProduct/FormProduct.tsx @@ -53,6 +53,18 @@ const FormProduct = () => { required />
    +
    + + +
    diff --git a/src/shared/ui/fullProduct/FullProduct.tsx b/src/shared/ui/fullProduct/FullProduct.tsx index 9fb7f245a..99e4f7c00 100644 --- a/src/shared/ui/fullProduct/FullProduct.tsx +++ b/src/shared/ui/fullProduct/FullProduct.tsx @@ -1,8 +1,12 @@ import React, { FC, memo, useState } from 'react'; -import { TrashButton } from '../trashButton'; +import { CartButton } from '../cartButton'; import IFullProduct from 'src/entities/interfaces/IFullProduct'; +import { useDispatch } from 'react-redux'; +import { AppDispath } from 'src/features/store/store'; +import { cartActions } from 'src/features/store/cart.slice'; export const FullProduct = memo(function FullProduct({ price, images, category, title, description }: IFullProduct) { + return (
    {images?.map((image, i) => ( @@ -14,7 +18,7 @@ export const FullProduct = memo(function FullProduct({ price, images, category,
    {category}

    {description}

    цена: {price}р

    - + {/* */}
    ); diff --git a/src/shared/ui/header/Header.tsx b/src/shared/ui/header/Header.tsx index 18002a9fe..c655fb411 100644 --- a/src/shared/ui/header/Header.tsx +++ b/src/shared/ui/header/Header.tsx @@ -3,17 +3,26 @@ import { withTranslation } from 'react-i18next'; import Logo from '../logo/Logo'; import { LangSwitcher } from 'src/features/LangSwitcher/LangSwitcher'; -import { NavLink } from 'react-router'; +import { Link, NavLink, useNavigate } from 'react-router'; import { ThemeContext } from 'src/context/themeContext'; import { clsx as cn } from 'clsx'; import { ChangeThemeButton } from '../changeThemeButton/ChangeThemeButton'; +import { useDispatch } from 'react-redux'; +import { AppDispath } from 'src/features/store/store'; +import { userActions } from 'src/features/store/user.slice'; export const HeaderOrigin: FC = () => { const theme = useContext(ThemeContext); + const dispatch = useDispatch(); + const navigate = useNavigate(); + const logout = () => { + dispatch(userActions.logout()); + navigate('/login'); + }; return (
    -
    • - + Добавить товар - +
    • - + Редактировать товар
    • +
    • +
    • +
    • + + Выход + +
    diff --git a/src/shared/ui/layouts/Layout.tsx b/src/shared/ui/layouts/Layout.tsx index c956bbfc6..278763d37 100644 --- a/src/shared/ui/layouts/Layout.tsx +++ b/src/shared/ui/layouts/Layout.tsx @@ -7,7 +7,8 @@ function Layout() { return ( <>
    -
    +
    +
    diff --git a/src/shared/ui/listProduct/ListProduct.tsx b/src/shared/ui/listProduct/ListProduct.tsx index f50bafc71..4b70254a9 100644 --- a/src/shared/ui/listProduct/ListProduct.tsx +++ b/src/shared/ui/listProduct/ListProduct.tsx @@ -2,12 +2,13 @@ import React, { FC, useEffect } from 'react'; import FuncProps from 'src/entities/interfaces/IFuncProps'; import { FullProduct } from '../fullProduct'; +import ShortProduct from '../shortProduct/ShortProduct'; export const ListProduct: FC = ({ products }) => { return ( <> {products.map((product, i: number) => ( - + ))} ); diff --git a/src/shared/ui/shortProduct/ShortProduct.tsx b/src/shared/ui/shortProduct/ShortProduct.tsx index f337c501f..76853fb73 100644 --- a/src/shared/ui/shortProduct/ShortProduct.tsx +++ b/src/shared/ui/shortProduct/ShortProduct.tsx @@ -1,9 +1,29 @@ -import React, { memo } from 'react'; +import React, { memo, useState } from 'react'; import IShortProduct from 'src/entities/interfaces/IShortProduct'; -import { TrashButton } from '../trashButton'; +import { CartButton } from '../cartButton'; +import { AppDispath } from 'src/features/store/store'; +import { useDispatch } from 'react-redux'; +import { cartActions } from 'src/features/store/cart.slice'; +import { ICartProduct } from 'src/entities/interfaces/ICartProduct'; + +const ShortProduct = memo(function ShortProduct({ id, title, price, description, image }: IShortProduct) { + const product = { id, title, price, description, image } as ICartProduct; + const [counter, setCounter] = useState(0); + const dispatch = useDispatch(); + + const handleAddProduct = () => { + dispatch(cartActions.add(product)); + setCounter((prev) => prev + 1); + }; + const handleIncrement = () => { + setCounter((prev) => prev + 1); + }; + + const handleDecrement = () => { + setCounter((prev) => prev - 1); + }; -const ShortProduct = memo(function ShortProduct({ title, price, description, image }: IShortProduct) { return (
    ... @@ -11,7 +31,14 @@ const ShortProduct = memo(function ShortProduct({ title, price, description, ima
    {title}
    {price}

    {description}

    - +
    +
    +
    ); diff --git a/src/shared/ui/trashButton/TrashButton.tsx b/src/shared/ui/trashButton/TrashButton.tsx deleted file mode 100644 index cb33a6193..000000000 --- a/src/shared/ui/trashButton/TrashButton.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import ITrashButton from 'src/entities/interfaces/ITrashButton'; - -export const TrashButton = ({ counter }: ITrashButton) => { - if (!counter) { - return ( - - ); - } - - return ( -
    - - - - + -
    - ); -}; diff --git a/src/shared/ui/trashButton/index.ts b/src/shared/ui/trashButton/index.ts deleted file mode 100644 index 2a16fbc5e..000000000 --- a/src/shared/ui/trashButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TrashButton'; diff --git a/src/shared/ui/trashProduct/TrashProduct.stories.ts b/src/shared/ui/trashProduct/TrashProduct.stories.ts deleted file mode 100644 index 217324859..000000000 --- a/src/shared/ui/trashProduct/TrashProduct.stories.ts +++ /dev/null @@ -1,16 +0,0 @@ -import type { Meta, StoryObj } from '@storybook/react'; -import TrashProduct from './TrashProduct'; - -const meta: Meta = { - title: 'Homework2/TrashProduct', - component: TrashProduct, - tags: ['autodocs'], - parameters: { - layout: 'fullscreen', - }, -}; - -export default meta; -type Story = StoryObj; - -export const PrimaryTrashProduct: Story = {}; diff --git a/src/shared/ui/trashProduct/TrashProduct.tsx b/src/shared/ui/trashProduct/TrashProduct.tsx deleted file mode 100644 index bfba8e178..000000000 --- a/src/shared/ui/trashProduct/TrashProduct.tsx +++ /dev/null @@ -1,17 +0,0 @@ -import React from 'react'; -function TrashProduct() { - return ( -
    - ... -
    - -
    -
    - ); -} -export default TrashProduct; diff --git a/src/widgets/main/Main.tsx b/src/widgets/main/Main.tsx index 8bc8babda..dacef9a67 100644 --- a/src/widgets/main/Main.tsx +++ b/src/widgets/main/Main.tsx @@ -28,9 +28,9 @@ export default function Main() { ]); } - useEffect(() => { - addProduct(); - }, []); + // useEffect(() => { + // addProduct(); + // }, []); useEffect(() => { if (inView) { diff --git a/tsconfig.json b/tsconfig.json index 9885eb596..e3c7925d7 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "baseUrl": ".", "paths": { "src/*": ["src/*"], + "*": ["types/*"], "@components/*": ["src/components/*"], "@images/*": ["scr/assets/*"], "@interfaces/*": ["scr/entities/interfaces/*"] @@ -18,6 +19,7 @@ "sourceMap": true, "noImplicitAny": true, "downlevelIteration": true, + "declaration": true, "moduleResolution": "node", "resolveJsonModule": true, "jsx": "react" From 2bd3cf5a0aa1b61aa40db587601244ebbe695ff8 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Sun, 15 Dec 2024 00:28:23 +0300 Subject: [PATCH 48/53] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/main.yml | 34 +++++----- src/app/App.tsx | 7 +-- src/entities/interfaces/IShortProduct.ts | 1 + src/entities/interfaces/IUserProfile.ts | 2 +- src/features/store/product.slice.ts | 2 +- src/features/store/user.slice.ts | 6 +- src/homeworks/ts1/3_write.ts | 2 +- src/index.tsx | 4 +- src/pages/AuthScreen/SingInBlock/Login.tsx | 29 +++++---- src/pages/ListProduct/ListProductPage.tsx | 40 ++---------- src/routes/helpers/ProtectedRouteAdmin.tsx | 6 +- src/routes/routes.data.tsx | 4 +- src/routes/routes.tsx | 63 +++++++++++++++++++ src/shared/ui/formProduct/FormProduct.tsx | 24 ++++--- src/shared/ui/header/Header.tsx | 12 ++-- .../ui/modals/modal/AddProductModal.tsx | 5 +- .../ui/modals/modal/EditProductModal.tsx | 4 +- 17 files changed, 148 insertions(+), 97 deletions(-) create mode 100644 src/routes/routes.tsx diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f7e89891d..853909327 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -35,27 +35,27 @@ jobs: run: npm run lint && npm test # Собираем приложение - # - name: Build Application - # run: npm run build + - name: Build Application + run: npm run build - # # Публикуем приложение на Github Pages - # - name: Deploy to Github Pages - # uses: JamesIves/github-pages-deploy-action@4.2.1 - # with: - # branch: gh-pages - # folder: dist - - # Собираем Storybook - - name: Build Storybook - run: npm run build-storybook - - # Публикуем Storybook на Github Pages - - name: Deploy Storybook to Github Pages + # Публикуем приложение на Github Pages + - name: Deploy to Github Pages uses: JamesIves/github-pages-deploy-action@4.2.1 with: branch: gh-pages - folder: storybook-static - commit-message: 'Automatically publish Storybook' + folder: dist + + # Собираем Storybook + # - name: Build Storybook + # run: npm run build-storybook + + # # Публикуем Storybook на Github Pages + # - name: Deploy Storybook to Github Pages + # uses: JamesIves/github-pages-deploy-action@4.2.1 + # with: + # branch: gh-pages + # folder: storybook-static + # commit-message: 'Automatically publish Storybook' # Останавливаем выполнение строго при неудачных тестах - name: Fail on failed tests diff --git a/src/app/App.tsx b/src/app/App.tsx index b4286956d..1f8fb73d2 100644 --- a/src/app/App.tsx +++ b/src/app/App.tsx @@ -4,9 +4,7 @@ import { Theme, ThemeContext } from 'src/context/themeContext'; import { LocalizationInitiator } from 'src/localization/LocalizationInitiator'; import { I18nextProvider } from 'react-i18next'; import i18n from 'src/localization/settings'; -import { BrowserRouter, Route, RouterProvider, Routes, useLocation } from 'react-router'; -import { routes } from 'src/routes/routes.data'; -import { EditProductModal } from 'src/shared/ui/modals/modal/EditProductModal'; +import { CustomRoutes } from 'src/routes/routes'; function App() { const [theme, setTheme] = useState(() => 'light'); @@ -31,7 +29,8 @@ function App() {
    - + + {/* */}
    diff --git a/src/entities/interfaces/IShortProduct.ts b/src/entities/interfaces/IShortProduct.ts index d54245c7f..c29487937 100644 --- a/src/entities/interfaces/IShortProduct.ts +++ b/src/entities/interfaces/IShortProduct.ts @@ -1,5 +1,6 @@ export default interface IShortProduct { id: number; + category: string; title: string; price: number; description: string; diff --git a/src/entities/interfaces/IUserProfile.ts b/src/entities/interfaces/IUserProfile.ts index 643a1c92b..024f38826 100644 --- a/src/entities/interfaces/IUserProfile.ts +++ b/src/entities/interfaces/IUserProfile.ts @@ -4,5 +4,5 @@ export interface IUserProfile { lastName: string; firstName: string; phone: string; - role: string; + isAdmin: boolean; } diff --git a/src/features/store/product.slice.ts b/src/features/store/product.slice.ts index 83ec8690f..d130e3472 100644 --- a/src/features/store/product.slice.ts +++ b/src/features/store/product.slice.ts @@ -40,4 +40,4 @@ const productSlice = createSlice({ }); export default productSlice.reducer; -export const cartActions = productSlice.actions; +export const productActions = productSlice.actions; diff --git a/src/features/store/user.slice.ts b/src/features/store/user.slice.ts index a04f27fd3..70ee06cb0 100644 --- a/src/features/store/user.slice.ts +++ b/src/features/store/user.slice.ts @@ -11,6 +11,7 @@ export interface IUserState { const initialState: IUserState = { jwt: loadState(JWT_PERSISTENT_STATE)?.jwt ?? null, + profile: loadState(JWT_PERSISTENT_STATE)?.profile ?? null, }; const userSlice = createSlice({ @@ -20,8 +21,9 @@ const userSlice = createSlice({ addJwt: (state, action: PayloadAction) => { state.jwt = action.payload; }, - addProfile: (state, action: PayloadAction) => { - state.profile = action.payload; + addProfile: (state, action: PayloadAction) => { + state.jwt = action.payload.jwt; + state.profile = action.payload.profile; }, logout: (state) => { state.jwt = null; diff --git a/src/homeworks/ts1/3_write.ts b/src/homeworks/ts1/3_write.ts index 92de62478..f237ef636 100644 --- a/src/homeworks/ts1/3_write.ts +++ b/src/homeworks/ts1/3_write.ts @@ -84,7 +84,7 @@ export type Profit = { * Принимает дату создания (строка) * */ export const createRandomProduct = (createdAt: string): Product => ({ - id: faker.number.int(), + id: faker.number.int({ max: 5 }), name: faker.commerce.product(), photo: faker.image.avatar(), desc: faker.commerce.productDescription(), diff --git a/src/index.tsx b/src/index.tsx index 38c358e5f..ab667e4df 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -14,9 +14,9 @@ root.render( - + - + diff --git a/src/pages/AuthScreen/SingInBlock/Login.tsx b/src/pages/AuthScreen/SingInBlock/Login.tsx index 04cec7437..b6ea6a303 100644 --- a/src/pages/AuthScreen/SingInBlock/Login.tsx +++ b/src/pages/AuthScreen/SingInBlock/Login.tsx @@ -3,7 +3,7 @@ import { useForm, SubmitHandler } from 'react-hook-form'; import cn from 'clsx'; import { useDispatch, useSelector } from 'react-redux'; import { AppDispath, RootState } from 'src/features/store/store'; -import { userActions } from 'src/features/store/user.slice'; +import { IUserState, userActions } from 'src/features/store/user.slice'; import { useNavigate } from 'react-router'; import { IUserProfile } from 'src/entities/interfaces/IUserProfile'; @@ -27,26 +27,25 @@ function Login() { const dispatch = useDispatch(); const navigate = useNavigate(); const jwt = useSelector((s: RootState) => s.user.userData?.jwt); - - const profile: IUserProfile = { + const [checked, setChecked] = React.useState(false); + let profile: IUserProfile = { id: 123, lastName: 'Ivanov', firstName: 'Ivan', phone: '89260010101', email: 'test@mail.ru', - role: 'Admin', + isAdmin: false, }; - useEffect(() => { - if (jwt) { - dispatch(userActions.addProfile(profile)); - navigate('/'); - } - }, [jwt, navigate]); + const handleChange = () => { + setChecked(!checked); + }; const onSubmit: SubmitHandler = (data) => { const jwt = Math.random().toString(16); - dispatch(userActions.addJwt(jwt)); + const user: IUserState = { jwt: jwt, profile: { ...profile, email: data.email, isAdmin: checked } }; + dispatch(userActions.addProfile(user)); + navigate('/'); }; const passwordError = ( @@ -59,7 +58,7 @@ function Login() { ); return ( -
    +
    {errors.password && passwordError}
    +
    + + +
    diff --git a/src/pages/ListProduct/ListProductPage.tsx b/src/pages/ListProduct/ListProductPage.tsx index cb7063dd8..06e675d21 100644 --- a/src/pages/ListProduct/ListProductPage.tsx +++ b/src/pages/ListProduct/ListProductPage.tsx @@ -1,51 +1,23 @@ import React, { useEffect, useState } from 'react'; import { ListProduct } from 'src/shared/ui/listProduct'; import { useInView } from 'react-intersection-observer'; -import { createRandomProduct } from 'src/homeworks/ts1/3_write'; -import Modal from 'src/stories/Homework2/modals/Modal'; -import FormProduct from 'src/shared/ui/formProduct/FormProduct'; -import IShortProduct from 'src/entities/interfaces/IShortProduct'; +import { useDispatch, useSelector } from 'react-redux'; +import { AppDispath, RootState } from 'src/features/store/store'; +import { productActions } from 'src/features/store/product.slice'; const ListProductPage = () => { const { ref, inView } = useInView({ threshold: 0.7 }); - const [products, setProducts] = useState([]); - function addProduct() { - const createdAt = '2023-06-06T12:06:56.957Z'; - const product = createRandomProduct(createdAt); - setProducts([ - ...products, - { - id: product.id, - price: product.price, - // category: product.category.name, - title: product.name, - description: product.desc, - image: product.photo, - }, - ]); - } + const products = useSelector((s: RootState) => s.user.productData.items); + const dispatch = useDispatch(); - // useEffect(() => { - // addProduct(); - // }, []); - - useEffect(() => { - if (inView) { - addProduct(); - } - }, [addProduct, inView]); return ( <> +
    -
    - -
    ); }; diff --git a/src/routes/helpers/ProtectedRouteAdmin.tsx b/src/routes/helpers/ProtectedRouteAdmin.tsx index 10c2b3747..41e5963ca 100644 --- a/src/routes/helpers/ProtectedRouteAdmin.tsx +++ b/src/routes/helpers/ProtectedRouteAdmin.tsx @@ -5,15 +5,13 @@ import { RootState } from 'src/features/store/store'; interface IProtectedRouteAdmin { children: React.ReactElement; - role: string; } -export const ProtectedRouteAdmin = ({ role, children }: IProtectedRouteAdmin) => { +export const ProtectedRouteAdmin = ({ children }: IProtectedRouteAdmin) => { const user = useSelector((s: RootState) => s.user); if (!user.userData?.jwt) return ; - - if (user.userData?.profile?.role !== role) return ; + if (!user.userData?.profile?.isAdmin) return ; return children; }; diff --git a/src/routes/routes.data.tsx b/src/routes/routes.data.tsx index d217883a8..ea0814da5 100644 --- a/src/routes/routes.data.tsx +++ b/src/routes/routes.data.tsx @@ -40,7 +40,7 @@ export const routes = createBrowserRouter([ { path: '/listProduct/edit', element: ( - + ), @@ -48,7 +48,7 @@ export const routes = createBrowserRouter([ { path: '/listProduct/add', element: ( - + ), diff --git a/src/routes/routes.tsx b/src/routes/routes.tsx new file mode 100644 index 000000000..cabc74826 --- /dev/null +++ b/src/routes/routes.tsx @@ -0,0 +1,63 @@ +import React from 'react'; +import { BrowserRouter, Route, RouterProvider, Routes, useLocation } from 'react-router'; +import { routes } from 'src/routes/routes.data'; +import { EditProductModal } from 'src/shared/ui/modals/modal/EditProductModal'; +import Layout from 'src/shared/ui/layouts/Layout'; +import Main from 'src/widgets/main/Main'; +import { ListProduct } from 'src/shared/ui/listProduct'; +import ListProductPage from 'src/pages/ListProduct/ListProductPage'; +import { ProtectedRouteAdmin } from 'src/routes/helpers/ProtectedRouteAdmin'; +import { RequireAuth } from 'src/routes/helpers/RequireAuth'; +import UserProfile from 'src/pages/profile/UserProfile'; +import CartProduct from 'src/shared/ui/cartProduct/CartProduct'; +import Error from 'src/pages/error/Error'; +import { AddProductModal } from 'src/shared/ui/modals/modal/AddProductModal'; +import Login from 'src/pages/AuthScreen/SingInBlock/Login'; + +export const CustomRoutes = () => { + const location = useLocation(); + const previousLocation = location.state?.previousLocation; + + return ( + <> + + + + + + + } + /> + + + + + + + + {previousLocation && ( + + + + + } + /> + + + + } + /> + + )} + + ); +}; diff --git a/src/shared/ui/formProduct/FormProduct.tsx b/src/shared/ui/formProduct/FormProduct.tsx index 4976f4d00..e1687beb3 100644 --- a/src/shared/ui/formProduct/FormProduct.tsx +++ b/src/shared/ui/formProduct/FormProduct.tsx @@ -2,6 +2,11 @@ import React, { FC } from 'react'; import IFullProduct from 'src/entities/interfaces/IFullProduct'; import { useForm, SubmitHandler } from 'react-hook-form'; import cn from 'clsx'; +import { useDispatch } from 'react-redux'; +import { AppDispath } from 'src/features/store/store'; +import { productActions } from 'src/features/store/product.slice'; +import IShortProduct from 'src/entities/interfaces/IShortProduct'; +import { faker } from '@faker-js/faker'; const FormProduct = () => { const { @@ -9,19 +14,22 @@ const FormProduct = () => { handleSubmit, reset, formState: { errors }, - } = useForm({ + } = useForm({ mode: 'onChange', }); + const dispatch = useDispatch(); - const onSubmit: SubmitHandler = (data) => { - console.log(data); + const onSubmit: SubmitHandler = (data) => { + console.log(data) + if (!data.id) data = { ...data, id: faker.number.int() }; + dispatch(productActions.add(data)); reset(); }; return (
    - + { />
    - + { />
    - + { />
    - + { />
    ); diff --git a/src/shared/ui/header/Header.tsx b/src/shared/ui/header/Header.tsx index c655fb411..5ddb8e691 100644 --- a/src/shared/ui/header/Header.tsx +++ b/src/shared/ui/header/Header.tsx @@ -3,7 +3,7 @@ import { withTranslation } from 'react-i18next'; import Logo from '../logo/Logo'; import { LangSwitcher } from 'src/features/LangSwitcher/LangSwitcher'; -import { Link, NavLink, useNavigate } from 'react-router'; +import { Link, NavLink, useLocation, useNavigate } from 'react-router'; import { ThemeContext } from 'src/context/themeContext'; import { clsx as cn } from 'clsx'; import { ChangeThemeButton } from '../changeThemeButton/ChangeThemeButton'; @@ -19,7 +19,7 @@ export const HeaderOrigin: FC = () => { dispatch(userActions.logout()); navigate('/login'); }; - + const location = useLocation(); return (
    ); From 68175a2757651a22a87f50ffb4b8c8acfeeb7917 Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Wed, 18 Dec 2024 20:14:25 +0300 Subject: [PATCH 52/53] =?UTF-8?q?=D1=80=D0=B5=D0=B2=D1=8C=D1=8E=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/routes/helpers/ProtectedRouteAdmin.tsx | 8 ++++++-- src/shared/ui/formProduct/FormProduct.tsx | 1 - 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/routes/helpers/ProtectedRouteAdmin.tsx b/src/routes/helpers/ProtectedRouteAdmin.tsx index 41e5963ca..f0ce79188 100644 --- a/src/routes/helpers/ProtectedRouteAdmin.tsx +++ b/src/routes/helpers/ProtectedRouteAdmin.tsx @@ -10,8 +10,12 @@ interface IProtectedRouteAdmin { export const ProtectedRouteAdmin = ({ children }: IProtectedRouteAdmin) => { const user = useSelector((s: RootState) => s.user); - if (!user.userData?.jwt) return ; - if (!user.userData?.profile?.isAdmin) return ; + const isAuthenticated = user.userData?.jwt; + const isAdmin = user.userData?.profile?.isAdmin; + + if (!isAuthenticated || !isAdmin) { + return ; + } return children; }; diff --git a/src/shared/ui/formProduct/FormProduct.tsx b/src/shared/ui/formProduct/FormProduct.tsx index e1687beb3..b0607a8db 100644 --- a/src/shared/ui/formProduct/FormProduct.tsx +++ b/src/shared/ui/formProduct/FormProduct.tsx @@ -20,7 +20,6 @@ const FormProduct = () => { const dispatch = useDispatch(); const onSubmit: SubmitHandler = (data) => { - console.log(data) if (!data.id) data = { ...data, id: faker.number.int() }; dispatch(productActions.add(data)); reset(); From 03bde8e16c11e671c1014e1c6c23909ce5e1b0ec Mon Sep 17 00:00:00 2001 From: yurabox2017 Date: Mon, 23 Dec 2024 15:06:52 +0300 Subject: [PATCH 53/53] =?UTF-8?q?=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BB=20?= =?UTF-8?q?=D1=82=D0=B5=D1=81=D1=82=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ts1/accountService/AccountService.test.ts | 53 +++++++++++++++++++ .../ts1/accountService/AccountService.ts | 35 ++++++++++++ .../ts1/accountService/accountServiceEnum.ts | 12 +++++ 3 files changed, 100 insertions(+) create mode 100644 src/homeworks/ts1/accountService/AccountService.test.ts create mode 100644 src/homeworks/ts1/accountService/AccountService.ts create mode 100644 src/homeworks/ts1/accountService/accountServiceEnum.ts diff --git a/src/homeworks/ts1/accountService/AccountService.test.ts b/src/homeworks/ts1/accountService/AccountService.test.ts new file mode 100644 index 000000000..0b779c193 --- /dev/null +++ b/src/homeworks/ts1/accountService/AccountService.test.ts @@ -0,0 +1,53 @@ +import { AccountService } from './AccountService'; +import { productDiscount, userDiscount } from './accountServiceEnum'; + +describe('AccountService', () => { + let accountService: AccountService; + + beforeEach(() => { + accountService = new AccountService(); + }); + + it('Set user discount', () => { + accountService.setUserDiscount(userDiscount.Premium, 30); + accountService.setUserDiscount(userDiscount.Standard, 20); + + const discountPremium = accountService.getDiscount(productDiscount.Car, userDiscount.Premium); + const discountStandard = accountService.getDiscount(productDiscount.Car, userDiscount.Standard); + + expect(discountPremium).toBe(30); + expect(discountStandard).toBe(20); + }); + + it('Set product and user discount', () => { + accountService.setUserDiscount(userDiscount.Premium, 30); + accountService.setUserDiscount(userDiscount.Standard, 20); + accountService.setUserDiscount(userDiscount.Gold, 30); + + accountService.setProductDiscount(productDiscount.Car, userDiscount.Premium, 10); + accountService.setProductDiscount(productDiscount.Car, userDiscount.Standard, 5); + + const discountPremium = accountService.getDiscount(productDiscount.Car, userDiscount.Premium); + const discountStandard = accountService.getDiscount(productDiscount.Car, userDiscount.Standard); + const discountFree = accountService.getDiscount(productDiscount.Car, userDiscount.Free); + const discountGold = accountService.getDiscount(productDiscount.Car, userDiscount.Gold); + + expect(discountPremium).toBe(40); + expect(discountStandard).toBe(25); + expect(discountGold).toBe(30); + expect(discountFree).toBe(0); + + }); + + it('Set user negative discount', () => { + expect(() => accountService.setUserDiscount(userDiscount.Premium, -15)).toThrow( + 'Скидка не может быть меньше нуля!' + ); + }); + + it('Set product negative discount', () => { + expect(() => accountService.setProductDiscount(productDiscount.Food, userDiscount.Premium, -10)).toThrow( + 'Скидка не может быть меньше нуля!' + ); + }); +}); diff --git a/src/homeworks/ts1/accountService/AccountService.ts b/src/homeworks/ts1/accountService/AccountService.ts new file mode 100644 index 000000000..8dac1f00f --- /dev/null +++ b/src/homeworks/ts1/accountService/AccountService.ts @@ -0,0 +1,35 @@ +import { productDiscount, userDiscount } from './accountServiceEnum'; + +export type UserType = userDiscount; +export type ProductType = productDiscount; + +export class AccountService { + private userDiscounts: Map = new Map(); + private productDiscounts: Map> = new Map(); + + setUserDiscount(user: UserType, discount: number) { + if (discount < 0) throw new Error('Скидка не может быть меньше нуля!'); + + this.userDiscounts.set(user, discount); + } + + setProductDiscount(productType: ProductType, userType: UserType, discount: number) { + if (discount < 0) { + throw new Error('Скидка не может быть меньше нуля!'); + } + + if (!this.productDiscounts.has(productType)) { + this.productDiscounts.set(productType, new Map()); + } + + const mapProducts = this.productDiscounts.get(productType); + mapProducts.set(userType, discount); + } + + getDiscount(productType: ProductType, userType: UserType): number { + const userDiscount = this.userDiscounts.get(userType) ?? 0; + const productDiscount = this.productDiscounts.get(productType)?.get(userType) ?? 0; + + return userDiscount + productDiscount; + } +} diff --git a/src/homeworks/ts1/accountService/accountServiceEnum.ts b/src/homeworks/ts1/accountService/accountServiceEnum.ts new file mode 100644 index 000000000..c2180be0a --- /dev/null +++ b/src/homeworks/ts1/accountService/accountServiceEnum.ts @@ -0,0 +1,12 @@ +export enum userDiscount { + Standard = 'Standard', + Premium = 'Premium', + Gold = 'Gold', + Free = 'Free', +} + +export enum productDiscount { + Car = 'Car', + Toy = 'Toy', + Food = 'Food', +}