diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..03adc8d --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,4 @@ +{ + "IDX.aI.enableInlineCompletion": true, + "IDX.aI.enableCodebaseIndexing": true +} \ No newline at end of file diff --git a/src/app/admin/flows/page.tsx b/src/app/admin/flows/page.tsx index 0be065b..2811f89 100644 --- a/src/app/admin/flows/page.tsx +++ b/src/app/admin/flows/page.tsx @@ -1,6 +1,9 @@ +'use client'; + import Link from 'next/link'; -import { getFlows, type Flow } from '@/lib/actions/flows'; +import { useEffect, useState, useTransition } from 'react'; +import { getFlows, deleteFlow, type Flow } from '@/lib/actions/flows'; import { Button } from '@/components/ui/button'; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; @@ -15,9 +18,89 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { Badge } from '@/components/ui/badge'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogDescription, + AlertDialogFooter, + AlertDialogHeader, + AlertDialogTitle, +} from "@/components/ui/alert-dialog" +import { useToast } from '@/hooks/use-toast'; +import { Skeleton } from '@/components/ui/skeleton'; -export default async function FlowsPage() { - const flows = await getFlows(); + +function DeleteFlowButton({ flowId, onFlowDeleted }: { flowId: number, onFlowDeleted: (id: number) => void }) { + const [isDialogOpen, setIsDialogOpen] = useState(false); + const [isPending, startTransition] = useTransition(); + const { toast } = useToast(); + + const handleDelete = () => { + startTransition(async () => { + const result = await deleteFlow(flowId); + if (result.success) { + toast({ + title: 'Success!', + description: result.message, + }); + onFlowDeleted(flowId); + setIsDialogOpen(false); + } else { + toast({ + variant: 'destructive', + title: 'Error', + description: result.message, + }); + } + }); + }; + + return ( + <> + + + + Are you absolutely sure? + + This action cannot be undone. This will permanently delete the flow. + + + + Cancel + + {isPending ? 'Deleting...' : 'Continue'} + + + + + setIsDialogOpen(true)} + > + Delete + + + ); +} + + +export default function FlowsPage() { + const [flows, setFlows] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + setIsLoading(true); + getFlows().then(data => { + setFlows(data); + setIsLoading(false); + }); + }, []); + + const handleFlowDeleted = (deletedFlowId: number) => { + setFlows(currentFlows => currentFlows.filter(flow => flow.id !== deletedFlowId)); + } return (
@@ -55,7 +138,19 @@ export default async function FlowsPage() { - {flows.length > 0 ? ( + {isLoading ? ( + <> + {[...Array(3)].map((_, i) => ( + + + + + + + + ))} + + ) : flows.length > 0 ? ( flows.map((flow) => ( {flow.name} @@ -83,9 +178,7 @@ export default async function FlowsPage() { View - - Delete - + diff --git a/src/lib/actions/flows.ts b/src/lib/actions/flows.ts index ac3d06f..b9ba7c7 100644 --- a/src/lib/actions/flows.ts +++ b/src/lib/actions/flows.ts @@ -67,6 +67,16 @@ export async function saveFlow(prevState: State, formData: FormData): Promise { try { + // Prevent deletion of the default flow (ID 1) + if (id === 1) { + return { success: false, message: "The default flow cannot be deleted." }; + } const stmt = db.prepare('DELETE FROM flows WHERE id = ?'); stmt.run(id); revalidatePath('/admin/flows');