PauseDisabledState.tsx209 lines · main
1import { useParams } from 'common'
2import { Database, Storage } from 'icons'
3import { ChevronDown, Download, ExternalLink } from 'lucide-react'
4import { useEffect, useState } from 'react'
5import { toast } from 'sonner'
6import {
7 Button,
8 DropdownMenu,
9 DropdownMenuContent,
10 DropdownMenuItem,
11 DropdownMenuTrigger,
12} from 'ui'
13import { Admonition, TimestampInfo } from 'ui-patterns'
14
15import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip'
16import { InlineLink } from '@/components/ui/InlineLink'
17import { useBackupDownloadMutation } from '@/data/database/backup-download-mutation'
18import { useProjectPauseStatusQuery } from '@/data/projects/project-pause-status-query'
19import { useStorageArchiveCreateMutation } from '@/data/storage/storage-archive-create-mutation'
20import { useStorageArchiveQuery } from '@/data/storage/storage-archive-query'
21import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
22import { DOCS_URL, PROJECT_STATUS } from '@/lib/constants'
23
24export const PauseDisabledState = () => {
25 const { ref } = useParams()
26 const { data: project } = useSelectedProjectQuery()
27 const [toastId, setToastId] = useState<string | number>()
28 const [refetchInterval, setRefetchInterval] = useState<number | false>(false)
29
30 const dbVersion = project?.dbVersion?.replace('briven-postgres-', '')
31
32 const { data: pauseStatus } = useProjectPauseStatusQuery(
33 { ref },
34 { enabled: project?.status === PROJECT_STATUS.INACTIVE }
35 )
36 const latestBackup = pauseStatus?.latest_downloadable_backup_id
37
38 const { data: storageArchive, isSuccess: isStorageArchiveSuccess } = useStorageArchiveQuery(
39 { projectRef: ref },
40 {
41 refetchInterval,
42 refetchOnWindowFocus: false,
43 }
44 )
45
46 useEffect(() => {
47 if (!isStorageArchiveSuccess) return
48 if (storageArchive.fileUrl && refetchInterval !== false) {
49 toast.success('Downloading storage objects', { id: toastId })
50 setToastId(undefined)
51 setRefetchInterval(false)
52 downloadStorageArchive(storageArchive.fileUrl)
53 }
54 }, [isStorageArchiveSuccess, storageArchive, refetchInterval])
55
56 const storageArchiveUrl = storageArchive?.fileUrl
57
58 const { mutate: downloadBackup } = useBackupDownloadMutation({
59 onSuccess: (res) => {
60 const { fileUrl } = res
61
62 // Trigger browser download by create,trigger and remove tempLink
63 const tempLink = document.createElement('a')
64 tempLink.href = fileUrl
65 document.body.appendChild(tempLink)
66 tempLink.click()
67 document.body.removeChild(tempLink)
68 },
69 })
70
71 const { mutate: createStorageArchive } = useStorageArchiveCreateMutation({
72 onSuccess: () => {
73 const toastId = toast.loading(
74 'Retrieving storage archive. This may take a few minutes depending on the size of your storage objects.'
75 )
76 setToastId(toastId)
77 setRefetchInterval(5000)
78 },
79 })
80
81 const onSelectDownloadBackup = () => {
82 if (ref === undefined) return console.error('Project ref is required')
83 if (!latestBackup) return toast.error('No backups available for download')
84
85 const toastId = toast.loading('Fetching database backup')
86
87 downloadBackup(
88 {
89 ref,
90 backup: {
91 id: latestBackup,
92 },
93 },
94 {
95 onSuccess: () => {
96 toast.success('Downloading database backup', { id: toastId })
97 },
98 }
99 )
100 }
101
102 const downloadStorageArchive = (url: string) => {
103 const tempLink = document.createElement('a')
104 tempLink.href = url
105 document.body.appendChild(tempLink)
106 tempLink.click()
107 document.body.removeChild(tempLink)
108 }
109
110 const onSelectDownloadStorageArchive = () => {
111 if (!storageArchiveUrl) {
112 createStorageArchive({ projectRef: ref })
113 } else {
114 toast.success('Downloading storage objects')
115 downloadStorageArchive(storageArchiveUrl)
116 }
117 }
118
119 return (
120 <>
121 <Admonition
122 showIcon={false}
123 type="warning"
124 className="rounded-none border-0 px-6 [&>div>div>div]:flex [&>div>div>div]:flex-col [&>div>div>div]:gap-y-3"
125 title="Project can no longer be restored through the dashboard"
126 >
127 <p className="leading-normal!">
128 This project has been paused for over{' '}
129 <span className="text-foreground">
130 {pauseStatus?.max_days_till_restore_disabled ?? 90} days
131 </span>{' '}
132 and cannot be restored through the dashboard. However, your data remains intact and can be
133 downloaded as a backup.
134 </p>
135
136 {!!pauseStatus?.last_paused_on && (
137 <p className="text-foreground-lighter text-sm">
138 Project last paused on{' '}
139 <TimestampInfo
140 className="text-sm"
141 labelFormat="DD MMM YYYY"
142 utcTimestamp={pauseStatus.last_paused_on}
143 />
144 </p>
145 )}
146
147 <div>
148 <p className="leading-normal! mb-1!">Recovery options:</p>
149 <ul className="flex flex-col gap-y-0.5">
150 <li className="flex items-center gap-x-2">
151 <ExternalLink size={14} />
152 <InlineLink
153 href={`${DOCS_URL}/guides/platform/migrating-within-briven/dashboard-restore`}
154 >
155 Restore the backup to a new Briven project
156 </InlineLink>
157 </li>
158 <li className="flex items-center gap-x-2">
159 <ExternalLink size={14} />
160 <InlineLink href={`${DOCS_URL}/guides/local-development/restoring-downloaded-backup`}>
161 Restore the backup on your local machine
162 </InlineLink>
163 </li>
164 </ul>
165 </div>
166 </Admonition>
167 <div className="border-t flex flex-col gap-3 sm:flex-row sm:justify-between sm:items-center px-6 py-4 bg-alternative">
168 <div>
169 <p className="text-sm">Export your data</p>
170 <p className="text-sm text-foreground-lighter">
171 Download backups for your database and storage objects
172 </p>
173 </div>
174 <DropdownMenu>
175 <DropdownMenuTrigger asChild>
176 <Button type="default" icon={<Download />} iconRight={<ChevronDown />}>
177 Download backups
178 </Button>
179 </DropdownMenuTrigger>
180 <DropdownMenuContent className="w-60" align="end">
181 <DropdownMenuItemTooltip
182 className="gap-x-2"
183 disabled={!latestBackup}
184 onClick={() => onSelectDownloadBackup()}
185 tooltip={{
186 content: {
187 side: 'right',
188 text: 'No backups available, please reach out via support for assistance',
189 },
190 }}
191 >
192 <Database size={16} />
193 Database backup (PG: {dbVersion})
194 </DropdownMenuItemTooltip>
195 <DropdownMenuItem className="gap-x-2" onClick={() => onSelectDownloadStorageArchive()}>
196 <Storage size={16} />
197 Storage objects
198 </DropdownMenuItem>
199 {/* [Joshen] Once storage object download is supported, can just use the below component */}
200 {/* <DropdownMenuItem className="gap-x-2" onClick={() => onSelectDownloadStorageArchive()}>
201 <Storage size={16} />
202 Download storage objects
203 </DropdownMenuItem> */}
204 </DropdownMenuContent>
205 </DropdownMenu>
206 </div>
207 </>
208 )
209}