import { zodResolver } from '@hookform/resolvers/zod' import { useParams } from 'common' import { last } from 'lodash' import Link from 'next/link' import { useEffect, useState } from 'react' import { useForm } from 'react-hook-form' import toast from 'react-hot-toast' import * as z from 'zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import SidePanelGitHubRepoLinker from 'components/interfaces/Organization/IntegrationSettings/SidePanelGitHubRepoLinker' import AlertError from 'components/ui/AlertError' import { GenericSkeletonLoader } from 'components/ui/ShimmeringLoader' import { useBranchCreateMutation } from 'data/branches/branch-create-mutation' import { useProjectUpgradeEligibilityQuery } from 'data/config/project-upgrade-eligibility-query' import { useCheckGithubBranchValidity } from 'data/integrations/github-branch-check-query' import { useGitHubConnectionsQuery } from 'data/integrations/github-connections-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useProjectAddonsQuery } from 'data/subscriptions/project-addons-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { DollarSign, FileText } from 'lucide-react' import { useAppStateSnapshot } from 'state/app-state' import { Button, Form_Shadcn_, IconExternalLink, IconGitBranch, Modal } from 'ui' import BranchingPITRNotice from './BranchingPITRNotice' import BranchingPlanNotice from './BranchingPlanNotice' import BranchingPostgresVersionNotice from './BranchingPostgresVersionNotice' import GithubRepositorySelection from './GithubRepositorySelection' const EnableBranchingModal = () => { const { ref } = useParams() const snap = useAppStateSnapshot() const selectedOrg = useSelectedOrganization() // [Joshen] There's something weird with RHF that I can't figure out atm // but calling form.formState.isValid somehow removes the onBlur check, // and makes the validation run onChange instead. This is a workaround const [isValid, setIsValid] = useState(false) const canCreateBranches = useCheckPermissions(PermissionAction.CREATE, 'preview_branches') const { data: connections, error: connectionsError, isLoading: isLoadingConnections, isSuccess: isSuccessConnections, isError: isErrorConnections, } = useGitHubConnectionsQuery({ organizationId: selectedOrg?.id }) const { data, error: upgradeEligibilityError, isLoading: isLoadingUpgradeEligibility, isError: isErrorUpgradeEligibility, isSuccess: isSuccessUpgradeEligibility, } = useProjectUpgradeEligibilityQuery({ projectRef: ref, }) const hasMinimumPgVersion = Number(last(data?.current_app_version.split('-') ?? [])?.split('.')[0] ?? 0) >= 15 const { data: subscription } = useOrgSubscriptionQuery({ orgSlug: selectedOrg?.slug }) const isFreePlan = subscription?.plan.id === 'free' const { data: addons } = useProjectAddonsQuery({ projectRef: ref }) const hasPitrEnabled = (addons?.selected_addons ?? []).find((addon) => addon.type === 'pitr') !== undefined const githubConnection = connections?.find((connection) => connection.project.ref === ref) const [repoOwner, repoName] = githubConnection?.repository.name.split('/') ?? [] const { mutateAsync: checkGithubBranchValidity, isLoading: isChecking } = useCheckGithubBranchValidity({ onError: () => {} }) const { mutate: createBranch, isLoading: isCreating } = useBranchCreateMutation({ onSuccess: () => { toast.success(`Successfully created new branch`) snap.setShowEnableBranchingModal(false) }, }) const formId = 'enable-branching-form' const FormSchema = z.object({ branchName: z .string() .refine((val) => val.length > 1, `Please enter a branch name from ${repoOwner}/${repoName}`) .refine(async (val) => { try { if (val.length > 0) { if (!githubConnection?.id) { throw new Error('No GitHub connection found') } await checkGithubBranchValidity({ connectionId: githubConnection.id, branchName: val, }) setIsValid(true) } return true } catch (error) { setIsValid(false) return false } }, `Unable to find branch from ${repoOwner}/${repoName}`), }) const form = useForm>({ mode: 'onBlur', reValidateMode: 'onChange', resolver: zodResolver(FormSchema), defaultValues: { branchName: '' }, }) const isLoading = isLoadingConnections || isLoadingUpgradeEligibility const isError = isErrorConnections || isErrorUpgradeEligibility const isSuccess = isSuccessConnections && isSuccessUpgradeEligibility const canSubmit = form.getValues('branchName').length > 0 && !isChecking && isValid const onSubmit = (data: z.infer) => { if (!ref) return console.error('Project ref is required') createBranch({ projectRef: ref, branchName: data.branchName, gitBranch: data.branchName }) } useEffect(() => { if (form && snap.showEnableBranchingModal) { setIsValid(false) form.reset() } }, [form, snap.showEnableBranchingModal]) return ( <> snap.setShowEnableBranchingModal(false)} className="block" size="medium" hideClose >
setIsValid(false)} >

Enable database branching

Manage environments in Supabase

{isLoading && ( <> )} {isError && ( <> {isErrorConnections ? ( ) : isErrorUpgradeEligibility ? ( ) : null} )} {isSuccess && ( <> {isFreePlan ? ( ) : !hasMinimumPgVersion ? ( ) : ( <> {!hasPitrEnabled && } )}

Please keep in mind the following:

Preview branches are billed $0.32 per day (approximately $10 per month)

Launching a new preview branch incurs additional compute costs at $0.32 per day. This cost will continue for as long as the branch has not been removed. This pricing is for Early Access and is subject to change.

Branching uses your GitHub repository to apply migrations

Database migrations are handled via the{' '} ./supabase directory in your GitHub repo. Migration files will run on both Preview Branches and Production when pushing to and merging git branches.

)}
) } export default EnableBranchingModal