mirror of
https://github.com/supabase/supabase.git
synced 2026-10-06 01:45:10 +03:00
## What kind of change does this PR introduce? Mechanical cleanup on top of the Button default-variant change (#50160). ## What is the current behavior? Many callsites still pass `variant="default"` even though that is now the component default. ## What is the new behavior? Removes redundant static `variant="default"` from legacy `Button` and `ButtonTooltip` callsites. Keeps explicit defaults where they document the API: - `button-default.tsx` and `button-sizes.tsx` demos - `DocsButton`, which pins neutral styling at the wrapper boundary ## To test Studio: - [Auth → Rate Limits](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/auth/rate-limits): dirty the form so Cancel appears; Cancel stays neutral, Save stays green - [Project Settings → API Keys](https://studio-staging-2s957kwc4-supabase.vercel.app/dashboard/project/_/settings/api-keys): `DocsButton` in the header actions stays neutral Design system: - [Design system → Button](https://design-system-git-dnywh-dc924ac1-supabase.vercel.app/design-system/docs/components/button): `button-default` / `button-sizes` still show explicit default styling; Primary (green) is restricted to the Primary section (and `asChild`) WWW: - [www → Brand assets](https://zone-www-dot-com-git-dnywh-dc924ac1-supabase.vercel.app/brand-assets): Download logo kit / Download button kit stay neutral
717 lines
27 KiB
TypeScript
717 lines
27 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod'
|
|
import { PermissionAction } from '@supabase/shared-types/out/constants'
|
|
import { AnimatePresence, motion } from 'framer-motion'
|
|
import { Loader2 } from 'lucide-react'
|
|
import { useEffect, useState } from 'react'
|
|
import { useForm, useWatch } from 'react-hook-form'
|
|
import { toast } from 'sonner'
|
|
import {
|
|
Button,
|
|
Card,
|
|
CardContent,
|
|
CardFooter,
|
|
cn,
|
|
Form,
|
|
FormControl,
|
|
FormField,
|
|
Input,
|
|
Switch,
|
|
} from 'ui'
|
|
import { Admonition } from 'ui-patterns/Admonition'
|
|
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
|
|
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
|
|
import * as z from 'zod'
|
|
|
|
import {
|
|
GitHubRepositoryField,
|
|
useGitHubRepositoryOptions,
|
|
} from '@/components/interfaces/Settings/Integrations/GithubIntegration/GitHubRepositoryField'
|
|
import { InlineLink } from '@/components/ui/InlineLink'
|
|
import { UpgradeToPro } from '@/components/ui/UpgradeToPro'
|
|
import { useBranchCreateMutation } from '@/data/branches/branch-create-mutation'
|
|
import { useBranchUpdateMutation } from '@/data/branches/branch-update-mutation'
|
|
import { useBranchesQuery } from '@/data/branches/branches-query'
|
|
import { useCheckGithubBranchValidity } from '@/data/integrations/github-branch-check-query'
|
|
import { useGitHubConnectionCreateMutation } from '@/data/integrations/github-connection-create-mutation'
|
|
import { useGitHubConnectionDeleteMutation } from '@/data/integrations/github-connection-delete-mutation'
|
|
import { useGitHubConnectionUpdateMutation } from '@/data/integrations/github-connection-update-mutation'
|
|
import type { GitHubConnection } from '@/data/integrations/integrations.types'
|
|
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
|
|
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
|
|
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
|
|
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
|
|
import { DOCS_URL } from '@/lib/constants'
|
|
|
|
interface GitHubIntegrationConnectionFormProps {
|
|
connection?: GitHubConnection
|
|
}
|
|
|
|
export const GitHubIntegrationConnectionForm = ({
|
|
connection,
|
|
}: GitHubIntegrationConnectionFormProps) => {
|
|
const { data: selectedProject } = useSelectedProjectQuery()
|
|
const { data: selectedOrganization } = useSelectedOrganizationQuery()
|
|
const [isConfirmingBranchChange, setIsConfirmingBranchChange] = useState(false)
|
|
const [isConfirmingRepoChange, setIsConfirmingRepoChange] = useState(false)
|
|
const isParentProject = !selectedProject?.parent_project_ref
|
|
|
|
const { hasAccess: hasAccessToGitHubIntegration, isLoading: isLoadingEntitlements } =
|
|
useCheckEntitlements('integrations.github_connections')
|
|
|
|
const { hasAccess: hasAccessToBranching } = useCheckEntitlements('branching_limit')
|
|
|
|
const { can: canUpdateGitHubConnection } = useAsyncCheckPermissions(
|
|
PermissionAction.UPDATE,
|
|
'integrations.github_connections'
|
|
)
|
|
const { can: canCreateGitHubConnection } = useAsyncCheckPermissions(
|
|
PermissionAction.CREATE,
|
|
'integrations.github_connections'
|
|
)
|
|
|
|
const {
|
|
gitHubAuthorization,
|
|
githubRepos,
|
|
hasPartialResponseDueToSSO,
|
|
isLoading: isLoadingRepositoryOptions,
|
|
refetch: refetchRepositoryOptions,
|
|
} = useGitHubRepositoryOptions()
|
|
|
|
const { mutate: updateBranch } = useBranchUpdateMutation({
|
|
onSuccess: () => {
|
|
toast.success('Production branch settings successfully updated')
|
|
},
|
|
})
|
|
const { mutate: createBranch } = useBranchCreateMutation({
|
|
onSuccess: () => {
|
|
toast.success('Production branch settings successfully updated')
|
|
},
|
|
onError: (error) => {
|
|
console.error('Failed to enable branching:', error)
|
|
},
|
|
})
|
|
|
|
const { data: existingBranches } = useBranchesQuery(
|
|
{ projectRef: selectedProject?.ref },
|
|
{ enabled: !!selectedProject?.ref }
|
|
)
|
|
|
|
const { mutateAsync: checkGithubBranchValidity, isPending: isCheckingBranch } =
|
|
useCheckGithubBranchValidity({ onError: () => {} })
|
|
|
|
const { mutate: createConnection, isPending: isCreatingConnection } =
|
|
useGitHubConnectionCreateMutation({
|
|
onSuccess: () => {
|
|
toast.success('GitHub connection updated')
|
|
},
|
|
onError: (error) => {
|
|
// Don't show error toast when connection already exists - the branch
|
|
// settings update will still proceed and show its own success toast
|
|
if (!error.message?.includes('already exists')) {
|
|
toast.error(`Failed to create GitHub connection: ${error.message}`)
|
|
}
|
|
},
|
|
})
|
|
|
|
const { mutateAsync: deleteConnection, isPending: isDeletingConnection } =
|
|
useGitHubConnectionDeleteMutation({
|
|
onSuccess: () => {
|
|
toast.success('GitHub connection removed')
|
|
},
|
|
})
|
|
|
|
const { mutate: updateConnectionSettings, isPending: isUpdatingConnection } =
|
|
useGitHubConnectionUpdateMutation()
|
|
|
|
const prodBranch = existingBranches?.find((branch) => branch.is_default)
|
|
|
|
// Combined GitHub Settings Form
|
|
const GitHubSettingsSchema = z
|
|
.object({
|
|
repositoryId: z.string().min(1, 'Please select a repository'),
|
|
enableProductionSync: z.boolean().default(true),
|
|
branchName: z.string().default('main'),
|
|
new_branch_per_pr: z.boolean().default(true),
|
|
supabaseDirectory: z.string().default('.'),
|
|
supabaseChangesOnly: z.boolean().default(true),
|
|
branchLimit: z.string().default('50'),
|
|
})
|
|
.superRefine(async (val, ctx) => {
|
|
if (val.enableProductionSync && val.branchName && val.branchName.length > 0) {
|
|
const repositoryId = val.repositoryId || connection?.repository.id.toString()
|
|
if (repositoryId) {
|
|
try {
|
|
await checkGithubBranchValidity({
|
|
repositoryId: Number(repositoryId),
|
|
branchName: val.branchName,
|
|
})
|
|
} catch {
|
|
const selectedRepo = githubRepos.find((repo) => repo.id === repositoryId)
|
|
const repoName =
|
|
selectedRepo?.name || connection?.repository.name || 'selected repository'
|
|
ctx.addIssue({
|
|
code: z.ZodIssueCode.custom,
|
|
message: `Branch "${val.branchName}" not found in ${repoName}`,
|
|
path: ['branchName'],
|
|
})
|
|
}
|
|
}
|
|
}
|
|
})
|
|
|
|
const githubSettingsForm = useForm<z.infer<typeof GitHubSettingsSchema>>({
|
|
resolver: zodResolver(GitHubSettingsSchema),
|
|
mode: 'onSubmit',
|
|
reValidateMode: 'onBlur',
|
|
defaultValues: {
|
|
repositoryId: connection?.repository.id.toString() || '',
|
|
enableProductionSync: true,
|
|
branchName: 'main',
|
|
new_branch_per_pr: true,
|
|
supabaseDirectory: '.',
|
|
supabaseChangesOnly: true,
|
|
branchLimit: '3',
|
|
},
|
|
})
|
|
|
|
const enableProductionSync = useWatch({
|
|
control: githubSettingsForm.control,
|
|
name: 'enableProductionSync',
|
|
})
|
|
const newBranchPerPr = useWatch({
|
|
control: githubSettingsForm.control,
|
|
name: 'new_branch_per_pr',
|
|
})
|
|
const currentRepositoryId = useWatch({
|
|
control: githubSettingsForm.control,
|
|
name: 'repositoryId',
|
|
})
|
|
|
|
const handleCreateOrUpdateConnection = async (data: z.infer<typeof GitHubSettingsSchema>) => {
|
|
if (!selectedProject?.ref || !selectedOrganization?.id) return
|
|
|
|
try {
|
|
if (connection) {
|
|
// Check if repository is being changed
|
|
if (connection.repository.id.toString() !== data.repositoryId) {
|
|
setIsConfirmingRepoChange(true)
|
|
return
|
|
}
|
|
// Update existing connection
|
|
await handleUpdateConnection(data, connection)
|
|
} else {
|
|
// Create new connection
|
|
const selectedRepo = githubRepos.find((repo) => repo.id === data.repositoryId)
|
|
if (!selectedRepo) {
|
|
toast.error('Please select a repository')
|
|
return
|
|
}
|
|
await handleCreateConnection(data, selectedRepo)
|
|
}
|
|
} catch (error) {
|
|
console.error('Error managing connection:', error)
|
|
}
|
|
}
|
|
|
|
const handleCreateConnection = async (
|
|
data: z.infer<typeof GitHubSettingsSchema>,
|
|
selectedRepo: { id: string; installation_id: number }
|
|
) => {
|
|
if (!selectedProject?.ref || !selectedOrganization?.id) return
|
|
|
|
createConnection({
|
|
organizationId: selectedOrganization.id,
|
|
connection: {
|
|
installation_id: selectedRepo.installation_id,
|
|
project_ref: selectedProject.ref,
|
|
repository_id: Number(selectedRepo.id),
|
|
workdir: data.supabaseDirectory,
|
|
...(hasAccessToBranching && {
|
|
supabase_changes_only: data.supabaseChangesOnly,
|
|
branch_limit: Number(data.branchLimit),
|
|
new_branch_per_pr: data.new_branch_per_pr ?? false,
|
|
}),
|
|
},
|
|
})
|
|
|
|
if (!prodBranch) {
|
|
createBranch({
|
|
projectRef: selectedProject.ref,
|
|
branchName: 'main',
|
|
gitBranch: data.branchName,
|
|
is_default: true,
|
|
})
|
|
} else {
|
|
updateBranch({
|
|
branchRef: prodBranch.project_ref,
|
|
projectRef: selectedProject.ref,
|
|
gitBranch: data.branchName,
|
|
})
|
|
}
|
|
}
|
|
|
|
const handleUpdateConnection = async (
|
|
data: z.infer<typeof GitHubSettingsSchema>,
|
|
currentConnection: GitHubConnection
|
|
) => {
|
|
if (!selectedProject?.ref || !selectedOrganization?.id) return
|
|
|
|
const originalBranchName = prodBranch?.git_branch
|
|
|
|
if (originalBranchName && data.branchName !== originalBranchName && data.enableProductionSync) {
|
|
setIsConfirmingBranchChange(true)
|
|
return
|
|
}
|
|
|
|
await executeUpdate(data, currentConnection)
|
|
}
|
|
|
|
const executeUpdate = async (
|
|
data: z.infer<typeof GitHubSettingsSchema>,
|
|
currentConnection: GitHubConnection
|
|
) => {
|
|
if (!selectedProject?.ref || !selectedOrganization?.id) return
|
|
|
|
updateConnectionSettings({
|
|
connectionId: currentConnection.id,
|
|
organizationId: selectedOrganization.id,
|
|
connection: {
|
|
workdir: data.supabaseDirectory,
|
|
...(hasAccessToBranching && {
|
|
supabase_changes_only: data.supabaseChangesOnly,
|
|
branch_limit: Number(data.branchLimit),
|
|
new_branch_per_pr: data.new_branch_per_pr ?? false,
|
|
}),
|
|
},
|
|
})
|
|
|
|
if (prodBranch) {
|
|
updateBranch({
|
|
branchRef: prodBranch.project_ref,
|
|
projectRef: selectedProject.ref,
|
|
gitBranch: data.enableProductionSync ? data.branchName : '',
|
|
branchName: data.branchName || 'main',
|
|
})
|
|
} else {
|
|
// if for some reason, the project doesn't have a default branch yet, create it.
|
|
createBranch({
|
|
projectRef: selectedProject.ref,
|
|
gitBranch: data.enableProductionSync ? data.branchName : '',
|
|
branchName: data.branchName || 'main',
|
|
is_default: true,
|
|
})
|
|
}
|
|
|
|
setIsConfirmingBranchChange(false)
|
|
}
|
|
|
|
const onConfirmBranchChange = async () => {
|
|
if (connection) {
|
|
await executeUpdate(githubSettingsForm.getValues(), connection)
|
|
}
|
|
}
|
|
|
|
const handleRemoveIntegration = async () => {
|
|
if (!connection || !selectedOrganization?.id) return
|
|
|
|
try {
|
|
await deleteConnection({
|
|
organizationId: selectedOrganization.id,
|
|
connectionId: connection.id,
|
|
})
|
|
|
|
githubSettingsForm.reset({
|
|
repositoryId: '',
|
|
enableProductionSync: true,
|
|
branchName: 'main',
|
|
new_branch_per_pr: true,
|
|
supabaseDirectory: '.',
|
|
supabaseChangesOnly: true,
|
|
branchLimit: '3',
|
|
})
|
|
} catch (error) {
|
|
console.error('Error removing integration:', error)
|
|
toast.error('Failed to remove integration')
|
|
}
|
|
}
|
|
|
|
const onConfirmRepoChange = async () => {
|
|
const data = githubSettingsForm.getValues()
|
|
const selectedRepo = githubRepos.find((repo) => repo.id === data.repositoryId)
|
|
|
|
if (!selectedRepo || !connection || !selectedOrganization?.id) return
|
|
|
|
try {
|
|
await deleteConnection({
|
|
organizationId: selectedOrganization.id,
|
|
connectionId: connection.id,
|
|
})
|
|
|
|
await handleCreateConnection(data, selectedRepo)
|
|
|
|
setIsConfirmingRepoChange(false)
|
|
} catch (error) {
|
|
console.error('Error changing repository:', error)
|
|
toast.error('Failed to change repository')
|
|
}
|
|
}
|
|
|
|
useEffect(() => {
|
|
if (connection) {
|
|
const hasGitBranch = Boolean(prodBranch?.git_branch?.trim())
|
|
|
|
githubSettingsForm.reset({
|
|
repositoryId: connection.repository.id.toString(),
|
|
enableProductionSync: hasGitBranch,
|
|
branchName: prodBranch?.git_branch || 'main',
|
|
new_branch_per_pr: connection.new_branch_per_pr,
|
|
supabaseDirectory: connection.workdir || '',
|
|
supabaseChangesOnly: connection.supabase_changes_only,
|
|
branchLimit: String(connection.branch_limit),
|
|
})
|
|
}
|
|
}, [connection, prodBranch, githubSettingsForm])
|
|
|
|
// Handle clearing branch name when production sync is disabled
|
|
useEffect(() => {
|
|
if (!enableProductionSync) {
|
|
githubSettingsForm.setValue('branchName', '')
|
|
} else if (enableProductionSync && !githubSettingsForm.getValues().branchName) {
|
|
githubSettingsForm.setValue('branchName', 'main')
|
|
}
|
|
}, [enableProductionSync, githubSettingsForm])
|
|
|
|
const isLoading =
|
|
isLoadingEntitlements ||
|
|
isCreatingConnection ||
|
|
isUpdatingConnection ||
|
|
isDeletingConnection ||
|
|
isLoadingRepositoryOptions
|
|
|
|
let repositoryDescription = 'Select the repository to connect to your project'
|
|
if (connection) {
|
|
repositoryDescription = 'Change the connected repository'
|
|
} else if (gitHubAuthorization === null) {
|
|
repositoryDescription = 'Connect GitHub to link a repository to this project'
|
|
}
|
|
|
|
return (
|
|
<>
|
|
<Form {...githubSettingsForm}>
|
|
<form
|
|
onSubmit={githubSettingsForm.handleSubmit(handleCreateOrUpdateConnection)}
|
|
className={cn(!isParentProject && 'opacity-25 pointer-events-none')}
|
|
>
|
|
<Card>
|
|
<CardContent className="space-y-6">
|
|
<GitHubRepositoryField
|
|
form={githubSettingsForm}
|
|
name="repositoryId"
|
|
label="GitHub repository"
|
|
layout="flex-row-reverse"
|
|
description={repositoryDescription}
|
|
disabled={
|
|
(!connection && !canCreateGitHubConnection) ||
|
|
(connection && !canUpdateGitHubConnection)
|
|
}
|
|
selectedRepositoryName={connection?.repository.name}
|
|
repositories={githubRepos}
|
|
gitHubAuthorization={gitHubAuthorization}
|
|
hasPartialResponseDueToSSO={hasPartialResponseDueToSSO}
|
|
isLoading={isLoadingRepositoryOptions}
|
|
refetch={refetchRepositoryOptions}
|
|
onRepositorySelect={(repo) => {
|
|
githubSettingsForm.setValue('branchName', repo.default_branch || 'main')
|
|
}}
|
|
/>
|
|
</CardContent>
|
|
|
|
<AnimatePresence>
|
|
{gitHubAuthorization !== null && !!currentRepositoryId && (
|
|
<motion.div
|
|
initial={{ opacity: 0, y: -16 }}
|
|
animate={{ opacity: 1, y: 0 }}
|
|
exit={{ opacity: 0, y: -16 }}
|
|
>
|
|
<CardContent>
|
|
<FormField
|
|
control={githubSettingsForm.control}
|
|
name="supabaseDirectory"
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
layout="flex-row-reverse"
|
|
label="Working directory"
|
|
description={
|
|
<>
|
|
Relative path to the directory containing your{' '}
|
|
<code className="text-code-inline whitespace-nowrap">supabase/</code>{' '}
|
|
folder.{' '}
|
|
<InlineLink
|
|
href={`${DOCS_URL}/guides/deployment/branching/github-integration#set-the-working-directory`}
|
|
>
|
|
Learn more
|
|
</InlineLink>
|
|
</>
|
|
}
|
|
>
|
|
<FormControl>
|
|
<Input
|
|
{...field}
|
|
placeholder="."
|
|
autoComplete="off"
|
|
disabled={!canUpdateGitHubConnection}
|
|
/>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
</CardContent>
|
|
<CardContent>
|
|
{/* Production Branch Sync Section */}
|
|
<div className="space-y-4">
|
|
<FormField
|
|
control={githubSettingsForm.control}
|
|
name="enableProductionSync"
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
layout="flex-row-reverse"
|
|
label="Deploy to production"
|
|
description="Apply changes to your production database when you merge into your configured production GitHub branch"
|
|
>
|
|
<FormControl>
|
|
<Switch
|
|
checked={field.value}
|
|
onCheckedChange={field.onChange}
|
|
disabled={!canUpdateGitHubConnection}
|
|
/>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
|
|
<div
|
|
className={cn(
|
|
'space-y-4 pl-6 border-l',
|
|
!enableProductionSync && 'opacity-25 pointer-events-none'
|
|
)}
|
|
>
|
|
<FormField
|
|
control={githubSettingsForm.control}
|
|
name="branchName"
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
layout="flex-row-reverse"
|
|
label="Production branch name"
|
|
description="The GitHub branch to sync with your production database (e.g., main, master)"
|
|
>
|
|
<div className="relative w-full">
|
|
<FormControl>
|
|
<Input
|
|
{...field}
|
|
autoComplete="off"
|
|
disabled={!canUpdateGitHubConnection || !enableProductionSync}
|
|
/>
|
|
</FormControl>
|
|
<div className="absolute top-2.5 right-3 flex items-center gap-2">
|
|
{isCheckingBranch && (
|
|
<Loader2 size={14} className="animate-spin" />
|
|
)}
|
|
</div>
|
|
</div>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
<CardContent>
|
|
{hasAccessToBranching ? (
|
|
<Admonition type="warning" title="Branching and billing" className="mb-4">
|
|
Branching Compute is not covered by your organization's Spend Cap.
|
|
Costs should be closely monitored, as they may be incurred.{' '}
|
|
<InlineLink
|
|
href={`${DOCS_URL}/guides/platform/cost-control#usage-items-not-covered-by-the-spend-cap`}
|
|
>
|
|
Learn more
|
|
</InlineLink>
|
|
</Admonition>
|
|
) : (
|
|
<UpgradeToPro
|
|
className="mb-4"
|
|
layout="vertical"
|
|
source="projectIntegrations"
|
|
featureProposition="automatically create preview branches from pull requests"
|
|
primaryText="Branching with GitHub integration"
|
|
secondaryText="Upgrade to the Pro Plan to enable branching and automatically create preview branches for every pull request"
|
|
docsUrl={`${DOCS_URL}/guides/deployment/branching`}
|
|
/>
|
|
)}
|
|
|
|
{/* Automatic Branching Section */}
|
|
<div className="space-y-4">
|
|
<FormField
|
|
disabled={!hasAccessToBranching}
|
|
control={githubSettingsForm.control}
|
|
name="new_branch_per_pr"
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
layout="flex-row-reverse"
|
|
label="Automatic branching"
|
|
className={cn(!hasAccessToBranching && 'opacity-25')}
|
|
description="Create preview branches for every pull request"
|
|
>
|
|
<FormControl>
|
|
<Switch
|
|
checked={!hasAccessToBranching ? false : field.value}
|
|
onCheckedChange={field.onChange}
|
|
disabled={!hasAccessToBranching || !canCreateGitHubConnection}
|
|
/>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
|
|
<div
|
|
className={cn(
|
|
'space-y-4 pl-6 border-l',
|
|
(!hasAccessToBranching || !newBranchPerPr) &&
|
|
'opacity-25 pointer-events-none'
|
|
)}
|
|
>
|
|
<FormField
|
|
control={githubSettingsForm.control}
|
|
name="branchLimit"
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
layout="flex-row-reverse"
|
|
label="Branch limit"
|
|
description="Maximum number of preview branches"
|
|
>
|
|
<FormControl>
|
|
<Input
|
|
{...field}
|
|
type="number"
|
|
autoComplete="off"
|
|
value={!hasAccessToBranching ? 0 : field.value}
|
|
disabled={
|
|
!hasAccessToBranching ||
|
|
!newBranchPerPr ||
|
|
!canUpdateGitHubConnection
|
|
}
|
|
/>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
|
|
<FormField
|
|
control={githubSettingsForm.control}
|
|
name="supabaseChangesOnly"
|
|
render={({ field }) => (
|
|
<FormItemLayout
|
|
layout="flex-row-reverse"
|
|
label="Supabase changes only"
|
|
description="Only create branches when Supabase files change"
|
|
>
|
|
<FormControl>
|
|
<Switch
|
|
checked={!hasAccessToBranching ? false : field.value}
|
|
onCheckedChange={(val) => field.onChange(val)}
|
|
disabled={
|
|
!hasAccessToBranching ||
|
|
!newBranchPerPr ||
|
|
!canUpdateGitHubConnection
|
|
}
|
|
/>
|
|
</FormControl>
|
|
</FormItemLayout>
|
|
)}
|
|
/>
|
|
</div>
|
|
</div>
|
|
</CardContent>
|
|
<CardFooter className="flex justify-between items-center">
|
|
<div>
|
|
{connection && (
|
|
<Button
|
|
variant="outline"
|
|
onClick={handleRemoveIntegration}
|
|
disabled={isDeletingConnection || isCheckingBranch}
|
|
loading={isDeletingConnection}
|
|
>
|
|
Disable integration
|
|
</Button>
|
|
)}
|
|
</div>
|
|
<div className="flex space-x-2">
|
|
{githubSettingsForm.formState.isDirty && (
|
|
<Button
|
|
onClick={() => githubSettingsForm.reset()}
|
|
disabled={!canUpdateGitHubConnection || isCheckingBranch}
|
|
>
|
|
Cancel
|
|
</Button>
|
|
)}
|
|
<Button
|
|
variant="primary"
|
|
type="submit"
|
|
disabled={
|
|
!hasAccessToGitHubIntegration ||
|
|
(!connection && !canCreateGitHubConnection) ||
|
|
(connection && !canUpdateGitHubConnection) ||
|
|
isCheckingBranch ||
|
|
isLoading ||
|
|
(!connection && !githubSettingsForm.getValues().repositoryId) ||
|
|
(connection && !githubSettingsForm.formState.isDirty)
|
|
}
|
|
loading={isLoading}
|
|
>
|
|
{connection ? 'Save changes' : 'Enable integration'}
|
|
</Button>
|
|
</div>
|
|
</CardFooter>
|
|
</motion.div>
|
|
)}
|
|
</AnimatePresence>
|
|
</Card>
|
|
</form>
|
|
</Form>
|
|
|
|
<ConfirmationModal
|
|
variant="warning"
|
|
visible={isConfirmingBranchChange}
|
|
title="Changing production git branch"
|
|
confirmLabel="Confirm"
|
|
size="medium"
|
|
onCancel={() => setIsConfirmingBranchChange(false)}
|
|
onConfirm={onConfirmBranchChange}
|
|
loading={isUpdatingConnection}
|
|
>
|
|
<p className="text-sm text-foreground-light">
|
|
Open pull requests will only update your Supabase project on merge if the git base branch
|
|
matches this new production git branch.
|
|
</p>
|
|
</ConfirmationModal>
|
|
|
|
<ConfirmationModal
|
|
variant="warning"
|
|
visible={isConfirmingRepoChange}
|
|
title="Changing GitHub repository"
|
|
confirmLabel="Change repository"
|
|
size="medium"
|
|
onCancel={() => setIsConfirmingRepoChange(false)}
|
|
onConfirm={onConfirmRepoChange}
|
|
loading={isLoading}
|
|
>
|
|
<div className="space-y-3">
|
|
<p className="text-sm text-foreground-light">
|
|
This will disconnect your current repository and create a new connection with the
|
|
selected repository. All existing Supabase branches that are connected to the old
|
|
repository will no longer be synced.
|
|
</p>
|
|
</div>
|
|
</ConfirmationModal>
|
|
</>
|
|
)
|
|
}
|