From 7b7bd6f430949e837da2f0f3df8fd043dfa2e7ba Mon Sep 17 00:00:00 2001 From: Ishaan Dey Date: Sat, 27 Apr 2024 21:24:20 -0400 Subject: [PATCH] implement server actions for sandbox data mutation --- backend/database/src/index.ts | 21 +++ .../dashboard/projectCard/dropdown.tsx | 41 ++++- frontend/components/dashboard/projects.tsx | 24 ++- frontend/components/ui/alert-dialog.tsx | 141 ++++++++++++++++++ frontend/lib/actions.ts | 26 ++++ frontend/package-lock.json | 29 ++++ frontend/package.json | 1 + 7 files changed, 276 insertions(+), 7 deletions(-) create mode 100644 frontend/components/ui/alert-dialog.tsx diff --git a/backend/database/src/index.ts b/backend/database/src/index.ts index 867f466..4a3315b 100644 --- a/backend/database/src/index.ts +++ b/backend/database/src/index.ts @@ -42,6 +42,27 @@ export default { const res = await db.select().from(sandbox).all(); return json(res ?? {}); } + } else if (method === "DELETE") { + const params = url.searchParams; + if (params.has("id")) { + const id = params.get("id") as string; + const res = await db.delete(sandbox).where(eq(sandbox.id, id)).get(); + return success; + } else { + return invalidRequest; + } + } else if (method === "POST") { + const initSchema = z.object({ + id: z.string(), + name: z.string().optional(), + visibility: z.enum(["public", "private"]).optional(), + }); + + const body = await request.json(); + const { id, name, visibility } = initSchema.parse(body); + const sb = await db.update(sandbox).set({ name, visibility }).where(eq(sandbox.id, id)).returning().get(); + + return success; } else if (method === "PUT") { const initSchema = z.object({ type: z.enum(["react", "node"]), diff --git a/frontend/components/dashboard/projectCard/dropdown.tsx b/frontend/components/dashboard/projectCard/dropdown.tsx index 89288d1..752c878 100644 --- a/frontend/components/dashboard/projectCard/dropdown.tsx +++ b/frontend/components/dashboard/projectCard/dropdown.tsx @@ -1,7 +1,7 @@ "use client" import { Sandbox } from "@/lib/types" -import { Ellipsis, Lock, Trash2 } from "lucide-react" +import { Ellipsis, Globe, Lock, Trash2 } from "lucide-react" import { DropdownMenu, @@ -12,7 +12,15 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu" -export default function ProjectCardDropdown({ sandbox }: { sandbox: Sandbox }) { +export default function ProjectCardDropdown({ + sandbox, + onVisibilityChange, + onDelete, +}: { + sandbox: Sandbox + onVisibilityChange: (sandbox: Sandbox) => void + onDelete: (sandbox: Sandbox) => void +}) { return ( - - - Make Private + { + e.stopPropagation() + onVisibilityChange(sandbox) + }} + className="cursor-pointer" + > + {sandbox.visibility === "public" ? ( + <> + + Make Private + + ) : ( + <> + + Make Public + + )} - + { + e.stopPropagation() + onDelete(sandbox) + }} + className="!text-destructive cursor-pointer" + > Delete Project diff --git a/frontend/components/dashboard/projects.tsx b/frontend/components/dashboard/projects.tsx index 729ebbc..aae9cfa 100644 --- a/frontend/components/dashboard/projects.tsx +++ b/frontend/components/dashboard/projects.tsx @@ -1,3 +1,5 @@ +"use client" + import { Sandbox } from "@/lib/types" import ProjectCard from "./projectCard" import Image from "next/image" @@ -5,12 +7,28 @@ import ProjectCardDropdown from "./projectCard/dropdown" import { Clock, Globe, Lock } from "lucide-react" import Link from "next/link" import { Card } from "../ui/card" +import { deleteSandbox, updateSandbox } from "@/lib/actions" +import { toast } from "sonner" export default function DashboardProjects({ sandboxes, }: { sandboxes: Sandbox[] }) { + const onDelete = async (sandbox: Sandbox) => { + toast(`Project ${sandbox.name} deleted.`) + const res = await deleteSandbox(sandbox.id) + } + + const onVisibilityChange = async (sandbox: Sandbox) => { + const newVisibility = sandbox.visibility === "public" ? "private" : "public" + toast(`Project ${sandbox.name} is now ${newVisibility}.`) + const res = await updateSandbox({ + id: sandbox.id, + visibility: newVisibility, + }) + } + return (
My Projects
@@ -38,7 +56,11 @@ export default function DashboardProjects({
{sandbox.name}
- +
diff --git a/frontend/components/ui/alert-dialog.tsx b/frontend/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..2f30afa --- /dev/null +++ b/frontend/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} diff --git a/frontend/lib/actions.ts b/frontend/lib/actions.ts index 01c0f08..53d65e9 100644 --- a/frontend/lib/actions.ts +++ b/frontend/lib/actions.ts @@ -1,5 +1,7 @@ "use server" +import { revalidatePath } from "next/cache" + export async function createSandbox(body: { type: string name: string @@ -16,3 +18,27 @@ export async function createSandbox(body: { return await res.text() } + +export async function updateSandbox(body: { + id: string + name?: string + visibility?: "public" | "private" +}) { + const res = await fetch("http://localhost:8787/api/sandbox", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }) + + revalidatePath("/dashboard") +} + +export async function deleteSandbox(id: string) { + const res = await fetch(`http://localhost:8787/api/sandbox?id=${id}`, { + method: "DELETE", + }) + + revalidatePath("/dashboard") +} diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 42c01ef..56a263f 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -12,6 +12,7 @@ "@clerk/themes": "^1.7.12", "@hookform/resolvers": "^3.3.4", "@monaco-editor/react": "^4.6.0", + "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0", @@ -593,6 +594,34 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.5.tgz", + "integrity": "sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dialog": "1.0.5", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", diff --git a/frontend/package.json b/frontend/package.json index 37b3546..16a7417 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -13,6 +13,7 @@ "@clerk/themes": "^1.7.12", "@hookform/resolvers": "^3.3.4", "@monaco-editor/react": "^4.6.0", + "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-icons": "^1.3.0",