Skip to content

Commit

Permalink
feat: multiple dish form
Browse files Browse the repository at this point in the history
  • Loading branch information
guan404ming committed May 25, 2024
1 parent 01f210a commit 60b52ae
Show file tree
Hide file tree
Showing 2 changed files with 166 additions and 119 deletions.
237 changes: 142 additions & 95 deletions src/app/my/_components/add-post-dialog.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";

import { useState } from "react";
import { useForm } from "react-hook-form";
import { useForm, useFieldArray } from "react-hook-form";

import Image from "next/image";

import { zodResolver } from "@hookform/resolvers/zod";
import { PlusCircle } from "lucide-react";
import { PlusCircle, X } from "lucide-react";
import { z } from "zod";

import { Button } from "@/components/ui/button";
Expand All @@ -30,12 +30,12 @@ import { Separator } from "@/components/ui/separator";
import { Textarea } from "@/components/ui/textarea";
import usePost from "@/hooks/use-post";
import { UploadButton } from "@/lib/uploadthing";
import { cn } from "@/lib/utils";

import LocationPicker from "./location-picker";

export default function AddPostDialog() {
const [open, setOpen] = useState<boolean>(false);
const [url, setUrl] = useState<string>("");
const [location, setLocation] = useState<{ lat: number; lng: number } | null>(
null,
);
Expand All @@ -44,9 +44,14 @@ export default function AddPostDialog() {
title: z.string().min(1),
description: z.string().optional(),
location: z.string().min(1),
dishName: z.string().min(1),
dishDescription: z.string().optional(),
quantity: z.number().min(1).max(10000),
dishes: z.array(
z.object({
dishName: z.string().min(1),
dishDescription: z.string().optional(),
quantity: z.number().min(1).max(10000),
url: z.string().url(),
}),
),
});

const { createPost } = usePost();
Expand All @@ -56,21 +61,28 @@ export default function AddPostDialog() {
title: "",
description: "",
location: "",
dishName: "",
dishDescription: "",
quantity: 0,
dishes: [
{
dishName: "",
dishDescription: "",
quantity: 0,
url: "",
},
],
},
});

const { fields, append, remove } = useFieldArray({
control: form.control,
name: "dishes",
});

