From 5e4de20bf25766e20a4a5a0a943c2b98b289f281 Mon Sep 17 00:00:00 2001 From: Francois Le Pape <32224751+Lp-Francois@users.noreply.github.com> Date: Tue, 26 May 2026 14:38:02 +0200 Subject: [PATCH] Add owner-only company deletion flow --- apps/cursor/src/actions/delete-company.ts | 60 +++++++++++++++++++ .../src/components/company/company-header.tsx | 4 +- .../company/delete-company-button.tsx | 52 ++++++++++++++++ 3 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 apps/cursor/src/actions/delete-company.ts create mode 100644 apps/cursor/src/components/company/delete-company-button.tsx diff --git a/apps/cursor/src/actions/delete-company.ts b/apps/cursor/src/actions/delete-company.ts new file mode 100644 index 00000000..851ed4ff --- /dev/null +++ b/apps/cursor/src/actions/delete-company.ts @@ -0,0 +1,60 @@ +"use server"; + +import { revalidatePath } from "next/cache"; +import { z } from "zod"; +import { createClient } from "@/utils/supabase/server"; +import { ActionError, authActionClient } from "./safe-action"; + +export const deleteCompanyAction = authActionClient + .metadata({ + actionName: "delete-company", + }) + .schema( + z.object({ + id: z.string(), + slug: z.string(), + }), + ) + .action(async ({ parsedInput: { id, slug }, ctx: { userId } }) => { + const supabase = await createClient(); + + const { data: company, error: companyError } = await supabase + .from("companies") + .select("id, owner_id") + .eq("id", id) + .single(); + + if (companyError || !company) { + throw new ActionError("Company not found."); + } + + if (company.owner_id !== userId) { + throw new ActionError("You do not have permission to delete this company."); + } + + const { error: unlinkMcpsError } = await supabase + .from("mcps") + .update({ company_id: null }) + .eq("company_id", id) + .eq("owner_id", userId); + + if (unlinkMcpsError) { + throw new ActionError(`Failed to unlink MCPs: ${unlinkMcpsError.message}`); + } + + const { error: deleteError } = await supabase + .from("companies") + .delete() + .eq("id", id) + .eq("owner_id", userId); + + if (deleteError) { + throw new ActionError(`Failed to delete company: ${deleteError.message}`); + } + + revalidatePath("/companies"); + revalidatePath(`/c/${slug}`); + revalidatePath("/"); + + return { success: true }; + }); diff --git a/apps/cursor/src/components/company/company-header.tsx b/apps/cursor/src/components/company/company-header.tsx index e0579fd8..4209d726 100644 --- a/apps/cursor/src/components/company/company-header.tsx +++ b/apps/cursor/src/components/company/company-header.tsx @@ -1,4 +1,5 @@ import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; +import { DeleteCompanyButton } from "./delete-company-button"; import { EditCompanyModal } from "../modals/edit-company-modal"; export function CompanyHeader({ @@ -47,7 +48,8 @@ export function CompanyHeader({ {isOwner && ( -
+
+ { + toast.success("Company deleted."); + router.push("/companies"); + router.refresh(); + }, + onError: ({ error }) => { + toast.error(error.serverError ?? "Failed to delete company."); + }, + }); + + const onDelete = () => { + const confirmed = window.confirm( + "Delete this company? This cannot be undone.", + ); + + if (!confirmed) { + return; + } + + execute({ id, slug }); + }; + + return ( + + ); +}