diff --git a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/actions/deleteVendor.ts b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/actions/deleteVendor.ts new file mode 100644 index 000000000..51ef230c3 --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/actions/deleteVendor.ts @@ -0,0 +1,85 @@ +'use server'; + +import { authActionClient } from '@/actions/safe-action'; +import type { ActionResponse } from '@/actions/types'; +import { db } from '@db'; +import { revalidatePath } from 'next/cache'; +import { z } from 'zod'; + +const deleteVendorSchema = z.object({ + vendorId: z.string(), +}); + +export const deleteVendor = authActionClient + .metadata({ + name: 'delete-vendor', + track: { + event: 'delete_vendor', + channel: 'organization', + }, + }) + .inputSchema(deleteVendorSchema) + .action(async ({ parsedInput, ctx }): Promise> => { + if (!ctx.session.activeOrganizationId) { + return { + success: false, + error: 'User does not have an active organization', + }; + } + + const { vendorId } = parsedInput; + + try { + const currentUserMember = await db.member.findFirst({ + where: { + organizationId: ctx.session.activeOrganizationId, + userId: ctx.user.id, + }, + }); + + if ( + !currentUserMember || + (!currentUserMember.role.includes('admin') && !currentUserMember.role.includes('owner')) + ) { + return { + success: false, + error: "You don't have permission to delete vendors.", + }; + } + + // Verify the vendor exists within the user's organization + const targetVendor = await db.vendor.findFirst({ + where: { + id: vendorId, + organizationId: ctx.session.activeOrganizationId, + }, + }); + + if (!targetVendor) { + return { + success: false, + error: 'Vendor not found in this organization.', + }; + } + + await db.vendor.delete({ + where: { + id: vendorId, + }, + }); + + // Revalidate the path to refresh the data on the vendors page + revalidatePath(`/${ctx.session.activeOrganizationId}/vendors`); + + return { + success: true, + data: { deleted: true }, + }; + } catch (error) { + console.error('Error deleting vendor:', error); + return { + success: false, + error: 'Failed to delete the vendor. Please try again.', + }; + } + }); diff --git a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorColumns.tsx b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorColumns.tsx index a5adb287f..8e81d6ff4 100644 --- a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorColumns.tsx +++ b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorColumns.tsx @@ -6,6 +6,7 @@ import type { ColumnDef } from '@tanstack/react-table'; import { UserIcon } from 'lucide-react'; import Link from 'next/link'; import type { GetVendorsResult } from '../data/queries'; +import { VendorDeleteCell } from './VendorDeleteCell'; type VendorRow = GetVendorsResult['data'][number]; @@ -125,4 +126,12 @@ export const columns: ColumnDef[] = [ variant: 'select', }, }, + { + id: 'delete-vendor', + cell: ({ row }) => { + return ; + }, + enableSorting: false, + enableHiding: false, + }, ]; diff --git a/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorDeleteCell.tsx b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorDeleteCell.tsx new file mode 100644 index 000000000..d40c9f6d6 --- /dev/null +++ b/apps/app/src/app/(app)/[orgId]/vendors/(overview)/components/VendorDeleteCell.tsx @@ -0,0 +1,78 @@ +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from '@comp/ui/alert-dialog'; +import { Button } from '@comp/ui/button'; +import { Trash2 } from 'lucide-react'; +import * as React from 'react'; +import { toast } from 'sonner'; +import { deleteVendor } from '../actions/deleteVendor'; +import type { GetVendorsResult } from '../data/queries'; + +type VendorRow = GetVendorsResult['data'][number]; + +interface VendorDeleteCellProps { + vendor: VendorRow; +} + +export const VendorDeleteCell: React.FC = ({ vendor }) => { + const [isRemoveAlertOpen, setIsRemoveAlertOpen] = React.useState(false); + const [isDeleting, setIsDeleting] = React.useState(false); + + const handleDeleteClick = async (event: React.MouseEvent) => { + event.stopPropagation(); + setIsDeleting(true); + + const response = await deleteVendor({ vendorId: vendor.id }); + + if (response?.data?.success) { + toast.success(`Vendor "${vendor.name}" has been deleted.`); + setIsRemoveAlertOpen(false); + } else { + toast.error(String(response?.data?.error) || 'Failed to delete vendor.'); + } + + setIsDeleting(false); + }; + + return ( + <> +
+ +
+ + e.stopPropagation()}> + + Delete Vendor + + Are you sure you want to delete {vendor.name}? This action cannot be + undone. + + + + Cancel + + {isDeleting ? 'Deleting...' : 'Delete'} + + + + + + ); +};