mirror of
https://github.com/bluewave-labs/Checkmate.git
synced 2026-01-17 15:19:50 -06:00
Merge pull request #1711 from Cihatata/i18n-support-client
feat: i18n support for client
This commit is contained in:
273
Client/package-lock.json
generated
273
Client/package-lock.json
generated
@@ -22,6 +22,7 @@
|
||||
"axios": "^1.7.4",
|
||||
"dayjs": "1.11.13",
|
||||
"flag-icons": "7.3.2",
|
||||
"i18next": "^24.2.2",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"joi": "17.13.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
@@ -31,10 +32,12 @@
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^15.4.0",
|
||||
"react-redux": "9.2.0",
|
||||
"react-router": "^6.23.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-toastify": "^10.0.5",
|
||||
"react-world-flags": "^1.6.0",
|
||||
"recharts": "2.15.1",
|
||||
"redux-persist": "6.0.0",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
@@ -2364,6 +2367,14 @@
|
||||
"@svgr/core": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@trysound/sax": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz",
|
||||
"integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==",
|
||||
"engines": {
|
||||
"node": ">=10.13.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/babel__core": {
|
||||
"version": "7.20.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
|
||||
@@ -2868,6 +2879,11 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/boolbase": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
|
||||
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
|
||||
},
|
||||
"node_modules/brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
@@ -3060,6 +3076,14 @@
|
||||
"node": ">= 0.8"
|
||||
}
|
||||
},
|
||||
"node_modules/commander": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz",
|
||||
"integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==",
|
||||
"engines": {
|
||||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
@@ -3112,6 +3136,74 @@
|
||||
"tiny-invariant": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz",
|
||||
"integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0",
|
||||
"css-what": "^6.1.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"domutils": "^3.0.1",
|
||||
"nth-check": "^2.0.1"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css-tree": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.3.1.tgz",
|
||||
"integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.30",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-what": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz",
|
||||
"integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==",
|
||||
"engines": {
|
||||
"node": ">= 6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/csso": {
|
||||
"version": "5.0.5",
|
||||
"resolved": "https://registry.npmjs.org/csso/-/csso-5.0.5.tgz",
|
||||
"integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
|
||||
"dependencies": {
|
||||
"css-tree": "~2.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csso/node_modules/css-tree": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/css-tree/-/css-tree-2.2.1.tgz",
|
||||
"integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
|
||||
"dependencies": {
|
||||
"mdn-data": "2.0.28",
|
||||
"source-map-js": "^1.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0",
|
||||
"npm": ">=7.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csso/node_modules/mdn-data": {
|
||||
"version": "2.0.28",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.28.tgz",
|
||||
"integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
|
||||
@@ -3436,6 +3528,57 @@
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/dom-serializer": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
|
||||
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.2",
|
||||
"entities": "^4.2.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domelementtype": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
|
||||
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
|
||||
"funding": [
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/domhandler": {
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
|
||||
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
|
||||
"dependencies": {
|
||||
"domelementtype": "^2.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domhandler?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/domutils": {
|
||||
"version": "3.2.2",
|
||||
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
|
||||
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
|
||||
"dependencies": {
|
||||
"dom-serializer": "^2.0.0",
|
||||
"domelementtype": "^2.3.0",
|
||||
"domhandler": "^5.0.3"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/domutils?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/dot-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz",
|
||||
@@ -4493,6 +4636,44 @@
|
||||
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"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==",
|
||||
"dependencies": {
|
||||
"void-elements": "3.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/i18next": {
|
||||
"version": "24.2.2",
|
||||
"resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.2.tgz",
|
||||
"integrity": "sha512-NE6i86lBCKRYZa5TaUDkU5S4HFgLIEJRLr3Whf2psgaxBleQ2LC1YW1Vc+SCgkAW7VEzndT6al6+CzegSUHcTQ==",
|
||||
"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"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.23.2"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": "^5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/ieee754": {
|
||||
"version": "1.2.1",
|
||||
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
|
||||
@@ -5292,6 +5473,11 @@
|
||||
"node": ">= 0.4"
|
||||
}
|
||||
},
|
||||
"node_modules/mdn-data": {
|
||||
"version": "2.0.30",
|
||||
"resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.0.30.tgz",
|
||||
"integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
|
||||
},
|
||||
"node_modules/memoize-one": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz",
|
||||
@@ -5413,6 +5599,17 @@
|
||||
"integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/nth-check": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
|
||||
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
|
||||
"dependencies": {
|
||||
"boolbase": "^1.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/fb55/nth-check?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
@@ -5888,6 +6085,27 @@
|
||||
"react": "^18.3.1"
|
||||
}
|
||||
},
|
||||
"node_modules/react-i18next": {
|
||||
"version": "15.4.0",
|
||||
"resolved": "https://registry.npmjs.org/react-i18next/-/react-i18next-15.4.0.tgz",
|
||||
"integrity": "sha512-Py6UkX3zV08RTvL6ZANRoBh9sL/ne6rQq79XlkHEdd82cZr2H9usbWpUNVadJntIZP2pu3M2rL1CN+5rQYfYFw==",
|
||||
"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": "18.3.1",
|
||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
|
||||
@@ -6003,6 +6221,19 @@
|
||||
"react-dom": ">=16.6.0"
|
||||
}
|
||||
},
|
||||
"node_modules/react-world-flags": {
|
||||
"version": "1.6.0",
|
||||
"resolved": "https://registry.npmjs.org/react-world-flags/-/react-world-flags-1.6.0.tgz",
|
||||
"integrity": "sha512-eutSeAy5YKoVh14js/JUCSlA6EBk1n4k+bDaV+NkNB50VhnG+f4QDTpYycnTUTsZ5cqw/saPmk0Z4Fa0VVZ1Iw==",
|
||||
"dependencies": {
|
||||
"svg-country-flags": "^1.2.10",
|
||||
"svgo": "^3.0.2",
|
||||
"world-countries": "^5.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": ">=0.14"
|
||||
}
|
||||
},
|
||||
"node_modules/recharts": {
|
||||
"version": "2.15.1",
|
||||
"resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.1.tgz",
|
||||
@@ -6630,12 +6861,41 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/svg-country-flags": {
|
||||
"version": "1.2.10",
|
||||
"resolved": "https://registry.npmjs.org/svg-country-flags/-/svg-country-flags-1.2.10.tgz",
|
||||
"integrity": "sha512-xrqwo0TYf/h2cfPvGpjdSuSguUbri4vNNizBnwzoZnX0xGo3O5nGJMlbYEp7NOYcnPGBm6LE2axqDWSB847bLw=="
|
||||
},
|
||||
"node_modules/svg-parser": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz",
|
||||
"integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/svgo": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-3.3.2.tgz",
|
||||
"integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
|
||||
"dependencies": {
|
||||
"@trysound/sax": "0.2.0",
|
||||
"commander": "^7.2.0",
|
||||
"css-select": "^5.1.0",
|
||||
"css-tree": "^2.3.1",
|
||||
"css-what": "^6.1.0",
|
||||
"csso": "^5.0.5",
|
||||
"picocolors": "^1.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"svgo": "bin/svgo"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/svgo"
|
||||
}
|
||||
},
|
||||
"node_modules/text-table": {
|
||||
"version": "0.2.0",
|
||||
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
|
||||
@@ -6936,6 +7196,14 @@
|
||||
"vite": ">=2.6.0"
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vt-pbf": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz",
|
||||
@@ -7061,6 +7329,11 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/world-countries": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/world-countries/-/world-countries-5.0.0.tgz",
|
||||
"integrity": "sha512-wAfOT9Y5i/xnxNOdKJKXdOCw9Q3yQLahBUeuRol+s+o20F6h2a4tLEbJ1lBCYwEQ30Sf9Meqeipk1gib3YwF5w=="
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
"axios": "^1.7.4",
|
||||
"dayjs": "1.11.13",
|
||||
"flag-icons": "7.3.2",
|
||||
"i18next": "^24.2.2",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"joi": "17.13.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
@@ -34,10 +35,12 @@
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-i18next": "^15.4.0",
|
||||
"react-redux": "9.2.0",
|
||||
"react-router": "^6.23.0",
|
||||
"react-router-dom": "^6.23.1",
|
||||
"react-toastify": "^10.0.5",
|
||||
"react-world-flags": "^1.6.0",
|
||||
"recharts": "2.15.1",
|
||||
"redux-persist": "6.0.0",
|
||||
"vite-plugin-svgr": "^4.2.0"
|
||||
|
||||
134
Client/src/Components/LanguageSelector.jsx
Normal file
134
Client/src/Components/LanguageSelector.jsx
Normal file
@@ -0,0 +1,134 @@
|
||||
import { useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { Box, MenuItem, Select, Stack } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import Flag from "react-world-flags";
|
||||
|
||||
const LanguageSelector = () => {
|
||||
const { i18n } = useTranslation();
|
||||
const theme = useTheme();
|
||||
const [language, setLanguage] = useState(i18n.language || "gb");
|
||||
|
||||
const handleChange = (event) => {
|
||||
const newLang = event.target.value;
|
||||
setLanguage(newLang);
|
||||
i18n.changeLanguage(newLang);
|
||||
};
|
||||
|
||||
// i18n instance'ından mevcut dilleri al
|
||||
const languages = Object.keys(i18n.options.resources || {});
|
||||
|
||||
return (
|
||||
<Select
|
||||
value={language}
|
||||
onChange={handleChange}
|
||||
size="small"
|
||||
sx={{
|
||||
height: 28,
|
||||
width: 64,
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastText,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
fontSize: 10,
|
||||
"& .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
},
|
||||
"&:hover .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"&.Mui-focused .MuiOutlinedInput-notchedOutline": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"& .MuiSvgIcon-root": {
|
||||
color: theme.palette.primary.contrastText,
|
||||
width: 16,
|
||||
height: 16,
|
||||
right: 4,
|
||||
top: "calc(50% - 8px)",
|
||||
},
|
||||
"& .MuiSelect-select": {
|
||||
padding: "2px 20px 2px 8px",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
fontSize: 10,
|
||||
},
|
||||
}}
|
||||
MenuProps={{
|
||||
PaperProps: {
|
||||
sx: {
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
marginTop: 1,
|
||||
width: 64,
|
||||
"& .MuiMenuItem-root": {
|
||||
padding: "2px 8px",
|
||||
minHeight: 28,
|
||||
fontSize: 10,
|
||||
},
|
||||
},
|
||||
},
|
||||
anchorOrigin: {
|
||||
vertical: "bottom",
|
||||
horizontal: "left",
|
||||
},
|
||||
transformOrigin: {
|
||||
vertical: "top",
|
||||
horizontal: "left",
|
||||
},
|
||||
}}
|
||||
>
|
||||
{languages.map((lang) => (
|
||||
<MenuItem
|
||||
key={lang}
|
||||
value={lang}
|
||||
sx={{
|
||||
color: theme.palette.primary.contrastText,
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
"&.Mui-selected": {
|
||||
backgroundColor: theme.palette.primary.lowContrast,
|
||||
"&:hover": {
|
||||
backgroundColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
ml={0.5}
|
||||
>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{
|
||||
width: 16,
|
||||
height: 12,
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
"& img": {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
objectFit: "cover",
|
||||
borderRadius: 0.5,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Flag code={lang.toUpperCase()} />
|
||||
</Box>
|
||||
<Box
|
||||
component="span"
|
||||
sx={{ textTransform: "uppercase", fontSize: 10 }}
|
||||
>
|
||||
{lang}
|
||||
</Box>
|
||||
</Stack>
|
||||
</MenuItem>
|
||||
))}
|
||||
</Select>
|
||||
);
|
||||
};
|
||||
|
||||
export default LanguageSelector;
|
||||
@@ -3,6 +3,7 @@ import { Box, Button, Stack, Typography } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import TextInput from "../../../../Components/Inputs/TextInput";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
/**
|
||||
* Renders the email step of the login process which includes an email field.
|
||||
@@ -18,6 +19,7 @@ import PropTypes from "prop-types";
|
||||
const EmailStep = ({ form, errors, onSubmit, onChange }) => {
|
||||
const theme = useTheme();
|
||||
const inputRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
@@ -33,8 +35,8 @@ const EmailStep = ({ form, errors, onSubmit, onChange }) => {
|
||||
position="relative"
|
||||
>
|
||||
<Box>
|
||||
<Typography component="h1">Log In</Typography>
|
||||
<Typography>Enter your email address</Typography>
|
||||
<Typography component="h1">{t("authLoginTitle")}</Typography>
|
||||
<Typography>{t("authLoginEnterEmail")}</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
textAlign="left"
|
||||
@@ -48,7 +50,7 @@ const EmailStep = ({ form, errors, onSubmit, onChange }) => {
|
||||
<TextInput
|
||||
type="email"
|
||||
id="login-email-input"
|
||||
label="Email"
|
||||
label={t("email")}
|
||||
isRequired={true}
|
||||
placeholder="jordan.ellis@domain.com"
|
||||
autoComplete="email"
|
||||
@@ -77,7 +79,7 @@ const EmailStep = ({ form, errors, onSubmit, onChange }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Box, Typography, useTheme } from "@mui/material";
|
||||
import PropTypes from "prop-types";
|
||||
import { useNavigate } from "react-router";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const ForgotPasswordLabel = ({ email, errorEmail }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleNavigate = () => {
|
||||
if (email !== "" && !errorEmail) {
|
||||
@@ -20,7 +22,7 @@ const ForgotPasswordLabel = ({ email, errorEmail }) => {
|
||||
display="inline-block"
|
||||
color={theme.palette.primary.main}
|
||||
>
|
||||
Forgot password?
|
||||
{t("authForgotPasswordTitle")}
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
@@ -29,15 +31,15 @@ const ForgotPasswordLabel = ({ email, errorEmail }) => {
|
||||
sx={{ userSelect: "none" }}
|
||||
onClick={handleNavigate}
|
||||
>
|
||||
Reset password
|
||||
{t("authForgotPasswordResetPassword")}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
ForgotPasswordLabel.proptype = {
|
||||
ForgotPasswordLabel.propTypes = {
|
||||
email: PropTypes.string.isRequired,
|
||||
emailError: PropTypes.string.isRequired,
|
||||
errorEmail: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
export default ForgotPasswordLabel;
|
||||
|
||||
@@ -7,7 +7,7 @@ import TextInput from "../../../../Components/Inputs/TextInput";
|
||||
import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments";
|
||||
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
/**
|
||||
* Renders the password step of the login process, including a password input field.
|
||||
*
|
||||
@@ -23,6 +23,7 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
const theme = useTheme();
|
||||
const inputRef = useRef(null);
|
||||
const authState = useSelector((state) => state.auth);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
@@ -38,8 +39,8 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
textAlign="center"
|
||||
>
|
||||
<Box>
|
||||
<Typography component="h1">Log In</Typography>
|
||||
<Typography>Enter your password</Typography>
|
||||
<Typography component="h1">{t("authLoginTitle")}</Typography>
|
||||
<Typography>{t("authLoginEnterPassword")}</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
component="form"
|
||||
@@ -56,7 +57,7 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
<TextInput
|
||||
type="password"
|
||||
id="login-password-input"
|
||||
label="Password"
|
||||
label={t("password")}
|
||||
isRequired={true}
|
||||
placeholder="••••••••••"
|
||||
autoComplete="current-password"
|
||||
@@ -87,7 +88,7 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
}}
|
||||
>
|
||||
<ArrowBackRoundedIcon />
|
||||
Back
|
||||
{t("commonBack")}{" "}
|
||||
</Button>
|
||||
<LoadingButton
|
||||
variant="contained"
|
||||
@@ -104,7 +105,7 @@ const PasswordStep = ({ form, errors, onSubmit, onChange, onBack }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
{t("continue")}
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -15,6 +15,7 @@ import EmailStep from "./Components/EmailStep";
|
||||
import PasswordStep from "./Components/PasswordStep";
|
||||
import ThemeSwitch from "../../../Components/ThemeSwitch";
|
||||
import ForgotPasswordLabel from "./Components/ForgotPasswordLabel";
|
||||
import LanguageSelector from "../../../Components/LanguageSelector";
|
||||
|
||||
const DEMO = import.meta.env.VITE_APP_DEMO;
|
||||
|
||||
@@ -163,11 +164,26 @@ const Login = () => {
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
px={theme.spacing(12)}
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
|
||||
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
|
||||
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
spacing={2}
|
||||
alignItems="center"
|
||||
>
|
||||
<LanguageSelector />
|
||||
<ThemeSwitch />
|
||||
</Stack>
|
||||
</Stack>
|
||||
<Stack
|
||||
width="100%"
|
||||
@@ -213,9 +229,6 @@ const Login = () => {
|
||||
email={form.email}
|
||||
errorEmail={errors.email}
|
||||
/>
|
||||
<Box marginX={"auto"}>
|
||||
<ThemeSwitch />
|
||||
</Box>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -15,7 +15,7 @@ import Background from "../../../assets/Images/background-grid.svg?react";
|
||||
import Logo from "../../../assets/icons/checkmate-icon.svg?react";
|
||||
import Mail from "../../../assets/icons/mail.svg?react";
|
||||
import "../index.css";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
/**
|
||||
* Displays the initial landing page.
|
||||
*
|
||||
@@ -26,7 +26,7 @@ import "../index.css";
|
||||
*/
|
||||
const LandingPage = ({ isSuperAdmin, onSignup }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
<Stack
|
||||
@@ -37,7 +37,9 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
|
||||
<Box>
|
||||
<Typography component="h1">Sign Up</Typography>
|
||||
<Typography>
|
||||
Create your {isSuperAdmin ? "Super admin " : ""}account to get started.
|
||||
{isSuperAdmin
|
||||
? t("authRegisterCreateSuperAdminAccount")
|
||||
: t("authRegisterCreateAccount")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box width="100%">
|
||||
@@ -60,12 +62,12 @@ const LandingPage = ({ isSuperAdmin, onSignup }) => {
|
||||
}}
|
||||
>
|
||||
<Mail />
|
||||
Sign up with Email
|
||||
{t("authRegisterSignUpWithEmail")}
|
||||
</Button>
|
||||
</Box>
|
||||
<Box maxWidth={400}>
|
||||
<Typography className="tos-p">
|
||||
By signing up, you agree to our{" "}
|
||||
{t("authRegisterBySigningUp")}
|
||||
<Typography
|
||||
component="span"
|
||||
onClick={() => {
|
||||
@@ -118,6 +120,7 @@ const Register = ({ isSuperAdmin }) => {
|
||||
const navigate = useNavigate();
|
||||
const { token } = useParams();
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
// TODO If possible, change the IDs of these fields to match the backend
|
||||
const idMap = {
|
||||
"register-firstname-input": "firstName",
|
||||
@@ -307,7 +310,7 @@ const Register = ({ isSuperAdmin }) => {
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
|
||||
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
|
||||
<Typography sx={{ userSelect: "none" }}>{t("commonAppName")}</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
width="100%"
|
||||
@@ -367,7 +370,9 @@ const Register = ({ isSuperAdmin }) => {
|
||||
textAlign="center"
|
||||
p={theme.spacing(12)}
|
||||
>
|
||||
<Typography display="inline-block">Already have an account? —</Typography>
|
||||
<Typography display="inline-block">
|
||||
{t("authRegisterAlreadyHaveAccount")} -
|
||||
</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
ml={theme.spacing(2)}
|
||||
@@ -376,7 +381,7 @@ const Register = ({ isSuperAdmin }) => {
|
||||
}}
|
||||
sx={{ userSelect: "none", color: theme.palette.primary.main }}
|
||||
>
|
||||
Log In
|
||||
{t("authLoginTitle")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -6,7 +6,7 @@ import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
import TextInput from "../../../../Components/Inputs/TextInput";
|
||||
import Check from "../../../../Components/Check/Check";
|
||||
import { useValidatePassword } from "../../hooks/useValidatePassword";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
StepThree.propTypes = {
|
||||
onSubmit: PropTypes.func,
|
||||
onBack: PropTypes.func,
|
||||
@@ -23,6 +23,7 @@ StepThree.propTypes = {
|
||||
function StepThree({ onSubmit, onBack }) {
|
||||
const theme = useTheme();
|
||||
const inputRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
@@ -38,8 +39,8 @@ function StepThree({ onSubmit, onBack }) {
|
||||
textAlign="center"
|
||||
>
|
||||
<Box>
|
||||
<Typography component="h1">Sign Up</Typography>
|
||||
<Typography>Create your password</Typography>
|
||||
<Typography component="h1">{t("signUp")}</Typography>
|
||||
<Typography>{t("createPassword")}</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
component="form"
|
||||
@@ -63,9 +64,9 @@ function StepThree({ onSubmit, onBack }) {
|
||||
type="password"
|
||||
id="register-password-input"
|
||||
name="password"
|
||||
label="Password"
|
||||
label={t("commonPassword")}
|
||||
isRequired={true}
|
||||
placeholder="Create a password"
|
||||
placeholder={t("createAPassword")}
|
||||
autoComplete="current-password"
|
||||
value={form.password}
|
||||
onChange={handleChange}
|
||||
@@ -76,9 +77,9 @@ function StepThree({ onSubmit, onBack }) {
|
||||
type="password"
|
||||
id="register-confirm-input"
|
||||
name="confirm"
|
||||
label="Confirm password"
|
||||
label={t("authSetNewPasswordConfirmPassword")}
|
||||
isRequired={true}
|
||||
placeholder="Confirm your password"
|
||||
placeholder={t("confirmPassword")}
|
||||
autoComplete="current-password"
|
||||
value={form.confirm}
|
||||
onChange={handleChange}
|
||||
@@ -90,33 +91,33 @@ function StepThree({ onSubmit, onBack }) {
|
||||
mb={{ xs: theme.spacing(6), sm: theme.spacing(8) }}
|
||||
>
|
||||
<Check
|
||||
noHighlightText={"Must be at least"}
|
||||
text={"8 characters long"}
|
||||
noHighlightText={t("authPasswordMustBeAtLeast")}
|
||||
text={t("authPasswordCharactersLong")}
|
||||
variant={feedbacks.length}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Must contain at least"}
|
||||
text={"one special character"}
|
||||
noHighlightText={t("authPasswordMustContainAtLeast")}
|
||||
text={t("authPasswordSpecialCharacter")}
|
||||
variant={feedbacks.special}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Must contain at least"}
|
||||
text={"one number"}
|
||||
noHighlightText={t("authPasswordMustContainAtLeast")}
|
||||
text={t("authPasswordOneNumber")}
|
||||
variant={feedbacks.number}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Must contain at least"}
|
||||
text={"one upper character"}
|
||||
noHighlightText={t("authPasswordMustContainAtLeast")}
|
||||
text={t("authPasswordUpperCharacter")}
|
||||
variant={feedbacks.uppercase}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Must contain at least"}
|
||||
text={"one lower character"}
|
||||
noHighlightText={t("authPasswordMustContainAtLeast")}
|
||||
text={t("authPasswordLowerCharacter")}
|
||||
variant={feedbacks.lowercase}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Confirm password and password"}
|
||||
text={"must match"}
|
||||
noHighlightText={t("authPasswordConfirmAndPassword")}
|
||||
text={t("authPasswordMustMatch")}
|
||||
variant={feedbacks.confirm}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -140,7 +141,7 @@ function StepThree({ onSubmit, onBack }) {
|
||||
}}
|
||||
>
|
||||
<ArrowBackRoundedIcon />
|
||||
Back
|
||||
{t("commonBack")}
|
||||
</Button>
|
||||
<Button
|
||||
type="submit"
|
||||
@@ -160,7 +161,7 @@ function StepThree({ onSubmit, onBack }) {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -4,6 +4,7 @@ import { useTheme } from "@emotion/react";
|
||||
import { Box, Button, Stack, Typography } from "@mui/material";
|
||||
import ArrowBackRoundedIcon from "@mui/icons-material/ArrowBackRounded";
|
||||
import TextInput from "../../../../Components/Inputs/TextInput";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
StepTwo.propTypes = {
|
||||
form: PropTypes.object,
|
||||
@@ -27,6 +28,7 @@ StepTwo.propTypes = {
|
||||
function StepTwo({ form, errors, onSubmit, onChange, onBack }) {
|
||||
const theme = useTheme();
|
||||
const inputRef = useRef(null);
|
||||
const { t } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (inputRef.current) {
|
||||
@@ -41,8 +43,8 @@ function StepTwo({ form, errors, onSubmit, onChange, onBack }) {
|
||||
textAlign="center"
|
||||
>
|
||||
<Box>
|
||||
<Typography component="h1">Sign Up</Typography>
|
||||
<Typography>Enter your email address</Typography>
|
||||
<Typography component="h1">{t("signUp")}</Typography>
|
||||
<Typography>{t("enterEmail")}</Typography>
|
||||
</Box>
|
||||
|
||||
<Box
|
||||
@@ -58,7 +60,7 @@ function StepTwo({ form, errors, onSubmit, onChange, onBack }) {
|
||||
<TextInput
|
||||
type="email"
|
||||
id="register-email-input"
|
||||
label="Email"
|
||||
label={t("commonEmail")}
|
||||
isRequired={true}
|
||||
placeholder="jordan.ellis@domain.com"
|
||||
autoComplete="email"
|
||||
@@ -89,7 +91,7 @@ function StepTwo({ form, errors, onSubmit, onChange, onBack }) {
|
||||
}}
|
||||
>
|
||||
<ArrowBackRoundedIcon />
|
||||
Back
|
||||
{t("commonBack")}
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
@@ -105,7 +107,7 @@ function StepTwo({ form, errors, onSubmit, onChange, onBack }) {
|
||||
},
|
||||
}}
|
||||
>
|
||||
Continue
|
||||
{t("continue")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
|
||||
@@ -17,11 +17,13 @@ import Logo from "../../assets/icons/checkmate-icon.svg?react";
|
||||
import Background from "../../assets/Images/background-grid.svg?react";
|
||||
import "./index.css";
|
||||
import { useValidatePassword } from "./hooks/useValidatePassword";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const SetNewPassword = () => {
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
const theme = useTheme();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const passwordId = useId();
|
||||
const confirmPasswordId = useId();
|
||||
@@ -97,7 +99,7 @@ const SetNewPassword = () => {
|
||||
gap={theme.spacing(4)}
|
||||
>
|
||||
<Logo style={{ borderRadius: theme.shape.borderRadius }} />
|
||||
<Typography sx={{ userSelect: "none" }}>Checkmate</Typography>
|
||||
<Typography sx={{ userSelect: "none" }}>{t("commonAppName")}</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
width="100%"
|
||||
@@ -142,10 +144,8 @@ const SetNewPassword = () => {
|
||||
<LockIcon alt="lock icon" />
|
||||
</IconBox>
|
||||
</Stack>
|
||||
<Typography component="h1">Set new password</Typography>
|
||||
<Typography>
|
||||
Your new password must be different to previously used passwords.
|
||||
</Typography>
|
||||
<Typography component="h1">{t("authSetNewPasswordTitle")}</Typography>
|
||||
<Typography>{t("authSetNewPasswordDescription")}</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
width="100%"
|
||||
@@ -166,7 +166,7 @@ const SetNewPassword = () => {
|
||||
id={passwordId}
|
||||
type="password"
|
||||
name="password"
|
||||
label="Password"
|
||||
label={t("commonPassword")}
|
||||
isRequired={true}
|
||||
placeholder="••••••••"
|
||||
value={form.password}
|
||||
@@ -186,7 +186,7 @@ const SetNewPassword = () => {
|
||||
id={confirmPasswordId}
|
||||
type="password"
|
||||
name="confirm"
|
||||
label="Confirm password"
|
||||
label={t("authSetNewPasswordConfirmPassword")}
|
||||
isRequired={true}
|
||||
placeholder="••••••••"
|
||||
value={form.confirm}
|
||||
@@ -201,33 +201,33 @@ const SetNewPassword = () => {
|
||||
mb={theme.spacing(12)}
|
||||
>
|
||||
<Check
|
||||
noHighlightText={"Must be at least"}
|
||||
text={"8 characters long"}
|
||||
noHighlightText={t("authPasswordMustBeAtLeast")}
|
||||
text={t("authPasswordCharactersLong")}
|
||||
variant={feedbacks.length}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Must contain at least"}
|
||||
text={"one special character"}
|
||||
noHighlightText={t("authPasswordMustContainAtLeast")}
|
||||
text={t("authPasswordSpecialCharacter")}
|
||||
variant={feedbacks.special}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Must contain at least"}
|
||||
text={"one number"}
|
||||
noHighlightText={t("authPasswordMustContainAtLeast")}
|
||||
text={t("authPasswordOneNumber")}
|
||||
variant={feedbacks.number}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Must contain at least"}
|
||||
text={"one upper character"}
|
||||
noHighlightText={t("authPasswordMustContainAtLeast")}
|
||||
text={t("authPasswordUpperCharacter")}
|
||||
variant={feedbacks.uppercase}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Must contain at least"}
|
||||
text={"one lower character"}
|
||||
noHighlightText={t("authPasswordMustContainAtLeast")}
|
||||
text={t("authPasswordLowerCharacter")}
|
||||
variant={feedbacks.lowercase}
|
||||
/>
|
||||
<Check
|
||||
noHighlightText={"Confirm password and password"}
|
||||
text={"must match"}
|
||||
noHighlightText={t("authPasswordConfirmAndPassword")}
|
||||
text={t("authPasswordMustMatch")}
|
||||
variant={feedbacks.confirm}
|
||||
/>
|
||||
</Stack>
|
||||
@@ -244,7 +244,7 @@ const SetNewPassword = () => {
|
||||
}
|
||||
sx={{ width: "100%", maxWidth: 400 }}
|
||||
>
|
||||
Reset password
|
||||
{t("authSetNewPasswordResetPassword")}
|
||||
</LoadingButton>
|
||||
</Stack>
|
||||
</Stack>
|
||||
@@ -252,7 +252,7 @@ const SetNewPassword = () => {
|
||||
textAlign="center"
|
||||
p={theme.spacing(12)}
|
||||
>
|
||||
<Typography display="inline-block">Go back to —</Typography>
|
||||
<Typography display="inline-block">{t("goBackTo")} —</Typography>
|
||||
<Typography
|
||||
component="span"
|
||||
color={theme.palette.primary.main}
|
||||
@@ -260,7 +260,7 @@ const SetNewPassword = () => {
|
||||
onClick={() => navigate("/login")}
|
||||
sx={{ userSelect: "none" }}
|
||||
>
|
||||
Log In
|
||||
{t("authLoginTitle")}
|
||||
</Typography>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import axios from "axios";
|
||||
import i18next from 'i18next';
|
||||
const BASE_URL = import.meta.env.VITE_APP_API_BASE_URL;
|
||||
const FALLBACK_BASE_URL = "http://localhost:5000/api/v1";
|
||||
import { clearAuthState } from "../Features/Auth/authSlice";
|
||||
@@ -23,6 +24,21 @@ class NetworkService {
|
||||
}
|
||||
this.setBaseUrl(baseURL);
|
||||
});
|
||||
this.axiosInstance.interceptors.request.use(
|
||||
(config) => {
|
||||
const currentLanguage = i18next.language || 'en';
|
||||
|
||||
config.headers = {
|
||||
...config.headers,
|
||||
"Accept-Language": currentLanguage,
|
||||
};
|
||||
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
this.axiosInstance.interceptors.response.use(
|
||||
(response) => response,
|
||||
(error) => {
|
||||
|
||||
36
Client/src/Utils/i18n.js
Normal file
36
Client/src/Utils/i18n.js
Normal file
@@ -0,0 +1,36 @@
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
const primaryLanguage = 'gb';
|
||||
|
||||
const translations = import.meta.glob('../locales/*.json', { eager: true });
|
||||
|
||||
const resources = {};
|
||||
Object.keys(translations).forEach((path) => {
|
||||
const langCode = path.match(/\/([^/]+)\.json$/)[1];
|
||||
resources[langCode] = {
|
||||
translation: translations[path].default || translations[path]
|
||||
};
|
||||
});
|
||||
|
||||
const savedLanguage = localStorage.getItem("language") || primaryLanguage;
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
lng: savedLanguage,
|
||||
fallbackLng: primaryLanguage,
|
||||
debug: import.meta.env.MODE === 'development',
|
||||
ns: ['translation'],
|
||||
defaultNS: 'translation',
|
||||
interpolation: {
|
||||
escapeValue: false,
|
||||
},
|
||||
});
|
||||
|
||||
i18n.on("languageChanged", (lng) => {
|
||||
localStorage.setItem("language", lng);
|
||||
});
|
||||
|
||||
export default i18n;
|
||||
59
Client/src/locales/gb.json
Normal file
59
Client/src/locales/gb.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"dontHaveAccount": "Don't have account",
|
||||
"email": "E-mail",
|
||||
"forgotPassword": "Forgot Password",
|
||||
"password": "password",
|
||||
"signUp": "Sign up",
|
||||
"submit": "Submit",
|
||||
"title": "Title",
|
||||
"continue": "Continue",
|
||||
"enterEmail": "Enter your email",
|
||||
"authLoginTitle": "Log In",
|
||||
"authLoginEnterPassword": "Enter your password",
|
||||
"commonPassword": "Password",
|
||||
"createPassword": "Create your password",
|
||||
"createAPassword": "Create a password",
|
||||
"commonBack": "Back",
|
||||
"authForgotPasswordTitle": "Forgot password?",
|
||||
"authForgotPasswordResetPassword": "Reset password",
|
||||
"authRegisterAlreadyHaveAccount": "Already have an account?",
|
||||
"commonAppName": "Checkmate",
|
||||
"authLoginEnterEmail": "Enter your email",
|
||||
"authRegisterTitle": "Create an account",
|
||||
"authRegisterStepOneTitle": "Create your account",
|
||||
"authRegisterStepOneDescription": "Enter your details to get started",
|
||||
"authRegisterStepTwoTitle": "Set up your profile",
|
||||
"authRegisterStepTwoDescription": "Tell us more about yourself",
|
||||
"authRegisterStepThreeTitle": "Almost done!",
|
||||
"authRegisterStepThreeDescription": "Review your information",
|
||||
"authForgotPasswordDescription": "No worries, we'll send you reset instructions.",
|
||||
"authForgotPasswordSendInstructions": "Send instructions",
|
||||
"authForgotPasswordBackTo": "Back to",
|
||||
"authCheckEmailTitle": "Check your email",
|
||||
"authCheckEmailDescription": "We sent a password reset link to {{email}}",
|
||||
"authCheckEmailResendEmail": "Resend email",
|
||||
"authCheckEmailBackTo": "Back to",
|
||||
"goBackTo": "Go back to",
|
||||
"authCheckEmailDidntReceiveEmail": "Didn't receive the email?",
|
||||
"authCheckEmailClickToResend": "Click to resend",
|
||||
"authSetNewPasswordTitle": "Set new password",
|
||||
"authSetNewPasswordDescription": "Your new password must be different from previously used passwords.",
|
||||
"authSetNewPasswordNewPassword": "New password",
|
||||
"authSetNewPasswordConfirmPassword": "Confirm password",
|
||||
"confirmPassword": "Confirm your password",
|
||||
"authSetNewPasswordResetPassword": "Reset password",
|
||||
"authSetNewPasswordBackTo": "Back to",
|
||||
"authPasswordMustBeAtLeast": "Must be at least",
|
||||
"authPasswordCharactersLong": "8 characters long",
|
||||
"authPasswordMustContainAtLeast": "Must contain at least",
|
||||
"authPasswordSpecialCharacter": "one special character",
|
||||
"authPasswordOneNumber": "one number",
|
||||
"authPasswordUpperCharacter": "one upper character",
|
||||
"authPasswordLowerCharacter": "one lower character",
|
||||
"authPasswordConfirmAndPassword": "Confirm password and password",
|
||||
"authPasswordMustMatch": "must match",
|
||||
"authRegisterCreateAccount": "Create your account to get started",
|
||||
"authRegisterCreateSuperAdminAccount": "Create your Super admin account to get started",
|
||||
"authRegisterSignUpWithEmail": "Sign up with Email",
|
||||
"authRegisterBySigningUp": "By signing up, you agree to our"
|
||||
}
|
||||
59
Client/src/locales/tr.json
Normal file
59
Client/src/locales/tr.json
Normal file
@@ -0,0 +1,59 @@
|
||||
{
|
||||
"dontHaveAccount": "Hesabınız yok mu",
|
||||
"email": "E-posta",
|
||||
"forgotPassword": "Parolamı unuttum",
|
||||
"password": "Parola",
|
||||
"signUp": "Kayıt Ol",
|
||||
"submit": "Gönder",
|
||||
"title": "Başlık",
|
||||
"continue": "Devam Et",
|
||||
"enterEmail": "E-posta adresinizi girin",
|
||||
"authLoginTitle": "Giriş Yap",
|
||||
"authLoginEnterPassword": "Parolanızı girin",
|
||||
"commonPassword": "Parola",
|
||||
"createPassword": "Parolanızı oluşturun",
|
||||
"createAPassword": "Bir parola oluşturun",
|
||||
"commonBack": "Geri",
|
||||
"authForgotPasswordTitle": "Parolanızı mı unuttunuz?",
|
||||
"authForgotPasswordResetPassword": "Parola sıfırla",
|
||||
"authRegisterAlreadyHaveAccount": "Zaten hesabınız var mı?",
|
||||
"commonAppName": "Checkmate",
|
||||
"authLoginEnterEmail": "E-posta adresinizi girin",
|
||||
"authRegisterTitle": "Hesap oluştur",
|
||||
"authRegisterStepOneTitle": "Hesabınızı oluşturun",
|
||||
"authRegisterStepOneDescription": "Başlamak için bilgilerinizi girin",
|
||||
"authRegisterStepTwoTitle": "Profilinizi ayarlayın",
|
||||
"authRegisterStepTwoDescription": "Kendiniz hakkında daha fazla bilgi verin",
|
||||
"authRegisterStepThreeTitle": "Neredeyse bitti!",
|
||||
"authRegisterStepThreeDescription": "Bilgilerinizi gözden geçirin",
|
||||
"authForgotPasswordDescription": "Endişelenmeyin, size sıfırlama talimatlarını göndereceğiz.",
|
||||
"authForgotPasswordSendInstructions": "Talimatları gönder",
|
||||
"authForgotPasswordBackTo": "Geri dön",
|
||||
"authCheckEmailTitle": "E-postanızı kontrol edin",
|
||||
"authCheckEmailDescription": "{{email}} adresine şifre sıfırlama bağlantısı gönderdik",
|
||||
"authCheckEmailResendEmail": "E-postayı yeniden gönder",
|
||||
"authCheckEmailBackTo": "Geri dön",
|
||||
"goBackTo": "Geri dön",
|
||||
"authCheckEmailDidntReceiveEmail": "E-posta almadınız mı?",
|
||||
"authCheckEmailClickToResend": "Yeniden göndermek için tıklayın",
|
||||
"authSetNewPasswordTitle": "Yeni şifre belirle",
|
||||
"authSetNewPasswordDescription": "Yeni şifreniz daha önce kullanılan şifrelerden farklı olmalıdır.",
|
||||
"authSetNewPasswordNewPassword": "Yeni şifre",
|
||||
"authSetNewPasswordConfirmPassword": "Parolayı onayla",
|
||||
"confirmPassword": "Parolanızı onaylayın",
|
||||
"authSetNewPasswordResetPassword": "Parola sıfırla",
|
||||
"authSetNewPasswordBackTo": "Geri dön",
|
||||
"authPasswordMustBeAtLeast": "En az",
|
||||
"authPasswordCharactersLong": "8 karakter uzunluğunda olmalı",
|
||||
"authPasswordMustContainAtLeast": "En az içermeli",
|
||||
"authPasswordSpecialCharacter": "bir özel karakter",
|
||||
"authPasswordOneNumber": "bir rakam",
|
||||
"authPasswordUpperCharacter": "bir büyük harf",
|
||||
"authPasswordLowerCharacter": "bir küçük harf",
|
||||
"authPasswordConfirmAndPassword": "Onay şifresi ve şifre",
|
||||
"authPasswordMustMatch": "eşleşmelidir",
|
||||
"authRegisterCreateAccount": "Hesap oluşturmak için devam et",
|
||||
"authRegisterCreateSuperAdminAccount": "Super admin hesabınızı oluşturmak için devam edin",
|
||||
"authRegisterSignUpWithEmail": "E-posta ile kayıt ol",
|
||||
"authRegisterBySigningUp": "Kayıt olarak, aşağıdaki şartları kabul ediyorsunuz:"
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import ReactDOM from "react-dom/client";
|
||||
import App from "./App.jsx";
|
||||
import "./index.css";
|
||||
import "./Utils/i18n";
|
||||
import { BrowserRouter as Router } from "react-router-dom";
|
||||
import { Provider } from "react-redux";
|
||||
import { persistor, store } from "./store";
|
||||
@@ -17,7 +18,7 @@ ReactDOM.createRoot(document.getElementById("root")).render(
|
||||
>
|
||||
<Router>
|
||||
<NetworkServiceProvider>
|
||||
<App />
|
||||
<App />
|
||||
</NetworkServiceProvider>
|
||||
</Router>
|
||||
</PersistGate>
|
||||
|
||||
Reference in New Issue
Block a user