From d8ca6373c0c9530b3eebb42da4fe2570cdd192c4 Mon Sep 17 00:00:00 2001 From: Shivansh Date: Sat, 26 Oct 2024 16:44:35 +0530 Subject: [PATCH] wishlist page made dynamic --- actions/get-products.tsx | 8 +- actions/wishlist-actions.tsx | 105 +++++++++---- .../WishList/components/wishlistItem.tsx | 21 +-- app/(Customer)/WishList/page.tsx | 146 +++++++++++++----- .../[storeId]/components/wishListForm.tsx | 16 +- app/(Customer)/shops/[storeId]/page.tsx | 12 +- components/Navbar/main-nav.tsx | 5 +- components/Navbar/navbar.tsx | 5 +- components/Navbar/sellerNav.tsx | 7 +- components/modals/flashAlert.tsx | 40 +++++ components/modals/underConstruction.tsx | 39 ----- context/flashAlertContext.tsx | 32 ++++ context/modalContext.tsx | 32 ---- providers/sessionProvider.tsx | 7 +- 14 files changed, 312 insertions(+), 163 deletions(-) create mode 100644 components/modals/flashAlert.tsx delete mode 100644 components/modals/underConstruction.tsx create mode 100644 context/flashAlertContext.tsx delete mode 100644 context/modalContext.tsx diff --git a/actions/get-products.tsx b/actions/get-products.tsx index 64246e7..0417d4c 100644 --- a/actions/get-products.tsx +++ b/actions/get-products.tsx @@ -49,7 +49,8 @@ interface GetProductsParams { export default async function getProducts( params: GetProductsParams, - storeId: string + storeId: string, + userId?:string ) { const { isFeatured, categoryId, productName } = params; @@ -75,6 +76,11 @@ export default async function getProducts( seller: { select: { storeName: true } }, category: { select: { name: true } }, images: true, + wishlists:userId?{ + where:{ + userId: userId?userId:undefined + } + }:undefined }, }); // console.log(products); diff --git a/actions/wishlist-actions.tsx b/actions/wishlist-actions.tsx index 84e436f..0d490bc 100644 --- a/actions/wishlist-actions.tsx +++ b/actions/wishlist-actions.tsx @@ -3,31 +3,84 @@ import prismadb from "@/lib/prismadb"; import { Prisma } from "@prisma/client"; // Import Prisma for error handling -export async function WishlistPost(data: FormData): Promise<{ success: boolean; error?: string }> { - const productId = data.get("productid") as string; - const userId = data.get("userid") as string; - - try { - const res = await prismadb.wishlist.create({ - data: { - userId: userId, - productId: productId, - }, - }); - - console.log(res); - return { success: true }; // Return success if the entry was created - } catch (err) { - if (err instanceof Prisma.PrismaClientKnownRequestError) { - // Check if the error code is P2002 (Unique constraint failed) - if (err.code === "P2002") { - console.log("Duplicate entry detected:", err.message); - return { success: false, error: "This item is already in your wishlist." }; // Customize your error message here - } - } - - // Log the error for any other type of error - console.log(err); - return { success: false, error: "An unexpected error occurred." }; // General error message +export async function WishlistPost( + data: FormData +): Promise<{ success: boolean; error?: string }> { + const productId = data.get("productid") as string; + const userId = data.get("userid") as string; + + try { + const res = await prismadb.wishlist.create({ + data: { + userId: userId, + productId: productId, + }, + }); + + console.log(res); + return { success: true }; // Return success if the entry was created + } catch (err) { + if (err instanceof Prisma.PrismaClientKnownRequestError) { + // Check if the error code is P2002 (Unique constraint failed) + if (err.code === "P2002") { + console.log("Duplicate entry detected:", err.message); + return { + success: false, + error: "This item is already in your wishlist.", + }; // Customize your error message here + } } + + // Log the error for any other type of error + console.log(err); + return { success: false, error: "An unexpected error occurred." }; // General error message + } +} + +export async function WishlistGetByUser( + userId: string +): Promise<{ success: boolean; data?: any; error?: string }> { + try { + const res = await prismadb.wishlist.findMany({ + where: { + userId, + }, + include: { + product: { + include: { + images: true, + }, + }, + }, + }); + + // console.log(res); + return { success: true, data: res }; // Include success and return the data + } catch (err) { + console.log(err); + + return { success: false, error: "An unexpected error occurred." }; + } +} + + + +export async function WishlistSpecificEntry( + userId:string + ,productId:string): Promise<{ success: boolean; error?: string }> { + + const res=await prismadb.wishlist.findUnique({ + where:{ + userId_productId:{ + userId, + productId + } + } + }) + + console.log(res); + + return {success:true} + + } diff --git a/app/(Customer)/WishList/components/wishlistItem.tsx b/app/(Customer)/WishList/components/wishlistItem.tsx index 0307b68..75615fb 100644 --- a/app/(Customer)/WishList/components/wishlistItem.tsx +++ b/app/(Customer)/WishList/components/wishlistItem.tsx @@ -1,22 +1,23 @@ import { Button } from "@/components/ui/button"; import Image from "next/image"; +import { productWithImages } from "../page"; interface wishlistItemProps{ - item:{ - id:number, - price:string, - name:string, - image:string - } + item:productWithImages + Id:string, + onRemove: (id: string) => void; } // WishlistItem.js -const WishlistItem:React.FC = ({ item }) => { +const WishlistItem:React.FC = ({ item, }) => { + + + // console.log(item); return ( -
- {item.name} +
+ {item.name}

