From f6caa5df1a6953199a8cd4c61a1d49f6fa7141cf Mon Sep 17 00:00:00 2001 From: Lukas May Date: Wed, 4 Feb 2026 21:04:42 +0100 Subject: [PATCH] feat(17-03): add SpawnArchitectDropdown and ActionMenu components - SpawnArchitectDropdown: discuss/breakdown modes via tRPC mutations - Brief success state on button text after spawn - ActionMenu: archive with browser confirm, disabled edit/duplicate/delete - No deleteInitiative tRPC procedure exists, so delete is placeholder - Both components invalidate listInitiatives on success --- packages/web/src/components/ActionMenu.tsx | 64 +++++++++++++++ .../src/components/SpawnArchitectDropdown.tsx | 80 +++++++++++++++++++ 2 files changed, 144 insertions(+) create mode 100644 packages/web/src/components/ActionMenu.tsx create mode 100644 packages/web/src/components/SpawnArchitectDropdown.tsx diff --git a/packages/web/src/components/ActionMenu.tsx b/packages/web/src/components/ActionMenu.tsx new file mode 100644 index 0000000..2713446 --- /dev/null +++ b/packages/web/src/components/ActionMenu.tsx @@ -0,0 +1,64 @@ +import { MoreHorizontal } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuSeparator, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { trpc } from "@/lib/trpc"; + +interface ActionMenuProps { + initiativeId: string; + onDelete?: () => void; +} + +export function ActionMenu({ initiativeId, onDelete }: ActionMenuProps) { + const utils = trpc.useUtils(); + + const archiveMutation = trpc.updateInitiative.useMutation({ + onSuccess: () => { + utils.listInitiatives.invalidate(); + onDelete?.(); + }, + onError: (err) => { + console.error("Failed to archive initiative:", err.message); + }, + }); + + function handleArchive() { + const confirmed = window.confirm( + "Are you sure you want to archive this initiative? It can be restored later." + ); + if (!confirmed) return; + + archiveMutation.mutate({ + id: initiativeId, + status: "archived", + }); + } + + return ( + + + + + + Edit + Duplicate + + + {archiveMutation.isPending ? "Archiving..." : "Archive"} + + Delete + + + ); +} diff --git a/packages/web/src/components/SpawnArchitectDropdown.tsx b/packages/web/src/components/SpawnArchitectDropdown.tsx new file mode 100644 index 0000000..e54bb97 --- /dev/null +++ b/packages/web/src/components/SpawnArchitectDropdown.tsx @@ -0,0 +1,80 @@ +import { useState } from "react"; +import { ChevronDown } from "lucide-react"; +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { trpc } from "@/lib/trpc"; + +interface SpawnArchitectDropdownProps { + initiativeId: string; + initiativeName: string; +} + +export function SpawnArchitectDropdown({ + initiativeId, + initiativeName, +}: SpawnArchitectDropdownProps) { + const [open, setOpen] = useState(false); + const [successText, setSuccessText] = useState(null); + + const discussMutation = trpc.spawnArchitectDiscuss.useMutation({ + onSuccess: () => { + setOpen(false); + setSuccessText("Spawned!"); + setTimeout(() => setSuccessText(null), 2000); + }, + onError: (err) => { + console.error("Failed to spawn discuss architect:", err.message); + }, + }); + + const breakdownMutation = trpc.spawnArchitectBreakdown.useMutation({ + onSuccess: () => { + setOpen(false); + setSuccessText("Spawned!"); + setTimeout(() => setSuccessText(null), 2000); + }, + onError: (err) => { + console.error("Failed to spawn breakdown architect:", err.message); + }, + }); + + const isPending = discussMutation.isPending || breakdownMutation.isPending; + + function handleDiscuss() { + discussMutation.mutate({ + name: initiativeName + "-discuss", + initiativeId, + }); + } + + function handleBreakdown() { + breakdownMutation.mutate({ + name: initiativeName + "-breakdown", + initiativeId, + }); + } + + return ( + + + + + + + Discuss + + + Breakdown + + + + ); +}