From 6954cf436492b5734951eb05b1f73b38087fc170 Mon Sep 17 00:00:00 2001 From: Leon Serfaty G Date: Fri, 18 Jul 2025 04:51:03 +0000 Subject: [PATCH] on the admin dashboard create a tab called "Flows" and add the estimate --- scripts/seed.ts | 27 ++++++ src/app/admin/flows/[id]/page.tsx | 134 ++++++++++++++++++++++++++++++ src/app/admin/flows/page.tsx | 107 ++++++++++++++++++++++++ src/app/admin/layout.tsx | 11 ++- src/lib/actions/flows.ts | 99 ++++++++++++++++++++++ src/lib/db.ts | 11 +++ 6 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 src/app/admin/flows/[id]/page.tsx create mode 100644 src/app/admin/flows/page.tsx create mode 100644 src/lib/actions/flows.ts diff --git a/scripts/seed.ts b/scripts/seed.ts index 6a684c7..3b438f3 100644 --- a/scripts/seed.ts +++ b/scripts/seed.ts @@ -23,6 +23,19 @@ function seed() { ) `); + // Create flows table + db.exec(` + CREATE TABLE IF NOT EXISTS flows ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + description TEXT, + path TEXT NOT NULL, + createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, + updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP + ) + `); + + // Check if the hourly_rate setting already exists const settingStmt = db.prepare('SELECT * FROM settings WHERE key = ?'); const hourlyRateSetting = settingStmt.get('hourly_rate'); @@ -98,6 +111,20 @@ function seed() { console.log('Default email template updated.'); } + // Seed default flow + const flowStmt = db.prepare('SELECT * FROM flows WHERE id = ?'); + const defaultFlow = flowStmt.get(1); + + if (!defaultFlow) { + const insertFlow = db.prepare( + "INSERT INTO flows (id, name, description, path) VALUES (?, ?, ?, ?)" + ); + insertFlow.run(1, 'Cost Estimator', 'The main cost estimation tool for clients.', '/'); + console.log('Default flow created.'); + } else { + console.log('Default flow already exists.'); + } + console.log('Seeding complete.'); } diff --git a/src/app/admin/flows/[id]/page.tsx b/src/app/admin/flows/[id]/page.tsx new file mode 100644 index 0000000..568fe87 --- /dev/null +++ b/src/app/admin/flows/[id]/page.tsx @@ -0,0 +1,134 @@ + +'use client'; + +import { useEffect, useState } from 'react'; +import { useFormState } from 'react-dom'; +import { getFlow, saveFlow, type Flow } from '@/lib/actions/flows'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { Label } from '@/components/ui/label'; +import { Textarea } from '@/components/ui/textarea'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card'; +import { useToast } from '@/hooks/use-toast'; +import Link from 'next/link'; +import { ChevronLeft } from 'lucide-react'; +import { Skeleton } from '@/components/ui/skeleton'; + +interface FlowFormPageProps { + params: { id: string }; +} + +function SubmitButton({ isNew }: { isNew: boolean }) { + return ( + + ); +} + +export default function FlowFormPage({ params }: FlowFormPageProps) { + const [state, formAction] = useFormState(saveFlow, { success: false, message: '', errors: null }); + const [flow, setFlow] = useState(null); + const [isLoading, setIsLoading] = useState(true); + const { toast } = useToast(); + + const isNew = params.id === 'new'; + + useEffect(() => { + if (!isNew) { + const fetchFlow = async () => { + setIsLoading(true); + const existingFlow = await getFlow(Number(params.id)); + setFlow(existingFlow); + setIsLoading(false); + }; + fetchFlow(); + } else { + setIsLoading(false); + } + }, [params.id, isNew]); + + useEffect(() => { + if (state.message) { + toast({ + title: state.success ? 'Success!' : 'Error', + description: state.message, + variant: state.success ? 'default' : 'destructive', + }); + } + }, [state, toast]); + + if (isLoading) { + return ( +
+ + + + + + + +
+ + +
+
+ + +
+
+ + +
+
+
+ +
+ ) + } + + return ( +
+
+ +

+ {isNew ? 'Create New Flow' : 'Edit Flow'} +

+
+ + + + Flow Details + + {isNew ? 'Fill in the details for your new flow.' : 'Update the details for this flow.'} + + + + +
+ + + {state.errors?.name &&

{state.errors.name[0]}

} +
+
+ + + {state.errors?.path &&

{state.errors.path[0]}

} +
+
+ +