PauseProjectButton.tsx124 lines · main
| 1 | import { PermissionAction } from '@supabase/shared-types/out/constants' |
| 2 | import { CirclePause } from 'lucide-react' |
| 3 | import { useRouter } from 'next/router' |
| 4 | import { useState } from 'react' |
| 5 | import { toast } from 'sonner' |
| 6 | import { |
| 7 | AlertDialog, |
| 8 | AlertDialogAction, |
| 9 | AlertDialogCancel, |
| 10 | AlertDialogContent, |
| 11 | AlertDialogDescription, |
| 12 | AlertDialogFooter, |
| 13 | AlertDialogHeader, |
| 14 | AlertDialogTitle, |
| 15 | } from 'ui' |
| 16 | |
| 17 | import { ButtonTooltip } from '@/components/ui/ButtonTooltip' |
| 18 | import { useSetProjectStatus } from '@/data/projects/project-detail-query' |
| 19 | import { useProjectPauseMutation } from '@/data/projects/project-pause-mutation' |
| 20 | import { useCheckEntitlements } from '@/hooks/misc/useCheckEntitlements' |
| 21 | import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions' |
| 22 | import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization' |
| 23 | import { useIsProjectActive, useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject' |
| 24 | import { PROJECT_STATUS } from '@/lib/constants' |
| 25 | |
| 26 | const PauseProjectButton = () => { |
| 27 | const router = useRouter() |
| 28 | const { data: project } = useSelectedProjectQuery() |
| 29 | const { data: organization } = useSelectedOrganizationQuery() |
| 30 | const { setProjectStatus } = useSetProjectStatus() |
| 31 | |
| 32 | const isProjectActive = useIsProjectActive() |
| 33 | const isProjectUnhealthy = project?.status === PROJECT_STATUS.ACTIVE_UNHEALTHY |
| 34 | const [isModalOpen, setIsModalOpen] = useState(false) |
| 35 | |
| 36 | const projectRef = project?.ref ?? '' |
| 37 | const isPaused = project?.status === PROJECT_STATUS.INACTIVE |
| 38 | const { can: canPauseProject } = useAsyncCheckPermissions( |
| 39 | PermissionAction.INFRA_EXECUTE, |
| 40 | 'queue_jobs.projects.pause' |
| 41 | ) |
| 42 | |
| 43 | const isFreePlan = organization?.plan.id === 'free' |
| 44 | const isBranch = Boolean(project?.parent_project_ref) |
| 45 | const { hasAccess: projectPausingAllowedInOrg } = useCheckEntitlements( |
| 46 | 'project_pausing', |
| 47 | organization?.slug |
| 48 | ) |
| 49 | |
| 50 | const { mutate: pauseProject, isPending: isPausing } = useProjectPauseMutation({ |
| 51 | onSuccess: (_, variables) => { |
| 52 | setProjectStatus({ ref: variables.ref, status: PROJECT_STATUS.PAUSING }) |
| 53 | toast.success('Pausing project...') |
| 54 | router.push(`/project/${projectRef}`) |
| 55 | }, |
| 56 | }) |
| 57 | |
| 58 | const requestPauseProject = () => { |
| 59 | if (!canPauseProject) { |
| 60 | return toast.error('You do not have the required permissions to pause this project') |
| 61 | } |
| 62 | pauseProject({ ref: projectRef }) |
| 63 | } |
| 64 | |
| 65 | const buttonDisabled = |
| 66 | isBranch || |
| 67 | !projectPausingAllowedInOrg || |
| 68 | project === undefined || |
| 69 | isPaused || |
| 70 | !canPauseProject || |
| 71 | !isProjectActive |
| 72 | |
| 73 | function getTooltipText() { |
| 74 | if (isPaused) return 'Your project is already paused' |
| 75 | if (!canPauseProject) return 'You need additional permissions to pause this project' |
| 76 | if (isProjectUnhealthy) |
| 77 | return 'Your project is unhealthy — restart it instead to restore normal operation' |
| 78 | if (!isProjectActive) return 'Unable to pause project as project is not active' |
| 79 | if (isBranch) return 'Branch projects cannot be paused' |
| 80 | if (!projectPausingAllowedInOrg && !isFreePlan) |
| 81 | return 'Projects on a paid plan will always be running' |
| 82 | return undefined |
| 83 | } |
| 84 | |
| 85 | return ( |
| 86 | <> |
| 87 | <ButtonTooltip |
| 88 | type="default" |
| 89 | icon={<CirclePause />} |
| 90 | onClick={() => setIsModalOpen(true)} |
| 91 | loading={isPausing} |
| 92 | disabled={buttonDisabled} |
| 93 | tooltip={{ |
| 94 | content: { |
| 95 | side: 'bottom', |
| 96 | text: getTooltipText(), |
| 97 | }, |
| 98 | }} |
| 99 | > |
| 100 | Pause project |
| 101 | </ButtonTooltip> |
| 102 | |
| 103 | <AlertDialog open={isModalOpen} onOpenChange={setIsModalOpen}> |
| 104 | <AlertDialogContent> |
| 105 | <AlertDialogHeader> |
| 106 | <AlertDialogTitle>Pause project?</AlertDialogTitle> |
| 107 | <AlertDialogDescription> |
| 108 | This project will be unavailable while paused. Paused projects can be resumed for 90 |
| 109 | days. After that, backups remain available to download. |
| 110 | </AlertDialogDescription> |
| 111 | </AlertDialogHeader> |
| 112 | <AlertDialogFooter> |
| 113 | <AlertDialogCancel disabled={isPausing}>Cancel</AlertDialogCancel> |
| 114 | <AlertDialogAction disabled={isPausing} onClick={requestPauseProject} variant="danger"> |
| 115 | {isPausing ? 'Pausing project...' : 'Pause project'} |
| 116 | </AlertDialogAction> |
| 117 | </AlertDialogFooter> |
| 118 | </AlertDialogContent> |
| 119 | </AlertDialog> |
| 120 | </> |
| 121 | ) |
| 122 | } |
| 123 | |
| 124 | export default PauseProjectButton |