ProjectPausedState.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153
  1. import { useParams } from 'common'
  2. import dayjs from 'dayjs'
  3. import { PauseCircle } from 'lucide-react'
  4. import Link from 'next/link'
  5. import {
  6. Button,
  7. Card,
  8. CardContent,
  9. CardFooter,
  10. cn,
  11. Tooltip,
  12. TooltipContent,
  13. TooltipTrigger,
  14. } from 'ui'
  15. import { TimestampInfo } from 'ui-patterns'
  16. import { GenericSkeletonLoader } from 'ui-patterns/ShimmeringLoader'
  17. import { PauseDisabledState } from './PauseDisabledState'
  18. import { ResumeProjectButton } from '@/components/interfaces/Project/ResumeProjectButton'
  19. import { AlertError } from '@/components/ui/AlertError'
  20. import { InlineLinkClassName } from '@/components/ui/InlineLink'
  21. import { UpgradePlanButton } from '@/components/ui/UpgradePlanButton'
  22. import { useProjectPauseStatusQuery } from '@/data/projects/project-pause-status-query'
  23. import { useSelectedOrganizationQuery } from '@/hooks/misc/useSelectedOrganization'
  24. import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
  25. import { usePHFlag } from '@/hooks/ui/useFlag'
  26. import { PROJECT_STATUS } from '@/lib/constants'
  27. export interface ProjectPausedStateProps {
  28. product?: string
  29. }
  30. export const ProjectPausedState = ({ product }: ProjectPausedStateProps) => {
  31. const { ref } = useParams()
  32. const { data: project } = useSelectedProjectQuery()
  33. const { data: selectedOrganization } = useSelectedOrganizationQuery()
  34. const enableProBenefitWording = usePHFlag('proBenefitWording')
  35. const {
  36. data: pauseStatus,
  37. error: pauseStatusError,
  38. isError,
  39. isSuccess: isPauseStatusSuccess,
  40. isPending: isLoading,
  41. } = useProjectPauseStatusQuery({ ref }, { enabled: project?.status === PROJECT_STATUS.INACTIVE })
  42. const finalDaysRemainingBeforeRestoreDisabled =
  43. pauseStatus?.remaining_days_till_restore_disabled ??
  44. pauseStatus?.max_days_till_restore_disabled ??
  45. 0
  46. const isFreePlan = selectedOrganization?.plan?.id === 'free'
  47. const isRestoreDisabled = isPauseStatusSuccess && !pauseStatus.can_restore
  48. return (
  49. <Card className="w-full max-w-160 mx-auto">
  50. <CardContent>
  51. <PauseCircle size={48} strokeWidth={1} className="text-foreground-lighter shrink-0 mb-4" />
  52. <div className="flex-1">
  53. <div>
  54. <h2 className="mb-4">The project "{project?.name}" is currently paused</h2>
  55. <div className="text-foreground-light max-w-4xl">
  56. {isLoading && <GenericSkeletonLoader className="mt-3" />}
  57. {isPauseStatusSuccess && !isRestoreDisabled ? (
  58. isFreePlan ? (
  59. <>
  60. <p className="text-sm">
  61. All data, including backups and storage objects, remains safe. You can resume
  62. this project from the dashboard within{' '}
  63. <Tooltip>
  64. <TooltipTrigger>
  65. <span className={cn(InlineLinkClassName, 'text-foreground')}>
  66. {finalDaysRemainingBeforeRestoreDisabled} day
  67. {finalDaysRemainingBeforeRestoreDisabled > 1 ? 's' : ''}
  68. </span>{' '}
  69. </TooltipTrigger>
  70. <TooltipContent side="bottom" className="w-80 text-center">
  71. Free projects cannot be restored through the dashboard if they are paused
  72. for more than {pauseStatus.max_days_till_restore_disabled} days
  73. </TooltipContent>
  74. </Tooltip>{' '}
  75. (until{' '}
  76. <TimestampInfo
  77. displayAs="local"
  78. utcTimestamp={dayjs()
  79. .utc()
  80. .add(pauseStatus.remaining_days_till_restore_disabled ?? 0, 'day')
  81. .toISOString()}
  82. className="text-sm text-foreground"
  83. labelFormat="DD MMM YYYY"
  84. />
  85. ). After that, this project will not be resumable, but data will still be
  86. available for download.
  87. </p>
  88. <p className="text-sm mt-4">
  89. {enableProBenefitWording === 'variant-a'
  90. ? 'Upgrade to Pro to prevent pauses and unlock features like branching, compute upgrades, and daily backups.'
  91. : 'To prevent future pauses, consider upgrading to Pro.'}
  92. </p>
  93. </>
  94. ) : (
  95. <p className="text-sm">
  96. Your project data is safe but inaccessible while paused. Once resumed, usage
  97. will be billed by compute size and hours active.
  98. </p>
  99. )
  100. ) : !isLoading ? (
  101. <p className="text-sm">
  102. All of your project's data is still intact, but your project is inaccessible while
  103. paused.{' '}
  104. {product !== undefined ? (
  105. <>
  106. Resume this project to access the{' '}
  107. <span className="text-brand">{product}</span> page.
  108. </>
  109. ) : !isRestoreDisabled ? (
  110. 'Resume this project and get back to building!'
  111. ) : null}
  112. </p>
  113. ) : null}
  114. </div>
  115. </div>
  116. </div>
  117. </CardContent>
  118. {isError && (
  119. <AlertError
  120. className="rounded-none border-0"
  121. error={pauseStatusError}
  122. subject="Failed to retrieve pause status"
  123. />
  124. )}
  125. {isPauseStatusSuccess && !isRestoreDisabled && (
  126. <CardFooter className="flex flex-wrap justify-end items-center gap-2">
  127. <ResumeProjectButton size="tiny" type="default" />
  128. {isFreePlan ? (
  129. <UpgradePlanButton source="projectPausedStateRestore" />
  130. ) : (
  131. <Button asChild type="default">
  132. <Link href={`/project/${ref}/settings/general`}>View project settings</Link>
  133. </Button>
  134. )}
  135. </CardFooter>
  136. )}
  137. {isPauseStatusSuccess && isRestoreDisabled && <PauseDisabledState />}
  138. </Card>
  139. )
  140. }