diff --git a/prisma/migrations/20260615000001_v0_2_flower_categories/migration.sql b/prisma/migrations/20260615000001_v0_2_flower_categories/migration.sql new file mode 100644 index 0000000..c6f25b0 --- /dev/null +++ b/prisma/migrations/20260615000001_v0_2_flower_categories/migration.sql @@ -0,0 +1,4 @@ +-- Add annual flower and perennial flower to PlantCategory enum +ALTER TYPE "PlantCategory" ADD VALUE 'ANNUAL_FLOWER'; +ALTER TYPE "PlantCategory" ADD VALUE 'PERENNIAL_FLOWER'; +ALTER TYPE "PlantCategory" ADD VALUE 'ANNUAL_VEGETABLE'; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 175e881..0772784 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -61,10 +61,13 @@ enum PlantCategory { HERB // comfrey, yarrow, mint, chives GROUNDCOVER // strawberries, clover, creeping thyme VINE // grapes, hops, kiwi - ANNUAL // tomatoes, squash, beans — seasonal - PERENNIAL // asparagus, rhubarb, perennial veg - BULB // garlic, onions, tulips - MUSHROOM // shiitake log, oyster bed, etc. + ANNUAL_VEGETABLE // tomatoes, squash, beans, peppers — seasonal veg + ANNUAL // legacy, treated as annual vegetable + ANNUAL_FLOWER // zinnias, marigolds, nasturtiums, cosmos + PERENNIAL // asparagus, rhubarb, perennial veg + PERENNIAL_FLOWER // coneflower, bee balm, black-eyed susan + BULB // garlic, onions, tulips, daffodils + MUSHROOM // shiitake log, oyster bed, etc. OTHER } diff --git a/src/components/garden/add-plant-button.tsx b/src/components/garden/add-plant-button.tsx index 69c1cf6..bc06365 100644 --- a/src/components/garden/add-plant-button.tsx +++ b/src/components/garden/add-plant-button.tsx @@ -1,6 +1,6 @@ "use client"; -import { useState } from "react"; +import { useState, useRef, useEffect } from "react"; import { useRouter } from "next/navigation"; import { useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; @@ -17,8 +17,10 @@ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { useToast } from "@/hooks/use-toast"; -import { Plus } from "lucide-react"; +import { Plus, Leaf } from "lucide-react"; import { CATEGORY_LABELS, CATEGORY_ORDER } from "@/lib/garden/categories"; +import { searchPlants, type PlantSuggestion } from "@/lib/garden/plant-db"; +import { cn } from "@/lib/utils"; const schema = z.object({ commonName: z.string().min(1, "Name is required"), @@ -34,6 +36,10 @@ type FormValues = z.infer; export function AddPlantButton() { const [open, setOpen] = useState(false); + const [suggestions, setSuggestions] = useState([]); + const [showSuggestions, setShowSuggestions] = useState(false); + const [category, setCategory] = useState("CANOPY_TREE"); + const nameRef = useRef(null); const router = useRouter(); const { toast } = useToast(); @@ -42,6 +48,23 @@ export function AddPlantButton() { defaultValues: { category: "CANOPY_TREE" }, }); + function onNameChange(value: string) { + form.setValue("commonName", value); + const results = searchPlants(value); + setSuggestions(results); + setShowSuggestions(results.length > 0); + } + + function applySuggestion(suggestion: PlantSuggestion) { + form.setValue("commonName", suggestion.commonName); + if (suggestion.species) form.setValue("species", suggestion.species); + if (suggestion.notes && !form.getValues("notes")) form.setValue("notes", suggestion.notes); + form.setValue("category", suggestion.category); + setCategory(suggestion.category); + setSuggestions([]); + setShowSuggestions(false); + } + async function onSubmit(values: FormValues) { const res = await fetch("/api/plants", { method: "POST", @@ -57,13 +80,21 @@ export function AddPlantButton() { toast({ title: "Plant added!", description: `${values.commonName} is now in your food forest.` }); setOpen(false); form.reset(); + setCategory("CANOPY_TREE"); + setSuggestions([]); router.push(`/garden/${plant.id}`); router.refresh(); } + function handleOpen() { + setOpen(true); + setSuggestions([]); + setShowSuggestions(false); + } + return ( <> - @@ -76,12 +107,48 @@ export function AddPlantButton() {
-
- - + + {/* Name with autocomplete */} +
+ + onNameChange(e.target.value)} + onBlur={() => setTimeout(() => setShowSuggestions(false), 150)} + onFocus={() => suggestions.length > 0 && setShowSuggestions(true)} + autoComplete="off" + /> {form.formState.errors.commonName && (

{form.formState.errors.commonName.message}

)} + + {/* Suggestion dropdown */} + {showSuggestions && ( +
+ {suggestions.map((s) => ( + + ))} +
+ Don't see it? Just type the name and fill in the rest yourself. +
+
+ )}
@@ -92,8 +159,12 @@ export function AddPlantButton() {
@@ -128,7 +199,7 @@ export function AddPlantButton() {
-