{item.name}

₹{item.price}

-
+
diff --git a/app/(Customer)/WishList/page.tsx b/app/(Customer)/WishList/page.tsx index cebe7fd..7dd4925 100644 --- a/app/(Customer)/WishList/page.tsx +++ b/app/(Customer)/WishList/page.tsx @@ -1,46 +1,118 @@ -// Wishlist.js +"use client"; import { Button } from "@/components/ui/button"; import WishlistItem from "./components/wishlistItem"; +import { useSession } from "next-auth/react"; +import { WishlistGetByUser } from "@/actions/wishlist-actions"; +import { useEffect, useState } from "react"; +import toast from "react-hot-toast"; +import { Image, Product, Wishlist } from "@prisma/client"; +// import { useTheme } from "@/context/themeProvider"; +import { Spinner } from "@/components/ui/spinner"; +import Link from "next/link"; -const wishlistItems = [ - { - id: 1, - name: "Hybrid Tomato", - price: "29", - image: "/products/tomato.avif", - }, - { - id: 2, - name: "Hen Fruit White Protein Rich Eggs", - price: "92", - image: "/products/eggs.avif", - }, -]; - -const Wishlist = () => { +// const wishlistItems = [ +// { +// id: 1, +// name: "Hybrid Tomato", +// price: "29", +// image: "/products/tomato.avif", +// }, +// { +// id: 2, +// name: "Hen Fruit White Protein Rich Eggs", +// price: "92", +// image: "/products/eggs.avif", +// }, +// ]; + +export interface productWithImages extends Product { + images: Image[]; +} +interface wishlistProps extends Wishlist { + product: productWithImages; +} +const WishlistPage = () => { + const session = useSession(); + const userId = session.data?.user.id; + + // const { theme } = useTheme() || { theme: "light" }; // Get the current theme and toggle function + + const [wishlistItems, setWishlistItems] = useState([]); + + const [loading, setLoading] = useState(false); + + // console.log(wishlistItems); + + + useEffect(() => { + const getItems = async () => { + const response = await WishlistGetByUser(userId||""); + if (response.success) { + setWishlistItems(response.data); + } else { + toast.error("error fetching wishlist"); + } + setLoading(false); + }; + getItems(); + }, [userId]); + + // if (!userId) { + // // openDialog(); + + // return ( + // + // } + // modalTitle={"User not logged in"} + // modalDescription={"Please login to access your wishlist"} + // /> + // ); + // } + + const handleRemove = (id: string) => { + setWishlistItems((prevItems) => prevItems.filter((item) => item.id !== id)); + }; + + if (loading) { + return ; + } + + // console.log(wishlistItems+"dakjnjkanefnj") return ( -
-
-
- Wish List -
-
-
- {wishlistItems.length > 0 ? ( -
- {wishlistItems.map((item) => ( - - ))} -
- ) : ( -
-

Your wishlist is empty!

- + <> +
+
+
+ Wish List
- )} +
+
+ {wishlistItems.length > 0 ? ( +
+ {wishlistItems.map((item) => ( + + ))} +
+ ) : ( +
+

Your wishlist is empty!

+ + + +
+ )} +
-
+ ); }; -export default Wishlist; +export default WishlistPage; diff --git a/app/(Customer)/shops/[storeId]/components/wishListForm.tsx b/app/(Customer)/shops/[storeId]/components/wishListForm.tsx index 8762407..15b65c4 100644 --- a/app/(Customer)/shops/[storeId]/components/wishListForm.tsx +++ b/app/(Customer)/shops/[storeId]/components/wishListForm.tsx @@ -1,16 +1,21 @@ "use client"; import { WishlistPost } from "@/actions/wishlist-actions"; +import { Wishlist } from "@prisma/client"; import { Heart } from "lucide-react"; +import { useState } from "react"; import toast from "react-hot-toast"; // Import react-hot-toast interface WishlistForm { productId: string; userId?: string; + isWishlisted:Wishlist[] } -export default function WishlistForm({ productId, userId }: WishlistForm) { - // This is a client-side function for handling form submissions +export default function WishlistForm({ productId, userId ,isWishlisted}: WishlistForm) { + + const [wishlisted, setWishlisted] = useState(isWishlisted.length > 0); + const handleSubmit = async (formData: FormData) => { // Prevent default form submission behavior if (!userId) { @@ -20,6 +25,7 @@ export default function WishlistForm({ productId, userId }: WishlistForm) { if (response.success) { // Display success message toast.success("Added to wishlist!"); + setWishlisted(true); } else { // Display error message toast.error(response.error || "error"); @@ -52,9 +58,11 @@ export default function WishlistForm({ productId, userId }: WishlistForm) { placeholder="User ID" required /> - + : + {toast.error("item is already wishlisted")}}/> +} ); } diff --git a/app/(Customer)/shops/[storeId]/page.tsx b/app/(Customer)/shops/[storeId]/page.tsx index db7c3fa..941a6ac 100644 --- a/app/(Customer)/shops/[storeId]/page.tsx +++ b/app/(Customer)/shops/[storeId]/page.tsx @@ -1,7 +1,7 @@ import getCategories from "@/actions/get-categories"; import getProducts from "@/actions/get-products"; import ProductCard from "@/components/shops/productCard"; -import { Category, Image, Product, Seller } from "@prisma/client"; +import { Category, Image, Product, Seller, Wishlist } from "@prisma/client"; import Filter from "./components/filter"; import prismadb from "@/lib/prismadb"; import ClientSearchBar from "@/components/shops/clientSearchBar"; @@ -31,6 +31,7 @@ export interface Products extends Product { name: string; }; images: Image[]; + wishlists: Wishlist[] } // export const revalidate = 0; const CategoryProducts: React.FC = async ({ @@ -43,6 +44,7 @@ const CategoryProducts: React.FC = async ({ const userId = session?.user?.id; console.log(userId); + // if(!userId) const categories: Category[] = await getCategories(params.storeId); const products: Products[] = await getProducts( { @@ -50,15 +52,17 @@ const CategoryProducts: React.FC = async ({ categoryId: searchParams.categoryId, productName: searchParams.productName, }, - params.storeId + params.storeId, + userId ); + const seller: Seller | null = await prismadb.seller.findUnique({ where: { id: params.storeId, }, }); - // console.log(products); + console.log(products); // if (loading) { // return ; @@ -120,7 +124,7 @@ const CategoryProducts: React.FC = async ({
{products.map((product) => (
- +
diff --git a/components/Navbar/main-nav.tsx b/components/Navbar/main-nav.tsx index a7511f7..87512a3 100644 --- a/components/Navbar/main-nav.tsx +++ b/components/Navbar/main-nav.tsx @@ -9,7 +9,8 @@ import { Heart, ShoppingCart } from "lucide-react"; // Import any required icons import { Menu, X } from "lucide-react"; // Icons for hamburger menu import { ModeToggle } from "../ui/themeButton"; import AuthButtons from "./authButtons"; -import { useConstruction } from "@/context/modalContext"; +import { useFlashAlert } from "@/context/flashAlertContext"; +// import { useConstruction } from "@/context/modalContext"; interface MainNavProps{ className?:React.HTMLAttributes @@ -20,7 +21,7 @@ interface MainNavProps{ export function MainNav({ className,theme }:MainNavProps) { const [loading,setLoading]=useState(true); - const {openDialog}=useConstruction(); + const {openDialog}=useFlashAlert(); const pathname = usePathname(); const [isOpen, setIsOpen] = useState(false); // State to toggle mobile menu visibility diff --git a/components/Navbar/navbar.tsx b/components/Navbar/navbar.tsx index 7ba820d..5250d0d 100644 --- a/components/Navbar/navbar.tsx +++ b/components/Navbar/navbar.tsx @@ -3,7 +3,8 @@ import Image from "next/image"; import Link from "next/link"; import { MainNav } from "./main-nav"; import { useTheme } from "@/context/themeProvider"; -import UnderConstructionAlert from "../modals/underConstruction"; +import FlashAlert from "../modals/flashAlert"; +import { Construction } from "lucide-react"; const Navbar = () => { @@ -38,7 +39,7 @@ const Navbar = () => { {/* all the navigation links */} - + } modalTitle={"This page is under development"} modalDescription={"We apologize for the inconvenience. Please check back soon."}/>
diff --git a/components/Navbar/sellerNav.tsx b/components/Navbar/sellerNav.tsx index 5507db0..03a341c 100644 --- a/components/Navbar/sellerNav.tsx +++ b/components/Navbar/sellerNav.tsx @@ -2,10 +2,10 @@ import Image from "next/image"; import Link from "next/link"; import { useTheme } from "@/context/themeProvider"; -import UnderConstructionAlert from "../modals/underConstruction"; import { SellerMainNav } from "./seller-main-nav"; -// import { useParams } from "next/navigation"; import { useSession } from "next-auth/react"; +import FlashAlert from "../modals/flashAlert"; +import { Construction } from "lucide-react"; const SellerNavbar = () => { const session=useSession(); @@ -45,7 +45,8 @@ const SellerNavbar = () => { {/* all the navigation links */} - + } modalTitle={"This page is under development"} modalDescription={"We apologize for the inconvenience. Please check back soon."}/> +
); diff --git a/components/modals/flashAlert.tsx b/components/modals/flashAlert.tsx new file mode 100644 index 0000000..bc9f49e --- /dev/null +++ b/components/modals/flashAlert.tsx @@ -0,0 +1,40 @@ +"use client"; +import react from 'react' +import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; +import { useFlashAlert } from "@/context/flashAlertContext"; + +// {theme}:{theme:string} + +interface FlashAlertProps{ + modalLogo:react.ReactNode; + modalTitle:string, + modalDescription:string + +} + +const FlashAlert:React.FC = ({modalLogo,modalTitle,modalDescription}) => { + const { isOpen, closeDialog } = useFlashAlert(); + + return ( + <> + + + + {modalLogo} + {/* */} + + {/* This page is under development */} + {modalTitle} + +

+ {/* We apologize for the inconvenience. Please check back soon. */} + {modalDescription} +

+
+
+
+ + ); +}; + +export default FlashAlert; diff --git a/components/modals/underConstruction.tsx b/components/modals/underConstruction.tsx deleted file mode 100644 index ba5d777..0000000 --- a/components/modals/underConstruction.tsx +++ /dev/null @@ -1,39 +0,0 @@ -"use client"; -import { Construction } from "lucide-react"; -import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"; -import { useConstruction } from "@/context/modalContext"; - -const UnderConstructionAlert = ({theme}:{theme:string}) => { - const { isOpen, closeDialog } = useConstruction(); - - return ( - <> - {/* { - e.preventDefault(); // Prevent default link behavior - openDialog(); // Open the dialog globally - }} - > - This page is under construction - */} - - - - - - - This page is under development - -

