import type { PostgresTable } from '@supabase/postgres-meta'
import { ExternalLink } from 'lucide-react'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { useCallback } from 'react'
import { toast } from 'sonner'
import { useParams } from 'common'
import { useTableFilter } from 'components/grid/hooks/useTableFilter'
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 { 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'
export type DeleteConfirmationDialogsProps = {
selectedTable?: Entity | PostgresTable
}
const DeleteConfirmationDialogs = ({ selectedTable }: DeleteConfirmationDialogsProps) => {
const { ref: projectRef } = useParams()
const router = useRouter()
const { project } = useProjectContext()
const snap = useTableEditorStateSnapshot()
const { selectedSchema } = useQuerySchemaState()
const { filters, onApplyFilters } = useTableFilter()
const getTables = useGetTables({
projectRef: project?.ref,
connectionString: project?.connectionString,
})
const removeDeletedColumnFromFiltersAndSorts = ({
columnName,
}: {
ref?: string
tableName?: string
schema?: string
columnName: string
}) => {
onApplyFilters(filters.filter((filter) => filter.column !== columnName))
}
const { mutate: deleteColumn } = useDatabaseColumnDeleteMutation({
onSuccess: () => {
if (!(snap.confirmationDialog?.type === 'column')) return
const selectedColumnToDelete = snap.confirmationDialog.column
removeDeletedColumnFromFiltersAndSorts({ 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(),
})
}
}
const onAfterDeleteTable = useCallback(
(tables: TablesData) => {
// For simplicity for now, we just open the first table within the same schema
if (tables.length > 0) {
router.push(`/project/${projectRef}/editor/${tables[0].id}`)
} else {
router.push(`/project/${projectRef}/editor`)
}
},
[router, projectRef]
)
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.