From a3235ee41204c87d6cdd9877ed0d362b2fca6568 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Tue, 8 Jul 2025 11:34:28 +0900 Subject: [PATCH 01/17] feat: add Settings page with configurable JWT token --- src/routes.jsx | 15 +++- src/ui/views/Settings/Settings.jsx | 106 +++++++++++++++++++++++++++++ src/ui/views/User/User.jsx | 3 - 3 files changed, 120 insertions(+), 4 deletions(-) create mode 100644 src/ui/views/Settings/Settings.jsx diff --git a/src/routes.jsx b/src/routes.jsx index c409f599e..acbfae14d 100644 --- a/src/routes.jsx +++ b/src/routes.jsx @@ -25,10 +25,11 @@ import User from './ui/views/User/User'; import UserList from './ui/views/UserList/UserList'; import RepoDetails from './ui/views/RepoDetails/RepoDetails'; import RepoList from './ui/views/RepoList/RepoList'; +import SettingsView from './ui/views/Settings/Settings'; import { RepoIcon } from '@primer/octicons-react'; -import { Group, AccountCircle, Dashboard } from '@material-ui/icons'; +import { Group, AccountCircle, Dashboard, Settings } from '@material-ui/icons'; const dashboardRoutes = [ { @@ -115,6 +116,18 @@ const dashboardRoutes = [ layout: '/dashboard', visible: false, }, + { + path: '/admin/settings', + name: 'Settings', + icon: Settings, + component: (props) => + , + layout: '/dashboard', + visible: true, + }, ]; export default dashboardRoutes; diff --git a/src/ui/views/Settings/Settings.jsx b/src/ui/views/Settings/Settings.jsx new file mode 100644 index 000000000..b666095ae --- /dev/null +++ b/src/ui/views/Settings/Settings.jsx @@ -0,0 +1,106 @@ +import React, { useState, useEffect } from 'react'; +import { + TextField, + IconButton, + InputAdornment, + FormLabel, + Typography, +} from '@material-ui/core'; +import { Visibility, VisibilityOff, Save, Clear } from '@material-ui/icons'; +import { makeStyles } from '@material-ui/core/styles'; + +import GridContainer from '../../components/Grid/GridContainer'; +import GridItem from '../../components/Grid/GridItem'; +import Card from '../../components/Card/Card'; +import CardBody from '../../components/Card/CardBody'; +import Button from '../../components/CustomButtons/Button'; + +const useStyles = makeStyles((theme) => ({ + root: { + '& .MuiTextField-root': { + margin: theme.spacing(1), + width: '100%', + }, + }, + buttonRow: { + display: 'flex', + justifyContent: 'flex-end', + marginTop: theme.spacing(2), + gap: theme.spacing(1), + }, +})); + +export default function SettingsView() { + const classes = useStyles(); + + const [jwtToken, setJwtToken] = useState(''); + const [showToken, setShowToken] = useState(false); + + useEffect(() => { + const savedToken = localStorage.getItem('ui_jwt_token'); + if (savedToken) setJwtToken(savedToken); + }, []); + + const handleSave = () => { + localStorage.setItem('ui_jwt_token', jwtToken); + }; + + const handleClear = () => { + setJwtToken(''); + localStorage.removeItem('ui_jwt_token'); + }; + + const toggleShowToken = () => { + setShowToken(!showToken); + }; + + return ( +
+ + + + + {/* Title */} + JWT Token for UI Authentication + + The JWT token used to authenticate UI requests to the server when the "apiAuthentication" is enabled in the config. + + setJwtToken(e.target.value)} + InputProps={{ + endAdornment: ( + + + {showToken ? : } + + + ), + style: { + marginTop: '10px', + marginLeft: '-8px', + marginRight: '8px', + }, + }} + /> +
+ + +
+
+
+
+
+
+ ); +} diff --git a/src/ui/views/User/User.jsx b/src/ui/views/User/User.jsx index 44fa64e1c..17c76a5ea 100644 --- a/src/ui/views/User/User.jsx +++ b/src/ui/views/User/User.jsx @@ -52,9 +52,6 @@ export default function Dashboard() { if (isLoading) return
Loading...
; if (isError) return
Something went wrong ...
; - if (!auth && window.location.pathname === '/dashboard/profile') { - return ; - } const updateProfile = async () => { try { From 39321c3bf6cb1ed9dd15eff325abe2ab967f85db Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Tue, 8 Jul 2025 14:06:39 +0900 Subject: [PATCH 02/17] feat: add Snackbar to Settings page --- src/ui/views/Settings/Settings.jsx | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/ui/views/Settings/Settings.jsx b/src/ui/views/Settings/Settings.jsx index b666095ae..fe166c76d 100644 --- a/src/ui/views/Settings/Settings.jsx +++ b/src/ui/views/Settings/Settings.jsx @@ -4,6 +4,7 @@ import { IconButton, InputAdornment, FormLabel, + Snackbar, Typography, } from '@material-ui/core'; import { Visibility, VisibilityOff, Save, Clear } from '@material-ui/icons'; @@ -35,6 +36,8 @@ export default function SettingsView() { const [jwtToken, setJwtToken] = useState(''); const [showToken, setShowToken] = useState(false); + const [snackbarMessage, setSnackbarMessage] = useState(''); + const [snackbarOpen, setSnackbarOpen] = useState(false); useEffect(() => { const savedToken = localStorage.getItem('ui_jwt_token'); @@ -43,11 +46,15 @@ export default function SettingsView() { const handleSave = () => { localStorage.setItem('ui_jwt_token', jwtToken); + setSnackbarMessage('JWT token saved'); + setSnackbarOpen(true); }; const handleClear = () => { setJwtToken(''); localStorage.removeItem('ui_jwt_token'); + setSnackbarMessage('JWT token cleared'); + setSnackbarOpen(true); }; const toggleShowToken = () => { @@ -63,7 +70,7 @@ export default function SettingsView() { {/* Title */} JWT Token for UI Authentication - The JWT token used to authenticate UI requests to the server when the "apiAuthentication" is enabled in the config. + The JWT token used to authenticate UI requests to the server when "apiAuthentication" is enabled in the config. + setSnackbarOpen(false)} + message={snackbarMessage} + /> ); } From 25e90ef44b25cdc3c5979697a923213555812d78 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Tue, 8 Jul 2025 14:19:43 +0900 Subject: [PATCH 03/17] fix: refactor axios config loading in UI --- src/ui/services/auth.js | 32 ++++++++++++++++++++++++++++++++ src/ui/services/git-push.js | 17 +++++++---------- src/ui/services/repo.js | 23 ++++++++++------------- src/ui/services/user.js | 15 ++++++--------- 4 files changed, 55 insertions(+), 32 deletions(-) diff --git a/src/ui/services/auth.js b/src/ui/services/auth.js index e1155e9f5..18352940b 100644 --- a/src/ui/services/auth.js +++ b/src/ui/services/auth.js @@ -1,3 +1,5 @@ +import { getCookie } from '../utils.jsx'; + const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}` : `${location.origin}`; @@ -20,3 +22,33 @@ export const getUserInfo = async () => { return null; } }; + +/** + * Gets the Axios config for the UI + * @return {Object} The Axios config + */ +export const getAxiosConfig = () => { + console.log('getAxiosConfig', getCookie('csrf'), localStorage.getItem('ui_jwt_token')); + const jwtToken = localStorage.getItem('ui_jwt_token'); + return { + headers: { + 'X-CSRF-TOKEN': getCookie('csrf'), + Authorization: jwtToken ? `Bearer ${jwtToken}` : undefined, + }, + }; +}; + +/** + * Processes authentication errors and returns a user-friendly error message + * @param {Object} error - The error object + * @return {string} The error message + */ +export const processAuthError = (error) => { + let errorMessage = `Failed to authorize user: ${error.response.data.trim()}. `; + if (!localStorage.getItem('ui_jwt_token')) { + errorMessage += 'Set your JWT token in the settings page or disable JWT auth in your app configuration.' + } else { + errorMessage += 'Check your JWT token or disable JWT auth in your app configuration.' + } + return errorMessage; +}; \ No newline at end of file diff --git a/src/ui/services/git-push.js b/src/ui/services/git-push.js index da654aaad..8826ebf95 100644 --- a/src/ui/services/git-push.js +++ b/src/ui/services/git-push.js @@ -1,17 +1,14 @@ import axios from 'axios'; import { getCookie } from '../utils.jsx'; +import { getAxiosConfig, processAuthError } from './auth.js'; const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}/api/v1` : `${location.origin}/api/v1`; -const config = { - withCredentials: true, -}; - const getPush = async (id, setIsLoading, setData, setAuth, setIsError) => { const url = `${baseUrl}/push/${id}`; - await axios(url, config) + await axios(url, getAxiosConfig()) .then((response) => { const data = response.data; data.diff = data.steps.find((x) => x.stepName === 'diff'); @@ -42,7 +39,7 @@ const getPushes = async ( url.search = new URLSearchParams(query); setIsLoading(true); - await axios(url.toString(), { withCredentials: true }) + await axios(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; setData(data); @@ -51,7 +48,7 @@ const getPushes = async ( setIsError(true); if (error.response && error.response.status === 401) { setAuth(false); - setErrorMessage('Failed to authorize user. If JWT auth is enabled, please check your configuration or disable it.'); + setErrorMessage(processAuthError(error)); } else { setErrorMessage(`Error fetching pushes: ${error.response.data.message}`); } @@ -72,7 +69,7 @@ const authorisePush = async (id, setMessage, setUserAllowedToApprove, attestatio attestation, }, }, - { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }, + getAxiosConfig(), ) .catch((error) => { if (error.response && error.response.status === 401) { @@ -89,7 +86,7 @@ const rejectPush = async (id, setMessage, setUserAllowedToReject) => { let errorMsg = ''; let isUserAllowedToReject = true; await axios - .post(url, {}, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .post(url, {}, getAxiosConfig()) .catch((error) => { if (error.response && error.response.status === 401) { errorMsg = 'You are not authorised to reject...'; @@ -103,7 +100,7 @@ const rejectPush = async (id, setMessage, setUserAllowedToReject) => { const cancelPush = async (id, setAuth, setIsError) => { const url = `${baseUrl}/push/${id}/cancel`; await axios - .post(url, {}, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .post(url, {}, getAxiosConfig()) .catch((error) => { if (error.response && error.response.status === 401) { setAuth(false); diff --git a/src/ui/services/repo.js b/src/ui/services/repo.js index 27d898c75..db7e984cd 100644 --- a/src/ui/services/repo.js +++ b/src/ui/services/repo.js @@ -1,18 +1,15 @@ import axios from 'axios'; import { getCookie } from '../utils.jsx'; +import { getAxiosConfig, processAuthError } from './auth.js'; const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}/api/v1` : `${location.origin}/api/v1`; -const config = { - withCredentials: true, -}; - const canAddUser = (repoName, user, action) => { const url = new URL(`${baseUrl}/repo/${repoName}`); return axios - .get(url.toString(), config) + .get(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; if (action === 'authorise') { @@ -44,7 +41,7 @@ const getRepos = async ( const url = new URL(`${baseUrl}/repo`); url.search = new URLSearchParams(query); setIsLoading(true); - await axios(url.toString(), config) + await axios(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; setData(data); @@ -53,9 +50,9 @@ const getRepos = async ( setIsError(true); if (error.response && error.response.status === 401) { setAuth(false); - setErrorMessage('Failed to authorize user. If JWT auth is enabled, please check your configuration or disable it.'); + setErrorMessage(processAuthError(error)); } else { - setErrorMessage(`Error fetching repositories: ${error.response.data.message}`); + setErrorMessage(`Error fetching repos: ${error.response.data.message}`); } }).finally(() => { setIsLoading(false); @@ -65,7 +62,7 @@ const getRepos = async ( const getRepo = async (setIsLoading, setData, setAuth, setIsError, id) => { const url = new URL(`${baseUrl}/repo/${id}`); setIsLoading(true); - await axios(url.toString(), config) + await axios(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; setData(data); @@ -84,7 +81,7 @@ const getRepo = async (setIsLoading, setData, setAuth, setIsError, id) => { const addRepo = async (onClose, setError, data) => { const url = new URL(`${baseUrl}/repo`); axios - .post(url, data, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .post(url, data, getAxiosConfig()) .then(() => { onClose(); }) @@ -100,7 +97,7 @@ const addUser = async (repoName, user, action) => { const url = new URL(`${baseUrl}/repo/${repoName}/user/${action}`); const data = { username: user }; await axios - .patch(url, data, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .patch(url, data, getAxiosConfig()) .catch((error) => { console.log(error.response.data.message); throw error; @@ -115,7 +112,7 @@ const deleteUser = async (user, repoName, action) => { const url = new URL(`${baseUrl}/repo/${repoName}/user/${action}/${user}`); await axios - .delete(url, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .delete(url, getAxiosConfig()) .catch((error) => { console.log(error.response.data.message); throw error; @@ -126,7 +123,7 @@ const deleteRepo = async (repoName) => { const url = new URL(`${baseUrl}/repo/${repoName}/delete`); await axios - .delete(url, { withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf') } }) + .delete(url, getAxiosConfig()) .catch((error) => { console.log(error.response.data.message); throw error; diff --git a/src/ui/services/user.js b/src/ui/services/user.js index bdccdfc45..bdcd9f28b 100644 --- a/src/ui/services/user.js +++ b/src/ui/services/user.js @@ -1,14 +1,11 @@ import axios from 'axios'; import { getCookie } from '../utils.jsx'; +import { getAxiosConfig, processAuthError } from './auth.js'; 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`; @@ -18,7 +15,7 @@ const getUser = async (setIsLoading, setData, setAuth, setIsError, id = null) => console.log(url); - await axios(url, config) + await axios(url, getAxiosConfig()) .then((response) => { const data = response.data; if (setData) { @@ -55,7 +52,7 @@ const getUsers = async ( const url = new URL(`${baseUrl}/api/v1/user`); url.search = new URLSearchParams(query); setIsLoading(true); - await axios(url.toString(), { withCredentials: true }) + await axios(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; setData(data); @@ -64,7 +61,7 @@ const getUsers = async ( setIsError(true); if (error.response && error.response.status === 401) { setAuth(false); - setErrorMessage('Failed to authorize user. If JWT auth is enabled, please check your configuration or disable it.'); + setErrorMessage(processAuthError(error)); } else { setErrorMessage(`Error fetching users: ${error.response.data.message}`); } @@ -77,7 +74,7 @@ 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') } }) + .post(url, data, getAxiosConfig()) .catch((error) => { console.log(error.response.data.message); throw error; @@ -87,7 +84,7 @@ const updateUser = async (data) => { const getUserLoggedIn = async (setIsLoading, setIsAdmin, setIsError, setAuth) => { const url = new URL(`${baseUrl}/api/auth/me`); - await axios(url.toString(), { withCredentials: true }) + await axios(url.toString(), getAxiosConfig()) .then((response) => { const data = response.data; setIsLoading(false); From efa4d5b1ef2fd957dbb1440002764b5eca6ce4fc Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Tue, 8 Jul 2025 22:48:28 +0900 Subject: [PATCH 04/17] fix: withCredentials missing issue and JWT auth skipping --- src/service/passport/jwtAuthHandler.js | 4 ---- src/ui/components/Navbars/DashboardNavbarLinks.jsx | 8 ++------ src/ui/services/auth.js | 3 ++- src/ui/views/Settings/Settings.jsx | 2 +- src/ui/views/User/User.jsx | 4 ++++ 5 files changed, 9 insertions(+), 12 deletions(-) diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index 32c81304d..73e09420b 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -17,10 +17,6 @@ const jwtAuthHandler = (overrideConfig = null) => { return next(); } - if (req.isAuthenticated()) { - return next(); - } - const token = req.header("Authorization"); if (!token) { return res.status(401).send("No token provided\n"); diff --git a/src/ui/components/Navbars/DashboardNavbarLinks.jsx b/src/ui/components/Navbars/DashboardNavbarLinks.jsx index a3e6e4177..1433fd8dd 100644 --- a/src/ui/components/Navbars/DashboardNavbarLinks.jsx +++ b/src/ui/components/Navbars/DashboardNavbarLinks.jsx @@ -16,6 +16,7 @@ import { AccountCircle } from '@material-ui/icons'; import { getUser } from '../../services/user'; import axios from 'axios'; import { getCookie } from '../../utils'; +import { getAxiosConfig } from '../../services/auth'; const useStyles = makeStyles(styles); @@ -52,12 +53,7 @@ export default function DashboardNavbarLinks() { .post( `${import.meta.env.VITE_API_URI}/api/auth/logout`, {}, - { - withCredentials: true, - headers: { - 'X-CSRF-TOKEN': getCookie('csrf'), - }, - }, + getAxiosConfig(), ) .then((res) => { if (!res.data.isAuth && !res.data.user) { diff --git a/src/ui/services/auth.js b/src/ui/services/auth.js index 18352940b..344f4e808 100644 --- a/src/ui/services/auth.js +++ b/src/ui/services/auth.js @@ -31,6 +31,7 @@ export const getAxiosConfig = () => { console.log('getAxiosConfig', getCookie('csrf'), localStorage.getItem('ui_jwt_token')); const jwtToken = localStorage.getItem('ui_jwt_token'); return { + withCredentials: true, headers: { 'X-CSRF-TOKEN': getCookie('csrf'), Authorization: jwtToken ? `Bearer ${jwtToken}` : undefined, @@ -51,4 +52,4 @@ export const processAuthError = (error) => { errorMessage += 'Check your JWT token or disable JWT auth in your app configuration.' } return errorMessage; -}; \ No newline at end of file +}; diff --git a/src/ui/views/Settings/Settings.jsx b/src/ui/views/Settings/Settings.jsx index fe166c76d..a14d8759b 100644 --- a/src/ui/views/Settings/Settings.jsx +++ b/src/ui/views/Settings/Settings.jsx @@ -70,7 +70,7 @@ export default function SettingsView() { {/* Title */} JWT Token for UI Authentication - The JWT token used to authenticate UI requests to the server when "apiAuthentication" is enabled in the config. + Authenticates UI requests to the server when "apiAuthentication" is enabled in the config. Loading...; if (isError) return
Something went wrong ...
; + if (!auth && window.location.pathname === '/dashboard/profile') { + return ; + } + const updateProfile = async () => { try { data.gitAccount = escapeHTML(gitAccount); From 766aa356e05bbaab615076328800a32f177d9666 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Wed, 9 Jul 2025 12:30:08 +0900 Subject: [PATCH 05/17] fix: Settings view card responsiveness --- src/ui/views/Settings/Settings.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/views/Settings/Settings.jsx b/src/ui/views/Settings/Settings.jsx index a14d8759b..bc31a69ea 100644 --- a/src/ui/views/Settings/Settings.jsx +++ b/src/ui/views/Settings/Settings.jsx @@ -64,7 +64,7 @@ export default function SettingsView() { return (
- + {/* Title */} From 4a0f1e4548c4be704afdb4662c8014f93c2964a8 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Wed, 9 Jul 2025 13:43:06 +0900 Subject: [PATCH 06/17] chore: fix linter --- src/ui/components/Navbars/DashboardNavbarLinks.jsx | 1 - src/ui/services/git-push.js | 1 - src/ui/services/repo.js | 1 - src/ui/services/user.js | 1 - src/ui/views/Settings/Settings.jsx | 2 +- 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/ui/components/Navbars/DashboardNavbarLinks.jsx b/src/ui/components/Navbars/DashboardNavbarLinks.jsx index 1433fd8dd..5ec373fa2 100644 --- a/src/ui/components/Navbars/DashboardNavbarLinks.jsx +++ b/src/ui/components/Navbars/DashboardNavbarLinks.jsx @@ -15,7 +15,6 @@ import { useNavigate } from 'react-router-dom'; import { AccountCircle } from '@material-ui/icons'; import { getUser } from '../../services/user'; import axios from 'axios'; -import { getCookie } from '../../utils'; import { getAxiosConfig } from '../../services/auth'; const useStyles = makeStyles(styles); diff --git a/src/ui/services/git-push.js b/src/ui/services/git-push.js index 8826ebf95..b3c88b17d 100644 --- a/src/ui/services/git-push.js +++ b/src/ui/services/git-push.js @@ -1,5 +1,4 @@ import axios from 'axios'; -import { getCookie } from '../utils.jsx'; import { getAxiosConfig, processAuthError } from './auth.js'; const baseUrl = import.meta.env.VITE_API_URI diff --git a/src/ui/services/repo.js b/src/ui/services/repo.js index db7e984cd..24f956dd2 100644 --- a/src/ui/services/repo.js +++ b/src/ui/services/repo.js @@ -1,5 +1,4 @@ import axios from 'axios'; -import { getCookie } from '../utils.jsx'; import { getAxiosConfig, processAuthError } from './auth.js'; const baseUrl = import.meta.env.VITE_API_URI diff --git a/src/ui/services/user.js b/src/ui/services/user.js index bdcd9f28b..7889537ce 100644 --- a/src/ui/services/user.js +++ b/src/ui/services/user.js @@ -1,5 +1,4 @@ import axios from 'axios'; -import { getCookie } from '../utils.jsx'; import { getAxiosConfig, processAuthError } from './auth.js'; const baseUrl = import.meta.env.VITE_API_URI diff --git a/src/ui/views/Settings/Settings.jsx b/src/ui/views/Settings/Settings.jsx index bc31a69ea..7649981cb 100644 --- a/src/ui/views/Settings/Settings.jsx +++ b/src/ui/views/Settings/Settings.jsx @@ -70,7 +70,7 @@ export default function SettingsView() { {/* Title */} JWT Token for UI Authentication - Authenticates UI requests to the server when "apiAuthentication" is enabled in the config. + Authenticates UI requests to the server when "apiAuthentication" is enabled in the config. Date: Thu, 10 Jul 2025 11:06:48 +0900 Subject: [PATCH 07/17] fix: move pushes error message out of table and replace all with Danger text --- src/ui/components/Typography/Danger.jsx | 2 +- .../OpenPushRequests/OpenPushRequests.jsx | 39 ++++++++++++++++--- .../components/PushesTable.jsx | 3 +- .../RepoList/Components/Repositories.jsx | 3 +- src/ui/views/UserList/Components/UserList.jsx | 3 +- 5 files changed, 40 insertions(+), 10 deletions(-) diff --git a/src/ui/components/Typography/Danger.jsx b/src/ui/components/Typography/Danger.jsx index aee271323..12693f660 100644 --- a/src/ui/components/Typography/Danger.jsx +++ b/src/ui/components/Typography/Danger.jsx @@ -8,7 +8,7 @@ const useStyles = makeStyles(styles); export default function Danger(props) { const classes = useStyles(); const { children } = props; - return
{children}
; + return
{children}
; } Danger.propTypes = { diff --git a/src/ui/views/OpenPushRequests/OpenPushRequests.jsx b/src/ui/views/OpenPushRequests/OpenPushRequests.jsx index f46f96715..b7c0c5c4b 100644 --- a/src/ui/views/OpenPushRequests/OpenPushRequests.jsx +++ b/src/ui/views/OpenPushRequests/OpenPushRequests.jsx @@ -1,14 +1,22 @@ -import React from 'react'; +import React, { useState } 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 Danger from '../../components/Typography/Danger'; import { Visibility, CheckCircle, Cancel, Block } from '@material-ui/icons'; export default function Dashboard() { + const [errorMessage, setErrorMessage] = useState(''); + + const handlePushTableError = (errorMessage) => { + setErrorMessage(errorMessage); + }; + return (
+ {errorMessage && {errorMessage}} + {!errorMessage && ( ), }, { tabName: 'Approved', tabIcon: CheckCircle, - tabContent: , + tabContent: ( + + ), }, { tabName: 'Canceled', tabIcon: Cancel, - tabContent: , + tabContent: ( + + ), }, { tabName: 'Rejected', tabIcon: Block, - tabContent: , + tabContent: ( + + ), }, ]} /> + )}
); } diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.jsx b/src/ui/views/OpenPushRequests/components/PushesTable.jsx index fad06e27e..0caaf8146 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.jsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.jsx @@ -36,7 +36,7 @@ export default function PushesTable(props) { for (const k in props) { if (k) query[k] = props[k]; } - getPushes(setIsLoading, setData, setAuth, setIsError, setErrorMessage, query); + getPushes(setIsLoading, setData, setAuth, setIsError, props.handlePushTableError, query); }, [props]); useEffect(() => { @@ -70,7 +70,6 @@ export default function PushesTable(props) { const paginate = (pageNumber) => setCurrentPage(pageNumber); if (isLoading) return
Loading...
; - if (isError) return
{errorMessage}
; return (
diff --git a/src/ui/views/RepoList/Components/Repositories.jsx b/src/ui/views/RepoList/Components/Repositories.jsx index 3b64944f2..92390d3d0 100644 --- a/src/ui/views/RepoList/Components/Repositories.jsx +++ b/src/ui/views/RepoList/Components/Repositories.jsx @@ -15,6 +15,7 @@ import PropTypes from 'prop-types'; import Search from '../../../components/Search/Search'; import Pagination from '../../../components/Pagination/Pagination'; import Filtering from '../../../components/Filtering/Filtering'; +import Danger from '../../../components/Typography/Danger'; export default function Repositories(props) { const useStyles = makeStyles(styles); @@ -101,7 +102,7 @@ export default function Repositories(props) { const paginatedData = filteredData.slice(startIdx, startIdx + itemsPerPage); if (isLoading) return
Loading...
; - if (isError) return
{errorMessage}
; + if (isError) return {errorMessage}; const addrepoButton = user.admin ? ( diff --git a/src/ui/views/UserList/Components/UserList.jsx b/src/ui/views/UserList/Components/UserList.jsx index a3fdc9de4..498a50440 100644 --- a/src/ui/views/UserList/Components/UserList.jsx +++ b/src/ui/views/UserList/Components/UserList.jsx @@ -16,6 +16,7 @@ import { getUsers } from '../../../services/user'; import Pagination from '../../../components/Pagination/Pagination'; import { CloseRounded, Check, KeyboardArrowRight } from '@material-ui/icons'; import Search from '../../../components/Search/Search'; +import Danger from '../../../components/Typography/Danger'; const useStyles = makeStyles(styles); @@ -44,7 +45,7 @@ export default function UserList(props) { }, [props]); if (isLoading) return
Loading...
; - if (isError) return
{errorMessage}
; + if (isError) return {errorMessage}; const filteredUsers = data.filter( (user) => From 334b6fe8c7c5b30cc86cacb7a86fb1f7f28c2927 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 10 Jul 2025 11:52:23 +0900 Subject: [PATCH 08/17] fix: undefined error message on missing JWT configuration --- src/service/passport/jwtAuthHandler.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/service/passport/jwtAuthHandler.js b/src/service/passport/jwtAuthHandler.js index 73e09420b..9ebfa2bcb 100644 --- a/src/service/passport/jwtAuthHandler.js +++ b/src/service/passport/jwtAuthHandler.js @@ -26,11 +26,15 @@ const jwtAuthHandler = (overrideConfig = null) => { const audience = expectedAudience || clientID; if (!authorityURL) { - return res.status(500).send("OIDC authority URL is not configured\n"); + return res.status(500).send({ + message: "JWT handler: authority URL is not configured\n" + }); } if (!clientID) { - return res.status(500).send("OIDC client ID is not configured\n"); + return res.status(500).send({ + message: "JWT handler: client ID is not configured\n" + }); } const tokenParts = token.split(" "); From e22eec5e6e1dcaa5bec6c68976b1772f2dd5edca Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Thu, 10 Jul 2025 12:07:19 +0900 Subject: [PATCH 09/17] chore: fix linter and broken tests --- src/ui/views/OpenPushRequests/components/PushesTable.jsx | 3 +-- test/testJwtAuthHandler.test.js | 4 ++-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.jsx b/src/ui/views/OpenPushRequests/components/PushesTable.jsx index 0caaf8146..263a78e99 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.jsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.jsx @@ -22,8 +22,7 @@ export default function PushesTable(props) { const [data, setData] = useState([]); const [filteredData, setFilteredData] = useState([]); const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); - const [errorMessage, setErrorMessage] = useState(''); + const [, setIsError] = useState(false); const navigate = useNavigate(); const [, setAuth] = useState(true); const [currentPage, setCurrentPage] = useState(1); diff --git a/test/testJwtAuthHandler.test.js b/test/testJwtAuthHandler.test.js index af2bb1bb2..536d10d05 100644 --- a/test/testJwtAuthHandler.test.js +++ b/test/testJwtAuthHandler.test.js @@ -167,7 +167,7 @@ describe('jwtAuthHandler', () => { await jwtAuthHandler(jwtConfig)(req, res, next); expect(res.status.calledWith(500)).to.be.true; - expect(res.send.calledWith('OIDC authority URL is not configured\n')).to.be.true; + expect(res.send.calledWith({ message: 'JWT handler: authority URL is not configured\n' })).to.be.true; }); it('should return 500 if clientID not configured', async () => { @@ -178,7 +178,7 @@ describe('jwtAuthHandler', () => { await jwtAuthHandler(jwtConfig)(req, res, next); expect(res.status.calledWith(500)).to.be.true; - expect(res.send.calledWith('OIDC client ID is not configured\n')).to.be.true; + expect(res.send.calledWith({ message: 'JWT handler: client ID is not configured\n' })).to.be.true; }); it('should return 401 if JWT validation fails', async () => { From 88e79d4d41ab6eb6586e5df2e31369c4623c63ed Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Mon, 21 Jul 2025 19:08:50 +0900 Subject: [PATCH 10/17] fix: navbar styling issue --- src/ui/components/Navbars/Navbar.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/Navbars/Navbar.tsx b/src/ui/components/Navbars/Navbar.tsx index dd1a931d9..4ae4474b9 100644 --- a/src/ui/components/Navbars/Navbar.tsx +++ b/src/ui/components/Navbars/Navbar.tsx @@ -38,7 +38,7 @@ const Header: React.FC = (props) => { }); return ( - +
{/* Here we create navbar brand, based on route name */} From 8131f12f3e9ccf35bbc233225eecccc4b63976ed Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Mon, 21 Jul 2025 19:09:17 +0900 Subject: [PATCH 11/17] fix: missing error display --- src/ui/services/auth.js | 2 +- src/ui/services/user.ts | 1 - src/ui/views/OpenPushRequests/components/PushesTable.tsx | 2 +- src/ui/views/RepoDetails/Components/AddUser.tsx | 7 ++++--- src/ui/views/UserList/Components/UserList.tsx | 5 ++--- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/ui/services/auth.js b/src/ui/services/auth.js index 344f4e808..bb2533d9a 100644 --- a/src/ui/services/auth.js +++ b/src/ui/services/auth.js @@ -1,4 +1,4 @@ -import { getCookie } from '../utils.jsx'; +import { getCookie } from '../utils'; const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}` diff --git a/src/ui/services/user.ts b/src/ui/services/user.ts index f07501769..18b2d36cf 100644 --- a/src/ui/services/user.ts +++ b/src/ui/services/user.ts @@ -40,7 +40,6 @@ const getUsers = async ( setIsLoading: SetStateCallback, setData: SetStateCallback, setAuth: SetStateCallback, - setIsError: SetStateCallback, setErrorMessage: SetStateCallback, query: Record = {}, ): Promise => { diff --git a/src/ui/views/OpenPushRequests/components/PushesTable.tsx b/src/ui/views/OpenPushRequests/components/PushesTable.tsx index e45dfb9ae..fb957f282 100644 --- a/src/ui/views/OpenPushRequests/components/PushesTable.tsx +++ b/src/ui/views/OpenPushRequests/components/PushesTable.tsx @@ -45,7 +45,7 @@ const PushesTable: React.FC = (props) => { authorised: props.authorised ?? false, rejected: props.rejected ?? false, }; - getPushes(setIsLoading, setData, setAuth, setIsError, props.handlePushTableError, query); + getPushes(setIsLoading, setData, setAuth, setIsError, props.handleError, query); }, [props]); useEffect(() => { diff --git a/src/ui/views/RepoDetails/Components/AddUser.tsx b/src/ui/views/RepoDetails/Components/AddUser.tsx index bf59676e6..523b28f0c 100644 --- a/src/ui/views/RepoDetails/Components/AddUser.tsx +++ b/src/ui/views/RepoDetails/Components/AddUser.tsx @@ -17,6 +17,7 @@ import { addUser } from '../../../services/repo'; import { getUsers } from '../../../services/user'; import { PersonAdd } from '@material-ui/icons'; import { UserData } from '../../../../types/models'; +import Danger from '../../../components/Typography/Danger'; interface AddUserDialogProps { repoName: string; @@ -37,7 +38,7 @@ const AddUserDialog: React.FC = ({ const [data, setData] = useState([]); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); const [error, setError] = useState(''); const [tip, setTip] = useState(false); @@ -76,10 +77,10 @@ const AddUserDialog: React.FC = ({ }; useEffect(() => { - getUsers(setIsLoading, setData, setAuth, setIsError, setError, {}); + getUsers(setIsLoading, setData, setAuth, setErrorMessage, {}); }, []); - if (isError) return
Something went wrong ...
; + if (errorMessage) return {errorMessage}; return ( <> diff --git a/src/ui/views/UserList/Components/UserList.tsx b/src/ui/views/UserList/Components/UserList.tsx index b595a709d..c38ed026d 100644 --- a/src/ui/views/UserList/Components/UserList.tsx +++ b/src/ui/views/UserList/Components/UserList.tsx @@ -30,7 +30,6 @@ const UserList: React.FC = (props) => { const [data, setData] = useState([]); const [, setAuth] = useState(true); const [isLoading, setIsLoading] = useState(false); - const [isError, setIsError] = useState(false); const [errorMessage, setErrorMessage] = useState(''); const navigate = useNavigate(); const [currentPage, setCurrentPage] = useState(1); @@ -47,11 +46,11 @@ const UserList: React.FC = (props) => { if (!k) continue; query[k] = props[k]; } - getUsers(setIsLoading, setData, setAuth, setIsError, setErrorMessage, query); + getUsers(setIsLoading, setData, setAuth, setErrorMessage, query); }, [props]); if (isLoading) return
Loading...
; - if (isError) return {errorMessage}; + if (errorMessage) return {errorMessage}; const filteredUsers = data.filter( (user) => From 1e66bfb105e3412b93c729ffb5ebca662ef66052 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Mon, 21 Jul 2025 19:12:24 +0900 Subject: [PATCH 12/17] chore: fix linter --- src/ui/components/Navbars/DashboardNavbarLinks.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui/components/Navbars/DashboardNavbarLinks.tsx b/src/ui/components/Navbars/DashboardNavbarLinks.tsx index 883e438ab..91ab3eed4 100644 --- a/src/ui/components/Navbars/DashboardNavbarLinks.tsx +++ b/src/ui/components/Navbars/DashboardNavbarLinks.tsx @@ -51,7 +51,7 @@ const DashboardNavbarLinks: React.FC = () => { const logout = async () => { try { - const response = await axios.post( + await axios.post( `${process.env.VITE_API_URI || 'http://localhost:3000'}/api/auth/logout`, {}, getAxiosConfig(), From e9ee6b69684b41fb3f0b41b5c5a8be19445db184 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 22 Aug 2025 12:57:36 +0900 Subject: [PATCH 13/17] chore: add clsx and replace class name building --- package-lock.json | 14 +++++--------- package.json | 2 +- src/ui/components/Card/Card.tsx | 4 ++-- src/ui/components/Card/CardAvatar.tsx | 4 ++-- src/ui/components/Card/CardBody.tsx | 4 ++-- src/ui/components/Card/CardFooter.tsx | 4 ++-- src/ui/components/Card/CardHeader.tsx | 4 ++-- src/ui/components/Card/CardIcon.tsx | 4 ++-- src/ui/components/CustomButtons/Button.tsx | 4 ++-- src/ui/components/CustomInput/CustomInput.jsx | 18 +++++++++--------- src/ui/components/CustomTabs/CustomTabs.tsx | 4 ++-- .../Navbars/DashboardNavbarLinks.tsx | 4 ++-- src/ui/components/Navbars/Navbar.tsx | 4 ++-- src/ui/components/Sidebar/Sidebar.tsx | 16 ++++++++-------- src/ui/components/Snackbar/Snackbar.tsx | 8 ++++---- src/ui/components/Snackbar/SnackbarContent.tsx | 8 ++++---- src/ui/components/Table/Table.jsx | 9 +++++---- src/ui/components/Typography/Danger.jsx | 3 ++- src/ui/components/Typography/Info.jsx | 3 ++- src/ui/components/Typography/Muted.jsx | 3 ++- src/ui/components/Typography/Primary.jsx | 3 ++- src/ui/components/Typography/Quote.jsx | 3 ++- src/ui/components/Typography/Success.jsx | 3 ++- src/ui/components/Typography/Warning.jsx | 3 ++- src/ui/services/auth.js | 1 - 25 files changed, 70 insertions(+), 67 deletions(-) diff --git a/package-lock.json b/package-lock.json index 45561bf1c..2d2f2de18 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "axios": "^1.6.0", "bcryptjs": "^3.0.2", "bit-mask": "^1.0.2", - "classnames": "2.5.1", + "clsx": "^2.1.1", "concurrently": "^9.0.0", "connect-mongo": "^5.1.0", "cors": "^2.8.5", @@ -5180,11 +5180,6 @@ "node": ">=8" } }, - "node_modules/classnames": { - "version": "2.5.1", - "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.5.1.tgz", - "integrity": "sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==" - }, "node_modules/clean-git-ref": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/clean-git-ref/-/clean-git-ref-2.0.1.tgz", @@ -5330,9 +5325,10 @@ } }, "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", "engines": { "node": ">=6" } diff --git a/package.json b/package.json index 8f5f9a9f5..f547800a7 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "axios": "^1.6.0", "bcryptjs": "^3.0.2", "bit-mask": "^1.0.2", - "classnames": "2.5.1", + "clsx": "^2.1.1", "concurrently": "^9.0.0", "connect-mongo": "^5.1.0", "cors": "^2.8.5", diff --git a/src/ui/components/Card/Card.tsx b/src/ui/components/Card/Card.tsx index edbf2ee2c..0ae6704db 100644 --- a/src/ui/components/Card/Card.tsx +++ b/src/ui/components/Card/Card.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardStyle'; @@ -23,7 +23,7 @@ const Card: React.FC = ({ }) => { const classes = useStyles(); - const cardClasses = classNames({ + const cardClasses = clsx({ [classes.card]: true, [classes.cardPlain]: plain, [classes.cardProfile]: profile, diff --git a/src/ui/components/Card/CardAvatar.tsx b/src/ui/components/Card/CardAvatar.tsx index 2bbfe9db4..c23554d3d 100644 --- a/src/ui/components/Card/CardAvatar.tsx +++ b/src/ui/components/Card/CardAvatar.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardAvatarStyle'; @@ -21,7 +21,7 @@ const CardAvatar: React.FC = ({ }) => { const classes = useStyles(); - const cardAvatarClasses = classNames({ + const cardAvatarClasses = clsx({ [classes.cardAvatar]: true, [classes.cardAvatarProfile]: profile, [classes.cardAvatarPlain]: plain, diff --git a/src/ui/components/Card/CardBody.tsx b/src/ui/components/Card/CardBody.tsx index 1a32e2354..dd4de1171 100644 --- a/src/ui/components/Card/CardBody.tsx +++ b/src/ui/components/Card/CardBody.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardBodyStyle'; @@ -21,7 +21,7 @@ const CardBody: React.FC = ({ }) => { const classes = useStyles(); - const cardBodyClasses = classNames({ + const cardBodyClasses = clsx({ [classes.cardBody]: true, [classes.cardBodyPlain]: plain, [classes.cardBodyProfile]: profile, diff --git a/src/ui/components/Card/CardFooter.tsx b/src/ui/components/Card/CardFooter.tsx index 57f9f36a5..635beabfc 100644 --- a/src/ui/components/Card/CardFooter.tsx +++ b/src/ui/components/Card/CardFooter.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardFooterStyle'; @@ -25,7 +25,7 @@ const CardFooter: React.FC = ({ }) => { const classes = useStyles(); - const cardFooterClasses = classNames({ + const cardFooterClasses = clsx({ [classes.cardFooter]: true, [classes.cardFooterPlain]: plain, [classes.cardFooterProfile]: profile, diff --git a/src/ui/components/Card/CardHeader.tsx b/src/ui/components/Card/CardHeader.tsx index c893715cc..a808a262f 100644 --- a/src/ui/components/Card/CardHeader.tsx +++ b/src/ui/components/Card/CardHeader.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardHeaderStyle'; @@ -20,7 +20,7 @@ const CardHeader: React.FC = (props) => { const classes = useStyles(); const { className, children, color, plain, stats, icon, ...rest } = props; - const cardHeaderClasses = classNames({ + const cardHeaderClasses = clsx({ [classes.cardHeader]: true, [color ? classes[`${color}CardHeader`] : '']: color, [classes.cardHeaderPlain]: plain, diff --git a/src/ui/components/Card/CardIcon.tsx b/src/ui/components/Card/CardIcon.tsx index 353f07c86..c9a6544de 100644 --- a/src/ui/components/Card/CardIcon.tsx +++ b/src/ui/components/Card/CardIcon.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/cardIconStyle'; @@ -18,7 +18,7 @@ const CardIcon: React.FC = (props) => { const classes = useStyles(); const { className, children, color, ...rest } = props; - const cardIconClasses = classNames({ + const cardIconClasses = clsx({ [classes.cardIcon]: true, [color ? classes[`${color}CardHeader`] : '']: color, [className || '']: className !== undefined, diff --git a/src/ui/components/CustomButtons/Button.tsx b/src/ui/components/CustomButtons/Button.tsx index 01e8e0eb1..e6eea281a 100644 --- a/src/ui/components/CustomButtons/Button.tsx +++ b/src/ui/components/CustomButtons/Button.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import Button, { ButtonProps } from '@material-ui/core/Button'; import styles from '../../assets/jss/material-dashboard-react/components/buttonStyle'; @@ -48,7 +48,7 @@ export default function RegularButton(props: RegularButtonProps) { ...rest } = props; - const btnClasses = classNames({ + const btnClasses = clsx({ [classes.button]: true, [classes[size as Size]]: size, [classes[color as Color]]: color, diff --git a/src/ui/components/CustomInput/CustomInput.jsx b/src/ui/components/CustomInput/CustomInput.jsx index b5eea6834..831f8c804 100644 --- a/src/ui/components/CustomInput/CustomInput.jsx +++ b/src/ui/components/CustomInput/CustomInput.jsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import PropTypes from 'prop-types'; import { makeStyles } from '@material-ui/core/styles'; import FormControl from '@material-ui/core/FormControl'; @@ -15,25 +15,25 @@ export default function CustomInput(props) { const classes = useStyles(); const { formControlProps, labelText, id, labelProps, inputProps, error, success } = props; - const labelClasses = classNames({ - [' ' + classes.labelRootError]: error, - [' ' + classes.labelRootSuccess]: success && !error, + const labelClasses = clsx({ + [classes.labelRootError]: error, + [classes.labelRootSuccess]: success && !error, }); - const underlineClasses = classNames({ + const underlineClasses = clsx({ [classes.underlineError]: error, [classes.underlineSuccess]: success && !error, [classes.underline]: true, }); - const marginTop = classNames({ + const marginTop = clsx({ [classes.marginTop]: labelText === undefined, }); const generateIcon = () => { if (error) { - return ; + return ; } if (success) { - return ; + return ; } return null; }; @@ -41,7 +41,7 @@ export default function CustomInput(props) { return ( {labelText !== undefined ? ( diff --git a/src/ui/components/CustomTabs/CustomTabs.tsx b/src/ui/components/CustomTabs/CustomTabs.tsx index 8f9daf3a3..8cd0c2d81 100644 --- a/src/ui/components/CustomTabs/CustomTabs.tsx +++ b/src/ui/components/CustomTabs/CustomTabs.tsx @@ -1,5 +1,5 @@ import React, { useState } from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import Tabs from '@material-ui/core/Tabs'; import Tab from '@material-ui/core/Tab'; @@ -40,7 +40,7 @@ const CustomTabs: React.FC = ({ setValue(newValue); }; - const cardTitle = classNames({ + const cardTitle = clsx({ [classes.cardTitle]: true, [classes.cardTitleRTL]: rtlActive, }); diff --git a/src/ui/components/Navbars/DashboardNavbarLinks.tsx b/src/ui/components/Navbars/DashboardNavbarLinks.tsx index 91ab3eed4..a53bd83d2 100644 --- a/src/ui/components/Navbars/DashboardNavbarLinks.tsx +++ b/src/ui/components/Navbars/DashboardNavbarLinks.tsx @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import MenuItem from '@material-ui/core/MenuItem'; import MenuList from '@material-ui/core/MenuList'; @@ -89,7 +89,7 @@ const DashboardNavbarLinks: React.FC = () => { anchorEl={openProfile} transition disablePortal - className={classNames({ [classes.popperClose]: !openProfile }) + ' ' + classes.popperNav} + className={clsx(classes.popperNav, { [classes.popperClose]: !openProfile })} > {({ TransitionProps, placement }) => ( = (props) => { }; const { color = 'primary' } = props; - const appBarClasses = classNames({ + const appBarClasses = clsx({ [` ${classes[color]}`]: color, }); diff --git a/src/ui/components/Sidebar/Sidebar.tsx b/src/ui/components/Sidebar/Sidebar.tsx index 5d6b27c40..a2f745948 100644 --- a/src/ui/components/Sidebar/Sidebar.tsx +++ b/src/ui/components/Sidebar/Sidebar.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { NavLink } from 'react-router-dom'; import { makeStyles } from '@material-ui/core/styles'; import Drawer from '@material-ui/core/Drawer'; @@ -40,10 +40,10 @@ const Sidebar: React.FC = ({ {routes.map((prop, key) => { const activePro = ' '; - const listItemClasses = classNames({ + const listItemClasses = clsx({ [` ${classes[color]}`]: activeRoute(prop.layout + prop.path), }); - const whiteFontClasses = classNames({ + const whiteFontClasses = clsx({ [` ${classes.whiteFont}`]: activeRoute(prop.layout + prop.path), }); @@ -61,7 +61,7 @@ const Sidebar: React.FC = ({ {typeof prop.icon === 'string' ? ( @@ -69,14 +69,14 @@ const Sidebar: React.FC = ({ ) : ( )} = ({ anchor={rtlActive ? 'left' : 'right'} open={open} classes={{ - paper: classNames(classes.drawerPaper, { + paper: clsx(classes.drawerPaper, { [classes.drawerPaperRTL]: rtlActive, }), }} @@ -131,7 +131,7 @@ const Sidebar: React.FC = ({ variant='permanent' open classes={{ - paper: classNames(classes.drawerPaper, { + paper: clsx(classes.drawerPaper, { [classes.drawerPaperRTL]: rtlActive, }), }} diff --git a/src/ui/components/Snackbar/Snackbar.tsx b/src/ui/components/Snackbar/Snackbar.tsx index 13dc6679d..df3134a64 100644 --- a/src/ui/components/Snackbar/Snackbar.tsx +++ b/src/ui/components/Snackbar/Snackbar.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import Snack from '@material-ui/core/Snackbar'; import IconButton from '@material-ui/core/IconButton'; @@ -27,7 +27,7 @@ const Snackbar: React.FC = (props) => { const { message, color = 'info', close, icon: Icon, place = 'tr', open, rtlActive } = props; let action: React.ReactNode[] = []; - const messageClasses = classNames({ + const messageClasses = clsx({ [classes.iconMessage]: Icon !== undefined, }); @@ -70,9 +70,9 @@ const Snackbar: React.FC = (props) => { action={action} ContentProps={{ classes: { - root: `${classes.root} ${classes[color]}`, + root: clsx(classes.root, classes[color]), message: classes.message, - action: classNames({ [classes.actionRTL]: rtlActive }), + action: clsx({ [classes.actionRTL]: rtlActive }), }, }} /> diff --git a/src/ui/components/Snackbar/SnackbarContent.tsx b/src/ui/components/Snackbar/SnackbarContent.tsx index 76e3c12b8..30f4a4e18 100644 --- a/src/ui/components/Snackbar/SnackbarContent.tsx +++ b/src/ui/components/Snackbar/SnackbarContent.tsx @@ -1,5 +1,5 @@ import React from 'react'; -import classNames from 'classnames'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import MuiSnackbarContent from '@material-ui/core/SnackbarContent'; import IconButton from '@material-ui/core/IconButton'; @@ -23,7 +23,7 @@ const SnackbarContent: React.FC = (props) => { const { message, color = 'info', close, icon: Icon, rtlActive } = props; let action: React.ReactNode[] = []; - const messageClasses = classNames({ + const messageClasses = clsx({ [classes.iconMessage]: Icon !== undefined, }); @@ -44,9 +44,9 @@ const SnackbarContent: React.FC = (props) => {
} classes={{ - root: `${classes.root} ${classes[color]}`, + root: clsx(classes.root, classes[color]), message: classes.message, - action: classNames({ [classes.actionRTL]: rtlActive }), + action: clsx({ [classes.actionRTL]: rtlActive }), }} action={action} /> diff --git a/src/ui/components/Table/Table.jsx b/src/ui/components/Table/Table.jsx index e754a090d..c2cebfecf 100644 --- a/src/ui/components/Table/Table.jsx +++ b/src/ui/components/Table/Table.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import Table from '@material-ui/core/Table'; @@ -18,11 +19,11 @@ export default function CustomTable(props) {
{tableHead !== undefined ? ( - + {tableHead.map((prop, key) => { return ( - + {prop} ); @@ -33,10 +34,10 @@ export default function CustomTable(props) { {tableData.map((prop, key) => { return ( - + {prop.map((p, k) => { return ( - + {p} ); diff --git a/src/ui/components/Typography/Danger.jsx b/src/ui/components/Typography/Danger.jsx index 12693f660..ee6e94b59 100644 --- a/src/ui/components/Typography/Danger.jsx +++ b/src/ui/components/Typography/Danger.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/typographyStyle'; @@ -8,7 +9,7 @@ const useStyles = makeStyles(styles); export default function Danger(props) { const classes = useStyles(); const { children } = props; - return
{children}
; + return
{children}
; } Danger.propTypes = { diff --git a/src/ui/components/Typography/Info.jsx b/src/ui/components/Typography/Info.jsx index f8aeebac1..17c3a9ddc 100644 --- a/src/ui/components/Typography/Info.jsx +++ b/src/ui/components/Typography/Info.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; import { makeStyles } from '@material-ui/core/styles'; import styles from '../../assets/jss/material-dashboard-react/components/typographyStyle'; @@ -8,7 +9,7 @@ const useStyles = makeStyles(styles); export default function Info(props) { const classes = useStyles(); const { children } = props; - return
{children}
; + return
{children}
; } Info.propTypes = { diff --git a/src/ui/components/Typography/Muted.jsx b/src/ui/components/Typography/Muted.jsx index 686547970..9b625c5f2 100644 --- a/src/ui/components/Typography/Muted.jsx +++ b/src/ui/components/Typography/Muted.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; // @material-ui/core components import { makeStyles } from '@material-ui/core/styles'; // core components @@ -10,7 +11,7 @@ const useStyles = makeStyles(styles); export default function Muted(props) { const classes = useStyles(); const { children } = props; - return
{children}
; + return
{children}
; } Muted.propTypes = { diff --git a/src/ui/components/Typography/Primary.jsx b/src/ui/components/Typography/Primary.jsx index 38f3d81ed..b58206c4f 100644 --- a/src/ui/components/Typography/Primary.jsx +++ b/src/ui/components/Typography/Primary.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; // @material-ui/core components import { makeStyles } from '@material-ui/core/styles'; // core components @@ -10,7 +11,7 @@ const useStyles = makeStyles(styles); export default function Primary(props) { const classes = useStyles(); const { children } = props; - return
{children}
; + return
{children}
; } Primary.propTypes = { diff --git a/src/ui/components/Typography/Quote.jsx b/src/ui/components/Typography/Quote.jsx index 5ade54e41..3dedbc7bb 100644 --- a/src/ui/components/Typography/Quote.jsx +++ b/src/ui/components/Typography/Quote.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; // @material-ui/core components import { makeStyles } from '@material-ui/core/styles'; // core components @@ -11,7 +12,7 @@ export default function Quote(props) { const classes = useStyles(); const { text, author } = props; return ( -
+

{text}

{author}
diff --git a/src/ui/components/Typography/Success.jsx b/src/ui/components/Typography/Success.jsx index 56951ce6b..a40affc47 100644 --- a/src/ui/components/Typography/Success.jsx +++ b/src/ui/components/Typography/Success.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; // @material-ui/core components import { makeStyles } from '@material-ui/core/styles'; // core components @@ -10,7 +11,7 @@ const useStyles = makeStyles(styles); export default function Success(props) { const classes = useStyles(); const { children } = props; - return
{children}
; + return
{children}
; } Success.propTypes = { diff --git a/src/ui/components/Typography/Warning.jsx b/src/ui/components/Typography/Warning.jsx index b8349411d..70db1ea6d 100644 --- a/src/ui/components/Typography/Warning.jsx +++ b/src/ui/components/Typography/Warning.jsx @@ -1,5 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; +import clsx from 'clsx'; // @material-ui/core components import { makeStyles } from '@material-ui/core/styles'; // core components @@ -10,7 +11,7 @@ const useStyles = makeStyles(styles); export default function Warning(props) { const classes = useStyles(); const { children } = props; - return
{children}
; + return
{children}
; } Warning.propTypes = { diff --git a/src/ui/services/auth.js b/src/ui/services/auth.js index bb2533d9a..ae022bfc8 100644 --- a/src/ui/services/auth.js +++ b/src/ui/services/auth.js @@ -28,7 +28,6 @@ export const getUserInfo = async () => { * @return {Object} The Axios config */ export const getAxiosConfig = () => { - console.log('getAxiosConfig', getCookie('csrf'), localStorage.getItem('ui_jwt_token')); const jwtToken = localStorage.getItem('ui_jwt_token'); return { withCredentials: true, From df0fbf411135d9d51e7af1352b642f4658872110 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 22 Aug 2025 13:41:33 +0900 Subject: [PATCH 14/17] chore: fix Code button styling issue --- src/ui/components/CustomButtons/CodeActionButton.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/ui/components/CustomButtons/CodeActionButton.tsx b/src/ui/components/CustomButtons/CodeActionButton.tsx index 47b3e3e20..8c9e37e7a 100644 --- a/src/ui/components/CustomButtons/CodeActionButton.tsx +++ b/src/ui/components/CustomButtons/CodeActionButton.tsx @@ -38,9 +38,13 @@ const CodeActionButton: React.FC = ({ cloneURL }) => { padding: '6px 10px 6px 10px', fontWeight: 'bold', cursor: 'pointer', - border: '1px solid rgba(240,246,252,0.1)', + border: '1px solid rgba(2, 2, 2, 0.1)', boxSizing: 'border-box', whiteSpace: 'nowrap', + display: 'inline-flex', + alignItems: 'center', + justifyContent: 'center', + boxShadow: '0 1px 2px 1px rgba(0, 0, 0, 0.2)', }} onClick={handleClick('bottom-end')} > From b5e5ddaec332427fd921e36d64ed4286763f1e51 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 22 Aug 2025 13:41:48 +0900 Subject: [PATCH 15/17] chore: clean up axios calls --- .../Navbars/DashboardNavbarLinks.tsx | 17 +++--- src/ui/services/git-push.js | 56 ++++++++++--------- 2 files changed, 37 insertions(+), 36 deletions(-) diff --git a/src/ui/components/Navbars/DashboardNavbarLinks.tsx b/src/ui/components/Navbars/DashboardNavbarLinks.tsx index a53bd83d2..e13c927d2 100644 --- a/src/ui/components/Navbars/DashboardNavbarLinks.tsx +++ b/src/ui/components/Navbars/DashboardNavbarLinks.tsx @@ -51,17 +51,16 @@ const DashboardNavbarLinks: React.FC = () => { const logout = async () => { try { - await axios.post( + const { data } = await axios.post( `${process.env.VITE_API_URI || 'http://localhost:3000'}/api/auth/logout`, {}, - getAxiosConfig(), - ) - .then((res) => { - if (!res.data.isAuth && !res.data.user) { - setAuth(false); - navigate(0); - } - }); + getAxiosConfig() + ); + + if (!data.isAuth && !data.user) { + setAuth(false); + navigate(0); + } } catch (error) { console.error('Logout failed:', error); } diff --git a/src/ui/services/git-push.js b/src/ui/services/git-push.js index b3c88b17d..23ac9f3a5 100644 --- a/src/ui/services/git-push.js +++ b/src/ui/services/git-push.js @@ -5,21 +5,22 @@ const baseUrl = import.meta.env.VITE_API_URI ? `${import.meta.env.VITE_API_URI}/api/v1` : `${location.origin}/api/v1`; -const getPush = async (id, setIsLoading, setData, setAuth, setIsError) => { - const url = `${baseUrl}/push/${id}`; - await axios(url, getAxiosConfig()) - .then((response) => { + const getPush = async (id, setIsLoading, setData, setAuth, setIsError) => { + const url = `${baseUrl}/push/${id}`; + setIsLoading(true); + + try { + const response = await axios(url, getAxiosConfig()); const data = response.data; data.diff = data.steps.find((x) => x.stepName === 'diff'); setData(data); - setIsLoading(false); - }) - .catch((error) => { - if (error.response && error.response.status === 401) setAuth(false); + } catch (error) { + if (error.response?.status === 401) setAuth(false); else setIsError(true); + } finally { setIsLoading(false); - }); -}; + } + }; const getPushes = async ( setIsLoading, @@ -32,28 +33,29 @@ const getPushes = async ( canceled: false, authorised: false, rejected: false, - }, + } ) => { const url = new URL(`${baseUrl}/push`); url.search = new URLSearchParams(query); setIsLoading(true); - await axios(url.toString(), getAxiosConfig()) - .then((response) => { - const data = response.data; - setData(data); - }) - .catch((error) => { - setIsError(true); - if (error.response && error.response.status === 401) { - setAuth(false); - setErrorMessage(processAuthError(error)); - } else { - setErrorMessage(`Error fetching pushes: ${error.response.data.message}`); - } - }).finally(() => { - setIsLoading(false); - }); + + try { + const response = await axios(url.toString(), getAxiosConfig()); + setData(response.data); + } catch (error) { + setIsError(true); + + if (error.response?.status === 401) { + setAuth(false); + setErrorMessage(processAuthError(error)); + } else { + const message = error.response?.data?.message || error.message; + setErrorMessage(`Error fetching pushes: ${message}`); + } + } finally { + setIsLoading(false); + } }; const authorisePush = async (id, setMessage, setUserAllowedToApprove, attestation) => { From aa8ee6a57b387a0d511cb75bc7346ad8f6021820 Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 22 Aug 2025 13:42:13 +0900 Subject: [PATCH 16/17] chore: fix dep vulnerability --- package-lock.json | 52 ++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/package-lock.json b/package-lock.json index 253e80512..e324697cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1881,6 +1881,15 @@ } } }, + "node_modules/@material-ui/core/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/@material-ui/icons": { "version": "4.11.3", "resolved": "https://registry.npmjs.org/@material-ui/icons/-/icons-4.11.3.tgz", @@ -1944,6 +1953,15 @@ } } }, + "node_modules/@material-ui/styles/node_modules/clsx": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", + "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/@material-ui/system": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.12.2.tgz", @@ -7740,7 +7758,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -11118,15 +11135,23 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, "node_modules/sha.js": { - "version": "2.4.11", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz", - "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==", + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", "dependencies": { - "inherits": "^2.0.1", - "safe-buffer": "^5.0.1" + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" }, "bin": { "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, "node_modules/shebang-command": { @@ -11883,6 +11908,20 @@ "node": ">=14.14" } }, + "node_modules/to-buffer": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", + "license": "MIT", + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -12493,7 +12532,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", From 9d29901d1a2399a8ad88ccaa1eb4c170c75240ed Mon Sep 17 00:00:00 2001 From: Juan Escalada Date: Fri, 22 Aug 2025 14:19:25 +0900 Subject: [PATCH 17/17] chore: refactor code button styling and add ClickAwayListener --- .../CustomButtons/CodeActionButton.tsx | 123 +++++++++--------- 1 file changed, 62 insertions(+), 61 deletions(-) diff --git a/src/ui/components/CustomButtons/CodeActionButton.tsx b/src/ui/components/CustomButtons/CodeActionButton.tsx index 8c9e37e7a..7962fdb48 100644 --- a/src/ui/components/CustomButtons/CodeActionButton.tsx +++ b/src/ui/components/CustomButtons/CodeActionButton.tsx @@ -1,5 +1,6 @@ import Popper from '@material-ui/core/Popper'; import Paper from '@material-ui/core/Paper'; +import ClickAwayListener from '@material-ui/core/ClickAwayListener'; import { CheckIcon, ChevronDownIcon, @@ -9,6 +10,7 @@ import { } from '@primer/octicons-react'; import React, { useState } from 'react'; import { PopperPlacementType } from '@material-ui/core/Popper'; +import Button from './Button'; interface CodeActionButtonProps { cloneURL: string; @@ -28,30 +30,27 @@ const CodeActionButton: React.FC = ({ cloneURL }) => { setPlacement(newPlacement); }; + const handleClickAway = () => { + setOpen(false); + }; + return ( <> -
{' '} - Code + Code -
+ = ({ cloneURL }) => { zIndex: 99, }} > - -
- {' '} - Clone -
-
- + +
+ {' '} + Clone +
+
- {cloneURL} - - - {!isCopied && ( - { - navigator.clipboard.writeText(`git clone ${cloneURL}`); - setIsCopied(true); - }} - > - - - )} - {isCopied && ( - - - - )} + + {cloneURL} + + + {!isCopied && ( + { + navigator.clipboard.writeText(`git clone ${cloneURL}`); + setIsCopied(true); + }} + > + + + )} + {isCopied && ( + + + + )} + +
+
+
+ + Use Git and run this command in your IDE or Terminal 👍
-
- - Use Git and run this command in your IDE or Terminal 👍 - -
-
- + + );