PauseFailedState.tsx168 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 PauseFailedState = () => {
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 pausing 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 the failure
83 while pausing. 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 gap-x-2 py-4 px-8">
93 <Button asChild type="default">
94 <SupportLink
95 queryParams={{
96 category: SupportCategories.DATABASE_UNRESPONSIVE,
97 projectRef: project?.ref,
98 subject: 'Pausing failed for project',
99 }}
100 >
101 Contact support
102 </SupportLink>
103 </Button>
104 <ButtonTooltip
105 type="default"
106 icon={<Download />}
107 disabled={isLoadingBackups}
108 loading={isDownloading || isLoadingBackups}
109 tooltip={{
110 content: {
111 side: 'bottom',
112 text: downloadBackupTooltipText,
113 },
114 }}
115 onClick={onClickDownloadBackup}
116 >
117 Download backup
118 </ButtonTooltip>
119 <DropdownMenu>
120 <DropdownMenuTrigger>
121 <Button type="default" className="px-1.5" icon={<MoreVertical />} />
122 </DropdownMenuTrigger>
123 <DropdownMenuContent className="w-72" align="end">
124 <DropdownMenuItemTooltip
125 onClick={() => setVisible(true)}
126 className="items-start gap-x-2"
127 disabled={!canDeleteProject}
128 tooltip={{
129 content: {
130 side: 'right',
131 text: !canDeleteProject
132 ? 'You need additional permissions to delete this project'
133 : undefined,
134 },
135 }}
136 >
137 <div className="translate-y-0.5">
138 <Trash size={14} />
139 </div>
140 <div className="">
141 <p>Delete project</p>
142 <p className="text-foreground-lighter">
143 Project cannot be restored once it is deleted
144 </p>
145 </div>
146 </DropdownMenuItemTooltip>
147 </DropdownMenuContent>
148 </DropdownMenu>
149 </div>
150 </div>
151 </div>
152 </div>
153
154 <Dialog open={showCliBackup} onOpenChange={setShowCliBackup}>
155 <DialogContent size="medium">
156 <DialogHeader>
157 <DialogTitle>Back up your database</DialogTitle>
158 </DialogHeader>
159 <DialogSection>
160 <LogicalBackupCliInstructions showResetPassword={false} />
161 </DialogSection>
162 </DialogContent>
163 </Dialog>
164
165 <DeleteProjectModal visible={visible} onClose={() => setVisible(false)} />
166 </>
167 )
168}