diff --git a/.github/workflows/unused-dependencies.yml b/.github/workflows/unused-dependencies.yml index a401f7b50..5568de7b3 100644 --- a/.github/workflows/unused-dependencies.yml +++ b/.github/workflows/unused-dependencies.yml @@ -21,7 +21,7 @@ jobs: node-version: '20.x' - name: 'Run depcheck' run: | - npx depcheck --skip-missing --ignores="tsx,@babel/*,@commitlint/*,eslint,eslint-*,husky,mocha,ts-mocha,ts-node,concurrently,nyc,prettier,typescript,tsconfig-paths,vite-tsconfig-paths" + npx depcheck --skip-missing --ignores="tsx,@babel/*,@commitlint/*,eslint,eslint-*,husky,mocha,ts-mocha,ts-node,concurrently,nyc,prettier,typescript,tsconfig-paths,vite-tsconfig-paths,history" echo $? if [[ $? == 1 ]]; then echo "Unused dependencies or devDependencies found" 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/package-lock.json b/package-lock.json index 57cb15249..c3b7a8833 100644 --- a/package-lock.json +++ b/package-lock.json @@ -69,6 +69,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", @@ -3692,6 +3693,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", @@ -3727,6 +3744,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", @@ -3791,6 +3820,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 4fb28ca04..8ce82902e 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\"", @@ -90,6 +90,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/context.js b/src/context.js deleted file mode 100644 index 40223ff1d..000000000 --- a/src/context.js +++ /dev/null @@ -1,3 +0,0 @@ -import { createContext } from 'react'; - -export const UserContext = createContext(null); diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 000000000..d8302c7cb --- /dev/null +++ b/src/context.ts @@ -0,0 +1,8 @@ +import { createContext } from 'react'; +import { UserContextType } from './ui/views/RepoDetails/RepoDetails'; + +export const UserContext = createContext({ + user: { + admin: false, + }, +}); 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'), 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', 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/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/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/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/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/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/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; 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; 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/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/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..c893715cc 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) { +export 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; diff --git a/src/ui/components/CustomButtons/Button.jsx b/src/ui/components/CustomButtons/Button.tsx similarity index 52% rename from src/ui/components/CustomButtons/Button.jsx rename to src/ui/components/CustomButtons/Button.tsx index 81090c191..01e8e0eb1 100644 --- a/src/ui/components/CustomButtons/Button.jsx +++ b/src/ui/components/CustomButtons/Button.tsx @@ -1,13 +1,37 @@ 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 Omit { + color?: Color; + round?: boolean; + disabled?: boolean; + simple?: boolean; + size?: 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, @@ -23,45 +47,23 @@ export default function RegularButton(props) { muiClasses, ...rest } = props; + const btnClasses = classNames({ [classes.button]: true, - [classes[size]]: size, - [classes[color]]: color, + [classes[size as Size]]: size, + [classes[color as 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 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; 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/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/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 diff --git a/src/ui/components/Grid/GridItem.jsx b/src/ui/components/Grid/GridItem.jsx deleted file mode 100644 index 29cc2b269..000000000 --- a/src/ui/components/Grid/GridItem.jsx +++ /dev/null @@ -1,26 +0,0 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import { makeStyles } from '@material-ui/core/styles'; -import Grid from '@material-ui/core/Grid'; - -const styles = { - grid: { - padding: '0 15px !important', - }, -}; - -const useStyles = makeStyles(styles); - -export default function GridItem(props) { - const classes = useStyles(); - const { children, ...rest } = props; - return ( - - {children} - - ); -} - -GridItem.propTypes = { - children: PropTypes.node, -}; diff --git a/src/ui/components/Grid/GridItem.tsx b/src/ui/components/Grid/GridItem.tsx new file mode 100644 index 000000000..4670d1649 --- /dev/null +++ b/src/ui/components/Grid/GridItem.tsx @@ -0,0 +1,24 @@ +import React, { ReactNode } from 'react'; +import { makeStyles, Theme } from '@material-ui/core/styles'; +import Grid, { GridProps } from '@material-ui/core/Grid'; + +const useStyles = makeStyles((theme: Theme) => ({ + grid: { + padding: '0 15px !important', + }, +})); + +export interface GridItemProps extends GridProps { + children?: ReactNode; +} + +const GridItem: React.FC = ({ children, ...rest }) => { + const classes = useStyles(); + return ( + + {children} + + ); +}; + +export default GridItem; 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 68% rename from src/ui/components/Navbars/Navbar.jsx rename to src/ui/components/Navbars/Navbar.tsx index e3925bc8f..b792d5a17 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'; @@ -10,24 +9,43 @@ 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); -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; 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.jsx b/src/ui/components/Search/Search.jsx deleted file mode 100644 index 5e1cbf6b4..000000000 --- a/src/ui/components/Search/Search.jsx +++ /dev/null @@ -1,37 +0,0 @@ -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'; - - -export default function Search({ onSearch }) { - const handleSearchChange = (event) => { - const query = event.target.value; - onSearch(query); - }; - - return ( -
- - - - ), - }} - /> -
- ); -} - - - - diff --git a/src/ui/components/Search/Search.tsx b/src/ui/components/Search/Search.tsx new file mode 100644 index 000000000..1e30abf24 --- /dev/null +++ b/src/ui/components/Search/Search.tsx @@ -0,0 +1,39 @@ +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'; + +interface SearchProps { + onSearch: (query: string) => void; + placeholder?: string; +} + +const Search: React.FC = ({ onSearch, placeholder = 'Search...' }) => { + const handleSearchChange = (event: React.ChangeEvent) => { + const query = event.target.value; + onSearch(query); + }; + + return ( +
+ + + + ), + }} + /> +
+ ); +}; + +export default Search; 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; 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; diff --git a/src/ui/layouts/Admin.jsx b/src/ui/layouts/Admin.tsx similarity index 63% rename from src/ui/layouts/Admin.jsx rename to src/ui/layouts/Admin.tsx index 8571cea1e..fab501643 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,78 +12,92 @@ 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; +} -const useStyles = makeStyles(styles); +let ps: PerfectScrollbar | undefined; +let refresh = false; + +const useStyles = makeStyles(styles as any); -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<'purple' | 'blue' | 'green' | 'orange' | 'red'>('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 ( - +
- {/* 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; 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 deleted file mode 100644 index 04a2fdccb..000000000 --- a/src/ui/services/user.js +++ /dev/null @@ -1,99 +0,0 @@ -import axios from 'axios'; -import { getCookie } from '../utils.jsx'; - -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/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 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; 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; 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; 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; 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; 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/UserList/UserList.jsx b/src/ui/views/RepoList/RepoList.tsx similarity index 84% rename from src/ui/views/UserList/UserList.jsx rename to src/ui/views/RepoList/RepoList.tsx index 7e94ecf1e..3e3bafec6 100644 --- a/src/ui/views/UserList/UserList.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 UserList() { +export default function RepoList(): React.ReactElement { return ( @@ -11,4 +11,4 @@ export default function UserList() { ); -} +} \ No newline at end of file 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) + } />
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 ? ( + + ) : ( + + )}