forked from cerc-io/snowballtools-base
Check for GitHub authentication in template create page and handle GitHub unauthorized errors (#173)
* Fix GitHub auth check on reloading template create page * Fix error handling for unauthenticated GitHub token
This commit is contained in:
parent
47231a6eab
commit
cc8f9527da
@ -74,12 +74,12 @@ export const MockConnectGitCard = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-2 relative z-0">
|
<div className="flex flex-col gap-2 relative z-0">
|
||||||
{IMPORT_CONTENT.map((repo, index) => (
|
{IMPORT_CONTENT.map((repo, index) => (
|
||||||
<>
|
<React.Fragment key={index}>
|
||||||
<MockProjectCard key={index} {...repo} />
|
<MockProjectCard {...repo} />
|
||||||
{index !== IMPORT_CONTENT.length - 1 && (
|
{index !== IMPORT_CONTENT.length - 1 && (
|
||||||
<div className="border-b border-base-border" />
|
<div className="border-b border-base-border" />
|
||||||
)}
|
)}
|
||||||
</>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
import { Octokit } from 'octokit';
|
|
||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
import { useDebounce } from 'usehooks-ts';
|
import { useDebounce } from 'usehooks-ts';
|
||||||
|
|
||||||
@ -14,20 +13,18 @@ import {
|
|||||||
import { Select, SelectOption } from 'components/shared/Select';
|
import { Select, SelectOption } from 'components/shared/Select';
|
||||||
import { Input } from 'components/shared/Input';
|
import { Input } from 'components/shared/Input';
|
||||||
import { Button } from 'components/shared/Button';
|
import { Button } from 'components/shared/Button';
|
||||||
|
import { useOctokit } from 'context/OctokitContext';
|
||||||
|
|
||||||
const DEFAULT_SEARCHED_REPO = '';
|
const DEFAULT_SEARCHED_REPO = '';
|
||||||
const REPOS_PER_PAGE = 5;
|
const REPOS_PER_PAGE = 5;
|
||||||
|
|
||||||
interface RepositoryListProps {
|
export const RepositoryList = () => {
|
||||||
octokit: Octokit;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const RepositoryList = ({ octokit }: RepositoryListProps) => {
|
|
||||||
const [searchedRepo, setSearchedRepo] = useState(DEFAULT_SEARCHED_REPO);
|
const [searchedRepo, setSearchedRepo] = useState(DEFAULT_SEARCHED_REPO);
|
||||||
const [selectedAccount, setSelectedAccount] = useState<SelectOption>();
|
const [selectedAccount, setSelectedAccount] = useState<SelectOption>();
|
||||||
const [orgs, setOrgs] = useState<GitOrgDetails[]>([]);
|
const [orgs, setOrgs] = useState<GitOrgDetails[]>([]);
|
||||||
// TODO: Add new type for Git user when required
|
// TODO: Add new type for Git user when required
|
||||||
const [gitUser, setGitUser] = useState<GitOrgDetails>();
|
const [gitUser, setGitUser] = useState<GitOrgDetails>();
|
||||||
|
const { octokit, isAuth } = useOctokit();
|
||||||
|
|
||||||
const [repositoryDetails, setRepositoryDetails] = useState<
|
const [repositoryDetails, setRepositoryDetails] = useState<
|
||||||
GitRepositoryDetails[]
|
GitRepositoryDetails[]
|
||||||
@ -35,15 +32,23 @@ export const RepositoryList = ({ octokit }: RepositoryListProps) => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUserAndOrgs = async () => {
|
const fetchUserAndOrgs = async () => {
|
||||||
|
try {
|
||||||
const user = await octokit.rest.users.getAuthenticated();
|
const user = await octokit.rest.users.getAuthenticated();
|
||||||
const orgs = await octokit.rest.orgs.listForAuthenticatedUser();
|
const orgs = await octokit.rest.orgs.listForAuthenticatedUser();
|
||||||
setOrgs(orgs.data);
|
setOrgs(orgs.data);
|
||||||
setGitUser(user.data);
|
setGitUser(user.data);
|
||||||
setSelectedAccount({ label: user.data.login, value: user.data.login });
|
setSelectedAccount({ label: user.data.login, value: user.data.login });
|
||||||
|
} catch (error) {
|
||||||
|
// Error handled by octokit error hook interceptor in Octokit context
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isAuth) {
|
||||||
fetchUserAndOrgs();
|
fetchUserAndOrgs();
|
||||||
}, [octokit]);
|
}
|
||||||
|
}, [octokit, isAuth]);
|
||||||
|
|
||||||
const debouncedSearchedRepo = useDebounce<string>(searchedRepo, 500);
|
const debouncedSearchedRepo = useDebounce<string>(searchedRepo, 500);
|
||||||
|
|
||||||
|
@ -8,8 +8,11 @@ import React, {
|
|||||||
useEffect,
|
useEffect,
|
||||||
} from 'react';
|
} from 'react';
|
||||||
import { Octokit, RequestError } from 'octokit';
|
import { Octokit, RequestError } from 'octokit';
|
||||||
|
import { useNavigate, useParams } from 'react-router-dom';
|
||||||
|
import { useDebounceCallback } from 'usehooks-ts';
|
||||||
|
|
||||||
import { useGQLClient } from './GQLClientContext';
|
import { useGQLClient } from './GQLClientContext';
|
||||||
|
import { useToast } from 'components/shared/Toast';
|
||||||
|
|
||||||
const UNAUTHORIZED_ERROR_CODE = 401;
|
const UNAUTHORIZED_ERROR_CODE = 401;
|
||||||
|
|
||||||
@ -26,17 +29,17 @@ const OctokitContext = createContext<ContextValue>({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const OctokitProvider = ({ children }: { children: ReactNode }) => {
|
export const OctokitProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const [authToken, setAuthToken] = useState<string>('');
|
const [authToken, setAuthToken] = useState<string | null>(null);
|
||||||
const [isAuth, setIsAuth] = useState(false);
|
const [isAuth, setIsAuth] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const { orgSlug } = useParams();
|
||||||
|
const { toast, dismiss } = useToast();
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
|
||||||
const fetchUser = useCallback(async () => {
|
const fetchUser = useCallback(async () => {
|
||||||
const { user } = await client.getUser();
|
const { user } = await client.getUser();
|
||||||
|
|
||||||
if (user.gitHubToken) {
|
|
||||||
setAuthToken(user.gitHubToken);
|
setAuthToken(user.gitHubToken);
|
||||||
}
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const updateAuth = useCallback(() => {
|
const updateAuth = useCallback(() => {
|
||||||
@ -57,6 +60,23 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
fetchUser();
|
fetchUser();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const debouncedUnauthorizedGithubHandler = useDebounceCallback(
|
||||||
|
useCallback(
|
||||||
|
(error: RequestError) => {
|
||||||
|
toast({
|
||||||
|
id: 'unauthorized-github-token',
|
||||||
|
title: `GitHub authentication error: ${error.message}`,
|
||||||
|
variant: 'error',
|
||||||
|
onDismiss: dismiss,
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate(`/${orgSlug}/projects/create`);
|
||||||
|
},
|
||||||
|
[toast, navigate, orgSlug],
|
||||||
|
),
|
||||||
|
500,
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// TODO: Handle React component error
|
// TODO: Handle React component error
|
||||||
const interceptor = async (error: RequestError | Error) => {
|
const interceptor = async (error: RequestError | Error) => {
|
||||||
@ -66,6 +86,8 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
) {
|
) {
|
||||||
await client.unauthenticateGithub();
|
await client.unauthenticateGithub();
|
||||||
await fetchUser();
|
await fetchUser();
|
||||||
|
|
||||||
|
debouncedUnauthorizedGithubHandler(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
throw error;
|
throw error;
|
||||||
@ -77,7 +99,7 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => {
|
|||||||
// Remove the interceptor when the component unmounts
|
// Remove the interceptor when the component unmounts
|
||||||
octokit.hook.remove('request', interceptor);
|
octokit.hook.remove('request', interceptor);
|
||||||
};
|
};
|
||||||
}, [octokit, client]);
|
}, [octokit, client, debouncedUnauthorizedGithubHandler]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<OctokitContext.Provider value={{ octokit, updateAuth, isAuth }}>
|
<OctokitContext.Provider value={{ octokit, updateAuth, isAuth }}>
|
||||||
|
@ -31,7 +31,7 @@ const NewProject = () => {
|
|||||||
<Heading as="h3" className="font-medium text-lg mt-10 pl-1 mb-3">
|
<Heading as="h3" className="font-medium text-lg mt-10 pl-1 mb-3">
|
||||||
Import a repository
|
Import a repository
|
||||||
</Heading>
|
</Heading>
|
||||||
<RepositoryList octokit={octokit} />
|
<RepositoryList />
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<ConnectAccount onAuth={updateAuth} />
|
<ConnectAccount onAuth={updateAuth} />
|
||||||
|
@ -26,7 +26,7 @@ type SubmitRepoValues = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const CreateRepo = () => {
|
const CreateRepo = () => {
|
||||||
const { octokit } = useOctokit();
|
const { octokit, isAuth } = useOctokit();
|
||||||
const { template } = useOutletContext<{ template: Template }>();
|
const { template } = useOutletContext<{ template: Template }>();
|
||||||
const client = useGQLClient();
|
const client = useGQLClient();
|
||||||
|
|
||||||
@ -104,6 +104,7 @@ const CreateRepo = () => {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const fetchUserAndOrgs = async () => {
|
const fetchUserAndOrgs = async () => {
|
||||||
|
try {
|
||||||
const user = await octokit?.rest.users.getAuthenticated();
|
const user = await octokit?.rest.users.getAuthenticated();
|
||||||
const orgs = await octokit?.rest.orgs.listForAuthenticatedUser();
|
const orgs = await octokit?.rest.orgs.listForAuthenticatedUser();
|
||||||
|
|
||||||
@ -112,10 +113,17 @@ const CreateRepo = () => {
|
|||||||
|
|
||||||
setGitAccounts([user.data.login, ...orgsLoginArr]);
|
setGitAccounts([user.data.login, ...orgsLoginArr]);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// Error handled by octokit error hook interceptor in Octokit context
|
||||||
|
console.error(error);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (isAuth) {
|
||||||
fetchUserAndOrgs();
|
fetchUserAndOrgs();
|
||||||
}, [octokit]);
|
}
|
||||||
|
}, [octokit, isAuth]);
|
||||||
|
|
||||||
const { handleSubmit, control, reset } = useForm<SubmitRepoValues>({
|
const { handleSubmit, control, reset } = useForm<SubmitRepoValues>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
|
Loading…
Reference in New Issue
Block a user