import { zodResolver } from '@hookform/resolvers/zod' import { PermissionAction } from '@supabase/shared-types/out/constants' import dayjs from 'dayjs' import { PauseCircle } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' import { useForm } from 'react-hook-form' import { toast } from 'sonner' import { z } from 'zod' import { useFlag, useParams } from 'common' import { extractPostgresVersionDetails, PostgresVersionSelector, } from 'components/interfaces/ProjectCreation/PostgresVersionSelector' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { InlineLinkClassName } from 'components/ui/InlineLink' import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-limit-check-query' import { useSetProjectStatus } from 'data/projects/project-detail-query' import { useProjectPauseStatusQuery } from 'data/projects/project-pause-status-query' import { useProjectRestoreMutation } from 'data/projects/project-restore-mutation' import { useAsyncCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganizationQuery } from 'hooks/misc/useSelectedOrganization' import { useSelectedProjectQuery } from 'hooks/misc/useSelectedProject' import { usePHFlag } from 'hooks/ui/useFlag' import { PROJECT_STATUS } from 'lib/constants' import { AWS_REGIONS, CloudProvider } from 'shared-data' import { Button, Card, CardContent, CardFooter, cn, Dialog, DialogContent, DialogFooter, DialogHeader, DialogSection, DialogTitle, Form_Shadcn_, FormField_Shadcn_, Tooltip, TooltipContent, TooltipTrigger, } from 'ui' import { TimestampInfo } from 'ui-patterns' import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal' import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader' import { PauseDisabledState } from './PauseDisabledState' export interface ProjectPausedStateProps { product?: string } export const ProjectPausedState = ({ product }: ProjectPausedStateProps) => { const { ref } = useParams() const { data: project } = useSelectedProjectQuery() const { data: selectedOrganization } = useSelectedOrganizationQuery() const { setProjectStatus } = useSetProjectStatus() const showPostgresVersionSelector = useFlag('showPostgresVersionSelector') const enableProBenefitWording = usePHFlag('proBenefitWording') const region = Object.values(AWS_REGIONS).find((x) => x.code === project?.region) const orgSlug = selectedOrganization?.slug const { data: pauseStatus, error: pauseStatusError, isError, isSuccess: isPauseStatusSuccess, isPending: isLoading, } = useProjectPauseStatusQuery({ ref }, { enabled: project?.status === PROJECT_STATUS.INACTIVE }) const finalDaysRemainingBeforeRestoreDisabled = pauseStatus?.remaining_days_till_restore_disabled ?? pauseStatus?.max_days_till_restore_disabled ?? 0 const isFreePlan = selectedOrganization?.plan?.id === 'free' const isRestoreDisabled = isPauseStatusSuccess && !pauseStatus.can_restore const { data: membersExceededLimit } = useFreeProjectLimitCheckQuery( { slug: orgSlug }, { enabled: isFreePlan } ) const hasMembersExceedingFreeTierLimit = (membersExceededLimit || []).length > 0 const [showConfirmRestore, setShowConfirmRestore] = useState(false) const [showFreeProjectLimitWarning, setShowFreeProjectLimitWarning] = useState(false) const { mutate: restoreProject, isPending: isRestoring } = useProjectRestoreMutation({ onSuccess: (_, variables) => { setProjectStatus({ ref: variables.ref, status: PROJECT_STATUS.RESTORING }) toast.success('Restoring project, project will be ready in a few minutes') }, }) const { can: canResumeProject } = useAsyncCheckPermissions( PermissionAction.INFRA_EXECUTE, 'queue_jobs.projects.initialize_or_resume' ) const onSelectRestore = () => { if (!canResumeProject) { toast.error('You do not have the required permissions to restore this project') } else if (hasMembersExceedingFreeTierLimit) setShowFreeProjectLimitWarning(true) else setShowConfirmRestore(true) } const onConfirmRestore = async (values: z.infer) => { if (!project) { return toast.error('Unable to restore: project is required') } if (!showPostgresVersionSelector) { restoreProject({ ref: project.ref }) } else { const { postgresVersionSelection } = values const postgresVersionDetails = extractPostgresVersionDetails(postgresVersionSelection) restoreProject({ ref: project.ref, releaseChannel: postgresVersionDetails.releaseChannel, postgresEngine: postgresVersionDetails.postgresEngine, }) } } const FormSchema = z.object({ postgresVersionSelection: z.string(), }) const form = useForm>({ resolver: zodResolver(FormSchema), mode: 'onChange', defaultValues: { postgresVersionSelection: '' }, }) return ( <>

The project "{project?.name}" is currently paused

{isLoading && } {isPauseStatusSuccess && !isRestoreDisabled ? ( isFreePlan ? ( <>

All data, including backups and storage objects, remains safe. You can resume this project from the dashboard within{' '} {finalDaysRemainingBeforeRestoreDisabled} day {finalDaysRemainingBeforeRestoreDisabled > 1 ? 's' : ''} {' '} Free projects cannot be restored through the dashboard if they are paused for more than {pauseStatus.max_days_till_restore_disabled} days {' '} (until{' '} ). After that, this project will not be resumable, but data will still be available for download.

{enableProBenefitWording === 'variant-a' ? 'Upgrade to Pro to prevent pauses and unlock features like branching, compute upgrades, and daily backups.' : 'To prevent future pauses, consider upgrading to Pro.'}

{!!pauseStatus.last_paused_on && (

Project last paused on{' '}

)} ) : (

Your project data is safe but inaccessible while paused. Once resumed, usage will be billed by compute size and hours active.

) ) : !isLoading ? (

All of your project's data is still intact, but your project is inaccessible while paused.{' '} {product !== undefined ? ( <> Resume this project to access the{' '} {product} page. ) : !isRestoreDisabled ? ( 'Resume this project and get back to building!' ) : null}

) : null}
{isError && ( )} {isPauseStatusSuccess && !isRestoreDisabled && ( Resume project {isFreePlan ? ( ) : ( )} )} {isPauseStatusSuccess && isRestoreDisabled && }
setShowConfirmRestore(false)} onConfirm={() => form.handleSubmit(onConfirmRestore)()} loading={isRestoring} confirmLabel="Resume" confirmLabelLoading="Resuming" cancelLabel="Cancel" >

{isFreePlan ? 'Your project’s data will be restored to when it was initially paused.' : 'Your project’s data will be restored and billing will resume based on compute size and hours active.'}

{showPostgresVersionSelector && (
( )} />
)}
setShowFreeProjectLimitWarning(false)} > Your organization has members who have exceeded their free project limits

The following members have reached their maximum limits for the number of active free plan projects within organizations where they are an administrator or owner:

    {(membersExceededLimit || []).map((member, idx: number) => (
  • {member.username || member.primary_email} (Limit: {member.free_project_limit} free projects)
  • ))}

These members will need to either delete, pause, or upgrade one or more of these projects before you're able to resume this project.

) }