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
This commit is contained in:
Lukas May
2026-02-04 21:04:42 +01:00
parent e5acb9f214
commit f6caa5df1a
2 changed files with 144 additions and 0 deletions

View File

@@ -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 (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreHorizontal className="h-4 w-4" />
<span className="sr-only">More actions</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem disabled>Edit</DropdownMenuItem>
<DropdownMenuItem disabled>Duplicate</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem
onClick={handleArchive}
disabled={archiveMutation.isPending}
>
{archiveMutation.isPending ? "Archiving..." : "Archive"}
</DropdownMenuItem>
<DropdownMenuItem disabled>Delete</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@@ -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<string | null>(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 (
<DropdownMenu open={open} onOpenChange={setOpen}>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="sm" disabled={isPending}>
{successText ?? "Spawn Architect"}
<ChevronDown className="ml-1 h-3 w-3" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="start">
<DropdownMenuItem onClick={handleDiscuss} disabled={isPending}>
Discuss
</DropdownMenuItem>
<DropdownMenuItem onClick={handleBreakdown} disabled={isPending}>
Breakdown
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}