-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[BUBO-30] Get list of files from GitHub (#33)
* get list of files from github * add error handlers and zerialization * remove GithubFileSelect component in main page * remove unused import * throw ServerError in requireGithubAuth * use Button component * move getting account to requireGitHubAuth * type safety with enable d option * separate components for select files and select repo * disable query if provider is not github * update GitHubProvider scope * fix checkbox state and change github sign in button label * remove extra space * filter file tree * useSession in useQuery * export type GithubRepository
- Loading branch information
Showing
11 changed files
with
894 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
'use client' | ||
|
||
import {signIn, useSession} from 'next-auth/react' | ||
import {CheckedState} from '@radix-ui/react-checkbox' | ||
|
||
import {Button} from '@/components/ui/Button' | ||
import {useGetRepoFiles} from '@/lib/queries/github/getGitHub' | ||
import { | ||
GetRepoFilesParams, | ||
GetRepoFilesResponse, | ||
} from '@/server/actions/github/getGithub' | ||
import {Checkbox} from '@/components/ui/Checkbox' | ||
import {ScrollArea} from '@/components/ui/ScrollArea' | ||
|
||
type GithubFileSelectProps = { | ||
selectedRepo: GetRepoFilesParams | null | ||
selectedFiles: string[] | ||
onSelectFiles: (files: string[]) => void | ||
} | ||
|
||
const GithubFileSelect = ({ | ||
selectedRepo, | ||
selectedFiles, | ||
onSelectFiles, | ||
}: GithubFileSelectProps) => { | ||
const session = useSession() | ||
|
||
const {data: repoFilesData, isLoading: repoFilesIsLoading} = useGetRepoFiles( | ||
selectedRepo ?? undefined, | ||
) | ||
|
||
if (session.data?.user.provider !== 'github') { | ||
return ( | ||
<Button | ||
onClick={() => signIn('github')} | ||
className="bg-gray-800 text-white"> | ||
Sign in with Github to select files | ||
</Button> | ||
) | ||
} | ||
|
||
return ( | ||
<div className="min-w-96 rounded-md border-2 border-gray-900 p-2"> | ||
{selectedRepo === null ? ( | ||
<span>No repo selected</span> | ||
) : ( | ||
<ScrollArea className="h-96 w-full" thumbClassname="bg-gray-400"> | ||
<div className="flex flex-col items-start gap-2"> | ||
<div className="flex flex-col"> | ||
<span className="pb-2 font-bold">Select files to include</span> | ||
<FileTree | ||
fileTree={repoFilesData} | ||
isLoading={repoFilesIsLoading} | ||
selectedFilePaths={selectedFiles} | ||
onSelectFilePaths={onSelectFiles} | ||
/> | ||
</div> | ||
</div> | ||
</ScrollArea> | ||
)} | ||
</div> | ||
) | ||
} | ||
|
||
type FileTreeProps = { | ||
fileTree: GetRepoFilesResponse | undefined | null | ||
isLoading: boolean | ||
selectedFilePaths: string[] | ||
onSelectFilePaths: (newFiles: string[]) => void | ||
} | ||
|
||
const FileTree = ({ | ||
fileTree, | ||
isLoading, | ||
selectedFilePaths, | ||
onSelectFilePaths, | ||
}: FileTreeProps) => { | ||
if (isLoading) return <span>Loading...</span> | ||
|
||
if (!fileTree || fileTree.length === 0) return <span>No files found</span> | ||
|
||
const handleCheckChange = ( | ||
checked: CheckedState, | ||
path: string | undefined, | ||
) => { | ||
if (!path) { | ||
return | ||
} | ||
|
||
if (checked) { | ||
onSelectFilePaths([...selectedFilePaths, path]) | ||
} else { | ||
onSelectFilePaths(selectedFilePaths.filter((file) => file !== path)) | ||
} | ||
} | ||
|
||
const filteredFileTree = fileTree.filter((file) => file.path != undefined) | ||
|
||
return filteredFileTree.map((file) => ( | ||
<div key={file.path}> | ||
<Checkbox | ||
id={file.path} | ||
checked={selectedFilePaths.includes(file.path ?? '')} | ||
onCheckedChange={(checked) => handleCheckChange(checked, file.path)} | ||
/> | ||
<label htmlFor={file.path} className="pl-2"> | ||
{file.path} | ||
</label> | ||
</div> | ||
)) | ||
} | ||
|
||
export default GithubFileSelect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
'use client' | ||
|
||
import {ArrowLeft} from 'lucide-react' | ||
import {signIn, useSession} from 'next-auth/react' | ||
|
||
import {Button} from '@/components/ui/Button' | ||
import {useGetPublicRepos} from '@/lib/queries/github/getGitHub' | ||
import {GithubRepository} from '@/server/actions/github/getGithub' | ||
import {ScrollArea} from '@/components/ui/ScrollArea' | ||
|
||
type GithubRepoSelect = { | ||
selectedRepo: GithubRepository | null | ||
onSelectRepo: (repo: GithubRepository | null) => void | ||
} | ||
|
||
const GithubRepoSelect = ({selectedRepo, onSelectRepo}: GithubRepoSelect) => { | ||
const session = useSession() | ||
|
||
const {data: publicReposData, isLoading: publicReposIsLoading} = | ||
useGetPublicRepos() | ||
|
||
if (session.data?.user.provider !== 'github') { | ||
return ( | ||
<Button | ||
onClick={() => signIn('github')} | ||
className="bg-gray-800 text-white"> | ||
Sign in with Github to select a repo | ||
</Button> | ||
) | ||
} | ||
|
||
return ( | ||
<div className="min-w-96 rounded-md border-2 border-gray-900 p-2"> | ||
{selectedRepo === null ? ( | ||
<div> | ||
<span className="pb-2 font-bold">Select a repo</span> | ||
<ScrollArea className="h-96 w-full" thumbClassname="bg-gray-400"> | ||
<div className="flex flex-col items-start gap-2"> | ||
<RepoList | ||
repos={publicReposData} | ||
isLoading={publicReposIsLoading} | ||
selectRepo={(params: GithubRepository) => onSelectRepo(params)} | ||
/> | ||
</div> | ||
</ScrollArea> | ||
</div> | ||
) : ( | ||
<div> | ||
<Button variant="outline" onClick={() => onSelectRepo(null)}> | ||
<ArrowLeft className="h-4 w-4" /> | ||
</Button> | ||
<span className="pl-2 font-bold">{`${selectedRepo.owner}/${selectedRepo.name}`}</span> | ||
</div> | ||
)} | ||
</div> | ||
) | ||
} | ||
|
||
type RepoListProps = { | ||
repos: GithubRepository[] | undefined | ||
isLoading: boolean | ||
selectRepo: (params: GithubRepository) => void | ||
} | ||
|
||
const RepoList = ({repos, isLoading, selectRepo}: RepoListProps) => { | ||
if (!repos && isLoading) return <span>Loading...</span> | ||
|
||
if (!repos || repos.length === 0) return <span>No public repos found</span> | ||
|
||
return repos.map((repo) => ( | ||
<Button key={repo.id} onClick={() => selectRepo(repo)}> | ||
{repo.fullName} | ||
</Button> | ||
)) | ||
} | ||
|
||
export default GithubRepoSelect |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
'use client' | ||
|
||
import * as React from 'react' | ||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox' | ||
import {CheckIcon} from '@radix-ui/react-icons' | ||
|
||
import {cn} from '@/lib/utils/client/tailwind' | ||
|
||
const Checkbox = React.forwardRef< | ||
React.ElementRef<typeof CheckboxPrimitive.Root>, | ||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root> | ||
>(({className, ...props}, ref) => ( | ||
<CheckboxPrimitive.Root | ||
ref={ref} | ||
className={cn( | ||
'border-primary focus-visible:ring-ring data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground peer h-4 w-4 shrink-0 rounded-sm border shadow focus-visible:outline-none focus-visible:ring-1 disabled:cursor-not-allowed disabled:opacity-50', | ||
className, | ||
)} | ||
{...props}> | ||
<CheckboxPrimitive.Indicator | ||
className={cn('flex items-center justify-center text-current')}> | ||
<CheckIcon className="h-4 w-4" /> | ||
</CheckboxPrimitive.Indicator> | ||
</CheckboxPrimitive.Root> | ||
)) | ||
Checkbox.displayName = CheckboxPrimitive.Root.displayName | ||
|
||
export {Checkbox} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
'use client' | ||
|
||
import * as React from 'react' | ||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area' | ||
|
||
import {cn} from '@/lib/utils/client/tailwind' | ||
|
||
type ScrollAreaProps = React.ComponentPropsWithoutRef< | ||
typeof ScrollAreaPrimitive.Root | ||
> & { | ||
thumbClassname?: string | ||
} | ||
|
||
const ScrollArea = React.forwardRef< | ||
React.ElementRef<typeof ScrollAreaPrimitive.Root>, | ||
ScrollAreaProps | ||
>(({className, children, thumbClassname, ...props}, ref) => ( | ||
<ScrollAreaPrimitive.Root | ||
ref={ref} | ||
className={cn('relative overflow-hidden', className)} | ||
{...props}> | ||
<ScrollAreaPrimitive.Viewport className="h-full w-full rounded-[inherit]"> | ||
{children} | ||
</ScrollAreaPrimitive.Viewport> | ||
<ScrollBar thumbClassname={thumbClassname} /> | ||
<ScrollAreaPrimitive.Corner /> | ||
</ScrollAreaPrimitive.Root> | ||
)) | ||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName | ||
|
||
type ScrollBarProps = React.ComponentPropsWithoutRef< | ||
typeof ScrollAreaPrimitive.ScrollAreaScrollbar | ||
> & { | ||
thumbClassname?: string | ||
} | ||
|
||
const ScrollBar = React.forwardRef< | ||
React.ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>, | ||
ScrollBarProps | ||
>(({className, orientation = 'vertical', thumbClassname, ...props}, ref) => ( | ||
<ScrollAreaPrimitive.ScrollAreaScrollbar | ||
ref={ref} | ||
orientation={orientation} | ||
className={cn( | ||
'flex touch-none select-none transition-colors', | ||
orientation === 'vertical' && | ||
'h-full w-2.5 border-l border-l-transparent p-[1px]', | ||
orientation === 'horizontal' && | ||
'h-2.5 flex-col border-t border-t-transparent p-[1px]', | ||
className, | ||
)} | ||
{...props}> | ||
<ScrollAreaPrimitive.ScrollAreaThumb | ||
className={cn( | ||
'relative flex-1 rounded-full bg-origin-border', | ||
thumbClassname, | ||
)} | ||
/> | ||
</ScrollAreaPrimitive.ScrollAreaScrollbar> | ||
)) | ||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName | ||
|
||
export {ScrollArea, ScrollBar} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import {useQuery} from '@tanstack/react-query' | ||
import {useSession} from 'next-auth/react' | ||
|
||
import {queryKeys} from '../keys' | ||
|
||
import { | ||
GetRepoFilesParams, | ||
getPublicRepos, | ||
getRepoFiles, | ||
} from '@/server/actions/github/getGithub' | ||
import {useUserId} from '@/lib/hooks/useUserId' | ||
import {withApiErrorHandler} from '@/lib/utils/common/error' | ||
|
||
const getPublicReposQueryOptions = (userId: string | undefined) => ({ | ||
queryKey: queryKeys.gitHub.publicRepos(userId).queryKey, | ||
queryFn: withApiErrorHandler(() => getPublicRepos()), | ||
}) | ||
|
||
export const useGetPublicRepos = () => { | ||
const userId = useUserId() | ||
const session = useSession() | ||
|
||
return useQuery({ | ||
...getPublicReposQueryOptions(userId), | ||
enabled: !!userId && session.data?.user.provider === 'github', | ||
}) | ||
} | ||
|
||
const getRepoFilesQueryOptions = (params: GetRepoFilesParams | undefined) => ({ | ||
queryKey: queryKeys.gitHub.repoFiles(params).queryKey, | ||
queryFn: withApiErrorHandler(() => { | ||
if (!params) { | ||
throw new Error('GetRepoFilesParams is required.') | ||
} | ||
|
||
return getRepoFiles(params) | ||
}), | ||
}) | ||
|
||
export const useGetRepoFiles = (params: GetRepoFilesParams | undefined) => { | ||
const session = useSession() | ||
|
||
return useQuery({ | ||
...getRepoFilesQueryOptions(params), | ||
enabled: !!params && session.data?.user.provider === 'github', | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.