RestoreFailedState.tsx170 lines · main
1import { PermissionAction, SupportCategories } from '@supabase/shared-types/out/constants'
2import { useParams } from 'common'
3import { Download, MoreVertical, Trash } from 'lucide-react'
4import { useState } from 'react'
5import {
6 Button,
7 CriticalIcon,
8 Dialog,
9 DialogContent,
10 DialogHeader,
11 DialogSection,
12 DialogTitle,
13 DropdownMenu,
14 DropdownMenuContent,
15 DropdownMenuTrigger,
16} from 'ui'
17
18import { DeleteProjectModal } from '@/components/interfaces/Settings/General/DeleteProjectPanel/DeleteProjectModal'
19import { SupportLink } from '@/components/interfaces/Support/SupportLink'
20import { LogicalBackupCliInstructions } from '@/components/layouts/ProjectLayout/LogicalBackupCliInstructions'
21import { ButtonTooltip } from '@/components/ui/ButtonTooltip'
22import { DropdownMenuItemTooltip } from '@/components/ui/DropdownMenuItemTooltip'
23import { InlineLink } from '@/components/ui/InlineLink'
24import { useBackupDownloadMutation } from '@/data/database/backup-download-mutation'
25import { useDownloadableBackupQuery } from '@/data/database/backup-query'
26import { useAsyncCheckPermissions } from '@/hooks/misc/useCheckPermissions'
27import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
28
29export const RestoreFailedState = () => {
30 const { ref } = useParams()
31 const { data: project } = useSelectedProjectQuery()
32 const [visible, setVisible] = useState(false)
33 const [showCliBackup, setShowCliBackup] = useState(false)
34
35 const { can: canDeleteProject } = useAsyncCheckPermissions(PermissionAction.UPDATE, 'projects', {
36 resource: { project_id: project?.id },
37 })
38
39 const { data, isPending: isLoadingBackups } = useDownloadableBackupQuery({ projectRef: ref })
40 const backups = data?.backups ?? []
41
42 const { mutate: downloadBackup, isPending: isDownloading } = useBackupDownloadMutation({
43 onSuccess: (res) => {
44 const { fileUrl } = res
45
46 // Trigger browser download by create,trigger and remove tempLink
47 const tempLink = document.createElement('a')
48 tempLink.href = fileUrl
49 document.body.appendChild(tempLink)
50 tempLink.click()
51 document.body.removeChild(tempLink)
52 },
53 })
54
55 const onClickDownloadBackup = () => {
56 if (!ref) return console.error('Project ref is required')
57 if (backups.length === 0 || data?.status === 'physical-backups-enabled')
58 return setShowCliBackup(true)
59 downloadBackup({ ref, backup: backups[0] })
60 }
61
62 const downloadBackupTooltipText = isLoadingBackups
63 ? undefined
64 : data?.status === 'physical-backups-enabled'
65 ? 'Project uses physical backups — click to see CLI backup instructions'
66 : backups.length === 0
67 ? 'No downloadable backup available — click to see CLI backup instructions'
68 : undefined
69
70 return (
71 <>
72 <div className="flex items-center justify-center h-full">
73 <div className="bg-surface-100 border border-overlay rounded-md w-3/4 lg:w-1/2">
74 <div className="space-y-6 pt-6">
75 <div className="flex px-8 space-x-8">
76 <div className="mt-1">
77 <CriticalIcon className="w-5 h-5" />
78 </div>
79 <div className="space-y-1">
80 <p>Something went wrong while restoring your project</p>
81 <p className="text-sm text-foreground-light">
82 Your project's data is intact, but your project is inaccessible due to a
83 restoration failure. Database backups for this project can still be accessed{' '}
84 <InlineLink href={`/project/${ref}/database/backups/scheduled`}>here</InlineLink>.
85 </p>
86 <p className="text-sm text-foreground-light">
87 Please contact support for assistance.
88 </p>
89 </div>
90 </div>
91
92 <div className="border-t border-overlay flex items-center justify-end py-4 px-8 gap-x-2">
93 <Button asChild type="default">
94 <SupportLink
95 queryParams={{
96 category: SupportCategories.DATABASE_UNRESPONSIVE,
97 projectRef: project?.ref,
98 subject: 'Restoration failed for project',
99 }}
100 >
101 Contact support
102 </SupportLink>
103 </Button>
104
105 <ButtonTooltip
106 type="default"
107 icon={<Download />}
108 disabled={isLoadingBackups}
109 loading={isDownloading || isLoadingBackups}
110 tooltip={{
111 content: {
112 side: 'bottom',
113 text: downloadBackupTooltipText,
114 },
115 }}
116 onClick={onClickDownloadBackup}
117 >
118 Download backup
119 </ButtonTooltip>
120
121 <DropdownMenu>
122 <DropdownMenuTrigger>
123 <Button type="default" className="w-7" icon={<MoreVertical />} />
124 </DropdownMenuTrigger>
125 <DropdownMenuContent className="w-72" align="end">
126 <DropdownMenuItemTooltip
127 onClick={() => setVisible(true)}
128 className="items-start gap-x-2"
129 disabled={!canDeleteProject}
130 tooltip={{
131 content: {
132 side: 'right',
133 text: !canDeleteProject
134 ? 'You need additional permissions to delete this project'
135 : undefined,
136 },
137 }}
138 >
139 <div className="translate-y-0.5">
140 <Trash size={14} />
141 </div>
142 <div className="">
143 <p>Delete project</p>
144 <p className="text-foreground-lighter">
145 Project cannot be restored once it is deleted
146 </p>
147 </div>
148 </DropdownMenuItemTooltip>
149 </DropdownMenuContent>
150 </DropdownMenu>
151 </div>
152 </div>
153 </div>
154 </div>
155
156 <Dialog open={showCliBackup} onOpenChange={setShowCliBackup}>
157 <DialogContent size="medium">
158 <DialogHeader>
159 <DialogTitle>Back up your database</DialogTitle>
160 </DialogHeader>
161 <DialogSection>
162 <LogicalBackupCliInstructions showResetPassword={false} />
163 </DialogSection>
164 </DialogContent>
165 </Dialog>
166
167 <DeleteProjectModal visible={visible} onClose={() => setVisible(false)} />
168 </>
169 )
170}