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