import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import { partition } from 'lodash' import { Clock, ExternalLink, Infinity, MoreVertical, Pencil, RefreshCw, Shield, Trash2, } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' import { toast } from 'sonner' import { useParams } from 'common' import { useIsBranching2Enabled } from 'components/interfaces/App/FeaturePreview/FeaturePreviewContext' import { DropdownMenuItemTooltip } from 'components/ui/DropdownMenuItemTooltip' import { useBranchQuery } from 'data/branches/branch-query' import { useBranchResetMutation } from 'data/branches/branch-reset-mutation' import { useBranchUpdateMutation } from 'data/branches/branch-update-mutation' import type { Branch } from 'data/branches/branches-query' import { branchKeys } from 'data/branches/keys' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { Button, DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from 'ui' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import TextConfirmModal from 'ui-patterns/Dialogs/TextConfirmModal' import { BranchLoader, BranchManagementSection, BranchRow, BranchRowLoader } from './BranchPanels' import { EditBranchModal } from './EditBranchModal' import { PreviewBranchesEmptyState } from './EmptyStates' interface OverviewProps { isLoading: boolean isSuccess: boolean repo: string mainBranch: Branch previewBranches: Branch[] onSelectCreateBranch: () => void onSelectDeleteBranch: (branch: Branch) => void generateCreatePullRequestURL: (branchName?: string) => string } export const Overview = ({ isLoading, isSuccess, repo, mainBranch, previewBranches, onSelectCreateBranch, onSelectDeleteBranch, generateCreatePullRequestURL, }: OverviewProps) => { const [persistentBranches, ephemeralBranches] = partition( previewBranches, (branch) => branch.persistent ) const { ref: projectRef } = useParams() return ( <> {isLoading && } {isSuccess && mainBranch !== undefined && ( {mainBranch.name} } repo={repo} rowActions={} /> )} {isSuccess && mainBranch === undefined && ( main )} {/* Persistent Branches Section */} {isLoading && } {isSuccess && persistentBranches.length === 0 && ( No persistent branches Persistent branches are long-lived, cannot be reset, and are ideal for staging environments. )} {isSuccess && persistentBranches.map((branch) => { return ( onSelectDeleteBranch(branch)} generateCreatePullRequestURL={generateCreatePullRequestURL} /> } /> ) })} {/* Ephemeral/Preview Branches Section */} {isLoading && } {isSuccess && ephemeralBranches.length === 0 && ( )} {isSuccess && ephemeralBranches.map((branch) => { return ( onSelectDeleteBranch(branch)} generateCreatePullRequestURL={generateCreatePullRequestURL} /> } /> ) })} > ) } // Row actions for preview branches (non-main) const PreviewBranchActions = ({ branch, onSelectDeleteBranch, generateCreatePullRequestURL, }: { branch: Branch repo: string onSelectDeleteBranch: () => void generateCreatePullRequestURL: (branchName?: string) => string }) => { const gitlessBranching = useIsBranching2Enabled() const queryClient = useQueryClient() const projectRef = branch.parent_project_ref ?? branch.project_ref const { can: canDeleteBranches } = useAsyncCheckProjectPermissions( PermissionAction.DELETE, 'preview_branches' ) const { can: canUpdateBranches } = useAsyncCheckProjectPermissions( PermissionAction.UPDATE, 'preview_branches' ) const { data } = useBranchQuery({ projectRef, id: branch.id }) const isBranchActiveHealthy = data?.status === 'ACTIVE_HEALTHY' const [showConfirmResetModal, setShowConfirmResetModal] = useState(false) const [showBranchModeSwitch, setShowBranchModeSwitch] = useState(false) const [showEditBranchModal, setShowEditBranchModal] = useState(false) const { mutate: resetBranch, isLoading: isResetting } = useBranchResetMutation({ onSuccess() { toast.success('Success! Please allow a few seconds for the branch to reset.') setShowConfirmResetModal(false) }, }) const { mutate: updateBranch, isLoading: isUpdatingBranch } = useBranchUpdateMutation({ onSuccess() { toast.success('Successfully updated branch') setShowBranchModeSwitch(false) if (projectRef) { queryClient.invalidateQueries({ queryKey: branchKeys.list(projectRef) }) } }, }) const onConfirmReset = () => { if (!projectRef) return resetBranch({ id: branch.id, projectRef }) } const onTogglePersistent = () => { if (!projectRef) return updateBranch({ id: branch.id, projectRef, persistent: !branch.persistent }) } return ( <> } className="px-1" onClick={(e) => e.stopPropagation()} /> { e.stopPropagation() setShowConfirmResetModal(true) }} onClick={(e) => { e.stopPropagation() setShowConfirmResetModal(true) }} tooltip={{ content: { side: 'left', text: !isBranchActiveHealthy ? 'Branch is still initializing. Please wait for it to become healthy before resetting.' : undefined, }, }} > Reset branch { e.stopPropagation() setShowBranchModeSwitch(true) }} onClick={(e) => { e.stopPropagation() setShowBranchModeSwitch(true) }} tooltip={{ content: { side: 'left', text: !isBranchActiveHealthy ? 'Branch is still initializing. Please wait for it to become healthy before switching.' : undefined, }, }} > {branch.persistent ? ( <> Switch to preview > ) : ( <> Switch to persistent > )} {/* Edit Branch (gitless) */} {gitlessBranching && ( { e.stopPropagation() setShowEditBranchModal(true) }} onClick={(e) => { e.stopPropagation() setShowEditBranchModal(true) }} tooltip={{ content: { side: 'left', text: !canUpdateBranches ? 'You need additional permissions to edit branches' : !isBranchActiveHealthy ? 'Branch is still initializing. Please wait for it to become healthy before editing.' : undefined, }, }} > Edit branch )} { e.stopPropagation() onSelectDeleteBranch() }} onClick={(e) => { e.stopPropagation() onSelectDeleteBranch() }} tooltip={{ content: { side: 'left', text: !canDeleteBranches ? 'You need additional permissions to delete branches' : undefined, }, }} > Delete branch {/* Create PR if applicable */} {branch.git_branch && branch.pr_number === undefined && ( e.stopPropagation()} > Create pull request )} setShowConfirmResetModal(false)} onConfirm={onConfirmReset} loading={isResetting} title="Reset branch" confirmLabel="Reset branch" confirmPlaceholder="Type in name of branch" confirmString={branch?.name ?? ''} alert={{ title: `Are you sure you want to reset the "${branch.name}" branch? All data will be deleted.`, }} /> setShowBranchModeSwitch(false)} onConfirm={onTogglePersistent} > Are you sure you want to switch the branch "{branch.name}" to{' '} {branch.persistent ? 'preview' : 'persistent'}? setShowEditBranchModal(false)} /> > ) } // Actions for main (production) branch const MainBranchActions = ({ branch, repo }: { branch: Branch; repo: string }) => { const { ref: projectRef } = useParams() const { can: canUpdateBranches } = useAsyncCheckProjectPermissions( PermissionAction.UPDATE, 'preview_branches' ) const [showEditBranchModal, setShowEditBranchModal] = useState(false) return ( <> } className="px-1" /> {repo ? ( Change production branch ) : ( setShowEditBranchModal(true)} onClick={() => setShowEditBranchModal(true)} > Edit Branch )} setShowEditBranchModal(false)} /> > ) }
No persistent branches
Persistent branches are long-lived, cannot be reset, and are ideal for staging environments.
Are you sure you want to switch the branch "{branch.name}" to{' '} {branch.persistent ? 'preview' : 'persistent'}?