"use client";
import { Product, Image, Color, Category, Size } from "@prisma/client";
import { Heading } from "@/components/ui/heading";
import { Button } from "@/components/ui/button";
import { Trash } from "lucide-react";
import { Separator } from "@/components/ui/separator";
import * as z from "zod";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { useState } from "react";
import {
FormControl,
FormField,
FormItem,
FormLabel,
Form,
FormMessage,
FormDescription,
} 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";
import ImageUpload from "@/components/ui/image-upload";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select";
import { Checkbox } from "@/components/ui/checkbox";
const formSchema = z.object({
// name: z.string().min(1), -- in prisma schema we have label and imageUrl, not a name.
name: z.string().min(1),
image: z.object({ url: z.string() }).array(),
price: z.coerce.number().min(1),
categoryId: z.string().min(1),
colorId: z.string().min(1),
sizeId: z.string().min(1),
isFeatured: z.boolean().default(false).optional(),
isArchived: z.boolean().default(false).optional(),
});
type ProductFormValues = z.infer<typeof formSchema> & { images: Image[] };
interface ProductFormProps {
initialData:
| (Product & {
images: Image[];
})
| null;
categories: Category[];
colors: Color[];
sizes: Size[];
}
export const ProductForm: React.FC<ProductFormProps> = ({
initialData,
categories,
colors,
sizes,
}) => {
const [open, setOpen] = useState(false);
const [loading, setLoading] = useState(false);
const params = useParams();
const router = useRouter();
const title = initialData ? "Edit product" : "Create product";
const description = initialData ? "Edit a product" : "Add a product";
const toastMessage = initialData ? "Product updated" : "Product created.";
const action = initialData ? "Save changes" : "Create";
const form = useForm<ProductFormValues>({
resolver: zodResolver(formSchema),
defaultValues: initialData
? {
...initialData,
price: parseFloat(String(initialData?.price)),
}
: {
name: "",
images: [],
price: 0,
categoryId: "",
colorId: "",
sizeId: "",
isFeatured: false,
isArchived: false,
},
});
const onSubmit = async (data: ProductFormValues) => {
try {
console.log("aaa");
setLoading(true);
if (initialData) {
await axios.patch(
`/api/${params.storeId}/products/${params.productId}`,
data
);
} else {
await axios.post(`/api/${params.storeId}/products`, data);
}
router.push(`/${params.storeId}/products`);
toast.success(toastMessage);
} catch (error) {
toast.error("Something went wrong.");
} finally {
setLoading(false);
router.refresh();
}
};
const onDelete = async () => {
try {
setLoading(true);
await axios.delete(
`/api//${params.storeId}/products/${params.productId}`
);
router.push(`/${params.storeId}/products`);
toast.success("Product deleted.");
} catch (error) {
toast.error("Something went wrong.");
} finally {
setLoading(false);
setOpen(false);
router.refresh();
}
};
const onInvalid = (errors: any) => console.error(errors);
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)}>
<Trash className="h-4 w-4" />
</Button>
)}
</div>
<Separator />
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit, onInvalid)}
className="space-y-8 w-full"
>
<FormField
control={form.control}
name="images"
render={({ field }) => (
<FormItem>
<FormLabel>Images</FormLabel>
<FormControl>
<ImageUpload
value={field.value.map((image) => image.url)}
disabled={loading}
onChange={(url) =>
field.onChange([...field.value, { url }])
}
onRemove={(url) =>
field.onChange([
...field.value.filter((current) => current.url !== url),
])
}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<div className="grid grid-cols-3 gap-8">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input
disabled={loading}
placeholder="Product name"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="price"
render={({ field }) => (
<FormItem>
<FormLabel>Price</FormLabel>
<FormControl>
<Input
type="number"
disabled={loading}
placeholder="9.99"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="categoryId"
render={({ field }) => (
<FormItem>
<FormLabel>Category</FormLabel>
<Select
disabled={loading}
onValueChange={field.onChange}
value={field.value}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue
defaultValue={field.value}
placeholder="Select a category"
></SelectValue>
</SelectTrigger>
</FormControl>
<SelectContent>
{categories.map((category) => (
<SelectItem key={category.id} value={category.id}>
{category.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="sizeId"
render={({ field }) => (
<FormItem>
<FormLabel>Size</FormLabel>
<Select
disabled={loading}
onValueChange={field.onChange}
value={field.value}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue
defaultValue={field.value}
placeholder="Select a size"
></SelectValue>
</SelectTrigger>
</FormControl>
<SelectContent>
{sizes.map((size) => (
<SelectItem key={size.id} value={size.id}>
{size.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="colorId"
render={({ field }) => (
<FormItem>
<FormLabel>Color</FormLabel>
<Select
disabled={loading}
onValueChange={field.onChange}
value={field.value}
defaultValue={field.value}
>
<FormControl>
<SelectTrigger>
<SelectValue
defaultValue={field.value}
placeholder="Select a color"
></SelectValue>
</SelectTrigger>
</FormControl>
<SelectContent>
{colors.map((color) => (
<SelectItem key={color.id} value={color.id}>
{color.name}
</SelectItem>
))}
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="isFeatured"
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}
></Checkbox>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Featured</FormLabel>
<FormDescription>
This form will appear on the home page.
</FormDescription>
</div>
</FormItem>
)}
/>
<FormField
control={form.control}
name="isArchived"
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}
></Checkbox>
</FormControl>
<div className="space-y-1 leading-none">
<FormLabel>Archived</FormLabel>
<FormDescription>
This form will not appear anywhere in the store.
</FormDescription>
</div>
</FormItem>
)}
/>
</div>
<Button disabled={loading} className="ml-auto" type="submit">
{action}
</Button>
</form>
</Form>
<Separator />
</>
);
};
export default ProductForm;
我试图在提交表单时在数据库中创建一个产品,并且产品的数据基于表单数据。但是,当我按下按钮提交表单时,OnSubmit 函数并未运行。我在函数内部添加了一个控制台日志,以验证该函数是否正在运行并且控制台中没有出现任何内容。没有错误,什么都没有。
编辑:我将 onInvalid 函数添加到我的提交中,该控制台记录错误:“ {图像: {…}} 图像 : {消息:'必需',类型:'invalid_type',参考:未定义} [[原型]] : 目的 ”
这似乎是一个与 zod 相关的错误,它显示“invalid_type”。我的代码可能有什么问题
我认为你需要按照以下方式修改你的
formSchema
:
const formSchema = z.object({
// name: z.string().min(1), -- in prisma schema we have label and imageUrl, not a name.
name: z.string().min(1),
images: z.object({ url: z.string() }).array(),
price: z.coerce.number().min(1),
categoryId: z.string().min(1),
colorId: z.string().min(1),
sizeId: z.string().min(1),
isFeatured: z.boolean().default(false).optional(),
isArchived: z.boolean().default(false).optional(),
});
变化:
image
替换为 images