- We apologize for the inconvenience. Please check back soon. -

-
-
-
- - ); -}; - -export default UnderConstructionAlert; diff --git a/context/flashAlertContext.tsx b/context/flashAlertContext.tsx new file mode 100644 index 0000000..c183a28 --- /dev/null +++ b/context/flashAlertContext.tsx @@ -0,0 +1,32 @@ +"use client"; // Ensure it runs only in the browser + +import React, { createContext, useContext, useState, ReactNode } from "react"; + +interface FlashAlertContextType { + isOpen: boolean; + openDialog: () => void; + closeDialog: () => void; +} + +const FlashAlertContext = createContext(undefined); + +export const FlashAlertProvider = ({ children }: { children: ReactNode }) => { + const [isOpen, setIsOpen] = useState(false); + + const openDialog = () => setIsOpen(true); + const closeDialog = () => setIsOpen(false); + + return ( + + {children} + + ); +}; + +export const useFlashAlert = () => { + const context = useContext(FlashAlertContext); + if (!context) { + throw new Error("useFlashAlert must be used within a FlashAlertProvider"); + } + return context; +}; diff --git a/context/modalContext.tsx b/context/modalContext.tsx deleted file mode 100644 index 0724aeb..0000000 --- a/context/modalContext.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client"; // Ensure it runs only in the browser - -import React, { createContext, useContext, useState, ReactNode } from "react"; - -interface ConstructionContextType { - isOpen: boolean; - openDialog: () => void; - closeDialog: () => void; -} - -const ConstructionContext = createContext(undefined); - -export const ConstructionProvider = ({ children }: { children: ReactNode }) => { - const [isOpen, setIsOpen] = useState(false); - - const openDialog = () => setIsOpen(true); - const closeDialog = () => setIsOpen(false); - - return ( - - {children} - - ); -}; - -export const useConstruction = () => { - const context = useContext(ConstructionContext); - if (!context) { - throw new Error("useConstruction must be used within a ConstructionProvider"); - } - return context; -}; diff --git a/providers/sessionProvider.tsx b/providers/sessionProvider.tsx index 04ffd7a..6e51df8 100644 --- a/providers/sessionProvider.tsx +++ b/providers/sessionProvider.tsx @@ -2,14 +2,15 @@ import React from "react"; import { SessionProvider } from "next-auth/react"; import { ThemeProvider } from "@/context/themeProvider"; -import { ConstructionProvider } from "@/context/modalContext"; +import { FlashAlertProvider } from "@/context/flashAlertContext"; +// import { ConstructionProvider } from "@/context/modalContext"; export const Providers = ({ children }: { children: React.ReactNode }) => { return ( - + {children} - + ); };