From f148750abbd66c065761f9ddda049441aafd27d6 Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Thu, 19 Mar 2026 20:11:38 +0100 Subject: [PATCH 1/2] Soft-delete declined plugins and show them in admin review page Replace hard delete with status update on decline. Add a declined section to the admin review page so declined plugins remain visible and can still be approved if needed. Made-with: Cursor --- apps/cursor/src/actions/review-plugin.ts | 4 +- apps/cursor/src/app/admin/plugins/page.tsx | 20 ++++++-- .../app/admin/plugins/plugin-review-list.tsx | 46 +++++++++++-------- apps/cursor/src/data/queries.ts | 34 ++++++++++++++ 4 files changed, 79 insertions(+), 25 deletions(-) diff --git a/apps/cursor/src/actions/review-plugin.ts b/apps/cursor/src/actions/review-plugin.ts index e559706d..94fb02df 100644 --- a/apps/cursor/src/actions/review-plugin.ts +++ b/apps/cursor/src/actions/review-plugin.ts @@ -16,7 +16,7 @@ export const approvePluginAction = adminActionClient const { error } = await supabase .from("plugins") - .update({ active: true }) + .update({ active: true, status: "approved" }) .eq("id", pluginId); if (error) { @@ -70,7 +70,7 @@ export const declinePluginAction = adminActionClient const { error } = await supabase .from("plugins") - .delete() + .update({ active: false, status: "declined" }) .eq("id", pluginId); if (error) { diff --git a/apps/cursor/src/app/admin/plugins/page.tsx b/apps/cursor/src/app/admin/plugins/page.tsx index 7ad0b145..390afad0 100644 --- a/apps/cursor/src/app/admin/plugins/page.tsx +++ b/apps/cursor/src/app/admin/plugins/page.tsx @@ -1,4 +1,4 @@ -import { getPendingPlugins } from "@/data/queries"; +import { getDeclinedPlugins, getPendingPlugins } from "@/data/queries"; import { isAdmin } from "@/utils/admin"; import { getSession } from "@/utils/supabase/auth"; import type { Metadata } from "next"; @@ -16,9 +16,10 @@ export default async function AdminPluginsPage() { redirect("/"); } - const { data: plugins } = await getPendingPlugins({ - since: "2026-03-16T00:00:00Z", - }); + const [{ data: plugins }, { data: declined }] = await Promise.all([ + getPendingPlugins({ since: "2026-03-16T00:00:00Z" }), + getDeclinedPlugins({ since: "2026-03-16T00:00:00Z" }), + ]); return (
@@ -32,6 +33,17 @@ export default async function AdminPluginsPage() {
+ + {declined && declined.length > 0 && ( +
+

Declined

+

+ {declined.length} declined{" "} + {declined.length === 1 ? "plugin" : "plugins"} +

+ +
+ )} ); diff --git a/apps/cursor/src/app/admin/plugins/plugin-review-list.tsx b/apps/cursor/src/app/admin/plugins/plugin-review-list.tsx index d1ed64ac..d30213f0 100644 --- a/apps/cursor/src/app/admin/plugins/plugin-review-list.tsx +++ b/apps/cursor/src/app/admin/plugins/plugin-review-list.tsx @@ -12,7 +12,10 @@ import { useAction } from "next-safe-action/hooks"; import { useState } from "react"; import { toast } from "sonner"; -function PluginReviewCard({ plugin }: { plugin: PluginRow }) { +function PluginReviewCard({ + plugin, + variant = "pending", +}: { plugin: PluginRow; variant?: "pending" | "declined" }) { const [dismissed, setDismissed] = useState(false); const { execute: approve, isExecuting: isApproving } = useAction( @@ -32,7 +35,7 @@ function PluginReviewCard({ plugin }: { plugin: PluginRow }) { declinePluginAction, { onSuccess: () => { - toast.success(`"${plugin.name}" declined and removed.`); + toast.success(`"${plugin.name}" declined.`); setDismissed(true); }, onError: ({ error }) => { @@ -50,7 +53,7 @@ function PluginReviewCard({ plugin }: { plugin: PluginRow }) { ]; return ( -
+
- + {variant === "pending" && ( + + )}
); @@ -149,7 +157,7 @@ export function PluginReviewList({ plugins }: { plugins: PluginRow[] }) { return (
{plugins.map((plugin) => ( - + ))}
); diff --git a/apps/cursor/src/data/queries.ts b/apps/cursor/src/data/queries.ts index cc8b51d0..4dad0e4d 100644 --- a/apps/cursor/src/data/queries.ts +++ b/apps/cursor/src/data/queries.ts @@ -351,6 +351,7 @@ export type PluginRow = { author_avatar: string | null; owner_id: string | null; active: boolean; + status: "pending" | "approved" | "declined"; plan: string; order: number; install_count: number; @@ -452,6 +453,39 @@ export async function getPendingPlugins({ .from("plugins") .select("*, plugin_components(*)") .eq("active", false) + .eq("status", "pending") + .order("created_at", { ascending: false }) + .range(from, from + PAGE_SIZE - 1); + + if (since) { + query = query.gte("created_at", since); + } + + const { data, error } = await query; + if (error) return { data: allData.length ? allData : null, error }; + if (!data || data.length === 0) break; + + allData = allData.concat(data as PluginRow[]); + if (data.length < PAGE_SIZE) break; + from += PAGE_SIZE; + } + + return { data: allData as PluginRow[], error: null }; +} + +export async function getDeclinedPlugins({ + since, +}: { since?: string } = {}) { + const supabase = await createClient(); + const PAGE_SIZE = 100; + let allData: PluginRow[] = []; + let from = 0; + + while (true) { + let query = supabase + .from("plugins") + .select("*, plugin_components(*)") + .eq("status", "declined") .order("created_at", { ascending: false }) .range(from, from + PAGE_SIZE - 1); From 14be307b8d4a75f6eb4f2b1eac1dd49495691aaa Mon Sep 17 00:00:00 2001 From: Pontus Abrahamsson Date: Thu, 19 Mar 2026 20:13:37 +0100 Subject: [PATCH 2/2] Add tabs to admin review page for pending and declined plugins Made-with: Cursor --- apps/cursor/src/app/admin/plugins/page.tsx | 22 +++------ .../app/admin/plugins/plugin-review-tabs.tsx | 45 +++++++++++++++++++ 2 files changed, 50 insertions(+), 17 deletions(-) create mode 100644 apps/cursor/src/app/admin/plugins/plugin-review-tabs.tsx diff --git a/apps/cursor/src/app/admin/plugins/page.tsx b/apps/cursor/src/app/admin/plugins/page.tsx index 390afad0..e6d2d80f 100644 --- a/apps/cursor/src/app/admin/plugins/page.tsx +++ b/apps/cursor/src/app/admin/plugins/page.tsx @@ -3,7 +3,7 @@ import { isAdmin } from "@/utils/admin"; import { getSession } from "@/utils/supabase/auth"; import type { Metadata } from "next"; import { redirect } from "next/navigation"; -import { PluginReviewList } from "./plugin-review-list"; +import { PluginReviewTabs } from "./plugin-review-tabs"; export const metadata: Metadata = { title: "Review Plugins | Admin", @@ -26,24 +26,12 @@ export default async function AdminPluginsPage() {

Review Plugins

-

- {plugins?.length ?? 0} pending{" "} - {plugins?.length === 1 ? "submission" : "submissions"} -

- - - {declined && declined.length > 0 && ( -
-

Declined

-

- {declined.length} declined{" "} - {declined.length === 1 ? "plugin" : "plugins"} -

- -
- )} +
); diff --git a/apps/cursor/src/app/admin/plugins/plugin-review-tabs.tsx b/apps/cursor/src/app/admin/plugins/plugin-review-tabs.tsx new file mode 100644 index 00000000..48b40a10 --- /dev/null +++ b/apps/cursor/src/app/admin/plugins/plugin-review-tabs.tsx @@ -0,0 +1,45 @@ +"use client"; + +import type { PluginRow } from "@/data/queries"; +import { + Tabs, + TabsContent, + TabsList, + TabsTrigger, +} from "@/components/ui/tabs"; +import { PluginReviewList } from "./plugin-review-list"; + +export function PluginReviewTabs({ + pending, + declined, +}: { + pending: PluginRow[]; + declined: PluginRow[]; +}) { + return ( + + + + Pending{" "} + + {pending.length} + + + + Declined{" "} + + {declined.length} + + + + + + + + + + + + + ); +}