Joshen/fe 4018 bug gh branch validation in branch modal fails silently if (#48432)

## Context

Realised that if the project has a GH integration, but the user's
account is not connected to GH - the branch validation in the "Sync with
Git Branch" field will not work. The Edit branch modal also obfuscates
the error being returned from the validation API so its not clear what
the issue is
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/739dfe7c-8920-4edf-a751-63d7f6273db4"
/>

Opting to show an "Authorize" CTA for this scenario so it's clear from
the user's POV what to do (Refer to "To test" below for screenshots)


## To test

- [ ] Verify that on an account which isn't connect to GH + project with
no GH integration - CTA is as per normal ("Configure") which should
direct you to the settings -> integrations page (Same for edit branch)
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/9a010fde-8ab0-43d6-b5c9-ced9fed1426e"
/>
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/d129cccf-7238-4305-913b-0cf78c7dcc26"
/>

- [ ] Set up a GH integration and check Create / Edit branch - the
branch input field should work with proper branch name validation
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/4d643956-2d11-406b-b198-193f3221b7a9"
/>

- [ ] Now go to Account settings and remove the GH connection, then
check the Create / Edit branch modals - should have the "Authorize" CTA
(instead of the input field)
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/ac152c0d-2e9c-4d89-95bc-36127c0fc8df"
/>
<img width="500" alt="image"
src="https://github.com/user-attachments/assets/d1f50d38-d801-4546-96fd-8cf3b5d0f805"
/>




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

* **New Features**
* Added a “Sync with a GitHub branch” connection entry with an inline
authorize flow.
* Integrated GitHub authorization awareness into branch create/edit
modals so users are guided to authorize or proceed to syncing.
* **Bug Fixes**
* Unified loading, success, and error handling for GitHub
authorization/connection checks across create and edit flows.
  * Improved Git branch validation messaging to show cleaner error text.
* **Accessibility/UX**
* Updated modal UI text and added an explicit label for the “Include
data” toggle.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Joshen Lim authored and GitHub committed 2026-07-31 16:52:12 +07:00
1 parent 97a935fb4f
commit fd67a8014f
3 files changed
+112 -60

No files matched your search

@@ -0,0 +1,56 @@
import { useParams } from 'common'
import { Github } from 'lucide-react'
import { useRouter } from 'next/router'
import { Button } from 'ui'
import { useGitHubAuthorizationQuery } from '@/data/integrations/github-authorization-query'
import { useGitHubConnectionsQuery } from '@/data/integrations/github-connections-query'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { openInstallGitHubIntegrationWindow } from '@/lib/github'
import { useAppStateSnapshot } from '@/state/app-state'
export const ConnectToGitHub = () => {
const router = useRouter()
const { ref } = useParams()
const { data: project } = useSelectedProjectQuery()
const { data: selectedOrg } = useSelectedOrganizationQuery()
const { showCreateBranchModal, setShowCreateBranchModal } = useAppStateSnapshot()
const isBranch = project?.parent_project_ref !== undefined
const projectRef =
project !== undefined ? (isBranch ? project.parent_project_ref : ref) : undefined
const { data: gitHubAuthorization } = useGitHubAuthorizationQuery()
const { data: connections } = useGitHubConnectionsQuery(
{ organizationId: selectedOrg?.id },
{ enabled: showCreateBranchModal }
)
const githubConnection = connections?.find((connection) => connection.project.ref === projectRef)
const showAuthorizeCta = githubConnection && !gitHubAuthorization
const onClick = () => {
if (showAuthorizeCta) {
openInstallGitHubIntegrationWindow('authorize')
} else {
if (showCreateBranchModal) setShowCreateBranchModal(false)
router.push(`/project/${projectRef}/settings/integrations`)
}
}
return (
<div className="flex items-center gap-2 justify-between">
<div className="flex flex-col gap-1">
<span className="text-sm text leading-none">Sync with a GitHub branch</span>
<p className="text-sm text-foreground-lighter">
Keep this preview branch in sync with a chosen GitHub branch
</p>
</div>
<Button variant="default" icon={<Github />} onClick={onClick}>
{showAuthorizeCta ? 'Authorize' : 'Configure'}
</Button>
</div>
)
}
@@ -3,7 +3,7 @@ import { PermissionAction } from '@supabase/shared-types/out/constants'
import { useQueryClient } from '@tanstack/react-query'
import { useDebounce } from '@uidotdev/usehooks'
import { useFlag, useParams } from 'common'
import { Check, DatabaseZap, DollarSign, Github, GitMerge, Loader2 } from 'lucide-react'
import { Check, DatabaseZap, DollarSign, GitMerge, Loader2 } from 'lucide-react'
import Image from 'next/image'
import Link from 'next/link'
import { useRouter } from 'next/router'
@@ -25,7 +25,6 @@ import {
FormControl,
FormField,
Input,
Label,
Switch,
Tooltip,
TooltipContent,
@@ -40,6 +39,7 @@ import {
estimateDiskCost,
estimateRestoreTime,
} from './BranchManagement.utils'
import { ConnectToGitHub } from './ConnectToGitHub'
import { TaxDisclaimer } from '@/components/interfaces/Billing/TaxDisclaimer'
import { getInfrastructurePath } from '@/components/interfaces/Settings/Infrastructure/Infrastructure.utils'
import { BranchingPITRNotice } from '@/components/layouts/AppLayout/EnableBranchingButton/BranchingPITRNotice'
@@ -50,6 +50,7 @@ import { UpgradeToPro } from '@/components/ui/UpgradeToPro'
import { useBranchCreateMutation } from '@/data/branches/branch-create-mutation'
import { useBranchesQuery } from '@/data/branches/branches-query'
import { DiskAttributesData, useDiskAttributesQuery } from '@/data/config/disk-attributes-query'
import { useGitHubAuthorizationQuery } from '@/data/integrations/github-authorization-query'
import { useCheckGithubBranchValidity } from '@/data/integrations/github-branch-check-query'
import { useGitHubConnectionsQuery } from '@/data/integrations/github-connections-query'
import { projectKeys } from '@/data/projects/keys'
@@ -118,6 +119,14 @@ export const CreateBranchModal = () => {
})
const debouncedGitBranchName = useDebounce(gitBranchName, 500)
const {
data: githubAuthorization,
error: authorizationError,
isPending: isLoadingAuthorization,
isSuccess: isSuccessAuthorization,
isError: isErrorAuthorization,
} = useGitHubAuthorizationQuery()
const {
data: connections,
error: connectionsError,
@@ -129,6 +138,11 @@ export const CreateBranchModal = () => {
{ enabled: showCreateBranchModal }
)
const isLoading = isLoadingAuthorization || isLoadingConnections
const isSuccess = isSuccessAuthorization && isSuccessConnections
const isError = isErrorAuthorization || isErrorConnections
const error = authorizationError || connectionsError
const { data: branches } = useBranchesQuery({ projectRef })
const { data: addons, isSuccess: isSuccessAddons } = useProjectAddonsQuery(
{ projectRef },
@@ -260,11 +274,6 @@ export const CreateBranchModal = () => {
})
}
const handleGitHubClick = () => {
setShowCreateBranchModal(false)
router.push(`/project/${projectRef}/settings/integrations`)
}
useEffect(() => {
if (showCreateBranchModal) form.reset()
}, [form, showCreateBranchModal])
@@ -323,22 +332,24 @@ export const CreateBranchModal = () => {
)}
/>
{isLoadingConnections && (
{isLoading && (
<div className="flex flex-col gap-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{isErrorConnections && (
{isError && (
<AlertError
error={connectionsError}
error={error}
subject="Failed to retrieve GitHub connection information"
/>
)}
{isSuccessConnections &&
(githubConnection ? (
{isSuccess &&
(!githubAuthorization || !githubConnection ? (
<ConnectToGitHub />
) : (
<FormField
control={form.control}
name="gitBranchName"
@@ -367,7 +378,11 @@ export const CreateBranchModal = () => {
</div>
}
labelOptional="Optional"
description="Automatically deploy changes on every commit"
description={
githubAuthorization
? 'Automatically deploy changes on every commit'
: undefined
}
>
<div className="relative w-full">
<FormControl>
@@ -394,18 +409,6 @@ export const CreateBranchModal = () => {
</FormItemLayout>
)}
/>
) : (
<div className="flex items-center gap-2 justify-between">
<div className="flex flex-col gap-1">
<Label>Sync with a GitHub branch</Label>
<p className="text-sm text-foreground-lighter">
Keep this preview branch in sync with a chosen GitHub branch
</p>
</div>
<Button variant="default" icon={<Github />} onClick={handleGitHubClick}>
Configure
</Button>
</div>
))}
{allowDataBranching && (
@@ -416,7 +419,7 @@ export const CreateBranchModal = () => {
<FormItemLayout
label={
<>
<Label className="mr-2">Include data</Label>
<span className="mr-2">Include data</span>
{!hasPitrEnabled && <Badge variant="warning">Requires PITR</Badge>}
</>
}
@@ -426,6 +429,7 @@ export const CreateBranchModal = () => {
>
<FormControl>
<Switch
aria-label="Include data"
disabled={!hasPitrEnabled}
checked={field.value}
onCheckedChange={field.onChange}
@@ -1,9 +1,8 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { useDebounce } from '@uidotdev/usehooks'
import { useParams } from 'common'
import { Check, Github, Loader2 } from 'lucide-react'
import { Check, Loader2 } from 'lucide-react'
import Image from 'next/image'
import { useRouter } from 'next/router'
import { useCallback, useEffect, useState } from 'react'
import { useForm, useWatch } from 'react-hook-form'
import { toast } from 'sonner'
@@ -21,16 +20,17 @@ import {
FormControl,
FormField,
Input,
Label,
} from 'ui'
import { FormItemLayout } from 'ui-patterns/form/FormItemLayout/FormItemLayout'
import { ShimmeringLoader } from 'ui-patterns/ShimmeringLoader'
import * as z from 'zod'
import { ConnectToGitHub } from './ConnectToGitHub'
import { AlertError } from '@/components/ui/AlertError'
import { InlineLink } from '@/components/ui/InlineLink'
import { useBranchUpdateMutation } from '@/data/branches/branch-update-mutation'
import { Branch, useBranchesQuery } from '@/data/branches/branches-query'
import { useGitHubAuthorizationQuery } from '@/data/integrations/github-authorization-query'
import { useCheckGithubBranchValidity } from '@/data/integrations/github-branch-check-query'
import { useGitHubConnectionsQuery } from '@/data/integrations/github-connections-query'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
@@ -45,7 +45,6 @@ interface EditBranchModalProps {
export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalProps) => {
const { ref } = useParams()
const router = useRouter()
const { data: projectDetails } = useSelectedProjectQuery()
const { data: selectedOrg } = useSelectedOrganizationQuery()
@@ -55,6 +54,14 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
const projectRef =
projectDetails !== undefined ? (isBranch ? projectDetails.parent_project_ref : ref) : undefined
const {
data: githubAuthorization,
error: authorizationError,
isPending: isLoadingAuthorization,
isSuccess: isSuccessAuthorization,
isError: isErrorAuthorization,
} = useGitHubAuthorizationQuery()
const {
data: connections,
error: connectionsError,
@@ -65,6 +72,11 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
organizationId: selectedOrg?.id,
})
const isLoading = isLoadingAuthorization || isLoadingConnections
const isSuccess = isSuccessAuthorization && isSuccessConnections
const isError = isErrorAuthorization || isErrorConnections
const error = authorizationError || connectionsError
const { data: branches } = useBranchesQuery({ projectRef })
const { mutate: checkGithubBranchValidity, isPending: isChecking } = useCheckGithubBranchValidity(
{ onError: () => {} }
@@ -113,14 +125,6 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
const isFormValid = form.formState.isValid && (!gitBranchName || isGitBranchValid)
const canSubmit = isFormValid && !isUpdating && !isChecking
const openLinkerPanel = () => {
onClose()
if (projectRef) {
router.push(`/project/${projectRef}/settings/integrations`)
}
}
const onSubmit = (data: z.infer<typeof FormSchema>) => {
if (!projectRef) return console.error('Project ref is required')
if (!branch?.project_ref) return console.error('Branch ref is required')
@@ -179,8 +183,9 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
if (form.getValues('gitBranchName') !== requested) return
setIsGitBranchValid(false)
form.setError('gitBranchName', {
...error,
message: `Unable to find branch "${branchName}" in ${repoOwner}/${repoName}`,
message:
error?.message ??
`Unable to find branch "${branchName}" in ${repoOwner}/${repoName}`,
})
},
}
@@ -234,22 +239,24 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
)}
/>
{isLoadingConnections && (
{isLoading && (
<div className="flex flex-col gap-y-2">
<ShimmeringLoader />
<ShimmeringLoader className="w-1/2" />
</div>
)}
{isErrorConnections && (
{isError && (
<AlertError
error={connectionsError}
error={error}
subject="Failed to retrieve GitHub connection information"
/>
)}
{isSuccessConnections &&
(githubConnection ? (
{isSuccess &&
(!githubAuthorization || !githubConnection ? (
<ConnectToGitHub />
) : (
<FormField
control={form.control}
name="gitBranchName"
@@ -300,21 +307,6 @@ export const EditBranchModal = ({ branch, visible, onClose }: EditBranchModalPro
</FormItemLayout>
)}
/>
) : (
<div className="flex items-center gap-2 justify-between">
<div className="flex flex-col gap-1">
<div className="flex items-center gap-2">
<Label>Sync with a GitHub branch</Label>
</div>
<p className="text-sm text-foreground-light">
Optionally connect to a GitHub repository to manage migrations automatically
for this branch.
</p>
</div>
<Button variant="default" icon={<Github />} onClick={openLinkerPanel}>
Connect to GitHub
</Button>
</div>
))}
</DialogSection>