Feat/improve security (#482)

* build(deps): bump org.hibernate.orm.tooling:hibernate-enhance-maven-plugin

Bumps [org.hibernate.orm.tooling:hibernate-enhance-maven-plugin](https://github.com/hibernate/hibernate-orm) from 6.6.15.Final to 6.6.16.Final.
- [Release notes](https://github.com/hibernate/hibernate-orm/releases)
- [Changelog](https://github.com/hibernate/hibernate-orm/blob/6.6.16/changelog.txt)
- [Commits](https://github.com/hibernate/hibernate-orm/compare/6.6.15...6.6.16)

---
updated-dependencies:
- dependency-name: org.hibernate.orm.tooling:hibernate-enhance-maven-plugin
  dependency-version: 6.6.16.Final
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump com.puppycrawl.tools:checkstyle in /server

Bumps [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) from 10.23.1 to 10.24.0.
- [Release notes](https://github.com/checkstyle/checkstyle/releases)
- [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.23.1...checkstyle-10.24.0)

---
updated-dependencies:
- dependency-name: com.puppycrawl.tools:checkstyle
  dependency-version: 10.24.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump org.springframework.boot:spring-boot-starter-parent

Bumps [org.springframework.boot:spring-boot-starter-parent](https://github.com/spring-projects/spring-boot) from 3.4.1 to 3.5.0.
- [Release notes](https://github.com/spring-projects/spring-boot/releases)
- [Commits](https://github.com/spring-projects/spring-boot/compare/v3.4.1...v3.5.0)

---
updated-dependencies:
- dependency-name: org.springframework.boot:spring-boot-starter-parent
  dependency-version: 3.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump @vitejs/plugin-react from 4.4.1 to 4.5.0 in /client

Bumps [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/tree/HEAD/packages/plugin-react) from 4.4.1 to 4.5.0.
- [Release notes](https://github.com/vitejs/vite-plugin-react/releases)
- [Changelog](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite-plugin-react/commits/plugin-react@4.5.0/packages/plugin-react)

---
updated-dependencies:
- dependency-name: "@vitejs/plugin-react"
  dependency-version: 4.5.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump react-router from 7.6.0 to 7.6.1 in /client

Bumps [react-router](https://github.com/remix-run/react-router/tree/HEAD/packages/react-router) from 7.6.0 to 7.6.1.
- [Release notes](https://github.com/remix-run/react-router/releases)
- [Changelog](https://github.com/remix-run/react-router/blob/main/packages/react-router/CHANGELOG.md)
- [Commits](https://github.com/remix-run/react-router/commits/react-router@7.6.1/packages/react-router)

---
updated-dependencies:
- dependency-name: react-router
  dependency-version: 7.6.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump react-datepicker from 8.3.0 to 8.4.0 in /client

Bumps [react-datepicker](https://github.com/Hacker0x01/react-datepicker) from 8.3.0 to 8.4.0.
- [Release notes](https://github.com/Hacker0x01/react-datepicker/releases)
- [Commits](https://github.com/Hacker0x01/react-datepicker/compare/v8.3.0...v8.4.0)

---
updated-dependencies:
- dependency-name: react-datepicker
  dependency-version: 8.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump @stylistic/eslint-plugin in /client

Bumps [@stylistic/eslint-plugin](https://github.com/eslint-stylistic/eslint-stylistic/tree/HEAD/packages/eslint-plugin) from 4.2.0 to 4.4.0.
- [Release notes](https://github.com/eslint-stylistic/eslint-stylistic/releases)
- [Changelog](https://github.com/eslint-stylistic/eslint-stylistic/blob/main/CHANGELOG.md)
- [Commits](https://github.com/eslint-stylistic/eslint-stylistic/commits/v4.4.0/packages/eslint-plugin)

---
updated-dependencies:
- dependency-name: "@stylistic/eslint-plugin"
  dependency-version: 4.4.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump globals from 16.1.0 to 16.2.0 in /client

Bumps [globals](https://github.com/sindresorhus/globals) from 16.1.0 to 16.2.0.
- [Release notes](https://github.com/sindresorhus/globals/releases)
- [Commits](https://github.com/sindresorhus/globals/compare/v16.1.0...v16.2.0)

---
updated-dependencies:
- dependency-name: globals
  dependency-version: 16.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump jasmine-core from 5.6.0 to 5.7.1 in /angular

Bumps [jasmine-core](https://github.com/jasmine/jasmine) from 5.6.0 to 5.7.1.
- [Release notes](https://github.com/jasmine/jasmine/releases)
- [Changelog](https://github.com/jasmine/jasmine/blob/main/RELEASE.md)
- [Commits](https://github.com/jasmine/jasmine/compare/v5.6.0...v5.7.1)

---
updated-dependencies:
- dependency-name: jasmine-core
  dependency-version: 5.7.1
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps-dev): bump @angular/cli from 19.2.12 to 19.2.13 in /angular

Bumps [@angular/cli](https://github.com/angular/angular-cli) from 19.2.12 to 19.2.13.
- [Release notes](https://github.com/angular/angular-cli/releases)
- [Changelog](https://github.com/angular/angular-cli/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular-cli/compare/19.2.12...19.2.13)

---
updated-dependencies:
- dependency-name: "@angular/cli"
  dependency-version: 19.2.13
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump @angular/platform-browser-dynamic in /angular

Bumps [@angular/platform-browser-dynamic](https://github.com/angular/angular/tree/HEAD/packages/platform-browser-dynamic) from 19.2.10 to 19.2.13.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.13/packages/platform-browser-dynamic)

---
updated-dependencies:
- dependency-name: "@angular/platform-browser-dynamic"
  dependency-version: 19.2.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump @angular/router from 19.2.10 to 19.2.13 in /angular

Bumps [@angular/router](https://github.com/angular/angular/tree/HEAD/packages/router) from 19.2.10 to 19.2.13.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.13/packages/router)

---
updated-dependencies:
- dependency-name: "@angular/router"
  dependency-version: 19.2.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* build(deps): bump @angular/platform-browser in /angular

Bumps [@angular/platform-browser](https://github.com/angular/angular/tree/HEAD/packages/platform-browser) from 19.2.10 to 19.2.13.
- [Release notes](https://github.com/angular/angular/releases)
- [Changelog](https://github.com/angular/angular/blob/main/CHANGELOG.md)
- [Commits](https://github.com/angular/angular/commits/19.2.13/packages/platform-browser)

---
updated-dependencies:
- dependency-name: "@angular/platform-browser"
  dependency-version: 19.2.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>

* fix: server/Dockerfile to reduce vulnerabilities

The following vulnerabilities are fixed with an upgrade:
- https://snyk.io/vuln/SNYK-DEBIAN12-GCC12-5901316
- https://snyk.io/vuln/SNYK-DEBIAN12-GCC12-5901316
- https://snyk.io/vuln/SNYK-DEBIAN12-GCC12-5901316
- https://snyk.io/vuln/SNYK-DEBIAN12-SHADOW-5879156
- https://snyk.io/vuln/SNYK-DEBIAN12-SHADOW-5879156

* fix: css styles and translations

* chore: fix authentication provider

* chore: fix line too long

* test: update test cases

* chore: prefer coalescing operator

* chore: prefer nullish coalescing operator

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: snyk-bot <snyk-bot@snyk.io>
This commit is contained in:
2025-05-28 18:15:37 -03:00
committed by GitHub
co-authored by dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> snyk-bot
parent a45378685b
commit fe2803393b
28 changed files with 576 additions and 241 deletions
+163 -43
View File
@@ -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"
},
+5 -5
View File
@@ -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",
+28 -21
View File
@@ -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",
+5 -5
View File
@@ -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",
@@ -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(
<MemoryRouter>
<ContentHeader
{ ...headerProps }
isHomeComponent={true}
newTaskI18n="New task"
newNoteI18n="New note"
/>
</MemoryRouter>
);
@@ -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!');
});
});
+21 -29
View File
@@ -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('Неправильный пользователь или пароль');
});
});
+20 -29
View File
@@ -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!');
});
});
@@ -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);
});
+4 -4
View File
@@ -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);
@@ -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: Props): React.ReactNode => {
type="button"
className="home-new-item w-45 mb-2"
>
New task
{props.newTaskI18n}
</button>
</NavLink>
<NavLink to="/notes/new" onClick={() => setNewPage('/notes/new')}>
@@ -62,7 +64,7 @@ const ContentHeader: React.FC<Props> = (props: Props): React.ReactNode => {
type="button"
className="home-new-item w-45 ms-2"
>
New note
{props.newNoteI18n}
</button>
</NavLink>
</Col>
+20 -10
View File
@@ -21,6 +21,9 @@ interface Props {
onChange?: (e: React.ChangeEvent<HTMLInputElement>) => void;
onChangeDate?: (date: Date | null) => void;
data_testid?: string;
pwdHideText?: string;
pwdShowText?: string;
pwdHelperTxt?: string;
}
/**
@@ -66,7 +69,7 @@ function FormInput(props: React.PropsWithChildren<Props>): React.ReactNode {
{props.type && props.type === 'password' && (
<small>
<a href="#" onClick={toggleShowPassword}>
{showingPwd ? ' (Hide)' : ' (Show)'}
{showingPwd ? ` (${props.pwdHideText})` : ` (${props.pwdShowText})`}
</a>
</small>
)}
@@ -109,15 +112,22 @@ function FormInput(props: React.PropsWithChildren<Props>): React.ReactNode {
/>
)
: (
<Form.Control
required={props.required}
type={formType}
name={props.name}
placeholder={props.placeholder ? props.placeholder : ''}
value={props?.value}
onChange={props.onChange}
data-testid={props.data_testid}
/>
<>
<Form.Control
required={props.required}
type={formType}
name={props.name}
placeholder={props.placeholder ? props.placeholder : ''}
value={props?.value}
onChange={props.onChange}
data-testid={props.data_testid}
/>
{props.type && props.type === 'password' && (
<Form.Text id="passwordHelpBlock" muted>
{props.pwdHelperTxt}
</Form.Text>
)}
</>
)}
</InputGroup>
</Form.Group>
+23 -9
View File
@@ -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}`}
</Button>
</div>
</>
@@ -189,7 +195,7 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode {
<Form noValidate validated={validated} onSubmit={handleSubmit}>
{prefix !== 'complete_reset' && (
<FormInput
labelText="Email"
labelText={t(`${prefix}_email_label`)}
iconName="At"
required={true}
name="email"
@@ -204,12 +210,16 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode {
{prefix !== 'reset' && (
<FormInput
labelText="Password"
labelText={t(`${prefix}_password_label`)}
iconName="Lock"
required={true}
type="password"
name="password"
placeholder={t(`${prefix}_password_placeholder`)}
value={password}
pwdShowText={t('password_show_txt')}
pwdHideText={t('password_hide_txt')}
pwdHelperTxt={t('password_helper')}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPassword(e.target.value);
}}
@@ -219,12 +229,16 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode {
{(prefix === 'register' || prefix === 'complete_reset') && (
<FormInput
labelText="Repeat password"
labelText={t('register_password_repeat_label')}
iconName="Lock"
required={true}
type="password"
name="passwordAgain"
placeholder={t('login_password_placeholder')}
value={passwordAgain}
pwdShowText={t('password_show_txt')}
pwdHideText={t('password_hide_txt')}
pwdHelperTxt={t('password_helper')}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
setPasswordAgain(e.target.value);
}}
@@ -256,7 +270,7 @@ function LoginForm({ prefix }: { prefix: string }): React.ReactNode {
<div className="text-center mt-3">
{prefix === 'login' && (
<Link to="/reset-password" className="text-decoration-none ms-2">
Forgot your password?
{t('login_forgot_password')}
</Link>
)}
</div>
+28 -7
View File
@@ -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!',
+19 -1
View File
@@ -67,45 +67,63 @@ export const serverResponsesTranslations: Record<string, string> = {
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: 'Неверная или отсутствующая информация!'
};
+39
View File
@@ -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',
+39
View File
@@ -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: 'Нет незавершённых задач',
+6
View File
@@ -4,13 +4,19 @@ export const serverResponses: Record<string, string> = {
'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'
};
+40 -1
View File
@@ -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',
+41 -13
View File
@@ -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;
+10 -3
View File
@@ -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<HTMLInputElement>) => {
setUserName(e.target.value);
+15 -10
View File
@@ -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<Status>('loading');
const [errorMessage, setErrorMessage] = useState<string>('');
const { i18n, t } = useTranslation();
const confirmEmail = async (): Promise<void> => {
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 (
<div
className="d-flex flex-column align-items-center justify-content-center min-vh-100 px-3 text-center"
@@ -52,25 +60,22 @@ const EmailConfirmation: React.FC = () => {
rel="stylesheet"
/>
<div className="p-4 bg-white rounded-3 shadow" style={{ maxWidth: '500px', width: '100%' }}>
<div className="p-5 bg-white rounded-3 shadow" style={{ maxWidth: '500px', width: '100%' }}>
{status === 'loading' && (
<>
<div className="spinner-border text-success mb-3"></div>
<p>Confirming your email address...</p>
<p>{t('email_confirmation_loading')}</p>
</>
)}
{status === 'success' && (
<>
<h1 className="mb-3"> Email Confirmed</h1>
<h1 className="mb-4" data-testid="email-confirm-success-msg">{t('email_confirmation_success')}</h1>
<p className="mb-4">
Thank you! Your email address has been successfully confirmed.
You can now return to the TaskNote app and log in.
</p>
<p className="mb-4">{t('email_confirmation_success_text')}</p>
<Link to="/login" className="btn btn-success px-4 py-2"style={{ fontWeight: 'bold', borderRadius: '6px' }}>
Go to Login
{t('email_confirmation_btn')}
</Link>
</>
)}
@@ -78,7 +83,7 @@ const EmailConfirmation: React.FC = () => {
{status === 'error' && (
<>
<h1 className="mb-3"> Oops!</h1>
<p className="mb-4">
<p className="mb-4" data-testid="email-confirm-error-msg">
{ errorMessage }
</p>
</>
+10 -8
View File
@@ -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 {
<ContentHeader
h1TextRegular={t('home_welcome_title')}
h1TextBold={name}
subtitle="Welcome to TaskNote! Get ready to complete your pending tasks"
h2BlackText="Start Your Day, Be"
h2GreenText="Productive"
subtitle={t('home_welcome_subtitle')}
h2BlackText={t('home_welcome_start')}
h2GreenText={t('home_welcome_productive')}
isHomeComponent
newTaskI18n={t('task_form_title')}
newNoteI18n={t('note_form_title')}
/>
<AlertError
@@ -295,7 +297,7 @@ function Home(): React.ReactNode {
id="search_term"
size="lg"
name="search_term"
placeholder="Filter tasks & notes"
placeholder={t('home_input_filter')}
value={filterText}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => filterTasksAndNotes(e.target.value, selectedOption)}
/>
@@ -309,7 +311,7 @@ function Home(): React.ReactNode {
<Form.Check
inline
type="radio"
label="Everything"
label={t('home_radio_everything')}
name="radioGroup"
id="everything"
value="everything"
@@ -323,7 +325,7 @@ function Home(): React.ReactNode {
<Form.Check
inline
type="radio"
label="Tasks"
label={t('home_radio_tasks')}
name="radioGroup"
id="onlyTasks"
value="onlyTasks"
@@ -337,7 +339,7 @@ function Home(): React.ReactNode {
<Form.Check
inline
type="radio"
label="Notes"
label={t('home_radio_notes')}
name="radioGroup"
id="onlyNotes"
value="onlyNotes"
+1 -1
View File
@@ -46,7 +46,7 @@ function Landing(): React.ReactNode {
<Link to="/register" className="login">{t('landing_btn_register')}</Link>
<aside>
<h3>You can choose one of these languages</h3>
<h3>{t('landing_languages')}</h3>
{languages.map((lang: LangAvailable) => (
<Button
key={lang.key}
+1
View File
@@ -25,6 +25,7 @@
border: none;
border-radius: 4px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
padding: 30px;
}
h2 {
+1 -1
View File
@@ -12,7 +12,7 @@ RUN ./mvnw -B package -Pnative -DskipTests
### Deployer
FROM debian:12.10-slim AS deploy
FROM debian:12.11-slim AS deploy
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
ca-certificates \
+3 -3
View File
@@ -5,7 +5,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.4.1</version>
<version>3.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
@@ -42,7 +42,7 @@
<jacoco.output.data>${project.build.directory}/coverage-reports</jacoco.output.data>
<timestamp>${maven.build.timestamp}</timestamp>
<maven.build.timestamp.format>yyyy-MM-dd HH:mm:ss</maven.build.timestamp.format>
<hibernate.version>6.6.15.Final</hibernate.version>
<hibernate.version>6.6.16.Final</hibernate.version>
<sonar.organization>ricardo-campos-org</sonar.organization>
<sonar.host.url>https://sonarcloud.io</sonar.host.url>
</properties>
@@ -423,7 +423,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.23.1</version>
<version>10.24.0</version>
</dependency>
</dependencies>
<configuration>
@@ -80,8 +80,8 @@ public class SecurityConfig {
*/
@Bean
public AuthenticationProvider authenticationProvider() {
DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userService.userDetailsService());
DaoAuthenticationProvider authProvider =
new DaoAuthenticationProvider(userService.userDetailsService());
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}