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:
Nabarun Gogoi 2024-04-11 17:19:15 +05:30 committed by GitHub
parent 47231a6eab
commit cc8f9527da
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 66 additions and 31 deletions

View File

@ -74,12 +74,12 @@ export const MockConnectGitCard = () => {
return (
<div className="flex flex-col gap-2 relative z-0">
{IMPORT_CONTENT.map((repo, index) => (
<>
<MockProjectCard key={index} {...repo} />
<React.Fragment key={index}>
<MockProjectCard {...repo} />
{index !== IMPORT_CONTENT.length - 1 && (
<div className="border-b border-base-border" />
)}
</>
</React.Fragment>
))}
</div>
);

View File

@ -1,5 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Octokit } from 'octokit';
import assert from 'assert';
import { useDebounce } from 'usehooks-ts';
@ -14,20 +13,18 @@ import {
import { Select, SelectOption } from 'components/shared/Select';
import { Input } from 'components/shared/Input';
import { Button } from 'components/shared/Button';
import { useOctokit } from 'context/OctokitContext';
const DEFAULT_SEARCHED_REPO = '';
const REPOS_PER_PAGE = 5;
interface RepositoryListProps {
octokit: Octokit;
}
export const RepositoryList = ({ octokit }: RepositoryListProps) => {
export const RepositoryList = () => {
const [searchedRepo, setSearchedRepo] = useState(DEFAULT_SEARCHED_REPO);
const [selectedAccount, setSelectedAccount] = useState<SelectOption>();
const [orgs, setOrgs] = useState<GitOrgDetails[]>([]);
// TODO: Add new type for Git user when required
const [gitUser, setGitUser] = useState<GitOrgDetails>();
const { octokit, isAuth } = useOctokit();
const [repositoryDetails, setRepositoryDetails] = useState<
GitRepositoryDetails[]
@ -35,15 +32,23 @@ export const RepositoryList = ({ octokit }: RepositoryListProps) => {
useEffect(() => {
const fetchUserAndOrgs = async () => {
try {
const user = await octokit.rest.users.getAuthenticated();
const orgs = await octokit.rest.orgs.listForAuthenticatedUser();
setOrgs(orgs.data);
setGitUser(user.data);
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();
}, [octokit]);
}
}, [octokit, isAuth]);
const debouncedSearchedRepo = useDebounce<string>(searchedRepo, 500);

View File

@ -8,8 +8,11 @@ import React, {
useEffect,
} from 'react';
import { Octokit, RequestError } from 'octokit';
import { useNavigate, useParams } from 'react-router-dom';
import { useDebounceCallback } from 'usehooks-ts';
import { useGQLClient } from './GQLClientContext';
import { useToast } from 'components/shared/Toast';
const UNAUTHORIZED_ERROR_CODE = 401;
@ -26,17 +29,17 @@ const OctokitContext = createContext<ContextValue>({
});
export const OctokitProvider = ({ children }: { children: ReactNode }) => {
const [authToken, setAuthToken] = useState<string>('');
const [authToken, setAuthToken] = useState<string | null>(null);
const [isAuth, setIsAuth] = useState(false);
const navigate = useNavigate();
const { orgSlug } = useParams();
const { toast, dismiss } = useToast();
const client = useGQLClient();
const fetchUser = useCallback(async () => {
const { user } = await client.getUser();
if (user.gitHubToken) {
setAuthToken(user.gitHubToken);
}
}, []);
const updateAuth = useCallback(() => {
@ -57,6 +60,23 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => {
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(() => {
// TODO: Handle React component error
const interceptor = async (error: RequestError | Error) => {
@ -66,6 +86,8 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => {
) {
await client.unauthenticateGithub();
await fetchUser();
debouncedUnauthorizedGithubHandler(error);
}
throw error;
@ -77,7 +99,7 @@ export const OctokitProvider = ({ children }: { children: ReactNode }) => {
// Remove the interceptor when the component unmounts
octokit.hook.remove('request', interceptor);
};
}, [octokit, client]);
}, [octokit, client, debouncedUnauthorizedGithubHandler]);
return (
<OctokitContext.Provider value={{ octokit, updateAuth, isAuth }}>

View File

@ -31,7 +31,7 @@ const NewProject = () => {
<Heading as="h3" className="font-medium text-lg mt-10 pl-1 mb-3">
Import a repository
</Heading>
<RepositoryList octokit={octokit} />
<RepositoryList />
</>
) : (
<ConnectAccount onAuth={updateAuth} />

View File

@ -26,7 +26,7 @@ type SubmitRepoValues = {
};
const CreateRepo = () => {
const { octokit } = useOctokit();
const { octokit, isAuth } = useOctokit();
const { template } = useOutletContext<{ template: Template }>();
const client = useGQLClient();
@ -104,6 +104,7 @@ const CreateRepo = () => {
useEffect(() => {
const fetchUserAndOrgs = async () => {
try {
const user = await octokit?.rest.users.getAuthenticated();
const orgs = await octokit?.rest.orgs.listForAuthenticatedUser();
@ -112,10 +113,17 @@ const CreateRepo = () => {
setGitAccounts([user.data.login, ...orgsLoginArr]);
}
} catch (error) {
// Error handled by octokit error hook interceptor in Octokit context
console.error(error);
return;
}
};
if (isAuth) {
fetchUserAndOrgs();
}, [octokit]);
}
}, [octokit, isAuth]);
const { handleSubmit, control, reset } = useForm<SubmitRepoValues>({
defaultValues: {