From bd10880d522a871ae6adc2477e139d274c371c7b Mon Sep 17 00:00:00 2001
From: Ivan Vasilov
Date: Wed, 25 Mar 2026 11:54:30 +0100
Subject: [PATCH] feat: Enable Github on free plan under a feature flag
(#44171)
Co-authored-by: Joshen Lim
---
.../GitHubIntegrationConnectionForm.tsx | 473 ++++++++++--------
.../GithubIntegration/GithubSection.tsx | 34 +-
.../ProjectBranchSelectorTrigger.tsx | 82 +--
apps/studio/components/ui/UpgradeToPro.tsx | 28 +-
4 files changed, 334 insertions(+), 283 deletions(-)
diff --git a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx
index 1d9d98afe9d..cb022790ea9 100644
--- a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx
+++ b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GitHubIntegrationConnectionForm.tsx
@@ -1,30 +1,11 @@
import { zodResolver } from '@hookform/resolvers/zod'
import { PermissionAction } from '@supabase/shared-types/out/constants'
-import { IS_PLATFORM } from 'common'
-import { useBranchCreateMutation } from 'data/branches/branch-create-mutation'
-import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation'
-import { useBranchesQuery } from 'data/branches/branches-query'
-import { useGitHubAuthorizationQuery } from 'data/integrations/github-authorization-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 { useGitHubRepositoriesQuery } from 'data/integrations/github-repositories-query'
-import type { GitHubConnection } from 'data/integrations/integrations.types'
-import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
-import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
-import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject'
-import { DOCS_URL } from 'lib/constants'
-import { openInstallGitHubIntegrationWindow } from 'lib/github'
-import { EMPTY_ARR } from 'lib/void'
+import { AnimatePresence, motion } from 'framer-motion'
import { ChevronDown, Loader2, PlusIcon, RefreshCw } from 'lucide-react'
import { useEffect, useMemo, useState } from 'react'
import { useForm } from 'react-hook-form'
import { toast } from 'sonner'
import {
- Alert_Shadcn_,
- AlertDescription_Shadcn_,
- AlertTitle_Shadcn_,
Button,
Card,
CardContent,
@@ -46,11 +27,30 @@ import {
PopoverTrigger_Shadcn_,
Switch,
} from 'ui'
-import { InlineLink } from 'components/ui/InlineLink'
+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 { 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 { useGitHubAuthorizationQuery } from '@/data/integrations/github-authorization-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 { useGitHubRepositoriesQuery } from '@/data/integrations/github-repositories-query'
+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'
+import { openInstallGitHubIntegrationWindow } from '@/lib/github'
+import { EMPTY_ARR } from '@/lib/void'
const GITHUB_ICON = (
@@ -69,7 +69,7 @@ interface GitHubIntegrationConnectionFormProps {
connection?: GitHubConnection
}
-const GitHubIntegrationConnectionForm = ({
+export const GitHubIntegrationConnectionForm = ({
disabled = false,
connection,
}: GitHubIntegrationConnectionFormProps) => {
@@ -82,8 +82,8 @@ const GitHubIntegrationConnectionForm = ({
const { hasAccess: hasAccessToGitHubIntegration, isLoading: isLoadingEntitlements } =
useCheckEntitlements('integrations.github_connections')
- const promptProPlanUpgrade =
- IS_PLATFORM && !isLoadingEntitlements && !hasAccessToGitHubIntegration
+
+ const { hasAccess: hasAccessToBranching } = useCheckEntitlements('branching_limit')
const { can: canUpdateGitHubConnection } = useAsyncCheckPermissions(
PermissionAction.UPDATE,
@@ -460,7 +460,7 @@ const GitHubIntegrationConnectionForm = ({
@@ -553,213 +553,244 @@ const GitHubIntegrationConnectionForm = ({
)}
/>
-
- (
-
-
-
-
-
- )}
- />
-
-
- {/* Production Branch Sync Section */}
-
-
(
-
-
-
-
-
- )}
- />
-
+ {!!currentRepositoryId && (
+
- (
-
-
+
+ (
+
-
- {isCheckingBranch && }
-
-
-
- )}
- />
-
-
-
-
-
- Branching and billing
-
- Branching Compute is not covered by your organization's Spend Cap. Costs
- should be closely monitored, as they may be incurred.{' '}
-
- Learn more
-
-
-
- {/* Automatic Branching Section */}
-
-
(
-
-
-
+ )}
+ />
+
+
+ {/* Production Branch Sync Section */}
+
+
(
+
+
+
+
+
+ )}
+ />
+
+
+
(
+
+
+
+
+
+
+ {isCheckingBranch && (
+
+ )}
+
+
+
+ )}
/>
-
-
- )}
- />
-
-
- (
-
-
-
-
-
+
+
+
+
+ {hasAccessToBranching ? (
+
+ Branching Compute is not covered by your organization's Spend Cap.
+ Costs should be closely monitored, as they may be incurred.{' '}
+
+ Learn more
+
+
+ ) : (
+
)}
- />
- (
-
+ (
+
+
+
+
+
+ )}
+ />
+
+
-
- field.onChange(val)}
- disabled={!newBranchPerPr || disabled || !canUpdateGitHubConnection}
- />
-
-
- )}
- />
-
-
-
-
-
- {connection && (
-
- Disable integration
-
- )}
-
-
- {githubSettingsForm.formState.isDirty && (
- {
- githubSettingsForm.reset()
- }}
- disabled={disabled || !canUpdateGitHubConnection}
- >
- Cancel
-
- )}
-
- {connection ? 'Save changes' : 'Enable integration'}
-
-
-
+ (
+
+
+
+
+
+ )}
+ />
+
+ (
+
+
+ field.onChange(val)}
+ disabled={
+ !hasAccessToBranching ||
+ !newBranchPerPr ||
+ !canUpdateGitHubConnection
+ }
+ />
+
+
+ )}
+ />
+
+
+
+
+
+ {connection && (
+
+ Disable integration
+
+ )}
+
+
+ {githubSettingsForm.formState.isDirty && (
+ githubSettingsForm.reset()}
+ disabled={!canUpdateGitHubConnection}
+ >
+ Cancel
+
+ )}
+
+ {connection ? 'Save changes' : 'Enable integration'}
+
+
+
+
+ )}
+
@@ -801,5 +832,3 @@ const GitHubIntegrationConnectionForm = ({
>
)
}
-
-export default GitHubIntegrationConnectionForm
diff --git a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GithubSection.tsx b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GithubSection.tsx
index 9ce834daa0b..4d87acfc1be 100644
--- a/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GithubSection.tsx
+++ b/apps/studio/components/interfaces/Settings/Integrations/GithubIntegration/GithubSection.tsx
@@ -1,22 +1,22 @@
import { PermissionAction } from '@supabase/shared-types/out/constants'
+import { IS_PLATFORM, useFlag, useParams } from 'common'
import { useMemo } from 'react'
+import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
-import { useGitHubAuthorizationQuery } from '@/data/integrations/github-authorization-query'
-import { useParams } from 'common'
+import { GitHubIntegrationConnectionForm } from './GitHubIntegrationConnectionForm'
import {
ScaffoldContainer,
ScaffoldSection,
ScaffoldSectionContent,
ScaffoldSectionDetail,
-} from 'components/layouts/Scaffold'
-import NoPermission from 'components/ui/NoPermission'
-import { UpgradeToPro } from 'components/ui/UpgradeToPro'
-import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query'
-import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions'
-import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization'
-import { BASE_PATH, IS_PLATFORM } from 'lib/constants'
-import { GenericSkeletonLoader } from 'ui-patterns'
-import GitHubIntegrationConnectionForm from './GitHubIntegrationConnectionForm'
+} from '@/components/layouts/Scaffold'
+import NoPermission from '@/components/ui/NoPermission'
+import { UpgradeToPro } from '@/components/ui/UpgradeToPro'
+import { useGitHubAuthorizationQuery } from '@/data/integrations/github-authorization-query'
+import { useGitHubConnectionsQuery } from '@/data/integrations/github-connections-query'
+import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
+import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
+import { BASE_PATH } from '@/lib/constants'
const IntegrationImageHandler = ({ title }: { title: 'vercel' | 'github' }) => {
return (
@@ -37,6 +37,7 @@ export const GitHubSection = () => {
const isProPlanAndUp = organization?.plan?.id !== 'free'
const promptProPlanUpgrade = IS_PLATFORM && !isProPlanAndUp
+ const isGithubOnFreePlan = useFlag('githubOnFreePlan')
const { data: gitHubAuthorization } = useGitHubAuthorizationQuery()
@@ -74,7 +75,7 @@ export const GitHubSection = () => {
branches for every pull request.
- {promptProPlanUpgrade && (
+ {!isGithubOnFreePlan && promptProPlanUpgrade && (
{
)}
- {/* [Joshen] Show connection form if GH has already been authorized OR no GH authorization but on a paid plan */}
- {/* So this shouldn't render if there's no GH authorization and on a free plan */}
- {(!!gitHubAuthorization || !promptProPlanUpgrade) && (
+ {/* [Joshen] Show connection form only if one of the following is true:
+ - GH has already been authorized OR
+ - no GH authorization but on a paid plan OR
+ - on a free plan but the GitHub integration flag is enabled
+ */}
+ {(!!gitHubAuthorization || !promptProPlanUpgrade || isGithubOnFreePlan) && (
void
}
-export function ProjectBranchSelectorTrigger({
- displayProjectName,
- selectedOrgInitial,
- isBranch,
- branchDisplayName,
- onClick,
-}: ProjectBranchSelectorTriggerProps) {
- return (
-
-
- {selectedOrgInitial}
-
-
-
- {displayProjectName}
+export const ProjectBranchSelectorTrigger = forwardRef<
+ HTMLButtonElement,
+ ProjectBranchSelectorTriggerProps
+>(
+ (
+ {
+ displayProjectName,
+ selectedOrgInitial,
+ isBranch,
+ branchDisplayName,
+ onClick,
+ }: ProjectBranchSelectorTriggerProps,
+ ref
+ ) => {
+ return (
+
+
+ {selectedOrgInitial}
-
-
-
{branchDisplayName}
+
+
+ {displayProjectName}
+
+
+
+ {branchDisplayName}
+
-
-
-
- )
-}
+
+
+ )
+ }
+)
+
+ProjectBranchSelectorTrigger.displayName = 'ProjectBranchSelectorTrigger'
diff --git a/apps/studio/components/ui/UpgradeToPro.tsx b/apps/studio/components/ui/UpgradeToPro.tsx
index e7059d417d9..c7e8b5ebf28 100644
--- a/apps/studio/components/ui/UpgradeToPro.tsx
+++ b/apps/studio/components/ui/UpgradeToPro.tsx
@@ -1,7 +1,8 @@
import { ReactNode } from 'react'
-
import { cn } from 'ui'
import { Admonition } from 'ui-patterns'
+
+import { DocsButton } from './DocsButton'
import { UpgradePlanButton } from './UpgradePlanButton'
interface UpgradeToProProps {
@@ -21,6 +22,7 @@ interface UpgradeToProProps {
layout?: 'vertical' | 'horizontal'
variant?: 'default' | 'primary'
className?: string
+ docsUrl?: string
}
export const UpgradeToPro = ({
@@ -37,6 +39,7 @@ export const UpgradeToPro = ({
layout = 'horizontal',
variant = 'primary',
className,
+ docsUrl,
}: UpgradeToProProps) => {
return (
- {buttonText}
-
+ <>
+
+ {buttonText}
+
+ {!!docsUrl && }
+ >
}
/>
)