import { PermissionAction } from '@supabase/shared-types/out/constants' import { useQueryClient } from '@tanstack/react-query' import dayjs from 'dayjs' import { Download, PauseCircle } from 'lucide-react' import Link from 'next/link' import { useState } from 'react' import toast from 'react-hot-toast' import { useParams } from 'common' import AlertError from 'components/ui/AlertError' import { ButtonTooltip } from 'components/ui/ButtonTooltip' import { useBackupDownloadMutation } from 'data/database/backup-download-mutation' import { useFreeProjectLimitCheckQuery } from 'data/organizations/free-project-limit-check-query' import { useProjectPauseStatusQuery } from 'data/projects/project-pause-status-query' import { useProjectRestoreMutation } from 'data/projects/project-restore-mutation' import { setProjectStatus } from 'data/projects/projects-query' import { useOrgSubscriptionQuery } from 'data/subscriptions/org-subscription-query' import { useCheckPermissions } from 'hooks/misc/useCheckPermissions' import { useSelectedOrganization } from 'hooks/misc/useSelectedOrganization' import { useFlag } from 'hooks/ui/useFlag' import { PROJECT_STATUS } from 'lib/constants' import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, Modal, TooltipContent_Shadcn_, TooltipTrigger_Shadcn_, Tooltip_Shadcn_, } from 'ui' import { GenericSkeletonLoader } from 'ui-patterns' import ConfirmModal from 'ui-patterns/Dialogs/ConfirmDialog' import { WarningIcon } from 'ui' import { useProjectContext } from './ProjectContext' import { RestorePaidPlanProjectNotice } from './RestorePaidPlanProjectNotice' export interface ProjectPausedStateProps { product?: string } const ProjectPausedState = ({ product }: ProjectPausedStateProps) => { const { ref } = useParams() const queryClient = useQueryClient() const { project } = useProjectContext() const selectedOrganization = useSelectedOrganization() const enforceNinetyDayUnpauseExpiry = useFlag('enforceNinetyDayUnpauseExpiry') const orgSlug = selectedOrganization?.slug const { data: subscription } = useOrgSubscriptionQuery({ orgSlug }) const { data: pauseStatus, error: pauseStatusError, isError, isSuccess, isLoading, } = useProjectPauseStatusQuery( { ref }, { enabled: project?.status === PROJECT_STATUS.INACTIVE && enforceNinetyDayUnpauseExpiry, } ) const finalDaysRemainingBeforeRestoreDisabled = pauseStatus?.remaining_days_till_restore_disabled ?? pauseStatus?.max_days_till_restore_disabled ?? 0 const isFreePlan = subscription?.plan?.id === 'free' const isRestoreDisabled = enforceNinetyDayUnpauseExpiry && isSuccess && !pauseStatus.can_restore const latestBackup = pauseStatus?.latest_downloadable_backup_id 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 } = useProjectRestoreMutation({ onSuccess: (_, variables) => { setProjectStatus(queryClient, variables.ref, PROJECT_STATUS.RESTORING) toast.success('Restoring project') }, }) const { mutate: downloadBackup, isLoading: isDownloading } = useBackupDownloadMutation({ onSuccess: (res) => { const { fileUrl } = res // Trigger browser download by create,trigger and remove tempLink const tempLink = document.createElement('a') tempLink.href = fileUrl document.body.appendChild(tempLink) tempLink.click() document.body.removeChild(tempLink) }, }) const canResumeProject = useCheckPermissions( 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 = () => { if (!project) { return toast.error('Unable to restore: project is required') } restoreProject({ ref: project.ref }) } const onSelectDownloadBackup = () => { if (ref === undefined) return console.error('Project ref is required') if (!latestBackup) return toast.error('No backups available for download') downloadBackup({ ref, backup: { id: latestBackup, // [Joshen] Just FYI these params aren't required for the download backup request // API types need to be updated project_id: -1, inserted_at: '', isPhysicalBackup: false, status: {}, }, }) } return ( <>

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

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

{enforceNinetyDayUnpauseExpiry && ( <> {isLoading && } {isError && ( )} {isSuccess && ( <> {isRestoreDisabled ? ( Project cannot be restored through the dashboard This project has been paused for over{' '} {pauseStatus?.max_days_till_restore_disabled ?? 90} days {' '} and cannot be restored through the dashboard. However, your data remains intact and can be downloaded as a backup. {!latestBackup && ( No backups available, please reach out via support for assistance )} ) : isFreePlan ? ( <>

To prevent future pauses, consider upgrading to Pro.

Project can be restored through the dashboard within the next{' '} {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 . The latest that your project can be restored is by{' '} {dayjs() .utc() .add(pauseStatus.max_days_till_restore_disabled ?? 0, 'day') .format('DD MMM YYYY')} . However, your database backup will still be available for download thereafter. ) : ( )} )} )} {!enforceNinetyDayUnpauseExpiry && !isFreePlan && }
{(!enforceNinetyDayUnpauseExpiry || (isSuccess && !isRestoreDisabled)) && (
Restore project {isFreePlan ? ( ) : ( )}
)}
setShowConfirmRestore(false)} onSelectConfirm={onConfirmRestore} /> setShowFreeProjectLimitWarning(false)} >

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 unpause this project.

) } export default ProjectPausedState