diff --git a/alimento-nextjs/.env.example b/alimento-nextjs/.env.example index 36157c9..9e52350 100644 --- a/alimento-nextjs/.env.example +++ b/alimento-nextjs/.env.example @@ -1 +1,22 @@ -DATABASE_URL="your mongodb url" \ No newline at end of file +# Database URL for MongoDB connection +# Format: "mongodb+srv://:@/" +DATABASE_URL="your_mongodb_connection_string_here" + +# SMTP Configuration for Email Services +# Replace with your SMTP provider's details if not using Gmail. +SMTP_HOST="smtp.gmail.com" # SMTP server host +SMTP_PORT=587 # SMTP server port (587 for TLS) +SMTP_SECURE=false # Use false for TLS/STARTTLS, true for SSL +SMTP_USER="your_email@example.com" # Email address to send from +SMTP_PASS="your_smtp_password_here" # SMTP password or app-specific password for Gmail + +# NextAuth Configuration +# NEXTAUTH_URL: Set to your app's URL for production; use http://localhost:3000 for local development. +NEXTAUTH_URL="http://localhost:3000" # Base URL for NextAuth authentication +NEXTAUTH_SECRET="your_nextauth_secret_here" # Secret used to sign NextAuth tokens + +# Cloudinary Configuration for Image Uploads +# Replace these with your Cloudinary account details for image uploads. +NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME="your_cloud_name_here" # Cloudinary cloud name +NEXT_PUBLIC_CLOUDINARY_CLOUD_PRESET="your_upload_preset" # Cloudinary upload preset + diff --git a/alimento-nextjs/actions/dish/dishCREATE.ts b/alimento-nextjs/actions/dish/dishCREATE.ts index 678842a..ac5a773 100644 --- a/alimento-nextjs/actions/dish/dishCREATE.ts +++ b/alimento-nextjs/actions/dish/dishCREATE.ts @@ -10,13 +10,15 @@ export async function createDish({ category, tags, vendorId, + images, }: { name: string; - description?: string; + description: string; price: number; category: Category; tags: Tag[]; vendorId: string; + images: string[] }): Promise<{ success: boolean; error?: string; data?: Dish }> { try { const newDish = await prismadb.dish.create({ @@ -27,6 +29,9 @@ export async function createDish({ category, tags, vendorId, + images: { + create: images.map(url => ({ url })), // Assuming you're passing URLs + }, }, }); return { success: true, data: newDish }; 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 new file mode 100644 index 0000000..2221aa2 --- /dev/null +++ b/alimento-nextjs/app/vendor/[vendorId]/components/dishesPage.tsx @@ -0,0 +1,52 @@ +import React from 'react'; +import { Button } from '@/components/ui/button'; // Import the Shadcn Button component +import Link from 'next/link'; +import { DishWithImages } from '../page'; +import DishCard from '@/components/vendor/dishCard'; + +const DishesPage = async ({ + vendorId, + Dishes, +}: { + vendorId: string + Dishes: DishWithImages[]; +}) => { + + return ( +
+
+

Your Dishes

+ + +
+ +
+ setSearchTerm(e.target.value)} + className="p-3 border rounded-full border-gray-300 w-full md:w-1/2" // Adjusted width to make it centered + /> +
+ +
+ {Dishes.map((Dish, index) => ( + + ))} +
+
+ ); +}; + +export default DishesPage; 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/app/vendor/[vendorId]/page.tsx b/alimento-nextjs/app/vendor/[vendorId]/page.tsx index 380f943..e60f994 100644 --- a/alimento-nextjs/app/vendor/[vendorId]/page.tsx +++ b/alimento-nextjs/app/vendor/[vendorId]/page.tsx @@ -1,6 +1,7 @@ import SetUpDishes from '@/components/multistepForm/setupDishes'; import prismadb from '@/lib/prismadb'; -import { Dish } from '@prisma/client'; +import { Dish, Image } from '@prisma/client'; +import DishesPage from './components/dishesPage'; interface VendorPageProps { params: { @@ -8,8 +9,12 @@ interface VendorPageProps { }; } +export interface DishWithImages extends Dish{ + images:Image[] +} + const VendorPage: React.FC = async ({ params }) => { - let Dishes: Dish[] | null = []; + let Dishes: DishWithImages[] | null = []; const { vendorId } = await params; try { @@ -17,6 +22,9 @@ const VendorPage: React.FC = async ({ params }) => { where: { vendorId: vendorId, }, + include:{ + images:true + } }); } catch (err) { console.error( @@ -26,7 +34,7 @@ const VendorPage: React.FC = async ({ params }) => { } if (Dishes.length){ - return
this will be the dishes page
+ return } else{ diff --git a/alimento-nextjs/components/multistepForm/components/sidebarConstants.tsx b/alimento-nextjs/components/multistepForm/components/sidebarConstants.tsx index bb2a25a..0f9630d 100644 --- a/alimento-nextjs/components/multistepForm/components/sidebarConstants.tsx +++ b/alimento-nextjs/components/multistepForm/components/sidebarConstants.tsx @@ -14,5 +14,10 @@ export const data = [ step: 'step 3', title: 'select tags', }, + { + id: 4, + step: 'step 4', + title: 'add images', + }, ]; \ No newline at end of file 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/formPage4.tsx b/alimento-nextjs/components/multistepForm/pages/formPage4.tsx new file mode 100644 index 0000000..802dfc4 --- /dev/null +++ b/alimento-nextjs/components/multistepForm/pages/formPage4.tsx @@ -0,0 +1,25 @@ +import ImageUpload from '@/components/ui/imageUpload'; +import { useGlobalDish } from '@/context/dishFormContext'; + +const FormPage4 = () => { + const { imageUrl, handleImageChange, handleImageRemove } = useGlobalDish(); + + return ( +
+

+ Add Images +

+

+ Please add some images for your listing +
+

+ imageUrl)} + onChange={handleImageChange} + onRemove={handleImageRemove} + /> +
+ ); +}; + +export default FormPage4; diff --git a/alimento-nextjs/components/multistepForm/pages/main.tsx b/alimento-nextjs/components/multistepForm/pages/main.tsx index 194431a..dedd032 100644 --- a/alimento-nextjs/components/multistepForm/pages/main.tsx +++ b/alimento-nextjs/components/multistepForm/pages/main.tsx @@ -4,7 +4,8 @@ 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'; const Main = ({ VendorId }: { VendorId: string }) => { const { @@ -77,7 +78,8 @@ const Main = ({ VendorId }: { VendorId: string }) => { {currentStep === 1 && } {currentStep === 2 && } - {currentStep === 3 && } + {currentStep === 3 && } + {currentStep === 4 && } {!formCompleted && (