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..e6d2d80f 100644 --- a/apps/cursor/src/app/admin/plugins/page.tsx +++ b/apps/cursor/src/app/admin/plugins/page.tsx @@ -1,9 +1,9 @@ -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"; 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", @@ -16,22 +16,22 @@ 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 (

Review Plugins

-

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

- +
); 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/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} + + + + + + + + + + + + + ); +} 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);