diff --git a/.coderabbit.yml b/.coderabbit.yml new file mode 100644 index 000000000..61b2e6361 --- /dev/null +++ b/.coderabbit.yml @@ -0,0 +1 @@ +release_notes: false diff --git a/.github/scripts/download-translations.js b/.github/scripts/download-translations.js index 7a8125ab8..185992fd5 100644 --- a/.github/scripts/download-translations.js +++ b/.github/scripts/download-translations.js @@ -7,7 +7,7 @@ import { URLSearchParams } from "url"; const API_TOKEN = process.env.POEDITOR_API; const PROJECT_ID = process.env.POEDITOR_PROJECT_ID; const LANGUAGES = ( - process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja" + process.env.LANGUAGES || "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn" ).split(","); const EXPORT_FORMAT = process.env.EXPORT_FORMAT || "key_value_json"; diff --git a/.github/workflows/check-format.yml b/.github/workflows/check-format.yml index d3f34635c..056e2c586 100644 --- a/.github/workflows/check-format.yml +++ b/.github/workflows/check-format.yml @@ -16,7 +16,7 @@ jobs: - name: Install client dependencies working-directory: client - run: npm ci + run: npm install - name: Check client formatting working-directory: client @@ -34,7 +34,7 @@ jobs: - name: Install server dependencies working-directory: server - run: npm ci + run: npm install - name: Check server formatting working-directory: server diff --git a/.github/workflows/poeditor-sync.yml b/.github/workflows/poeditor-sync.yml index a8b330290..87396d3d0 100644 --- a/.github/workflows/poeditor-sync.yml +++ b/.github/workflows/poeditor-sync.yml @@ -7,7 +7,7 @@ on: languages: description: "Languages to synchronize (comma separated, e.g.: tr,en,es)" required: false - default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja" + default: "ar,zh-tw,cs,en,fi,fr,de,pt-br,ru,es,tr,ja,zh-cn" format: description: "Export format (key_value_json or json)" required: false diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..600d2d33b --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.vscode \ No newline at end of file diff --git a/client/package-lock.json b/client/package-lock.json index f58f56f44..32446c5d0 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -21,7 +21,7 @@ "dayjs": "1.11.13", "flag-icons": "7.3.2", "html2canvas": "^1.4.1", - "i18next": "^24.2.2", + "i18next": "25.4.2", "joi": "17.13.3", "mui-color-input": "^6.0.0", "react": "18.3.1", @@ -46,9 +46,6 @@ "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", "vite": "6.3.5" - }, - "optionalDependencies": { - "@rollup/rollup-linux-arm64-musl": "4.41.0" } }, "node_modules/@ampproject/remapping": { @@ -283,9 +280,9 @@ } }, "node_modules/@babel/runtime": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.1.tgz", - "integrity": "sha512-1x3D2xEk2fRo3PAhwQwu5UubzgiVWSXTBfWpVd2Mx2AzRqJuDJCsgaDVZ7HB5iGzDW1Hl1sWN2mFyKjmR9uAog==", + "version": "7.28.3", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.3.tgz", + "integrity": "sha512-9uIQ10o0WGdpP6GDhXcdOJPJuDgFtIDtN/9+ArJQ2NAfAmiuhTQdzkaTGR33v43GYS2UrSA0eX2pPPHoFVvpxA==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -491,262 +488,6 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", - "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", - "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", - "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", - "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", - "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", - "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", - "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", - "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", - "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", - "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", - "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", - "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", - "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", - "cpu": [ - "mips64el" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", - "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", - "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", - "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@esbuild/linux-x64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", @@ -763,134 +504,6 @@ "node": ">=18" } }, - "node_modules/@esbuild/netbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", - "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", - "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", - "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", - "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", - "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", - "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", - "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.25.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", - "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.7.0.tgz", @@ -2063,123 +1676,6 @@ } } }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.41.0.tgz", - "integrity": "sha512-KxN+zCjOYHGwCl4UCtSfZ6jrq/qi88JDUtiEFk8LELEHq2Egfc/FgW+jItZiOLRuQfb/3xJSgFuNPC9jzggX+A==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.41.0.tgz", - "integrity": "sha512-yDvqx3lWlcugozax3DItKJI5j05B0d4Kvnjx+5mwiUpWramVvmAByYigMplaoAQ3pvdprGCTCE03eduqE/8mPQ==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.41.0.tgz", - "integrity": "sha512-2KOU574vD3gzcPSjxO0eyR5iWlnxxtmW1F5CkNOHmMlueKNCQkxR6+ekgWyVnz6zaZihpUNkGxjsYrkTJKhkaw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.41.0.tgz", - "integrity": "sha512-gE5ACNSxHcEZyP2BA9TuTakfZvULEW4YAOtxl/A/YDbIir/wPKukde0BNPlnBiP88ecaN4BJI2TtAd+HKuZPQQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.41.0.tgz", - "integrity": "sha512-GSxU6r5HnWij7FoSo7cZg3l5GPg4HFLkzsFFh0N/b16q5buW1NAWuCJ+HMtIdUEi6XF0qH+hN0TEd78laRp7Dg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.41.0.tgz", - "integrity": "sha512-KGiGKGDg8qLRyOWmk6IeiHJzsN/OYxO6nSbT0Vj4MwjS2XQy/5emsmtoqLAabqrohbgLWJ5GV3s/ljdrIr8Qjg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] - }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.41.0.tgz", - "integrity": "sha512-46OzWeqEVQyX3N2/QdiU/CMXYDH/lSHpgfBkuhl3igpZiaB3ZIfSjKuOnybFVBQzjsLwkus2mjaESy8H41SzvA==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.41.0.tgz", - "integrity": "sha512-lfgW3KtQP4YauqdPpcUZHPcqQXmTmH4nYU0cplNeW583CMkAGjtImw4PKli09NFi2iQgChk4e9erkwlfYem6Lg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.41.0.tgz", - "integrity": "sha512-nn8mEyzMbdEJzT7cwxgObuwviMx6kPRxzYiOl6o/o+ChQq23gfdlZcUNnt89lPhhz3BYsZ72rp0rxNqBSfqlqw==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rollup/rollup-linux-arm64-musl": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.41.0.tgz", @@ -2193,71 +1689,6 @@ "linux" ] }, - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.41.0.tgz", - "integrity": "sha512-WbnJaxPv1gPIm6S8O/Wg+wfE/OzGSXlBMbOe4ie+zMyykMOeqmgD1BhPxZQuDqwUN+0T/xOFtL2RUWBspnZj3w==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.41.0.tgz", - "integrity": "sha512-eRDWR5t67/b2g8Q/S8XPi0YdbKcCs4WQ8vklNnUYLaSWF+Cbv2axZsp4jni6/j7eKvMLYCYdcsv8dcU+a6QNFg==", - "cpu": [ - "ppc64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.41.0.tgz", - "integrity": "sha512-TWrZb6GF5jsEKG7T1IHwlLMDRy2f3DPqYldmIhnA2DVqvvhY2Ai184vZGgahRrg8k9UBWoSlHv+suRfTN7Ua4A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.41.0.tgz", - "integrity": "sha512-ieQljaZKuJpmWvd8gW87ZmSFwid6AxMDk5bhONJ57U8zT77zpZ/TPKkU9HpnnFrM4zsgr4kiGuzbIbZTGi7u9A==", - "cpu": [ - "riscv64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.41.0.tgz", - "integrity": "sha512-/L3pW48SxrWAlVsKCN0dGLB2bi8Nv8pr5S5ocSM+S0XCn5RCVCXqi8GVtHFsOBBCSeR+u9brV2zno5+mg3S4Aw==", - "cpu": [ - "s390x" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, "node_modules/@rollup/rollup-linux-x64-gnu": { "version": "4.41.0", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.41.0.tgz", @@ -2284,45 +1715,6 @@ "linux" ] }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.41.0.tgz", - "integrity": "sha512-4yodtcOrFHpbomJGVEqZ8fzD4kfBeCbpsUy5Pqk4RluXOdsWdjLnjhiKy2w3qzcASWd04fp52Xz7JKarVJ5BTg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.41.0.tgz", - "integrity": "sha512-tmazCrAsKzdkXssEc65zIE1oC6xPHwfy9d5Ta25SRCDOZS+I6RypVVShWALNuU9bxIfGA0aqrmzlzoM5wO5SPQ==", - "cpu": [ - "ia32" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.41.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.41.0.tgz", - "integrity": "sha512-h1J+Yzjo/X+0EAvR2kIXJDuTuyT7drc+t2ALY0nIcGPbTatNOf0VWdhEA2Z4AAjv6X1NJV7SYo5oCTYRJhSlVA==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, "node_modules/@sideway/address": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/@sideway/address/-/address-4.1.5.tgz", @@ -4342,14 +3734,15 @@ } }, "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", "mime-types": "^2.1.12" }, "engines": { @@ -4363,20 +3756,6 @@ "dev": true, "license": "ISC" }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -4690,9 +4069,9 @@ } }, "node_modules/i18next": { - "version": "24.2.3", - "resolved": "https://registry.npmjs.org/i18next/-/i18next-24.2.3.tgz", - "integrity": "sha512-lfbf80OzkocvX7nmZtu7nSTNbrTYR52sLWxPtlXX1zAhVw8WEnFk4puUkCR4B1dNQwbSpEHHHemcZu//7EcB7A==", + "version": "25.4.2", + "resolved": "https://registry.npmjs.org/i18next/-/i18next-25.4.2.tgz", + "integrity": "sha512-gD4T25a6ovNXsfXY1TwHXXXLnD/K2t99jyYMCSimSCBnBRJVQr5j+VAaU83RJCPzrTGhVQ6dqIga66xO2rtd5g==", "funding": [ { "type": "individual", @@ -4709,7 +4088,7 @@ ], "license": "MIT", "dependencies": { - "@babel/runtime": "^7.26.10" + "@babel/runtime": "^7.27.6" }, "peerDependencies": { "typescript": "^5" diff --git a/client/package.json b/client/package.json index 611ba5093..92da6fd13 100644 --- a/client/package.json +++ b/client/package.json @@ -26,7 +26,7 @@ "dayjs": "1.11.13", "flag-icons": "7.3.2", "html2canvas": "^1.4.1", - "i18next": "^24.2.2", + "i18next": "25.4.2", "joi": "17.13.3", "mui-color-input": "^6.0.0", "react": "18.3.1", @@ -59,8 +59,5 @@ "eslint-plugin-react-refresh": "^0.4.6", "prettier": "^3.3.3", "vite": "6.3.5" - }, - "optionalDependencies": { - "@rollup/rollup-linux-arm64-musl": "4.41.0" } } diff --git a/client/src/Components/Charts/StatusPageBarChart/index.jsx b/client/src/Components/Charts/StatusPageBarChart/index.jsx index a28db760f..eab0c7d1d 100644 --- a/client/src/Components/Charts/StatusPageBarChart/index.jsx +++ b/client/src/Components/Charts/StatusPageBarChart/index.jsx @@ -42,7 +42,7 @@ const Bar = forwardRef( Bar.displayName = "Bar"; Bar.propTypes = { - width: PropTypes.string.isRequired, + width: PropTypes.oneOfType([PropTypes.string, PropTypes.object]).isRequired, height: PropTypes.string.isRequired, backgroundColor: PropTypes.string.isRequired, borderRadius: PropTypes.string, diff --git a/client/src/Components/Check/Check.jsx b/client/src/Components/Check/Check.jsx index 7f37a4dc1..02b9fd78a 100644 --- a/client/src/Components/Check/Check.jsx +++ b/client/src/Components/Check/Check.jsx @@ -65,7 +65,7 @@ const Check = ({ text, noHighlightText, variant = "info", outlined = false }) => }; Check.propTypes = { - text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]).isRequired, + text: PropTypes.oneOfType([PropTypes.string, PropTypes.element]), noHighlightText: PropTypes.string, variant: PropTypes.oneOf(["info", "error", "success"]), outlined: PropTypes.bool, diff --git a/client/src/Components/Dialog/genericDialog.jsx b/client/src/Components/Dialog/genericDialog.jsx index b2cbe1299..a13eba849 100644 --- a/client/src/Components/Dialog/genericDialog.jsx +++ b/client/src/Components/Dialog/genericDialog.jsx @@ -2,7 +2,7 @@ import { useId } from "react"; import PropTypes from "prop-types"; import { Modal, Stack, Typography } from "@mui/material"; -const GenericDialog = ({ title, description, open, onClose, theme, children }) => { +const GenericDialog = ({ title, description, open, onClose, theme, children, width }) => { const titleId = useId(); const descriptionId = useId(); const ariaDescribedBy = description?.length > 0 ? descriptionId : ""; @@ -16,6 +16,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) = > {title} @@ -46,6 +48,7 @@ const GenericDialog = ({ title, description, open, onClose, theme, children }) = {description} @@ -64,6 +67,7 @@ GenericDialog.propTypes = { theme: PropTypes.object.isRequired, children: PropTypes.oneOfType([PropTypes.arrayOf(PropTypes.node), PropTypes.node]) .isRequired, + width: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.object]), }; export { GenericDialog }; diff --git a/client/src/Components/Table/index.jsx b/client/src/Components/Table/index.jsx index 66db50fbe..bfafb5387 100644 --- a/client/src/Components/Table/index.jsx +++ b/client/src/Components/Table/index.jsx @@ -7,6 +7,7 @@ import { TableHead, TableRow, } from "@mui/material"; +import Tooltip from "@mui/material/Tooltip"; import SkeletonLayout from "./skeleton"; import PropTypes from "prop-types"; import { useTheme } from "@emotion/react"; @@ -41,6 +42,7 @@ const DataTable = ({ data = [], config = { emptyView: "No data", + tooltipContent: null, onRowClick: () => {}, }, }) => { @@ -101,24 +103,54 @@ const DataTable = ({ data.map((row) => { const key = row.id || row._id || Math.random(); return ( - config.onRowClick(row) : null} + followCursor + enterDelay={500} + enterNextDelay={500} + title={ + typeof config.tooltipContent === "function" + ? config.tooltipContent(row) + : config.tooltipContent + } + slotProps={{ + tooltip: { + sx: { + background: "unset", + }, + }, + popper: { + modifiers: [ + { + name: "offset", + options: { + offset: ({ popper }) => { + return [popper.width / 2 + 20, -popper.height / 8]; + }, + }, + }, + ], + }, + }} > - {headers.map((header, index) => { - return ( - header.onClick(e, row) : null} - sx={header.getCellSx ? header.getCellSx(row) : {}} - > - {header.render(row)} - - ); - })} - + config.onRowClick(row) : null} + > + {headers.map((header, index) => { + return ( + header.onClick(e, row) : null} + sx={header.getCellSx ? header.getCellSx(row) : {}} + > + {header.render(row)} + + ); + })} + + ); }) )} diff --git a/client/src/Hooks/inviteHooks.js b/client/src/Hooks/inviteHooks.js index 705906efa..fb577272a 100644 --- a/client/src/Hooks/inviteHooks.js +++ b/client/src/Hooks/inviteHooks.js @@ -14,25 +14,25 @@ const useGetInviteToken = () => { const clearToken = () => { setToken(undefined); }; - + const fetchToken = async (email, role) => { + const response = await networkService.requestInvitationToken({ email, role }); + const token = response?.data?.data?.token; + if (typeof token === "undefined") { + throw new Error(t("inviteNoTokenFound")); + } + return token; + }; const getInviteToken = async ({ email, role }) => { try { - const response = await networkService.requestInvitationToken({ - email, - role, - }); - const token = response?.data?.data?.token; - if (typeof token === "undefined") { - throw new Error(t("inviteNoTokenFound")); - } - + setIsLoading(true); + const token = await fetchToken(email, role); let inviteLink = token; if (typeof CLIENT_HOST !== "undefined") { inviteLink = `${CLIENT_HOST}/register/${token}`; } - setToken(inviteLink); + return token; } catch (error) { setError(error); } finally { @@ -40,7 +40,26 @@ const useGetInviteToken = () => { } }; - return [getInviteToken, clearToken, isLoading, error, token]; + const addTeamMember = async (formData, role) => { + try { + setIsLoading(true); + const token = await fetchToken(formData.email, role); + const toSubmit = { + ...formData, + inviteToken: token, + }; + delete toSubmit.confirm; + const responseRegister = await networkService.registerUser(toSubmit); + return responseRegister; + } catch (error) { + setError(error); + throw error; + } finally { + setIsLoading(false); + } + }; + + return [getInviteToken, clearToken, isLoading, error, token, addTeamMember]; }; export { useGetInviteToken }; diff --git a/client/src/Hooks/logHooks.js b/client/src/Hooks/logHooks.js index b954efb4b..989870eea 100644 --- a/client/src/Hooks/logHooks.js +++ b/client/src/Hooks/logHooks.js @@ -15,9 +15,6 @@ const useFetchLogs = () => { setIsLoading(true); const response = await networkService.getLogs(); setLogs(response.data.data); - createToast({ - body: t("logsPage.toast.fetchLogsSuccess"), - }); } catch (error) { setError(error); createToast({ diff --git a/client/src/Pages/Account/components/AddMemberMenu/index.jsx b/client/src/Pages/Account/components/AddMemberMenu/index.jsx new file mode 100644 index 000000000..ab1ba599d --- /dev/null +++ b/client/src/Pages/Account/components/AddMemberMenu/index.jsx @@ -0,0 +1,68 @@ +import { useState } from "react"; +import Button from "@mui/material/Button"; +import Menu from "@mui/material/Menu"; +import MenuItem from "@mui/material/MenuItem"; +import { useTheme } from "@emotion/react"; +import ArrowDropDownIcon from "@mui/icons-material/ArrowDropDown"; +import { useTranslation } from "react-i18next"; +import Proptypes from "prop-types"; + +const AddMemberMenu = ({ handleInviteOpen, handleIsRegisterOpen }) => { + const [anchorEl, setAnchorEl] = useState(null); + const open = Boolean(anchorEl); + const { t } = useTranslation(); + const theme = useTheme(); + const handleClick = (event) => { + setAnchorEl(event.currentTarget); + }; + const handleClose = () => { + setAnchorEl(null); + }; + + return ( + <> + + + { + handleClose(); + handleInviteOpen(); + }} + > + {t("teamPanel.inviteTeamMember")} + + { + handleClose(); + handleIsRegisterOpen(true); + }} + > + {t("teamPanel.register")} + + + + ); +}; + +AddMemberMenu.propTypes = { + handleInviteOpen: Proptypes.func.isRequired, + handleIsRegisterOpen: Proptypes.func.isRequired, +}; + +export default AddMemberMenu; diff --git a/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx new file mode 100644 index 000000000..e42c504b2 --- /dev/null +++ b/client/src/Pages/Account/components/AddTeamMember/hooks/useAddTeamMember.jsx @@ -0,0 +1,48 @@ +import { useState } from "react"; +import { newOrChangedCredentials } from "../../../../../Validation/validation"; +import { useTranslation } from "react-i18next"; +const useAddTeamMember = () => { + const { t } = useTranslation(); + const [errors, setErrors] = useState({}); + + const clearErrors = () => setErrors({}); + + const validateFields = (name, value, formData) => { + const { error } = newOrChangedCredentials.validate( + { [name]: value }, + { abortEarly: false, context: { password: formData.password } } + ); + + setErrors((prev) => ({ + ...prev, + [name]: error?.details?.[0]?.message || "", + })); + }; + + const validateForm = (formData, role) => { + const { error } = newOrChangedCredentials.validate(formData, { + abortEarly: false, + context: { password: formData.password }, + }); + const formErrors = {}; + if (error) { + for (const err of error.details) { + formErrors[err.path[0]] = err.message; + } + } + if (!role[0] || role.length === 0) { + formErrors.role = t( + "teamPanel.registerTeamMember.auth.common.inputs.role.errors.empty" + ); + } + if (Object.keys(formErrors).length > 0) { + setErrors(formErrors); + return false; + } + setErrors({}); + return true; + }; + + return { errors, setErrors, clearErrors, validateFields, validateForm }; +}; +export default useAddTeamMember; diff --git a/client/src/Pages/Account/components/AddTeamMember/index.jsx b/client/src/Pages/Account/components/AddTeamMember/index.jsx new file mode 100644 index 000000000..2df8fb226 --- /dev/null +++ b/client/src/Pages/Account/components/AddTeamMember/index.jsx @@ -0,0 +1,213 @@ +import { Button, Stack } from "@mui/material"; +import { GenericDialog } from "../../../../Components/Dialog/genericDialog"; +import TextInput from "../../../../Components/Inputs/TextInput"; +import Select from "../../../../Components/Inputs/Select"; +import { useGetInviteToken } from "../../../../Hooks/inviteHooks"; +import { useTheme } from "@emotion/react"; +import { useTranslation } from "react-i18next"; +import { createToast } from "../../../../Utils/toastUtils"; +import { useState } from "react"; +import PasswordTooltip from "../../../Auth/components/PasswordTooltip"; +import useAddTeamMember from "./hooks/useAddTeamMember"; +import usePasswordFeedback from "../../../Auth/hooks/usePasswordFeedback"; +import { PasswordEndAdornment } from "../../../../Components/Inputs/TextInput/Adornments"; +import PropTypes from "prop-types"; + +const INITIAL_FORM_STATE = { + firstName: "", + lastName: "", + email: "", + password: "", + confirm: "", + teamId: "", +}; + +const INITIAL_ROLE_STATE = ["user"]; +const AddTeamMember = ({ handleIsRegisterOpen, isRegisterOpen, onMemberAdded }) => { + const theme = useTheme(); + const { t } = useTranslation(); + const { errors, setErrors, clearErrors, validateFields, validateForm } = + useAddTeamMember(); + const { feedback, handlePasswordFeedback } = usePasswordFeedback(); + const [getInviteToken, clearToken, isLoading, error, token, addTeamMember] = + useGetInviteToken(); + const [form, setForm] = useState(INITIAL_FORM_STATE); + const [role, setRole] = useState(INITIAL_ROLE_STATE); + const [isLoadingSubmit, setIsLoadingSubmit] = useState(false); + const closeAddMemberModal = () => { + handleIsRegisterOpen(false); + setForm(INITIAL_FORM_STATE); + setRole(INITIAL_ROLE_STATE); + clearErrors(); + clearToken(); + }; + + const onChange = (e) => { + let { name, value } = e.target; + if (name === "email") value = value.toLowerCase(); + const updatedForm = { ...form, [name]: value }; + validateFields(name, value, updatedForm); + setForm(updatedForm); + + if (name === "password" || name === "confirm") { + handlePasswordFeedback(updatedForm, name, value, form, errors, setErrors); + } + }; + + const onsubmitAddMember = async (event) => { + event.preventDefault(); + if (!validateForm(form, role)) return; + try { + setIsLoadingSubmit(true); + await addTeamMember(form, role); + createToast({ + body: t("teamPanel.registerToast.success"), + }); + onMemberAdded(); + closeAddMemberModal(); + } catch (error) { + const errorMsg = error.response?.data?.msg || error.message || "unknownError"; + createToast({ + type: "error", + body: t(errorMsg), + }); + } finally { + setIsLoadingSubmit(false); + } + }; + const tErr = (key) => (key ? t([`teamPanel.registerTeamMember.${key}`, key]) : ""); + return ( + <> + + + + + + + +