Files
supabase/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubRepositoryField.tsx
T
Gildas Garcia 243e079a2c chore: remove _Shadcn_ suffix from Command components (#46153)
## Problem

The `_Shadcn_` suffix isn't needed anymore on `Command` components

## Solution

- Remove the `_Shadcn_` suffix
- Simplify UI package exports
- Apply prettier

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Simplified command component imports and exports across the UI library
by removing internal naming aliases and adopting direct component
references. Updated the public UI package barrel export to use wildcard
re-exports for cleaner API surface.

<!-- review_stack_entry_start -->

[![Review Change
Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/supabase/supabase/pull/46153?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)

<!-- review_stack_entry_end -->

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-05-20 15:45:32 +02:00

256 lines
9.5 KiB
TypeScript

import { ChevronDown, PlusIcon, RefreshCw } from 'lucide-react'
import { useMemo, useState, type ComponentProps, type ReactNode } from 'react'
import type { FieldValues, Path, UseFormReturn } from 'react-hook-form'
import {
Button,
Command,
CommandEmpty,
CommandGroup,
CommandInput,
CommandItem,
CommandList,
CommandSeparator,
FormControl,
FormField,
Popover,
PopoverContent,
PopoverTrigger,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { useGitHubAuthorizationQuery } from '@/data/integrations/github-authorization-query'
import { useGitHubRepositoriesQuery } from '@/data/integrations/github-repositories-query'
import { openInstallGitHubIntegrationWindow } from '@/lib/github'
import { EMPTY_ARR } from '@/lib/void'
export type GitHubRepository = {
id: string
name: string
installation_id: number
default_branch: string
}
export const GITHUB_ICON = (
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 98 96" className="w-5 shrink-0">
<title>GitHub icon</title>
<path
fill="currentColor"
fillRule="evenodd"
d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"
clipRule="evenodd"
/>
</svg>
)
export const useGitHubRepositoryOptions = () => {
const {
data: gitHubAuthorization,
isPending: isLoadingGitHubAuthorization,
refetch: refetchGitHubAuthorization,
} = useGitHubAuthorizationQuery()
const {
data: githubReposData,
isPending: isLoadingGitHubRepos,
refetch: refetchGitHubRepositories,
} = useGitHubRepositoriesQuery({
enabled: Boolean(gitHubAuthorization),
})
const githubRepos = useMemo<GitHubRepository[]>(
() =>
githubReposData?.repositories?.map((repo) => ({
id: repo.id.toString(),
name: repo.name,
installation_id: repo.installation_id,
default_branch: repo.default_branch || 'main',
})) ?? EMPTY_ARR,
[githubReposData]
)
const refetchGitHubAuthorizationAndRepositories = () => {
setTimeout(() => {
refetchGitHubAuthorization()
refetchGitHubRepositories()
}, 2000)
}
return {
gitHubAuthorization,
githubRepos,
hasPartialResponseDueToSSO: githubReposData?.partial_response_due_to_sso ?? false,
isLoading: isLoadingGitHubAuthorization || isLoadingGitHubRepos,
refetch: refetchGitHubAuthorizationAndRepositories,
}
}
interface GitHubRepositoryFieldProps<TFormValues extends FieldValues> {
form: UseFormReturn<TFormValues>
name: Path<TFormValues>
label: string
description?: ReactNode
layout?: ComponentProps<typeof FormItemLayout>['layout']
disabled?: boolean
selectedRepositoryName?: string
installationIdField?: Path<TFormValues>
repositoryNameField?: Path<TFormValues>
repositories: GitHubRepository[]
gitHubAuthorization: unknown | null
hasPartialResponseDueToSSO?: boolean
isLoading?: boolean
placeholder?: string
refetch: () => void
onConnectClick?: () => void
onRepositorySelect?: (repo: GitHubRepository) => void
}
export const GitHubRepositoryField = <TFormValues extends FieldValues>({
form,
name,
label,
description,
layout = 'horizontal',
disabled = false,
selectedRepositoryName,
installationIdField,
repositoryNameField,
repositories,
gitHubAuthorization,
hasPartialResponseDueToSSO = false,
isLoading = false,
placeholder = 'Choose GitHub repository',
refetch,
onConnectClick,
onRepositorySelect,
}: GitHubRepositoryFieldProps<TFormValues>) => {
const [isRepoSelectorOpen, setIsRepoSelectorOpen] = useState(false)
const currentRepositoryId = form.watch(name) as string | undefined
const selectedRepository = repositories.find((repo) => repo.id === currentRepositoryId)
return (
<FormField
control={form.control}
name={name}
render={({ field }) => (
<FormItemLayout label={label} layout={layout} description={description}>
{gitHubAuthorization === null ? (
<FormControl>
<Button
type="default"
size="small"
htmlType="button"
disabled={disabled}
onClick={() => {
onConnectClick?.()
openInstallGitHubIntegrationWindow('authorize', refetch)
}}
icon={GITHUB_ICON}
>
Connect GitHub
</Button>
</FormControl>
) : (
<Popover open={isRepoSelectorOpen} onOpenChange={setIsRepoSelectorOpen}>
<PopoverTrigger asChild>
<FormControl>
<Button
type="default"
htmlType="button"
className="justify-start h-[34px] w-full"
disabled={disabled || isLoading}
loading={isLoading}
icon={GITHUB_ICON}
iconRight={
<span className="grow flex justify-end">
<ChevronDown />
</span>
}
>
{selectedRepository?.name ||
selectedRepositoryName ||
(isLoading ? 'Loading GitHub repositories...' : placeholder)}
</Button>
</FormControl>
</PopoverTrigger>
<PopoverContent className="p-0" side="bottom" align="start" sameWidthAsTrigger>
<Command>
<CommandInput placeholder="Search repositories..." />
<CommandList className="!max-h-[220px]">
<CommandEmpty>No repositories found.</CommandEmpty>
{repositories.length > 0 ? (
<CommandGroup>
{repositories.map((repo) => (
<CommandItem
key={repo.id}
value={`${repo.name.replaceAll('"', '')}-${repo.id}`}
className="flex gap-2 items-center"
onSelect={() => {
field.onChange(repo.id)
if (installationIdField !== undefined) {
form.setValue(installationIdField, repo.installation_id as never, {
shouldDirty: true,
})
}
if (repositoryNameField !== undefined) {
form.setValue(repositoryNameField, repo.name as never, {
shouldDirty: true,
})
}
onRepositorySelect?.(repo)
setIsRepoSelectorOpen(false)
}}
>
{GITHUB_ICON}
<span className="truncate" title={repo.name}>
{repo.name}
</span>
</CommandItem>
))}
</CommandGroup>
) : null}
<CommandGroup>
<CommandItem
className="flex gap-2 items-center cursor-pointer"
onSelect={() => {
setIsRepoSelectorOpen(false)
openInstallGitHubIntegrationWindow('install', refetch)
}}
>
<PlusIcon size={16} />
Add GitHub Repositories
</CommandItem>
</CommandGroup>
{hasPartialResponseDueToSSO && (
<>
<CommandSeparator />
<CommandGroup>
<CommandItem
className="flex gap-2 items-start cursor-pointer"
onSelect={() => {
setIsRepoSelectorOpen(false)
openInstallGitHubIntegrationWindow('authorize', refetch)
}}
>
<RefreshCw size={16} className="mt-0.5 shrink-0" />
<div className="text-xs text-foreground-light">
Re-authorize GitHub with SSO to show all repositories
</div>
</CommandItem>
</CommandGroup>
</>
)}
</CommandList>
</Command>
</PopoverContent>
</Popover>
)}
</FormItemLayout>
)}
/>
)
}