Files
supabase/apps/studio/components/interfaces/Settings/General/Infrastructure/PauseProjectButton.tsx
7b02aa2f0d fix: allow pausing branch projects again (#47636)
## I have read the
[CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md)
file.

YES

## What kind of change does this PR introduce?

Bug fix (regression).

## What is the current behavior?

#44172 replaced the old `isAwsK8s`-based check with an entitlement-based
check (`useCheckEntitlements('project_pausing', ...)`) for the pause
project button. In doing so, it also introduced an explicit `isBranch`
condition that unconditionally disables the pause button and shows
"Branch projects cannot be paused" for any branch project.

Prior to #44172, branches were exempt from the paid-plan pause
restriction entirely — the old `isPaidAndNotAwsK8s` computation
(`!isBranch && !isFreePlan && !isAwsK8s`) evaluated to `false` whenever
`isBranch` was `true`, so branch projects were never blocked by that
check. The new code changed this exemption into a hard block, which is
the regression: branches can no longer be paused at all.

## What is the new behavior?

Restore the original exemption: branch projects bypass the
`project_pausing` entitlement check (as they did the old AWS K8s check),
instead of being unconditionally blocked. The pause button and tooltip
for branches now only take into account permissions, project status, and
whether the project is active — matching pre-#44172 behavior.

```tsx
const buttonDisabled =
  (!isBranch && !projectPausingAllowedInOrg) ||
  project === undefined ||
  isPaused ||
  !canPauseProject ||
  !isProjectActive
```

## Additional context

Regression introduced in
https://github.com/supabase/supabase/pull/44172.


---
_Generated by [Claude
Code](https://claude.ai/code/session_01VdtDMaXzz8zgqQHSMbtMAA)_

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

## Summary by CodeRabbit

* **Bug Fixes**
* Updated project pause availability so the button now reflects the
correct conditions for branch and non-branch projects.
* Improved pause-related tooltips to show more accurate messaging based
on plan type and project status, including when pausing may not be
available on free plans.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Ivan Vasilov <vasilov.ivan@gmail.com>
2026-07-06 23:08:31 +08:00

123 lines
4.3 KiB
TypeScript

import { PermissionAction } from '@supabase/shared-types/out/constants'
import { CirclePause } from 'lucide-react'
import { useRouter } from 'next/router'
import { useState } from 'react'
import { toast } from 'sonner'
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from 'ui'
import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
import { useSetProjectStatus } from '@/data/projects/project-detail-query'
import { useProjectPauseMutation } from '@/data/projects/project-pause-mutation'
import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements'
import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
import { useIsProjectActive, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
import { PROJECT_STATUS } from '@/lib/constants'
const PauseProjectButton = () => {
const router = useRouter()
const { data: project } = useSelectedProjectQuery()
const { data: organization } = useSelectedOrganizationQuery()
const { setProjectStatus } = useSetProjectStatus()
const isProjectActive = useIsProjectActive()
const isProjectUnhealthy = project?.status === PROJECT_STATUS.ACTIVE_UNHEALTHY
const [isModalOpen, setIsModalOpen] = useState(false)
const projectRef = project?.ref ?? ''
const isPaused = project?.status === PROJECT_STATUS.INACTIVE
const { can: canPauseProject } = useAsyncCheckPermissions(
PermissionAction.INFRA_EXECUTE,
'queue_jobs.projects.pause'
)
const isFreePlan = organization?.plan.id === 'free'
const isBranch = Boolean(project?.parent_project_ref)
const { hasAccess: projectPausingAllowedInOrg } = useCheckEntitlements(
'project_pausing',
organization?.slug
)
const { mutate: pauseProject, isPending: isPausing } = useProjectPauseMutation({
onSuccess: (_, variables) => {
setProjectStatus({ ref: variables.ref, status: PROJECT_STATUS.PAUSING })
toast.success('Pausing project...')
router.push(`/project/${projectRef}`)
},
})
const requestPauseProject = () => {
if (!canPauseProject) {
return toast.error('You do not have the required permissions to pause this project')
}
pauseProject({ ref: projectRef })
}
const buttonDisabled =
(!isBranch && !projectPausingAllowedInOrg) ||
project === undefined ||
isPaused ||
!canPauseProject ||
!isProjectActive
function getTooltipText() {
if (isPaused) return 'Your project is already paused'
if (!canPauseProject) return 'You need additional permissions to pause this project'
if (isProjectUnhealthy)
return 'Your project is unhealthy — restart it instead to restore normal operation'
if (!isProjectActive) return 'Unable to pause project as project is not active'
if (!isBranch && !projectPausingAllowedInOrg && !isFreePlan)
return 'Projects on a paid plan will always be running'
return undefined
}
return (
<>
<ButtonTooltip
variant="default"
icon={<CirclePause />}
onClick={() => setIsModalOpen(true)}
loading={isPausing}
disabled={buttonDisabled}
tooltip={{
content: {
side: 'bottom',
text: getTooltipText(),
},
}}
>
Pause project
</ButtonTooltip>
<AlertDialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>Pause project?</AlertDialogTitle>
<AlertDialogDescription>
This project will be unavailable while paused. Paused projects can be resumed for 90
days. After that, backups remain available to download.
</AlertDialogDescription>
</AlertDialogHeader>
<AlertDialogFooter>
<AlertDialogCancel disabled={isPausing}>Cancel</AlertDialogCancel>
<AlertDialogAction disabled={isPausing} onClick={requestPauseProject} variant="danger">
{isPausing ? 'Pausing project...' : 'Pause project'}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
</>
)
}
export default PauseProjectButton