Commit 6ab37d66 authored by bekzat kapan's avatar bekzat kapan

Merge branch '7-frontend-logic' into 'master'

Resolve "Фронтенд: Бизнес-логика"

Closes #7

See merge request !13
parents 3f46dd44 1d803225
{
"root": true,
"env": {
"browser": true,
"es2021": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:react-hooks/recommended",
"plugin:@typescript-eslint/recommended",
"plugin:jsx-a11y/recommended",
"prettier"
],
"plugins": ["react", "react-hooks", "@typescript-eslint", "jsx-a11y", "prettier"],
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"jsx": true
},
"ecmaVersion": 12,
"sourceType": "module"
},
"rules": {
"prettier/prettier": ["error"],
"react/react-in-jsx-scope": "off",
"react/prop-types": "off",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/no-unused-vars": ["warn", { "argsIgnorePattern": "^_" }]
},
"settings": {
"react": {
"version": "detect"
}
}
}
\ No newline at end of file
{
"semi": true,
"singleQuote": true,
"tabWidth": 2,
"trailingComma": "es5",
"printWidth": 80
}
\ No newline at end of file
...@@ -12,15 +12,21 @@ ...@@ -12,15 +12,21 @@
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.2.1", "@mui/icons-material": "^6.2.1",
"@mui/material": "^6.2.1", "@mui/material": "^6.2.1",
"@reduxjs/toolkit": "^2.5.0",
"axios": "^1.7.9",
"next": "15.1.1", "next": "15.1.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0" "react-dom": "^19.0.0",
"react-redux": "^9.2.0",
"redux": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@types/react-redux": "^7.1.34",
"@types/redux": "^3.6.31",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.1.1", "eslint-config-next": "15.1.1",
"typescript": "^5" "typescript": "^5"
...@@ -1370,6 +1376,30 @@ ...@@ -1370,6 +1376,30 @@
"url": "https://opencollective.com/popperjs" "url": "https://opencollective.com/popperjs"
} }
}, },
"node_modules/@reduxjs/toolkit": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.5.0.tgz",
"integrity": "sha512-awNe2oTodsZ6LmRqmkFhtb/KH03hUhxOamEQy411m3Njj3BbFvoBovxo4Q1cBWnV1ErprVj9MlF0UPXkng0eyg==",
"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 || ^19",
"react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-redux": {
"optional": true
}
}
},
"node_modules/@rtsao/scc": { "node_modules/@rtsao/scc": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
...@@ -1406,6 +1436,17 @@ ...@@ -1406,6 +1436,17 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/@types/hoist-non-react-statics": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.6.tgz",
"integrity": "sha512-lPByRJUer/iN/xa4qpyL0qmL11DqNW81iU/IG1S3uvRUq4oKagz8VCxZjiWkumgt66YT3vOdDgZ0o32sGKtCEw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0"
}
},
"node_modules/@types/json-schema": { "node_modules/@types/json-schema": {
"version": "7.0.15", "version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
...@@ -1461,6 +1502,29 @@ ...@@ -1461,6 +1502,29 @@
"@types/react": "^19.0.0" "@types/react": "^19.0.0"
} }
}, },
"node_modules/@types/react-redux": {
"version": "7.1.34",
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.34.tgz",
"integrity": "sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/hoist-non-react-statics": "^3.3.0",
"@types/react": "*",
"hoist-non-react-statics": "^3.3.0",
"redux": "^4.0.0"
}
},
"node_modules/@types/react-redux/node_modules/redux": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
"dev": true,
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.9.2"
}
},
"node_modules/@types/react-transition-group": { "node_modules/@types/react-transition-group": {
"version": "4.4.12", "version": "4.4.12",
"resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.12.tgz",
...@@ -1470,6 +1534,19 @@ ...@@ -1470,6 +1534,19 @@
"@types/react": "*" "@types/react": "*"
} }
}, },
"node_modules/@types/redux": {
"version": "3.6.31",
"resolved": "https://registry.npmjs.org/@types/redux/-/redux-3.6.31.tgz",
"integrity": "sha512-UEa68g5Q1EPG4Wsnxqhbl0luFVRyX5dbKF3MQstkoWawSNKLulS2WsZZbALsPUX4Ax6SY9faqEs6dPM47cBAcg==",
"dev": true,
"license": "MIT"
},
"node_modules/@types/use-sync-external-store": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz",
"integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==",
"license": "MIT"
},
"node_modules/@typescript-eslint/eslint-plugin": { "node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.18.1", "version": "8.18.1",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.1.tgz",
...@@ -1943,6 +2020,12 @@ ...@@ -1943,6 +2020,12 @@
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"license": "MIT"
},
"node_modules/available-typed-arrays": { "node_modules/available-typed-arrays": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
...@@ -1969,6 +2052,17 @@ ...@@ -1969,6 +2052,17 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/axios": {
"version": "1.7.9",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
"license": "MIT",
"dependencies": {
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axobject-query": { "node_modules/axobject-query": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
...@@ -2192,6 +2286,18 @@ ...@@ -2192,6 +2286,18 @@
"simple-swizzle": "^0.2.2" "simple-swizzle": "^0.2.2"
} }
}, },
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"license": "MIT",
"dependencies": {
"delayed-stream": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
"node_modules/concat-map": { "node_modules/concat-map": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
...@@ -2363,6 +2469,15 @@ ...@@ -2363,6 +2469,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"license": "MIT",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/detect-libc": { "node_modules/detect-libc": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
...@@ -3212,6 +3327,26 @@ ...@@ -3212,6 +3327,26 @@
"dev": true, "dev": true,
"license": "ISC" "license": "ISC"
}, },
"node_modules/follow-redirects": {
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"license": "MIT",
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": { "node_modules/for-each": {
"version": "0.3.3", "version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
...@@ -3222,6 +3357,20 @@ ...@@ -3222,6 +3357,20 @@
"is-callable": "^1.1.3" "is-callable": "^1.1.3"
} }
}, },
"node_modules/form-data": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
"license": "MIT",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
...@@ -3496,6 +3645,16 @@ ...@@ -3496,6 +3645,16 @@
"node": ">= 4" "node": ">= 4"
} }
}, },
"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/import-fresh": { "node_modules/import-fresh": {
"version": "3.3.0", "version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
...@@ -4175,6 +4334,27 @@ ...@@ -4175,6 +4334,27 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"license": "MIT",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime-types": {
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"license": "MIT",
"dependencies": {
"mime-db": "1.52.0"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
...@@ -4594,6 +4774,12 @@ ...@@ -4594,6 +4774,12 @@
"react-is": "^16.13.1" "react-is": "^16.13.1"
} }
}, },
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
"license": "MIT"
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
...@@ -4652,6 +4838,29 @@ ...@@ -4652,6 +4838,29 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/react-redux": {
"version": "9.2.0",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
"license": "MIT",
"dependencies": {
"@types/use-sync-external-store": "^0.0.6",
"use-sync-external-store": "^1.4.0"
},
"peerDependencies": {
"@types/react": "^18.2.25 || ^19",
"react": "^18.0 || ^19",
"redux": "^5.0.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"redux": {
"optional": true
}
}
},
"node_modules/react-transition-group": { "node_modules/react-transition-group": {
"version": "4.4.5", "version": "4.4.5",
"resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz",
...@@ -4668,6 +4877,21 @@ ...@@ -4668,6 +4877,21 @@
"react-dom": ">=16.6.0" "react-dom": ">=16.6.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-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/reflect.getprototypeof": { "node_modules/reflect.getprototypeof": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.8.tgz",
...@@ -4716,6 +4940,12 @@ ...@@ -4716,6 +4940,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"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": { "node_modules/resolve": {
"version": "1.22.9", "version": "1.22.9",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.9.tgz",
...@@ -5445,6 +5675,15 @@ ...@@ -5445,6 +5675,15 @@
"punycode": "^2.1.0" "punycode": "^2.1.0"
} }
}, },
"node_modules/use-sync-external-store": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz",
"integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==",
"license": "MIT",
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
}
},
"node_modules/which": { "node_modules/which": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
......
...@@ -3,25 +3,32 @@ ...@@ -3,25 +3,32 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev -p 0666",
"build": "next build", "build": "next build",
"start": "next start", "start": "next start",
"lint": "next lint" "lint": "next lint",
"format": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@emotion/react": "^11.14.0", "@emotion/react": "^11.14.0",
"@emotion/styled": "^11.14.0", "@emotion/styled": "^11.14.0",
"@mui/icons-material": "^6.2.1", "@mui/icons-material": "^6.2.1",
"@mui/material": "^6.2.1", "@mui/material": "^6.2.1",
"@reduxjs/toolkit": "^2.5.0",
"axios": "^1.7.9",
"next": "15.1.1", "next": "15.1.1",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0" "react-dom": "^19.0.0",
"react-redux": "^9.2.0",
"redux": "^5.0.1"
}, },
"devDependencies": { "devDependencies": {
"@eslint/eslintrc": "^3", "@eslint/eslintrc": "^3",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"@types/react-redux": "^7.1.34",
"@types/redux": "^3.6.31",
"eslint": "^9", "eslint": "^9",
"eslint-config-next": "15.1.1", "eslint-config-next": "15.1.1",
"typescript": "^5" "typescript": "^5"
......
.loader {
width: 100%;
display: flex;
justify-content: center;
}
.lds-ripple {
color: #c7cad1
}
.lds-ripple,
.lds-ripple div {
box-sizing: border-box;
}
.lds-ripple {
display: inline-block;
position: relative;
width: 80px;
height: 80px;
}
.lds-ripple div {
position: absolute;
border: 4px solid currentColor;
opacity: 1;
border-radius: 50%;
animation: lds-ripple 1s cubic-bezier(0, 0.2, 0.8, 1) infinite;
}
.lds-ripple div:nth-child(2) {
animation-delay: -0.5s;
}
@keyframes lds-ripple {
0% {
top: 36px;
left: 36px;
width: 8px;
height: 8px;
opacity: 0;
}
4.9% {
top: 36px;
left: 36px;
width: 8px;
height: 8px;
opacity: 0;
}
5% {
top: 36px;
left: 36px;
width: 8px;
height: 8px;
opacity: 1;
}
100% {
top: 0;
left: 0;
width: 80px;
height: 80px;
opacity: 0;
}
}
\ No newline at end of file
"use client";
import { store } from "@/store/store";
import { Provider } from "react-redux";
export default function RootLayout({ export default function RootLayout({
children, children,
...@@ -5,10 +8,10 @@ export default function RootLayout({ ...@@ -5,10 +8,10 @@ export default function RootLayout({
children: React.ReactNode; children: React.ReactNode;
}>) { }>) {
return ( return (
<html lang="en"> <Provider store={store}>
<body> <html lang="en">
{children} <body>{children}</body>
</body> </html>
</html> </Provider>
); );
} }
"use client"; 'use client';
import { Box, Button, Container, Grid2, Typography } from "@mui/material"; import { Box, Button, Container, Grid2, Typography } from '@mui/material';
import ArrowDownwardIcon from "@mui/icons-material/ArrowDownward"; import ArrowDownwardIcon from '@mui/icons-material/ArrowDownward';
import { ChangeEvent, FormEvent, useState } from "react"; import { ChangeEvent, useEffect, useState } from 'react';
import InputField from "./components/InputField"; import InputField from './components/InputField';
import '@/animations/Loader.css';
import { useAppDispatch, useAppSelector } from '@/store/hooks';
import { encodeMessage, decodeMessage } from '@/features/requestSlice';
interface IFormData { interface IFormData {
decoded: string; decoded: string;
...@@ -11,101 +14,170 @@ interface IFormData { ...@@ -11,101 +14,170 @@ interface IFormData {
} }
export default function Home() { export default function Home() {
const [formData, setFormData] = useState<IFormData>({ const dispatch = useAppDispatch();
encoded: "", const { loading, result, error } = useAppSelector((state) => state.request);
decoded: "",
password: "",
});
const submitFormHandler = (e: FormEvent<HTMLFormElement>) => { const [formData, setFormData] = useState<IFormData>({
e.preventDefault(); encoded: '',
}; decoded: '',
password: '',
});
const onInputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => { const onCypherHandler = (): void => {
const { name, value } = e.target; if (!formData.password.trim()) {
setFormData((prevData) => ({ alert('Please, type in password!');
...prevData, return;
[name.toLowerCase()]: value, }
})); if (!formData.encoded.trim()) {
}; alert('Please fill in the "Decoded" field!');
return;
}
dispatch(
encodeMessage({ password: formData.password, message: formData.decoded })
);
};
const onDecypherHandler = (): void => {
if (!formData.password.trim()) {
alert('Please, type in password!');
return;
}
if (!formData.decoded.trim()) {
alert('Please fill in the "Decoded" field!');
return;
}
dispatch(
decodeMessage({ password: formData.password, message: formData.decoded })
);
};
return ( useEffect(() => {
<div className="App"> if (result) {
<Container sx={{ marginTop: 2 }} maxWidth="lg"> if (formData.decoded) {
<Typography setFormData((prev) => ({ ...prev, encoded: result }));
variant="h3" } else {
sx={{ setFormData((prev) => ({ ...prev, decoded: result }));
fontSize: { }
xs: "24px", }
sm: "36px", }, [result]);
},
}}
>
Cypher Coder/Decoder
</Typography>
<Box
sx={{
maxWidth: {
xs: "90%",
sm: "80%",
lg: "70%",
},
}}
component="form"
autoComplete="off"
onSubmit={submitFormHandler}
paddingY={2}
>
<Grid2 container direction="column" spacing={2}>
<InputField name="Decoded" value={formData.decoded} onChange={onInputChangeHandler} />
<Grid2
container
alignItems="center"
sx={{
flexDirection: {
xs: "column",
md: "row",
},
}}
>
<Grid2 size={7}>
<InputField name="Password" value={formData.password} onChange={onInputChangeHandler} />
</Grid2>
<Grid2 container justifyContent={{}}>
<Grid2>
<Button size="small" variant="contained" startIcon={<ArrowDownwardIcon />}>
<Typography
sx={{
display: { xs: "none", md: "inline" },
}}
>
Encode
</Typography>
</Button>
</Grid2>
<Grid2> const onInputChangeHandler = (e: ChangeEvent<HTMLInputElement>) => {
<Button const { name, value } = e.target;
size="small" setFormData((prevData) => ({
variant="contained" ...prevData,
startIcon={<ArrowDownwardIcon sx={{ transform: "rotate(180deg)" }} />} [name.toLowerCase()]: value,
> }));
<Typography };
sx={{
display: { xs: "none", md: "inline" },
}}
>
Decode
</Typography>
</Button>
</Grid2>
</Grid2>
</Grid2>
<InputField name="Encoded" value={formData.encoded} onChange={onInputChangeHandler} /> return (
</Grid2>
</Box> <div className="App">
</Container> <Container sx={{ marginTop: 2 }} maxWidth="lg">
</div> <Typography
); variant="h3"
sx={{
fontSize: {
xs: '24px',
sm: '36px',
},
}}
>
Cypher Coder/Decoder
</Typography>
<Box
sx={{
maxWidth: {
xs: '90%',
sm: '80%',
lg: '70%',
},
}}
component="form"
autoComplete="off"
padding={2}
>
<Grid2 container direction="column" spacing={2}>
<InputField
name="Decoded"
value={formData.decoded}
onChange={onInputChangeHandler}
/>
<Grid2
container
alignItems="center"
sx={{
flexDirection: {
xs: 'column',
md: 'row',
},
}}
>
<Grid2 size={7}>
<InputField
name="Password"
value={formData.password}
onChange={onInputChangeHandler}
/>
</Grid2>
<Grid2 container>
<Grid2>
<Button
size="small"
variant="contained"
startIcon={<ArrowDownwardIcon />}
onClick={onDecypherHandler}
>
<Typography
sx={{
display: { xs: 'none', md: 'inline' },
}}
>
Encode
</Typography>
</Button>
</Grid2>
<Grid2>
<Button
size="small"
variant="contained"
startIcon={
<ArrowDownwardIcon
sx={{ transform: 'rotate(180deg)' }}
/>
}
onClick={onCypherHandler}
>
<Typography
sx={{
display: { xs: 'none', md: 'inline' },
}}
>
Decode
</Typography>
</Button>
</Grid2>
</Grid2>
</Grid2>
<InputField
name="Encoded"
value={formData.encoded}
onChange={onInputChangeHandler}
/>
</Grid2>
</Box>
{loading && (
<div className="loader">
<div className="lds-ripple">
<div></div>
<div></div>
</div>
</div>
)}
{error && <Typography color="error">{error}</Typography>}
</Container>
</div>
);
} }
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
import axiosClient from '@/helpers/axiosClient';
interface RequestState {
loading: boolean;
error?: string | null;
result: string;
}
interface IData {
password: string;
message: string;
}
const initialState: RequestState = {
loading: false,
error: null,
result: '',
};
export const encodeMessage = createAsyncThunk(
'request/encode',
async (data: IData) => {
try {
const response = await axiosClient.post('/encode', data);
return response.data.encoded;
} catch (error: any) {
throw new Error(
error.response?.data?.message || 'Error decoding message'
);
}
}
);
export const decodeMessage = createAsyncThunk(
'request/decode',
async (data: IData) => {
try {
const response = await axiosClient.post('/decode', data);
return response.data.decoded;
} catch (error: any) {
throw new Error(
error.response?.data?.message || 'Error encoding message'
);
}
}
);
const requestSlice = createSlice({
name: 'request',
initialState,
reducers: {},
extraReducers: (builder) => {
builder
.addCase(encodeMessage.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(encodeMessage.fulfilled, (state, action) => {
state.loading = false;
state.result = action.payload;
})
.addCase(encodeMessage.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || "Error occured";
})
.addCase(decodeMessage.pending, (state) => {
state.loading = true;
state.error = null;
})
.addCase(decodeMessage.fulfilled, (state, action) => {
state.loading = false;
state.result = action.payload;
})
.addCase(decodeMessage.rejected, (state, action) => {
state.loading = false;
state.error = action.error.message || "Error occured";
});
},
});
export default requestSlice.reducer;
import axios from 'axios';
const axiosClient = axios.create({
baseURL: 'https://localhost/8000',
});
export default axiosClient;
import { useDispatch, useSelector } from "react-redux";
import type { TypedUseSelectorHook } from "react-redux";
import type { RootState, AppDispatch } from "./store";
export const useAppDispatch: () => AppDispatch = useDispatch;
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
import { configureStore } from '@reduxjs/toolkit';
import requestReducer from '@/features/requestSlice';
export const store = configureStore({
reducer: {
request: requestReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment