import type { PostgresTable } from '@supabase/postgres-meta'
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { toast } from 'sonner'
import { formatFilterURLParams } from 'components/grid/SupabaseGrid.utils'
import type { SupaRow } from 'components/grid/types'
import { useProjectContext } from 'components/layouts/ProjectLayout/ProjectContext'
import { useDatabaseColumnDeleteMutation } from 'data/database-columns/database-column-delete-mutation'
import { Entity } from 'data/table-editor/table-editor-types'
import { useTableRowDeleteAllMutation } from 'data/table-rows/table-row-delete-all-mutation'
import { useTableRowDeleteMutation } from 'data/table-rows/table-row-delete-mutation'
import { useTableRowTruncateMutation } from 'data/table-rows/table-row-truncate-mutation'
import { useTableDeleteMutation } from 'data/tables/table-delete-mutation'
import { TablesData, useGetTables } from 'data/tables/tables-query'
import { useQuerySchemaState } from 'hooks/misc/useSchemaQueryState'
import { useUrlState } from 'hooks/ui/useUrlState'
import { noop } from 'lib/void'
import { useGetImpersonatedRole } from 'state/role-impersonation-state'
import { useTableEditorStateSnapshot } from 'state/table-editor'
import { AlertDescription_Shadcn_, AlertTitle_Shadcn_, Alert_Shadcn_, Button, Checkbox } from 'ui'
import ConfirmationModal from 'ui-patterns/Dialogs/ConfirmationModal'
export type DeleteConfirmationDialogsProps = {
selectedTable?: Entity | PostgresTable
onAfterDeleteTable?: (tables: TablesData) => void
}
const DeleteConfirmationDialogs = ({
selectedTable,
onAfterDeleteTable = noop,
}: DeleteConfirmationDialogsProps) => {
const { project } = useProjectContext()
const snap = useTableEditorStateSnapshot()
const { selectedSchema } = useQuerySchemaState()
const [{ filter }, setParams] = useUrlState({ arrayKeys: ['filter', 'sort'] })
const filters = formatFilterURLParams(filter as string[])
const getTables = useGetTables({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const removeDeletedColumnFromFiltersAndSorts = (columnName: string) => {
setParams((prevParams) => {
const existingFilters = (prevParams?.filter ?? []) as string[]
const existingSorts = (prevParams?.sort ?? []) as string[]
return {
...prevParams,
filter: existingFilters.filter((filter: string) => {
const [column] = filter.split(':')
if (column !== columnName) return filter
}),
sort: existingSorts.filter((sort: string) => {
const [column] = sort.split(':')
if (column !== columnName) return sort
}),
}
})
}
const { mutate: deleteColumn } = useDatabaseColumnDeleteMutation({
onSuccess: () => {
if (!(snap.confirmationDialog?.type === 'column')) return
const selectedColumnToDelete = snap.confirmationDialog.column
removeDeletedColumnFromFiltersAndSorts(selectedColumnToDelete.name)
toast.success(`Successfully deleted column "${selectedColumnToDelete.name}"`)
},
onError: (error) => {
if (!(snap.confirmationDialog?.type === 'column')) return
const selectedColumnToDelete = snap.confirmationDialog.column
toast.error(`Failed to delete ${selectedColumnToDelete!.name}: ${error.message}`)
},
onSettled: () => {
snap.closeConfirmationDialog()
},
})
const { mutate: deleteTable } = useTableDeleteMutation({
onSuccess: async () => {
const tables = await getTables(selectedSchema)
onAfterDeleteTable(tables)
toast.success(`Successfully deleted table "${selectedTable?.name}"`)
},
onError: (error) => {
toast.error(`Failed to delete ${selectedTable?.name}: ${error.message}`)
},
onSettled: () => {
snap.closeConfirmationDialog()
},
})
const { mutate: deleteRows } = useTableRowDeleteMutation({
onSuccess: () => {
if (snap.confirmationDialog?.type === 'row') {
snap.confirmationDialog.callback?.()
}
toast.success(`Successfully deleted selected row(s)`)
},
onSettled: () => {
snap.closeConfirmationDialog()
},
})
const { mutate: deleteAllRows } = useTableRowDeleteAllMutation({
onSuccess: () => {
if (snap.confirmationDialog?.type === 'row') {
snap.confirmationDialog.callback?.()
}
toast.success(`Successfully deleted selected rows`)
},
onError: (error) => {
toast.error(`Failed to delete rows: ${error.message}`)
},
onSettled: () => {
snap.closeConfirmationDialog()
},
})
const { mutate: truncateRows } = useTableRowTruncateMutation({
onSuccess: () => {
if (snap.confirmationDialog?.type === 'row') {
snap.confirmationDialog.callback?.()
}
toast.success(`Successfully deleted all rows from table`)
},
onError: (error) => {
toast.error(`Failed to delete rows: ${error.message}`)
},
onSettled: () => {
snap.closeConfirmationDialog()
},
})
const isAllRowsSelected =
snap.confirmationDialog?.type === 'row' ? snap.confirmationDialog.allRowsSelected : false
const numRows =
snap.confirmationDialog?.type === 'row'
? snap.confirmationDialog.allRowsSelected
? snap.confirmationDialog.numRows ?? 0
: snap.confirmationDialog.rows.length
: 0
const isDeleteWithCascade =
snap.confirmationDialog?.type === 'column' || snap.confirmationDialog?.type === 'table'
? snap.confirmationDialog.isDeleteWithCascade
: false
const onConfirmDeleteColumn = async () => {
if (!(snap.confirmationDialog?.type === 'column')) return
if (project === undefined) return
const selectedColumnToDelete = snap.confirmationDialog.column
if (selectedColumnToDelete === undefined) return
deleteColumn({
id: selectedColumnToDelete.id,
cascade: isDeleteWithCascade,
projectRef: project.ref,
connectionString: project?.connectionString,
table: selectedTable,
})
}
const onConfirmDeleteTable = async () => {
if (!(snap.confirmationDialog?.type === 'table')) return
const selectedTableToDelete = selectedTable
if (selectedTableToDelete === undefined) return
deleteTable({
projectRef: project?.ref!,
connectionString: project?.connectionString,
schema: selectedTableToDelete.schema,
id: selectedTableToDelete.id,
cascade: isDeleteWithCascade,
})
}
const getImpersonatedRole = useGetImpersonatedRole()
const onConfirmDeleteRow = async () => {
if (!project) return console.error('Project ref is required')
if (!selectedTable) return console.error('Selected table required')
if (snap.confirmationDialog?.type !== 'row') return
const selectedRowsToDelete = snap.confirmationDialog.rows
if (snap.confirmationDialog.allRowsSelected) {
if (filters.length === 0) {
if (getImpersonatedRole() !== undefined) {
snap.closeConfirmationDialog()
return toast.error('Table truncation is not supported when impersonating a role')
}
truncateRows({
projectRef: project.ref,
connectionString: project.connectionString,
table: selectedTable as any,
})
} else {
deleteAllRows({
projectRef: project.ref,
connectionString: project.connectionString,
table: selectedTable as any,
filters,
impersonatedRole: getImpersonatedRole(),
})
}
} else {
deleteRows({
projectRef: project.ref,
connectionString: project.connectionString,
table: selectedTable as any,
rows: selectedRowsToDelete as SupaRow[],
impersonatedRole: getImpersonatedRole(),
})
}
}
return (
<>
Are you sure you want to delete the selected column? This action cannot be undone.
Are you sure you want to delete the selected table? This action cannot be undone.
Are you sure you want to delete {isAllRowsSelected ? 'all' : 'the selected'} {numRows > 1 && `${numRows} `} row {numRows > 1 && 's'} ? This action cannot be undone.