the flow we just created is not showing up

This commit is contained in:
Leon Serfaty G
2025-07-18 04:52:21 +00:00
parent 6954cf4364
commit 80aa0f32ba
3 changed files with 58 additions and 14 deletions
+14 -9
View File
@@ -2,7 +2,7 @@
'use client'; 'use client';
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { useFormState } from 'react-dom'; import { useFormState, useFormStatus } from 'react-dom';
import { getFlow, saveFlow, type Flow } from '@/lib/actions/flows'; import { getFlow, saveFlow, type Flow } from '@/lib/actions/flows';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input'; import { Input } from '@/components/ui/input';
@@ -19,9 +19,10 @@ interface FlowFormPageProps {
} }
function SubmitButton({ isNew }: { isNew: boolean }) { function SubmitButton({ isNew }: { isNew: boolean }) {
const { pending } = useFormStatus();
return ( return (
<Button type="submit"> <Button type="submit" disabled={pending}>
{isNew ? 'Create Flow' : 'Save Changes'} {pending ? (isNew ? 'Creating...' : 'Saving...') : (isNew ? 'Create Flow' : 'Save Changes')}
</Button> </Button>
); );
} }
@@ -49,11 +50,11 @@ export default function FlowFormPage({ params }: FlowFormPageProps) {
}, [params.id, isNew]); }, [params.id, isNew]);
useEffect(() => { useEffect(() => {
if (state.message) { if (state.message && !state.success) {
toast({ toast({
title: state.success ? 'Success!' : 'Error', title: 'Error',
description: state.message, description: state.message,
variant: state.success ? 'default' : 'destructive', variant: 'destructive',
}); });
} }
}, [state, toast]); }, [state, toast]);
@@ -87,6 +88,10 @@ export default function FlowFormPage({ params }: FlowFormPageProps) {
) )
} }
const getError = (field: string) => {
return state.errors?.find(e => e.path?.[0] === field)?.message;
}
return ( return (
<form action={formAction} className="space-y-8"> <form action={formAction} className="space-y-8">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
@@ -113,17 +118,17 @@ export default function FlowFormPage({ params }: FlowFormPageProps) {
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="name">Flow Name</Label> <Label htmlFor="name">Flow Name</Label>
<Input id="name" name="name" defaultValue={flow?.name} required /> <Input id="name" name="name" defaultValue={flow?.name} required />
{state.errors?.name && <p className="text-sm text-destructive">{state.errors.name[0]}</p>} {getError('name') && <p className="text-sm text-destructive">{getError('name')}</p>}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="path">Path</Label> <Label htmlFor="path">Path</Label>
<Input id="path" name="path" defaultValue={flow?.path} placeholder="/my-cool-flow" required /> <Input id="path" name="path" defaultValue={flow?.path} placeholder="/my-cool-flow" required />
{state.errors?.path && <p className="text-sm text-destructive">{state.errors.path[0]}</p>} {getError('path') && <p className="text-sm text-destructive">{getError('path')}</p>}
</div> </div>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="description">Description</Label> <Label htmlFor="description">Description</Label>
<Textarea id="description" name="description" defaultValue={flow?.description || ''} placeholder="A brief description of what this flow does." /> <Textarea id="description" name="description" defaultValue={flow?.description || ''} placeholder="A brief description of what this flow does." />
{state.errors?.description && <p className="text-sm text-destructive">{state.errors.description[0]}</p>} {getError('description') && <p className="text-sm text-destructive">{getError('description')}</p>}
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
+16 -3
View File
@@ -22,6 +22,12 @@ const flowSchema = z.object({
path: z.string().min(1, 'Path is required').startsWith('/', { message: 'Path must start with /' }), path: z.string().min(1, 'Path is required').startsWith('/', { message: 'Path must start with /' }),
}); });
type State = {
success: boolean;
message: string;
errors?: z.ZodIssue[] | null;
}
export async function getFlows(): Promise<Flow[]> { export async function getFlows(): Promise<Flow[]> {
try { try {
const stmt = db.prepare( const stmt = db.prepare(
@@ -46,14 +52,14 @@ export async function getFlow(id: number): Promise<Flow | null> {
} }
} }
export async function saveFlow(formData: FormData) { export async function saveFlow(prevState: State, formData: FormData): Promise<State> {
const validatedFields = flowSchema.safeParse(Object.fromEntries(formData.entries())); const validatedFields = flowSchema.safeParse(Object.fromEntries(formData.entries()));
if (!validatedFields.success) { if (!validatedFields.success) {
return { return {
success: false, success: false,
message: 'Invalid fields.', message: 'Invalid fields.',
errors: validatedFields.error.flatten().fieldErrors, errors: validatedFields.error.issues,
}; };
} }
@@ -75,9 +81,16 @@ export async function saveFlow(formData: FormData) {
} }
} catch (error: any) { } catch (error: any) {
console.error('Failed to save flow:', error); console.error('Failed to save flow:', error);
if (error.code === 'SQLITE_CONSTRAINT_UNIQUE') {
return {
success: false,
message: 'A flow with this path already exists.',
errors: [{ path: ['path'], message: 'A flow with this path already exists.', code: 'custom' }],
};
}
return { return {
success: false, success: false,
message: error.code === 'SQLITE_CONSTRAINT_UNIQUE' ? 'A flow with this path already exists.' : 'An internal error occurred.', message: 'An internal error occurred.',
errors: null, errors: null,
}; };
} }
+28 -2
View File
@@ -4,7 +4,7 @@ import Database from 'better-sqlite3';
// Use a file-based database in development // Use a file-based database in development
const db = new Database('local.db'); const db = new Database('local.db');
// Create the tables if they don't exist // --- SCHEMA CREATION ---
db.exec(` db.exec(`
CREATE TABLE IF NOT EXISTS users ( CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
@@ -44,11 +44,37 @@ db.exec(`
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL,
description TEXT, description TEXT,
path TEXT NOT NULL, path TEXT NOT NULL UNIQUE,
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP, createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
) )
`); `);
// --- SEEDING LOGIC ---
console.log('Running database checks and seeding if necessary...');
// Seed default user
const userStmt = db.prepare('SELECT id FROM users WHERE id = ?');
const defaultUser = userStmt.get(1);
if (!defaultUser) {
const insertUser = db.prepare(
"INSERT INTO users (id, email, password, name) VALUES (?, ?, ?, ?)"
);
insertUser.run(1, 'admin@example.com', 'password', 'Admin User');
console.log('Default user created.');
}
// Seed default flow
const flowStmt = db.prepare("SELECT id FROM flows WHERE path = ?");
const defaultFlow = flowStmt.get('/');
if (!defaultFlow) {
const insertFlow = db.prepare(
"INSERT INTO flows (name, description, path) VALUES (?, ?, ?)"
);
insertFlow.run('Cost Estimator', 'The main cost estimation tool for clients.', '/');
console.log('Default flow created.');
}
console.log('Database setup complete.');
export default db; export default db;