import type { PostgresTable } from '@supabase/postgres-meta'
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { toast } from 'sonner'
import {
formatFilterURLParams,
saveTableEditorStateToLocalStorageDebounced,
} 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 { noop } from 'lib/void'
import { useGetImpersonatedRoleState } 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'
import { useTableEditorFiltersSort } from 'hooks/misc/useTableEditorFiltersSort'
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 { filters: filter, setParams } = useTableEditorFiltersSort()
const filters = formatFilterURLParams(filter as string[])
const getTables = useGetTables({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const removeDeletedColumnFromFiltersAndSorts = ({
ref,
tableName,
schema,
columnName,
}: {
ref: string
tableName: string
schema: string
columnName: string
}) => {
setParams((prevParams) => {
const existingFilters = (prevParams?.filter ?? []) as string[]
const existingSorts = (prevParams?.sort ?? []) as string[]
const newFiltersAndSorts = {
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
}),
}
// Overwrite local storage without the deleted column
saveTableEditorStateToLocalStorageDebounced({
projectRef: ref,
tableName,
schema,
filters: newFiltersAndSorts.filter,
sorts: newFiltersAndSorts.sort,
})
return {
...prevParams,
...newFiltersAndSorts,
}
})
}
const { mutate: deleteColumn } = useDatabaseColumnDeleteMutation({
onSuccess: () => {
if (!(snap.confirmationDialog?.type === 'column')) return
const selectedColumnToDelete = snap.confirmationDialog.column
if (!project?.ref) return
if (!selectedTable?.name) return
removeDeletedColumnFromFiltersAndSorts({
ref: project?.ref,
tableName: selectedTable?.name,
schema: selectedColumnToDelete.schema,
columnName: 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 getImpersonatedRoleState = useGetImpersonatedRoleState()
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 (getImpersonatedRoleState().role !== 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,
roleImpersonationState: getImpersonatedRoleState(),
})
}
} else {
deleteRows({
projectRef: project.ref,
connectionString: project.connectionString,
table: selectedTable as any,
rows: selectedRowsToDelete as SupaRow[],
roleImpersonationState: getImpersonatedRoleState(),
})
}
}
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.