diff --git a/alimento-nextjs/actions/dish/dishCREATE.ts b/alimento-nextjs/actions/dish/dishCREATE.ts index d05edb3..ac5a773 100644 --- a/alimento-nextjs/actions/dish/dishCREATE.ts +++ b/alimento-nextjs/actions/dish/dishCREATE.ts @@ -13,7 +13,7 @@ export async function createDish({ images, }: { name: string; - description?: string; + description: string; price: number; category: Category; tags: Tag[]; diff --git a/alimento-nextjs/app/api/testing/dish/route.ts b/alimento-nextjs/app/api/testing/dish/route.ts index 69f8cd1..fe4b2fb 100644 --- a/alimento-nextjs/app/api/testing/dish/route.ts +++ b/alimento-nextjs/app/api/testing/dish/route.ts @@ -1,20 +1,19 @@ import { createDish } from "@/actions/dish/dishCREATE"; import { getAllDishes } from "@/actions/dish/dishGETALL"; -import { Category, Tag } from "@prisma/client"; import { NextResponse } from "next/server"; // POST route: Creates a new dish export async function POST(req: Request) { try { const body = await req.json(); - const { name, description, price, category, tags, vendorId } = body; + const { name, description, price, category, tags, vendorId ,images} = body; - if (!name || !price || !category || !vendorId) { + if (!name || !price || !category || !vendorId || !images) { return new NextResponse("Name, price, category, and vendor ID are required!", { status: 400 }); } - const resp = await createDish({ name, description, price, category, tags, vendorId }); + const resp = await createDish({ name, description, price, category, tags, vendorId, images }); if (!resp.success) { return new NextResponse(resp.error || "Failed to create dish", { status: 500 }); @@ -30,7 +29,6 @@ export async function POST(req: Request) { // GET route: Retrieves all dishes with optional filtering export async function GET(req: Request) { try { - const url = new URL(req.url); const resp = await getAllDishes({}); diff --git a/alimento-nextjs/app/layout.tsx b/alimento-nextjs/app/layout.tsx index 7c3f28a..400a61f 100644 --- a/alimento-nextjs/app/layout.tsx +++ b/alimento-nextjs/app/layout.tsx @@ -1,8 +1,6 @@ import type { Metadata } from "next"; import "./globals.css"; import { Providers } from "@/lib/providers"; -import Navbar from "@/components/common/navbar"; -import Footer from "@/components/common/footer"; export const metadata: Metadata = { diff --git a/alimento-nextjs/app/vendor/[vendorId]/components/dishesPage.tsx b/alimento-nextjs/app/vendor/[vendorId]/components/dishesPage.tsx index ae47169..2221aa2 100644 --- a/alimento-nextjs/app/vendor/[vendorId]/components/dishesPage.tsx +++ b/alimento-nextjs/app/vendor/[vendorId]/components/dishesPage.tsx @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React from 'react'; import { Button } from '@/components/ui/button'; // Import the Shadcn Button component import Link from 'next/link'; import { DishWithImages } from '../page'; diff --git a/alimento-nextjs/app/vendor/[vendorId]/createDish/components/postDishForm.tsx b/alimento-nextjs/app/vendor/[vendorId]/createDish/components/postDishForm.tsx new file mode 100644 index 0000000..a501acf --- /dev/null +++ b/alimento-nextjs/app/vendor/[vendorId]/createDish/components/postDishForm.tsx @@ -0,0 +1,275 @@ +"use client"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { useParams, useRouter } from "next/navigation"; +import { useCallback, useState } from "react"; +import { useForm } from "react-hook-form"; +import toast from "react-hot-toast"; +import * as z from "zod"; + +import { Button } from "@/components/ui/button"; +import { + Form, + FormControl, + FormField, + FormItem, + FormLabel, + FormMessage, +} from "@/components/ui/form"; +import { Heading } from "@/components/ui/heading"; +import ImageUpload from "@/components/ui/imageUpload"; +import { Input } from "@/components/ui/input"; +import { Separator } from "@/components/ui/separator"; +import { createDish } from "@/actions/dish/dishCREATE"; +import TagSelectorCreateDish from "@/components/vendor/tagSelectorCreateDishForm"; + +const formSchema = z.object({ + name: z.string().min(1, "Name is required"), + price: z.number().min(0, "Price must be a positive number"), + description: z.string().min(1, "Description is required"), + category: z.enum( + ["APPETIZER", "MAIN_COURSE", "DESSERT", "BEVERAGE", "SNACK"], + { + errorMap: () => ({ message: "Category is required" }), + } + ), + tags: z.array(z.enum([ + "SPICY", + "VEGETARIAN", + "VEGAN", + "GLUTEN_FREE", + "DAIRY_FREE", + "NUT_FREE", + "INDIAN", + "CHINESE", + "ITALIAN", + "ARABIC", + ])).min(1, "Atleast 1 tag is required"), + images: z.array(z.string()).min(1, "At least one image is required"), +}); + +type CreateDishFormValues = z.infer; + +export const CreateDishForm: React.FC = () => { + const params = useParams(); + const router = useRouter(); + const [loading, setLoading] = useState(false); + + const title = "Create Dish"; + const description = "Fill in the details to create a new Dish."; + const toastMessage = "Dish created."; + const action = "Create"; + + const form = useForm({ + resolver: zodResolver(formSchema), + defaultValues: { + name: "", + price: 0, + description: "", + category: "APPETIZER", + tags: [], + images: [], + }, + }); + + const onSubmit = async (data: CreateDishFormValues) => { + try { + if (!params.vendorId) { + toast.error("vendorId is required!"); + return; + } + + const vendorId = Array.isArray(params.vendorId) + ? params.vendorId[0] + : params.vendorId; + + if (!vendorId) { + toast.error("Valid vendorId is required!"); + return; + } + + setLoading(true); + + console.log(data,vendorId) + await createDish({ + vendorId: vendorId, + name : data.name, + description :data.description, + price : data.price, + category : data.category, + tags : data.tags, + images : data.images + }); + + router.push(`/vendor/${vendorId}/`); + toast.success(toastMessage); + } catch (err) { + toast.error("Something went wrong"); + console.error(err); + } finally { + setLoading(false); + } + }; + + // Handle image change + const handleImageChange = useCallback( + (url: string) => { + form.setValue("images", [...form.getValues("images"), url]); + }, + [form] + ); + + // Handle image removal + const handleImageRemove = useCallback( + (url: string) => { + const updatedImages = form + .getValues("images") + .filter((image) => image !== url); + form.setValue("images", updatedImages); + }, + [form] + ); + + return ( + <> +
+ +
+ + +
+ + ( + + Images + + + + + + )} + /> + +
+ ( + + Name + + + + + + )} + /> + ( + + Price + + { + const value = parseFloat(e.target.value); + field.onChange(isNaN(value) ? 0 : value); + }} + /> + + + + )} + /> + ( + + Description + + + + + + )} + /> + ( + + Category + + + + + + )} + /> + + ( + + Tags + + field.onChange(tags)} + disabled={loading} + /> + + + + )} + /> +
+ + + + + + + ); +}; diff --git a/alimento-nextjs/app/vendor/[vendorId]/createDish/page.tsx b/alimento-nextjs/app/vendor/[vendorId]/createDish/page.tsx new file mode 100644 index 0000000..5169b81 --- /dev/null +++ b/alimento-nextjs/app/vendor/[vendorId]/createDish/page.tsx @@ -0,0 +1,15 @@ + +import { CreateDishForm } from "./components/postDishForm"; + +const DishPage = () => { + + return ( +
+
+ +
+
+ ); +}; + +export default DishPage; diff --git a/alimento-nextjs/components/multistepForm/components/tagSelector.tsx b/alimento-nextjs/components/multistepForm/components/tagSelector.tsx index e506703..bbbe960 100644 --- a/alimento-nextjs/components/multistepForm/components/tagSelector.tsx +++ b/alimento-nextjs/components/multistepForm/components/tagSelector.tsx @@ -7,7 +7,6 @@ import { } from "@/components/ui/select"; import { useGlobalDish } from "@/context/dishFormContext"; import { Tag } from "@prisma/client"; - import { useState } from "react"; const TagSelector = () => { const tags = Object.values(Tag) diff --git a/alimento-nextjs/components/multistepForm/pages/formPage2.tsx b/alimento-nextjs/components/multistepForm/pages/formPage2.tsx index 89cdd7f..53b4ce0 100644 --- a/alimento-nextjs/components/multistepForm/pages/formPage2.tsx +++ b/alimento-nextjs/components/multistepForm/pages/formPage2.tsx @@ -7,7 +7,7 @@ import { DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; import { useGlobalDish } from '@/context/dishFormContext'; -import { Category, Prisma } from '@prisma/client'; +import { Category } from '@prisma/client'; import { ChevronDown } from 'lucide-react'; const categories =Object.values(Category) diff --git a/alimento-nextjs/components/multistepForm/pages/main.tsx b/alimento-nextjs/components/multistepForm/pages/main.tsx index 6354526..dedd032 100644 --- a/alimento-nextjs/components/multistepForm/pages/main.tsx +++ b/alimento-nextjs/components/multistepForm/pages/main.tsx @@ -4,7 +4,6 @@ import { MouseEventHandler, useEffect } from 'react'; import { Toaster } from 'react-hot-toast'; import FormPage1 from './formPage1'; import FormPage2 from './formPage2'; -import TagSelector from './formPage3'; import FormPage3 from './formPage3'; import FormPage4 from './formPage4'; diff --git a/alimento-nextjs/components/ui/heading.tsx b/alimento-nextjs/components/ui/heading.tsx new file mode 100644 index 0000000..bb58c7c --- /dev/null +++ b/alimento-nextjs/components/ui/heading.tsx @@ -0,0 +1,13 @@ +interface HeadingProps { + title: string; + description: string; +} + +export const Heading: React.FC = ({ title, description }) => { + return ( +
+

{title}

+

{description}

+
+ ); +}; diff --git a/alimento-nextjs/components/ui/separator.tsx b/alimento-nextjs/components/ui/separator.tsx new file mode 100644 index 0000000..12d81c4 --- /dev/null +++ b/alimento-nextjs/components/ui/separator.tsx @@ -0,0 +1,31 @@ +"use client" + +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/alimento-nextjs/components/vendor/tagSelectorCreateDishForm.tsx b/alimento-nextjs/components/vendor/tagSelectorCreateDishForm.tsx new file mode 100644 index 0000000..6b5f364 --- /dev/null +++ b/alimento-nextjs/components/vendor/tagSelectorCreateDishForm.tsx @@ -0,0 +1,85 @@ +import { + Select, + SelectTrigger, + SelectValue, + SelectContent, + SelectItem, +} from "@/components/ui/select"; +import { Tag } from "@prisma/client"; +import React from "react"; + +interface TagSelectorCreateDishProps { + dishTags: Tag[]; + setDishTags: (tags: Tag[]) => void; + disabled: boolean; +} + +const TagSelectorCreateDish: React.FC = ({ + dishTags, + setDishTags, + disabled, +}) => { + const tags = Object.values(Tag); // Get tags from Prisma enum + + // Handle selecting or deselecting a tag + const handleTagSelect = (tag: Tag) => { + if (disabled) return; // Prevent action if disabled + if (dishTags.includes(tag)) { + // Remove tag if already selected + setDishTags(dishTags.filter((t) => t !== tag)); + } else { + // Add tag if not already selected + setDishTags([...dishTags, tag]); + } + }; + + return ( +
+
+ handleTagSelect(tag)} + disabled={disabled} + className="mr-2" + /> + {tag} +
+ + ))} + + +
+ + {/* Display Selected Tags */} +
+ {dishTags.map((tag) => ( + + {tag} + + + ))} +
+ + ); +}; + +export default TagSelectorCreateDish; diff --git a/alimento-nextjs/package.json b/alimento-nextjs/package.json index 23068f8..e8fc500 100644 --- a/alimento-nextjs/package.json +++ b/alimento-nextjs/package.json @@ -15,6 +15,7 @@ "@radix-ui/react-icons": "^1.3.1", "@radix-ui/react-label": "^2.1.0", "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-separator": "^1.1.0", "@radix-ui/react-slot": "^1.1.0", "@types/nodemailer": "^6.4.16", "axios": "^1.7.7",