name: scaffold-form description: Generate a React Hook Form + Zod form component with create/edit mode user_invocable: true arguments:
- name: resource description: "Resource name in singular form (e.g. 'size', 'color')" required: true
- name: fields description: "Comma-separated field definitions: name:type:label (e.g. 'name:string:Name,value:string:Value'). Defaults to 'name:string:Name,value:string:Value'" required: false
Scaffold Form Component
Generate a form component following the lemnArt admin pattern with React Hook Form, Zod validation, create/edit mode, and delete support.
Inputs
$ARGUMENTS.resource— singular resource name (e.g.tag)$ARGUMENTS.fields— comma-separatedname:type:labeltriples (default:name:string:Name,value:string:Value)
Output Path
admin/app/(dashboard)/[storeId]/(routes)/<resources>/[<resourceId>]/components/<resource>-form.tsx
Template
"use client"
import { useState } from 'react'
import * as z from 'zod'
import { Resource } from "@prisma/client";
import { Heading } from "@/components/ui/heading";
import { Button } from "@/components/ui/button";
import { Separator } from "@/components/ui/separator";
import { Trash } from "lucide-react";
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { toast } from 'react-hot-toast';
import axios from 'axios';
import { useParams, useRouter } from 'next/navigation';
import { AlertModal } from '@/components/modals/alert-modal';
interface ResourceFormProps {
initialData: Resource | null;
}
const formSchema = z.object({
// For each field:
// name: z.string().min(1),
// For boolean fields: z.boolean().default(false)
// For number fields: z.coerce.number().min(0)
})
type ResourceFormValues = z.infer<typeof formSchema>;
export const ResourceForm: React.FC<ResourceFormProps> = ({ initialData }) => {
const params = useParams();
const router = useRouter();
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const title = initialData ? 'Edit resource' : 'Create resource'
const description = initialData ? 'Edit a resource' : 'Add a new resource'
const toastMessage = initialData ? 'Resource updated.' : 'Resource created.'
const action = initialData ? 'Save changes' : 'Create'
const form = useForm<ResourceFormValues>({
resolver: zodResolver(formSchema),
defaultValues: initialData || {
// default empty values for each field
}
});
const onSubmit = async (data: ResourceFormValues) => {
try {
setLoading(true);
if (initialData) {
await axios.patch(`/api/${params.storeId}/resources/${params.resourceId}`, data)
} else {
await axios.post(`/api/${params.storeId}/resources`, data)
}
router.refresh();
router.push(`/${params.storeId}/resources`);
toast.success(toastMessage)
} catch(err) {
toast.error("Something went wrong.");
} finally {
setLoading(false)
}
}
const onDelete = async () => {
try {
setLoading(true);
await axios.delete(`/api/${params.storeId}/resources/${params.resourceId}`)
router.refresh();
router.push(`/${params.storeId}/resources`)
toast.success("Resource deleted.")
} catch(err) {
toast.error("Make sure you removed all dependencies first.");
} finally {
setLoading(false)
setOpen(false);
}
}
return (
<>
<AlertModal isOpen={open} onClose={() => setOpen(false)} onConfirm={onDelete} loading={loading} />
<div className="flex items-center justify-between">
<Heading title={title} description={description} />
{initialData && (
<Button variant="destructive" size="sm" onClick={() => setOpen(true)} disabled={loading}>
<Trash className="w-4 h-4" />
</Button>
)}
</div>
<Separator />
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="w-full space-y-8">
<div className='grid grid-cols-3 gap-8'>
{/* FormField for each field */}
</div>
<Button disabled={loading} className='ml-auto' type='submit'>{action}</Button>
</form>
</Form>
</>
)
}
FormField Patterns
String field:
<FormField
control={form.control}
name="fieldName"
render={({field}) => (
<FormItem>
<FormLabel>Field Label</FormLabel>
<FormControl>
<Input disabled={loading} placeholder='Field placeholder' {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
Boolean field (checkbox):
<FormField
control={form.control}
name="fieldName"
render={({field}) => (
<FormItem className="flex flex-row items-start space-x-3 space-y-0 rounded-md border p-4">
<FormControl>
<Checkbox checked={field.value} onCheckedChange={field.onChange} />
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Field Label</FormLabel>
</div>
</FormItem>
)}
/>
Image upload field:
<FormField
control={form.control}
name="imageUrl"
render={({field}) => (
<FormItem>
<FormLabel>Image</FormLabel>
<FormControl>
<ImageUpload
value={field.value ? [field.value] : []}
disabled={loading}
onChange={(url) => field.onChange(url)}
onRemove={() => field.onChange("")}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
Instructions
- Ask the user for the resource name and fields if not provided.
- Derive naming variants (singular, plural, PascalCase, camelCase).
- Replace all placeholder tokens with actual names.
- Generate a Zod schema entry and FormField for each field based on its type.
- Set appropriate default values:
''for strings,falsefor booleans,0for numbers. - Create the file at the correct path.
Next.js App Router Expert
Development
A skill that turns Claude into a Next.js App Router expert.
README Generator
Development
Creates professional and comprehensive README.md files for your projects.
API Documentation Writer
Development
Generates comprehensive API documentation in OpenAPI/Swagger format.