diff --git a/angular/package-lock.json b/angular/package-lock.json index 6427f83..13deec3 100644 --- a/angular/package-lock.json +++ b/angular/package-lock.json @@ -12,19 +12,19 @@ "@angular/compiler": "^19.2.10", "@angular/core": "^19.2.0", "@angular/forms": "^19.2.10", - "@angular/platform-browser": "^19.2.10", - "@angular/platform-browser-dynamic": "^19.2.0", - "@angular/router": "^19.2.0", + "@angular/platform-browser": "^19.2.13", + "@angular/platform-browser-dynamic": "^19.2.13", + "@angular/router": "^19.2.13", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, "devDependencies": { "@angular-devkit/build-angular": "^19.2.9", - "@angular/cli": "^19.2.11", + "@angular/cli": "^19.2.13", "@angular/compiler-cli": "^19.2.10", "@types/jasmine": "~5.1.0", - "jasmine-core": "~5.6.0", + "jasmine-core": "~5.7.1", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", @@ -278,13 +278,13 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "19.2.12", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.12.tgz", - "integrity": "sha512-vK5NI/asi1snWFkw02DpmC8tLq6u5ZbUwwXxgALKuVwGl3g1VLzrHrkoSCrcsOO9Nu6GQOPbxax2lR/DICmytg==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.13.tgz", + "integrity": "sha512-NhSPz3lI9njEo8eMUlZVGtlXl12UcNZv5lWTBZY/FGWUu6P5ciD/9iJINbc1jiaDH5E/DLEicUNuai0Q91X4Nw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.12", + "@angular-devkit/core": "19.2.13", "jsonc-parser": "3.3.1", "magic-string": "0.30.17", "ora": "5.4.1", @@ -296,6 +296,34 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular-devkit/schematics/node_modules/@angular-devkit/core": { + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.13.tgz", + "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -494,18 +522,18 @@ } }, "node_modules/@angular/cli": { - "version": "19.2.12", - "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.12.tgz", - "integrity": "sha512-cZkHpM16uh3VouHG1XdWSk0ZWisQRxMVADk5IJlM9jMcPqnFyJwD/UXCS+XTaW3POpNDwsmbh2UB9Xabdgo7rw==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-19.2.13.tgz", + "integrity": "sha512-dDRCS73/lrItWx9j4SmwHR56GiZsW8ObNi2q9l/1ny813CG9K43STYFG/wJvGS7ZF3y5hvjIiJOwBx2YIouOIw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/architect": "0.1902.12", - "@angular-devkit/core": "19.2.12", - "@angular-devkit/schematics": "19.2.12", + "@angular-devkit/architect": "0.1902.13", + "@angular-devkit/core": "19.2.13", + "@angular-devkit/schematics": "19.2.13", "@inquirer/prompts": "7.3.2", "@listr2/prompt-adapter-inquirer": "2.0.18", - "@schematics/angular": "19.2.12", + "@schematics/angular": "19.2.13", "@yarnpkg/lockfile": "1.1.0", "ini": "5.0.0", "jsonc-parser": "3.3.1", @@ -527,6 +555,60 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@angular/cli/node_modules/@angular-devkit/architect": { + "version": "0.1902.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.1902.13.tgz", + "integrity": "sha512-ZMj+PjK22Ph2U8usG6L7LqEfvWlbaOvmiWXSrEt9YiC9QJt6rsumCkOgUIsmHQtucm/lK+9CMtyYdwH2fYycjg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.13", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@angular/cli/node_modules/@angular-devkit/core": { + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.13.tgz", + "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@angular/cli/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@angular/common": { "version": "19.2.10", "resolved": "https://registry.npmjs.org/@angular/common/-/common-19.2.10.tgz", @@ -667,9 +749,9 @@ } }, "node_modules/@angular/platform-browser": { - "version": "19.2.10", - "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.10.tgz", - "integrity": "sha512-MYX4av0UvCmtUyCo6vW2RjOT2nbZsChhNjKr70DrWcCRMhYYv4cB4PMr6PnyGKJb7QZSSdzbqbbGtvvaB/quqw==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-19.2.13.tgz", + "integrity": "sha512-YeuRfGbo8qFepoAUoubk/1079wOown5Qgr9eAhgCXxoXb2rt87xbJF3YCSSim38SP3kK1rJQqP+Sr8n7ef+n5Q==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -678,9 +760,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/animations": "19.2.10", - "@angular/common": "19.2.10", - "@angular/core": "19.2.10" + "@angular/animations": "19.2.13", + "@angular/common": "19.2.13", + "@angular/core": "19.2.13" }, "peerDependenciesMeta": { "@angular/animations": { @@ -689,9 +771,9 @@ } }, "node_modules/@angular/platform-browser-dynamic": { - "version": "19.2.10", - "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.10.tgz", - "integrity": "sha512-ETyTBTL/Kt17BjTDGXoUJVfjx1ZI0ryg3gvEvvtiMfBcmT/ohiIstYfaoF1QSPVMtbiTRLbCtdE439e4B7YoaQ==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-19.2.13.tgz", + "integrity": "sha512-qbIPwnqkqQZ1sK56cbb2k/qtg+BKYicU6aS/YKfRrEfM9zFNyxfSCdKOwL7hogKGZKJulFfFKpi44wJcdW13rg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -700,16 +782,16 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "19.2.10", - "@angular/compiler": "19.2.10", - "@angular/core": "19.2.10", - "@angular/platform-browser": "19.2.10" + "@angular/common": "19.2.13", + "@angular/compiler": "19.2.13", + "@angular/core": "19.2.13", + "@angular/platform-browser": "19.2.13" } }, "node_modules/@angular/router": { - "version": "19.2.10", - "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.10.tgz", - "integrity": "sha512-bmt3ws3L4pYTlI8FW5L1ShyOeUQobXA7wy6TbcIToEonZk+i8uj3xlTD9Ymnjkb9wbNXrD1qoNSpP86c7C1rWQ==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular/router/-/router-19.2.13.tgz", + "integrity": "sha512-BZObWQtGkDv2WHyLVRRecGbLwalbI8kOXKaVgN5dqP4z/t5bpzYXZixPO9e0E1Ff0+m4tQalhTc84j8X7XZuTw==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" @@ -718,9 +800,9 @@ "node": "^18.19.1 || ^20.11.1 || >=22.0.0" }, "peerDependencies": { - "@angular/common": "19.2.10", - "@angular/core": "19.2.10", - "@angular/platform-browser": "19.2.10", + "@angular/common": "19.2.13", + "@angular/core": "19.2.13", + "@angular/platform-browser": "19.2.13", "rxjs": "^6.5.3 || ^7.4.0" } }, @@ -4860,14 +4942,14 @@ ] }, "node_modules/@schematics/angular": { - "version": "19.2.12", - "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.2.12.tgz", - "integrity": "sha512-6S6tclFctLrjMvhpi8eVvswIpXqlybRpZLCTWyVeWIC6PHYLEyFmFoOhuhcSmOdtnwudvzOt6xWnWEVb3qXZbQ==", + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-19.2.13.tgz", + "integrity": "sha512-SOpK4AwH0isXo7Y2SkgXLyGLMw4GxWPAun6sCLiprmop4KlqKGGALn4xIW0yjq0s5GS0Vx0FFjz8bBfPkgnawA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "19.2.12", - "@angular-devkit/schematics": "19.2.12", + "@angular-devkit/core": "19.2.13", + "@angular-devkit/schematics": "19.2.13", "jsonc-parser": "3.3.1" }, "engines": { @@ -4876,6 +4958,44 @@ "yarn": ">= 1.13.0" } }, + "node_modules/@schematics/angular/node_modules/@angular-devkit/core": { + "version": "19.2.13", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.13.tgz", + "integrity": "sha512-iq73hE5Uvms1w3uMUSk4i4NDXDMQ863VAifX8LOTadhG6U0xISjNJ11763egVCxQmaKmg7zbG4rda88wHJATzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.17.1", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@schematics/angular/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@sigstore/bundle": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@sigstore/bundle/-/bundle-3.1.0.tgz", @@ -9071,9 +9191,9 @@ } }, "node_modules/jasmine-core": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.6.0.tgz", - "integrity": "sha512-niVlkeYVRwKFpmfWg6suo6H9CrNnydfBLEqefM5UjibYS+UoTjZdmvPJSiuyrRLGnFj1eYRhFd/ch+5hSlsFVA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/jasmine-core/-/jasmine-core-5.7.1.tgz", + "integrity": "sha512-QnurrtpKsPoixxG2R3d1xP0St/2kcX5oTZyDyQJMY+Vzi/HUlu1kGm+2V8Tz+9lV991leB1l0xcsyz40s9xOOw==", "dev": true, "license": "MIT" }, diff --git a/angular/package.json b/angular/package.json index b532550..2ccfea9 100644 --- a/angular/package.json +++ b/angular/package.json @@ -14,19 +14,19 @@ "@angular/compiler": "^19.2.10", "@angular/core": "^19.2.0", "@angular/forms": "^19.2.10", - "@angular/platform-browser": "^19.2.10", - "@angular/platform-browser-dynamic": "^19.2.0", - "@angular/router": "^19.2.0", + "@angular/platform-browser": "^19.2.13", + "@angular/platform-browser-dynamic": "^19.2.13", + "@angular/router": "^19.2.13", "rxjs": "~7.8.0", "tslib": "^2.3.0", "zone.js": "~0.15.0" }, "devDependencies": { "@angular-devkit/build-angular": "^19.2.9", - "@angular/cli": "^19.2.11", + "@angular/cli": "^19.2.13", "@angular/compiler-cli": "^19.2.10", "@types/jasmine": "~5.1.0", - "jasmine-core": "~5.6.0", + "jasmine-core": "~5.7.1", "karma": "~6.4.0", "karma-chrome-launcher": "~3.2.0", "karma-coverage": "~2.2.0", diff --git a/client/package-lock.json b/client/package-lock.json index c572720..d3c2348 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -12,7 +12,7 @@ "@popperjs/core": "^2.11.8", "@types/node": "^22.15.21", "@types/react-dom": "^19.1.5", - "@vitejs/plugin-react": "^4.4.1", + "@vitejs/plugin-react": "^4.5.0", "bootstrap": "^5.3.6", "dompurify": "^3.2.6", "i18next": "^25.2.0", @@ -20,11 +20,11 @@ "react-bootstrap": "^2.10.10", "react-bootstrap-icons": "^1.11.6", "react-charts": "^3.0.0-beta.57", - "react-datepicker": "^8.3.0", + "react-datepicker": "^8.4.0", "react-dom": "^19.1.0", "react-i18next": "^15.5.2", "react-markdown": "^10.1.0", - "react-router": "^7.6.0", + "react-router": "^7.6.1", "react-router-bootstrap": "^0.26.3", "typescript": "^5.8.3", "vite": "^6.3.5" @@ -33,7 +33,7 @@ "@eslint/compat": "^1.2.9", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.27.0", - "@stylistic/eslint-plugin": "^4.2.0", + "@stylistic/eslint-plugin": "^4.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", @@ -48,7 +48,7 @@ "eslint-plugin-n": "^17.18.0", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-react": "^7.37.5", - "globals": "^16.1.0", + "globals": "^16.2.0", "jsdom": "^26.1.0", "prettier": "^3.5.3", "sass": "^1.89.0", @@ -1123,6 +1123,12 @@ "react": ">=16.14.0" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.9", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.9.tgz", + "integrity": "sha512-e9MeMtVWo186sgvFFJOPGy7/d2j2mZhLJIdVW0C/xDluuOvymEATqz6zKsP0ZmXGzQtqlyjz5sC1sYQUoJG98w==", + "license": "MIT" + }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.40.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.40.0.tgz", @@ -1164,13 +1170,13 @@ "license": "MIT" }, "node_modules/@stylistic/eslint-plugin": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.2.0.tgz", - "integrity": "sha512-8hXezgz7jexGHdo5WN6JBEIPHCSFyyU4vgbxevu4YLVS5vl+sxqAAGyXSzfNDyR6xMNSH5H1x67nsXcYMOHtZA==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-4.4.0.tgz", + "integrity": "sha512-bIh/d9X+OQLCAMdhHtps+frvyjvAM4B1YlSJzcEEhl7wXLIqPar3ngn9DrHhkBOrTA/z9J0bUMtctAspe0dxdQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/utils": "^8.23.0", + "@typescript-eslint/utils": "^8.32.1", "eslint-visitor-keys": "^4.2.0", "espree": "^10.3.0", "estraverse": "^5.3.0", @@ -1867,14 +1873,15 @@ "license": "ISC" }, "node_modules/@vitejs/plugin-react": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.4.1.tgz", - "integrity": "sha512-IpEm5ZmeXAP/osiBXVVP5KjFMzbWOonMs0NaQQl+xYnUAcq4oHUBsF2+p4MgKWG4YMmFYJU8A6sxRPuowllm6w==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.5.0.tgz", + "integrity": "sha512-JuLWaEqypaJmOJPLWwO335Ig6jSgC1FTONCWAxnqcQthLTK/Yc9aH6hr9z/87xciejbQcnP3GnA1FWUSWeXaeg==", "license": "MIT", "dependencies": { "@babel/core": "^7.26.10", "@babel/plugin-transform-react-jsx-self": "^7.25.9", "@babel/plugin-transform-react-jsx-source": "^7.25.9", + "@rolldown/pluginutils": "1.0.0-beta.9", "@types/babel__core": "^7.20.5", "react-refresh": "^0.17.0" }, @@ -4320,9 +4327,9 @@ } }, "node_modules/globals": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-16.1.0.tgz", - "integrity": "sha512-aibexHNbb/jiUSObBgpHLj+sIuUmJnYcgXBlrfsiDZ9rt4aF2TFRbyLgZ2iFQuVZ1K5Mx3FVkbKRSgKrbK3K2g==", + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.2.0.tgz", + "integrity": "sha512-O+7l9tPdHCU320IigZZPj5zmRCFG9xHmx9cU8FqU2Rp+JN714seHV+2S9+JslCpY4gJwU2vOGox0wzgae/MCEg==", "dev": true, "license": "MIT", "engines": { @@ -7064,9 +7071,9 @@ } }, "node_modules/react-datepicker": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-8.3.0.tgz", - "integrity": "sha512-DhfrIJnTPJTUVRtXU7c7zooug40rD6q+Fc8UTCt19dYEotLpDQgTN98MfocY6Rc4S99oOFFEoxyanOM/TKauuw==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/react-datepicker/-/react-datepicker-8.4.0.tgz", + "integrity": "sha512-6nPDnj8vektWCIOy9ArS3avus9Ndsyz5XgFCJ7nBxXASSpBdSL6lG9jzNNmViPOAOPh6T5oJyGaXuMirBLECag==", "license": "MIT", "dependencies": { "@floating-ui/react": "^0.27.3", @@ -7166,9 +7173,9 @@ } }, "node_modules/react-router": { - "version": "7.6.0", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz", - "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==", + "version": "7.6.1", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.1.tgz", + "integrity": "sha512-hPJXXxHJZEsPFNVbtATH7+MMX43UDeOauz+EAU4cgqTn7ojdI9qQORqS8Z0qmDlL1TclO/6jLRYUEtbWidtdHQ==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", diff --git a/client/package.json b/client/package.json index a6c0fad..44cce41 100644 --- a/client/package.json +++ b/client/package.json @@ -15,7 +15,7 @@ "@popperjs/core": "^2.11.8", "@types/node": "^22.15.21", "@types/react-dom": "^19.1.5", - "@vitejs/plugin-react": "^4.4.1", + "@vitejs/plugin-react": "^4.5.0", "bootstrap": "^5.3.6", "dompurify": "^3.2.6", "i18next": "^25.2.0", @@ -23,11 +23,11 @@ "react-bootstrap": "^2.10.10", "react-bootstrap-icons": "^1.11.6", "react-charts": "^3.0.0-beta.57", - "react-datepicker": "^8.3.0", + "react-datepicker": "^8.4.0", "react-dom": "^19.1.0", "react-i18next": "^15.5.2", "react-markdown": "^10.1.0", - "react-router": "^7.6.0", + "react-router": "^7.6.1", "react-router-bootstrap": "^0.26.3", "typescript": "^5.8.3", "vite": "^6.3.5" @@ -63,7 +63,7 @@ "@eslint/compat": "^1.2.9", "@eslint/eslintrc": "^3.3.1", "@eslint/js": "^9.27.0", - "@stylistic/eslint-plugin": "^4.2.0", + "@stylistic/eslint-plugin": "^4.4.0", "@testing-library/jest-dom": "^6.6.3", "@testing-library/react": "^16.3.0", "@testing-library/user-event": "^14.6.1", @@ -78,7 +78,7 @@ "eslint-plugin-n": "^17.18.0", "eslint-plugin-promise": "^7.2.1", "eslint-plugin-react": "^7.37.5", - "globals": "^16.1.0", + "globals": "^16.2.0", "jsdom": "^26.1.0", "prettier": "^3.5.3", "sass": "^1.89.0", diff --git a/client/src/__test__/components/ContentHeader.test.tsx b/client/src/__test__/components/ContentHeader.test.tsx index 111d800..e038a13 100644 --- a/client/src/__test__/components/ContentHeader.test.tsx +++ b/client/src/__test__/components/ContentHeader.test.tsx @@ -24,12 +24,14 @@ describe('ContentHeader test cases', () => { expect(getByText(headerProps.h2GreenText)).toBeDefined(); }); - it('should render all the text elements', () => { + it('should render all the text elements for home', () => { const { getByText } = render( ); diff --git a/client/src/__test__/utils/PortugueseUtils.test.ts b/client/src/__test__/utils/PortugueseUtils.test.ts index 1f8031d..96d898f 100644 --- a/client/src/__test__/utils/PortugueseUtils.test.ts +++ b/client/src/__test__/utils/PortugueseUtils.test.ts @@ -35,33 +35,25 @@ describe('Portuguese Utils unit tests', () => { it('should translate all server responses to pt_br', () => { const keys: string[] = Object.keys(serverResponses); - expect(translateServerResponse(keys[0], 'pt_br')) - .toBe('Senha fraca: Senha deve possuir pelo menos 8 letras, 1 maiúscula, 1 caracter especial'); - expect(translateServerResponse(keys[1], 'pt_br')) - .toBe('Senha fraca: Senha deve possuir pelo menos 1 maiúscula, 1 caracter especial'); - expect(translateServerResponse(keys[2], 'pt_br')) - .toBe('Senha fraca: Senha deve possuir pelo menos 1 caracter especial'); - expect(translateServerResponse(keys[3], 'pt_br')) - .toBe('E-mail já cadastrado!'); - expect(translateServerResponse(keys[4], 'pt_br')) - .toBe('Proibido! Acesso negado'); - expect(translateServerResponse(keys[5], 'pt_br')) - .toBe('Erro Interno do Servidor!'); - expect(translateServerResponse(keys[6], 'pt_br')) - .toBe('Limite máximo de tentativas atingido. Por favor aguarde 30 minutos'); - expect(translateServerResponse(keys[7], 'pt_br')) - .toBe('Por ravor, preencha todos os campos'); - expect(translateServerResponse(keys[8], 'pt_br')) - .toBe('Por favor, informe seu e-mail e senha!'); - expect(translateServerResponse(keys[9], 'pt_br')) - .toBe('Por favor, digite pelo menos 3 letras'); - expect(translateServerResponse(keys[10], 'pt_br')) - .toBe('O tamanho máximo do texto é 2000'); - expect(translateServerResponse(keys[11], 'pt_br')) - .toBe('Erro desconhecido'); - expect(translateServerResponse(keys[12], 'pt_br')) - .toBe('Informação errada ou incompleta!'); - expect(translateServerResponse(keys[13], 'pt_br')) - .toBe('E-mail ou senha inválidos!'); + expect(translateServerResponse(keys[0], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 8 letras, 1 maiúscula, 1 caracter especial'); + expect(translateServerResponse(keys[1], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 1 maiúscula, 1 caracter especial'); + expect(translateServerResponse(keys[2], 'pt_br')).toBe('Senha fraca: Senha deve possuir pelo menos 1 caracter especial'); + expect(translateServerResponse(keys[3], 'pt_br')).toBe('E-mail já cadastrado!'); + expect(translateServerResponse(keys[4], 'pt_br')).toBe('Proibido! Acesso negado'); + expect(translateServerResponse(keys[5], 'pt_br')).toBe('Se o endereço de e-mail informado estiver associado a uma conta, você receberá um link para resetar a senha em breve.'); + expect(translateServerResponse(keys[6], 'pt_br')).toBe('Erro Interno do Servidor!'); + expect(translateServerResponse(keys[7], 'pt_br')).toBe('Limite máximo de tentativas atingido. Por favor aguarde 30 minutos'); + expect(translateServerResponse(keys[8], 'pt_br')).toBe('Erro de rede ao tentar obter recursos.'); + expect(translateServerResponse(keys[9], 'pt_br')).toBe('Por favor, confirme seu e-mail antes de continuar'); + expect(translateServerResponse(keys[10], 'pt_br')).toBe('Por ravor, preencha todos os campos'); + expect(translateServerResponse(keys[11], 'pt_br')).toBe('Por favor, informe seu e-mail'); + expect(translateServerResponse(keys[12], 'pt_br')).toBe('Por favor, informe seu e-mail e senha!'); + expect(translateServerResponse(keys[13], 'pt_br')).toBe('Por favor, informe a nova senha'); + expect(translateServerResponse(keys[14], 'pt_br')).toBe('Por favor, digite pelo menos 3 letras'); + expect(translateServerResponse(keys[15], 'pt_br')).toBe('O tamanho máximo do texto é 2000'); + expect(translateServerResponse(keys[16], 'pt_br')).toBe('Erro desconhecido'); + expect(translateServerResponse(keys[17], 'pt_br')).toBe('Identificação incorreta ou faltando'); + expect(translateServerResponse(keys[18], 'pt_br')).toBe('Informação errada ou incompleta!'); + expect(translateServerResponse(keys[19], 'pt_br')).toBe('E-mail ou senha inválidos!'); }); }); diff --git a/client/src/__test__/utils/RussianUtils.test.ts b/client/src/__test__/utils/RussianUtils.test.ts index 2b032e0..75115ae 100644 --- a/client/src/__test__/utils/RussianUtils.test.ts +++ b/client/src/__test__/utils/RussianUtils.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import { translateTimeMessage, translateServerResponse } from '../../utils/TranslatorUtils'; import { serverResponses } from '../../constants/serverResponses'; -describe('Portuguese Utils unit tests', () => { +describe('Russian Utils unit tests', () => { it('should translate all time ago messages to ru', () => { expect(translateTimeMessage('2 years ago', 'ru')).toBe('2 года назад'); expect(translateTimeMessage('1 year ago', 'ru')).toBe('1 год назад'); @@ -56,33 +56,25 @@ describe('Portuguese Utils unit tests', () => { it('should translate all server responses to ru', () => { const keys: string[] = Object.keys(serverResponses); - expect(translateServerResponse(keys[0], 'ru')) - .toBe('Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 заглавную букву, 1 специальный символ.'); - expect(translateServerResponse(keys[1], 'ru')) - .toBe('Неправильный пароль: Пароль должен содержать как минимум 1 заглавную букву и 1 специальный символ.'); - expect(translateServerResponse(keys[2], 'ru')) - .toBe('Неправильный пароль: Пароль должен содержать хотя бы 1 специальный символ.'); - expect(translateServerResponse(keys[3], 'ru')) - .toBe('Электронная почта уже используется!'); - expect(translateServerResponse(keys[4], 'ru')) - .toBe('Доступ запрещен!'); - expect(translateServerResponse(keys[5], 'ru')) - .toBe('Внутренняя ошибка сервера!'); - expect(translateServerResponse(keys[6], 'ru')) - .toBe('Достигнут максимальный лимит попыток входа. Пожалуйста, подождите 30 минут'); - expect(translateServerResponse(keys[7], 'ru')) - .toBe('Пожалуйста, заполните все поля'); - expect(translateServerResponse(keys[8], 'ru')) - .toBe('Пожалуйста, введите свое имя пользователя и пароль!'); - expect(translateServerResponse(keys[9], 'ru')) - .toBe('Пожалуйста, введите не менее 3 символов'); - expect(translateServerResponse(keys[10], 'ru')) - .toBe('Максимальная длина текста — 2000 символов.'); - expect(translateServerResponse(keys[11], 'ru')) - .toBe('Неизвестная ошибка'); - expect(translateServerResponse(keys[12], 'ru')) - .toBe('Неверная или отсутствующая информация!'); - expect(translateServerResponse(keys[13], 'ru')) - .toBe('Неправильный пользователь или пароль'); + expect(translateServerResponse(keys[0], 'ru')).toBe('Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 заглавную букву, 1 специальный символ.'); + expect(translateServerResponse(keys[1], 'ru')).toBe('Неправильный пароль: Пароль должен содержать как минимум 1 заглавную букву и 1 специальный символ.'); + expect(translateServerResponse(keys[2], 'ru')).toBe('Неправильный пароль: Пароль должен содержать хотя бы 1 специальный символ.'); + expect(translateServerResponse(keys[3], 'ru')).toBe('Электронная почта уже используется!'); + expect(translateServerResponse(keys[4], 'ru')).toBe('Доступ запрещен!'); + expect(translateServerResponse(keys[5], 'ru')).toBe('Если введенный вами адрес электронной почты связан с учетной записью, вы вскоре получите ссылку для сброса пароля.'); + expect(translateServerResponse(keys[6], 'ru')).toBe('Внутренняя ошибка сервера!'); + expect(translateServerResponse(keys[7], 'ru')).toBe('Достигнут максимальный лимит попыток входа. Пожалуйста, подождите 30 минут'); + expect(translateServerResponse(keys[8], 'ru')).toBe('Ошибка сети при попытке получить ресурс.'); + expect(translateServerResponse(keys[9], 'ru')).toBe('Пожалуйста, подтвердите свой адрес электронной почты, прежде чем продолжить'); + expect(translateServerResponse(keys[10], 'ru')).toBe('Пожалуйста, заполните все поля'); + expect(translateServerResponse(keys[11], 'ru')).toBe('Пожалуйста, введите свой адрес электронной почты'); + expect(translateServerResponse(keys[12], 'ru')).toBe('Пожалуйста, введите свое имя пользователя и пароль!'); + expect(translateServerResponse(keys[13], 'ru')).toBe('Пожалуйста, введите новый пароль'); + expect(translateServerResponse(keys[14], 'ru')).toBe('Пожалуйста, введите не менее 3 символов'); + expect(translateServerResponse(keys[15], 'ru')).toBe('Максимальная длина текста — 2000 символов.'); + expect(translateServerResponse(keys[16], 'ru')).toBe('Неизвестная ошибка'); + expect(translateServerResponse(keys[17], 'ru')).toBe('Неправильная или отсутствующая идентификация'); + expect(translateServerResponse(keys[18], 'ru')).toBe('Неверная или отсутствующая информация!'); + expect(translateServerResponse(keys[19], 'ru')).toBe('Неправильный пользователь или пароль'); }); }); diff --git a/client/src/__test__/utils/SpanishUtils.test.ts b/client/src/__test__/utils/SpanishUtils.test.ts index 9d0ed83..875a70d 100644 --- a/client/src/__test__/utils/SpanishUtils.test.ts +++ b/client/src/__test__/utils/SpanishUtils.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from 'vitest'; import { translateTimeMessage, translateServerResponse } from '../../utils/TranslatorUtils'; import { serverResponses } from '../../constants/serverResponses'; -describe('Portuguese Utils unit tests', () => { +describe('Spanish Utils unit tests', () => { it('should translate all time ago messages to es', () => { expect(translateTimeMessage('2 years ago', 'es')).toBe('Hace 2 años'); expect(translateTimeMessage('1 year ago', 'es')).toBe('Hace 1 año'); @@ -35,33 +35,24 @@ describe('Portuguese Utils unit tests', () => { it('should translate all server responses to es', () => { const keys: string[] = Object.keys(serverResponses); - expect(translateServerResponse(keys[0], 'es')) - .toBe('Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 mayúscula y 1 carácter especial'); - expect(translateServerResponse(keys[1], 'es')) - .toBe('Contraseña inválida: La contraseña debe tener al menos 1 mayúscula y 1 carácter especial'); - expect(translateServerResponse(keys[2], 'es')) - .toBe('Contraseña inválida: La contraseña debe tener al menos 1 carácter especial'); - expect(translateServerResponse(keys[3], 'es')) - .toBe('¡El correo ya está registrado!'); - expect(translateServerResponse(keys[4], 'es')) - .toBe('¡Prohibido! Acceso denegado'); - expect(translateServerResponse(keys[5], 'es')) - .toBe('¡Error interno del servidor!'); - expect(translateServerResponse(keys[6], 'es')) - .toBe('Has alcanzado el límite máximo de intentos de inicio de sesión. Por favor, espera 30 minutos'); - expect(translateServerResponse(keys[7], 'es')) - .toBe('Por favor, completa todos los campos'); - expect(translateServerResponse(keys[8], 'es')) - .toBe('¡Por favor, ingresa tu nombre de usuario y contraseña!'); - expect(translateServerResponse(keys[9], 'es')) - .toBe('¡Por favor, escriba al menos 3 caracteres!'); - expect(translateServerResponse(keys[10], 'es')) - .toBe('La longitud máxima del texto es 2000'); - expect(translateServerResponse(keys[11], 'es')) - .toBe('Error desconocido'); - expect(translateServerResponse(keys[12], 'es')) - .toBe('¡Información incorrecta o incompleta!'); - expect(translateServerResponse(keys[13], 'es')) - .toBe('¡Usuario o contraseña incorrectos!'); + expect(translateServerResponse(keys[0], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 mayúscula y 1 carácter especial'); + expect(translateServerResponse(keys[1], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 1 mayúscula y 1 carácter especial'); + expect(translateServerResponse(keys[2], 'es')).toBe('Contraseña inválida: La contraseña debe tener al menos 1 carácter especial'); + expect(translateServerResponse(keys[3], 'es')).toBe('¡El correo ya está registrado!'); + expect(translateServerResponse(keys[4], 'es')).toBe('¡Prohibido! Acceso denegado'); + expect(translateServerResponse(keys[5], 'es')).toBe('Si la dirección de correo electrónico ingresada está asociada a una cuenta, recibirá un enlace para restablecer su contraseña en breve.'); + expect(translateServerResponse(keys[6], 'es')).toBe('¡Error interno del servidor!'); + expect(translateServerResponse(keys[7], 'es')).toBe('Has alcanzado el límite máximo de intentos de inicio de sesión. Por favor, espera 30 minutos'); + expect(translateServerResponse(keys[8], 'es')).toBe('Error de red al intentar obtener el recurso.'); + expect(translateServerResponse(keys[9], 'es')).toBe('Por favor, confirme su correo electrónico antes de continuar'); + expect(translateServerResponse(keys[10], 'es')).toBe('Por favor, completa todos los campos'); + expect(translateServerResponse(keys[11], 'es')).toBe('Por favor, ingresa tu correo electronico'); + expect(translateServerResponse(keys[12], 'es')).toBe('¡Por favor, ingresa tu correo electronico y contraseña!'); + expect(translateServerResponse(keys[13], 'es')).toBe('Por favor, rellene la nueva contraseña'); + expect(translateServerResponse(keys[14], 'es')).toBe('¡Por favor, escriba al menos 3 caracteres!'); + expect(translateServerResponse(keys[15], 'es')).toBe('La longitud máxima del texto es 2000'); + expect(translateServerResponse(keys[16], 'es')).toBe('Error desconocido'); + expect(translateServerResponse(keys[17], 'es')).toBe('Identificación incorrecta o faltante'); + expect(translateServerResponse(keys[18], 'es')).toBe('¡Información incorrecta o incompleta!'); }); }); diff --git a/client/src/__test__/views/EmailConfirmation.test.tsx b/client/src/__test__/views/EmailConfirmation.test.tsx index b2b2517..1c0a6ae 100644 --- a/client/src/__test__/views/EmailConfirmation.test.tsx +++ b/client/src/__test__/views/EmailConfirmation.test.tsx @@ -37,7 +37,7 @@ describe('EmailConfirmation Component', () => { await waitFor(() => { expect(screen.getByText('❌ Oops!')).toBeDefined(); - expect(screen.getByText('Wrong or missing identification.')).toBeDefined(); + expect(screen.getByTestId('email-confirm-error-msg')).toBeDefined(); }); }); @@ -46,7 +46,7 @@ describe('EmailConfirmation Component', () => { renderFn(); await waitFor(() => { - expect(screen.getByText('✅ Email Confirmed')).toBeDefined(); + expect(screen.getByTestId('email-confirm-success-msg')).toBeDefined(); }); }); @@ -83,9 +83,9 @@ describe('EmailConfirmation Component', () => { renderFn(); await waitFor(() => { - const successMessage = screen.getByText('✅ Email Confirmed'); + const successMessage = screen.getByTestId('email-confirm-success-msg'); expect(successMessage).toBeDefined(); - expect(successMessage.classList.contains('mb-3')).toBe(true); + expect(successMessage.classList.contains('mb-4')).toBe(true); }); }); @@ -105,7 +105,7 @@ describe('EmailConfirmation Component', () => { renderFn(); await waitFor(() => { - const loginButton = screen.getByText('Go to Login'); + const loginButton = screen.getByText('Login'); expect(loginButton).toBeDefined(); expect(loginButton.classList.contains('btn-success')).toBe(true); }); diff --git a/client/src/__test__/views/Home.test.tsx b/client/src/__test__/views/Home.test.tsx index 851964c..d3ecc30 100644 --- a/client/src/__test__/views/Home.test.tsx +++ b/client/src/__test__/views/Home.test.tsx @@ -178,7 +178,7 @@ describe('Home Component', () => { }); expect(screen.getByTestId('content-header')).toBeDefined(); - expect(screen.getByPlaceholderText('Filter tasks & notes')).toBeDefined(); + expect(screen.getByPlaceholderText('home_input_filter')).toBeDefined(); // Check that API calls were made expect(api.getJSON).toHaveBeenCalledTimes(3); @@ -206,7 +206,7 @@ describe('Home Component', () => { expect(screen.getAllByTestId('note-title').length).toBe(2); }); - const searchInput = screen.getByPlaceholderText('Filter tasks & notes'); + const searchInput = screen.getByPlaceholderText('home_input_filter'); // Test filtering by entering text await act(async () => { @@ -238,7 +238,7 @@ describe('Home Component', () => { }); // Select "Only Tasks" radio button - const onlyTasksRadio = screen.getByLabelText('Tasks'); + const onlyTasksRadio = screen.getByLabelText('home_radio_tasks'); await act(async () => { fireEvent.click(onlyTasksRadio); @@ -251,7 +251,7 @@ describe('Home Component', () => { }); // Select "Only Notes" radio button - const onlyNotesRadio = screen.getByLabelText('Notes'); + const onlyNotesRadio = screen.getByLabelText('home_radio_notes'); await act(async () => { fireEvent.click(onlyNotesRadio); diff --git a/client/src/components/ContentHeader/index.tsx b/client/src/components/ContentHeader/index.tsx index a89d52b..a17d8ea 100644 --- a/client/src/components/ContentHeader/index.tsx +++ b/client/src/components/ContentHeader/index.tsx @@ -10,6 +10,8 @@ type Props = { h2BlackText: string; h2GreenText: string; isHomeComponent?: boolean; + newTaskI18n?: string; + newNoteI18n?: string; }; /** @@ -54,7 +56,7 @@ const ContentHeader: React.FC = (props: Props): React.ReactNode => { type="button" className="home-new-item w-45 mb-2" > - New task + {props.newTaskI18n} setNewPage('/notes/new')}> @@ -62,7 +64,7 @@ const ContentHeader: React.FC = (props: Props): React.ReactNode => { type="button" className="home-new-item w-45 ms-2" > - New note + {props.newNoteI18n} diff --git a/client/src/components/FormInput/index.tsx b/client/src/components/FormInput/index.tsx index c07f1dc..79fe00c 100644 --- a/client/src/components/FormInput/index.tsx +++ b/client/src/components/FormInput/index.tsx @@ -21,6 +21,9 @@ interface Props { onChange?: (e: React.ChangeEvent) => void; onChangeDate?: (date: Date | null) => void; data_testid?: string; + pwdHideText?: string; + pwdShowText?: string; + pwdHelperTxt?: string; } /** @@ -66,7 +69,7 @@ function FormInput(props: React.PropsWithChildren): React.ReactNode { {props.type && props.type === 'password' && ( - {showingPwd ? ' (Hide)' : ' (Show)'} + {showingPwd ? ` (${props.pwdHideText})` : ` (${props.pwdShowText})`} )} @@ -109,15 +112,22 @@ function FormInput(props: React.PropsWithChildren): React.ReactNode { /> ) : ( - + <> + + {props.type && props.type === 'password' && ( + + {props.pwdHelperTxt} + + )} + )} diff --git a/client/src/components/LoginForm/index.tsx b/client/src/components/LoginForm/index.tsx index f086f58..025e6af 100644 --- a/client/src/components/LoginForm/index.tsx +++ b/client/src/components/LoginForm/index.tsx @@ -63,8 +63,11 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode { const form = event.currentTarget; if (form.checkValidity() === false) { setFormInvalid(true); - if (prefix !== 'reset') { - setErrorMessage(translateServerResponse('Please fill in your username and password!', i18n.language)); + if (prefix === 'complete_reset') { + setErrorMessage(translateServerResponse('Please fill in the new password', i18n.language)); + } + else if (prefix !== 'reset') { + setErrorMessage(translateServerResponse('Please fill in your username and password!' + prefix, i18n.language)); } else { setErrorMessage(translateServerResponse('Please fill in your email', i18n.language)); @@ -88,13 +91,13 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode { // Do not clear the email, because user might request to resend setPassword(''); setPasswordAgain(''); - setSuccessMessage('Please confirm your email before proceeding.'); + setSuccessMessage(translateServerResponse('Please confirm your email before proceeding', i18n.language)); setSecondsLeft(30); setIsResendEnabled(false); } else if (prefix === 'reset') { await api.postJSON(ApiConfig.resetPwdUrl, { email }); - setSuccessMessage('If the email address you entered is associated with an account, you will receive a password reset link shortly.'); + setSuccessMessage(translateServerResponse('If the email address you entered is associated with an account, you will receive a password reset link shortly.', i18n.language)); } else if (prefix === 'complete_reset') { const token = searchParams.get('token'); @@ -103,11 +106,14 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode { } } catch (e) { + console.log(e); setFormInvalid(true); if (typeof e === 'string') { + console.log('string'); setErrorMessage(translateServerResponse(e, i18n.language)); } else if (e instanceof Error) { + console.log(e.message); setErrorMessage(translateServerResponse(e.message, i18n.language)); } } @@ -174,7 +180,7 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode { onClick={handleResend} disabled={!isResendEnabled} > - {isResendEnabled ? 'Resend Confirmation Email' : `Resend in ${secondsLeft}s`} + {isResendEnabled ? `${t('register_resent_email')}` : `${t('register_resent_email_secs')} ${secondsLeft}`} @@ -189,7 +195,7 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode {
{prefix !== 'complete_reset' && ( ) => { setPassword(e.target.value); }} @@ -219,12 +229,16 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode { {(prefix === 'register' || prefix === 'complete_reset') && ( ) => { setPasswordAgain(e.target.value); }} @@ -256,7 +270,7 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode {
{prefix === 'login' && ( - Forgot your password? + {t('login_forgot_password')} )}
diff --git a/client/src/constants/english.ts b/client/src/constants/english.ts index 091fb0f..20a866d 100644 --- a/client/src/constants/english.ts +++ b/client/src/constants/english.ts @@ -7,32 +7,41 @@ const enTranslations = { landing_btn_portuguese: 'Português (BR)', landing_btn_spanish: 'Español', landing_btn_russian: 'Русский', + landing_languages: 'You can choose one of these languages', + + password_show_txt: 'Show', + password_hide_txt: 'Hide', + password_helper: 'The password must be at least 8 characters long and include a number, a capital letter, and a special character', login_title: 'Login', login_email_label: 'Email', - login_email_placeholder: 'Type your email', + login_email_placeholder: 'you@example.com', login_password_label: 'Password', - login_password_placeholder: 'Type your password', + login_password_placeholder: 'Your password', login_btn_submit: 'Login', login_account: 'Don\'t have an account yet?', login_go_other: 'Create account', login_back_home: 'Back to home', + login_forgot_password: 'Forgot your password?', register_title: 'Create account', register_email_label: 'Email', - register_email_placeholder: 'Type your email', + register_email_placeholder: 'you@example.com', register_password_label: 'Password', - register_password_placeholder: 'Type your password', + register_password_placeholder: 'Your password', + register_password_repeat_label: 'Repeat password', register_btn_submit: 'Create account', register_account: 'Already have an account?', register_go_other: 'Login', register_back_home: 'Back to home', + register_resent_email: 'Resend confirmation email', + register_resent_email_secs: 'Resend in ', reset_title: 'Reset your password', reset_email_label: 'Email', - reset_email_placeholder: 'Type your email', + reset_email_placeholder: 'you@example.com', reset_password_label: 'Password', - reset_password_placeholder: 'Type your password', + reset_password_placeholder: 'Your password', reset_btn_submit: 'Send confirmation email', reset_account: 'Have you remembered your password?', reset_go_other: 'Login', @@ -46,12 +55,24 @@ const enTranslations = { complete_reset_go_other: 'Login', complete_reset_back_home: 'Back to home', + email_confirmation_loading: 'Confirming your email address...', + email_confirmation_success: '✅ Email confirmed', + email_confirmation_success_text: 'Thank you! Your email address has been successfully confirmed. You can now return to the TaskNote app and log in.', + email_confirmation_btn: 'Login', + home_nav_home: 'Home', home_nav_tasks: 'Tasks', home_nav_notes: 'Notes', home_nav_about: 'About', home_welcome_title: 'Hello, ', - home_card_task_title: 'Tasks Summary', + home_welcome_subtitle: 'Welcome to TaskNote! Get ready to complete your pending tasks', + home_welcome_start: 'Start Your Day, Be', + home_welcome_productive: 'Productive', + home_input_filter: 'Filter tasks & notes', + home_radio_everything: 'Everything', + home_radio_tasks: 'Tasks', + home_radio_notes: 'Notes', + home_card_task_title: 'Tasks Summary', // remove home_card_task_pending: 'Pending tasks', home_card_task_empty: 'No pending tasks', home_card_task_done: 'done tasks!', diff --git a/client/src/constants/languageConstants.ts b/client/src/constants/languageConstants.ts index 1a577b1..76b6fcb 100644 --- a/client/src/constants/languageConstants.ts +++ b/client/src/constants/languageConstants.ts @@ -67,45 +67,63 @@ export const serverResponsesTranslations: Record = { BAD_PASSWORD_3_pt_br: 'Senha fraca: Senha deve possuir pelo menos 8 letras, 1 maiúscula, 1 caracter especial', BAD_PASSWORD_2_pt_br: 'Senha fraca: Senha deve possuir pelo menos 1 maiúscula, 1 caracter especial', BAD_PASSWORD_1_pt_br: 'Senha fraca: Senha deve possuir pelo menos 1 caracter especial', + CONFIRM_EMAIL_TO_GO_ON_pt_br: 'Por favor, confirme seu e-mail antes de continuar', EMAIL_EXISTS_pt_br: 'E-mail já cadastrado!', FORBIDDEN_pt_br: 'Proibido! Acesso negado', INTERNAL_ERROR_pt_br: 'Erro Interno do Servidor!', INVALID_CREDENTIALS_pt_br: 'E-mail ou senha inválidos!', MAX_LOGIN_ATTEMPT_pt_br: 'Limite máximo de tentativas atingido. Por favor aguarde 30 minutos', + NETWORK_ERROR_pt_br: 'Erro de rede ao tentar obter recursos.', FILL_ALL_FIELDS_pt_br: 'Por ravor, preencha todos os campos', + FILL_NEW_PASSWORD_pt_br: 'Por favor, informe a nova senha', + FILL_USER_pt_br: 'Por favor, informe seu e-mail', FILL_USER_AND_PASS_pt_br: 'Por favor, informe seu e-mail e senha!', FILL_AT_LEAST_3_pt_br: 'Por favor, digite pelo menos 3 letras', MAX_TEXT_LENGTH_2000_pt_br: 'O tamanho máximo do texto é 2000', + RECOVER_PASSWORD_pt_br: 'Se o endereço de e-mail informado estiver associado a uma conta, você receberá um link para resetar a senha em breve.', UNKNOWN_pt_br: 'Erro desconhecido', + WRONG_IDENTIFICATION_pt_br: 'Identificação incorreta ou faltando', WRONG_OR_MISSING_INFO_pt_br: 'Informação errada ou incompleta!', BAD_PASSWORD_3_es: 'Contraseña inválida: La contraseña debe tener al menos 8 caracteres, 1 mayúscula y 1 carácter especial', BAD_PASSWORD_2_es: 'Contraseña inválida: La contraseña debe tener al menos 1 mayúscula y 1 carácter especial', BAD_PASSWORD_1_es: 'Contraseña inválida: La contraseña debe tener al menos 1 carácter especial', + CONFIRM_EMAIL_TO_GO_ON_es: 'Por favor, confirme su correo electrónico antes de continuar', EMAIL_EXISTS_es: '¡El correo ya está registrado!', FORBIDDEN_es: '¡Prohibido! Acceso denegado', INTERNAL_ERROR_es: '¡Error interno del servidor!', INVALID_CREDENTIALS_es: '¡Usuario o contraseña incorrectos!', MAX_LOGIN_ATTEMPT_es: 'Has alcanzado el límite máximo de intentos de inicio de sesión. Por favor, espera 30 minutos', + NETWORK_ERROR_es: 'Error de red al intentar obtener el recurso.', FILL_ALL_FIELDS_es: 'Por favor, completa todos los campos', - FILL_USER_AND_PASS_es: '¡Por favor, ingresa tu nombre de usuario y contraseña!', + FILL_NEW_PASSWORD_es: 'Por favor, rellene la nueva contraseña', + FILL_USER_es: 'Por favor, ingresa tu correo electronico', + FILL_USER_AND_PASS_es: '¡Por favor, ingresa tu correo electronico y contraseña!', FILL_AT_LEAST_3_es: '¡Por favor, escriba al menos 3 caracteres!', MAX_TEXT_LENGTH_2000_es: 'La longitud máxima del texto es 2000', + RECOVER_PASSWORD_es: 'Si la dirección de correo electrónico ingresada está asociada a una cuenta, recibirá un enlace para restablecer su contraseña en breve.', UNKNOWN_es: 'Error desconocido', + WRONG_IDENTIFICATION_es: 'Identificación incorrecta o faltante', WRONG_OR_MISSING_INFO_es: '¡Información incorrecta o incompleta!', BAD_PASSWORD_3_ru: 'Неправильный пароль: Пароль должен содержать не менее 8 символов, 1 заглавную букву, 1 специальный символ.', BAD_PASSWORD_2_ru: 'Неправильный пароль: Пароль должен содержать как минимум 1 заглавную букву и 1 специальный символ.', BAD_PASSWORD_1_ru: 'Неправильный пароль: Пароль должен содержать хотя бы 1 специальный символ.', + CONFIRM_EMAIL_TO_GO_ON_ru: 'Пожалуйста, подтвердите свой адрес электронной почты, прежде чем продолжить', EMAIL_EXISTS_ru: 'Электронная почта уже используется!', FORBIDDEN_ru: 'Доступ запрещен!', INTERNAL_ERROR_ru: 'Внутренняя ошибка сервера!', INVALID_CREDENTIALS_ru: 'Неправильный пользователь или пароль', MAX_LOGIN_ATTEMPT_ru: 'Достигнут максимальный лимит попыток входа. Пожалуйста, подождите 30 минут', + NETWORK_ERROR_ru: 'Ошибка сети при попытке получить ресурс.', FILL_ALL_FIELDS_ru: 'Пожалуйста, заполните все поля', + FILL_NEW_PASSWORD_ru: 'Пожалуйста, введите новый пароль', + FILL_USER_ru: 'Пожалуйста, введите свой адрес электронной почты', FILL_USER_AND_PASS_ru: 'Пожалуйста, введите свое имя пользователя и пароль!', FILL_AT_LEAST_3_ru: 'Пожалуйста, введите не менее 3 символов', MAX_TEXT_LENGTH_2000_ru: 'Максимальная длина текста — 2000 символов.', + RECOVER_PASSWORD_ru: 'Если введенный вами адрес электронной почты связан с учетной записью, вы вскоре получите ссылку для сброса пароля.', UNKNOWN_ru: 'Неизвестная ошибка', + WRONG_IDENTIFICATION_ru: 'Неправильная или отсутствующая идентификация', WRONG_OR_MISSING_INFO_ru: 'Неверная или отсутствующая информация!' }; diff --git a/client/src/constants/portuguese.ts b/client/src/constants/portuguese.ts index 2eda915..01f988f 100644 --- a/client/src/constants/portuguese.ts +++ b/client/src/constants/portuguese.ts @@ -7,6 +7,11 @@ const ptBrTranslations = { landing_btn_portuguese: 'Português (BR)', landing_btn_spanish: 'Español', landing_btn_russian: 'Русский', + landing_languages: 'Você pode escolher um destes idiomas', + + password_show_txt: 'Mostrar', + password_hide_txt: 'Esconder', + password_helper: 'A senha deve ter no mínimo 8 caracteres e incluir um número, uma letra maiúscula, e um caracter especial', login_title: 'Entrar', login_email_label: 'E-mail', @@ -17,22 +22,56 @@ const ptBrTranslations = { login_account: 'Não possui uma conta ainda?', login_go_other: 'Criar conta', login_back_home: 'Voltar para o início', + login_forgot_password: 'Esqueceu sua senha?', register_title: 'Criar conta', register_email_label: 'E-mail', register_email_placeholder: 'Digite seu e-mail', register_password_label: 'Senha', register_password_placeholder: 'Digite sua senha', + register_password_repeat_label: 'Repetir senha', register_btn_submit: 'Criar conta', register_account: 'Já possui conta?', register_go_other: 'Entrar', register_back_home: 'Voltar para o início', + register_resent_email: 'Re-enviar e-mail de confirmação', + register_resent_email_secs: 'Re-enviar em ', + + reset_title: 'Resete sua senha', + reset_email_label: 'E-mail', + reset_email_placeholder: 'Digite seu e-mail', + reset_password_label: 'Senha', + reset_password_placeholder: 'Digite sua senha', + reset_btn_submit: 'Enviar e-mail de confirmação', + reset_account: 'Lembrou da sua senha?', + reset_go_other: 'Entrar', + reset_back_home: 'Voltar para o início', + + complete_reset_title: 'Crie sua nova senha', + complete_reset_password_label: 'Senha', + complete_reset_password_placeholder: 'Digite sua senha', + complete_reset_btn_submit: 'Resetar', + complete_reset_account: 'Lembrou da sua senha?', + complete_reset_go_other: 'Login', + complete_reset_back_home: 'Voltar para o início', + + email_confirmation_loading: 'Confirmando seu endereço de e-mail...', + email_confirmation_success: '✅ E-mail confirmado', + email_confirmation_success_text: 'Obrigado! Seu endereço de e-mail foi confirmado com sucesso. Agora você pode voltar ao app TaskNote e entrar.', + email_confirmation_btn: 'Entrar', home_nav_home: 'Início', home_nav_tasks: 'Tarefas', home_nav_notes: 'Notas', home_nav_about: 'Sobre', home_welcome_title: 'Olá, ', + home_welcome_subtitle: 'Bem vindo ao TaskNote! Se prepare para completar suas tarefas pendentes', + home_welcome_start: 'Comece Seu Dia, Seja', + home_welcome_productive: 'Produtivo(a)', + home_input_filter: 'Filtrar tarefas e notas', + home_radio_everything: 'Tudo', + home_radio_tasks: 'Tarefas', + home_radio_notes: 'Notas', home_card_task_title: 'Resumo de Tarefas', home_card_task_pending: 'Tarefa(s) pendente(s)', home_card_task_empty: 'Nenhuma tarefa pendente', diff --git a/client/src/constants/russian.ts b/client/src/constants/russian.ts index 94528b4..2696b27 100644 --- a/client/src/constants/russian.ts +++ b/client/src/constants/russian.ts @@ -7,6 +7,11 @@ const ruTranslations = { landing_btn_portuguese: 'Português (BR)', landing_btn_spanish: 'Español', landing_btn_russian: 'Русский', + landing_languages: 'Вы можете выбрать один из этих языков', + + password_show_txt: 'Показать', + password_hide_txt: 'Скрыть', + password_helper: 'Пароль должен быть длиной не менее 8 символов и включать цифру, заглавную букву и специальный символ', login_title: 'Войти', login_email_label: 'Электронная почта', @@ -17,22 +22,56 @@ const ruTranslations = { login_account: 'Ещё нет аккаунта?', login_go_other: 'Создать аккаунт', login_back_home: 'Вернуться на главную', + login_forgot_password: 'Забыли пароль?', register_title: 'Создать аккаунт', register_email_label: 'Электронная почта', register_email_placeholder: 'Введите вашу электронную почту', register_password_label: 'Пароль', register_password_placeholder: 'Введите ваш пароль', + register_password_repeat_label: 'Повторите пароль', register_btn_submit: 'Создать аккаунт', register_account: 'Уже есть аккаунт?', register_go_other: 'Войти', register_back_home: 'Вернуться на главную', + register_resent_email: 'Повторно отправить письмо с подтверждением', + register_resent_email_secs: 'Повторно отправить в ', + + reset_title: 'Сбросьте свой пароль', + reset_email_label: 'Электронная почта', + reset_email_placeholder: 'Введите вашу электронную почту', + reset_password_label: 'Пароль', + reset_password_placeholder: 'Введите ваш пароль', + reset_btn_submit: 'Отправить подтверждение по электронной почте', + reset_account: 'Вы вспомнили свой пароль?', + reset_go_other: 'Войти', + reset_back_home: 'Вернуться на главную', + + complete_reset_title: 'Создайте новый пароль', + complete_reset_password_label: 'Пароль', + complete_reset_password_placeholder: 'Введите ваш пароль', + complete_reset_btn_submit: 'Reset', + complete_reset_account: 'Вы вспомнили свой пароль?', + complete_reset_go_other: 'Войти', + complete_reset_back_home: 'Вернуться на главную', + + email_confirmation_loading: 'Подтверждение вашего адреса электронной почты...', + email_confirmation_success: '✅ Электронная почта подтверждена', + email_confirmation_success_text: 'Спасибо! Ваш адрес электронной почты успешно подтвержден. Теперь вы можете вернуться в приложение TaskNote и войти в систему.', + email_confirmation_btn: 'Войти', home_nav_home: 'Главная', home_nav_tasks: 'Задачи', home_nav_notes: 'Заметки', home_nav_about: 'О приложении', home_welcome_title: 'Привет, ', + home_welcome_subtitle: 'Добро пожаловать в TaskNote! Приготовьтесь завершить свои отложенные задачи', + home_welcome_start: 'Начните свой день, будьте', + home_welcome_productive: 'Продуктивный', + home_input_filter: 'Фильтровать задачи и заметки', + home_radio_everything: 'Все', + home_radio_tasks: 'Задачи', + home_radio_notes: 'Заметки', home_card_task_title: 'Обзор задач', home_card_task_pending: 'Незавершённые задачи', home_card_task_empty: 'Нет незавершённых задач', diff --git a/client/src/constants/serverResponses.ts b/client/src/constants/serverResponses.ts index ea8f04b..aee6899 100644 --- a/client/src/constants/serverResponses.ts +++ b/client/src/constants/serverResponses.ts @@ -4,13 +4,19 @@ export const serverResponses: Record = { 'Bad password: Password must have at least 1 special character': 'BAD_PASSWORD_1', 'Email already exists!': 'EMAIL_EXISTS', 'Forbidden! Access denied!': 'FORBIDDEN', + 'If the email address you entered is associated with an account, you will receive a password reset link shortly.': 'RECOVER_PASSWORD', 'Internal Server Error!': 'INTERNAL_ERROR', 'Max login attempt limit reached. Please wait 30 minutes': 'MAX_LOGIN_ATTEMPT', + 'NetworkError when attempting to fetch resource.': 'NETWORK_ERROR', + 'Please confirm your email before proceeding': 'CONFIRM_EMAIL_TO_GO_ON', 'Please fill in all the fields': 'FILL_ALL_FIELDS', + 'Please fill in your email': 'FILL_USER', 'Please fill in your username and password!': 'FILL_USER_AND_PASS', + 'Please fill in the new password': 'FILL_NEW_PASSWORD', 'Please type at least 3 characters': 'FILL_AT_LEAST_3', 'The maximum text length is 2000': 'MAX_TEXT_LENGTH_2000', 'Unknown error': 'UNKNOWN', + 'Wrong or missing identification': 'WRONG_IDENTIFICATION', 'Wrong or missing information!': 'WRONG_OR_MISSING_INFO', 'Invalid credentials': 'INVALID_CREDENTIALS' }; diff --git a/client/src/constants/spanish.ts b/client/src/constants/spanish.ts index 65b6029..43e94e6 100644 --- a/client/src/constants/spanish.ts +++ b/client/src/constants/spanish.ts @@ -7,6 +7,11 @@ const esTranslations = { landing_btn_portuguese: 'Português (BR)', landing_btn_spanish: 'Español', landing_btn_russian: 'Русский', + landing_languages: 'Puedes elegir uno de estos idiomas', + + password_show_txt: 'Mostrar', + password_hide_txt: 'Ocultar', + password_helper: 'La contraseña debe tener al menos 8 caracteres e incluir un número, una letra mayúscula y un carácter especial', login_title: 'Acceso', login_email_label: 'Correo electrónico', @@ -17,22 +22,56 @@ const esTranslations = { login_account: '¿No tienes una cuenta?', login_go_other: 'Regístrate aquí', login_back_home: 'Volver a inicio', + login_forgot_password: '¿Olvidaste tu contraseña?', register_title: 'Crear cuenta', register_email_label: 'Correo electrónico', register_email_placeholder: 'Escribe tu correo aquí', register_password_label: 'Contraseña', register_password_placeholder: 'Escribe tu contraseña aquí', + register_password_repeat_label: 'Repita la contraseña', register_btn_submit: 'Crear cuenta', register_account: '¿Ya tienes una cuenta?', register_go_other: 'Ir a iniciar sesión', register_back_home: 'Volver a inicio', + register_resent_email: 'Reenviar correo electrónico de confirmación', + register_resent_email_secs: 'Reenviar en ', - home_nav_home: 'Inicio', + reset_title: 'Restablecer su contraseña', + reset_email_label: 'Correo electrónico', + reset_email_placeholder: 'Escribe tu correo aquí', + reset_password_label: 'Contraseña', + reset_password_placeholder: 'Escribe tu contraseña aquí', + reset_btn_submit: 'Enviar correo electrónico de confirmación', + reset_account: '¿Has recordado tu contraseña?', + reset_go_other: 'Ir a iniciar sesión', + reset_back_home: 'Volver a inicio', + + complete_reset_title: 'Crea tu nueva contraseña', + complete_reset_password_label: 'Contraseña', + complete_reset_password_placeholder: 'Escribe tu contraseña aquí', + complete_reset_btn_submit: 'Reiniciar', + complete_reset_account: '¿Has recordado tu contraseña?', + complete_reset_go_other: 'Ir a iniciar sesión', + complete_reset_back_home: 'Volver a inicio', + + email_confirmation_loading: 'Confirmando su dirección de correo electrónico...', + email_confirmation_success: '✅ Correo electrónico confirmado', + email_confirmation_success_text: '¡Gracias! Tu dirección de correo electrónico se ha confirmado correctamente. Ya puedes volver a la app TaskNote e iniciar sesión.', + email_confirmation_btn: 'Iniciar sesión', + + home_nav_home: 'Início', home_nav_tasks: 'Tareas', home_nav_notes: 'Notas', home_nav_about: 'Acerca de', home_welcome_title: 'Hola, ', + home_welcome_subtitle: '¡Bienvenido a TaskNote! Prepárate para completar tus tareas pendientes.', + home_welcome_start: 'Comienza Tu Día, Sé', + home_welcome_productive: 'Productivo(a)', + home_input_filter: 'Filtrar tareas y notas', + home_radio_everything: 'Todo', + home_radio_tasks: 'Tareas', + home_radio_notes: 'Notas', home_card_task_title: 'Resumen de Tareas', home_card_task_pending: 'Tarea(s) pendiente(s)', home_card_task_empty: 'No tienes tareas pendientes', diff --git a/client/src/styles/custom.scss b/client/src/styles/custom.scss index e994ded..c925d62 100644 --- a/client/src/styles/custom.scss +++ b/client/src/styles/custom.scss @@ -11,6 +11,15 @@ a, .btn-link { text-decoration: none; } +.btn:focus-visible, +.btn:hover, +.btn:active, +:not(.btn-check) + .btn:active { + background-color: var(--bs-primary); + border-color: var(--bs-primary); + box-shadow: var(--bs-primary); +} + a:hover, .btn-link:hover { color: var(--bs-primary); } @@ -265,13 +274,43 @@ code { } .input-group-text { - border-right: none !important; padding: 0.375rem 0.10rem 0.375rem 0.75rem; } .input-group > .form-control, .react-datepicker__input-container > .form-control { - border-left: none !important; + border-left: none; +} + +.form-control:focus { + box-shadow: none; + outline: none; + border-color: var(--bs-border-color); +} + +.form-control:invalid { + box-shadow: none; + outline: none; + border-color: var(--bs-border-color); +} + +.form-control:invalid:focus { + box-shadow: none; + outline: none; + border-color: var(--bs-border-color); +} + +.was-validated .form-control:invalid:focus { + box-shadow: none; + outline: none; + border-color: var(--bs-border-color); +} + +.was-validated .form-control:valid, +.was-validated .form-control:valid:focus { + box-shadow: none; + outline: none; + border-color: var(--bs-border-color); } .dropdown-toggle.btn.btn-success { @@ -280,17 +319,6 @@ code { border: none; } -.form-control:focus { - box-shadow: none; - outline: none; - border-color: var(--bs-border-color); /* optional – prevents border from changing on focus */ -} - -.form-control:invalid { - box-shadow: none; - outline: none; -} - .home-filter { all: unset; font-size: 12px; diff --git a/client/src/views/Account/index.tsx b/client/src/views/Account/index.tsx index ab7a635..d405a5d 100644 --- a/client/src/views/Account/index.tsx +++ b/client/src/views/Account/index.tsx @@ -85,8 +85,8 @@ function Account(): React.ReactNode { * Resets the input fields to their default values. */ const resetInputs = (userUpdated: UserResponse): void => { - setUserName(userUpdated.name ? userUpdated.name : ''); - setUserEmail(userUpdated.email); + setUserName(''); + setUserEmail(''); updateUser(userUpdated); setValidated(false); @@ -96,6 +96,7 @@ function Account(): React.ReactNode { event.preventDefault(); event.stopPropagation(); setValidated(true); + setErrorMessage(''); const form = event.currentTarget; @@ -106,6 +107,12 @@ function Account(): React.ReactNode { passwordAgain: userPasswordAgain }; + const sizeOfPayload = JSON.stringify(patchPayload).length; + if (sizeOfPayload === 57) { + setErrorMessage('Nothing to update!'); + return; + } + const updated: UserResponse | undefined = await patchUserInfo(patchPayload); if (updated) { form.reset(); @@ -145,7 +152,7 @@ function Account(): React.ReactNode { iconName="Person" required={false} name="name" - placeholder={user?.name ? user.name : ''} + placeholder={user?.name ?? 'Your name'} value={userName} onChange={(e: React.ChangeEvent) => { setUserName(e.target.value); diff --git a/client/src/views/EmailConfirmation/index.tsx b/client/src/views/EmailConfirmation/index.tsx index aac7409..3e81a95 100644 --- a/client/src/views/EmailConfirmation/index.tsx +++ b/client/src/views/EmailConfirmation/index.tsx @@ -2,19 +2,23 @@ import React, { useEffect, useState } from 'react'; import { Link } from 'react-router'; import api from '../../api-service/api'; import ApiConfig from '../../api-service/apiConfig'; +import { translateServerResponse } from '../../utils/TranslatorUtils'; +import { useTranslation } from 'react-i18next'; +import { handleDefaultLang } from '../../lang-service/LangHandler'; type Status = 'loading' | 'success' | 'error'; const EmailConfirmation: React.FC = () => { const [status, setStatus] = useState('loading'); const [errorMessage, setErrorMessage] = useState(''); + const { i18n, t } = useTranslation(); const confirmEmail = async (): Promise => { const urlParams = new URLSearchParams(window.location.search); const id = urlParams.get('identification'); if (!id) { setStatus('error'); - setErrorMessage('Wrong or missing identification.'); + setErrorMessage(translateServerResponse('Wrong or missing identification', i18n.language)); return; } @@ -38,6 +42,10 @@ const EmailConfirmation: React.FC = () => { confirmEmail(); }, []); + useEffect(() => { + handleDefaultLang(); + }, [i18n.language]); + return (
{ rel="stylesheet" /> -
+
{status === 'loading' && ( <>
-

Confirming your email address...

+

{t('email_confirmation_loading')}

)} {status === 'success' && ( <> -

✅ Email Confirmed

+

{t('email_confirmation_success')}

-

- Thank you! Your email address has been successfully confirmed. - You can now return to the TaskNote app and log in. -

+

{t('email_confirmation_success_text')}

- Go to Login + {t('email_confirmation_btn')} )} @@ -78,7 +83,7 @@ const EmailConfirmation: React.FC = () => { {status === 'error' && ( <>

❌ Oops!

-

+

{ errorMessage }

diff --git a/client/src/views/Home/index.tsx b/client/src/views/Home/index.tsx index 36d82f0..f624392 100644 --- a/client/src/views/Home/index.tsx +++ b/client/src/views/Home/index.tsx @@ -264,7 +264,7 @@ function Home(): React.ReactNode { useEffect(() => { handleDefaultLang(); - setName(user?.name ? user?.name : 'User'); + setName(user?.name ?? 'User'); loadTags(); loadAllTasks(); loadAllNotes(); @@ -275,10 +275,12 @@ function Home(): React.ReactNode { ) => filterTasksAndNotes(e.target.value, selectedOption)} /> @@ -309,7 +311,7 @@ function Home(): React.ReactNode { {t('landing_btn_register')}