function onSubmit(values: z.infer<typeof formSchema>) {
console.log(values);
if (!url || !location?.lat || !location?.lng) return;
if (!location?.lat || !location?.lng) return;
createPost({
...values,
name: values.dishName,
image: url,
lat: location?.lat,
lng: location?.lng,
lat: location.lat,
lng: location.lng,
});
setOpen(false);
}
Expand Down Expand Up @@ -122,89 +134,124 @@ export default function AddPostDialog() {
)}
/>
<Separator orientation="horizontal" />
<FormField
control={form.control}
name="dishName"
render={({ field }) => (
<FormItem>
<FormLabel>Dish Name*</FormLabel>
<FormControl>
<Input
placeholder="Enter the name of the dish"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<FormField
control={form.control}
name="dishDescription"
render={({ field }) => (
<FormItem>
<FormLabel>Dish Description</FormLabel>
<FormControl>
<Textarea
placeholder="Enter a brief description of your dish"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{fields.map((field, index) => (
<div key={field.id} className="space-y-4">
<FormField
control={form.control}
name={`dishes.${index}.dishName`}
render={({ field }) => (
<FormItem>
<div className="flex items-center justify-between">
<FormLabel>Dish Name*</FormLabel>
<Button
type="button"
variant="ghost"
size="icon"
onClick={() => remove(index)}
className={cn("h-6 w-6", index === 0 && "invisible")}
>
<X className="h-5 w-5 cursor-pointer" />
</Button>
</div>

<FormField
control={form.control}
name="quantity"
render={({ field }) => (
<FormItem>
<FormLabel>Dish Quantity*</FormLabel>
<FormControl>
<Input
placeholder="quantity"
type="number"
{...field}
onChange={(value) =>
field.onChange(value.target.valueAsNumber)
}
min={0}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormControl>
<Input
placeholder="Enter the name of the dish"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<div className="grid w-full max-w-sm items-center gap-2">
<FormLabel htmlFor="description" className="mb-4">
Dish Image*
</FormLabel>
<UploadButton
className="w-full text-black"
endpoint="imageUploader"
appearance={{ button: "w-full text-black bg-white border" }}
onClientUploadComplete={(res) => {
console.log("Files: ", res);
setUrl(res[0].url);
}}
onUploadError={(error: Error) => {
console.log(`ERROR! ${error.message}`);
}}
/>
{url && (
<Image
src={url}
alt={""}
width={100}
height={100}
className="aspect-auto w-full border object-cover"
<FormField
control={form.control}
name={`dishes.${index}.dishDescription`}
render={({ field }) => (
<FormItem>
<FormLabel>Dish Description</FormLabel>
<FormControl>
<Textarea
placeholder="Enter a brief description of your dish"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
)}
</div>
<Separator orientation="horizontal" />

<FormField
control={form.control}
name={`dishes.${index}.quantity`}
render={({ field }) => (
<FormItem>
<FormLabel>Dish Quantity*</FormLabel>
<FormControl>
<Input
placeholder="quantity"
type="number"
{...field}
onChange={(value) =>
field.onChange(value.target.valueAsNumber)
}
min={0}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>

<div className="grid w-full max-w-sm items-center gap-2">
<FormLabel htmlFor={`dishes.${index}.url`} className="mb-4">
Dish Image*
</FormLabel>
<UploadButton
className="w-full text-black"
endpoint="imageUploader"
appearance={{ button: "w-full text-black bg-white border" }}
onClientUploadComplete={(res) => {
console.log("Files: ", res);
form.setValue(`dishes.${index}.url`, res[0].url);
}}
onUploadError={(error: Error) => {
console.log(`ERROR! ${error.message}`);
}}
/>
{form.watch(`dishes.${index}.url`) && (
<Image
src={form.watch(`dishes.${index}.url`)}
alt={`Dish Image ${index + 1}`}
width={100}
height={100}
className="aspect-auto w-full border object-cover"
/>
)}
</div>
<Separator orientation="horizontal" />
</div>
))}

<Button
type="button"
variant="ghost"
size="icon"
className="flex w-full items-center justify-center space-x-2 border"
onClick={() =>
append({
dishName: "",
dishDescription: "",
quantity: 0,
url: "",
})
}
>
<PlusCircle className="h-5 w-5 cursor-pointer" />
<p>add more dish</p>
</Button>

<div className="grid w-full max-w-sm items-center gap-2">
<FormLabel htmlFor="locationPicker" className="mb-4">
Expand All @@ -214,7 +261,7 @@ export default function AddPostDialog() {
<div className="w-full text-center text-sm">
{location?.lat && location?.lng
? `[${location?.lat}, ${location?.lng}]`
: "location not selected"}
: "Location not selected"}
</div>
</div>

Expand Down
48 changes: 24 additions & 24 deletions src/hooks/use-post.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,26 +117,24 @@ export default function usePost() {
title,
description,
location,
name,
dishDescription,
quantity,
image,
dishes,
lat,
lng,
}: InsertPost &
InsertPostDish & { dishDescription?: string } & {
lat: number;
lng: number;
}) => {
}: InsertPost & {
dishes: {
dishName: string;
dishDescription?: string;
quantity: number;
url: string;
}[];
}) => {
setLoading(true);

const body = await handleFetch({
data: {
title,
description,
location,
name,
quantity,
userId: session?.user?.id,
lat,
lng,
Expand All @@ -147,19 +145,21 @@ export default function usePost() {
setLoading,
});

await handleFetch({
data: {
postId: body.data.id,
name,
quantity,
description: dishDescription,
image,
},
method: "POST",
url: "/api/posts/post-dishes",
successMessage: "Post dish has been created.",
setLoading,
});
for (const dish of dishes) {
await handleFetch({
data: {
postId: body.data.id,
name: dish.dishName,
quantity: dish.quantity,
description: dish.dishDescription,
image: dish.url,
},
method: "POST",
url: "/api/posts/post-dishes",
successMessage: "Post dish has been created.",
setLoading,
});
}
router.refresh();
};

Expand Down

0 comments on commit 60b52ae

Please sign in to comment.