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