From 2e85fa9d830bdf869a75d4c365c240fd8547b1de Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 3 Apr 2025 15:30:28 +0200 Subject: [PATCH 01/33] refactor(tsx): vite.config.ts --- package.json | 2 +- vite.config.js | 14 -------------- vite.config.ts | 12 ++++++++++++ 3 files changed, 13 insertions(+), 15 deletions(-) delete mode 100644 vite.config.js create mode 100644 vite.config.ts diff --git a/package.json b/package.json index 9c8605957..50a0c2421 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "Deploy custom push protections and policies on top of Git.", "scripts": { "cli": "node ./packages/git-proxy-cli/index.js", - "client": "vite --config vite.config.js", + "client": "vite --config vite.config.ts", "clientinstall": "npm install --prefix client", "server": "tsx index.ts", "start": "concurrently \"npm run server\" \"npm run client\"", diff --git a/vite.config.js b/vite.config.js deleted file mode 100644 index 668d17b4d..000000000 --- a/vite.config.js +++ /dev/null @@ -1,14 +0,0 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -export default defineConfig(() => { - return { - build: { - outDir: 'build', - }, - server: { - port: 3000, - }, - plugins: [react()], - }; -}); diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 000000000..75a2b404e --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + build: { + outDir: 'build', + }, + server: { + port: 3000, + }, + plugins: [react()], +}); From ef2478736caa734473d69da5b29c2d8c0dd63e8e Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 3 Apr 2025 15:31:10 +0200 Subject: [PATCH 02/33] refactor(ts): update eslinrc --- .eslintrc.json | 22 +++- package-lock.json | 290 +++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 + 3 files changed, 307 insertions(+), 7 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index f2d9db8eb..fb129879f 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,5 +1,5 @@ { - "parser": "@babel/eslint-parser", + "parser": "@typescript-eslint/parser", "env": { "node": true, "browser": true, @@ -9,13 +9,23 @@ }, "extends": [ "eslint:recommended", + "plugin:@typescript-eslint/recommended", "plugin:react/recommended", "google", "prettier", "plugin:json/recommended" ], - "overrides": [], + "overrides": [ + { + "files": ["test/**/*.js", "**/*.json"], + "parser": "espree", + "rules": { + "@typescript-eslint/no-unused-expressions": "off" + } + } + ], "parserOptions": { + "project": "./tsconfig.json", "requireConfigFile": false, "ecmaVersion": 12, "sourceType": "module", @@ -27,11 +37,15 @@ "presets": ["@babel/preset-react"] } }, - "plugins": ["react", "prettier"], + "plugins": ["@typescript-eslint", "react", "prettier"], "rules": { "react/prop-types": "off", "require-jsdoc": "off", - "no-async-promise-executor": "off" + "no-async-promise-executor": "off", + "@typescript-eslint/no-explicit-any": "off", // temporary until TS refactor is complete + "@typescript-eslint/no-unused-vars": "off", // temporary until TS refactor is complete + "@typescript-eslint/no-require-imports": "off", // prevents error on old "require" imports + "@typescript-eslint/no-unused-expressions": "off" // prevents error on test "expect" expressions }, "settings": { "react": { diff --git a/package-lock.json b/package-lock.json index a9bdaad79..5264ad221 100644 --- a/package-lock.json +++ b/package-lock.json @@ -66,6 +66,8 @@ "@types/mocha": "^10.0.10", "@types/node": "^22.13.5", "@types/yargs": "^17.0.33", + "@typescript-eslint/eslint-plugin": "^8.29.0", + "@typescript-eslint/parser": "^8.29.0", "@vitejs/plugin-react": "^4.0.2", "chai": "^4.2.0", "chai-http": "^4.3.0", @@ -3719,6 +3721,226 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.29.0.tgz", + "integrity": "sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/type-utils": "8.29.0", + "@typescript-eslint/utils": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "graphemer": "^1.4.0", + "ignore": "^5.3.1", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.29.0.tgz", + "integrity": "sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.29.0.tgz", + "integrity": "sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.29.0.tgz", + "integrity": "sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "8.29.0", + "@typescript-eslint/utils": "8.29.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.29.0.tgz", + "integrity": "sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.29.0.tgz", + "integrity": "sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/visitor-keys": "8.29.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.0.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.29.0.tgz", + "integrity": "sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.29.0", + "@typescript-eslint/types": "8.29.0", + "@typescript-eslint/typescript-estree": "8.29.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.9.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.29.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.29.0.tgz", + "integrity": "sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.29.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@ungap/structured-clone": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", @@ -6687,6 +6909,34 @@ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==", "dev": true }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -7675,9 +7925,9 @@ ] }, "node_modules/ignore": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz", - "integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "engines": { "node": ">= 4" } @@ -9393,6 +9643,15 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -9401,6 +9660,19 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -12373,6 +12645,18 @@ "tree-kill": "cli.js" } }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, "node_modules/tsconfck": { "version": "3.1.5", "resolved": "https://registry.npmjs.org/tsconfck/-/tsconfck-3.1.5.tgz", diff --git a/package.json b/package.json index 50a0c2421..e2309c00b 100644 --- a/package.json +++ b/package.json @@ -84,6 +84,8 @@ "@types/mocha": "^10.0.10", "@types/node": "^22.13.5", "@types/yargs": "^17.0.33", + "@typescript-eslint/eslint-plugin": "^8.29.0", + "@typescript-eslint/parser": "^8.29.0", "@vitejs/plugin-react": "^4.0.2", "chai": "^4.2.0", "chai-http": "^4.3.0", From bc039b6d204d4e716b193b4500a551dcfcf8aef7 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 3 Apr 2025 15:35:40 +0200 Subject: [PATCH 03/33] refactor(tsx): convert index file --- index.html | 2 +- src/{index.jsx => index.tsx} | 11 ++++------- 2 files changed, 5 insertions(+), 8 deletions(-) rename src/{index.jsx => index.tsx} (56%) diff --git a/index.html b/index.html index 426501f07..32d56d7f9 100644 --- a/index.html +++ b/index.html @@ -79,5 +79,5 @@ To create a production bundle, use `npm run build` or `yarn build`. --> - + diff --git a/src/index.jsx b/src/index.tsx similarity index 56% rename from src/index.jsx rename to src/index.tsx index 4aca4983b..bdbe2b985 100644 --- a/src/index.jsx +++ b/src/index.tsx @@ -1,6 +1,5 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import { createBrowserHistory } from 'history'; import { BrowserRouter as Router, Route, Routes, Navigate } from 'react-router-dom'; // core components @@ -8,14 +7,12 @@ import Admin from './ui/layouts/Admin'; import Login from './ui/views/Login/Login'; import './ui/assets/css/material-dashboard-react.css'; -const hist = createBrowserHistory(); - ReactDOM.render( - + - } /> - } /> - } /> + } /> + } /> + } /> , document.getElementById('root'), From 27101e806c4f0ce8900afa10a730fc734fcecfd0 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 3 Apr 2025 15:39:42 +0200 Subject: [PATCH 04/33] refactor(tsx): convert routes file --- src/{routes.jsx => routes.tsx} | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) rename src/{routes.jsx => routes.tsx} (90%) diff --git a/src/routes.jsx b/src/routes.tsx similarity index 90% rename from src/routes.jsx rename to src/routes.tsx index 526b452aa..219e2c968 100644 --- a/src/routes.jsx +++ b/src/routes.tsx @@ -16,6 +16,7 @@ */ +import React from 'react'; import Person from '@material-ui/icons/Person'; import OpenPushRequests from './ui/views/OpenPushRequests/OpenPushRequests'; import PushDetails from './ui/views/PushDetails/PushDetails'; @@ -25,10 +26,18 @@ import RepoDetails from './ui/views/RepoDetails/RepoDetails'; import RepoList from './ui/views/RepoList/RepoList'; import { RepoIcon } from '@primer/octicons-react'; - import { Group, AccountCircle, Dashboard } from '@material-ui/icons'; -const dashboardRoutes = [ +interface RouteType { + path: string; + name: string; + icon: React.ComponentType; + component: React.ComponentType; + layout: string; + visible: boolean; +} + +const dashboardRoutes: RouteType[] = [ { path: '/repo', name: 'Repositories', From 012bfbbb1be268b9ddafd522a9a0ea3a1c385d12 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 3 Apr 2025 15:40:05 +0200 Subject: [PATCH 05/33] refactor(tsx): convert utils file --- src/ui/services/git-push.js | 2 +- src/ui/services/repo.js | 2 +- src/ui/services/user.js | 2 +- src/ui/{utils.jsx => utils.tsx} | 14 +++++++------- 4 files changed, 10 insertions(+), 10 deletions(-) rename src/ui/{utils.jsx => utils.tsx} (62%) diff --git a/src/ui/services/git-push.js b/src/ui/services/git-push.js index df8f6f354..8cdfeb234 100644 --- a/src/ui/services/git-push.js +++ b/src/ui/services/git-push.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { getCookie } from '../utils.jsx'; +import { getCookie } from '../utils.tsx'; const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}/api/v1` diff --git a/src/ui/services/repo.js b/src/ui/services/repo.js index 2ac0bc98d..dd0b0ec97 100644 --- a/src/ui/services/repo.js +++ b/src/ui/services/repo.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { getCookie } from '../utils.jsx'; +import { getCookie } from '../utils.tsx'; const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}/api/v1` diff --git a/src/ui/services/user.js b/src/ui/services/user.js index 04a2fdccb..37a84a1b9 100644 --- a/src/ui/services/user.js +++ b/src/ui/services/user.js @@ -1,5 +1,5 @@ import axios from 'axios'; -import { getCookie } from '../utils.jsx'; +import { getCookie } from '../utils.tsx'; const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}` diff --git a/src/ui/utils.jsx b/src/ui/utils.tsx similarity index 62% rename from src/ui/utils.jsx rename to src/ui/utils.tsx index 48a37f7f9..be15a19ef 100644 --- a/src/ui/utils.jsx +++ b/src/ui/utils.tsx @@ -1,17 +1,17 @@ /** * Retrieve a decoded cookie value from `document.cookie` with given `name`. - * @param {string} name - * @return {string} + * @param {string} name - The name of the cookie to retrieve + * @return {string | null} - The cookie value or null if not found */ -export const getCookie = (name) => { +export const getCookie = (name: string): string | null => { if (!document.cookie) return null; - + const cookies = document.cookie .split(';') .map((c) => c.trim()) .filter((c) => c.startsWith(name + '=')); - + if (!cookies.length) return null; - + return decodeURIComponent(cookies[0].split('=')[1]); -}; +}; \ No newline at end of file From 2038426349622b2f9f1a3a408f77dbb6fe055ef0 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 3 Apr 2025 15:40:23 +0200 Subject: [PATCH 06/33] refactor(tsx): convert GridItem component --- src/ui/components/Grid/{GridItem.jsx => GridItem.tsx} | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) rename src/ui/components/Grid/{GridItem.jsx => GridItem.tsx} (65%) diff --git a/src/ui/components/Grid/GridItem.jsx b/src/ui/components/Grid/GridItem.tsx similarity index 65% rename from src/ui/components/Grid/GridItem.jsx rename to src/ui/components/Grid/GridItem.tsx index 29cc2b269..2165e2072 100644 --- a/src/ui/components/Grid/GridItem.jsx +++ b/src/ui/components/Grid/GridItem.tsx @@ -1,7 +1,7 @@ -import React from 'react'; +import React, { ReactNode } from 'react'; import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; -import Grid from '@material-ui/core/Grid'; +import Grid, { GridProps } from '@material-ui/core/Grid'; const styles = { grid: { @@ -11,7 +11,11 @@ const styles = { const useStyles = makeStyles(styles); -export default function GridItem(props) { +interface GridItemProps extends GridProps { + children?: ReactNode; +} + +export default function GridItem(props: GridItemProps) { const classes = useStyles(); const { children, ...rest } = props; return ( From c3a01cd87a382244ee79d7699f1f09539f5f5a6a Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Wed, 9 Apr 2025 15:09:29 +0200 Subject: [PATCH 07/33] refactor(tsx): convert GridContainer component --- .../Grid/{GridContainer.jsx => GridContainer.tsx} | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) rename src/ui/components/Grid/{GridContainer.jsx => GridContainer.tsx} (68%) diff --git a/src/ui/components/Grid/GridContainer.jsx b/src/ui/components/Grid/GridContainer.tsx similarity index 68% rename from src/ui/components/Grid/GridContainer.jsx rename to src/ui/components/Grid/GridContainer.tsx index e67c15aef..5ee7f7521 100644 --- a/src/ui/components/Grid/GridContainer.jsx +++ b/src/ui/components/Grid/GridContainer.tsx @@ -1,7 +1,7 @@ import React from 'react'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import Grid from '@material-ui/core/Grid'; +import { GridProps } from '@material-ui/core/Grid'; const styles = { grid: { @@ -12,7 +12,11 @@ const styles = { const useStyles = makeStyles(styles); -export default function GridContainer(props) { +interface GridContainerProps extends GridProps { + children?: React.ReactNode; +} + +export default function GridContainer(props: GridContainerProps) { const classes = useStyles(); const { children, ...rest } = props; return ( @@ -20,8 +24,4 @@ export default function GridContainer(props) { {children} ); -} - -GridContainer.propTypes = { - children: PropTypes.node, -}; +} \ No newline at end of file From 48cae4e91e0ac610f6aff0f51217698b0b8843e8 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Wed, 9 Apr 2025 16:03:44 +0200 Subject: [PATCH 08/33] refactor(tsx): convert Buttons components --- .../{buttonStyle.js => buttonStyle.ts} | 182 ++++++------------ .../CustomButtons/{Button.jsx => Button.tsx} | 66 ++++--- ...eActionButton.jsx => CodeActionButton.tsx} | 19 +- 3 files changed, 109 insertions(+), 158 deletions(-) rename src/ui/assets/jss/material-dashboard-react/components/{buttonStyle.js => buttonStyle.ts} (54%) rename src/ui/components/CustomButtons/{Button.jsx => Button.tsx} (51%) rename src/ui/components/CustomButtons/{CodeActionButton.jsx => CodeActionButton.tsx} (84%) diff --git a/src/ui/assets/jss/material-dashboard-react/components/buttonStyle.js b/src/ui/assets/jss/material-dashboard-react/components/buttonStyle.ts similarity index 54% rename from src/ui/assets/jss/material-dashboard-react/components/buttonStyle.js rename to src/ui/assets/jss/material-dashboard-react/components/buttonStyle.ts index d9e0e39c9..138fb234d 100644 --- a/src/ui/assets/jss/material-dashboard-react/components/buttonStyle.js +++ b/src/ui/assets/jss/material-dashboard-react/components/buttonStyle.ts @@ -1,3 +1,4 @@ +import { createStyles, Theme } from '@material-ui/core/styles'; import { grayColor, primaryColor, @@ -9,76 +10,67 @@ import { whiteColor, blackColor, hexToRgb, -} from '../../material-dashboard-react.js'; +} from '../../material-dashboard-react'; -const buttonStyle = { +const buttonStyle = (theme: Theme) => createStyles({ button: { minHeight: 'auto', minWidth: 'auto', backgroundColor: grayColor[0], color: whiteColor, boxShadow: - '0 2px 2px 0 rgba(' + - hexToRgb(grayColor[0]) + - ', 0.14), 0 3px 1px -2px rgba(' + - hexToRgb(grayColor[0]) + - ', 0.2), 0 1px 5px 0 rgba(' + - hexToRgb(grayColor[0]) + - ', 0.12)', + `0 2px 2px 0 rgba(${hexToRgb(grayColor[0])}, 0.14), + 0 3px 1px -2px rgba(${hexToRgb(grayColor[0])}, 0.2), + 0 1px 5px 0 rgba(${hexToRgb(grayColor[0])}, 0.12)`, border: 'none', borderRadius: '3px', - position: 'relative', + position: 'relative' as const, padding: '12px 30px', margin: '.3125rem 1px', fontSize: '12px', - fontWeight: '400', - textTransform: 'uppercase', + fontWeight: 400 as const, + textTransform: 'uppercase' as const, letterSpacing: '0', willChange: 'box-shadow, transform', transition: - // eslint-disable-next-line max-len 'box-shadow 0.2s cubic-bezier(0.4, 0, 1, 1), background-color 0.2s cubic-bezier(0.4, 0, 0.2, 1)', lineHeight: '1.42857143', - textAlign: 'center', - whiteSpace: 'nowrap', - verticalAlign: 'middle', - touchAction: 'manipulation', - cursor: 'pointer', + textAlign: 'center' as const, + whiteSpace: 'nowrap' as const, + verticalAlign: 'middle' as const, + touchAction: 'manipulation' as const, + cursor: 'pointer' as const, '&:hover,&:focus': { color: whiteColor, backgroundColor: grayColor[0], boxShadow: - '0 14px 26px -12px rgba(' + - hexToRgb(grayColor[0]) + - ', 0.42), 0 4px 23px 0px rgba(' + - hexToRgb(blackColor) + - ', 0.12), 0 8px 10px -5px rgba(' + - hexToRgb(grayColor[0]) + - ', 0.2)', + `0 14px 26px -12px rgba(${hexToRgb(grayColor[0])}, 0.42), + 0 4px 23px 0px rgba(${hexToRgb(blackColor)}, 0.12), + 0 8px 10px -5px rgba(${hexToRgb(grayColor[0])}, 0.2)`, }, '& .fab,& .fas,& .far,& .fal, &.material-icons': { - position: 'relative', - display: 'inline-block', + position: 'relative' as const, + display: 'inline-block' as const, top: '0', marginTop: '-1em', marginBottom: '-1em', fontSize: '1.1rem', marginRight: '4px', - verticalAlign: 'middle', + verticalAlign: 'middle' as const, }, '& svg': { - position: 'relative', - display: 'inline-block', + position: 'relative' as const, + display: 'inline-block' as const, top: '0', width: '18px', height: '18px', marginRight: '4px', - verticalAlign: 'middle', + verticalAlign: 'middle' as const, }, '&$justIcon': { '& .fab,& .fas,& .far,& .fal,& .material-icons': { marginTop: '0px', - position: 'absolute', + position: 'absolute' as const, width: '100%', transform: 'none', left: '0px', @@ -98,133 +90,85 @@ const buttonStyle = { rose: { backgroundColor: roseColor[0], boxShadow: - '0 2px 2px 0 rgba(' + - hexToRgb(roseColor[0]) + - ', 0.14), 0 3px 1px -2px rgba(' + - hexToRgb(roseColor[0]) + - ', 0.2), 0 1px 5px 0 rgba(' + - hexToRgb(roseColor[0]) + - ', 0.12)', + `0 2px 2px 0 rgba(${hexToRgb(roseColor[0])}, 0.14), + 0 3px 1px -2px rgba(${hexToRgb(roseColor[0])}, 0.2), + 0 1px 5px 0 rgba(${hexToRgb(roseColor[0])}, 0.12)`, '&:hover,&:focus': { backgroundColor: roseColor[0], boxShadow: - '0 14px 26px -12px rgba(' + - hexToRgb(roseColor[0]) + - ', 0.42), 0 4px 23px 0px rgba(' + - hexToRgb(blackColor) + - ', 0.12), 0 8px 10px -5px rgba(' + - hexToRgb(roseColor[0]) + - ', 0.2)', + `0 14px 26px -12px rgba(${hexToRgb(roseColor[0])}, 0.42), + 0 4px 23px 0px rgba(${hexToRgb(blackColor)}, 0.12), + 0 8px 10px -5px rgba(${hexToRgb(roseColor[0])}, 0.2)`, }, }, primary: { backgroundColor: primaryColor[0], boxShadow: - '0 2px 2px 0 rgba(' + - hexToRgb(primaryColor[0]) + - ', 0.14), 0 3px 1px -2px rgba(' + - hexToRgb(primaryColor[0]) + - ', 0.2), 0 1px 5px 0 rgba(' + - hexToRgb(primaryColor[0]) + - ', 0.12)', + `0 2px 2px 0 rgba(${hexToRgb(primaryColor[0])}, 0.14), + 0 3px 1px -2px rgba(${hexToRgb(primaryColor[0])}, 0.2), + 0 1px 5px 0 rgba(${hexToRgb(primaryColor[0])}, 0.12)`, '&:hover,&:focus': { backgroundColor: primaryColor[0], boxShadow: - '0 14px 26px -12px rgba(' + - hexToRgb(primaryColor[0]) + - ', 0.42), 0 4px 23px 0px rgba(' + - hexToRgb(blackColor) + - ', 0.12), 0 8px 10px -5px rgba(' + - hexToRgb(primaryColor[0]) + - ', 0.2)', + `0 14px 26px -12px rgba(${hexToRgb(primaryColor[0])}, 0.42), + 0 4px 23px 0px rgba(${hexToRgb(blackColor)}, 0.12), + 0 8px 10px -5px rgba(${hexToRgb(primaryColor[0])}, 0.2)`, }, }, info: { backgroundColor: infoColor[0], boxShadow: - '0 2px 2px 0 rgba(' + - hexToRgb(infoColor[0]) + - ', 0.14), 0 3px 1px -2px rgba(' + - hexToRgb(infoColor[0]) + - ', 0.2), 0 1px 5px 0 rgba(' + - hexToRgb(infoColor[0]) + - ', 0.12)', + `0 2px 2px 0 rgba(${hexToRgb(infoColor[0])}, 0.14), + 0 3px 1px -2px rgba(${hexToRgb(infoColor[0])}, 0.2), + 0 1px 5px 0 rgba(${hexToRgb(infoColor[0])}, 0.12)`, '&:hover,&:focus': { backgroundColor: infoColor[0], boxShadow: - '0 14px 26px -12px rgba(' + - hexToRgb(infoColor[0]) + - ', 0.42), 0 4px 23px 0px rgba(' + - hexToRgb(blackColor) + - ', 0.12), 0 8px 10px -5px rgba(' + - hexToRgb(infoColor[0]) + - ', 0.2)', + `0 14px 26px -12px rgba(${hexToRgb(infoColor[0])}, 0.42), + 0 4px 23px 0px rgba(${hexToRgb(blackColor)}, 0.12), + 0 8px 10px -5px rgba(${hexToRgb(infoColor[0])}, 0.2)`, }, }, success: { backgroundColor: successColor[0], boxShadow: - '0 2px 2px 0 rgba(' + - hexToRgb(successColor[0]) + - ', 0.14), 0 3px 1px -2px rgba(' + - hexToRgb(successColor[0]) + - ', 0.2), 0 1px 5px 0 rgba(' + - hexToRgb(successColor[0]) + - ', 0.12)', + `0 2px 2px 0 rgba(${hexToRgb(successColor[0])}, 0.14), + 0 3px 1px -2px rgba(${hexToRgb(successColor[0])}, 0.2), + 0 1px 5px 0 rgba(${hexToRgb(successColor[0])}, 0.12)`, '&:hover,&:focus': { backgroundColor: successColor[0], boxShadow: - '0 14px 26px -12px rgba(' + - hexToRgb(successColor[0]) + - ', 0.42), 0 4px 23px 0px rgba(' + - hexToRgb(blackColor) + - ', 0.12), 0 8px 10px -5px rgba(' + - hexToRgb(successColor[0]) + - ', 0.2)', + `0 14px 26px -12px rgba(${hexToRgb(successColor[0])}, 0.42), + 0 4px 23px 0px rgba(${hexToRgb(blackColor)}, 0.12), + 0 8px 10px -5px rgba(${hexToRgb(successColor[0])}, 0.2)`, }, }, warning: { backgroundColor: warningColor[0], boxShadow: - '0 2px 2px 0 rgba(' + - hexToRgb(warningColor[0]) + - ', 0.14), 0 3px 1px -2px rgba(' + - hexToRgb(warningColor[0]) + - ', 0.2), 0 1px 5px 0 rgba(' + - hexToRgb(warningColor[0]) + - ', 0.12)', + `0 2px 2px 0 rgba(${hexToRgb(warningColor[0])}, 0.14), + 0 3px 1px -2px rgba(${hexToRgb(warningColor[0])}, 0.2), + 0 1px 5px 0 rgba(${hexToRgb(warningColor[0])}, 0.12)`, '&:hover,&:focus': { backgroundColor: warningColor[0], boxShadow: - '0 14px 26px -12px rgba(' + - hexToRgb(warningColor[0]) + - ', 0.42), 0 4px 23px 0px rgba(' + - hexToRgb(blackColor) + - ', 0.12), 0 8px 10px -5px rgba(' + - hexToRgb(warningColor[0]) + - ', 0.2)', + `0 14px 26px -12px rgba(${hexToRgb(warningColor[0])}, 0.42), + 0 4px 23px 0px rgba(${hexToRgb(blackColor)}, 0.12), + 0 8px 10px -5px rgba(${hexToRgb(warningColor[0])}, 0.2)`, }, }, danger: { backgroundColor: dangerColor[0], boxShadow: - '0 2px 2px 0 rgba(' + - hexToRgb(dangerColor[0]) + - ', 0.14), 0 3px 1px -2px rgba(' + - hexToRgb(dangerColor[0]) + - ', 0.2), 0 1px 5px 0 rgba(' + - hexToRgb(dangerColor[0]) + - ', 0.12)', + `0 2px 2px 0 rgba(${hexToRgb(dangerColor[0])}, 0.14), + 0 3px 1px -2px rgba(${hexToRgb(dangerColor[0])}, 0.2), + 0 1px 5px 0 rgba(${hexToRgb(dangerColor[0])}, 0.12)`, '&:hover,&:focus': { backgroundColor: dangerColor[0], boxShadow: - '0 14px 26px -12px rgba(' + - hexToRgb(dangerColor[0]) + - ', 0.42), 0 4px 23px 0px rgba(' + - hexToRgb(blackColor) + - ', 0.12), 0 8px 10px -5px rgba(' + - hexToRgb(dangerColor[0]) + - ', 0.2)', + `0 14px 26px -12px rgba(${hexToRgb(dangerColor[0])}, 0.42), + 0 4px 23px 0px rgba(${hexToRgb(blackColor)}, 0.12), + 0 8px 10px -5px rgba(${hexToRgb(dangerColor[0])}, 0.2)`, }, }, simple: { @@ -273,7 +217,7 @@ const buttonStyle = { }, disabled: { opacity: '0.65', - pointerEvents: 'none', + pointerEvents: 'none' as const, }, lg: { padding: '1.125rem 2.25rem', @@ -338,6 +282,6 @@ const buttonStyle = { }, }, }, -}; +}); -export default buttonStyle; +export default buttonStyle; \ No newline at end of file diff --git a/src/ui/components/CustomButtons/Button.jsx b/src/ui/components/CustomButtons/Button.tsx similarity index 51% rename from src/ui/components/CustomButtons/Button.jsx rename to src/ui/components/CustomButtons/Button.tsx index 81090c191..cadb4e140 100644 --- a/src/ui/components/CustomButtons/Button.jsx +++ b/src/ui/components/CustomButtons/Button.tsx @@ -1,21 +1,45 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; -import Button from '@material-ui/core/Button'; +import Button, { ButtonProps } from '@material-ui/core/Button'; import styles from '../../assets/jss/material-dashboard-react/components/buttonStyle'; const useStyles = makeStyles(styles); -export default function RegularButton(props) { +type Color = + | 'primary' + | 'info' + | 'success' + | 'warning' + | 'danger' + | 'rose' + | 'white' + | 'transparent'; +type Size = 'sm' | 'lg'; + +interface RegularButtonProps extends ButtonProps { + customColor?: Color; + round?: boolean; + disabled?: boolean; + simple?: boolean; + customSize?: Size; + block?: boolean; + link?: boolean; + justIcon?: boolean; + className?: string; + muiClasses?: Record; + children?: React.ReactNode; +} + +export default function RegularButton(props: RegularButtonProps) { const classes = useStyles(); const { - color, + customColor: color, round, children, disabled, simple, - size, + customSize: size, block, link, justIcon, @@ -23,45 +47,23 @@ export default function RegularButton(props) { muiClasses, ...rest } = props; + const btnClasses = classNames({ [classes.button]: true, - [classes[size]]: size, - [classes[color]]: color, + [size ? classes[size] : '']: size, + [color ? classes[color] : '']: color, [classes.round]: round, [classes.disabled]: disabled, [classes.simple]: simple, [classes.block]: block, [classes.link]: link, [classes.justIcon]: justIcon, - [className]: className, + [className || '']: className, }); + return ( ); } - -RegularButton.propTypes = { - color: PropTypes.oneOf([ - 'primary', - 'info', - 'success', - 'warning', - 'danger', - 'rose', - 'white', - 'transparent', - ]), - size: PropTypes.oneOf(['sm', 'lg']), - simple: PropTypes.bool, - round: PropTypes.bool, - disabled: PropTypes.bool, - block: PropTypes.bool, - link: PropTypes.bool, - justIcon: PropTypes.bool, - className: PropTypes.string, - // use this to pass the classes props from Material-UI - muiClasses: PropTypes.object, - children: PropTypes.node, -}; diff --git a/src/ui/components/CustomButtons/CodeActionButton.jsx b/src/ui/components/CustomButtons/CodeActionButton.tsx similarity index 84% rename from src/ui/components/CustomButtons/CodeActionButton.jsx rename to src/ui/components/CustomButtons/CodeActionButton.tsx index 68c796316..d5ce26eeb 100644 --- a/src/ui/components/CustomButtons/CodeActionButton.jsx +++ b/src/ui/components/CustomButtons/CodeActionButton.tsx @@ -8,14 +8,19 @@ import { TerminalIcon, } from '@primer/octicons-react'; import React, { useState } from 'react'; +import { PopperPlacementType } from '@material-ui/core/Popper'; -const CodeActionButton = ({ cloneURL }) => { - const [anchorEl, setAnchorEl] = useState(null); - const [open, setOpen] = useState(false); - const [placement, setPlacement] = useState(); - const [isCopied, setIsCopied] = useState(false); +interface CodeActionButtonProps { + cloneURL: string; +} - const handleClick = (newPlacement) => (event) => { +const CodeActionButton: React.FC = ({ cloneURL }) => { + const [anchorEl, setAnchorEl] = useState(null); + const [open, setOpen] = useState(false); + const [placement, setPlacement] = useState(); + const [isCopied, setIsCopied] = useState(false); + + const handleClick = (newPlacement: PopperPlacementType) => (event: React.MouseEvent) => { setIsCopied(false); setAnchorEl(event.currentTarget); setOpen((prev) => placement !== newPlacement || !prev); @@ -114,4 +119,4 @@ const CodeActionButton = ({ cloneURL }) => { ); }; -export default CodeActionButton; +export default CodeActionButton; \ No newline at end of file From 1efa5046294680f4244b42ece09118e79bbdfe57 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 10 Apr 2025 12:32:25 +0200 Subject: [PATCH 09/33] refactor(tsx): convert Snackbar components --- .../components/snackbarContentStyle.js | 131 ------------------ .../components/snackbarContentStyle.ts | 129 +++++++++++++++++ .../Snackbar/{Snackbar.jsx => Snackbar.tsx} | 54 ++++---- ...nackbarContent.jsx => SnackbarContent.tsx} | 42 +++--- 4 files changed, 183 insertions(+), 173 deletions(-) delete mode 100644 src/ui/assets/jss/material-dashboard-react/components/snackbarContentStyle.js create mode 100644 src/ui/assets/jss/material-dashboard-react/components/snackbarContentStyle.ts rename src/ui/components/Snackbar/{Snackbar.jsx => Snackbar.tsx} (55%) rename src/ui/components/Snackbar/{SnackbarContent.jsx => SnackbarContent.tsx} (55%) diff --git a/src/ui/assets/jss/material-dashboard-react/components/snackbarContentStyle.js b/src/ui/assets/jss/material-dashboard-react/components/snackbarContentStyle.js deleted file mode 100644 index 923e439c7..000000000 --- a/src/ui/assets/jss/material-dashboard-react/components/snackbarContentStyle.js +++ /dev/null @@ -1,131 +0,0 @@ -import { - defaultFont, - primaryBoxShadow, - infoBoxShadow, - successBoxShadow, - warningBoxShadow, - dangerBoxShadow, - roseBoxShadow, - whiteColor, - blackColor, - grayColor, - infoColor, - successColor, - dangerColor, - roseColor, - primaryColor, - warningColor, - hexToRgb, -} from '../../material-dashboard-react.js'; - -const snackbarContentStyle = { - root: { - ...defaultFont, - flexWrap: 'unset', - position: 'relative', - padding: '20px 15px', - lineHeight: '20px', - marginBottom: '20px', - fontSize: '14px', - backgroundColor: whiteColor, - color: grayColor[7], - borderRadius: '3px', - minWidth: 'unset', - maxWidth: 'unset', - boxShadow: - '0 12px 20px -10px rgba(' + - hexToRgb(whiteColor) + - ', 0.28), 0 4px 20px 0px rgba(' + - hexToRgb(blackColor) + - ', 0.12), 0 7px 8px -5px rgba(' + - hexToRgb(whiteColor) + - ', 0.2)', - }, - top20: { - top: '20px', - }, - top40: { - top: '40px', - }, - info: { - backgroundColor: infoColor[3], - color: whiteColor, - ...infoBoxShadow, - }, - success: { - backgroundColor: successColor[3], - color: whiteColor, - ...successBoxShadow, - }, - warning: { - backgroundColor: warningColor[3], - color: whiteColor, - ...warningBoxShadow, - }, - danger: { - backgroundColor: dangerColor[3], - color: whiteColor, - ...dangerBoxShadow, - }, - primary: { - backgroundColor: primaryColor[3], - color: whiteColor, - ...primaryBoxShadow, - }, - rose: { - backgroundColor: roseColor[3], - color: whiteColor, - ...roseBoxShadow, - }, - message: { - padding: '0', - display: 'block', - maxWidth: '89%', - }, - close: { - width: '11px', - height: '11px', - }, - iconButton: { - width: '24px', - height: '24px', - padding: '0px', - }, - icon: { - display: 'block', - left: '15px', - position: 'absolute', - top: '50%', - marginTop: '-15px', - width: '30px', - height: '30px', - }, - infoIcon: { - color: infoColor[3], - }, - successIcon: { - color: successColor[3], - }, - warningIcon: { - color: warningColor[3], - }, - dangerIcon: { - color: dangerColor[3], - }, - primaryIcon: { - color: primaryColor[3], - }, - roseIcon: { - color: roseColor[3], - }, - iconMessage: { - paddingLeft: '50px', - display: 'block', - }, - actionRTL: { - marginLeft: '-8px', - marginRight: 'auto', - }, -}; - -export default snackbarContentStyle; diff --git a/src/ui/assets/jss/material-dashboard-react/components/snackbarContentStyle.ts b/src/ui/assets/jss/material-dashboard-react/components/snackbarContentStyle.ts new file mode 100644 index 000000000..812191688 --- /dev/null +++ b/src/ui/assets/jss/material-dashboard-react/components/snackbarContentStyle.ts @@ -0,0 +1,129 @@ +import { createStyles, Theme } from '@material-ui/core/styles'; +import { + defaultFont, + primaryBoxShadow, + infoBoxShadow, + successBoxShadow, + warningBoxShadow, + dangerBoxShadow, + roseBoxShadow, + whiteColor, + blackColor, + grayColor, + infoColor, + successColor, + dangerColor, + roseColor, + primaryColor, + warningColor, + hexToRgb, +} from '../../material-dashboard-react'; + +const snackbarContentStyle = (theme: Theme) => + createStyles({ + root: { + ...defaultFont, + flexWrap: 'unset', + position: 'relative' as const, + padding: '20px 15px', + lineHeight: '20px', + marginBottom: '20px', + fontSize: '14px', + backgroundColor: whiteColor, + color: grayColor[7], + borderRadius: '3px', + minWidth: 'unset', + maxWidth: 'unset', + boxShadow: `0 12px 20px -10px rgba(${hexToRgb(whiteColor)}, 0.28), + 0 4px 20px 0px rgba(${hexToRgb(blackColor)}, 0.12), + 0 7px 8px -5px rgba(${hexToRgb(whiteColor)}, 0.2)`, + fontWeight: 400, + }, + top20: { + top: '20px', + }, + top40: { + top: '40px', + }, + info: { + backgroundColor: infoColor[3], + color: whiteColor, + ...infoBoxShadow, + }, + success: { + backgroundColor: successColor[3], + color: whiteColor, + ...successBoxShadow, + }, + warning: { + backgroundColor: warningColor[3], + color: whiteColor, + ...warningBoxShadow, + }, + danger: { + backgroundColor: dangerColor[3], + color: whiteColor, + ...dangerBoxShadow, + }, + primary: { + backgroundColor: primaryColor[3], + color: whiteColor, + ...primaryBoxShadow, + }, + rose: { + backgroundColor: roseColor[3], + color: whiteColor, + ...roseBoxShadow, + }, + message: { + padding: '0', + display: 'block' as const, + maxWidth: '89%', + }, + close: { + width: '11px', + height: '11px', + }, + iconButton: { + width: '24px', + height: '24px', + padding: '0px', + }, + icon: { + display: 'block' as const, + left: '15px', + position: 'absolute' as const, + top: '50%', + marginTop: '-15px', + width: '30px', + height: '30px', + }, + infoIcon: { + color: infoColor[3], + }, + successIcon: { + color: successColor[3], + }, + warningIcon: { + color: warningColor[3], + }, + dangerIcon: { + color: dangerColor[3], + }, + primaryIcon: { + color: primaryColor[3], + }, + roseIcon: { + color: roseColor[3], + }, + iconMessage: { + paddingLeft: '50px', + display: 'block' as const, + }, + actionRTL: { + marginLeft: '-8px', + marginRight: 'auto', + }, + }); + +export default snackbarContentStyle; diff --git a/src/ui/components/Snackbar/Snackbar.jsx b/src/ui/components/Snackbar/Snackbar.tsx similarity index 55% rename from src/ui/components/Snackbar/Snackbar.jsx rename to src/ui/components/Snackbar/Snackbar.tsx index 2d09372a7..13dc6679d 100644 --- a/src/ui/components/Snackbar/Snackbar.jsx +++ b/src/ui/components/Snackbar/Snackbar.tsx @@ -1,6 +1,5 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import Snack from '@material-ui/core/Snackbar'; import IconButton from '@material-ui/core/IconButton'; @@ -9,31 +8,47 @@ import styles from '../../assets/jss/material-dashboard-react/components/snackba const useStyles = makeStyles(styles); -export default function Snackbar(props) { +type Color = 'info' | 'success' | 'warning' | 'danger' | 'primary'; +type Placement = 'tl' | 'tr' | 'tc' | 'br' | 'bl' | 'bc'; + +interface SnackbarProps { + message: React.ReactNode; + color?: Color; + close?: boolean; + icon?: React.ComponentType<{ className: string }>; + place?: Placement; + open: boolean; + rtlActive?: boolean; + closeNotification: () => void; +} + +const Snackbar: React.FC = (props) => { const classes = useStyles(); - const { message, color, close, icon, place, open, rtlActive } = props; - let action = []; + const { message, color = 'info', close, icon: Icon, place = 'tr', open, rtlActive } = props; + + let action: React.ReactNode[] = []; const messageClasses = classNames({ - [classes.iconMessage]: icon !== undefined, + [classes.iconMessage]: Icon !== undefined, }); - if (close !== undefined) { + + if (close) { action = [ props.closeNotification()} + onClick={props.closeNotification} > , ]; } - const calculateHorizontal = () => { - if (place.indexOf('l') !== -1) { + const calculateHorizontal = (): 'left' | 'center' | 'right' => { + if (place.includes('l')) { return 'left'; - } else if (place.indexOf('c') !== -1) { + } else if (place.includes('c')) { return 'center'; } return 'right'; @@ -42,35 +57,26 @@ export default function Snackbar(props) { return ( - {icon !== undefined ? : null} + {Icon && } {message} } action={action} ContentProps={{ classes: { - root: classes.root + ' ' + classes[color], + root: `${classes.root} ${classes[color]}`, message: classes.message, action: classNames({ [classes.actionRTL]: rtlActive }), }, }} /> ); -} - -Snackbar.propTypes = { - message: PropTypes.node.isRequired, - color: PropTypes.oneOf(['info', 'success', 'warning', 'danger', 'primary']), - close: PropTypes.bool, - icon: PropTypes.object, - place: PropTypes.oneOf(['tl', 'tr', 'tc', 'br', 'bl', 'bc']), - open: PropTypes.bool, - rtlActive: PropTypes.bool, - closeNotification: PropTypes.func, }; + +export default Snackbar; diff --git a/src/ui/components/Snackbar/SnackbarContent.jsx b/src/ui/components/Snackbar/SnackbarContent.tsx similarity index 55% rename from src/ui/components/Snackbar/SnackbarContent.jsx rename to src/ui/components/Snackbar/SnackbarContent.tsx index cd7bf0fe3..76e3c12b8 100644 --- a/src/ui/components/Snackbar/SnackbarContent.jsx +++ b/src/ui/components/Snackbar/SnackbarContent.tsx @@ -1,50 +1,56 @@ import React from 'react'; -import PropTypes from 'prop-types'; import classNames from 'classnames'; import { makeStyles } from '@material-ui/core/styles'; -import Snack from '@material-ui/core/SnackbarContent'; +import MuiSnackbarContent from '@material-ui/core/SnackbarContent'; import IconButton from '@material-ui/core/IconButton'; import Close from '@material-ui/icons/Close'; import styles from '../../assets/jss/material-dashboard-react/components/snackbarContentStyle'; const useStyles = makeStyles(styles); -export default function SnackbarContent(props) { +type Color = 'info' | 'success' | 'warning' | 'danger' | 'primary'; + +interface SnackbarContentProps { + message: React.ReactNode; + color?: Color; + close?: boolean; + icon?: React.ComponentType<{ className: string }>; + rtlActive?: boolean; +} + +const SnackbarContent: React.FC = (props) => { const classes = useStyles(); - const { message, color, close, icon, rtlActive } = props; - let action = []; + const { message, color = 'info', close, icon: Icon, rtlActive } = props; + + let action: React.ReactNode[] = []; const messageClasses = classNames({ - [classes.iconMessage]: icon !== undefined, + [classes.iconMessage]: Icon !== undefined, }); - if (close !== undefined) { + + if (close) { action = [ , ]; } + return ( - - {icon !== undefined ? : null} + {Icon && } {message} } classes={{ - root: classes.root + ' ' + classes[color], + root: `${classes.root} ${classes[color]}`, message: classes.message, action: classNames({ [classes.actionRTL]: rtlActive }), }} action={action} /> ); -} - -SnackbarContent.propTypes = { - message: PropTypes.node.isRequired, - color: PropTypes.oneOf(['info', 'success', 'warning', 'danger', 'primary']), - close: PropTypes.bool, - icon: PropTypes.object, - rtlActive: PropTypes.bool, }; + +export default SnackbarContent; From 0c12d1a1bcff4cd1ebb689bc83ce38a5501f5cc6 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 10 Apr 2025 12:32:56 +0200 Subject: [PATCH 10/33] refactor(tsx): fix button issues with colors --- src/ui/components/CustomButtons/Button.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/components/CustomButtons/Button.tsx b/src/ui/components/CustomButtons/Button.tsx index cadb4e140..473a3caca 100644 --- a/src/ui/components/CustomButtons/Button.tsx +++ b/src/ui/components/CustomButtons/Button.tsx @@ -34,12 +34,12 @@ interface RegularButtonProps extends ButtonProps { export default function RegularButton(props: RegularButtonProps) { const classes = useStyles(); const { - customColor: color, + color, round, children, disabled, simple, - customSize: size, + size, block, link, justIcon, @@ -50,8 +50,8 @@ export default function RegularButton(props: RegularButtonProps) { const btnClasses = classNames({ [classes.button]: true, - [size ? classes[size] : '']: size, - [color ? classes[color] : '']: color, + [classes[size as Size]]: size, + [classes[color as Color]]: color, [classes.round]: round, [classes.disabled]: disabled, [classes.simple]: simple, From 9a82ce69ee81320e06362ac3cc737b408c74e4d7 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Fri, 11 Apr 2025 11:33:04 +0200 Subject: [PATCH 11/33] refactor(tsx): convert Card components --- .../{cardBodyStyle.js => cardBodyStyle.ts} | 10 +- .../components/cardHeaderStyle.js | 127 ----------------- .../components/cardHeaderStyle.ts | 129 ++++++++++++++++++ .../components/{cardStyle.js => cardStyle.ts} | 23 ++-- src/ui/components/Card/{Card.jsx => Card.tsx} | 33 +++-- .../Card/{CardAvatar.jsx => CardAvatar.tsx} | 30 ++-- .../Card/{CardBody.jsx => CardBody.tsx} | 30 ++-- .../Card/{CardFooter.jsx => CardFooter.tsx} | 36 +++-- .../Card/{CardHeader.jsx => CardHeader.tsx} | 31 +++-- .../Card/{CardIcon.jsx => CardIcon.tsx} | 26 ++-- 10 files changed, 263 insertions(+), 212 deletions(-) rename src/ui/assets/jss/material-dashboard-react/components/{cardBodyStyle.js => cardBodyStyle.ts} (59%) delete mode 100644 src/ui/assets/jss/material-dashboard-react/components/cardHeaderStyle.js create mode 100644 src/ui/assets/jss/material-dashboard-react/components/cardHeaderStyle.ts rename src/ui/assets/jss/material-dashboard-react/components/{cardStyle.js => cardStyle.ts} (52%) rename src/ui/components/Card/{Card.jsx => Card.tsx} (59%) rename src/ui/components/Card/{CardAvatar.jsx => CardAvatar.tsx} (60%) rename src/ui/components/Card/{CardBody.jsx => CardBody.tsx} (60%) rename src/ui/components/Card/{CardFooter.jsx => CardFooter.tsx} (60%) rename src/ui/components/Card/{CardHeader.jsx => CardHeader.tsx} (57%) rename src/ui/components/Card/{CardIcon.jsx => CardIcon.tsx} (56%) diff --git a/src/ui/assets/jss/material-dashboard-react/components/cardBodyStyle.js b/src/ui/assets/jss/material-dashboard-react/components/cardBodyStyle.ts similarity index 59% rename from src/ui/assets/jss/material-dashboard-react/components/cardBodyStyle.js rename to src/ui/assets/jss/material-dashboard-react/components/cardBodyStyle.ts index ec5f58a07..0667c856a 100644 --- a/src/ui/assets/jss/material-dashboard-react/components/cardBodyStyle.js +++ b/src/ui/assets/jss/material-dashboard-react/components/cardBodyStyle.ts @@ -1,9 +1,11 @@ -const cardBodyStyle = { +import { createStyles } from '@material-ui/core/styles'; + +const cardBodyStyle = createStyles({ cardBody: { padding: '0.9375rem 20px', flex: '1 1 auto', - WebkitBoxFlex: '1', - position: 'relative', + WebkitBoxFlex: 1, + position: 'relative' as const, }, cardBodyPlain: { paddingLeft: '5px', @@ -12,6 +14,6 @@ const cardBodyStyle = { cardBodyProfile: { marginTop: '15px', }, -}; +}); export default cardBodyStyle; diff --git a/src/ui/assets/jss/material-dashboard-react/components/cardHeaderStyle.js b/src/ui/assets/jss/material-dashboard-react/components/cardHeaderStyle.js deleted file mode 100644 index 01096b78f..000000000 --- a/src/ui/assets/jss/material-dashboard-react/components/cardHeaderStyle.js +++ /dev/null @@ -1,127 +0,0 @@ -import { - warningCardHeader, - successCardHeader, - dangerCardHeader, - infoCardHeader, - primaryCardHeader, - roseCardHeader, - whiteColor, -} from '../../material-dashboard-react.js'; - -const cardHeaderStyle = { - cardHeader: { - padding: '0.75rem 1.25rem', - marginBottom: '0', - borderBottom: 'none', - background: 'transparent', - zIndex: '3 !important', - '&$cardHeaderPlain,&$cardHeaderIcon,&$cardHeaderStats,&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader': - { - margin: '0 15px', - padding: '0', - position: 'relative', - color: whiteColor, - }, - '&:first-child': { - borderRadius: 'calc(.25rem - 1px) calc(.25rem - 1px) 0 0', - }, - '&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader': - { - '&:not($cardHeaderIcon)': { - borderRadius: '3px', - marginTop: '-20px', - padding: '15px', - }, - }, - '&$cardHeaderStats svg': { - fontSize: '36px', - lineHeight: '56px', - textAlign: 'center', - width: '36px', - height: '36px', - margin: '10px 10px 4px', - }, - '&$cardHeaderStats i,&$cardHeaderStats .material-icons': { - fontSize: '36px', - lineHeight: '56px', - width: '56px', - height: '56px', - textAlign: 'center', - overflow: 'unset', - marginBottom: '1px', - }, - '&$cardHeaderStats$cardHeaderIcon': { - textAlign: 'right', - }, - }, - cardHeaderPlain: { - marginLeft: '0px !important', - marginRight: '0px !important', - }, - cardHeaderStats: { - '& $cardHeaderIcon': { - textAlign: 'right', - }, - '& h1,& h2,& h3,& h4,& h5,& h6': { - margin: '0 !important', - }, - }, - cardHeaderIcon: { - '&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader': - { - background: 'transparent', - boxShadow: 'none', - }, - '& i,& .material-icons': { - width: '33px', - height: '33px', - textAlign: 'center', - lineHeight: '33px', - }, - '& svg': { - width: '24px', - height: '24px', - textAlign: 'center', - lineHeight: '33px', - margin: '5px 4px 0px', - }, - }, - warningCardHeader: { - color: whiteColor, - '&:not($cardHeaderIcon)': { - ...warningCardHeader, - }, - }, - successCardHeader: { - color: whiteColor, - '&:not($cardHeaderIcon)': { - ...successCardHeader, - }, - }, - dangerCardHeader: { - color: whiteColor, - '&:not($cardHeaderIcon)': { - ...dangerCardHeader, - }, - }, - infoCardHeader: { - color: whiteColor, - '&:not($cardHeaderIcon)': { - ...infoCardHeader, - }, - }, - primaryCardHeader: { - color: whiteColor, - '&:not($cardHeaderIcon)': { - ...primaryCardHeader, - }, - }, - roseCardHeader: { - color: whiteColor, - '&:not($cardHeaderIcon)': { - ...roseCardHeader, - }, - }, -}; - -export default cardHeaderStyle; diff --git a/src/ui/assets/jss/material-dashboard-react/components/cardHeaderStyle.ts b/src/ui/assets/jss/material-dashboard-react/components/cardHeaderStyle.ts new file mode 100644 index 000000000..d2de0546e --- /dev/null +++ b/src/ui/assets/jss/material-dashboard-react/components/cardHeaderStyle.ts @@ -0,0 +1,129 @@ +import { createStyles, Theme } from '@material-ui/core/styles'; +import { + warningCardHeader, + successCardHeader, + dangerCardHeader, + infoCardHeader, + primaryCardHeader, + roseCardHeader, + whiteColor, +} from '../../material-dashboard-react'; + +const cardHeaderStyle = (theme: Theme) => + createStyles({ + cardHeader: { + padding: '0.75rem 1.25rem', + marginBottom: '0', + borderBottom: 'none', + background: 'transparent', + zIndex: '3 !important' as any, + '&$cardHeaderPlain,&$cardHeaderIcon,&$cardHeaderStats,&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader': + { + margin: '0 15px', + padding: '0', + position: 'relative', + color: whiteColor, + }, + '&:first-child': { + borderRadius: 'calc(.25rem - 1px) calc(.25rem - 1px) 0 0', + }, + '&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader': + { + '&:not($cardHeaderIcon)': { + borderRadius: '3px', + marginTop: '-20px', + padding: '15px', + }, + }, + '&$cardHeaderStats svg': { + fontSize: '36px', + lineHeight: '56px', + textAlign: 'center', + width: '36px', + height: '36px', + margin: '10px 10px 4px', + }, + '&$cardHeaderStats i,&$cardHeaderStats .material-icons': { + fontSize: '36px', + lineHeight: '56px', + width: '56px', + height: '56px', + textAlign: 'center', + overflow: 'unset', + marginBottom: '1px', + }, + '&$cardHeaderStats$cardHeaderIcon': { + textAlign: 'right', + }, + }, + cardHeaderPlain: { + marginLeft: '0px !important', + marginRight: '0px !important', + }, + cardHeaderStats: { + '& $cardHeaderIcon': { + textAlign: 'right', + }, + '& h1,& h2,& h3,& h4,& h5,& h6': { + margin: '0 !important', + }, + }, + cardHeaderIcon: { + '&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader': + { + background: 'transparent', + boxShadow: 'none', + }, + '& i,& .material-icons': { + width: '33px', + height: '33px', + textAlign: 'center', + lineHeight: '33px', + }, + '& svg': { + width: '24px', + height: '24px', + textAlign: 'center', + lineHeight: '33px', + margin: '5px 4px 0px', + }, + }, + warningCardHeader: { + color: whiteColor, + '&:not($cardHeaderIcon)': { + ...warningCardHeader, + }, + }, + successCardHeader: { + color: whiteColor, + '&:not($cardHeaderIcon)': { + ...successCardHeader, + }, + }, + dangerCardHeader: { + color: whiteColor, + '&:not($cardHeaderIcon)': { + ...dangerCardHeader, + }, + }, + infoCardHeader: { + color: whiteColor, + '&:not($cardHeaderIcon)': { + ...infoCardHeader, + }, + }, + primaryCardHeader: { + color: whiteColor, + '&:not($cardHeaderIcon)': { + ...primaryCardHeader, + }, + }, + roseCardHeader: { + color: whiteColor, + '&:not($cardHeaderIcon)': { + ...roseCardHeader, + }, + }, + }); + +export default cardHeaderStyle; diff --git a/src/ui/assets/jss/material-dashboard-react/components/cardStyle.js b/src/ui/assets/jss/material-dashboard-react/components/cardStyle.ts similarity index 52% rename from src/ui/assets/jss/material-dashboard-react/components/cardStyle.js rename to src/ui/assets/jss/material-dashboard-react/components/cardStyle.ts index 5c850da6c..80b324b89 100644 --- a/src/ui/assets/jss/material-dashboard-react/components/cardStyle.js +++ b/src/ui/assets/jss/material-dashboard-react/components/cardStyle.ts @@ -1,20 +1,21 @@ -import { blackColor, whiteColor, hexToRgb } from '../../material-dashboard-react.js'; +import { createStyles } from '@material-ui/core/styles'; +import { blackColor, whiteColor, hexToRgb } from '../../material-dashboard-react'; -const cardStyle = { +const cardStyle = createStyles({ card: { border: '0', marginBottom: '30px', marginTop: '30px', borderRadius: '6px', - color: 'rgba(' + hexToRgb(blackColor) + ', 0.87)', + color: `rgba(${hexToRgb(blackColor)}, 0.87)`, background: whiteColor, width: '100%', - boxShadow: '0 1px 4px 0 rgba(' + hexToRgb(blackColor) + ', 0.14)', - position: 'relative', - display: 'flex', - flexDirection: 'column', + boxShadow: `0 1px 4px 0 rgba(${hexToRgb(blackColor)}, 0.14)`, + position: 'relative' as const, + display: 'flex' as const, + flexDirection: 'column' as const, minWidth: '0', - wordWrap: 'break-word', + wordWrap: 'break-word' as const, fontSize: '.875rem', }, cardPlain: { @@ -23,7 +24,7 @@ const cardStyle = { }, cardProfile: { marginTop: '30px', - textAlign: 'center', + textAlign: 'center' as const, }, cardChart: { '& p': { @@ -31,6 +32,6 @@ const cardStyle = { paddingTop: '0px', }, }, -}; +}); -export default cardStyle; +export default cardStyle; \ No newline at end of file diff --git a/src/ui/components/Card/Card.jsx b/src/ui/components/Card/Card.tsx similarity index 59% rename from src/ui/components/Card/Card.jsx rename to src/ui/components/Card/Card.tsx index c3bb4013d..edbf2ee2c 100644 --- a/src/ui/components/Card/Card.jsx +++ b/src/ui/components/Card/Card.tsx @@ -1,32 +1,41 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardStyle'; const useStyles = makeStyles(styles); -export default function Card(props) { +interface CardProps extends React.ComponentProps<'div'> { + className?: string; + plain?: boolean; + profile?: boolean; + chart?: boolean; + children?: React.ReactNode; +} + +const Card: React.FC = ({ + className = '', + children, + plain = false, + profile = false, + chart = false, + ...rest +}) => { const classes = useStyles(); - const { className, children, plain, profile, chart, ...rest } = props; + const cardClasses = classNames({ [classes.card]: true, [classes.cardPlain]: plain, [classes.cardProfile]: profile, [classes.cardChart]: chart, - [className]: className !== undefined, + [className]: className, }); + return (
{children}
); -} - -Card.propTypes = { - className: PropTypes.string, - plain: PropTypes.bool, - profile: PropTypes.bool, - chart: PropTypes.bool, - children: PropTypes.node, }; + +export default Card; \ No newline at end of file diff --git a/src/ui/components/Card/CardAvatar.jsx b/src/ui/components/Card/CardAvatar.tsx similarity index 60% rename from src/ui/components/Card/CardAvatar.jsx rename to src/ui/components/Card/CardAvatar.tsx index 8814717ec..2bbfe9db4 100644 --- a/src/ui/components/Card/CardAvatar.jsx +++ b/src/ui/components/Card/CardAvatar.tsx @@ -1,30 +1,38 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardAvatarStyle'; const useStyles = makeStyles(styles); -export default function CardAvatar(props) { +interface CardAvatarProps extends React.ComponentProps<'div'> { + children: React.ReactNode; + className?: string; + profile?: boolean; + plain?: boolean; +} + +const CardAvatar: React.FC = ({ + children, + className = '', + profile = false, + plain = false, + ...rest +}) => { const classes = useStyles(); - const { children, className, plain, profile, ...rest } = props; + const cardAvatarClasses = classNames({ [classes.cardAvatar]: true, [classes.cardAvatarProfile]: profile, [classes.cardAvatarPlain]: plain, - [className]: className !== undefined, + [className]: className, }); + return (
{children}
); -} - -CardAvatar.propTypes = { - children: PropTypes.node.isRequired, - className: PropTypes.string, - profile: PropTypes.bool, - plain: PropTypes.bool, }; + +export default CardAvatar; diff --git a/src/ui/components/Card/CardBody.jsx b/src/ui/components/Card/CardBody.tsx similarity index 60% rename from src/ui/components/Card/CardBody.jsx rename to src/ui/components/Card/CardBody.tsx index fa988f6c4..1a32e2354 100644 --- a/src/ui/components/Card/CardBody.jsx +++ b/src/ui/components/Card/CardBody.tsx @@ -1,30 +1,38 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardBodyStyle'; const useStyles = makeStyles(styles); -export default function CardBody(props) { +interface CardBodyProps extends React.ComponentProps<'div'> { + className?: string; + plain?: boolean; + profile?: boolean; + children?: React.ReactNode; +} + +const CardBody: React.FC = ({ + className = '', + children, + plain = false, + profile = false, + ...rest +}) => { const classes = useStyles(); - const { className, children, plain, profile, ...rest } = props; + const cardBodyClasses = classNames({ [classes.cardBody]: true, [classes.cardBodyPlain]: plain, [classes.cardBodyProfile]: profile, - [className]: className !== undefined, + [className]: className, }); + return (
{children}
); -} - -CardBody.propTypes = { - className: PropTypes.string, - plain: PropTypes.bool, - profile: PropTypes.bool, - children: PropTypes.node, }; + +export default CardBody; \ No newline at end of file diff --git a/src/ui/components/Card/CardFooter.jsx b/src/ui/components/Card/CardFooter.tsx similarity index 60% rename from src/ui/components/Card/CardFooter.jsx rename to src/ui/components/Card/CardFooter.tsx index 4b6c02d4b..57f9f36a5 100644 --- a/src/ui/components/Card/CardFooter.jsx +++ b/src/ui/components/Card/CardFooter.tsx @@ -1,34 +1,44 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardFooterStyle'; const useStyles = makeStyles(styles); -export default function CardFooter(props) { +interface CardFooterProps extends React.ComponentProps<'div'> { + className?: string; + plain?: boolean; + profile?: boolean; + stats?: boolean; + chart?: boolean; + children?: React.ReactNode; +} + +const CardFooter: React.FC = ({ + className, + children, + plain, + profile, + stats, + chart, + ...rest +}) => { const classes = useStyles(); - const { className, children, plain, profile, stats, chart, ...rest } = props; + const cardFooterClasses = classNames({ [classes.cardFooter]: true, [classes.cardFooterPlain]: plain, [classes.cardFooterProfile]: profile, [classes.cardFooterStats]: stats, [classes.cardFooterChart]: chart, - [className]: className !== undefined, + [className || '']: className !== undefined, }); + return (
{children}
); -} - -CardFooter.propTypes = { - className: PropTypes.string, - plain: PropTypes.bool, - profile: PropTypes.bool, - stats: PropTypes.bool, - chart: PropTypes.bool, - children: PropTypes.node, }; + +export default CardFooter; diff --git a/src/ui/components/Card/CardHeader.jsx b/src/ui/components/Card/CardHeader.tsx similarity index 57% rename from src/ui/components/Card/CardHeader.jsx rename to src/ui/components/Card/CardHeader.tsx index 80227a10a..0a01c3df6 100644 --- a/src/ui/components/Card/CardHeader.jsx +++ b/src/ui/components/Card/CardHeader.tsx @@ -1,34 +1,39 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardHeaderStyle'; const useStyles = makeStyles(styles); -export default function CardHeader(props) { +type CardHeaderColor = 'warning' | 'success' | 'danger' | 'info' | 'primary' | 'rose'; + +interface CardHeaderProps extends React.ComponentProps<'div'> { + className?: string; + color?: CardHeaderColor; + plain?: boolean; + stats?: boolean; + icon?: boolean; + children?: React.ReactNode; +} + +const CardHeader: React.FC = (props) => { const classes = useStyles(); const { className, children, color, plain, stats, icon, ...rest } = props; + const cardHeaderClasses = classNames({ [classes.cardHeader]: true, - [classes[color + 'CardHeader']]: color, + [color ? classes[`${color}CardHeader`] : '']: color, [classes.cardHeaderPlain]: plain, [classes.cardHeaderStats]: stats, [classes.cardHeaderIcon]: icon, - [className]: className !== undefined, + [className || '']: className !== undefined, }); + return (
{children}
); -} - -CardHeader.propTypes = { - className: PropTypes.string, - color: PropTypes.oneOf(['warning', 'success', 'danger', 'info', 'primary', 'rose']), - plain: PropTypes.bool, - stats: PropTypes.bool, - icon: PropTypes.bool, - children: PropTypes.node, }; + +export default CardHeader; diff --git a/src/ui/components/Card/CardIcon.jsx b/src/ui/components/Card/CardIcon.tsx similarity index 56% rename from src/ui/components/Card/CardIcon.jsx rename to src/ui/components/Card/CardIcon.tsx index f2b994691..353f07c86 100644 --- a/src/ui/components/Card/CardIcon.jsx +++ b/src/ui/components/Card/CardIcon.tsx @@ -1,28 +1,34 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardIconStyle'; const useStyles = makeStyles(styles); -export default function CardIcon(props) { +type CardIconColor = 'warning' | 'success' | 'danger' | 'info' | 'primary' | 'rose'; + +interface CardIconProps { + className?: string; + color?: CardIconColor; + children?: React.ReactNode; + [key: string]: any; +} + +const CardIcon: React.FC = (props) => { const classes = useStyles(); const { className, children, color, ...rest } = props; + const cardIconClasses = classNames({ [classes.cardIcon]: true, - [classes[color + 'CardHeader']]: color, - [className]: className !== undefined, + [color ? classes[`${color}CardHeader`] : '']: color, + [className || '']: className !== undefined, }); + return (
{children}
); -} - -CardIcon.propTypes = { - className: PropTypes.string, - color: PropTypes.oneOf(['warning', 'success', 'danger', 'info', 'primary', 'rose']), - children: PropTypes.node, }; + +export default CardIcon; From 5523283dbff52a2838bec5baba770821674420f5 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Fri, 11 Apr 2025 14:54:38 +0200 Subject: [PATCH 12/33] refactor(tsx): convert Search component --- .../Search/{Search.jsx => Search.tsx} | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) rename src/ui/components/Search/{Search.jsx => Search.tsx} (64%) diff --git a/src/ui/components/Search/Search.jsx b/src/ui/components/Search/Search.tsx similarity index 64% rename from src/ui/components/Search/Search.jsx rename to src/ui/components/Search/Search.tsx index 5e1cbf6b4..0e10819c8 100644 --- a/src/ui/components/Search/Search.jsx +++ b/src/ui/components/Search/Search.tsx @@ -2,13 +2,16 @@ import React from 'react'; import { TextField } from '@material-ui/core'; import './Search.css'; import InputAdornment from '@material-ui/core/InputAdornment'; -import SearchIcon from '@material-ui/icons/Search'; +import SearchIcon from '@material-ui/icons/Search'; +interface SearchProps { + onSearch: (query: string) => void; +} -export default function Search({ onSearch }) { - const handleSearchChange = (event) => { +const Search: React.FC = ({ onSearch }) => { + const handleSearchChange = (event: React.ChangeEvent) => { const query = event.target.value; - onSearch(query); + onSearch(query); }; return ( @@ -18,7 +21,7 @@ export default function Search({ onSearch }) { variant="outlined" fullWidth margin="normal" - onChange={handleSearchChange} + onChange={handleSearchChange} placeholder="Search..." InputProps={{ startAdornment: ( @@ -30,8 +33,6 @@ export default function Search({ onSearch }) { /> ); -} - - - +}; +export default Search; \ No newline at end of file From 56e04e6ab9099d5f2a95f61f397ff5c273ac41f0 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Tue, 15 Apr 2025 11:29:46 +0200 Subject: [PATCH 13/33] refactor(tsx): convert NavBar components --- ...eaderLinksStyle.js => headerLinksStyle.ts} | 49 +++++++++++++-- .../{headerStyle.js => headerStyle.ts} | 21 ++++++- ...inNavbarLinks.jsx => AdminNavbarLinks.tsx} | 62 +++++++++++-------- .../Navbars/{Navbar.jsx => Navbar.tsx} | 48 ++++++++------ 4 files changed, 129 insertions(+), 51 deletions(-) rename src/ui/assets/jss/material-dashboard-react/components/{headerLinksStyle.js => headerLinksStyle.ts} (71%) rename src/ui/assets/jss/material-dashboard-react/components/{headerStyle.js => headerStyle.ts} (74%) rename src/ui/components/Navbars/{AdminNavbarLinks.jsx => AdminNavbarLinks.tsx} (73%) rename src/ui/components/Navbars/{Navbar.jsx => Navbar.tsx} (70%) diff --git a/src/ui/assets/jss/material-dashboard-react/components/headerLinksStyle.js b/src/ui/assets/jss/material-dashboard-react/components/headerLinksStyle.ts similarity index 71% rename from src/ui/assets/jss/material-dashboard-react/components/headerLinksStyle.js rename to src/ui/assets/jss/material-dashboard-react/components/headerLinksStyle.ts index bdeb9dc44..49687a1ce 100644 --- a/src/ui/assets/jss/material-dashboard-react/components/headerLinksStyle.js +++ b/src/ui/assets/jss/material-dashboard-react/components/headerLinksStyle.ts @@ -1,9 +1,50 @@ -import { defaultFont, dangerColor, whiteColor } from '../../material-dashboard-react.js'; +import { Theme } from '@material-ui/core/styles'; +import { defaultFont, dangerColor, whiteColor } from '../../material-dashboard-react'; +import dropdownStyle from '../dropdownStyle'; -// eslint-disable-next-line max-len -import dropdownStyle from '../dropdownStyle.js'; +interface HeaderLinksStyle { + [key: string]: any; + search: { + '& > div': { + marginTop: string; + }; + [themeBreakpoint: string]: any; + }; + linkText: { + zIndex: string; + fontSize: string; + margin: string; + [key: string]: any; + }; + buttonLink: { + [themeBreakpoint: string]: any; + }; + searchButton: { + [themeBreakpoint: string]: any; + }; + margin: { + zIndex: string; + margin: string; + }; + searchIcon: { + width: string; + zIndex: string; + }; + notifications: { + zIndex: string; + [themeBreakpoint: string]: any; + }; + manager: { + [themeBreakpoint: string]: any; + display: string; + }; + searchWrapper: { + [themeBreakpoint: string]: any; + display: string; + }; +} -const headerLinksStyle = (theme) => ({ +const headerLinksStyle = (theme: Theme): HeaderLinksStyle => ({ ...dropdownStyle(theme), search: { '& > div': { diff --git a/src/ui/assets/jss/material-dashboard-react/components/headerStyle.js b/src/ui/assets/jss/material-dashboard-react/components/headerStyle.ts similarity index 74% rename from src/ui/assets/jss/material-dashboard-react/components/headerStyle.js rename to src/ui/assets/jss/material-dashboard-react/components/headerStyle.ts index 48439db2c..bfd328fa0 100644 --- a/src/ui/assets/jss/material-dashboard-react/components/headerStyle.js +++ b/src/ui/assets/jss/material-dashboard-react/components/headerStyle.ts @@ -8,9 +8,24 @@ import { warningColor, dangerColor, whiteColor, -} from '../../material-dashboard-react.js'; +} from '../../material-dashboard-react'; -const headerStyle = () => ({ +interface StyleProps { + appBar: React.CSSProperties; + container: React.CSSProperties; + flex: React.CSSProperties; + title: React.CSSProperties & { + '&:hover,&:focus': React.CSSProperties; + }; + appResponsive: React.CSSProperties; + primary: React.CSSProperties; + info: React.CSSProperties; + success: React.CSSProperties; + warning: React.CSSProperties; + danger: React.CSSProperties; +} + +const headerStyle = (): StyleProps => ({ appBar: { backgroundColor: 'transparent', boxShadow: 'none', @@ -78,4 +93,4 @@ const headerStyle = () => ({ }, }); -export default headerStyle; +export default headerStyle; \ No newline at end of file diff --git a/src/ui/components/Navbars/AdminNavbarLinks.jsx b/src/ui/components/Navbars/AdminNavbarLinks.tsx similarity index 73% rename from src/ui/components/Navbars/AdminNavbarLinks.jsx rename to src/ui/components/Navbars/AdminNavbarLinks.tsx index 1821f52c1..d8ef97761 100644 --- a/src/ui/components/Navbars/AdminNavbarLinks.jsx +++ b/src/ui/components/Navbars/AdminNavbarLinks.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import classNames from 'classnames'; import { makeStyles } from '@material-ui/core/styles'; import MenuItem from '@material-ui/core/MenuItem'; @@ -7,7 +7,7 @@ import Grow from '@material-ui/core/Grow'; import Paper from '@material-ui/core/Paper'; import ClickAwayListener from '@material-ui/core/ClickAwayListener'; import Hidden from '@material-ui/core/Hidden'; -import Poppers from '@material-ui/core/Popper'; +import Popper from '@material-ui/core/Popper'; import Divider from '@material-ui/core/Divider'; import Button from '../CustomButtons/Button'; import styles from '../../assets/jss/material-dashboard-react/components/headerLinksStyle'; @@ -19,26 +19,33 @@ import { getCookie } from '../../utils'; const useStyles = makeStyles(styles); -export default function AdminNavbarLinks() { +interface UserData { + id: string; + name: string; + email: string; +} + +const AdminNavbarLinks: React.FC = () => { const classes = useStyles(); const navigate = useNavigate(); - const [openProfile, setOpenProfile] = React.useState(null); - const [, setAuth] = React.useState(true); - const [, setIsLoading] = React.useState(true); - const [, setIsError] = React.useState(false); - const [data, setData] = React.useState(false); + const [openProfile, setOpenProfile] = useState(null); + const [, setAuth] = useState(true); + const [, setIsLoading] = useState(true); + const [, setIsError] = useState(false); + const [data, setData] = useState(null); useEffect(() => { getUser(setIsLoading, setData, setAuth, setIsError); }, []); - const handleClickProfile = (event) => { - if (openProfile && openProfile.contains(event.target)) { + const handleClickProfile = (event: React.MouseEvent) => { + if (openProfile && openProfile.contains(event.target as Node)) { setOpenProfile(null); } else { setOpenProfile(event.currentTarget); } }; + const handleCloseProfile = () => { setOpenProfile(null); }; @@ -47,10 +54,10 @@ export default function AdminNavbarLinks() { navigate('/admin/profile', { replace: true }); }; - const logout = () => { - axios - .post( - `${import.meta.env.VITE_API_URI}/api/auth/logout`, + const logout = async () => { + try { + const response = await axios.post( + `${import.meta.env.VITE_API_URI || 'http://localhost:3000'}/api/auth/logout`, {}, { withCredentials: true, @@ -58,13 +65,15 @@ export default function AdminNavbarLinks() { 'X-CSRF-TOKEN': getCookie('csrf'), }, }, - ) - .then((res) => { - if (!res.data.isAuth && !res.data.user) { - setAuth(false); - navigate(0); - } - }); + ); + + if (!response.data.isAuth && !response.data.user) { + setAuth(false); + navigate(0); + } + } catch (error) { + console.error('Logout failed:', error); + } }; return ( @@ -74,7 +83,7 @@ export default function AdminNavbarLinks() { color={window.innerWidth > 959 ? 'transparent' : 'white'} justIcon={window.innerWidth > 959} simple={window.innerWidth <= 959} - aria-owns={openProfile ? 'profile-menu-list-grow' : null} + aria-owns={openProfile ? 'profile-menu-list-grow' : undefined} aria-haspopup='true' onClick={handleClickProfile} className={classes.buttonLink} @@ -84,7 +93,7 @@ export default function AdminNavbarLinks() {

Profile

- ( )} - + ); -} +}; + +export default AdminNavbarLinks; diff --git a/src/ui/components/Navbars/Navbar.jsx b/src/ui/components/Navbars/Navbar.tsx similarity index 70% rename from src/ui/components/Navbars/Navbar.jsx rename to src/ui/components/Navbars/Navbar.tsx index e3925bc8f..15494efae 100644 --- a/src/ui/components/Navbars/Navbar.jsx +++ b/src/ui/components/Navbars/Navbar.tsx @@ -1,6 +1,5 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import AppBar from '@material-ui/core/AppBar'; import Toolbar from '@material-ui/core/Toolbar'; @@ -12,22 +11,41 @@ import styles from '../../assets/jss/material-dashboard-react/components/headerS const useStyles = makeStyles(styles); -export default function Header(props) { +interface Route { + component: any; + icon: any; + layout: string; + name: string; + rtlName?: string; + path: string; + visible: boolean; +} + +interface HeaderProps { + color?: 'primary' | 'info' | 'success' | 'warning' | 'danger'; + rtlActive?: boolean; + handleDrawerToggle: () => void; + routes: Route[]; +} + +const Header: React.FC = (props) => { const classes = useStyles(); - function makeBrand() { - let name; - props.routes.map((prop) => { + + const makeBrand = (): string => { + let name = ''; + props.routes.forEach((prop) => { if (window.location.href.indexOf(prop.layout + prop.path) !== -1) { - name = props.rtlActive ? prop.rtlName : prop.name; + name = props.rtlActive ? prop.rtlName || prop.name : prop.name; } - return null; }); return name; - } - const { color } = props; + }; + + const { color = 'primary' } = props; const appBarClasses = classNames({ - [' ' + classes[color]]: color, + [` ${classes[color]}`]: color, }); + return ( @@ -35,7 +53,6 @@ export default function Header(props) { {/* Here we create navbar brand, based on route name */}

