diff --git a/src/ui/auth/AuthProvider.tsx b/src/ui/auth/AuthProvider.tsx index 94da60f65..a2409da60 100644 --- a/src/ui/auth/AuthProvider.tsx +++ b/src/ui/auth/AuthProvider.tsx @@ -1,16 +1,9 @@ import React, { createContext, useContext, useState, useEffect } from 'react'; import { getUserInfo } from '../services/auth'; - -// Interface for when we convert to TypeScript -// interface AuthContextType { -// user: any; -// setUser: (user: any) => void; -// refreshUser: () => Promise; -// isLoading: boolean; -// } +import { UserData } from '../../types/models'; interface AuthContextType { - user: any; + user: UserData | null; setUser: React.Dispatch; refreshUser: () => Promise; isLoading: boolean; @@ -19,7 +12,7 @@ interface AuthContextType { const AuthContext = createContext(undefined); export const AuthProvider: React.FC> = ({ children }) => { - const [user, setUser] = useState(null); + const [user, setUser] = useState(null); const [isLoading, setIsLoading] = useState(true); const refreshUser = async () => { diff --git a/src/ui/components/RouteGuard/RouteGuard.tsx b/src/ui/components/RouteGuard/RouteGuard.tsx index 4efb2c3c1..a975ce2fb 100644 --- a/src/ui/components/RouteGuard/RouteGuard.tsx +++ b/src/ui/components/RouteGuard/RouteGuard.tsx @@ -1,7 +1,8 @@ import React, { useEffect, useState } from 'react'; import { Navigate } from 'react-router-dom'; import { useAuth } from '../../auth/AuthProvider'; -import { getUIRouteAuth } from '../../services/config'; +import { setUIRouteAuthData } from '../../services/config'; +import { UIRouteAuth } from '../../../config/generated/config'; import CircularProgress from '@material-ui/core/CircularProgress'; interface RouteGuardProps { @@ -9,15 +10,6 @@ interface RouteGuardProps { fullRoutePath: string; } -interface UIRouteAuth { - enabled: boolean; - rules: { - pattern: string; - adminOnly: boolean; - loginRequired: boolean; - }[]; -} - const RouteGuard = ({ component: Component, fullRoutePath }: RouteGuardProps) => { const { user, isLoading } = useAuth(); @@ -26,14 +18,14 @@ const RouteGuard = ({ component: Component, fullRoutePath }: RouteGuardProps) => const [authChecked, setAuthChecked] = useState(false); useEffect(() => { - getUIRouteAuth((uiRouteAuth: UIRouteAuth) => { + setUIRouteAuthData((uiRouteAuth: UIRouteAuth) => { if (uiRouteAuth?.enabled) { - for (const rule of uiRouteAuth.rules) { - if (new RegExp(rule.pattern).test(fullRoutePath)) { + for (const rule of uiRouteAuth.rules ?? []) { + if (new RegExp(rule.pattern ?? '').test(fullRoutePath)) { // Allow multiple rules to be applied according to route precedence // Ex: /dashboard/admin/* will override /dashboard/* - setLoginRequired(loginRequired || rule.loginRequired); - setAdminOnly(adminOnly || rule.adminOnly); + setLoginRequired(loginRequired || rule.loginRequired || false); + setAdminOnly(adminOnly || rule.adminOnly || false); } } } diff --git a/src/ui/services/auth.js b/src/ui/services/auth.ts similarity index 67% rename from src/ui/services/auth.js rename to src/ui/services/auth.ts index 498001032..b855a26f8 100644 --- a/src/ui/services/auth.js +++ b/src/ui/services/auth.ts @@ -1,21 +1,25 @@ import { getCookie } from '../utils'; +import { UserData } from '../../types/models'; +import { API_BASE } from '../apiBase'; +import { AxiosError } from 'axios'; -const baseUrl = import.meta.env.VITE_API_URI - ? `${import.meta.env.VITE_API_URI}` - : `${location.origin}`; +interface AxiosConfig { + withCredentials: boolean; + headers: { + 'X-CSRF-TOKEN': string; + Authorization?: string; + }; +} /** * Gets the current user's information - * @return {Promise} The user's information */ -export const getUserInfo = async () => { +export const getUserInfo = async (): Promise => { try { - const response = await fetch(`${baseUrl}/api/auth/me`, { + const response = await fetch(`${API_BASE}/api/auth/me`, { credentials: 'include', // Sends cookies }); - if (!response.ok) throw new Error(`Failed to fetch user info: ${response.statusText}`); - return await response.json(); } catch (error) { console.error('Error fetching user info:', error); @@ -25,9 +29,8 @@ export const getUserInfo = async () => { /** * Gets the Axios config for the UI - * @return {Object} The Axios config */ -export const getAxiosConfig = () => { +export const getAxiosConfig = (): AxiosConfig => { const jwtToken = localStorage.getItem('ui_jwt_token'); return { withCredentials: true, @@ -40,11 +43,9 @@ export const getAxiosConfig = () => { /** * 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, jwtAuthEnabled = false) => { - let errorMessage = `Failed to authorize user: ${error.response.data.trim()}. `; +export const processAuthError = (error: AxiosError, jwtAuthEnabled = false): string => { + let errorMessage = `Failed to authorize user: ${error.response?.data?.trim() ?? ''}. `; if (jwtAuthEnabled && !localStorage.getItem('ui_jwt_token')) { errorMessage += 'Set your JWT token in the settings page or disable JWT auth in your app configuration.'; diff --git a/src/ui/services/config.js b/src/ui/services/config.js deleted file mode 100644 index 286aab9a0..000000000 --- a/src/ui/services/config.js +++ /dev/null @@ -1,35 +0,0 @@ -import axios from 'axios'; - -const baseUrl = import.meta.env.VITE_API_URI - ? `${import.meta.env.VITE_API_URI}/api/v1` - : `${location.origin}/api/v1`; - -const getAttestationConfig = async (setData) => { - const url = new URL(`${baseUrl}/config/attestation`); - await axios(url.toString()).then((response) => { - setData(response.data.questions); - }); -}; - -const getURLShortener = async (setData) => { - const url = new URL(`${baseUrl}/config/urlShortener`); - await axios(url.toString()).then((response) => { - setData(response.data); - }); -}; - -const getEmailContact = async (setData) => { - const url = new URL(`${baseUrl}/config/contactEmail`); - await axios(url.toString()).then((response) => { - setData(response.data); - }); -}; - -const getUIRouteAuth = async (setData) => { - const url = new URL(`${baseUrl}/config/uiRouteAuth`); - await axios(url.toString()).then((response) => { - setData(response.data); - }); -}; - -export { getAttestationConfig, getURLShortener, getEmailContact, getUIRouteAuth }; diff --git a/src/ui/services/config.ts b/src/ui/services/config.ts new file mode 100644 index 000000000..3ececdc0f --- /dev/null +++ b/src/ui/services/config.ts @@ -0,0 +1,36 @@ +import axios from 'axios'; +import { API_BASE } from '../apiBase'; +import { FormQuestion } from '../views/PushDetails/components/AttestationForm'; +import { UIRouteAuth } from '../../config/generated/config'; + +const API_V1_BASE = `${API_BASE}/api/v1`; + +const setAttestationConfigData = async (setData: (data: FormQuestion[]) => void) => { + const url = new URL(`${API_V1_BASE}/config/attestation`); + await axios(url.toString()).then((response) => { + setData(response.data.questions); + }); +}; + +const setURLShortenerData = async (setData: (data: string) => void) => { + const url = new URL(`${API_V1_BASE}/config/urlShortener`); + await axios(url.toString()).then((response) => { + setData(response.data); + }); +}; + +const setEmailContactData = async (setData: (data: string) => void) => { + const url = new URL(`${API_V1_BASE}/config/contactEmail`); + await axios(url.toString()).then((response) => { + setData(response.data); + }); +}; + +const setUIRouteAuthData = async (setData: (data: UIRouteAuth) => void) => { + const url = new URL(`${API_V1_BASE}/config/uiRouteAuth`); + await axios(url.toString()).then((response) => { + setData(response.data); + }); +}; + +export { setAttestationConfigData, setURLShortenerData, setEmailContactData, setUIRouteAuthData }; diff --git a/src/ui/services/git-push.js b/src/ui/services/git-push.js deleted file mode 100644 index d72746efd..000000000 --- a/src/ui/services/git-push.js +++ /dev/null @@ -1,110 +0,0 @@ -import axios from 'axios'; -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 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); - } catch (error) { - if (error.response?.status === 401) setAuth(false); - else setIsError(true); - } finally { - setIsLoading(false); - } -}; - -const getPushes = async ( - setIsLoading, - setData, - setAuth, - setIsError, - setErrorMessage, - query = { - blocked: true, - canceled: false, - authorised: false, - rejected: false, - }, -) => { - const url = new URL(`${baseUrl}/push`); - url.search = new URLSearchParams(query); - - setIsLoading(true); - - 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) => { - const url = `${baseUrl}/push/${id}/authorise`; - let errorMsg = ''; - let isUserAllowedToApprove = true; - await axios - .post( - url, - { - params: { - attestation, - }, - }, - getAxiosConfig(), - ) - .catch((error) => { - if (error.response && error.response.status === 401) { - errorMsg = 'You are not authorised to approve...'; - isUserAllowedToApprove = false; - } - }); - await setMessage(errorMsg); - await setUserAllowedToApprove(isUserAllowedToApprove); -}; - -const rejectPush = async (id, setMessage, setUserAllowedToReject) => { - const url = `${baseUrl}/push/${id}/reject`; - let errorMsg = ''; - let isUserAllowedToReject = true; - await axios.post(url, {}, getAxiosConfig()).catch((error) => { - if (error.response && error.response.status === 401) { - errorMsg = 'You are not authorised to reject...'; - isUserAllowedToReject = false; - } - }); - await setMessage(errorMsg); - await setUserAllowedToReject(isUserAllowedToReject); -}; - -const cancelPush = async (id, setAuth, setIsError) => { - const url = `${baseUrl}/push/${id}/cancel`; - await axios.post(url, {}, getAxiosConfig()).catch((error) => { - if (error.response && error.response.status === 401) { - setAuth(false); - } else { - setIsError(true); - } - }); -}; - -export { getPush, getPushes, authorisePush, rejectPush, cancelPush }; diff --git a/src/ui/services/git-push.ts b/src/ui/services/git-push.ts new file mode 100644 index 000000000..2b0420680 --- /dev/null +++ b/src/ui/services/git-push.ts @@ -0,0 +1,128 @@ +import axios from 'axios'; +import { getAxiosConfig, processAuthError } from './auth'; +import { API_BASE } from '../apiBase'; + +const API_V1_BASE = `${API_BASE}/api/v1`; + +const getPush = async ( + id: string, + setIsLoading: (isLoading: boolean) => void, + setData: (data: any) => void, + setAuth: (auth: boolean) => void, + setIsError: (isError: boolean) => void, +): Promise => { + const url = `${API_V1_BASE}/push/${id}`; + setIsLoading(true); + + try { + const response = await axios(url, getAxiosConfig()); + const data = response.data; + data.diff = data.steps.find((x: any) => x.stepName === 'diff'); + setData(data); + } catch (error: any) { + if (error.response?.status === 401) setAuth(false); + else setIsError(true); + } finally { + setIsLoading(false); + } +}; + +const getPushes = async ( + setIsLoading: (isLoading: boolean) => void, + setData: (data: any) => void, + setAuth: (auth: boolean) => void, + setIsError: (isError: boolean) => void, + setErrorMessage: (errorMessage: string) => void, + query = { + blocked: true, + canceled: false, + authorised: false, + rejected: false, + }, +): Promise => { + const url = new URL(`${API_V1_BASE}/push`); + url.search = new URLSearchParams(query as any).toString(); + + setIsLoading(true); + + try { + const response = await axios(url.toString(), getAxiosConfig()); + setData(response.data); + } catch (error: any) { + 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: string, + setMessage: (message: string) => void, + setUserAllowedToApprove: (userAllowedToApprove: boolean) => void, + attestation: Array<{ label: string; checked: boolean }>, +): Promise => { + const url = `${API_V1_BASE}/push/${id}/authorise`; + let errorMsg = ''; + let isUserAllowedToApprove = true; + await axios + .post( + url, + { + params: { + attestation, + }, + }, + getAxiosConfig(), + ) + .catch((error: any) => { + if (error.response && error.response.status === 401) { + errorMsg = 'You are not authorised to approve...'; + isUserAllowedToApprove = false; + } + }); + setMessage(errorMsg); + setUserAllowedToApprove(isUserAllowedToApprove); +}; + +const rejectPush = async ( + id: string, + setMessage: (message: string) => void, + setUserAllowedToReject: (userAllowedToReject: boolean) => void, +): Promise => { + const url = `${API_V1_BASE}/push/${id}/reject`; + let errorMsg = ''; + let isUserAllowedToReject = true; + await axios.post(url, {}, getAxiosConfig()).catch((error: any) => { + if (error.response && error.response.status === 401) { + errorMsg = 'You are not authorised to reject...'; + isUserAllowedToReject = false; + } + }); + setMessage(errorMsg); + setUserAllowedToReject(isUserAllowedToReject); +}; + +const cancelPush = async ( + id: string, + setAuth: (auth: boolean) => void, + setIsError: (isError: boolean) => void, +): Promise => { + const url = `${API_BASE}/push/${id}/cancel`; + await axios.post(url, {}, getAxiosConfig()).catch((error: any) => { + if (error.response && error.response.status === 401) { + setAuth(false); + } else { + setIsError(true); + } + }); +}; + +export { getPush, getPushes, authorisePush, rejectPush, cancelPush }; diff --git a/src/ui/services/repo.js b/src/ui/services/repo.js deleted file mode 100644 index 4327f0520..000000000 --- a/src/ui/services/repo.js +++ /dev/null @@ -1,132 +0,0 @@ -import axios from 'axios'; -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 canAddUser = (repoId, user, action) => { - const url = new URL(`${baseUrl}/repo/${repoId}`); - return axios - .get(url.toString(), getAxiosConfig()) - .then((response) => { - const data = response.data; - if (action === 'authorise') { - return !data.users.canAuthorise.includes(user); - } else { - return !data.users.canPush.includes(user); - } - }) - .catch((error) => { - throw error; - }); -}; - -class DupUserValidationError extends Error { - constructor(message) { - super(message); - this.name = 'The user already has this role...'; - } -} - -const getRepos = async ( - setIsLoading, - setData, - setAuth, - setIsError, - setErrorMessage, - query = {}, -) => { - const url = new URL(`${baseUrl}/repo`); - url.search = new URLSearchParams(query); - setIsLoading(true); - await axios(url.toString(), getAxiosConfig()) - .then((response) => { - const sortedRepos = response.data.sort((a, b) => a.name.localeCompare(b.name)); - setData(sortedRepos); - }) - .catch((error) => { - setIsError(true); - if (error.response && error.response.status === 401) { - setAuth(false); - setErrorMessage(processAuthError(error)); - } else { - setErrorMessage(`Error fetching repos: ${error.response.data.message}`); - } - }) - .finally(() => { - setIsLoading(false); - }); -}; - -const getRepo = async (setIsLoading, setData, setAuth, setIsError, id) => { - const url = new URL(`${baseUrl}/repo/${id}`); - setIsLoading(true); - await axios(url.toString(), getAxiosConfig()) - .then((response) => { - const data = response.data; - setData(data); - }) - .catch((error) => { - if (error.response && error.response.status === 401) { - setAuth(false); - } else { - setIsError(true); - } - }) - .finally(() => { - setIsLoading(false); - }); -}; - -const addRepo = async (data) => { - const url = new URL(`${baseUrl}/repo`); - - try { - const response = await axios.post(url, data, getAxiosConfig()); - return { - success: true, - repo: response.data, - }; - } catch (error) { - return { - success: false, - message: error.response?.data?.message || error.message, - }; - } -}; - -const addUser = async (repoId, user, action) => { - const canAdd = await canAddUser(repoId, user, action); - if (canAdd) { - const url = new URL(`${baseUrl}/repo/${repoId}/user/${action}`); - const data = { username: user }; - await axios.patch(url, data, getAxiosConfig()).catch((error) => { - console.log(error.response.data.message); - throw error; - }); - } else { - console.log('Duplicate user can not be added'); - throw new DupUserValidationError(); - } -}; - -const deleteUser = async (user, repoId, action) => { - const url = new URL(`${baseUrl}/repo/${repoId}/user/${action}/${user}`); - - await axios.delete(url, getAxiosConfig()).catch((error) => { - console.log(error.response.data.message); - throw error; - }); -}; - -const deleteRepo = async (repoId) => { - const url = new URL(`${baseUrl}/repo/${repoId}/delete`); - - await axios.delete(url, getAxiosConfig()).catch((error) => { - console.log(error.response.data.message); - throw error; - }); -}; - -export { addUser, deleteUser, getRepos, getRepo, addRepo, deleteRepo }; diff --git a/src/ui/services/repo.ts b/src/ui/services/repo.ts new file mode 100644 index 000000000..5b168e882 --- /dev/null +++ b/src/ui/services/repo.ts @@ -0,0 +1,143 @@ +import axios from 'axios'; +import { getAxiosConfig, processAuthError } from './auth.js'; +import { API_BASE } from '../apiBase'; +import { RepositoryData, RepositoryDataWithId } from '../views/RepoList/Components/NewRepo'; + +const API_V1_BASE = `${API_BASE}/api/v1`; + +const canAddUser = (repoId: string, user: string, action: string) => { + const url = new URL(`${API_V1_BASE}/repo/${repoId}`); + return axios + .get(url.toString(), getAxiosConfig()) + .then((response) => { + const data = response.data; + if (action === 'authorise') { + return !data.users.canAuthorise.includes(user); + } else { + return !data.users.canPush.includes(user); + } + }) + .catch((error: any) => { + throw error; + }); +}; + +class DupUserValidationError extends Error { + constructor(message: string) { + super(message); + this.name = 'The user already has this role...'; + } +} + +const getRepos = async ( + setIsLoading: (isLoading: boolean) => void, + setData: (data: any) => void, + setAuth: (auth: boolean) => void, + setIsError: (isError: boolean) => void, + setErrorMessage: (errorMessage: string) => void, + query: Record = {}, +): Promise => { + const url = new URL(`${API_V1_BASE}/repo`); + url.search = new URLSearchParams(query as any).toString(); + setIsLoading(true); + await axios(url.toString(), getAxiosConfig()) + .then((response) => { + const sortedRepos = response.data.sort((a: RepositoryData, b: RepositoryData) => + a.name.localeCompare(b.name), + ); + setData(sortedRepos); + }) + .catch((error: any) => { + setIsError(true); + if (error.response && error.response.status === 401) { + setAuth(false); + setErrorMessage(processAuthError(error)); + } else { + setErrorMessage(`Error fetching repos: ${error.response.data.message}`); + } + }) + .finally(() => { + setIsLoading(false); + }); +}; + +const getRepo = async ( + setIsLoading: (isLoading: boolean) => void, + setData: (data: any) => void, + setAuth: (auth: boolean) => void, + setIsError: (isError: boolean) => void, + id: string, +): Promise => { + const url = new URL(`${API_V1_BASE}/repo/${id}`); + setIsLoading(true); + await axios(url.toString(), getAxiosConfig()) + .then((response) => { + const data = response.data; + setData(data); + }) + .catch((error: any) => { + if (error.response && error.response.status === 401) { + setAuth(false); + } else { + setIsError(true); + } + }) + .finally(() => { + setIsLoading(false); + }); +}; + +const addRepo = async ( + data: RepositoryData, +): Promise<{ success: boolean; message?: string; repo: RepositoryDataWithId | null }> => { + const url = new URL(`${API_V1_BASE}/repo`); + + try { + const response = await axios.post(url.toString(), data, getAxiosConfig()); + return { + success: true, + repo: response.data, + }; + } catch (error: any) { + return { + success: false, + message: error.response?.data?.message || error.message, + repo: null, + }; + } +}; + +const addUser = async (repoId: string, user: string, action: string): Promise => { + const canAdd = await canAddUser(repoId, user, action); + if (canAdd) { + const url = new URL(`${API_V1_BASE}/repo/${repoId}/user/${action}`); + const data = { username: user }; + await axios.patch(url.toString(), data, getAxiosConfig()).catch((error: any) => { + console.log(error.response.data.message); + throw error; + }); + } else { + console.log('Duplicate user can not be added'); + throw new DupUserValidationError('Duplicate user can not be added'); + } +}; + +const deleteUser = async (user: string, repoId: string, action: string): Promise => { + const url = new URL(`${API_V1_BASE}/repo/${repoId}/user/${action}/${user}`); + + await axios.delete(url.toString(), getAxiosConfig()).catch((error: any) => { + console.log(error.response.data.message); + throw error; + }); +}; + +const deleteRepo = async (repoId: string): Promise => { + const url = new URL(`${API_V1_BASE}/repo/${repoId}/delete`); + + await axios.delete(url.toString(), getAxiosConfig()).catch((error: any) => { + console.log(error.response.data.message); + throw error; + }); +}; + +export { addUser, deleteUser, getRepos, getRepo, addRepo, deleteRepo }; diff --git a/src/ui/views/PushDetails/components/Attestation.tsx b/src/ui/views/PushDetails/components/Attestation.tsx index a69e04cb3..dc68bf5d2 100644 --- a/src/ui/views/PushDetails/components/Attestation.tsx +++ b/src/ui/views/PushDetails/components/Attestation.tsx @@ -5,7 +5,11 @@ import DialogActions from '@material-ui/core/DialogActions'; import { CheckCircle, ErrorOutline } from '@material-ui/icons'; import Button from '../../../components/CustomButtons/Button'; import AttestationForm, { FormQuestion } from './AttestationForm'; -import { getAttestationConfig, getURLShortener, getEmailContact } from '../../../services/config'; +import { + setAttestationConfigData, + setURLShortenerData, + setEmailContactData, +} from '../../../services/config'; interface AttestationProps { approveFn: (data: { label: string; checked: boolean }[]) => void; @@ -19,15 +23,15 @@ const Attestation: React.FC = ({ approveFn }) => { useEffect(() => { if (!open) { - getAttestationConfig(setFormData); + setAttestationConfigData(setFormData); } if (open) { if (!urlShortener) { - getURLShortener(setURLShortener); + setURLShortenerData(setURLShortener); } if (!contactEmail) { - getEmailContact(setContactEmail); + setEmailContactData(setContactEmail); } } }, [open, urlShortener, contactEmail]); diff --git a/src/ui/views/PushDetails/components/AttestationView.tsx b/src/ui/views/PushDetails/components/AttestationView.tsx index ec565ddb6..60f348a1c 100644 --- a/src/ui/views/PushDetails/components/AttestationView.tsx +++ b/src/ui/views/PushDetails/components/AttestationView.tsx @@ -10,7 +10,7 @@ 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'; +import { setURLShortenerData } from '../../../services/config'; import { AttestationViewProps } from '../attestation.types'; const StyledFormControlLabel = withStyles({ @@ -39,7 +39,7 @@ const AttestationView: React.FC = ({ attestation, setAttes useEffect(() => { if (attestation && !urlShortener) { - getURLShortener(setURLShortener); + setURLShortenerData(setURLShortener); } }, [attestation, urlShortener]); diff --git a/src/ui/views/RepoList/Components/NewRepo.tsx b/src/ui/views/RepoList/Components/NewRepo.tsx index af36db2f1..6758a1bb1 100644 --- a/src/ui/views/RepoList/Components/NewRepo.tsx +++ b/src/ui/views/RepoList/Components/NewRepo.tsx @@ -96,7 +96,7 @@ const AddRepositoryDialog: React.FC = ({ open, onClose } const result = await addRepo(data); - if (result.success) { + if (result.success && result.repo) { handleSuccess(result.repo); handleClose(); } else {