DeleteBucketModal.tsx96 lines · main
1import { useParams } from 'common'
2import { useRouter } from 'next/router'
3import { toast } from 'sonner'
4
5import { extractBucketNameFromDefinition } from './Storage.utils'
6import { TextConfirmModal } from '@/components/ui/TextConfirmModalWrapper'
7import { useDatabasePoliciesQuery } from '@/data/database-policies/database-policies-query'
8import { useDatabasePolicyDeleteMutation } from '@/data/database-policies/database-policy-delete-mutation'
9import { useBucketDeleteMutation } from '@/data/storage/bucket-delete-mutation'
10import { Bucket } from '@/data/storage/buckets-query'
11import { useSelectedProjectQuery } from '@/hooks/misc/useSelectedProject'
12
13export interface DeleteBucketModalProps {
14 visible: boolean
15 bucket: Bucket
16 onClose: () => void
17}
18
19export const DeleteBucketModal = ({ visible, bucket, onClose }: DeleteBucketModalProps) => {
20 const router = useRouter()
21 const { ref: projectRef, bucketId } = useParams()
22 const { data: project } = useSelectedProjectQuery()
23
24 const { data: policies } = useDatabasePoliciesQuery({
25 projectRef: project?.ref,
26 connectionString: project?.connectionString,
27 schema: 'storage',
28 })
29
30 const { mutateAsync: deletePolicy, isPending: isDeletingPolicies } =
31 useDatabasePolicyDeleteMutation()
32
33 const { mutate: deleteBucket, isPending: isDeletingBucket } = useBucketDeleteMutation({
34 onSuccess: async () => {
35 if (!project) return console.error('Project is required')
36
37 // Clean up policies from the corresponding bucket that was deleted
38 const bucketPolicies = (policies ?? []).filter((policy) => {
39 if (policy.table !== 'objects') return false
40
41 const policyBucket = extractBucketNameFromDefinition(policy.definition ?? policy.check)
42 return policyBucket === bucket.name
43 })
44
45 try {
46 await Promise.all(
47 bucketPolicies.map((policy) =>
48 deletePolicy({
49 projectRef: project?.ref,
50 connectionString: project?.connectionString,
51 originalPolicy: policy,
52 })
53 )
54 )
55
56 toast.success(`Successfully deleted bucket ${bucket.id}`)
57 if (!!bucketId) router.push(`/project/${projectRef}/storage/files`)
58 onClose()
59 } catch (error) {
60 toast.success(
61 `Successfully deleted bucket ${bucket.id}. However, there was a problem deleting the policies tied to the bucket. Please review them in the storage policies section`
62 )
63 }
64 },
65 })
66
67 const onConfirmDelete = async () => {
68 if (!projectRef) return console.error('Project ref is required')
69 if (!bucket) return console.error('No bucket is selected')
70 deleteBucket({ projectRef, id: bucket.id })
71 }
72
73 return (
74 <TextConfirmModal
75 visible={visible}
76 size="medium"
77 variant="destructive"
78 title={`Delete bucket “${bucket.id}”`}
79 loading={isDeletingBucket || isDeletingPolicies}
80 confirmPlaceholder="Type bucket name"
81 confirmString={bucket.id}
82 confirmLabel="Delete bucket"
83 onCancel={onClose}
84 onConfirm={onConfirmDelete}
85 alert={{
86 title: 'You cannot recover this bucket once deleted',
87 description: 'This action cannot be undone',
88 }}
89 >
90 <p className="text-sm">
91 Your bucket <span className="font-bold text-foreground">{bucket.id}</span> and all of its
92 contents will be permanently deleted.
93 </p>
94 </TextConfirmModal>
95 )
96}