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:
64
packages/web/src/components/ActionMenu.tsx
Normal file
64
packages/web/src/components/ActionMenu.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
80
packages/web/src/components/SpawnArchitectDropdown.tsx
Normal file
80
packages/web/src/components/SpawnArchitectDropdown.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user