npm run build
This commit is contained in:
@@ -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 (
|
||||
<>
|
||||
<AlertDialog open={isDialogOpen} onOpenChange={setIsDialogOpen}>
|
||||
<AlertDialogContent>
|
||||
<AlertDialogHeader>
|
||||
<AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
|
||||
<AlertDialogDescription>
|
||||
This action cannot be undone. This will permanently delete the flow.
|
||||
</AlertDialogDescription>
|
||||
</AlertDialogHeader>
|
||||
<AlertDialogFooter>
|
||||
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
||||
<AlertDialogAction onClick={handleDelete} disabled={isPending}>
|
||||
{isPending ? 'Deleting...' : 'Continue'}
|
||||
</AlertDialogAction>
|
||||
</AlertDialogFooter>
|
||||
</AlertDialogContent>
|
||||
</AlertDialog>
|
||||
<DropdownMenuItem
|
||||
className="text-destructive focus:bg-destructive/10 focus:text-destructive"
|
||||
onSelect={() => setIsDialogOpen(true)}
|
||||
>
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export default function FlowsPage() {
|
||||
const [flows, setFlows] = useState<Flow[]>([]);
|
||||
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 (
|
||||
<div className="space-y-8">
|
||||
@@ -55,7 +138,19 @@ export default async function FlowsPage() {
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{flows.length > 0 ? (
|
||||
{isLoading ? (
|
||||
<>
|
||||
{[...Array(3)].map((_, i) => (
|
||||
<TableRow key={i}>
|
||||
<TableCell><Skeleton className="h-5 w-24" /></TableCell>
|
||||
<TableCell><Skeleton className="h-5 w-48" /></TableCell>
|
||||
<TableCell><Skeleton className="h-5 w-16" /></TableCell>
|
||||
<TableCell><Skeleton className="h-5 w-32" /></TableCell>
|
||||
<TableCell className="text-right"><Skeleton className="h-8 w-8 ml-auto" /></TableCell>
|
||||
</TableRow>
|
||||
))}
|
||||
</>
|
||||
) : flows.length > 0 ? (
|
||||
flows.map((flow) => (
|
||||
<TableRow key={flow.id}>
|
||||
<TableCell className="font-medium">{flow.name}</TableCell>
|
||||
@@ -83,9 +178,7 @@ export default async function FlowsPage() {
|
||||
<DropdownMenuItem>View</DropdownMenuItem>
|
||||
</Link>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem className="text-destructive focus:bg-destructive/10 focus:text-destructive">
|
||||
Delete
|
||||
</DropdownMenuItem>
|
||||
<DeleteFlowButton flowId={flow.id} onFlowDeleted={handleFlowDeleted} />
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</TableCell>
|
||||
|
||||
@@ -67,6 +67,16 @@ export async function saveFlow(prevState: State, formData: FormData): Promise<St
|
||||
|
||||
try {
|
||||
if (id) {
|
||||
// Check if path is unique before updating
|
||||
const checkStmt = db.prepare('SELECT id FROM flows WHERE path = ? AND id != ?');
|
||||
const existing = checkStmt.get(path, id);
|
||||
if (existing) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'A flow with this path already exists.',
|
||||
errors: [{ path: ['path'], message: 'A flow with this path already exists.', code: 'custom' }],
|
||||
};
|
||||
}
|
||||
// Update existing flow
|
||||
const stmt = db.prepare(
|
||||
'UPDATE flows SET name = ?, description = ?, path = ?, updatedAt = CURRENT_TIMESTAMP WHERE id = ?'
|
||||
@@ -101,6 +111,10 @@ export async function saveFlow(prevState: State, formData: FormData): Promise<St
|
||||
|
||||
export async function deleteFlow(id: number): Promise<{ success: boolean, message: string }> {
|
||||
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');
|
||||
|
||||
Reference in New Issue
Block a user