{makeBrand()} @@ -52,11 +69,6 @@ export default function Header(props) { ); -} - -Header.propTypes = { - color: PropTypes.oneOf(['primary', 'info', 'success', 'warning', 'danger']), - rtlActive: PropTypes.bool, - handleDrawerToggle: PropTypes.func, - routes: PropTypes.arrayOf(PropTypes.object), }; + +export default Header; From 5cfc666c058c3d00a62d3667cafccb8b27c9d0b5 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Tue, 15 Apr 2025 11:46:39 +0200 Subject: [PATCH 14/33] refactor(tsx): convert Admin component --- .../layouts/{adminStyle.js => adminStyle.ts} | 19 +++- src/ui/components/Navbars/Navbar.tsx | 2 +- src/ui/layouts/{Admin.jsx => Admin.tsx} | 87 +++++++++++-------- 3 files changed, 67 insertions(+), 41 deletions(-) rename src/ui/assets/jss/material-dashboard-react/layouts/{adminStyle.js => adminStyle.ts} (56%) rename src/ui/layouts/{Admin.jsx => Admin.tsx} (65%) diff --git a/src/ui/assets/jss/material-dashboard-react/layouts/adminStyle.js b/src/ui/assets/jss/material-dashboard-react/layouts/adminStyle.ts similarity index 56% rename from src/ui/assets/jss/material-dashboard-react/layouts/adminStyle.js rename to src/ui/assets/jss/material-dashboard-react/layouts/adminStyle.ts index 075819c14..411803438 100644 --- a/src/ui/assets/jss/material-dashboard-react/layouts/adminStyle.js +++ b/src/ui/assets/jss/material-dashboard-react/layouts/adminStyle.ts @@ -1,6 +1,17 @@ -import { drawerWidth, transition, container } from '../../material-dashboard-react.js'; +import { Theme } from '@material-ui/core/styles/createTheme'; +import { drawerWidth, transition, container } from '../../material-dashboard-react'; -const appStyle = (theme) => ({ +interface AppStyleProps { + wrapper: React.CSSProperties; + mainPanel: React.CSSProperties & { + [key: string]: any; + }; + content: React.CSSProperties; + container: typeof container; + map: React.CSSProperties; +} + +const appStyle = (theme: Theme): AppStyleProps => ({ wrapper: { position: 'relative', top: '0', @@ -16,14 +27,14 @@ const appStyle = (theme) => ({ ...transition, maxHeight: '100%', width: '100%', - overflowScrolling: 'touch', + WebkitOverflowScrolling: 'touch' as any, }, content: { marginTop: '70px', padding: '30px 15px', minHeight: 'calc(100vh - 123px)', }, - container, + container: { ...container }, map: { marginTop: '70px', }, diff --git a/src/ui/components/Navbars/Navbar.tsx b/src/ui/components/Navbars/Navbar.tsx index 15494efae..178da8af0 100644 --- a/src/ui/components/Navbars/Navbar.tsx +++ b/src/ui/components/Navbars/Navbar.tsx @@ -41,7 +41,7 @@ const Header: React.FC = (props) => { return name; }; - const { color = 'primary' } = props; + const { color } = props; const appBarClasses = classNames({ [` ${classes[color]}`]: color, }); diff --git a/src/ui/layouts/Admin.jsx b/src/ui/layouts/Admin.tsx similarity index 65% rename from src/ui/layouts/Admin.jsx rename to src/ui/layouts/Admin.tsx index 8571cea1e..0f1e9a73d 100644 --- a/src/ui/layouts/Admin.jsx +++ b/src/ui/layouts/Admin.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect, useRef } from 'react'; import { Routes, Route, Navigate, useParams } from 'react-router-dom'; import PerfectScrollbar from 'perfect-scrollbar'; import 'perfect-scrollbar/css/perfect-scrollbar.css'; @@ -12,76 +12,90 @@ import logo from '../assets/img/git-proxy.png'; import { UserContext } from '../../context'; import { getUser } from '../services/user'; -let ps; -let refresh = false; +interface RouteType { + layout: string; + path: string; + component: React.ComponentType; +} -const switchRoutes = ( - - {routes.map((prop, key) => { - if (prop.layout === '/admin') { - return } key={key} />; - } - return null; - })} - } /> - -); +interface AdminProps { + [key: string]: any; +} + +interface UserType { + id?: string; + name?: string; + email?: string; +} + +let ps: PerfectScrollbar | undefined; +let refresh = false; const useStyles = makeStyles(styles); -export default function Admin({ ...rest }) { - // styles +const Admin: React.FC = ({ ...rest }) => { const classes = useStyles(); - // ref to help us initialize PerfectScrollbar on windows devices - const mainPanel = React.createRef(); - // states and functions - const [color] = React.useState('blue'); - const [mobileOpen, setMobileOpen] = React.useState(false); - const [user, setUser] = useState({}); + const mainPanel = useRef(null); + const [color] = useState('blue'); + const [mobileOpen, setMobileOpen] = useState(false); + const [user, setUser] = useState({}); + const { id } = useParams(); const handleDrawerToggle = () => { setMobileOpen(!mobileOpen); }; - const getRoute = () => { + + const getRoute = (): boolean => { return window.location.pathname !== '/admin/maps'; }; + const resizeFunction = () => { if (window.innerWidth >= 960) { setMobileOpen(false); } }; - // initialize and destroy the PerfectScrollbar plugin - const { id } = useParams(); - React.useEffect(() => { + const switchRoutes = ( + + {routes.map((prop: RouteType, key: number) => { + if (prop.layout === '/admin') { + const Component = prop.component; + return } key={key} />; + } + return null; + })} + } /> + + ); + + useEffect(() => { async function loadUser() { - if (navigator.platform.indexOf('Win') > -1) { + if (navigator.platform.indexOf('Win') > -1 && mainPanel.current) { ps = new PerfectScrollbar(mainPanel.current, { suppressScrollX: true, suppressScrollY: false, }); document.body.style.overflow = 'hidden'; - if (!refresh) { - refresh = true; - await getUser(null, setUser, null, null, null); - } } - window.addEventListener('resize', resizeFunction); + if (!refresh) { refresh = true; await getUser(null, setUser, null, null, null); } + + window.addEventListener('resize', resizeFunction); } + loadUser(); - // Specify how to clean up after this effect: - return function cleanup() { + return () => { if (navigator.platform.indexOf('Win') > -1 && ps) { ps.destroy(); } window.removeEventListener('resize', resizeFunction); }; }, [id]); + return (
@@ -96,7 +110,6 @@ export default function Admin({ ...rest }) { />
- {/* On the /maps route we want the map to be on full screen - this is not possible if the content and conatiner classes are present because they have some paddings which would make the map smaller */} {getRoute() ? (
{switchRoutes}
@@ -109,4 +122,6 @@ export default function Admin({ ...rest }) {
); -} +}; + +export default Admin; From e62a77d872cc616a92dfc17542ffdb2f1b861a83 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:20:21 +0200 Subject: [PATCH 15/33] refactor(tsx): convert Diff to tsx --- package-lock.json | 39 ++++++++++++++++++++ package.json | 1 + src/ui/views/PushDetails/components/Diff.jsx | 12 ------ src/ui/views/PushDetails/components/Diff.tsx | 19 ++++++++++ 4 files changed, 59 insertions(+), 12 deletions(-) delete mode 100644 src/ui/views/PushDetails/components/Diff.jsx create mode 100644 src/ui/views/PushDetails/components/Diff.tsx diff --git a/package-lock.json b/package-lock.json index 3052eaddc..3fc72e000 100644 --- a/package-lock.json +++ b/package-lock.json @@ -68,6 +68,7 @@ "@types/lodash": "^4.17.15", "@types/mocha": "^10.0.10", "@types/node": "^22.13.5", + "@types/react-html-parser": "^2.0.7", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/parser": "^8.26.1", @@ -3690,6 +3691,22 @@ "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true }, + "node_modules/@types/domhandler": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/@types/domhandler/-/domhandler-2.4.5.tgz", + "integrity": "sha512-lANhC2grmFG1gBac/8sDAKdIXx+TzAdkJIAjEOSMA+qW3297ybACEbacJnG15aNYfrzDO6fdcoouokqAKsy6aQ==", + "dev": true + }, + "node_modules/@types/domutils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/domutils/-/domutils-2.1.0.tgz", + "integrity": "sha512-5oQOJFsEXmVRW2gcpNrBrv1bj+FVge2Zwd5iDqxan5tu9/EKxaufqpR8lIY5sGIZJRhD5jgTM0iBmzjdpeQutQ==", + "deprecated": "This is a stub types definition. domutils provides its own type definitions, so you do not need this installed.", + "dev": true, + "dependencies": { + "domutils": "*" + } + }, "node_modules/@types/express": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.1.tgz", @@ -3725,6 +3742,18 @@ "@types/send": "*" } }, + "node_modules/@types/htmlparser2": { + "version": "3.10.7", + "resolved": "https://registry.npmjs.org/@types/htmlparser2/-/htmlparser2-3.10.7.tgz", + "integrity": "sha512-ycBs4PNr9rY9XFFp4WkP+M1UcO49ahn0+9b24cmIY6KWy0w35rW0G8+JTTe9Rp6Wnyqn5SEHZrhCBMa0TIOxBw==", + "dev": true, + "dependencies": { + "@types/domhandler": "^2.4.3", + "@types/domutils": "*", + "@types/node": "*", + "domhandler": "^2.4.0" + } + }, "node_modules/@types/http-errors": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", @@ -3789,6 +3818,16 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-html-parser": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/react-html-parser/-/react-html-parser-2.0.7.tgz", + "integrity": "sha512-1OeS8GG+sVYlLlhMIT/smA7L2AAAxJCHtUxu66UjIuE68m8eB+XM71Kpzn49b5BRb6MRm/UrmzGUNBP1oTY7QA==", + "dev": true, + "dependencies": { + "@types/htmlparser2": "^3.9.0", + "@types/react": "*" + } + }, "node_modules/@types/react-transition-group": { "version": "4.4.10", "resolved": "https://registry.npmjs.org/@types/react-transition-group/-/react-transition-group-4.4.10.tgz", diff --git a/package.json b/package.json index a87882467..193e71765 100644 --- a/package.json +++ b/package.json @@ -86,6 +86,7 @@ "@types/lodash": "^4.17.15", "@types/mocha": "^10.0.10", "@types/node": "^22.13.5", + "@types/react-html-parser": "^2.0.7", "@types/yargs": "^17.0.33", "@typescript-eslint/eslint-plugin": "^8.26.1", "@typescript-eslint/parser": "^8.26.1", diff --git a/src/ui/views/PushDetails/components/Diff.jsx b/src/ui/views/PushDetails/components/Diff.jsx deleted file mode 100644 index 44e60cd0a..000000000 --- a/src/ui/views/PushDetails/components/Diff.jsx +++ /dev/null @@ -1,12 +0,0 @@ -import * as Diff2Html from 'diff2html'; -import ReactHtmlParser from 'react-html-parser'; - -export default function Diff(props) { - const { diff } = props; - const outputHtml = Diff2Html.html(diff, { - drawFileList: true, - matching: 'lines', - }); - - return new ReactHtmlParser(outputHtml); -} diff --git a/src/ui/views/PushDetails/components/Diff.tsx b/src/ui/views/PushDetails/components/Diff.tsx new file mode 100644 index 000000000..e9a0daeb3 --- /dev/null +++ b/src/ui/views/PushDetails/components/Diff.tsx @@ -0,0 +1,19 @@ +import * as Diff2Html from 'diff2html'; +import reactHtmlParser from 'react-html-parser'; // Renamed to follow function naming conventions +import React from 'react'; + +interface DiffProps { + diff: string; +} + +const Diff: React.FC = ({ diff }) => { + const outputHtml = Diff2Html.html(diff, { + drawFileList: true, + matching: 'lines', + outputFormat: 'side-by-side', + }); + + return <>{reactHtmlParser(outputHtml)}; +}; + +export default Diff; From ec96e377ad7d8e461361d53ea4b04bb70dfe4791 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:22:09 +0200 Subject: [PATCH 16/33] refactor(tsx): convert User view --- src/ui/services/user.js | 99 ---------------- src/ui/services/user.ts | 137 +++++++++++++++++++++++ src/ui/views/User/{User.jsx => User.tsx} | 75 ++++++++----- 3 files changed, 187 insertions(+), 124 deletions(-) delete mode 100644 src/ui/services/user.js create mode 100644 src/ui/services/user.ts rename src/ui/views/User/{User.jsx => User.tsx} (71%) diff --git a/src/ui/services/user.js b/src/ui/services/user.js deleted file mode 100644 index 37a84a1b9..000000000 --- a/src/ui/services/user.js +++ /dev/null @@ -1,99 +0,0 @@ -import axios from 'axios'; -import { getCookie } from '../utils.tsx'; - -const baseUrl = import.meta.env.VITE_API_URI - ? `${import.meta.env.VITE_API_URI}` - : `${location.origin}`; - -const config = { - withCredentials: true, -}; - -const getUser = async (setIsLoading, setData, setAuth, setIsError, id = null) => { - let url = `${baseUrl}/api/auth/profile`; - - if (id) { - url = `${baseUrl}/api/v1/user/${id}`; - } - - console.log(url); - - await axios(url, config) - .then((response) => { - const data = response.data; - if (setData) { - setData(data); - } - if (setIsLoading) { - setIsLoading(false); - } - }) - .catch((error) => { - if (error.response && error.response.status === 401) { - if (setAuth) { - setAuth(false); - } - } else { - if (setIsError) { - setIsError(true); - } - } - if (setIsLoading) { - setIsLoading(false); - } - }); -}; - -const getUsers = async (setIsLoading, setData, setAuth, setIsError, query = {}) => { - const url = new URL(`${baseUrl}/api/v1/user`); - url.search = new URLSearchParams(query); - setIsLoading(true); - await axios(url.toString(), { withCredentials: true }) - .then((response) => { - const data = response.data; - setData(data); - setIsLoading(false); - }) - .catch((error) => { - setIsLoading(false); - if (error.response && error.response.status === 401) { - setAuth(false); - } else { - setIsError(true); - } - setIsLoading(false); - }); -}; - -const updateUser = async (data) => { - console.log(data); - const url = new URL(`${baseUrl}/api/auth/gitAccount`); - await axios - .post(url, data, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) - .catch((error) => { - console.log(error.response.data.message); - throw error; - }); -}; - -const getUserLoggedIn = async (setIsLoading, setIsAdmin, setIsError, setAuth) => { - const url = new URL(`${baseUrl}/api/auth/userLoggedIn`); - - await axios(url.toString(), { withCredentials: true }) - .then((response) => { - const data = response.data; - setIsLoading(false); - setIsAdmin(data.admin); - }) - .catch((error) => { - setIsLoading(false); - if (error.response && error.response.status === 401) { - setAuth(false); - } else { - setIsError(true); - } - setIsLoading(false); - }); -}; - -export { getUser, getUsers, updateUser, getUserLoggedIn }; diff --git a/src/ui/services/user.ts b/src/ui/services/user.ts new file mode 100644 index 000000000..db8ad1bf2 --- /dev/null +++ b/src/ui/services/user.ts @@ -0,0 +1,137 @@ +import axios, { AxiosError, AxiosResponse } from 'axios'; +import { getCookie } from '../utils'; + +export interface UserData { + username: string; + email?: string; + displayName?: string; + title?: string; + gitAccount?: string; + admin?: boolean; +} + +type SetStateCallback = (value: T | ((prevValue: T) => T)) => void; + +const baseUrl = import.meta.env.VITE_API_URI + ? `${import.meta.env.VITE_API_URI}` + : `${location.origin}`; + +const config = { + withCredentials: true, +}; + +const getUser = async ( + setIsLoading?: SetStateCallback, + setData?: (userData: UserData) => void, + setAuth?: SetStateCallback, + setIsError?: SetStateCallback, + id: string | null = null, +): Promise => { + let url = `${baseUrl}/api/auth/profile`; + if (id) { + url = `${baseUrl}/api/v1/user/${id}`; + } + console.log(url); + + try { + const response: AxiosResponse = await axios(url, config); + const data = response.data; + + if (setData) { + setData(data); + } + if (setIsLoading) { + setIsLoading(false); + } + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response && axiosError.response.status === 401) { + if (setAuth) { + setAuth(false); + } + } else { + if (setIsError) { + setIsError(true); + } + } + if (setIsLoading) { + setIsLoading(false); + } + } +}; + +const getUsers = async ( + setIsLoading: SetStateCallback, + setData: SetStateCallback, + setAuth: SetStateCallback, + setIsError: SetStateCallback, + query: Record = {}, +): Promise => { + const url = new URL(`${baseUrl}/api/v1/user`); + url.search = new URLSearchParams(query).toString(); + + setIsLoading(true); + + try { + const response: AxiosResponse = await axios(url.toString(), { + withCredentials: true, + }); + const data = response.data; + setData(data); + setIsLoading(false); + } catch (error) { + setIsLoading(false); + const axiosError = error as AxiosError; + if (axiosError.response && axiosError.response.status === 401) { + setAuth(false); + } else { + setIsError(true); + } + } +}; + +const updateUser = async (data: UserData): Promise => { + console.log(data); + const url = new URL(`${baseUrl}/api/auth/gitAccount`); + + try { + await axios.post(url.toString(), data, { + withCredentials: true, + headers: { 'X-CSRF-TOKEN': getCookie('csrf') }, + }); + } catch (error) { + const axiosError = error as AxiosError; + if (axiosError.response) { + console.log((axiosError.response.data as any).message); + } + throw error; + } +}; + +const getUserLoggedIn = async ( + setIsLoading: SetStateCallback, + setIsAdmin: SetStateCallback, + setIsError: SetStateCallback, + setAuth: SetStateCallback, +): Promise => { + const url = new URL(`${baseUrl}/api/auth/userLoggedIn`); + + try { + const response: AxiosResponse = await axios(url.toString(), { + withCredentials: true, + }); + const data = response.data; + setIsLoading(false); + setIsAdmin(data.admin || false); + } catch (error) { + setIsLoading(false); + const axiosError = error as AxiosError; + if (axiosError.response && axiosError.response.status === 401) { + setAuth(false); + } else { + setIsError(true); + } + } +}; + +export { getUser, getUsers, updateUser, getUserLoggedIn }; diff --git a/src/ui/views/User/User.jsx b/src/ui/views/User/User.tsx similarity index 71% rename from src/ui/views/User/User.jsx rename to src/ui/views/User/User.tsx index c8b46ebe5..046543b83 100644 --- a/src/ui/views/User/User.jsx +++ b/src/ui/views/User/User.tsx @@ -6,15 +6,15 @@ import Card from '../../components/Card/Card'; import CardBody from '../../components/Card/CardBody'; import Button from '../../components/CustomButtons/Button'; import FormLabel from '@material-ui/core/FormLabel'; -import { getUser, updateUser, getUserLoggedIn } from '../../services/user'; +import { getUser, updateUser, getUserLoggedIn, UserData } from '../../services/user'; import { makeStyles } from '@material-ui/core/styles'; import { LogoGithubIcon } from '@primer/octicons-react'; import CloseRounded from '@material-ui/icons/CloseRounded'; import { Check, Save } from '@material-ui/icons'; -import { TextField } from '@material-ui/core'; +import { TextField, Theme } from '@material-ui/core'; -const useStyles = makeStyles((theme) => ({ +const useStyles = makeStyles((theme: Theme) => ({ root: { '& .MuiTextField-root': { margin: theme.spacing(1), @@ -23,17 +23,17 @@ const useStyles = makeStyles((theme) => ({ }, })); -export default function Dashboard() { +export default function UserProfile(): React.ReactElement { const classes = useStyles(); - const [data, setData] = useState([]); - const [auth, setAuth] = useState(true); - const [isLoading, setIsLoading] = useState(true); - const [isError, setIsError] = useState(false); - const [isProfile, setIsProfile] = useState(false); - const [isAdmin, setIsAdmin] = useState(false); - const [gitAccount, setGitAccount] = useState(''); + const [data, setData] = useState(null); + const [auth, setAuth] = useState(true); + const [isLoading, setIsLoading] = useState(true); + const [isError, setIsError] = useState(false); + const [isProfile, setIsProfile] = useState(false); + const [isAdmin, setIsAdmin] = useState(false); + const [gitAccount, setGitAccount] = useState(''); const navigate = useNavigate(); - const { id } = useParams(); + const { id } = useParams<{ id?: string }>(); useEffect(() => { if (id == null) { @@ -41,38 +41,60 @@ export default function Dashboard() { } if (id) { - getUser(setIsLoading, setData, setAuth, setIsError, id); + getUser( + setIsLoading, + (userData: UserData) => { + setData(userData); + setGitAccount(userData.gitAccount || ''); + }, + setAuth, + setIsError, + id, + ); getUserLoggedIn(setIsLoading, setIsAdmin, setIsError, setAuth); } else { console.log('getting user data'); setIsProfile(true); - getUser(setIsLoading, setData, setAuth, setIsError); + getUser( + setIsLoading, + (userData: UserData) => { + setData(userData); + setGitAccount(userData.gitAccount || ''); + }, + setAuth, + setIsError, + ); } - }, []); + }, [id]); if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; if (!auth && window.location.pathname === '/admin/profile') { return ; } + if (!data) return
No user data available
; - const updateProfile = async () => { + const updateProfile = async (): Promise => { try { - data.gitAccount = escapeHTML(gitAccount); - await updateUser(data); + const updatedData = { + ...data, + gitAccount: escapeHTML(gitAccount), + }; + await updateUser(updatedData); navigate(`/admin/user/${data.username}`); } catch { setIsError(true); } }; - const UpdateButton = () => ( + const UpdateButton = (): React.ReactElement => ( ); - const escapeHTML = (str) => { + const escapeHTML = (str: string): string => { return str .replace(/&/g, '&') .replace(/ + alt={`${data.displayName}'s GitHub avatar`} + /> )} @@ -138,7 +161,7 @@ export default function Dashboard() { ) : ( - + )} @@ -147,7 +170,7 @@ export default function Dashboard() {
- What is your username? + What is your username?
setGitAccount(e.target.value)} + onChange={(e: React.ChangeEvent) => + setGitAccount(e.target.value) + } />
From 499279b3ce5efbfb371d09a9fac6b1c80653d7b7 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:23:19 +0200 Subject: [PATCH 17/33] refactor(tsx): convert Footer component --- .../components/footerStyle.js | 49 ----------------- .../components/footerStyle.ts | 55 +++++++++++++++++++ .../Footer/{Footer.jsx => Footer.tsx} | 9 ++- src/ui/components/Navbars/Navbar.tsx | 4 +- 4 files changed, 63 insertions(+), 54 deletions(-) delete mode 100644 src/ui/assets/jss/material-dashboard-react/components/footerStyle.js create mode 100644 src/ui/assets/jss/material-dashboard-react/components/footerStyle.ts rename src/ui/components/Footer/{Footer.jsx => Footer.tsx} (88%) diff --git a/src/ui/assets/jss/material-dashboard-react/components/footerStyle.js b/src/ui/assets/jss/material-dashboard-react/components/footerStyle.js deleted file mode 100644 index 46b4cfa01..000000000 --- a/src/ui/assets/jss/material-dashboard-react/components/footerStyle.js +++ /dev/null @@ -1,49 +0,0 @@ -import { defaultFont, container, primaryColor, grayColor } from '../../material-dashboard-react.js'; - -const footerStyle = { - block: { - color: 'inherit', - padding: '15px', - textTransform: 'uppercase', - borderRadius: '3px', - textDecoration: 'none', - position: 'relative', - display: 'block', - ...defaultFont, - fontWeight: '500', - fontSize: '12px', - }, - left: { - float: 'left!important', - display: 'block', - }, - right: { - padding: '15px 0', - margin: '0', - fontSize: '14px', - float: 'right!important', - }, - footer: { - bottom: '0', - borderTop: '1px solid ' + grayColor[11], - padding: '15px 0', - ...defaultFont, - }, - container, - a: { - color: primaryColor, - textDecoration: 'none', - backgroundColor: 'transparent', - }, - list: { - marginBottom: '0', - padding: '0', - marginTop: '0', - }, - inlineBlock: { - display: 'inline-block', - padding: '0px', - width: 'auto', - }, -}; -export default footerStyle; diff --git a/src/ui/assets/jss/material-dashboard-react/components/footerStyle.ts b/src/ui/assets/jss/material-dashboard-react/components/footerStyle.ts new file mode 100644 index 000000000..7abbe3144 --- /dev/null +++ b/src/ui/assets/jss/material-dashboard-react/components/footerStyle.ts @@ -0,0 +1,55 @@ +import { Theme } from '@material-ui/core/styles'; +import { createStyles } from '@material-ui/styles'; +import { defaultFont, container, primaryColor, grayColor } from '../../material-dashboard-react'; + +const footerStyle = (theme: Theme) => + createStyles({ + block: { + color: 'inherit', + padding: '15px', + textTransform: 'uppercase', + borderRadius: '3px', + textDecoration: 'none', + position: 'relative', + display: 'block', + ...defaultFont, + fontWeight: 500, + fontSize: '12px', + }, + left: { + float: 'left !important' as 'left', + display: 'block', + }, + right: { + padding: '15px 0', + margin: '0', + fontSize: '14px', + float: 'right !important' as 'right', + }, + footer: { + bottom: '0', + borderTop: `1px solid ${grayColor[11]}`, + padding: '15px 0', + ...defaultFont, + }, + container: { + ...container, + }, + a: { + color: primaryColor[0], + textDecoration: 'none', + backgroundColor: 'transparent', + }, + list: { + marginBottom: '0', + padding: '0', + marginTop: '0', + }, + inlineBlock: { + display: 'inline-block', + padding: '0px', + width: 'auto', + }, + }); + +export default footerStyle; diff --git a/src/ui/components/Footer/Footer.jsx b/src/ui/components/Footer/Footer.tsx similarity index 88% rename from src/ui/components/Footer/Footer.jsx rename to src/ui/components/Footer/Footer.tsx index 7cd9933f0..9fdac5e0d 100644 --- a/src/ui/components/Footer/Footer.jsx +++ b/src/ui/components/Footer/Footer.tsx @@ -7,8 +7,9 @@ import { MarkGithubIcon } from '@primer/octicons-react'; const useStyles = makeStyles(styles); -export default function Footer() { +const Footer: React.FC = () => { const classes = useStyles(); + return (
@@ -27,9 +28,11 @@ export default function Footer() {

- © {1900 + new Date().getYear()} GitProxy + © {new Date().getFullYear()} GitProxy

); -} +}; + +export default Footer; diff --git a/src/ui/components/Navbars/Navbar.tsx b/src/ui/components/Navbars/Navbar.tsx index 178da8af0..b792d5a17 100644 --- a/src/ui/components/Navbars/Navbar.tsx +++ b/src/ui/components/Navbars/Navbar.tsx @@ -9,7 +9,7 @@ import Menu from '@material-ui/icons/Menu'; import AdminNavbarLinks from './AdminNavbarLinks'; import styles from '../../assets/jss/material-dashboard-react/components/headerStyle'; -const useStyles = makeStyles(styles); +const useStyles = makeStyles(styles as any); interface Route { component: any; @@ -41,7 +41,7 @@ const Header: React.FC = (props) => { return name; }; - const { color } = props; + const { color = 'primary' } = props; const appBarClasses = classNames({ [` ${classes[color]}`]: color, }); From 1feec68c0ae7c577bccedfe8835b54982d1ac473 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:24:41 +0200 Subject: [PATCH 18/33] refactor(tsx): convert Filtering Pagination and Search components --- src/ui/components/Filtering/Filtering.jsx | 66 ------------------- src/ui/components/Filtering/Filtering.tsx | 64 ++++++++++++++++++ .../{Pagination.jsx => Pagination.tsx} | 23 +++++-- src/ui/components/Search/Search.tsx | 15 +++-- 4 files changed, 90 insertions(+), 78 deletions(-) delete mode 100644 src/ui/components/Filtering/Filtering.jsx create mode 100644 src/ui/components/Filtering/Filtering.tsx rename src/ui/components/Pagination/{Pagination.jsx => Pagination.tsx} (59%) diff --git a/src/ui/components/Filtering/Filtering.jsx b/src/ui/components/Filtering/Filtering.jsx deleted file mode 100644 index aa9d26c78..000000000 --- a/src/ui/components/Filtering/Filtering.jsx +++ /dev/null @@ -1,66 +0,0 @@ -import React, { useState } from 'react'; -import './Filtering.css'; - -const Filtering = ({ onFilterChange }) => { - const [isOpen, setIsOpen] = useState(false); // State to toggle dropdown open/close - const [selectedOption, setSelectedOption] = useState('Sort by'); // Initial state - const [sortOrder, setSortOrder] = useState('asc'); // Track sort order (asc/desc) - - const toggleDropdown = () => { - setIsOpen(!isOpen); // Toggle dropdown open/close state - }; - - const toggleSortOrder = () => { - // Toggle sort order only if an option is selected - if (selectedOption !== 'Sort by') { - const newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; - setSortOrder(newSortOrder); - onFilterChange(selectedOption, newSortOrder); // Trigger filtering with new order - } - }; - - const handleOptionClick = (option) => { - // Handle filter option selection - setSelectedOption(option); - onFilterChange(option, sortOrder); // Call the parent function with selected filter and order - setIsOpen(false); // Collapse the dropdown after selection - }; - - return ( -
-
- {/* Make the entire button clickable for toggling dropdown */} - - - {isOpen && ( -
-
handleOptionClick('Date Modified')} className="dropdown-item"> - Date Modified -
-
handleOptionClick('Date Created')} className="dropdown-item"> - Date Created -
-
handleOptionClick('Alphabetical')} className="dropdown-item"> - Alphabetical -
-
- )} -
-
- ); -}; - -export default Filtering; - - - - diff --git a/src/ui/components/Filtering/Filtering.tsx b/src/ui/components/Filtering/Filtering.tsx new file mode 100644 index 000000000..0466c665e --- /dev/null +++ b/src/ui/components/Filtering/Filtering.tsx @@ -0,0 +1,64 @@ +import React, { useState } from 'react'; +import './Filtering.css'; + +export type FilterOption = 'Date Modified' | 'Date Created' | 'Alphabetical' | 'Sort by'; +export type SortOrder = 'asc' | 'desc'; + +interface FilteringProps { + onFilterChange: (option: FilterOption, order: SortOrder) => void; +} + +const Filtering: React.FC = ({ onFilterChange }) => { + const [isOpen, setIsOpen] = useState(false); + const [selectedOption, setSelectedOption] = useState('Sort by'); + const [sortOrder, setSortOrder] = useState('asc'); + + const toggleDropdown = () => { + setIsOpen(!isOpen); + }; + + const toggleSortOrder = (e: React.MouseEvent) => { + e.stopPropagation(); + if (selectedOption !== 'Sort by') { + const newSortOrder = sortOrder === 'asc' ? 'desc' : 'asc'; + setSortOrder(newSortOrder); + onFilterChange(selectedOption, newSortOrder); + } + }; + + const handleOptionClick = (option: FilterOption) => { + setSelectedOption(option); + onFilterChange(option, sortOrder); + setIsOpen(false); + }; + + return ( +
+
+ + + {isOpen && ( +
+
handleOptionClick('Date Modified')} className='dropdown-item'> + Date Modified +
+
handleOptionClick('Date Created')} className='dropdown-item'> + Date Created +
+
handleOptionClick('Alphabetical')} className='dropdown-item'> + Alphabetical +
+
+ )} +
+
+ ); +}; + +export default Filtering; diff --git a/src/ui/components/Pagination/Pagination.jsx b/src/ui/components/Pagination/Pagination.tsx similarity index 59% rename from src/ui/components/Pagination/Pagination.jsx rename to src/ui/components/Pagination/Pagination.tsx index e87e43c17..ff9742d40 100644 --- a/src/ui/components/Pagination/Pagination.jsx +++ b/src/ui/components/Pagination/Pagination.tsx @@ -1,11 +1,22 @@ import React from 'react'; -import './Pagination.css'; +import './Pagination.css'; -export default function Pagination({ currentPage, totalItems = 0, itemsPerPage, onPageChange }) { +interface PaginationProps { + currentPage: number; + totalItems?: number; + itemsPerPage: number; + onPageChange: (page: number) => void; +} +const Pagination: React.FC = ({ + currentPage, + totalItems = 0, + itemsPerPage, + onPageChange, +}) => { const totalPages = Math.ceil(totalItems / itemsPerPage); - const handlePageClick = (page) => { + const handlePageClick = (page: number) => { if (page >= 1 && page <= totalPages) { onPageChange(page); } @@ -28,10 +39,12 @@ export default function Pagination({ currentPage, totalItems = 0, itemsPerPage,
); -} +}; + +export default Pagination; \ No newline at end of file diff --git a/src/ui/components/Search/Search.tsx b/src/ui/components/Search/Search.tsx index 0e10819c8..1e30abf24 100644 --- a/src/ui/components/Search/Search.tsx +++ b/src/ui/components/Search/Search.tsx @@ -6,9 +6,10 @@ import SearchIcon from '@material-ui/icons/Search'; interface SearchProps { onSearch: (query: string) => void; + placeholder?: string; } -const Search: React.FC = ({ onSearch }) => { +const Search: React.FC = ({ onSearch, placeholder = 'Search...' }) => { const handleSearchChange = (event: React.ChangeEvent) => { const query = event.target.value; onSearch(query); @@ -17,15 +18,15 @@ const Search: React.FC = ({ onSearch }) => { return (
+ ), @@ -35,4 +36,4 @@ const Search: React.FC = ({ onSearch }) => { ); }; -export default Search; \ No newline at end of file +export default Search; From e6a75a124fac44ade143f130b306e9125c1d67c0 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:25:32 +0200 Subject: [PATCH 19/33] refactor(tsx): convert SideBar component --- .../Sidebar/{Sidebar.jsx => Sidebar.tsx} | 78 +++++++++++-------- 1 file changed, 47 insertions(+), 31 deletions(-) rename src/ui/components/Sidebar/{Sidebar.jsx => Sidebar.tsx} (70%) diff --git a/src/ui/components/Sidebar/Sidebar.jsx b/src/ui/components/Sidebar/Sidebar.tsx similarity index 70% rename from src/ui/components/Sidebar/Sidebar.jsx rename to src/ui/components/Sidebar/Sidebar.tsx index 174e31a4e..ac23bab1f 100644 --- a/src/ui/components/Sidebar/Sidebar.jsx +++ b/src/ui/components/Sidebar/Sidebar.tsx @@ -1,6 +1,5 @@ import React from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { NavLink } from 'react-router-dom'; import { makeStyles } from '@material-ui/core/styles'; import Drawer from '@material-ui/core/Drawer'; @@ -11,30 +10,53 @@ import ListItemText from '@material-ui/core/ListItemText'; import Icon from '@material-ui/core/Icon'; import styles from '../../assets/jss/material-dashboard-react/components/sidebarStyle'; -const useStyles = makeStyles(styles); +const useStyles = makeStyles(styles as any); -export default function Sidebar(props) { +interface Route { + path: string; + layout: string; + name: string; + icon: string | React.ComponentType; + visible?: boolean; + rtlName?: string; + component: React.ComponentType; +} + +interface SidebarProps { + color: 'purple' | 'blue' | 'green' | 'orange' | 'red'; + logo: string; + routes: Route[]; + background: string; + rtlActive?: boolean; + handleDrawerToggle: () => void; + open: boolean; +} + +const Sidebar: React.FC = (props) => { const classes = useStyles(); - // verifies if routeName is the one active (in browser input) - function activeRoute(routeName) { - return window.location.href.indexOf(routeName) > -1 ? true : false; - } - const { color, logo, routes, background } = props; + + const activeRoute = (routeName: string): boolean => { + return window.location.href.indexOf(routeName) > -1; + }; + + const { color, logo, routes, background, rtlActive, open, handleDrawerToggle } = props; + const links = ( {routes.map((prop, key) => { const activePro = ' '; const listItemClasses = classNames({ - [' ' + classes[color]]: activeRoute(prop.layout + prop.path), + [` ${classes[color]}`]: activeRoute(prop.layout + prop.path), }); const whiteFontClasses = classNames({ - [' ' + classes.whiteFont]: activeRoute(prop.layout + prop.path), + [` ${classes.whiteFont}`]: activeRoute(prop.layout + prop.path), }); if (!prop.visible) { return
; } + return ( {prop.icon} @@ -54,14 +76,14 @@ export default function Sidebar(props) { ) : ( )} @@ -71,6 +93,7 @@ export default function Sidebar(props) { })}
); + const brand = ( ); + return (
{brand} @@ -109,12 +133,12 @@ export default function Sidebar(props) { @@ -125,14 +149,6 @@ export default function Sidebar(props) {
); -} - -Sidebar.propTypes = { - rtlActive: PropTypes.bool, - handleDrawerToggle: PropTypes.func, - bgColor: PropTypes.oneOf(['purple', 'blue', 'green', 'orange', 'red']), - logo: PropTypes.string, - image: PropTypes.string, - routes: PropTypes.arrayOf(PropTypes.object), - open: PropTypes.bool, }; + +export default Sidebar; From ba2036cd4f30d779b44f79c7fe7a0bbfe1505b0f Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:25:46 +0200 Subject: [PATCH 20/33] refactor(tsx): convert CustomTabs component --- .../{CustomTabs.jsx => CustomTabs.tsx} | 76 ++++++++++--------- 1 file changed, 40 insertions(+), 36 deletions(-) rename src/ui/components/CustomTabs/{CustomTabs.jsx => CustomTabs.tsx} (59%) diff --git a/src/ui/components/CustomTabs/CustomTabs.jsx b/src/ui/components/CustomTabs/CustomTabs.tsx similarity index 59% rename from src/ui/components/CustomTabs/CustomTabs.jsx rename to src/ui/components/CustomTabs/CustomTabs.tsx index ad125b00c..8f9daf3a3 100644 --- a/src/ui/components/CustomTabs/CustomTabs.jsx +++ b/src/ui/components/CustomTabs/CustomTabs.tsx @@ -1,28 +1,50 @@ -import React from 'react'; +import React, { useState } from 'react'; import classNames from 'classnames'; -import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; import Card from '../Card/Card'; import CardBody from '../Card/CardBody'; import CardHeader from '../Card/CardHeader'; - import styles from '../../assets/jss/material-dashboard-react/components/customTabsStyle'; -const useStyles = makeStyles(styles); +const useStyles = makeStyles(styles as any); -export default function CustomTabs(props) { - const [value, setValue] = React.useState(0); - const handleChange = (event, val) => { - setValue(val); - }; +type HeaderColor = 'warning' | 'success' | 'danger' | 'info' | 'primary' | 'rose'; + +interface TabItem { + tabName: string; + tabIcon?: React.ComponentType; + tabContent: React.ReactNode; +} + +interface CustomTabsProps { + headerColor?: HeaderColor; + title?: string; + tabs: TabItem[]; + rtlActive?: boolean; + plainTabs?: boolean; +} + +const CustomTabs: React.FC = ({ + headerColor = 'primary', + plainTabs = false, + tabs, + title, + rtlActive = false, +}) => { + const [value, setValue] = useState(0); const classes = useStyles(); - const { headerColor, plainTabs, tabs, title, rtlActive } = props; + + const handleChange = (event: React.ChangeEvent, newValue: number) => { + setValue(newValue); + }; + const cardTitle = classNames({ [classes.cardTitle]: true, [classes.cardTitleRTL]: rtlActive, }); + return ( @@ -39,12 +61,7 @@ export default function CustomTabs(props) { scrollButtons='auto' > {tabs.map((prop, key) => { - let icon = {}; - if (prop.tabIcon) { - icon = { - icon: , - }; - } + const icon = prop.tabIcon ? { icon: } : {}; return ( - {tabs.map((prop, key) => { - if (key === value) { - return
{prop.tabContent}
; - } - return null; - })} + {tabs.map((prop, key) => ( +
+ {prop.tabContent} +
+ ))}
); -} - -CustomTabs.propTypes = { - headerColor: PropTypes.oneOf(['warning', 'success', 'danger', 'info', 'primary', 'rose']), - title: PropTypes.string, - tabs: PropTypes.arrayOf( - PropTypes.shape({ - tabName: PropTypes.string.isRequired, - tabIcon: PropTypes.object, - tabContent: PropTypes.node.isRequired, - }), - ), - rtlActive: PropTypes.bool, - plainTabs: PropTypes.bool, }; + +export default CustomTabs; From e7435431c75bb7caec5e1d7b937cefa31254156b Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:26:41 +0200 Subject: [PATCH 21/33] fix(tsx): fixing colors and size --- src/ui/components/Card/CardHeader.tsx | 2 +- src/ui/components/CustomButtons/Button.tsx | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/ui/components/Card/CardHeader.tsx b/src/ui/components/Card/CardHeader.tsx index 0a01c3df6..c893715cc 100644 --- a/src/ui/components/Card/CardHeader.tsx +++ b/src/ui/components/Card/CardHeader.tsx @@ -5,7 +5,7 @@ import styles from '../../assets/jss/material-dashboard-react/components/cardHea const useStyles = makeStyles(styles); -type CardHeaderColor = 'warning' | 'success' | 'danger' | 'info' | 'primary' | 'rose'; +export type CardHeaderColor = 'warning' | 'success' | 'danger' | 'info' | 'primary' | 'rose'; interface CardHeaderProps extends React.ComponentProps<'div'> { className?: string; diff --git a/src/ui/components/CustomButtons/Button.tsx b/src/ui/components/CustomButtons/Button.tsx index 473a3caca..01e8e0eb1 100644 --- a/src/ui/components/CustomButtons/Button.tsx +++ b/src/ui/components/CustomButtons/Button.tsx @@ -17,12 +17,12 @@ type Color = | 'transparent'; type Size = 'sm' | 'lg'; -interface RegularButtonProps extends ButtonProps { - customColor?: Color; +interface RegularButtonProps extends Omit { + color?: Color; round?: boolean; disabled?: boolean; simple?: boolean; - customSize?: Size; + size?: Size; block?: boolean; link?: boolean; justIcon?: boolean; From 0bac84ffada3b9d15cd1142b0b7a716c650d3cab Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:27:06 +0200 Subject: [PATCH 22/33] refactor(tsx): convert Styles to ts --- src/ui/assets/jss/material-dashboard-react.js | 2 +- ...{customTabsStyle.js => customTabsStyle.ts} | 28 +- .../components/sidebarStyle.js | 301 ------------------ 3 files changed, 23 insertions(+), 308 deletions(-) rename src/ui/assets/jss/material-dashboard-react/components/{customTabsStyle.js => customTabsStyle.ts} (66%) delete mode 100644 src/ui/assets/jss/material-dashboard-react/components/sidebarStyle.js diff --git a/src/ui/assets/jss/material-dashboard-react.js b/src/ui/assets/jss/material-dashboard-react.js index 1cc9c2cd6..c98035c62 100644 --- a/src/ui/assets/jss/material-dashboard-react.js +++ b/src/ui/assets/jss/material-dashboard-react.js @@ -60,7 +60,7 @@ const container = { const defaultFont = { fontFamily: '"Roboto", "Helvetica", "Arial", sans-serif', - fontWeight: '300', + fontWeight: 300, lineHeight: '1.5em', }; diff --git a/src/ui/assets/jss/material-dashboard-react/components/customTabsStyle.js b/src/ui/assets/jss/material-dashboard-react/components/customTabsStyle.ts similarity index 66% rename from src/ui/assets/jss/material-dashboard-react/components/customTabsStyle.js rename to src/ui/assets/jss/material-dashboard-react/components/customTabsStyle.ts index a686e7b08..ca9f3b01b 100644 --- a/src/ui/assets/jss/material-dashboard-react/components/customTabsStyle.js +++ b/src/ui/assets/jss/material-dashboard-react/components/customTabsStyle.ts @@ -1,6 +1,22 @@ -import { hexToRgb, whiteColor } from '../../material-dashboard-react.js'; +import { hexToRgb, whiteColor } from '../../material-dashboard-react'; -const customTabsStyle = { +interface CustomTabsStyle { + cardTitle: React.CSSProperties; + cardTitleRTL: React.CSSProperties; + displayNone: React.CSSProperties; + tabsRoot: React.CSSProperties & { + '& $tabRootButton': React.CSSProperties; + }; + tabRootButton: React.CSSProperties & { + '&:last-child': React.CSSProperties; + }; + tabSelected: React.CSSProperties; + tabWrapper: React.CSSProperties & { + '& > svg, & > .material-icons': React.CSSProperties; + }; +} + +const customTabsStyle: CustomTabsStyle = { cardTitle: { float: 'left', padding: '10px 10px 10px 0px', @@ -31,14 +47,14 @@ const customTabsStyle = { borderRadius: '3px', lineHeight: '24px', border: '0 !important', - color: whiteColor + ' !important', + color: `${whiteColor} !important`, marginLeft: '4px', '&:last-child': { marginLeft: '0px', }, }, tabSelected: { - backgroundColor: 'rgba(' + hexToRgb(whiteColor) + ', 0.2)', + backgroundColor: `rgba(${hexToRgb(whiteColor)}, 0.2)`, transition: '0.2s background-color 0.1s', }, tabWrapper: { @@ -52,11 +68,11 @@ const customTabsStyle = { fontWeight: '500', fontSize: '12px', marginTop: '1px', - '& > svg,& > .material-icons': { + '& > svg, & > .material-icons': { verticalAlign: 'middle', margin: '-1px 5px 0 0 !important', }, }, }; -export default customTabsStyle; +export default customTabsStyle; \ No newline at end of file diff --git a/src/ui/assets/jss/material-dashboard-react/components/sidebarStyle.js b/src/ui/assets/jss/material-dashboard-react/components/sidebarStyle.js deleted file mode 100644 index fddf94b48..000000000 --- a/src/ui/assets/jss/material-dashboard-react/components/sidebarStyle.js +++ /dev/null @@ -1,301 +0,0 @@ -import { - drawerWidth, - transition, - boxShadow, - defaultFont, - primaryColor, - primaryBoxShadow, - infoColor, - successColor, - warningColor, - dangerColor, - whiteColor, - grayColor, - blackColor, - hexToRgb, -} from '../../material-dashboard-react.js'; - -const sidebarStyle = (theme) => ({ - drawerPaper: { - border: 'none', - position: 'fixed', - top: '0', - bottom: '0', - left: '0', - zIndex: '1', - ...boxShadow, - width: drawerWidth, - [theme.breakpoints.up('md')]: { - width: drawerWidth, - position: 'fixed', - height: '100%', - }, - [theme.breakpoints.down('sm')]: { - width: drawerWidth, - ...boxShadow, - position: 'fixed', - display: 'block', - top: '0', - height: '100vh', - right: '0', - left: 'auto', - zIndex: '1032', - visibility: 'visible', - overflowY: 'visible', - borderTop: 'none', - textAlign: 'left', - paddingRight: '0px', - paddingLeft: '0', - transform: `translate3d(${drawerWidth}px, 0, 0)`, - ...transition, - }, - }, - drawerPaperRTL: { - [theme.breakpoints.up('md')]: { - left: 'auto !important', - right: '0 !important', - }, - [theme.breakpoints.down('sm')]: { - left: '0 !important', - right: 'auto !important', - }, - }, - logo: { - position: 'relative', - padding: '20px 20px', - zIndex: '4', - '&:after': { - content: '""', - position: 'absolute', - bottom: '0', - - height: '1px', - right: '15px', - width: 'calc(100% - 30px)', - backgroundColor: 'rgba(' + hexToRgb(grayColor[6]) + ', 0.3)', - }, - }, - logoLink: { - ...defaultFont, - textTransform: 'uppercase', - padding: '5px 0', - display: 'block', - fontSize: '18px', - textAlign: 'left', - fontWeight: '400', - lineHeight: '30px', - textDecoration: 'none', - backgroundColor: 'transparent', - '&,&:hover': { - color: whiteColor, - }, - }, - logoLinkRTL: { - textAlign: 'right', - }, - logoImage: { - width: '30px', - display: 'inline-block', - maxHeight: '30px', - marginLeft: '10px', - marginRight: '15px', - }, - img: { - width: '35px', - top: '22px', - position: 'absolute', - verticalAlign: 'middle', - border: '0', - }, - background: { - position: 'absolute', - zIndex: '1', - height: '100%', - width: '100%', - display: 'block', - top: '0', - left: '0', - backgroundSize: 'cover', - backgroundPosition: 'center center', - '&:after': { - position: 'absolute', - zIndex: '3', - width: '100%', - height: '100%', - content: '""', - display: 'block', - background: blackColor, - opacity: '.8', - }, - }, - list: { - marginTop: '20px', - paddingLeft: '0', - paddingTop: '0', - paddingBottom: '0', - marginBottom: '0', - listStyle: 'none', - position: 'unset', - }, - item: { - position: 'relative', - display: 'block', - textDecoration: 'none', - '&:hover,&:focus,&:visited,&': { - color: whiteColor, - }, - }, - itemLink: { - width: 'auto', - transition: 'all 300ms linear', - margin: '10px 15px 0', - borderRadius: '3px', - position: 'relative', - display: 'block', - padding: '10px 15px', - backgroundColor: 'transparent', - ...defaultFont, - }, - itemIcon: { - width: '24px', - height: '30px', - fontSize: '24px', - lineHeight: '30px', - float: 'left', - marginRight: '15px', - textAlign: 'center', - verticalAlign: 'middle', - color: 'rgba(' + hexToRgb(whiteColor) + ', 0.8)', - }, - itemIconRTL: { - marginRight: '3px', - marginLeft: '15px', - float: 'right', - }, - itemText: { - ...defaultFont, - margin: '0', - lineHeight: '30px', - fontSize: '14px', - color: whiteColor, - }, - itemTextRTL: { - textAlign: 'right', - }, - whiteFont: { - color: whiteColor, - }, - purple: { - backgroundColor: primaryColor[0], - ...primaryBoxShadow, - '&:hover,&:focus': { - backgroundColor: primaryColor[0], - ...primaryBoxShadow, - }, - }, - blue: { - backgroundColor: infoColor[0], - boxShadow: - '0 12px 20px -10px rgba(' + - hexToRgb(infoColor[0]) + - ',.28), 0 4px 20px 0 rgba(' + - hexToRgb(blackColor) + - ',.12), 0 7px 8px -5px rgba(' + - hexToRgb(infoColor[0]) + - ',.2)', - '&:hover,&:focus': { - backgroundColor: infoColor[0], - boxShadow: - '0 12px 20px -10px rgba(' + - hexToRgb(infoColor[0]) + - ',.28), 0 4px 20px 0 rgba(' + - hexToRgb(blackColor) + - ',.12), 0 7px 8px -5px rgba(' + - hexToRgb(infoColor[0]) + - ',.2)', - }, - }, - green: { - backgroundColor: successColor[0], - boxShadow: - '0 12px 20px -10px rgba(' + - hexToRgb(successColor[0]) + - ',.28), 0 4px 20px 0 rgba(' + - hexToRgb(blackColor) + - ',.12), 0 7px 8px -5px rgba(' + - hexToRgb(successColor[0]) + - ',.2)', - '&:hover,&:focus': { - backgroundColor: successColor[0], - boxShadow: - '0 12px 20px -10px rgba(' + - hexToRgb(successColor[0]) + - ',.28), 0 4px 20px 0 rgba(' + - hexToRgb(blackColor) + - ',.12), 0 7px 8px -5px rgba(' + - hexToRgb(successColor[0]) + - ',.2)', - }, - }, - orange: { - backgroundColor: warningColor[0], - boxShadow: - '0 12px 20px -10px rgba(' + - hexToRgb(warningColor[0]) + - ',.28), 0 4px 20px 0 rgba(' + - hexToRgb(blackColor) + - ',.12), 0 7px 8px -5px rgba(' + - hexToRgb(warningColor[0]) + - ',.2)', - '&:hover,&:focus': { - backgroundColor: warningColor[0], - boxShadow: - '0 12px 20px -10px rgba(' + - hexToRgb(warningColor[0]) + - ',.28), 0 4px 20px 0 rgba(' + - hexToRgb(blackColor) + - ',.12), 0 7px 8px -5px rgba(' + - hexToRgb(warningColor[0]) + - ',.2)', - }, - }, - red: { - backgroundColor: dangerColor[0], - boxShadow: - '0 12px 20px -10px rgba(' + - hexToRgb(dangerColor[0]) + - ',.28), 0 4px 20px 0 rgba(' + - hexToRgb(blackColor) + - ',.12), 0 7px 8px -5px rgba(' + - hexToRgb(dangerColor[0]) + - ',.2)', - '&:hover,&:focus': { - backgroundColor: dangerColor[0], - boxShadow: - '0 12px 20px -10px rgba(' + - hexToRgb(dangerColor[0]) + - ',.28), 0 4px 20px 0 rgba(' + - hexToRgb(blackColor) + - ',.12), 0 7px 8px -5px rgba(' + - hexToRgb(dangerColor[0]) + - ',.2)', - }, - }, - sidebarWrapper: { - position: 'relative', - height: 'calc(100vh - 75px)', - overflow: 'auto', - width: '260px', - zIndex: '4', - overflowScrolling: 'touch', - }, - activePro: { - [theme.breakpoints.up('md')]: { - position: 'absolute', - width: '100%', - bottom: '13px', - }, - }, -}); - -export default sidebarStyle; From 20ab300a6052ca0e61363fedcb2efb2d976d42f3 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:27:59 +0200 Subject: [PATCH 23/33] refactor(tsx): convert SideBar Style to ts --- .../components/sidebarStyle.ts | 297 ++++++++++++++++++ 1 file changed, 297 insertions(+) create mode 100644 src/ui/assets/jss/material-dashboard-react/components/sidebarStyle.ts diff --git a/src/ui/assets/jss/material-dashboard-react/components/sidebarStyle.ts b/src/ui/assets/jss/material-dashboard-react/components/sidebarStyle.ts new file mode 100644 index 000000000..e0b2bae7e --- /dev/null +++ b/src/ui/assets/jss/material-dashboard-react/components/sidebarStyle.ts @@ -0,0 +1,297 @@ +import { Theme, createStyles } from '@material-ui/core/styles'; +import { + drawerWidth, + transition, + boxShadow, + defaultFont, + primaryColor, + primaryBoxShadow, + infoColor, + successColor, + warningColor, + dangerColor, + whiteColor, + grayColor, + blackColor, + hexToRgb, +} from '../../material-dashboard-react'; + +interface SidebarStyle { + drawerPaper: React.CSSProperties & { + [key: string]: any; + '&:after'?: React.CSSProperties; + }; + drawerPaperRTL: { + [key: string]: React.CSSProperties; + }; + logo: React.CSSProperties & { + '&:after': React.CSSProperties; + }; + logoLink: React.CSSProperties & { + '&,&:hover': React.CSSProperties; + }; + logoLinkRTL: React.CSSProperties; + logoImage: React.CSSProperties; + img: React.CSSProperties; + background: React.CSSProperties & { + '&:after': React.CSSProperties; + }; + list: React.CSSProperties; + item: React.CSSProperties & { + '&:hover,&:focus,&:visited,&': React.CSSProperties; + }; + itemLink: React.CSSProperties; + itemIcon: React.CSSProperties; + itemIconRTL: React.CSSProperties; + itemText: React.CSSProperties; + itemTextRTL: React.CSSProperties; + whiteFont: React.CSSProperties; + purple: React.CSSProperties & { + '&:hover,&:focus': React.CSSProperties; + }; + blue: React.CSSProperties & { + '&:hover,&:focus': React.CSSProperties; + }; + green: React.CSSProperties & { + '&:hover,&:focus': React.CSSProperties; + }; + orange: React.CSSProperties & { + '&:hover,&:focus': React.CSSProperties; + }; + red: React.CSSProperties & { + '&:hover,&:focus': React.CSSProperties; + }; + sidebarWrapper: React.CSSProperties; + activePro: { + [key: string]: React.CSSProperties; + }; +} + +const sidebarStyle = (theme: Theme) => + createStyles({ + drawerPaper: { + border: 'none', + position: 'fixed', + top: '0', + bottom: '0', + left: '0', + zIndex: 1, + ...boxShadow, + width: drawerWidth, + [theme.breakpoints.up('md')]: { + width: drawerWidth, + position: 'fixed', + height: '100%', + }, + [theme.breakpoints.down('sm')]: { + width: drawerWidth, + ...boxShadow, + position: 'fixed', + display: 'block', + top: '0', + height: '100vh', + right: '0', + left: 'auto', + zIndex: '1032', + visibility: 'visible', + overflowY: 'visible', + borderTop: 'none', + textAlign: 'left', + paddingRight: '0px', + paddingLeft: '0', + transform: `translate3d(${drawerWidth}px, 0, 0)`, + ...transition, + }, + }, + drawerPaperRTL: { + [theme.breakpoints.up('md')]: { + left: 'auto !important', + right: '0 !important', + }, + [theme.breakpoints.down('sm')]: { + left: '0 !important', + right: 'auto !important', + }, + }, + logo: { + position: 'relative', + padding: '20px 20px', + zIndex: 4, + '&:after': { + content: '""', + position: 'absolute', + bottom: '0', + height: '1px', + right: '15px', + width: 'calc(100% - 30px)', + backgroundColor: `rgba(${hexToRgb(grayColor[6])}, 0.3)`, + }, + }, + logoLink: { + ...defaultFont, + textTransform: 'uppercase', + padding: '5px 0', + display: 'block', + fontSize: '18px', + textAlign: 'left', + fontWeight: 400, + lineHeight: '30px', + textDecoration: 'none', + backgroundColor: 'transparent', + '&,&:hover': { + color: whiteColor, + }, + }, + logoLinkRTL: { + textAlign: 'right', + }, + logoImage: { + width: '30px', + display: 'inline-block', + maxHeight: '30px', + marginLeft: '10px', + marginRight: '15px', + }, + img: { + width: '35px', + top: '22px', + position: 'absolute', + verticalAlign: 'middle', + border: '0', + }, + background: { + position: 'absolute', + zIndex: 1, + height: '100%', + width: '100%', + display: 'block', + top: '0', + left: '0', + backgroundSize: 'cover', + backgroundPosition: 'center center', + '&:after': { + position: 'absolute', + zIndex: '3', + width: '100%', + height: '100%', + content: '""', + display: 'block', + background: blackColor, + opacity: '.8', + }, + }, + list: { + marginTop: '20px', + paddingLeft: '0', + paddingTop: '0', + paddingBottom: '0', + marginBottom: '0', + listStyle: 'none', + position: 'unset', + }, + item: { + position: 'relative', + display: 'block', + textDecoration: 'none', + '&:hover,&:focus,&:visited,&': { + color: whiteColor, + }, + }, + itemLink: { + width: 'auto', + transition: 'all 300ms linear', + margin: '10px 15px 0', + borderRadius: '3px', + position: 'relative', + display: 'block', + padding: '10px 15px', + backgroundColor: 'transparent', + ...defaultFont, + }, + itemIcon: { + width: '24px', + height: '30px', + fontSize: '24px', + lineHeight: '30px', + float: 'left', + marginRight: '15px', + textAlign: 'center', + verticalAlign: 'middle', + color: `rgba(${hexToRgb(whiteColor)}, 0.8)`, + }, + itemIconRTL: { + marginRight: '3px', + marginLeft: '15px', + float: 'right', + }, + itemText: { + ...defaultFont, + margin: '0', + lineHeight: '30px', + fontSize: '14px', + color: whiteColor, + }, + itemTextRTL: { + textAlign: 'right', + }, + whiteFont: { + color: whiteColor, + }, + purple: { + backgroundColor: primaryColor[0], + ...primaryBoxShadow, + '&:hover,&:focus': { + backgroundColor: primaryColor[0], + ...primaryBoxShadow, + }, + }, + blue: { + backgroundColor: infoColor[0], + boxShadow: `0 12px 20px -10px rgba(${hexToRgb(infoColor[0])},.28), 0 4px 20px 0 rgba(${hexToRgb(blackColor)},.12), 0 7px 8px -5px rgba(${hexToRgb(infoColor[0])},.2)`, + '&:hover,&:focus': { + backgroundColor: infoColor[0], + boxShadow: `0 12px 20px -10px rgba(${hexToRgb(infoColor[0])},.28), 0 4px 20px 0 rgba(${hexToRgb(blackColor)},.12), 0 7px 8px -5px rgba(${hexToRgb(infoColor[0])},.2)`, + }, + }, + green: { + backgroundColor: successColor[0], + boxShadow: `0 12px 20px -10px rgba(${hexToRgb(successColor[0])},.28), 0 4px 20px 0 rgba(${hexToRgb(blackColor)},.12), 0 7px 8px -5px rgba(${hexToRgb(successColor[0])},.2)`, + '&:hover,&:focus': { + backgroundColor: successColor[0], + boxShadow: `0 12px 20px -10px rgba(${hexToRgb(successColor[0])},.28), 0 4px 20px 0 rgba(${hexToRgb(blackColor)},.12), 0 7px 8px -5px rgba(${hexToRgb(successColor[0])},.2)`, + }, + }, + orange: { + backgroundColor: warningColor[0], + boxShadow: `0 12px 20px -10px rgba(${hexToRgb(warningColor[0])},.28), 0 4px 20px 0 rgba(${hexToRgb(blackColor)},.12), 0 7px 8px -5px rgba(${hexToRgb(warningColor[0])},.2)`, + '&:hover,&:focus': { + backgroundColor: warningColor[0], + boxShadow: `0 12px 20px -10px rgba(${hexToRgb(warningColor[0])},.28), 0 4px 20px 0 rgba(${hexToRgb(blackColor)},.12), 0 7px 8px -5px rgba(${hexToRgb(warningColor[0])},.2)`, + }, + }, + red: { + backgroundColor: dangerColor[0], + boxShadow: `0 12px 20px -10px rgba(${hexToRgb(dangerColor[0])},.28), 0 4px 20px 0 rgba(${hexToRgb(blackColor)},.12), 0 7px 8px -5px rgba(${hexToRgb(dangerColor[0])},.2)`, + '&:hover,&:focus': { + backgroundColor: dangerColor[0], + boxShadow: `0 12px 20px -10px rgba(${hexToRgb(dangerColor[0])},.28), 0 4px 20px 0 rgba(${hexToRgb(blackColor)},.12), 0 7px 8px -5px rgba(${hexToRgb(dangerColor[0])},.2)`, + }, + }, + sidebarWrapper: { + position: 'relative', + height: 'calc(100vh - 75px)', + overflow: 'auto', + width: '260px', + zIndex: 4, + WebkitOverflowScrolling: 'touch', + }, + activePro: { + [theme.breakpoints.up('md')]: { + position: 'absolute', + width: '100%', + bottom: '13px', + }, + }, + }); + +export default sidebarStyle; From f76c65950fca9470d15f21b2cf2a8d029c22a8e7 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:28:39 +0200 Subject: [PATCH 24/33] refactor(tsx): convert Login view --- src/ui/views/Login/{Login.jsx => Login.tsx} | 98 +++++++++++++-------- 1 file changed, 59 insertions(+), 39 deletions(-) rename src/ui/views/Login/{Login.jsx => Login.tsx} (70%) diff --git a/src/ui/views/Login/Login.jsx b/src/ui/views/Login/Login.tsx similarity index 70% rename from src/ui/views/Login/Login.jsx rename to src/ui/views/Login/Login.tsx index 719714ec2..99a203c52 100644 --- a/src/ui/views/Login/Login.jsx +++ b/src/ui/views/Login/Login.tsx @@ -1,8 +1,6 @@ -import React, { useState } from 'react'; -// @material-ui/core components +import React, { useState, FormEvent } from 'react'; import FormControl from '@material-ui/core/FormControl'; import InputLabel from '@material-ui/core/InputLabel'; -// core components import GridItem from '../../components/Grid/GridItem'; import GridContainer from '../../components/Grid/GridContainer'; import Input from '@material-ui/core/Input'; @@ -11,75 +9,81 @@ import Card from '../../components/Card/Card'; import CardHeader from '../../components/Card/CardHeader'; import CardBody from '../../components/Card/CardBody'; import CardFooter from '../../components/Card/CardFooter'; -import axios from 'axios'; +import axios, { AxiosError } from 'axios'; import { Navigate } from 'react-router-dom'; import logo from '../../assets/img/git-proxy.png'; import { Badge, CircularProgress, Snackbar } from '@material-ui/core'; import { getCookie } from '../../utils'; +interface LoginResponse { + username: string; + password: string; +} + const loginUrl = `${import.meta.env.VITE_API_URI}/api/auth/login`; -export default function UserProfile() { - const [username, setUsername] = useState(''); - const [password, setPassword] = useState(''); - const [message, setMessage] = useState(''); - const [success, setSuccess] = useState(false); - const [gitAccountError, setGitAccountError] = useState(false); - const [isLoading, setIsLoading] = useState(false); +const UserProfile: React.FC = () => { + const [username, setUsername] = useState(''); + const [password, setPassword] = useState(''); + const [message, setMessage] = useState(''); + const [success, setSuccess] = useState(false); + const [gitAccountError, setGitAccountError] = useState(false); + const [isLoading, setIsLoading] = useState(false); - function validateForm() { + function validateForm(): boolean { return ( username.length > 0 && username.length < 100 && password.length > 0 && password.length < 200 ); } - function handleOIDCLogin() { + function handleOIDCLogin(): void { window.location.href = `${import.meta.env.VITE_API_URI}/api/auth/oidc`; } - function handleSubmit(event) { + function handleSubmit(event: FormEvent): void { + event.preventDefault(); setIsLoading(true); + axios - .post( + .post( loginUrl, { - username: username, - password: password, + username, + password, }, { withCredentials: true, headers: { 'Content-Type': 'application/json', - 'X-CSRF-TOKEN': getCookie('csrf'), + 'X-CSRF-TOKEN': getCookie('csrf') || '', }, }, ) - .then(function () { + .then(() => { window.sessionStorage.setItem('git.proxy.login', 'success'); setMessage('Success!'); setSuccess(true); - setIsLoading(false); }) - .catch(function (error) { - if (error.response.status === 307) { + .catch((error: AxiosError) => { + if (error.response?.status === 307) { window.sessionStorage.setItem('git.proxy.login', 'success'); setGitAccountError(true); - } else if (error.response.status === 403) { + } else if (error.response?.status === 403) { setMessage('You do not have the correct access permissions...'); } else { setMessage('You entered an invalid username or password...'); } + }) + .finally(() => { setIsLoading(false); }); - - event.preventDefault(); } if (gitAccountError) { - return ; + return ; } if (success) { - return ; + return ; } return ( @@ -114,24 +118,24 @@ export default function UserProfile() { - - - Username + + + Username setUsername(e.target.value)} - autoFocus={true} + autoFocus data-test='username' /> - - - Password + + + Password ) : ( -
+
)} -
- {' '} +
+ View our open source activity feed or{' '} scroll through projects we contribute to @@ -177,4 +195,6 @@ export default function UserProfile() { ); -} +}; + +export default UserProfile; From 90299cefe19514234a2dbda1f7e2a2ed8496805d Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:29:02 +0200 Subject: [PATCH 25/33] refactor(tsx): convert OpenPushRequests view --- .../OpenPushRequests/OpenPushRequests.jsx | 50 -------- .../OpenPushRequests/OpenPushRequests.tsx | 60 ++++++++++ .../{PushesTable.jsx => PushesTable.tsx} | 107 +++++++++++------- 3 files changed, 129 insertions(+), 88 deletions(-) delete mode 100644 src/ui/views/OpenPushRequests/OpenPushRequests.jsx create mode 100644 src/ui/views/OpenPushRequests/OpenPushRequests.tsx rename src/ui/views/OpenPushRequests/components/{PushesTable.jsx => PushesTable.tsx} (69%) diff --git a/src/ui/views/OpenPushRequests/OpenPushRequests.jsx b/src/ui/views/OpenPushRequests/OpenPushRequests.jsx deleted file mode 100644 index f46f96715..000000000 --- a/src/ui/views/OpenPushRequests/OpenPushRequests.jsx +++ /dev/null @@ -1,50 +0,0 @@ -import React from 'react'; -import GridItem from '../../components/Grid/GridItem'; -import GridContainer from '../../components/Grid/GridContainer'; -import PushesTable from './components/PushesTable'; -import CustomTabs from '../../components/CustomTabs/CustomTabs'; - -import { Visibility, CheckCircle, Cancel, Block } from '@material-ui/icons'; - -export default function Dashboard() { - return ( -
- - - - ), - }, - { - tabName: 'Approved', - tabIcon: CheckCircle, - tabContent: , - }, - { - tabName: 'Canceled', - tabIcon: Cancel, - tabContent: , - }, - { - tabName: 'Rejected', - tabIcon: Block, - tabContent: , - }, - ]} - /> - - -
- ); -} diff --git a/src/ui/views/OpenPushRequests/OpenPushRequests.tsx b/src/ui/views/OpenPushRequests/OpenPushRequests.tsx new file mode 100644 index 000000000..bd332237e --- /dev/null +++ b/src/ui/views/OpenPushRequests/OpenPushRequests.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import GridItem from '../../components/Grid/GridItem'; +import GridContainer from '../../components/Grid/GridContainer'; +import PushesTable from './components/PushesTable'; +import CustomTabs from '../../components/CustomTabs/CustomTabs'; +import { Visibility, CheckCircle, Cancel, Block } from '@material-ui/icons'; +import { SvgIconProps } from '@material-ui/core'; + +interface TabConfig { + tabName: string; + tabIcon: React.ComponentType; + tabContent: React.ReactNode; +} + +const Dashboard: React.FC = () => { + const tabs: TabConfig[] = [ + { + tabName: 'Pending', + tabIcon: Visibility, + tabContent: ( + + ), + }, + { + tabName: 'Approved', + tabIcon: CheckCircle, + tabContent: , + }, + { + tabName: 'Canceled', + tabIcon: Cancel, + tabContent: , + }, + { + tabName: 'Rejected', + tabIcon: Block, + tabContent: , + }, + ]; + + return ( +
+ + + + + +
+ ); +}; + +export default Dashboard; \ No newline at end of file diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.jsx b/src/ui/views/OpenPushRequests/components/PushesTable.tsx similarity index 69% rename from src/ui/views/OpenPushRequests/components/PushesTable.jsx rename to src/ui/views/OpenPushRequests/components/PushesTable.tsx index 2b7213c60..530831bc2 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.jsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.tsx @@ -16,11 +16,33 @@ import { KeyboardArrowRight } from '@material-ui/icons'; import Search from '../../../components/Search/Search'; import Pagination from '../../../components/Pagination/Pagination'; -export default function PushesTable(props) { - const useStyles = makeStyles(styles); +interface CommitData { + commitTs?: number; + commitTimestamp?: number; + message: string; + committer: string; + author: string; + authorEmail?: string; +} + +interface PushData { + id: string; + repo: string; + branch: string; + commitTo: string; + commitData: CommitData[]; +} + +interface PushesTableProps { + [key: string]: any; +} + +const useStyles = makeStyles(styles as any); + +const PushesTable: React.FC = (props) => { const classes = useStyles(); - const [data, setData] = useState([]); - const [filteredData, setFilteredData] = useState([]); + const [data, setData] = useState([]); + const [filteredData, setFilteredData] = useState([]); const [isLoading, setIsLoading] = useState(false); const [isError, setIsError] = useState(false); const navigate = useNavigate(); @@ -28,13 +50,16 @@ export default function PushesTable(props) { const [currentPage, setCurrentPage] = useState(1); const itemsPerPage = 5; const [searchTerm, setSearchTerm] = useState(''); - const openPush = (push) => navigate(`/admin/push/${push}`, { replace: true }); + + const openPush = (pushId: string) => navigate(`/admin/push/${pushId}`, { replace: true }); useEffect(() => { - const query = {}; - for (const k in props) { - if (k) query[k] = props[k]; - } + const query = { + blocked: props.blocked ?? false, + canceled: props.canceled ?? false, + authorised: props.authorised ?? false, + rejected: props.rejected ?? false, + }; getPushes(setIsLoading, setData, setAuth, setIsError, query); }, [props]); @@ -49,16 +74,16 @@ export default function PushesTable(props) { (item) => item.repo.toLowerCase().includes(lowerCaseTerm) || item.commitTo.toLowerCase().includes(lowerCaseTerm) || - item.commitData[0].message.toLowerCase().includes(lowerCaseTerm), + item.commitData[0]?.message.toLowerCase().includes(lowerCaseTerm), ) : data; setFilteredData(filtered); setCurrentPage(1); }, [searchTerm, data]); - const handleSearch = (term) => setSearchTerm(term.trim()); + const handleSearch = (term: string) => setSearchTerm(term.trim()); - const handlePageChange = (page) => { + const handlePageChange = (page: number) => { setCurrentPage(page); }; @@ -66,14 +91,12 @@ export default function PushesTable(props) { const indexOfFirstItem = indexOfLastItem - itemsPerPage; const currentItems = filteredData.slice(indexOfFirstItem, indexOfLastItem); - const paginate = (pageNumber) => setCurrentPage(pageNumber); - if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; return (
- {} + @@ -91,16 +114,16 @@ export default function PushesTable(props) { - {currentItems.reverse().map((row) => { + {[...currentItems].reverse().map((row) => { const repoFullName = row.repo.replace('.git', ''); const repoBranch = row.branch.replace('refs/heads/', ''); + const commitTimestamp = + row.commitData[0]?.commitTs || row.commitData[0]?.commitTimestamp; return ( - {moment - .unix(row.commitData[0].commitTs || row.commitData[0].commitTimestamp) - .toString()} + {commitTimestamp ? moment.unix(commitTimestamp).toString() : 'N/A'} @@ -126,25 +149,33 @@ export default function PushesTable(props) { - - {row.commitData[0].committer} - + {row.commitData[0]?.committer ? ( + + {row.commitData[0].committer} + + ) : ( + 'N/A' + )} - - {row.commitData[0].author} - + {row.commitData[0]?.author ? ( + + {row.commitData[0].author} + + ) : ( + 'N/A' + )} - {row.commitData[0].authorEmail ? ( + {row.commitData[0]?.authorEmail ? ( {row.commitData[0].authorEmail} @@ -152,7 +183,7 @@ export default function PushesTable(props) { 'No data...' )} - {row.commitData[0].message} + {row.commitData[0]?.message || 'N/A'} {row.commitData.length}
- {/* Pagination Component */}
); -} +}; + +export default PushesTable; From 91836f9a30a1a0a6eb5e8eec941155ab79741f7d Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:29:27 +0200 Subject: [PATCH 26/33] refactor(tsx): convert PushDetails view --- .../{PushDetails.jsx => PushDetails.tsx} | 158 +++++++++++------- .../{Attestation.jsx => Attestation.tsx} | 39 +++-- ...ttestationForm.jsx => AttestationForm.tsx} | 74 +++++--- ...ttestationView.jsx => AttestationView.tsx} | 103 +++++++----- 4 files changed, 230 insertions(+), 144 deletions(-) rename src/ui/views/PushDetails/{PushDetails.jsx => PushDetails.tsx} (78%) rename src/ui/views/PushDetails/components/{Attestation.jsx => Attestation.tsx} (77%) rename src/ui/views/PushDetails/components/{AttestationForm.jsx => AttestationForm.tsx} (64%) rename src/ui/views/PushDetails/components/{AttestationView.jsx => AttestationView.tsx} (55%) diff --git a/src/ui/views/PushDetails/PushDetails.jsx b/src/ui/views/PushDetails/PushDetails.tsx similarity index 78% rename from src/ui/views/PushDetails/PushDetails.jsx rename to src/ui/views/PushDetails/PushDetails.tsx index ee493afdb..9ba21d536 100644 --- a/src/ui/views/PushDetails/PushDetails.jsx +++ b/src/ui/views/PushDetails/PushDetails.tsx @@ -7,7 +7,7 @@ import GridContainer from '../../components/Grid/GridContainer'; import Card from '../../components/Card/Card'; import CardIcon from '../../components/Card/CardIcon'; import CardBody from '../../components/Card/CardBody'; -import CardHeader from '../../components/Card/CardHeader'; +import CardHeader, { CardHeaderColor } from '../../components/Card/CardHeader'; import CardFooter from '../../components/Card/CardFooter'; import Button from '../../components/CustomButtons/Button'; import Diff from './components/Diff'; @@ -23,29 +23,75 @@ import { CheckCircle, Visibility, Cancel, Block } from '@material-ui/icons'; import Snackbar from '@material-ui/core/Snackbar'; import Tooltip from '@material-ui/core/Tooltip'; -export default function Dashboard() { - const { id } = useParams(); - const [data, setData] = useState([]); +interface CommitData { + commitTs?: number; + commitTimestamp?: number; + committer: string; + author: string; + authorEmail?: string; + message: string; +} + +interface Reviewer { + username: string; + gitAccount: string; +} + +interface AttestationData { + reviewer: Reviewer; + timestamp: string | Date; + questions: Array<{ label: string; checked: boolean }>; +} + +interface PushData { + id: string; + repo: string; + branch: string; + commitFrom: string; + commitTo: string; + commitData: CommitData[]; + diff: { + content: string; + }; + canceled?: boolean; + rejected?: boolean; + authorised?: boolean; + attestation?: AttestationData; + autoApproved?: boolean; + timestamp: string | Date; +} + +const Dashboard: React.FC = () => { + const { id } = useParams<{ id: string }>(); + const [data, setData] = useState(null); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(false); const [message, setMessage] = useState(''); const [attestation, setAttestation] = useState(false); const navigate = useNavigate(); + let isUserAllowedToApprove = true; let isUserAllowedToReject = true; - function setUserAllowedToApprove(userAllowedToApprove) { + + const setUserAllowedToApprove = (userAllowedToApprove: boolean) => { isUserAllowedToApprove = userAllowedToApprove; console.log('isUserAllowedToApprove:' + isUserAllowedToApprove); - } - function setUserAllowedToReject(userAllowedToReject) { + }; + + const setUserAllowedToReject = (userAllowedToReject: boolean) => { isUserAllowedToReject = userAllowedToReject; console.log({ isUserAllowedToReject }); - } + }; + useEffect(() => { - getPush(id, setIsLoading, setData, setAuth, setIsError); + if (id) { + getPush(id, setIsLoading, setData, setAuth, setIsError); + } }, [id]); - const authorise = async (attestationData) => { + + const authorise = async (attestationData: Array<{ label: string; checked: boolean }>) => { + if (!id) return; await authorisePush(id, setMessage, setUserAllowedToApprove, attestationData); if (isUserAllowedToApprove) { navigate('/admin/push/'); @@ -53,6 +99,7 @@ export default function Dashboard() { }; const reject = async () => { + if (!id) return; await rejectPush(id, setMessage, setUserAllowedToReject); if (isUserAllowedToReject) { navigate('/admin/push/'); @@ -60,14 +107,16 @@ export default function Dashboard() { }; const cancel = async () => { + if (!id) return; await cancelPush(id, setAuth, setIsError); - navigate(`/admin/push/`); + navigate('/admin/push/'); }; if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; + if (!data) return
No data found
; - let headerData = { + let headerData: { title: string; color: CardHeaderColor } = { title: 'Pending', color: 'warning', }; @@ -96,18 +145,18 @@ export default function Dashboard() { const repoFullName = data.repo.replace('.git', ''); const repoBranch = data.branch.replace('refs/heads/', ''); - const generateIcon = (title) => { + const generateIcon = (title: string) => { switch (title) { case 'Approved': - return ; + return ; case 'Pending': - return ; + return ; case 'Canceled': - return ; + return ; case 'Rejected': - return ; + return ; default: - return ; + return ; } }; @@ -131,28 +180,18 @@ export default function Dashboard() { {generateIcon(headerData.title)}

{headerData.title}

- {!(data.canceled || data.rejected || data.authorised) ? ( + {!(data.canceled || data.rejected || data.authorised) && (
- - - +
- ) : null} - {data.attestation && data.authorised ? ( + )} + {data.attestation && data.authorised && (
- + {data.autoApproved ? ( - <> -
-

- Auto-approved by system -

-
- +
+

+ Auto-approved by system +

+
) : ( <> Reviewer
@@ -225,9 +257,7 @@ export default function Dashboard() { - {data.autoApproved ? ( - <> - ) : ( + {!data.autoApproved && ( )}
- ) : null} + )} @@ -297,17 +327,19 @@ export default function Dashboard() { - Timestamp - Committer - Author - Author E-mail - Message + + Timestamp + Committer + Author + Author E-mail + Message + {data.commitData.map((c) => ( - + - {moment.unix(c.commitTs || c.commitTimestamp).toString()} + {moment.unix(c.commitTs || c.commitTimestamp || 0).toString()} - + - + ); -} +}; + +export default Dashboard; diff --git a/src/ui/views/PushDetails/components/Attestation.jsx b/src/ui/views/PushDetails/components/Attestation.tsx similarity index 77% rename from src/ui/views/PushDetails/components/Attestation.jsx rename to src/ui/views/PushDetails/components/Attestation.tsx index c13663ed0..a69e04cb3 100644 --- a/src/ui/views/PushDetails/components/Attestation.jsx +++ b/src/ui/views/PushDetails/components/Attestation.tsx @@ -1,18 +1,21 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import Dialog from '@material-ui/core/Dialog'; import DialogContent from '@material-ui/core/DialogContent'; import DialogActions from '@material-ui/core/DialogActions'; import { CheckCircle, ErrorOutline } from '@material-ui/icons'; import Button from '../../../components/CustomButtons/Button'; -import AttestationForm from './AttestationForm'; - +import AttestationForm, { FormQuestion } from './AttestationForm'; import { getAttestationConfig, getURLShortener, getEmailContact } from '../../../services/config'; -export default function Attestation(props) { - const [open, setOpen] = React.useState(false); - const [formData, setFormData] = React.useState([]); - const [urlShortener, setURLShortener] = React.useState(''); - const [contactEmail, setContactEmail] = React.useState(''); +interface AttestationProps { + approveFn: (data: { label: string; checked: boolean }[]) => void; +} + +const Attestation: React.FC = ({ approveFn }) => { + const [open, setOpen] = useState(false); + const [formData, setFormData] = useState([]); + const [urlShortener, setURLShortener] = useState(''); + const [contactEmail, setContactEmail] = useState(''); useEffect(() => { if (!open) { @@ -27,7 +30,7 @@ export default function Attestation(props) { getEmailContact(setContactEmail); } } - }, [open]); + }, [open, urlShortener, contactEmail]); const handleClickOpen = () => { setOpen(true); @@ -38,13 +41,11 @@ export default function Attestation(props) { }; const handleApprove = () => { - const data = formData.map((question) => { - return { - label: question.label, - checked: question.checked, - }; - }); - props.approveFn(data); + const data = formData.map((question) => ({ + label: question.label, + checked: question.checked, + })); + approveFn(data); }; return ( @@ -95,7 +96,7 @@ export default function Attestation(props) { color='success' onClick={handleApprove} autoFocus - disabled={!formData.every((question) => !!question.checked)} + disabled={!formData.every((question) => question.checked)} > Approve @@ -103,4 +104,6 @@ export default function Attestation(props) { ); -} +}; + +export default Attestation; diff --git a/src/ui/views/PushDetails/components/AttestationForm.jsx b/src/ui/views/PushDetails/components/AttestationForm.tsx similarity index 64% rename from src/ui/views/PushDetails/components/AttestationForm.jsx rename to src/ui/views/PushDetails/components/AttestationForm.tsx index d1c1d781f..04f794f99 100644 --- a/src/ui/views/PushDetails/components/AttestationForm.jsx +++ b/src/ui/views/PushDetails/components/AttestationForm.tsx @@ -3,6 +3,44 @@ import { withStyles } from '@material-ui/core/styles'; import { green } from '@material-ui/core/colors'; import { Help } from '@material-ui/icons'; import { Grid, Tooltip, Checkbox, FormGroup, FormControlLabel } from '@material-ui/core'; +import { Theme } from '@material-ui/core/styles'; + +interface TooltipLink { + text: string; + url: string; +} + +interface TooltipContent { + text: string; + links?: TooltipLink[]; +} + +export interface FormQuestion { + label: string; + checked: boolean; + tooltip: TooltipContent; +} + +interface AttestationFormProps { + formData: FormQuestion[]; + passFormData: (data: FormQuestion[]) => void; +} + +const styles = (theme: Theme) => ({ + tooltip: { + backgroundColor: '#f5f5f9', + color: 'rgba(0, 0, 0, 0.87)', + maxWidth: 220, + fontSize: theme.typography.pxToRem(12), + border: '1px solid #dadde9', + }, +}); + +interface GreenCheckboxProps { + checked: boolean; + onChange: (event: React.ChangeEvent) => void; + name: string; +} const GreenCheckbox = withStyles({ root: { @@ -13,36 +51,32 @@ const GreenCheckbox = withStyles({ paddingRight: '35px', }, checked: {}, -})((props) => ); +})((props: GreenCheckboxProps) => ); -const HTMLTooltip = withStyles((theme) => ({ - tooltip: { - backgroundColor: '#f5f5f9', - color: 'rgba(0, 0, 0, 0.87)', - maxWidth: 220, - fontSize: theme.typography.pxToRem(12), - border: '1px solid #dadde9', - }, -}))(Tooltip); +const HTMLTooltip = withStyles(styles)(Tooltip); -export default function AttestationForm(props) { - const handleChange = (event) => { - const name = event.target.name; +const AttestationForm: React.FC = ({ formData, passFormData }) => { + const handleChange = (event: React.ChangeEvent) => { + const name = parseInt(event.target.name); const checked = event.target.checked; - const clone = [...props.formData]; + const clone = [...formData]; clone[name] = { ...clone[name], checked }; - props.passFormData(clone); + passFormData(clone); }; return ( - {props.formData.map((question, index) => { + {formData.map((question, index) => { return ( + } label={question.label} /> @@ -72,7 +106,7 @@ export default function AttestationForm(props) { } > - + @@ -80,4 +114,6 @@ export default function AttestationForm(props) { })} ); -} +}; + +export default AttestationForm; diff --git a/src/ui/views/PushDetails/components/AttestationView.jsx b/src/ui/views/PushDetails/components/AttestationView.tsx similarity index 55% rename from src/ui/views/PushDetails/components/AttestationView.jsx rename to src/ui/views/PushDetails/components/AttestationView.tsx index 70540ca76..5075c0db2 100644 --- a/src/ui/views/PushDetails/components/AttestationView.jsx +++ b/src/ui/views/PushDetails/components/AttestationView.tsx @@ -7,13 +7,43 @@ import FormControlLabel from '@material-ui/core/FormControlLabel'; import { CheckCircle } from '@material-ui/icons'; import Tooltip from '@material-ui/core/Tooltip'; import moment from 'moment'; - import Checkbox from '@material-ui/core/Checkbox'; import { withStyles } from '@material-ui/core/styles'; import { green } from '@material-ui/core/colors'; - import { getURLShortener } from '../../../services/config'; +interface Question { + label: string; + checked: boolean; +} + +interface Reviewer { + username: string; + gitAccount: string; +} + +interface AttestationData { + reviewer: Reviewer; + timestamp: string | Date; + questions: Question[]; +} + +interface AttestationViewProps { + attestation: boolean; + setAttestation: (value: boolean) => void; + data: AttestationData; +} + +const StyledFormControlLabel = withStyles({ + root: { + color: 'white', + '&$disabled': { + color: 'white', + }, + }, + disabled: {}, +})(FormControlLabel); + const GreenCheckbox = withStyles({ root: { color: green[500], @@ -23,23 +53,23 @@ const GreenCheckbox = withStyles({ paddingRight: '35px', }, checked: {}, -})((props) => ); +})((props: { checked: boolean }) => ); -export default function AttestationView(props) { - const [urlShortener, setURLShortener] = React.useState(''); +const AttestationView: React.FC = ({ attestation, setAttestation, data }) => { + const [urlShortener, setURLShortener] = React.useState(''); useEffect(() => { - if (props.attestation && !urlShortener) { + if (attestation && !urlShortener) { getURLShortener(setURLShortener); } - }, [props.attestation]); + }, [attestation, urlShortener]); return ( props.setAttestation(false)} + open={attestation} + onClose={() => setAttestation(false)} aria-labelledby='alert-dialog-title' aria-describedby='alert-dialog-description' style={{ margin: '0px 15px 0px 15px' }} @@ -51,10 +81,9 @@ export default function AttestationView(props) { margin: '24px 24px', padding: '24px 24px', }} - color='warning' >
- + What does it mean for a code contribution to be approved? @@ -62,30 +91,23 @@ export default function AttestationView(props) {

Prior to making this code contribution publicly accessible via GitHub, this code contribution was reviewed and approved by{' '} - - {props.data.reviewer.gitAccount} - - . As a reviewer, it was their responsibility to confirm that open sourcing this - contribution followed the requirements of the company open source contribution policy. + {data.reviewer.gitAccount}. As a + reviewer, it was their responsibility to confirm that open sourcing this contribution + followed the requirements of the company open source contribution policy.

- - {props.data.reviewer.gitAccount} - {' '} + {data.reviewer.gitAccount}{' '} approved this contribution{' '} - + - {moment(props.data.timestamp).fromNow()} + {moment(data.timestamp).fromNow()} {' '} and confirmed that: @@ -96,29 +118,20 @@ export default function AttestationView(props) { style={{ margin: '0px 15px 0px 35px', rowGap: '20px', padding: '20px' }} row={false} > - {props.data.questions.map((question, index) => { - return ( -

- } - disabled={true} - label={question.label} - /> -
- ); - })} + {data.questions.map((question, index) => ( +
+ } + disabled={true} + label={question.label} + /> +
+ ))}
); -} +}; + +export default AttestationView; From 414ac29d90832150cc6821912a8e0e720c0b86a5 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Thu, 17 Apr 2025 17:29:47 +0200 Subject: [PATCH 27/33] refactor(tsx): convert RepoDetails view --- .../Components/{AddUser.jsx => AddUser.tsx} | 87 ++++++----- .../{RepoDetails.jsx => RepoDetails.tsx} | 147 ++++++++++-------- 2 files changed, 134 insertions(+), 100 deletions(-) rename src/ui/views/RepoDetails/Components/{AddUser.jsx => AddUser.tsx} (74%) rename src/ui/views/RepoDetails/{RepoDetails.jsx => RepoDetails.tsx} (65%) diff --git a/src/ui/views/RepoDetails/Components/AddUser.jsx b/src/ui/views/RepoDetails/Components/AddUser.tsx similarity index 74% rename from src/ui/views/RepoDetails/Components/AddUser.jsx rename to src/ui/views/RepoDetails/Components/AddUser.tsx index afab44a53..f1d816e48 100644 --- a/src/ui/views/RepoDetails/Components/AddUser.jsx +++ b/src/ui/views/RepoDetails/Components/AddUser.tsx @@ -1,5 +1,4 @@ import React, { useState, useEffect } from 'react'; -import PropTypes from 'prop-types'; import InputLabel from '@material-ui/core/InputLabel'; import FormControl from '@material-ui/core/FormControl'; import FormHelperText from '@material-ui/core/FormHelperText'; @@ -18,18 +17,33 @@ import { addUser } from '../../../services/repo'; import { getUsers } from '../../../services/user'; import { PersonAdd } from '@material-ui/icons'; -function AddUserDialog(props) { - const repoName = props.repoName; - const type = props.type; - const refreshFn = props.refreshFn; - const [username, setUsername] = useState(''); - const [data, setData] = useState([]); - const [, setAuth] = useState(true); - const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); - const [error, setError] = useState(''); - const [tip, setTip] = useState(false); - const { onClose, open } = props; +interface User { + username: string; + gitAccount: string; +} + +interface AddUserDialogProps { + repoName: string; + type: string; + refreshFn: () => void; + open: boolean; + onClose: () => void; +} + +const AddUserDialog: React.FC = ({ + repoName, + type, + refreshFn, + open, + onClose, +}) => { + const [username, setUsername] = useState(''); + const [data, setData] = useState([]); + const [, setAuth] = useState(true); + const [isLoading, setIsLoading] = useState(false); + const [isError, setIsError] = useState(false); + const [error, setError] = useState(''); + const [tip, setTip] = useState(false); const handleClose = () => { setError(''); @@ -41,8 +55,8 @@ function AddUserDialog(props) { refreshFn(); }; - const handleChange = (event) => { - setUsername(event.target.value); + const handleChange = (event: React.ChangeEvent<{ value: unknown }>) => { + setUsername(event.target.value as string); }; const add = async () => { @@ -53,29 +67,24 @@ function AddUserDialog(props) { handleClose(); } catch (e) { setIsLoading(false); - if (e.message) { - setError(JSON.stringify(e)); + if (e instanceof Error) { + setError(e.message); } else { - setError(e.toString()); + setError('An unknown error occurred'); } } }; - const inputStyle = { + const inputStyle: React.CSSProperties = { width: '100%', }; useEffect(() => { getUsers(setIsLoading, setData, setAuth, setIsError, {}); - }, [props]); + }, []); if (isError) return
Something went wrong ...
; - let spinner; - if (isLoading) { - spinner = ; - } - return ( <> - Add a user...

{error}

{spinner} + Add a user... +

{error}

+ {isLoading && }
@@ -134,20 +145,16 @@ function AddUserDialog(props) { ); -} - -AddUserDialog.propTypes = { - onClose: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired, - refreshFn: PropTypes.func.isRequired, }; -export default function AddUser(props) { - const [open, setOpen] = React.useState(false); +interface AddUserProps { + repoName: string; + type: string; + refreshFn: () => void; +} - const repoName = props.repoName; - const type = props.type; - const refreshFn = props.refreshFn; +const AddUser: React.FC = ({ repoName, type, refreshFn }) => { + const [open, setOpen] = useState(false); const handleClickOpen = () => { setOpen(true); @@ -160,7 +167,7 @@ export default function AddUser(props) { return ( <> ); -} +}; + +export default AddUser; diff --git a/src/ui/views/RepoDetails/RepoDetails.jsx b/src/ui/views/RepoDetails/RepoDetails.tsx similarity index 65% rename from src/ui/views/RepoDetails/RepoDetails.jsx rename to src/ui/views/RepoDetails/RepoDetails.tsx index 9c91c1b68..a33baffb5 100644 --- a/src/ui/views/RepoDetails/RepoDetails.jsx +++ b/src/ui/views/RepoDetails/RepoDetails.tsx @@ -21,6 +21,23 @@ import { UserContext } from '../../../context'; import CodeActionButton from '../../components/CustomButtons/CodeActionButton'; import { Box } from '@material-ui/core'; +interface RepoData { + project: string; + name: string; + proxyURL: string; + url: string; + users: { + canAuthorise: string[]; + canPush: string[]; + }; +} + +export interface UserContextType { + user: { + admin: boolean; + }; +} + const useStyles = makeStyles((theme) => ({ root: { '& .MuiTextField-root': { @@ -28,38 +45,49 @@ const useStyles = makeStyles((theme) => ({ width: '100%', }, }, + table: { + minWidth: 650, + }, })); -export default function RepoDetails() { +const RepoDetails: React.FC = () => { const navigate = useNavigate(); const classes = useStyles(); - const [data, setData] = useState([]); + const [data, setData] = useState(null); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(true); const [isError, setIsError] = useState(false); - const { user } = useContext(UserContext); - const { id: repoName } = useParams(); + const { user } = useContext(UserContext); + const { id: repoName } = useParams<{ id: string }>(); useEffect(() => { - getRepo(setIsLoading, setData, setAuth, setIsError, repoName); - }, []); + if (repoName) { + getRepo(setIsLoading, setData, setAuth, setIsError, repoName); + } + }, [repoName]); - const removeUser = async (userToRemove, action) => { + const removeUser = async (userToRemove: string, action: 'authorise' | 'push') => { + if (!repoName) return; await deleteUser(userToRemove, repoName, action); getRepo(setIsLoading, setData, setAuth, setIsError, repoName); }; - const removeRepository = async (name) => { + const removeRepository = async (name: string) => { await deleteRepo(name); navigate('/admin/repo', { replace: true }); }; - const refresh = () => getRepo(setIsLoading, setData, setAuth, setIsError, repoName); + const refresh = () => { + if (repoName) { + getRepo(setIsLoading, setData, setAuth, setIsError, repoName); + } + }; if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; + if (!data) return
No repository data found
; - const { project: org, name, proxyURL } = data || {}; + const { project: org, name, proxyURL } = data; const cloneURL = `${proxyURL}/${org}/${name}.git`; return ( @@ -74,7 +102,7 @@ export default function RepoDetails() { color='secondary' onClick={() => removeRepository(data.name)} > - +
)} @@ -89,7 +117,8 @@ export default function RepoDetails() { width={'75px'} style={{ borderRadius: '5px' }} src={`https://github.com/${data.project}.png`} - > + alt={`${data.project} logo`} + /> Organization @@ -130,11 +159,11 @@ export default function RepoDetails() {

- Reviewers + Reviewers

{user.admin && (
- +
)} @@ -146,44 +175,42 @@ export default function RepoDetails() {
- {data.users.canAuthorise.map((row) => { - if (row) - return ( - - - {row} - - {user.admin && ( - - - - )} - - ); - })} + {data.users.canAuthorise.map((row) => ( + + + {row} + + {user.admin && ( + + + + )} + + ))}
+

- Contributors + Contributors

{user.admin && (
- +
)} - +
Username @@ -191,28 +218,24 @@ export default function RepoDetails() { - {data.users.canPush.map((row) => { - if (row) { - return ( - - - {row} - - {user.admin && ( - - - - )} - - ); - } - })} + {data.users.canPush.map((row) => ( + + + {row} + + {user.admin && ( + + + + )} + + ))}
@@ -223,4 +246,6 @@ export default function RepoDetails() {
); -} +}; + +export default RepoDetails; From 88f7e4e1495a8fad27bd6d983ebe644775ba6a86 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Fri, 18 Apr 2025 09:25:13 +0200 Subject: [PATCH 28/33] refactor(tsx): convert RepoList view --- .../Components/{NewRepo.jsx => NewRepo.tsx} | 85 ++++---- .../{RepoOverview.jsx => RepoOverview.tsx} | 77 ++++--- .../RepoList/Components/Repositories.jsx | 169 ---------------- .../RepoList/Components/Repositories.tsx | 190 ++++++++++++++++++ .../Components/{TabList.jsx => TabList.tsx} | 4 +- .../RepoList/{RepoList.jsx => RepoList.tsx} | 4 +- 6 files changed, 298 insertions(+), 231 deletions(-) rename src/ui/views/RepoList/Components/{NewRepo.jsx => NewRepo.tsx} (76%) rename src/ui/views/RepoList/Components/{RepoOverview.jsx => RepoOverview.tsx} (91%) delete mode 100644 src/ui/views/RepoList/Components/Repositories.jsx create mode 100644 src/ui/views/RepoList/Components/Repositories.tsx rename src/ui/views/RepoList/Components/{TabList.jsx => TabList.tsx} (86%) rename src/ui/views/RepoList/{RepoList.jsx => RepoList.tsx} (84%) diff --git a/src/ui/views/RepoList/Components/NewRepo.jsx b/src/ui/views/RepoList/Components/NewRepo.tsx similarity index 76% rename from src/ui/views/RepoList/Components/NewRepo.jsx rename to src/ui/views/RepoList/Components/NewRepo.tsx index e1c912069..8189566a9 100644 --- a/src/ui/views/RepoList/Components/NewRepo.jsx +++ b/src/ui/views/RepoList/Components/NewRepo.tsx @@ -1,5 +1,4 @@ import React, { useState } from 'react'; -import PropTypes from 'prop-types'; import InputLabel from '@material-ui/core/InputLabel'; import Input from '@material-ui/core/Input'; import FormControl from '@material-ui/core/FormControl'; @@ -17,15 +16,35 @@ import { makeStyles } from '@material-ui/core/styles'; import styles from '../../../assets/jss/material-dashboard-react/views/dashboardStyle'; import { RepoIcon } from '@primer/octicons-react'; -const useStyles = makeStyles(styles); +interface AddRepositoryDialogProps { + open: boolean; + onClose: () => void; + onSuccess: (data: RepositoryData) => void; +} + +export interface RepositoryData { + _id?: string; + project: string; + name: string; + url: string; + maxUser: number; + lastModified?: string; + dateCreated?: string; + proxyURL?: string; +} -function AddRepositoryDialog(props) { +interface NewRepoProps { + onSuccess: (data: RepositoryData) => void; +} + +const useStyles = makeStyles(styles as any); + +const AddRepositoryDialog: React.FC = ({ open, onClose, onSuccess }) => { const [project, setProject] = useState(''); const [name, setName] = useState(''); const [url, setUrl] = useState(''); const [error, setError] = useState(''); const [tip, setTip] = useState(false); - const { onClose, open, onSuccess } = props; const classes = useStyles(); const handleClose = () => { @@ -34,7 +53,7 @@ function AddRepositoryDialog(props) { onClose(); }; - const handleSuccess = (data) => { + const handleSuccess = (data: RepositoryData) => { onSuccess(data); setTip(true); }; @@ -46,27 +65,27 @@ function AddRepositoryDialog(props) { }; const add = async () => { - const data = { - project: project, - name: name, - url: url, + const data: RepositoryData = { + project: project.trim(), + name: name.trim(), + url: url.trim(), maxUser: 1, }; - if (data.project.trim().length == 0 || data.project.length > 100) { - setError('project name length unexpected'); + if (data.project.length === 0 || data.project.length > 100) { + setError('Project name length must be between 1 and 100 characters'); return; } - if (data.name.trim().length == 0 || data.name.length > 100) { - setError('Repo name length unexpected'); + if (data.name.length === 0 || data.name.length > 100) { + setError('Repository name length must be between 1 and 100 characters'); return; } try { new URL(data.url); } catch { - setError('Invalid URL'); + setError('Invalid URL format'); return; } @@ -75,15 +94,15 @@ function AddRepositoryDialog(props) { handleSuccess(data); handleClose(); } catch (e) { - if (e.message) { + if (e instanceof Error) { setError(e.message); } else { - setError(e.toString()); + setError('An unexpected error occurred'); } } }; - const inputStyle = { + const inputStyle: React.CSSProperties = { width: '100%', }; @@ -106,9 +125,11 @@ function AddRepositoryDialog(props) { fullWidth maxWidth='md' > - - {error} - + {error && ( + + {error} + + )} Add a repository... @@ -123,6 +144,7 @@ function AddRepositoryDialog(props) { inputProps={{ maxLength: 200, minLength: 3 }} aria-describedby='project-helper-text' onChange={(e) => setProject(e.target.value)} + value={project} /> GitHub Organization @@ -135,6 +157,7 @@ function AddRepositoryDialog(props) { id='name' aria-describedby='name-helper-text' onChange={(e) => setName(e.target.value)} + value={name} /> GitHub Repository Name @@ -148,6 +171,7 @@ function AddRepositoryDialog(props) { id='url' aria-describedby='url-helper-text' onChange={(e) => setUrl(e.target.value)} + value={url} /> GitHub Repository URL @@ -168,20 +192,11 @@ function AddRepositoryDialog(props) { ); -} - -AddRepositoryDialog.propTypes = { - onClose: PropTypes.func.isRequired, - open: PropTypes.bool.isRequired, - onSuccess: PropTypes.func.isRequired, }; -NewRepo.propTypes = { - onSuccess: PropTypes.func.isRequired, -}; +const NewRepo: React.FC = ({ onSuccess }) => { + const [open, setOpen] = useState(false); -export default function NewRepo(props) { - const [open, setOpen] = React.useState(false); const handleClickOpen = () => { setOpen(true); }; @@ -193,9 +208,11 @@ export default function NewRepo(props) { return (
- +
); -} +}; + +export default NewRepo; diff --git a/src/ui/views/RepoList/Components/RepoOverview.jsx b/src/ui/views/RepoList/Components/RepoOverview.tsx similarity index 91% rename from src/ui/views/RepoList/Components/RepoOverview.jsx rename to src/ui/views/RepoList/Components/RepoOverview.tsx index a431dc721..c2187c853 100644 --- a/src/ui/views/RepoList/Components/RepoOverview.jsx +++ b/src/ui/views/RepoList/Components/RepoOverview.tsx @@ -1,11 +1,41 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import TableCell from '@material-ui/core/TableCell'; import TableRow from '@material-ui/core/TableRow'; import GridContainer from '../../../components/Grid/GridContainer'; import GridItem from '../../../components/Grid/GridItem'; import { CodeReviewIcon, LawIcon, PeopleIcon } from '@primer/octicons-react'; +import axios from 'axios'; +import moment from 'moment'; +import CodeActionButton from '../../../components/CustomButtons/CodeActionButton'; -const colors = { +interface RepositoriesProps { + data: { + project: string; + name: string; + proxyURL: string; + users?: { + canPush?: string[]; + canAuthorise?: string[]; + }; + }; +} + +interface GitHubRepository { + description?: string; + language?: string; + license?: { + spdx_id: string; + }; + parent?: { + full_name: string; + html_url: string; + }; + created_at?: string; + updated_at?: string; + pushed_at?: string; +} + +const colors: Record = { '1C Enterprise': '#814CCC', '2-Dimensional Array': '#38761D', '4D': '#004289', @@ -562,26 +592,23 @@ const colors = { Zephir: '#118f9e', Zig: '#ec915c', ZIL: '#dc75e5', - Zimpl: '#d67711', + Zimpl: '#d67711' }; -import axios from 'axios'; -import moment from 'moment'; -import CodeActionButton from '../../../components/CustomButtons/CodeActionButton'; - -export default function Repositories(props) { - const [github, setGitHub] = React.useState({}); +const Repositories: React.FC = (props) => { + const [github, setGitHub] = useState({}); useEffect(() => { getGitHubRepository(); }, [props.data.project, props.data.name]); - const getGitHubRepository = async () => { - await axios - .get(`https://api.github.com/repos/${props.data.project}/${props.data.name}`) - .then((res) => { - setGitHub(res.data); - }); + const getGitHubRepository = async (): Promise => { + try { + const res = await axios.get(`https://api.github.com/repos/${props.data.project}/${props.data.name}`); + setGitHub(res.data); + } catch (error) { + console.error("Error fetching GitHub repository data:", error); + } }; const { project: org, name, proxyURL } = props?.data || {}; @@ -624,7 +651,7 @@ export default function Repositories(props) { style={{ height: '12px', width: '12px', - backgroundColor: `${colors[github.language]}`, + backgroundColor: `${colors[github.language] || '#ccc'}`, borderRadius: '50px', display: 'inline-block', marginRight: '5px', @@ -635,16 +662,16 @@ export default function Repositories(props) { )} {github.license && ( - {' '} + {' '} {github.license.spdx_id} )} - {' '} + {' '} {props.data.users?.canPush?.length || 0} - {' '} + {' '} {props.data.users?.canAuthorise?.length || 0} @@ -654,9 +681,9 @@ export default function Repositories(props) { Last updated{' '} {moment .max([ - moment(github.created_at), - moment(github.updated_at), - moment(github.pushed_at), + moment(github.created_at || 0), + moment(github.updated_at || 0), + moment(github.pushed_at || 0), ]) .fromNow()} @@ -664,11 +691,13 @@ export default function Repositories(props) {
- +
); -} +}; + +export default Repositories; \ No newline at end of file diff --git a/src/ui/views/RepoList/Components/Repositories.jsx b/src/ui/views/RepoList/Components/Repositories.jsx deleted file mode 100644 index 4be87b36d..000000000 --- a/src/ui/views/RepoList/Components/Repositories.jsx +++ /dev/null @@ -1,169 +0,0 @@ -import React, { useState, useEffect, useContext } from 'react'; -import { makeStyles } from '@material-ui/core/styles'; -import { useNavigate } from 'react-router-dom'; -import Table from '@material-ui/core/Table'; -import TableBody from '@material-ui/core/TableBody'; -import TableContainer from '@material-ui/core/TableContainer'; -import styles from '../../../assets/jss/material-dashboard-react/views/dashboardStyle'; -import { getRepos } from '../../../services/repo'; -import GridContainer from '../../../components/Grid/GridContainer'; -import GridItem from '../../../components/Grid/GridItem'; -import NewRepo from './NewRepo'; -import RepoOverview from './RepoOverview'; -import { UserContext } from '../../../../context'; -import PropTypes from 'prop-types'; -import Search from '../../../components/Search/Search'; -import Pagination from '../../../components/Pagination/Pagination'; -import Filtering from '../../../components/Filtering/Filtering'; - - -export default function Repositories(props) { - const useStyles = makeStyles(styles); - const classes = useStyles(); - const [data, setData] = useState([]); - const [filteredData, setFilteredData] = useState([]); - const [, setAuth] = useState(true); - const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); - const [currentPage, setCurrentPage] = useState(1); - const itemsPerPage = 5; - const navigate = useNavigate(); - const { user } = useContext(UserContext); - const openRepo = (repo) => navigate(`/admin/repo/${repo}`, { replace: true }); - - useEffect(() => { - const query = {}; - for (const k in props) { - if (!k) continue; - query[k] = props[k]; - } - getRepos(setIsLoading, (data) => { - setData(data); - setFilteredData(data); - }, setAuth, setIsError, query); - }, [props]); - - const refresh = async (repo) => { - const updatedData = [...data, repo]; - setData(updatedData); - setFilteredData(updatedData); - }; - - const handleSearch = (query) => { - setCurrentPage(1); - if (!query) { - setFilteredData(data); - } else { - const lowercasedQuery = query.toLowerCase(); - setFilteredData( - data.filter(repo => - repo.name.toLowerCase().includes(lowercasedQuery) || - repo.project.toLowerCase().includes(lowercasedQuery) - ) - ); - } - }; - - // New function for handling filter changes - const handleFilterChange = (filterOption, sortOrder) => { - const sortedData = [...data]; - switch (filterOption) { - case 'dateModified': - sortedData.sort((a, b) => new Date(a.lastModified) - new Date(b.lastModified)); - break; - case 'dateCreated': - sortedData.sort((a, b) => new Date(a.dateCreated) - new Date(b.dateCreated)); - break; - case 'alphabetical': - sortedData.sort((a, b) => a.name.localeCompare(b.name)); - break; - default: - break; - } - - if (sortOrder === 'desc') { - sortedData.reverse(); - } - - setFilteredData(sortedData); - }; - - - const handlePageChange = (page) => setCurrentPage(page); - const startIdx = (currentPage - 1) * itemsPerPage; - const paginatedData = filteredData.slice(startIdx, startIdx + itemsPerPage); - - if (isLoading) return
Loading...
; - if (isError) return
Something went wrong ...
; - - const addrepoButton = user.admin ? ( - - - - ) : ( - - ); - - return ( - - ); -} - -GetGridContainerLayOut.propTypes = { - classes: PropTypes.object, - openRepo: PropTypes.func.isRequired, - data: PropTypes.array, - repoButton: PropTypes.object, - onSearch: PropTypes.func.isRequired, - currentPage: PropTypes.number.isRequired, - totalItems: PropTypes.number.isRequired, - itemsPerPage: PropTypes.number.isRequired, - onPageChange: PropTypes.func.isRequired, -}; - -function GetGridContainerLayOut(props) { - return ( - - {props.repoButton} - - - - {/* Include the Filtering component */} - - - - {props.data.map((row) => { - if (row.project && row.name) { - return ; - } - })} - -
-
-
- - - -
- ); -} - diff --git a/src/ui/views/RepoList/Components/Repositories.tsx b/src/ui/views/RepoList/Components/Repositories.tsx new file mode 100644 index 000000000..cf7e4bf05 --- /dev/null +++ b/src/ui/views/RepoList/Components/Repositories.tsx @@ -0,0 +1,190 @@ +import React, { useState, useEffect, useContext } from 'react'; +import { makeStyles } from '@material-ui/core/styles'; +import { useNavigate } from 'react-router-dom'; +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableContainer from '@material-ui/core/TableContainer'; +import styles from '../../../assets/jss/material-dashboard-react/views/dashboardStyle'; +import { getRepos } from '../../../services/repo'; +import GridContainer from '../../../components/Grid/GridContainer'; +import GridItem from '../../../components/Grid/GridItem'; +import NewRepo, { RepositoryData } from './NewRepo'; +import RepoOverview from './RepoOverview'; +import { UserContext } from '../../../../context'; +import Search from '../../../components/Search/Search'; +import Pagination from '../../../components/Pagination/Pagination'; +import Filtering, { FilterOption, SortOrder } from '../../../components/Filtering/Filtering'; + +interface RepositoriesProps { + [key: string]: any; +} + +interface GridContainerLayoutProps { + classes: any; + openRepo: (repo: string) => void; + data: RepositoryData[]; + repoButton: React.ReactNode; + onSearch: (query: string) => void; + currentPage: number; + totalItems: number; + itemsPerPage: number; + onPageChange: (page: number) => void; + onFilterChange: (filterOption: FilterOption, sortOrder: SortOrder) => void; +} + +interface UserContextType { + user: { + admin: boolean; + [key: string]: any; + }; +} + +export default function Repositories(props: RepositoriesProps): React.ReactElement { + const useStyles = makeStyles(styles as any); + const classes = useStyles(); + const [data, setData] = useState([]); + const [filteredData, setFilteredData] = useState([]); + const [, setAuth] = useState(true); + const [isLoading, setIsLoading] = useState(false); + const [isError, setIsError] = useState(false); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage: number = 5; + const navigate = useNavigate(); + const { user } = useContext(UserContext); + + const openRepo = (repo: string): void => navigate(`/admin/repo/${repo}`, { replace: true }); + + useEffect(() => { + const query: Record = {}; + for (const k in props) { + if (!k) continue; + query[k] = props[k]; + } + getRepos( + setIsLoading, + (data: RepositoryData[]) => { + setData(data); + setFilteredData(data); + }, + setAuth, + setIsError, + query, + ); + }, [props]); + + const refresh = async (repo: RepositoryData): Promise => { + const updatedData = [...data, repo]; + setData(updatedData); + setFilteredData(updatedData); + }; + + const handleSearch = (query: string): void => { + setCurrentPage(1); + if (!query) { + setFilteredData(data); + } else { + const lowercasedQuery = query.toLowerCase(); + setFilteredData( + data.filter( + (repo) => + repo.name.toLowerCase().includes(lowercasedQuery) || + repo.project.toLowerCase().includes(lowercasedQuery), + ), + ); + } + }; + + const handleFilterChange = (filterOption: FilterOption, sortOrder: SortOrder): void => { + const sortedData = [...data]; + switch (filterOption) { + case 'Date Modified': + sortedData.sort( + (a, b) => + new Date(a.lastModified || 0).getTime() - new Date(b.lastModified || 0).getTime(), + ); + break; + case 'Date Created': + sortedData.sort( + (a, b) => new Date(a.dateCreated || 0).getTime() - new Date(b.dateCreated || 0).getTime(), + ); + break; + case 'Alphabetical': + sortedData.sort((a, b) => a.name.localeCompare(b.name)); + break; + default: + break; + } + if (sortOrder === 'desc') { + sortedData.reverse(); + } + + setFilteredData(sortedData); + }; + + const handlePageChange = (page: number): void => setCurrentPage(page); + const startIdx = (currentPage - 1) * itemsPerPage; + const paginatedData = filteredData.slice(startIdx, startIdx + itemsPerPage); + + if (isLoading) return
Loading...
; + if (isError) return
Something went wrong ...
; + + const addrepoButton = user.admin ? ( + + + + ) : ( + + ); + + return ( + + ); +} + +function GetGridContainerLayOut(props: GridContainerLayoutProps): React.ReactElement { + return ( + + {props.repoButton} + + + + + + + {props.data.map((row) => { + if (row.project && row.name) { + return ( + + ); + } + return null; + })} + +
+
+
+ + + +
+ ); +} diff --git a/src/ui/views/RepoList/Components/TabList.jsx b/src/ui/views/RepoList/Components/TabList.tsx similarity index 86% rename from src/ui/views/RepoList/Components/TabList.jsx rename to src/ui/views/RepoList/Components/TabList.tsx index 40d865613..3f1fdea5e 100644 --- a/src/ui/views/RepoList/Components/TabList.jsx +++ b/src/ui/views/RepoList/Components/TabList.tsx @@ -3,7 +3,7 @@ import GridItem from '../../../components/Grid/GridItem'; import GridContainer from '../../../components/Grid/GridContainer'; import Repositories from './Repositories'; -export default function Dashboard() { +export default function Dashboard(): React.ReactElement { return (
@@ -13,4 +13,4 @@ export default function Dashboard() {
); -} +} \ No newline at end of file diff --git a/src/ui/views/RepoList/RepoList.jsx b/src/ui/views/RepoList/RepoList.tsx similarity index 84% rename from src/ui/views/RepoList/RepoList.jsx rename to src/ui/views/RepoList/RepoList.tsx index 890909f69..3e3bafec6 100644 --- a/src/ui/views/RepoList/RepoList.jsx +++ b/src/ui/views/RepoList/RepoList.tsx @@ -3,7 +3,7 @@ import GridItem from '../../components/Grid/GridItem'; import GridContainer from '../../components/Grid/GridContainer'; import TabList from './Components/TabList'; -export default function RepoList() { +export default function RepoList(): React.ReactElement { return ( @@ -11,4 +11,4 @@ export default function RepoList() { ); -} +} \ No newline at end of file From 9935b0b3d09b7d056b34acd5c741b67ea973ce75 Mon Sep 17 00:00:00 2001 From: fabiovincenzi Date: Fri, 18 Apr 2025 09:25:30 +0200 Subject: [PATCH 29/33] refactor(tsx): convert UserList view --- .../Components/{TabList.jsx => TabList.tsx} | 6 +- .../Components/{UserList.jsx => UserList.tsx} | 72 ++++++++++++------- .../UserList/{UserList.jsx => UserList.tsx} | 6 +- 3 files changed, 53 insertions(+), 31 deletions(-) rename src/ui/views/UserList/Components/{TabList.jsx => TabList.tsx} (84%) rename src/ui/views/UserList/Components/{UserList.jsx => UserList.tsx} (68%) rename src/ui/views/UserList/{UserList.jsx => UserList.tsx} (83%) diff --git a/src/ui/views/UserList/Components/TabList.jsx b/src/ui/views/UserList/Components/TabList.tsx similarity index 84% rename from src/ui/views/UserList/Components/TabList.jsx rename to src/ui/views/UserList/Components/TabList.tsx index 2d8d69e8b..16cbfd88a 100644 --- a/src/ui/views/UserList/Components/TabList.jsx +++ b/src/ui/views/UserList/Components/TabList.tsx @@ -3,7 +3,7 @@ import GridItem from '../../../components/Grid/GridItem'; import GridContainer from '../../../components/Grid/GridContainer'; import UserList from './UserList'; -export default function Dashboard() { +const Dashboard: React.FC = () => { return (
@@ -13,4 +13,6 @@ export default function Dashboard() {
); -} +}; + +export default Dashboard; \ No newline at end of file diff --git a/src/ui/views/UserList/Components/UserList.jsx b/src/ui/views/UserList/Components/UserList.tsx similarity index 68% rename from src/ui/views/UserList/Components/UserList.jsx rename to src/ui/views/UserList/Components/UserList.tsx index f148a8384..af4dc8f37 100644 --- a/src/ui/views/UserList/Components/UserList.jsx +++ b/src/ui/views/UserList/Components/UserList.tsx @@ -17,25 +17,36 @@ import Pagination from '../../../components/Pagination/Pagination'; import { CloseRounded, Check, KeyboardArrowRight } from '@material-ui/icons'; import Search from '../../../components/Search/Search'; -const useStyles = makeStyles(styles); +interface User { + username: string; + displayName?: string; + title?: string; + email?: string; + gitAccount?: string; + admin?: boolean; +} + +interface UserListProps { + [key: string]: any; +} -export default function UserList(props) { - +const useStyles = makeStyles(styles as any); + +const UserList: React.FC = (props) => { const classes = useStyles(); - const [data, setData] = useState([]); - const [, setAuth] = useState(true); - const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); + const [data, setData] = useState([]); + const [, setAuth] = useState(true); + const [isLoading, setIsLoading] = useState(false); + const [isError, setIsError] = useState(false); const navigate = useNavigate(); - const [currentPage, setCurrentPage] = useState(1); - const itemsPerPage = 5; - const [searchQuery, setSearchQuery] = useState(''); - - const openUser = (username) => navigate(`/admin/user/${username}`, { replace: true }); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 5; + const [searchQuery, setSearchQuery] = useState(''); + const openUser = (username: string) => navigate(`/admin/user/${username}`, { replace: true }); useEffect(() => { - const query = {}; + const query: Record = {}; for (const k in props) { if (!k) continue; @@ -47,32 +58,30 @@ export default function UserList(props) { if (isLoading) return
Loading...
; if (isError) return
Something went wrong...
; - - const filteredUsers = data.filter(user => - user.displayName && user.displayName.toLowerCase().includes(searchQuery.toLowerCase()) || - user.username && user.username.toLowerCase().includes(searchQuery.toLowerCase()) -); + const filteredUsers = data.filter( + (user) => + (user.displayName && user.displayName.toLowerCase().includes(searchQuery.toLowerCase())) || + (user.username && user.username.toLowerCase().includes(searchQuery.toLowerCase())), + ); const indexOfLastItem = currentPage * itemsPerPage; const indexOfFirstItem = indexOfLastItem - itemsPerPage; const currentItems = filteredUsers.slice(indexOfFirstItem, indexOfLastItem); const totalItems = filteredUsers.length; - - const handlePageChange = (page) => { + const handlePageChange = (page: number) => { setCurrentPage(page); }; - - const handleSearch = (query) => { + const handleSearch = (query: string) => { setSearchQuery(query); - setCurrentPage(1); + setCurrentPage(1); }; return ( - + @@ -94,12 +103,20 @@ export default function UserList(props) { {row.email} - + {row.gitAccount} - {row.admin ? : } + {row.admin ? ( + + ) : ( + + )}