diff --git a/src/components/DataForm.tsx b/src/components/DataForm.tsx new file mode 100644 index 0000000..de1002e --- /dev/null +++ b/src/components/DataForm.tsx @@ -0,0 +1,195 @@ +// --------------------------------------------------------------------------- +// DataForm — pre-built form component wired to AnyDB insertOne +// --------------------------------------------------------------------------- +// PRE-INSTALLED. CodeWriterAgent just uses: +// import { DataForm } from "@/components/DataForm" +// +// --------------------------------------------------------------------------- + +import { useState } from "react"; +import { useMutation } from "@/hooks/use-anydb"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { useToast } from "@/hooks/use-toast"; +import { Loader2, CheckCircle } from "lucide-react"; + +interface FormField { + name: string; + label: string; + type?: "text" | "email" | "tel" | "number" | "date" | "time" | "url" | "textarea" | "select"; + placeholder?: string; + required?: boolean; + options?: { value: string; label: string }[]; // For select type + rows?: number; // For textarea + min?: number; + max?: number; + defaultValue?: string | number; + className?: string; + gridCol?: number; // 1 or 2 — for grid layout +} + +interface DataFormProps { + collection: string; + fields: FormField[]; + submitLabel?: string; + successMessage?: string; + onSuccess?: (data: any) => void; + className?: string; + layout?: "stack" | "grid"; // stack = vertical, grid = 2-col +} + +export function DataForm({ + collection, + fields, + submitLabel = "Submit", + successMessage = "Saved successfully!", + onSuccess, + className = "", + layout = "stack", +}: DataFormProps) { + const { toast } = useToast(); + const { mutate, loading } = useMutation(collection); + const [submitted, setSubmitted] = useState(false); + + // Build initial form state from fields + const initialState: Record = {}; + fields.forEach(f => { initialState[f.name] = f.defaultValue || ""; }); + const [formData, setFormData] = useState(initialState); + + const handleChange = (name: string, value: any) => { + setFormData(prev => ({ ...prev, [name]: value })); + }; + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + // Basic validation + for (const field of fields) { + if (field.required && !formData[field.name]) { + toast({ + title: "Required field", + description: `Please fill in ${field.label}`, + variant: "destructive", + }); + return; + } + if (field.type === "email" && formData[field.name] && !formData[field.name].includes("@")) { + toast({ + title: "Invalid email", + description: "Please enter a valid email address", + variant: "destructive", + }); + return; + } + } + + try { + const result = await mutate(formData); + setSubmitted(true); + toast({ title: "Success", description: successMessage }); + setFormData(initialState); + onSuccess?.(result); + + // Reset submitted state after 3 seconds + setTimeout(() => setSubmitted(false), 3000); + } catch (err) { + toast({ + title: "Error", + description: "Something went wrong. Please try again.", + variant: "destructive", + }); + } + }; + + const renderField = (field: FormField) => { + const id = `form-${collection}-${field.name}`; + + return ( +
+ + + {field.type === "textarea" ? ( +