PausingState.tsx 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. import { SupportCategories } from '@supabase/shared-types/out/constants'
  2. import { LOCAL_STORAGE_KEYS, useParams } from 'common'
  3. import { Circle, Loader } from 'lucide-react'
  4. import { useEffect, useState } from 'react'
  5. import { Badge, Button } from 'ui'
  6. import { SupportLink } from '@/components/interfaces/Support/SupportLink'
  7. import { useInvalidateProjectsInfiniteQuery } from '@/data/projects/org-projects-infinite-query'
  8. import { Project, useInvalidateProjectDetailsQuery } from '@/data/projects/project-detail-query'
  9. import { useProjectStatusQuery } from '@/data/projects/project-status-query'
  10. import { useLongRunningTransitionState } from '@/hooks/misc/useLongRunningTransitionState'
  11. import { PROJECT_STATUS } from '@/lib/constants'
  12. import {
  13. clearPersistedTransitionStartTime,
  14. FALLBACK_LONG_RUNNING_STATE_THRESHOLD_MINUTES,
  15. minutesToMilliseconds,
  16. } from '@/lib/project-transition-state'
  17. const LONG_RUNNING_STATE_THRESHOLD_MINUTES = FALLBACK_LONG_RUNNING_STATE_THRESHOLD_MINUTES
  18. const LONG_RUNNING_STATE_THRESHOLD_MS = minutesToMilliseconds(LONG_RUNNING_STATE_THRESHOLD_MINUTES)
  19. export interface PausingStateProps {
  20. project: Project
  21. }
  22. export const PausingState = ({ project }: PausingStateProps) => {
  23. const { ref } = useParams()
  24. const [startPolling, setStartPolling] = useState(false)
  25. const pauseStateStartStorageKey = ref ? LOCAL_STORAGE_KEYS.PROJECT_PAUSING_STARTED_AT(ref) : null
  26. const isTakingLongerThanExpected = useLongRunningTransitionState({
  27. storageKey: pauseStateStartStorageKey,
  28. thresholdMs: LONG_RUNNING_STATE_THRESHOLD_MS,
  29. })
  30. const { invalidateProjectsQuery } = useInvalidateProjectsInfiniteQuery()
  31. const { invalidateProjectDetailsQuery } = useInvalidateProjectDetailsQuery()
  32. const { data: projectStatusData, isSuccess: isProjectStatusSuccess } = useProjectStatusQuery(
  33. { projectRef: ref },
  34. {
  35. enabled: startPolling,
  36. refetchInterval: (query) => {
  37. const data = query.state.data
  38. return data?.status === PROJECT_STATUS.INACTIVE ||
  39. data?.status === PROJECT_STATUS.PAUSE_FAILED
  40. ? false
  41. : 2000
  42. },
  43. }
  44. )
  45. useEffect(() => {
  46. if (!isProjectStatusSuccess) return
  47. if (
  48. projectStatusData?.status === PROJECT_STATUS.INACTIVE ||
  49. projectStatusData?.status === PROJECT_STATUS.PAUSE_FAILED
  50. ) {
  51. if (pauseStateStartStorageKey) {
  52. clearPersistedTransitionStartTime(pauseStateStartStorageKey)
  53. }
  54. if (ref) invalidateProjectDetailsQuery(ref)
  55. invalidateProjectsQuery()
  56. }
  57. }, [
  58. isProjectStatusSuccess,
  59. projectStatusData,
  60. pauseStateStartStorageKey,
  61. ref,
  62. invalidateProjectDetailsQuery,
  63. invalidateProjectsQuery,
  64. ])
  65. useEffect(() => {
  66. const timeoutId = setTimeout(() => setStartPolling(true), 4000)
  67. return () => clearTimeout(timeoutId)
  68. }, [])
  69. return (
  70. <div className="mx-auto my-16 w-full max-w-7xl space-y-16">
  71. <div className="mx-6 space-y-16">
  72. <div className="flex flex-col space-y-4 lg:flex-row lg:items-center lg:space-y-0 lg:space-x-6">
  73. <h1 className="text-3xl">{project.name}</h1>
  74. <div>
  75. <Badge>
  76. <div className="flex items-center gap-2">
  77. <Loader className="animate-spin" size={12} />
  78. <span>Pausing project</span>
  79. </div>
  80. </Badge>
  81. </div>
  82. </div>
  83. <div className="mx-auto mt-8 mb-16 w-full max-w-7xl">
  84. <div className="flex h-[500px] items-center justify-center rounded-sm border border-overlay bg-surface-100 p-8">
  85. <div className="grid w-[380px] gap-4">
  86. <div className="relative mx-auto max-w-[300px]">
  87. <div className="absolute flex h-full w-full items-center justify-center">
  88. <Loader className="animate-spin" size={20} strokeWidth={2} />
  89. </div>
  90. <Circle className="text-foreground-lighter" size={50} strokeWidth={1.5} />
  91. </div>
  92. <p className="text-center">Pausing {project.name}</p>
  93. <p className="text-center text-sm text-foreground-light">
  94. {isTakingLongerThanExpected
  95. ? `This is taking longer than usual. Contact support if your project is still pausing after ${LONG_RUNNING_STATE_THRESHOLD_MINUTES} minutes.`
  96. : 'Your project is being paused now. This usually takes a few minutes. While paused, your data stays safe, and you can turn the project back on anytime.'}
  97. </p>
  98. {isTakingLongerThanExpected && (
  99. <div className="flex justify-center">
  100. <Button asChild type="default">
  101. <SupportLink
  102. queryParams={{
  103. category: SupportCategories.DATABASE_UNRESPONSIVE,
  104. projectRef: project.ref,
  105. subject: 'Project stuck in pausing state',
  106. message: `Project "${project.name}" has remained in a pausing state for over ${LONG_RUNNING_STATE_THRESHOLD_MINUTES} minutes.`,
  107. }}
  108. >
  109. Contact support
  110. </SupportLink>
  111. </Button>
  112. </div>
  113. )}
  114. </div>
  115. </div>
  116. </div>
  117. </div>
  118. </div>
  119. )
  120. }