├── app ├── favicon.ico ├── globals.css ├── admin │ ├── page.js │ ├── admins │ │ ├── page.js │ │ └── components │ │ │ ├── ListView.js │ │ │ └── Form.js │ ├── brands │ │ ├── page.js │ │ └── components │ │ │ ├── ListView.js │ │ │ └── Form.js │ ├── categories │ │ ├── page.js │ │ └── components │ │ │ ├── ListView.js │ │ │ └── Form.js │ ├── collections │ │ ├── page.js │ │ └── components │ │ │ └── ListView.js │ ├── products │ │ ├── page.js │ │ └── form │ │ │ └── components │ │ │ ├── Description.js │ │ │ ├── Images.js │ │ │ └── BasicDetails.js │ ├── layout.js │ └── components │ │ ├── Header.js │ │ ├── AdminLayout.js │ │ └── Sidebar.js ├── (auth) │ ├── layout.js │ ├── forget-passward │ │ └── page.js │ ├── sign-up │ │ └── page.js │ └── login │ │ └── page.js ├── (pages) │ ├── products │ │ └── [productId] │ │ │ ├── components │ │ │ ├── Review.js │ │ │ ├── Description.js │ │ │ ├── pageServer.js │ │ │ ├── RelatedProducts.js │ │ │ ├── pageClient.js │ │ │ ├── Photos.js │ │ │ ├── Details.js │ │ │ └── ProductCard.js │ │ │ └── page.js │ ├── layout.js │ └── categories │ │ └── [categoryId] │ │ └── page.js ├── components │ ├── AboveNav.js │ ├── LogOutButton.js │ ├── CategorySlider.js │ ├── CustomerReviews.js │ ├── Header.js │ ├── HeaderClientButtons.js │ ├── AboutUs.js │ ├── Footer.js │ ├── AddToCardButton.js │ ├── FavoriteButton.js │ └── ProductsList.js ├── products │ └── page.js ├── layout.js ├── (user) │ ├── checkout │ │ ├── layout.js │ │ └── page.js │ ├── layout.js │ ├── account │ │ └── page.js │ ├── cart │ │ └── page.js │ └── favorites │ │ └── page.js └── page.js ├── public ├── assets │ ├── google.png │ └── logo.png ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── jsconfig.json ├── postcss.config.mjs ├── next.config.mjs ├── lib ├── firestore │ ├── admins │ │ ├── read_server.js │ │ ├── read.js │ │ └── write.js │ ├── brands │ │ ├── read_server.js │ │ ├── read.js │ │ └── write.js │ ├── collections │ │ ├── read_server.js │ │ ├── read.js │ │ └── write.js │ ├── categories │ │ ├── read.js │ │ ├── read_server.js │ │ └── write.js │ ├── users │ │ ├── write.js │ │ └── read.js │ ├── products │ │ ├── read_server.js │ │ ├── write.js │ │ └── read.js │ ├── orders │ │ └── read.js │ └── checkout │ │ └── write.js ├── firebase_admin.js ├── cloudinary.js └── firebase.js ├── pages └── api │ ├── upload_admin.js │ ├── upload_brand.js │ ├── upload_product.js │ ├── upload_collection.js │ ├── upload_category.js │ ├── deleteImage.js │ └── razorpay │ ├── save-order.js │ └── order.js ├── .gitignore ├── tailwind.config.mjs ├── package.json ├── context └── AuthContext.js └── README.md /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akpadia02/paresh_novelty/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/assets/google.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akpadia02/paresh_novelty/HEAD/public/assets/google.png -------------------------------------------------------------------------------- /public/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akpadia02/paresh_novelty/HEAD/public/assets/logo.png -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./*"] 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html { 6 | @apply font-sans; 7 | } 8 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /app/admin/page.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function page() { 4 | return ( 5 |
6 |

DashBoard

7 |
8 | ) 9 | } 10 | 11 | export default page -------------------------------------------------------------------------------- /next.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | // const nextConfig = {}; 3 | const nextConfig = { 4 | images: { 5 | domains: ["res.cloudinary.com"], // Add your allowed domains here 6 | }, 7 | }; 8 | 9 | export default nextConfig; 10 | -------------------------------------------------------------------------------- /app/(auth)/layout.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import AuthContextProvider from '@/context/AuthContext' 4 | import React from 'react' 5 | 6 | function layout({children}) { 7 | return ( 8 | {children} 9 | ) 10 | } 11 | 12 | export default layout -------------------------------------------------------------------------------- /app/(pages)/products/[productId]/components/Review.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | async function Review({productId}) { 4 | return ( 5 |
6 |

Reviews

7 |
8 | ) 9 | } 10 | 11 | export default Review -------------------------------------------------------------------------------- /lib/firestore/admins/read_server.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase" 2 | import { doc, getDoc } from "firebase/firestore" 3 | 4 | export const getAdmin = async({id}) =>{ 5 | const data = await getDoc(doc(db,`admins/${id}`)); 6 | if(data.exists()){ 7 | return data.data(); 8 | }else{ 9 | return null; 10 | } 11 | } -------------------------------------------------------------------------------- /lib/firestore/brands/read_server.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase" 2 | import { doc, getDoc } from "firebase/firestore" 3 | 4 | export const getBrand = async({id}) =>{ 5 | const data = await getDoc(doc(db,`brands/${id}`)); 6 | if(data.exists()){ 7 | return data.data(); 8 | }else{ 9 | return null; 10 | } 11 | } -------------------------------------------------------------------------------- /lib/firestore/collections/read_server.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase" 2 | import { doc, getDoc } from "firebase/firestore" 3 | 4 | export const getCollection = async({id}) =>{ 5 | const data = await getDoc(doc(db,`collections/${id}`)); 6 | if(data.exists()){ 7 | return data.data(); 8 | }else{ 9 | return null; 10 | } 11 | } -------------------------------------------------------------------------------- /lib/firebase_admin.js: -------------------------------------------------------------------------------- 1 | export const admin = require("firebase-admin"); 2 | 3 | const serviceAccount = JSON.parse( 4 | process.env.FIREBASE_SERVICE_ACCOUNT_KEYS 5 | ); 6 | 7 | if (admin.apps.length === 0) { 8 | admin.initializeApp({ 9 | credential: admin.credential.cert(serviceAccount), 10 | }); 11 | } 12 | 13 | export const adminDB = admin.firestore(); -------------------------------------------------------------------------------- /app/admin/admins/page.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import React from 'react' 3 | import Form from './components/Form' 4 | import ListView from './components/ListView' 5 | 6 | function page() { 7 | return ( 8 |
9 |
10 | 11 |
12 | ) 13 | } 14 | 15 | export default page -------------------------------------------------------------------------------- /app/admin/brands/page.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import React from 'react' 3 | import Form from './components/Form' 4 | import ListView from './components/ListView' 5 | 6 | function page() { 7 | return ( 8 |
9 | 10 | 11 |
12 | ) 13 | } 14 | 15 | export default page -------------------------------------------------------------------------------- /lib/cloudinary.js: -------------------------------------------------------------------------------- 1 | import { v2 as cloudinary } from 'cloudinary'; 2 | 3 | cloudinary.config({ 4 | cloud_name: process.env.NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME, 5 | api_key: process.env.NEXT_PUBLIC_CLOUDINARY_API_KEY, 6 | api_secret: process.env.CLOUDINARY_API_SECRET, // Secret should never be exposed on the client side 7 | }); 8 | 9 | export default cloudinary; 10 | -------------------------------------------------------------------------------- /app/admin/categories/page.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import React from 'react' 3 | import Form from './components/Form' 4 | import ListView from './components/ListView' 5 | 6 | function page() { 7 | return ( 8 |
9 | 10 | 11 |
12 | ) 13 | } 14 | 15 | export default page -------------------------------------------------------------------------------- /app/admin/collections/page.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import React from 'react' 3 | import Form from './components/Form' 4 | import ListView from './components/ListView' 5 | 6 | function page() { 7 | return ( 8 |
9 | 10 | 11 |
12 | ) 13 | } 14 | 15 | export default page -------------------------------------------------------------------------------- /app/components/AboveNav.js: -------------------------------------------------------------------------------- 1 | export default function AboveNav() { 2 | return ( 3 |
4 |
5 | Paresh Novelty – Where Elegance Meets Affordability! 6 |
7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /app/products/page.js: -------------------------------------------------------------------------------- 1 | 2 | import { getAllProducts } from "@/lib/firestore/products/read_server"; // ✅ Server-side function 3 | import ProductsList from "../components/ProductsList"; 4 | 5 | export default async function ProductsPage() { 6 | const products = await getAllProducts(); // ✅ Fetch products on the server 7 | 8 | return ; 9 | } 10 | -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/(pages)/layout.js: -------------------------------------------------------------------------------- 1 | import AboveNav from "../components/AboveNav"; 2 | import Footer from "../components/Footer"; 3 | import Header from "../components/Header"; 4 | 5 | export default function Layout({children}){ 6 | return ( 7 |
8 | 9 |
10 | {children} 11 |
12 |
13 | ) 14 | } -------------------------------------------------------------------------------- /app/(pages)/products/[productId]/components/Description.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | function Description({ product }) { 4 | return ( 5 |
6 |

7 | Description 8 |

9 |
{product?.description ?? ""}
10 |
11 | ) 12 | } 13 | 14 | export default Description -------------------------------------------------------------------------------- /app/(pages)/products/[productId]/components/pageServer.js: -------------------------------------------------------------------------------- 1 | // app/product/[productId]/PageServer.js (Server Component) 2 | import { getProduct } from '@/lib/firestore/products/read_server'; 3 | import PageClient from './pageClient'; 4 | 5 | export default async function PageServer({ params }) { 6 | const awaitedParams = await params; // ✅ Await params first 7 | const { productId } = awaitedParams; 8 | const product = await getProduct({ id: productId }); 9 | 10 | return ; 11 | } 12 | -------------------------------------------------------------------------------- /app/admin/products/page.js: -------------------------------------------------------------------------------- 1 | 2 | import Link from 'next/link' 3 | import React from 'react' 4 | import ListView from './components/ListView' 5 | 6 | function page() { 7 | return ( 8 |
9 |
10 |

Products

11 | 12 | 13 | 14 |
15 | 16 |
17 | ) 18 | } 19 | 20 | export default page -------------------------------------------------------------------------------- /pages/api/upload_admin.js: -------------------------------------------------------------------------------- 1 | import cloudinary from "@/lib/cloudinary"; 2 | 3 | export default async function handler(req, res) { 4 | if (req.method !== "POST") { 5 | return res.status(405).json({ message: "Only POST requests are allowed" }); 6 | } 7 | 8 | const { image } = req.body; 9 | 10 | if (!image) { 11 | return res.status(400).json({ message: "Image is required" }); 12 | } 13 | 14 | try { 15 | const uploadResponse = await cloudinary.uploader.upload(image, { 16 | folder: "admins", 17 | }); 18 | 19 | res.status(200).json({ url: uploadResponse.secure_url }); 20 | } catch (error) { 21 | res.status(500).json({ message: "Cloudinary upload failed", error }); 22 | } 23 | } -------------------------------------------------------------------------------- /pages/api/upload_brand.js: -------------------------------------------------------------------------------- 1 | import cloudinary from "@/lib/cloudinary"; 2 | 3 | export default async function handler(req, res) { 4 | if (req.method !== "POST") { 5 | return res.status(405).json({ message: "Only POST requests are allowed" }); 6 | } 7 | 8 | const { image } = req.body; 9 | 10 | if (!image) { 11 | return res.status(400).json({ message: "Image is required" }); 12 | } 13 | 14 | try { 15 | const uploadResponse = await cloudinary.uploader.upload(image, { 16 | folder: "brands", 17 | }); 18 | 19 | res.status(200).json({ url: uploadResponse.secure_url }); 20 | } catch (error) { 21 | res.status(500).json({ message: "Cloudinary upload failed", error }); 22 | } 23 | } -------------------------------------------------------------------------------- /pages/api/upload_product.js: -------------------------------------------------------------------------------- 1 | import cloudinary from "@/lib/cloudinary"; 2 | 3 | export default async function handler(req, res) { 4 | if (req.method !== "POST") { 5 | return res.status(405).json({ message: "Only POST requests are allowed" }); 6 | } 7 | 8 | const { image } = req.body; 9 | 10 | if (!image) { 11 | return res.status(400).json({ message: "Image is required" }); 12 | } 13 | 14 | try { 15 | const uploadResponse = await cloudinary.uploader.upload(image, { 16 | folder: "products", 17 | }); 18 | 19 | res.status(200).json({ url: uploadResponse.secure_url }); 20 | } catch (error) { 21 | res.status(500).json({ message: "Cloudinary upload failed", error }); 22 | } 23 | } -------------------------------------------------------------------------------- /pages/api/upload_collection.js: -------------------------------------------------------------------------------- 1 | import cloudinary from "@/lib/cloudinary"; 2 | 3 | export default async function handler(req, res) { 4 | if (req.method !== "POST") { 5 | return res.status(405).json({ message: "Only POST requests are allowed" }); 6 | } 7 | 8 | const { image } = req.body; 9 | 10 | if (!image) { 11 | return res.status(400).json({ message: "Image is required" }); 12 | } 13 | 14 | try { 15 | const uploadResponse = await cloudinary.uploader.upload(image, { 16 | folder: "collections", 17 | }); 18 | 19 | res.status(200).json({ url: uploadResponse.secure_url }); 20 | } catch (error) { 21 | res.status(500).json({ message: "Cloudinary upload failed", error }); 22 | } 23 | } -------------------------------------------------------------------------------- /pages/api/upload_category.js: -------------------------------------------------------------------------------- 1 | import cloudinary from "@/lib/cloudinary"; 2 | 3 | export default async function handler(req, res) { 4 | if (req.method !== "POST") { 5 | return res.status(405).json({ message: "Only POST requests are allowed" }); 6 | } 7 | 8 | const { image } = req.body; 9 | 10 | if (!image) { 11 | return res.status(400).json({ message: "Image is required" }); 12 | } 13 | 14 | try { 15 | const uploadResponse = await cloudinary.uploader.upload(image, { 16 | folder: "categories", 17 | }); 18 | 19 | res.status(200).json({ url: uploadResponse.secure_url }); 20 | } catch (error) { 21 | res.status(500).json({ message: "Cloudinary upload failed", error }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | /config/firebase-service-account.json 14 | 15 | 16 | # testing 17 | /coverage 18 | 19 | # next.js 20 | /.next/ 21 | /out/ 22 | 23 | # production 24 | /build 25 | 26 | # misc 27 | .DS_Store 28 | *.pem 29 | 30 | # debug 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | .pnpm-debug.log* 35 | 36 | # env files (can opt-in for committing if needed) 37 | .env* 38 | 39 | # vercel 40 | .vercel 41 | 42 | # typescript 43 | *.tsbuildinfo 44 | next-env.d.ts 45 | config/firebase-service-account.json 46 | -------------------------------------------------------------------------------- /lib/firestore/brands/read.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { db } from "@/lib/firebase"; 4 | import { collection, onSnapshot } from "firebase/firestore"; 5 | import useSWRSubscription from "swr/subscription"; 6 | 7 | export function useBrands() { 8 | const { data, error } = useSWRSubscription( 9 | ["brands"], 10 | ([path], { next }) => { 11 | const ref = collection(db, path); 12 | const unsub = onSnapshot( 13 | ref, 14 | (snapshot) => 15 | next( 16 | null, 17 | snapshot.docs.length === 0 18 | ? null 19 | : snapshot.docs.map((snap) => snap.data()) 20 | ), 21 | (err) => next(err, null) 22 | ); 23 | return () => unsub(); 24 | } 25 | ); 26 | 27 | return { data, error: error?.message, isLoading: data === undefined }; 28 | } -------------------------------------------------------------------------------- /lib/firestore/categories/read.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { db } from "@/lib/firebase"; 4 | import { collection, onSnapshot } from "firebase/firestore"; 5 | import useSWRSubscription from "swr/subscription"; 6 | 7 | export function useCategories() { 8 | const { data, error } = useSWRSubscription( 9 | ["categories"], 10 | ([path], { next }) => { 11 | const ref = collection(db, path); 12 | const unsub = onSnapshot( 13 | ref, 14 | (snapshot) => 15 | next( 16 | null, 17 | snapshot.docs.length === 0 18 | ? null 19 | : snapshot.docs.map((snap) => snap.data()) 20 | ), 21 | (err) => next(err, null) 22 | ); 23 | return () => unsub(); 24 | } 25 | ); 26 | 27 | return { data, error: error?.message, isLoading: data === undefined }; 28 | } -------------------------------------------------------------------------------- /lib/firestore/collections/read.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { db } from "@/lib/firebase"; 4 | import { collection, onSnapshot } from "firebase/firestore"; 5 | import useSWRSubscription from "swr/subscription"; 6 | 7 | export function useCollections() { 8 | const { data, error } = useSWRSubscription( 9 | ["collections"], 10 | ([path], { next }) => { 11 | const ref = collection(db, path); 12 | const unsub = onSnapshot( 13 | ref, 14 | (snapshot) => 15 | next( 16 | null, 17 | snapshot.docs.length === 0 18 | ? null 19 | : snapshot.docs.map((snap) => snap.data()) 20 | ), 21 | (err) => next(err, null) 22 | ); 23 | return () => unsub(); 24 | } 25 | ); 26 | 27 | return { data, error: error?.message, isLoading: data === undefined }; 28 | } -------------------------------------------------------------------------------- /lib/firestore/users/write.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase"; 2 | import { doc, setDoc, Timestamp } from "firebase/firestore"; 3 | 4 | export const createUser = async ({ uid, displayName, photoURL }) => { 5 | await setDoc( 6 | doc(db, `users/${uid}`), 7 | { 8 | displayName: displayName, 9 | photoURL: photoURL ?? "", 10 | timestampCreate: Timestamp.now(), 11 | }, 12 | { merge: true } 13 | ); 14 | }; 15 | 16 | export const updateFavorites = async ({ uid, list }) => { 17 | await setDoc( 18 | doc(db, `users/${uid}`), 19 | { 20 | favorites: list, 21 | }, 22 | { 23 | merge: true, 24 | } 25 | ); 26 | }; 27 | 28 | export const updateCarts = async ({ uid, list }) => { 29 | await setDoc( 30 | doc(db, `users/${uid}`), 31 | { 32 | carts: list, 33 | }, 34 | { 35 | merge: true, 36 | } 37 | ); 38 | }; -------------------------------------------------------------------------------- /app/(pages)/products/[productId]/components/RelatedProducts.js: -------------------------------------------------------------------------------- 1 | import { getProductsByCategory } from "@/lib/firestore/products/read_server"; 2 | import { ProductCard } from "./ProductCard"; 3 | 4 | export default async function RelatedProducts({ categoryId }) { 5 | if (!categoryId) return null; 6 | 7 | const products = await getProductsByCategory({categoryId:categoryId}); 8 | 9 | return ( 10 |
11 |
12 |

Related Products

13 |
14 | {products?.map((item) => ( 15 | 16 | ))} 17 |
18 |
19 |
20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /tailwind.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | import {nextui} from "@nextui-org/react"; 3 | export default { 4 | content: [ 5 | "./pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./app/**/*.{js,ts,jsx,tsx,mdx}", 8 | "./node_modules/@nextui-org/theme/dist/**/*.{js,ts,jsx,tsx}", 9 | ], 10 | theme: { 11 | extend: { 12 | colors: { 13 | background: "var(--background)", 14 | foreground: "var(--foreground)", 15 | }, 16 | keyframes: { 17 | marquee: { 18 | '0%': { transform: 'translateX(100%)' }, 19 | '100%': { transform: 'translateX(-100%)' }, 20 | }, 21 | }, 22 | animation: { 23 | marquee: 'marquee 20s linear infinite', 24 | }, 25 | fontFamily: { 26 | playfair: ['"Playfair Display"', 'serif'], 27 | }, 28 | }, 29 | }, 30 | darkMode: "class", 31 | plugins: [nextui()] 32 | }; 33 | 34 | 35 | -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/(pages)/products/[productId]/components/pageClient.js: -------------------------------------------------------------------------------- 1 | // app/product/[productId]/PageClient.js (Client Component) 2 | // "use client"; 3 | import React from "react"; 4 | import Photos from "./Photos"; 5 | import Details from "./Details"; 6 | import Review from "./Review"; 7 | import Description from "./Description"; 8 | import RelatedProducts from "./RelatedProducts"; 9 | 10 | 11 | export default function PageClient({ product }) { 12 | return ( 13 |
14 | {/* Photo & Details */} 15 |
16 | 17 |
18 |
19 | {/* Description & Reviews */} 20 |
21 | {/* */} 22 | {/* */} 23 |
24 | {/* Related Products */} 25 | 26 |
27 | ); 28 | } 29 | -------------------------------------------------------------------------------- /lib/firebase.js: -------------------------------------------------------------------------------- 1 | import { getApp, getApps, initializeApp } from "firebase/app"; 2 | import { getAnalytics, isSupported } from "firebase/analytics"; 3 | import { getFirestore } from "firebase/firestore"; 4 | import { getAuth } from "firebase/auth"; 5 | // import { getStorage } from "firebase/storage"; 6 | 7 | const firebaseConfig = { 8 | apiKey: process.env.NEXT_PUBLIC_FIREBASE_API_KEY, 9 | authDomain: process.env.NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN, 10 | projectId: process.env.NEXT_PUBLIC_FIREBASE_PROJECT_ID, 11 | storageBucket: process.env.NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET, 12 | messagingSenderId: process.env.NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID, 13 | appId: process.env.NEXT_PUBLIC_FIREBASE_APP_ID, 14 | measurementId: process.env.NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID, 15 | }; 16 | 17 | const app = !getApps().length ? initializeApp(firebaseConfig) : getApp(); 18 | export const analytics = isSupported().then((yes) => 19 | yes ? getAnalytics(app) : null 20 | ); 21 | export const db = getFirestore(app); 22 | export const auth = getAuth(app); 23 | // export const storage = getStorage(app); -------------------------------------------------------------------------------- /pages/api/deleteImage.js: -------------------------------------------------------------------------------- 1 | // pages/api/deleteImage.js 2 | 3 | import cloudinary from "@/lib/cloudinary"; 4 | 5 | 6 | export default async function handler(req, res) { 7 | if (req.method !== 'POST') { 8 | return res.status(405).json({ message: 'Only POST requests are allowed' }); 9 | } 10 | 11 | const { publicId } = req.body; 12 | 13 | if (!publicId) { 14 | return res.status(400).json({ message: 'Public ID is required' }); 15 | } 16 | 17 | console.log('Attempting to delete image with publicId:', publicId); // Debug log 18 | 19 | try { 20 | const result = await cloudinary.uploader.destroy(publicId); 21 | 22 | console.log('Cloudinary delete result:', result); // Debug log 23 | 24 | if (result.result === 'ok') { 25 | return res.status(200).json({ message: 'Image deleted successfully' }); 26 | } 27 | 28 | return res.status(500).json({ message: 'Error deleting image from Cloudinary', result }); 29 | } catch (error) { 30 | console.error('Error deleting image from Cloudinary:', error); 31 | return res.status(500).json({ message: 'Error deleting image', error: error.message }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "paresh_novelty", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@emotion/react": "^11.14.0", 13 | "@emotion/styled": "^11.14.0", 14 | "@mui/icons-material": "^6.4.7", 15 | "@mui/material": "^6.4.7", 16 | "@nextui-org/react": "^2.6.11", 17 | "@tanstack/react-virtual": "^3.11.2", 18 | "canvas-confetti": "^1.9.3", 19 | "cloudinary": "^2.5.1", 20 | "dotenv": "^16.4.7", 21 | "draft-js": "^0.11.7", 22 | "firebase": "^11.1.0", 23 | "firebase-admin": "^13.4.0", 24 | "framer-motion": "^11.18.2", 25 | "lucide-react": "^0.469.0", 26 | "next": "15.1.2", 27 | "razorpay": "^2.9.6", 28 | "react": "^18.2.0", 29 | "react-dom": "^18.2.0", 30 | "react-hot-toast": "^2.4.1", 31 | "react-quill": "^2.0.0", 32 | "react-slick": "^0.30.3", 33 | "slick-carousel": "^1.8.1", 34 | "swr": "^2.3.0" 35 | }, 36 | "devDependencies": { 37 | "postcss": "^8", 38 | "tailwindcss": "^3.4.1" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/components/LogOutButton.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useAuth } from '@/context/AuthContext'; 3 | import { auth } from '@/lib/firebase'; 4 | import { signOut } from 'firebase/auth'; 5 | import { LogOut } from 'lucide-react'; 6 | import React from 'react' 7 | import toast from 'react-hot-toast'; 8 | 9 | function LogOutButton() { 10 | const {user} = useAuth(); 11 | if(!user){ 12 | return <> 13 | } 14 | return ( 15 | 31 | ) 32 | } 33 | 34 | export default LogOutButton -------------------------------------------------------------------------------- /app/admin/layout.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import AuthContextProvider, { useAuth } from "@/context/AuthContext"; 4 | import { CircularProgress } from "@nextui-org/react"; 5 | import { useRouter } from "next/navigation"; 6 | import { useEffect } from "react"; 7 | import AdminLayout from "./components/AdminLayout"; 8 | 9 | export default function Layout({ children }) { 10 | return ( 11 | 12 | {children} 13 | 14 | ); 15 | } 16 | 17 | function AdminChecking({ children }) { 18 | const { user, isLoading } = useAuth(); 19 | const router = useRouter(); 20 | 21 | useEffect(() => { 22 | if (!user && !isLoading) { 23 | router.push("/login"); 24 | } 25 | }, [user, isLoading]); 26 | 27 | if (isLoading) { 28 | return ( 29 |
30 | 31 |
32 | ); 33 | } 34 | 35 | if (!user) { 36 | return ( 37 |
38 |

Please Login First!

39 |
40 | ); 41 | } 42 | 43 | return {children}; 44 | } -------------------------------------------------------------------------------- /app/(pages)/products/[productId]/page.js: -------------------------------------------------------------------------------- 1 | // "use client" 2 | // import { getProduct } from '@/lib/firestore/products/read_server'; 3 | // import React from 'react' 4 | // import Photos from './components/Photos'; 5 | // import Details from './components/Details'; 6 | 7 | import PageServer from "./components/pageServer"; 8 | 9 | // async function Page({ params }) { 10 | // const { productId } = params; 11 | // const product = await getProduct({ id: productId }) 12 | // return ( 13 | //
14 | // {/* Photo & Details */} 15 | //
16 | // 17 | //
18 | //
19 | // {/* Description & Reviews */} 20 | //
21 | //
22 | //
23 | //
24 | // {/* Related Products */} 25 | //
26 | 27 | //
28 | //
29 | // ) 30 | // } 31 | 32 | // export default Page 33 | 34 | 35 | 36 | // app/product/[productId]/page.js 37 | 38 | 39 | export default function Page({ params }) { 40 | return ; 41 | } 42 | -------------------------------------------------------------------------------- /app/layout.js: -------------------------------------------------------------------------------- 1 | import { Geist, Geist_Mono, Playfair_Display } from "next/font/google"; 2 | import "./globals.css"; 3 | import { NextUIProvider } from "@nextui-org/react"; 4 | import toast, { Toaster } from 'react-hot-toast'; 5 | import "slick-carousel/slick/slick.css"; 6 | import "slick-carousel/slick/slick-theme.css"; 7 | 8 | const geistSans = Geist({ 9 | variable: "--font-geist-sans", 10 | subsets: ["latin"], 11 | }); 12 | 13 | const geistMono = Geist_Mono({ 14 | variable: "--font-geist-mono", 15 | subsets: ["latin"], 16 | }); 17 | 18 | const playfair = Playfair_Display({ 19 | variable: "--font-playfair", 20 | subsets: ["latin"], 21 | weight: ["400", "600"], // Include the weights you need 22 | }); 23 | 24 | export const metadata = { 25 | title: "Paresh Novelty – Where Elegance Meets Affordability", 26 | description: "Explore a wide range of bindis, bangles, and elegant novelties.", 27 | }; 28 | 29 | export default function RootLayout({ children }) { 30 | return ( 31 | 32 | 35 | 36 | 37 | {children} 38 | 39 | 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /app/(user)/checkout/layout.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useAuth } from "@/context/AuthContext"; 4 | import { useUser } from "@/lib/firestore/users/read"; 5 | import { CircularProgress } from "@nextui-org/react"; 6 | import { SearchCheck } from "lucide-react"; 7 | import { useSearchParams } from "next/navigation" 8 | 9 | export default function Layout({ children }) { 10 | const { user } = useAuth(); 11 | const searchParams = useSearchParams(); 12 | const type = searchParams.get("type"); 13 | const productId = searchParams.get("productId"); 14 | 15 | const { data, isLoading, error } = useUser({ uid: user.uid }); 16 | 17 | if(isLoading){ 18 | return( 19 |
20 | 21 |
22 | ) 23 | } 24 | 25 | if(error){ 26 | return
{error}
27 | } 28 | 29 | if (type === 'cart' && (!data?.carts || data?.carts?.length === 0)) { 30 | return ( 31 |
32 |

Your Cart is Empty

33 |
34 | ) 35 | } 36 | 37 | if(type === 'buynow' && !productId){ 38 | return
39 |

Product Not Found!

40 |
41 | } 42 | 43 | 44 | 45 | return <> 46 | {children} 47 | 48 | } -------------------------------------------------------------------------------- /app/(pages)/products/[productId]/components/Photos.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useState } from "react"; 3 | 4 | function Photos({ imageList = [] }) { 5 | if (!imageList.length) return null; 6 | 7 | const [selectedImage, setSelectedImage] = useState(imageList[0]); 8 | 9 | return ( 10 |
11 | {/* Main Image Display */} 12 |
13 | Selected 18 |
19 | 20 | {/* Thumbnail List */} 21 |
22 | {imageList.map((item, index) => ( 23 |
setSelectedImage(item)} 26 | className={`w-[90px] border-2 rounded p-2 cursor-pointer transition-all ${ 27 | selectedImage === item ? "border-blue-500 scale-110" : "border-gray-300" 28 | }`} 29 | > 30 | {`Thumbnail 31 |
32 | ))} 33 |
34 |
35 | ); 36 | } 37 | 38 | export default Photos; 39 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/admin/components/Header.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { useAuth } from '@/context/AuthContext' 3 | import { useAdmin } from '@/lib/firestore/admins/read'; 4 | import { Avatar } from '@nextui-org/react'; 5 | import { Menu } from 'lucide-react' 6 | import React from 'react' 7 | 8 | function Header({ toogleSidebar }) { 9 | const { user } = useAuth(); 10 | const { data: admin } = useAdmin({ email: user?.email }); 11 | return ( 12 |
13 |
14 | 15 |
16 |
17 |

DashBoard

18 |
19 |
20 |

{admin?.name}

21 |

{admin?.email}

22 |
23 | 24 |
25 |
26 |
27 | ) 28 | } 29 | 30 | export default Header -------------------------------------------------------------------------------- /lib/firestore/users/read.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { db } from "@/lib/firebase"; 4 | import { 5 | collection, 6 | doc, 7 | onSnapshot, 8 | orderBy, 9 | query, 10 | } from "firebase/firestore"; 11 | import useSWRSubscription from "swr/subscription"; 12 | 13 | export function useUser({ uid }) { 14 | const { data, error } = useSWRSubscription( 15 | ["users", uid], 16 | ([path, uid], { next }) => { 17 | const ref = doc(db, `users/${uid}`); 18 | const unsub = onSnapshot( 19 | ref, 20 | (snapshot) => next(null, snapshot.exists() ? snapshot.data() : null), 21 | (err) => next(err, null) 22 | ); 23 | return () => unsub(); 24 | } 25 | ); 26 | 27 | return { data, error: error?.message, isLoading: data === undefined }; 28 | } 29 | 30 | export function useUsers() { 31 | const { data, error } = useSWRSubscription(["users"], ([path], { next }) => { 32 | const q = query(collection(db, path), orderBy("timestampCreate", "desc")); 33 | const unsub = onSnapshot( 34 | q, 35 | (snapshot) => 36 | next( 37 | null, 38 | snapshot.docs.length === 0 39 | ? null 40 | : snapshot.docs.map((snap) => snap.data()) 41 | ), 42 | (err) => next(err, null) 43 | ); 44 | return () => unsub(); 45 | }); 46 | 47 | return { data, error: error?.message, isLoading: data === undefined }; 48 | } -------------------------------------------------------------------------------- /lib/firestore/admins/read.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { db } from "@/lib/firebase"; 4 | import { collection, doc, onSnapshot } from "firebase/firestore"; 5 | import useSWRSubscription from "swr/subscription"; 6 | 7 | export function useAdmins() { 8 | const { data, error } = useSWRSubscription( 9 | ["admins"], 10 | ([path], { next }) => { 11 | const ref = collection(db, path); 12 | const unsub = onSnapshot( 13 | ref, 14 | (snapshot) => 15 | next( 16 | null, 17 | snapshot.docs.length === 0 18 | ? null 19 | : snapshot.docs.map((snap) => snap.data()) 20 | ), 21 | (err) => next(err, null) 22 | ); 23 | return () => unsub(); 24 | } 25 | ); 26 | 27 | return { data, error: error?.message, isLoading: data === undefined }; 28 | } 29 | 30 | export function useAdmin({ email }) { 31 | const { data, error } = useSWRSubscription( 32 | ["admins", email], 33 | ([path, email], { next }) => { 34 | const ref = doc(db, `admins/${email}`); 35 | const unsub = onSnapshot( 36 | ref, 37 | (snapshot) => 38 | next( 39 | null, 40 | snapshot.exists() ? snapshot.data() : null 41 | ), 42 | (err) => next(err, null) 43 | ); 44 | return () => unsub(); 45 | } 46 | ); 47 | 48 | return { data, error: error?.message, isLoading: data === undefined }; 49 | } -------------------------------------------------------------------------------- /app/(user)/layout.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import AuthContextProvider, { useAuth } from "@/context/AuthContext"; 4 | import Footer from "../components/Footer"; 5 | import Header from "../components/Header"; 6 | import { CircularProgress } from "@nextui-org/react"; 7 | import Link from "next/link"; 8 | 9 | export default function Layout({ children }) { 10 | return
11 |
12 | 13 | 14 |
15 | {children} 16 |
17 |
18 |
19 |
20 |
21 | } 22 | 23 | function UserChecking({ children }) { 24 | const { user, isLoading } = useAuth(); 25 | if (isLoading) { 26 | return ( 27 |
28 | 29 |
30 | ); 31 | } 32 | if (!user) { 33 | return ( 34 |
35 |

You are not Logged In!

36 | 37 | 38 | 39 |
40 | ); 41 | } 42 | return <>{children} 43 | } -------------------------------------------------------------------------------- /app/page.js: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | import AboveNav from "./components/AboveNav"; 3 | import Header from "./components/Header"; 4 | import FeaturedCategorySlider from "./components/Sliders"; 5 | import { getAllCategories, getFeaturedCategories } from "@/lib/firestore/categories/read_server"; 6 | import FeaturedProducts from "./components/FeaturedProducts"; 7 | import { getFeaturedProducts } from "@/lib/firestore/products/read_server"; 8 | import Footer from "./components/Footer"; 9 | import CategorySlider from "./components/CategorySlider"; 10 | import CustomerReviews from "./components/CustomerReviews"; 11 | import AboutUs from "./components/AboutUs"; 12 | 13 | export default async function Home() { 14 | // const featuredCategories = await getFeaturedCategories(); 15 | // const featuredProducts = await getFeaturedProducts(); 16 | // const categories = await getAllCategories(); 17 | 18 | 19 | //fetch at together once 20 | const [featuredCategories, featuredProducts, categories] = await Promise.all([ 21 | getFeaturedCategories(), 22 | getFeaturedProducts(), 23 | getAllCategories(), 24 | ]) 25 | return ( 26 |
27 | 28 |
29 | 30 | 31 | 32 | 33 | {/* */} 34 |
35 |
36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /pages/api/razorpay/save-order.js: -------------------------------------------------------------------------------- 1 | import { getFirestore } from "firebase-admin/firestore"; 2 | import { initializeApp, applicationDefault, cert } from "firebase-admin/app"; 3 | 4 | // Initialize Firebase Admin SDK (do this once) 5 | const serviceAccount = JSON.parse(process.env.FIREBASE_SERVICE_ACCOUNT_KEY); 6 | 7 | if (!getFirestore.apps?.length) { 8 | initializeApp({ 9 | credential: cert(serviceAccount), 10 | }); 11 | } 12 | 13 | const db = getFirestore(); 14 | 15 | export default async function handler(req, res) { 16 | if (req.method !== "POST") { 17 | return res.status(405).end(); // Method not allowed 18 | } 19 | 20 | const { uid, address, productList, paymentDetails } = req.body; 21 | 22 | try { 23 | if (!uid || !address || !productList || !paymentDetails) { 24 | return res.status(400).json({ error: "Missing required fields" }); 25 | } 26 | 27 | // Example of logging data for debugging 28 | console.log("Request body:", req.body); 29 | 30 | const orderData = { 31 | uid, 32 | address, 33 | productList, 34 | paymentDetails, 35 | paymentMode: "prepaid", 36 | status: "paid", 37 | createdAt: new Date().toISOString(), 38 | }; 39 | 40 | // Check if orderData is properly formed 41 | console.log("Order Data to Save:", orderData); 42 | 43 | // Saving to Firestore 44 | const docRef = await db.collection("orders").add(orderData); 45 | res.status(200).json({ orderId: docRef.id }); 46 | } catch (err) { 47 | console.error("Error saving order to Firestore:", err); 48 | res.status(500).json({ error: "Failed to save order", details: err.message }); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /context/AuthContext.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { auth } from '@/lib/firebase'; 3 | import { onAuthStateChanged } from 'firebase/auth'; 4 | import React, { createContext, useContext, useEffect, useState } from 'react' 5 | 6 | const AuthContext = createContext(); 7 | 8 | function AuthContextProvider({ children }) { 9 | 10 | const [user, setUser] = useState(undefined); 11 | 12 | useEffect(() => { 13 | const unsub = onAuthStateChanged(auth, (user) => { 14 | if (user) { 15 | setUser(user); 16 | } else { 17 | setUser(null); 18 | } 19 | }); 20 | return () => unsub(); 21 | }, []) 22 | 23 | return ( 24 | {children} 28 | ) 29 | } 30 | 31 | export default AuthContextProvider 32 | export const useAuth = () => useContext(AuthContext); 33 | 34 | 35 | // React Context is a feature used to share values between components without having to pass props 36 | // through every level of the component tree. 37 | // It is particularly useful for managing global states like authentication, themes, or settings. 38 | 39 | // This code defines an authentication context in a React/Next.js app using Firebase Authentication. It uses React's `createContext` to create a global state 40 | // for managing the user's authentication status. The `AuthContextProvider` component listens to authentication changes via Firebase's `onAuthStateChanged` and updates the `user` state, 41 | // which holds the logged-in user's details, `null` for logged-out users, or `undefined` during loading. The context provides `user` and `isLoading` to all wrapped components, 42 | // enabling easy access to authentication data through the custom `useAuth` hook, simplifying app-wide state management for authentication. -------------------------------------------------------------------------------- /app/components/CategorySlider.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import Slider from "react-slick"; 4 | import Image from "next/image"; 5 | import Link from "next/link"; 6 | 7 | function CategorySlider({ categories }) { 8 | const settings = { 9 | dots: false, // Remove dots 10 | infinite: true, 11 | speed: 5000, // Increased transition speed 12 | slidesToShow: 4, 13 | slidesToScroll: 1, 14 | autoplay: true, 15 | autoplaySpeed: 5000, // Increased auto slide time 16 | cssEase: "linear", 17 | responsive: [ 18 | { 19 | breakpoint: 1024, 20 | settings: { slidesToShow: 3, slidesToScroll: 1 }, 21 | }, 22 | { 23 | breakpoint: 768, 24 | settings: { slidesToShow: 2, slidesToScroll: 1 }, 25 | }, 26 | { 27 | breakpoint: 480, 28 | settings: { slidesToShow: 1, slidesToScroll: 1 }, 29 | }, 30 | ], 31 | }; 32 | 33 | return ( 34 |
35 |
36 |

Categories

37 |
38 | 39 | {categories.map((category) => ( 40 |
41 | 42 |
43 | {category.name} 49 |
50 |

51 | {category.name} 52 |

53 | 54 |
55 | ))} 56 |
57 |
58 | ); 59 | } 60 | 61 | export default CategorySlider; 62 | -------------------------------------------------------------------------------- /lib/firestore/categories/read_server.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase" 2 | import { collection, doc, getDoc, getDocs, query, where } from "firebase/firestore" 3 | 4 | export const getCategory = async ({ id }) => { 5 | const data = await getDoc(doc(db, `categories/${id}`)); 6 | if (data.exists()) { 7 | return data.data(); 8 | } else { 9 | return null; 10 | } 11 | } 12 | 13 | 14 | // export const getFeaturedCategories = async () => { 15 | // const list = await getDocs( 16 | // query(collection(db,"categories"), where("isFeatured", "==", true)) 17 | // ); 18 | // return list.docs.map((snap)=>snap.data()); 19 | 20 | // } 21 | 22 | import { Timestamp } from "firebase/firestore"; 23 | 24 | const serializeFirestoreData = (data) => { 25 | const serializedData = { ...data }; 26 | 27 | Object.keys(serializedData).forEach((key) => { 28 | if (serializedData[key] instanceof Timestamp) { 29 | // Convert Firestore Timestamp to ISO string 30 | serializedData[key] = serializedData[key].toDate().toISOString(); 31 | } 32 | }); 33 | 34 | return serializedData; 35 | }; 36 | 37 | 38 | export const getFeaturedCategories = async () => { 39 | try { 40 | const list = await getDocs( 41 | query(collection(db, "categories"), where("isFeatured", "==", true)) 42 | ); 43 | return list.docs.map((snap) => ({ 44 | id: snap.id, 45 | ...serializeFirestoreData(snap.data()), // Ensure fields are serializable 46 | })); 47 | } catch (error) { 48 | console.error("Error fetching featured categories:", error); 49 | return []; 50 | } 51 | }; 52 | 53 | 54 | export const getAllCategories = async () => { 55 | try { 56 | const list = await getDocs(collection(db, "categories")); // No filtering 57 | return list.docs.map((snap) => ({ 58 | id: snap.id, 59 | ...serializeFirestoreData(snap.data()), // Ensure fields are serializable 60 | })); 61 | } catch (error) { 62 | console.error("Error fetching all categories:", error); 63 | return []; 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /app/(user)/checkout/page.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useAuth } from "@/context/AuthContext" 4 | import { useProductsByIds } from "@/lib/firestore/products/read"; 5 | import { useUser } from "@/lib/firestore/users/read"; 6 | import { CircularProgress } from "@nextui-org/react"; 7 | import { useSearchParams } from "next/navigation"; 8 | import Chcekout from "./components/Checkout"; 9 | import Checkout from "./components/Checkout"; 10 | 11 | export default function Page() { 12 | const { user } = useAuth(); 13 | const { data } = useUser({ uid: user?.uid }); 14 | 15 | const searchParams = useSearchParams(); 16 | const type = searchParams.get("type"); 17 | const productId = searchParams.get("productId"); 18 | 19 | const productIdsList = (type === 'buynow') ? [productId] : data?.carts?.map((item)=>item?.id); 20 | 21 | 22 | const {data:products, error, isLoading} = useProductsByIds({idsList:productIdsList}); 23 | 24 | if(isLoading){ 25 | return( 26 |
27 | 28 |
29 | ) 30 | } 31 | 32 | if(error){ 33 | return
{error}
34 | } 35 | 36 | if(productIdsList?.length === 0 && !productIdsList){ 37 | return
38 |

Products Not Found

39 |
40 | } 41 | 42 | // const productList =[ 43 | // { 44 | // quantity:1, 45 | // product:{ 46 | // id, 47 | // name, 48 | // } 49 | // } 50 | // ] 51 | 52 | const productList = (type === 'buynow') ? [ 53 | { 54 | id: productId, 55 | quantity: 1, 56 | product: products[0], 57 | } 58 | ]: data?.carts?.map((item)=>{ 59 | return { 60 | ...item, 61 | product: products?.find((e)=>e?.id === item?.id), 62 | } 63 | }) 64 | 65 | return <> 66 |
67 |

Checkout

68 | 69 |
70 | 71 | } -------------------------------------------------------------------------------- /app/(pages)/categories/[categoryId]/page.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useParams } from "next/navigation"; 4 | import { useEffect, useState } from "react"; 5 | import { getProductsByCategory } from "@/lib/firestore/products/read_server"; 6 | import { ProductCard } from "../../products/[productId]/components/ProductCard"; 7 | import { getCategory } from "@/lib/firestore/categories/read_server"; 8 | 9 | export default function Page() { 10 | const { categoryId } = useParams(); 11 | const [products, setProducts] = useState([]); 12 | const [category, setCategory] = useState(null); // Store category data 13 | 14 | useEffect(() => { 15 | if (!categoryId) return; 16 | 17 | const fetchData = async () => { 18 | try { 19 | const categoryData = await getCategory({ id: categoryId }); 20 | setCategory(categoryData); 21 | 22 | const productsData = await getProductsByCategory({ categoryId }); 23 | setProducts(productsData); 24 | } catch (error) { 25 | console.error("Error fetching data:", error); 26 | } 27 | }; 28 | 29 | fetchData(); 30 | }, [categoryId]); 31 | 32 | if (!categoryId) { 33 | return
Error: Category ID not found
; 34 | } 35 | 36 | return ( 37 |
38 |
39 |
40 |

41 | {category ? category.name : "Loading..."} {/* Handle loading state */} 42 |

43 |
44 | {products?.map((item) => ( 45 | 46 | ))} 47 |
48 |
49 |
50 |
51 | ); 52 | } 53 | -------------------------------------------------------------------------------- /app/components/CustomerReviews.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Rating from '@mui/material/Rating'; 3 | 4 | 5 | const list = [ 6 | { 7 | id: 1, // ✅ Added unique id 8 | name: "Lorem ipsum", 9 | message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit...", 10 | rating: 4.5, 11 | imageUrl: "https://i.postimg.cc/j5x7BkHZ/person.jpg" 12 | }, 13 | { 14 | id: 2, // ✅ Added unique id 15 | name: "Lorem ipsum", 16 | message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit...", 17 | rating: 4.0, 18 | imageUrl: "https://i.postimg.cc/j5x7BkHZ/person.jpg" 19 | }, 20 | { 21 | id: 3, // ✅ Added unique id 22 | name: "Lorem ipsum", 23 | message: "Lorem ipsum dolor sit amet, consectetur adipiscing elit...", 24 | rating: 3.5, 25 | imageUrl: "https://i.postimg.cc/j5x7BkHZ/person.jpg" 26 | }, 27 | ]; 28 | 29 | 30 | function CustomerReviews() { 31 | return ( 32 |
33 |
34 |

Customer's Review

35 |
36 | {list?.map((item) => { 37 | return ( 38 |
39 | 40 |

{item?.name}

41 | 42 |

{item?.message}

43 |
44 | ) 45 | })} 46 |
47 |
48 |
49 | ) 50 | } 51 | 52 | export default CustomerReviews -------------------------------------------------------------------------------- /app/components/Header.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Link from 'next/link'; 3 | import { Heart, Search, ShoppingCart, UserCircle2 } from 'lucide-react'; 4 | import LogOutButton from './LogOutButton'; 5 | import AuthContextProvider from '@/context/AuthContext'; 6 | import HeaderClientButtons from './HeaderClientButtons'; 7 | 8 | function Header() { 9 | const menuList = [ 10 | { 11 | name: "Home", 12 | link: "/" 13 | }, 14 | { 15 | name: "Contact", 16 | link: "/contact" 17 | }, 18 | { 19 | name: "About", 20 | link: "/about" 21 | } 22 | ]; 23 | return ( 24 | 52 | ); 53 | } 54 | 55 | export default Header; 56 | -------------------------------------------------------------------------------- /app/components/HeaderClientButtons.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | 4 | import { useAuth } from "@/context/AuthContext"; 5 | import { useUser } from "@/lib/firestore/users/read"; 6 | import { Badge } from "@nextui-org/react"; 7 | import { Heart, ShoppingCart } from "lucide-react"; 8 | import Link from "next/link"; 9 | 10 | export default function HeaderClientButtons() { 11 | const { user } = useAuth(); 12 | const { data } = useUser({ uid: user?.uid }); 13 | return ( 14 |
15 | 16 | {(data?.favorites?.length ?? 0) != 0 && ( 17 | 23 | 29 | 30 | )} 31 | {(data?.favorites?.length ?? 0) === 0 && ( 32 | 38 | )} 39 | 40 | 41 | {(data?.carts?.length ?? 0) != 0 && ( 42 | 48 | 54 | 55 | )} 56 | {(data?.carts?.length ?? 0) === 0 && ( 57 | 63 | )} 64 | 65 |
66 | ); 67 | } -------------------------------------------------------------------------------- /lib/firestore/brands/write.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase"; 2 | import { collection, deleteDoc, doc, setDoc, Timestamp } from "firebase/firestore"; 3 | 4 | export const createNewBrand = async ({ data, image }) => { 5 | if (!image) { 6 | throw new Error("Image is Required"); 7 | } 8 | if (!data?.name) { 9 | throw new Error("Name is required"); 10 | } 11 | 12 | // Upload image to Cloudinary via API route 13 | const response = await fetch("/api/upload_brand", { 14 | method: "POST", 15 | headers: { "Content-Type": "application/json" }, 16 | body: JSON.stringify({ image }), 17 | }); 18 | const result = await response.json(); 19 | console.log(result); 20 | 21 | if (!response.ok) { 22 | throw new Error(result.message || "Cloudinary upload failed"); 23 | } 24 | 25 | const imageURL = result.url; 26 | 27 | const newId = doc(collection(db, `brands`)).id; 28 | 29 | await setDoc(doc(db, `brands/${newId}`), { 30 | ...data, 31 | id: newId, 32 | imageURL, 33 | timestampCreate: Timestamp.now(), 34 | }); 35 | }; 36 | 37 | export const deleteBrand = async ({ id }) => { 38 | if (!id) { 39 | throw new Error("ID is required"); 40 | } 41 | await deleteDoc(doc(db, `brands/${id}`)); 42 | }; 43 | 44 | 45 | export const updateBrand = async ({ data, image }) => { 46 | if (!data?.name) { 47 | throw new Error("Name is required"); 48 | } 49 | if (!data?.id) { 50 | throw new Error("ID is required"); 51 | } 52 | 53 | let imageURL = data?.imageURL; // Use existing imageURL if no new image is provided 54 | 55 | if (image) { 56 | // Upload new image to Cloudinary via API route 57 | const response = await fetch("/api/upload_brand", { 58 | method: "POST", 59 | headers: { "Content-Type": "application/json" }, 60 | body: JSON.stringify({ image }), 61 | }); 62 | 63 | const result = await response.json(); 64 | if (!response.ok) { 65 | throw new Error(result.message || "Cloudinary upload failed"); 66 | } 67 | 68 | imageURL = result.url; // Update imageURL with the new one 69 | } 70 | 71 | // Update the existing document with `merge: true` to avoid overwriting 72 | const brandRef = doc(db, `brands/${data.id}`); 73 | await setDoc( 74 | brandRef, 75 | { 76 | ...data, 77 | imageURL, 78 | timestampUpdated: Timestamp.now(), 79 | }, 80 | { merge: true } // Ensures fields are merged instead of overwriting 81 | ); 82 | }; -------------------------------------------------------------------------------- /app/components/AboutUs.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | 4 | const AboutUs = () => { 5 | return ( 6 |
7 | {/* Parallax Section 1 */} 8 |
15 | 16 |

17 | About Us 18 |

19 |
20 | 21 | {/* Content Section 1 */} 22 |
23 |

24 | Who We Are 25 |

26 |

27 | We are a passionate team dedicated to innovation and excellence. 28 | Through creativity and technology, we craft solutions that drive 29 | positive change and empower individuals and organizations. 30 |

31 |
32 | 33 | {/* Parallax Section 2 */} 34 |
41 |

42 | Our Vision 43 |

44 |
45 | 46 | {/* Content Section 2 */} 47 |
48 |

49 | Our Mission 50 |

51 |

52 | To lead with innovation, inspire with creativity, and deliver with 53 | integrity. Our mission is to create meaningful experiences and 54 | solutions that make a difference in people's lives. 55 |

56 |
57 |
58 | ); 59 | }; 60 | 61 | export default AboutUs; 62 | -------------------------------------------------------------------------------- /lib/firestore/collections/write.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase"; 2 | import { collection, deleteDoc, doc, setDoc, Timestamp } from "firebase/firestore"; 3 | 4 | export const createNewCollection = async ({ data, image }) => { 5 | if (!image) { 6 | throw new Error("Image is Required"); 7 | } 8 | if (!data?.title) { 9 | throw new Error("Title is required"); 10 | } 11 | if (!data?.products || data?.products?.length === 0) { 12 | throw new Error("Products are required"); 13 | } 14 | 15 | // Upload image to Cloudinary via API route 16 | const response = await fetch("/api/upload_collection", { 17 | method: "POST", 18 | headers: { "Content-Type": "application/json" }, 19 | body: JSON.stringify({ image }), 20 | }); 21 | const result = await response.json(); 22 | console.log(result); 23 | 24 | if (!response.ok) { 25 | throw new Error(result.message || "Cloudinary upload failed"); 26 | } 27 | 28 | const imageURL = result.url; 29 | 30 | const newId = doc(collection(db, `collections`)).id; 31 | 32 | await setDoc(doc(db, `collections/${newId}`), { 33 | ...data, 34 | id: newId, 35 | imageURL, 36 | timestampCreate: Timestamp.now(), 37 | }); 38 | }; 39 | 40 | export const deleteCollection = async ({ id }) => { 41 | if (!id) { 42 | throw new Error("ID is required"); 43 | } 44 | await deleteDoc(doc(db, `collections/${id}`)); 45 | }; 46 | 47 | 48 | export const updateCollection = async ({ data, image }) => { 49 | if (!data?.title) { 50 | throw new Error("Title is required"); 51 | } 52 | if (!data?.products || data?.products?.length === 0) { 53 | throw new Error("Products are required"); 54 | } 55 | if (!data?.id) { 56 | throw new Error("ID is required"); 57 | } 58 | 59 | let imageURL = data?.imageURL; // Use existing imageURL if no new image is provided 60 | 61 | if (image) { 62 | // Upload new image to Cloudinary via API route 63 | const response = await fetch("/api/upload_collection", { 64 | method: "POST", 65 | headers: { "Content-Type": "application/json" }, 66 | body: JSON.stringify({ image }), 67 | }); 68 | 69 | const result = await response.json(); 70 | if (!response.ok) { 71 | throw new Error(result.message || "Cloudinary upload failed"); 72 | } 73 | 74 | imageURL = result.url; // Update imageURL with the new one 75 | } 76 | 77 | // Update the existing document with `merge: true` to avoid overwriting 78 | const categoryRef = doc(db, `collections/${data.id}`); 79 | await setDoc( 80 | categoryRef, 81 | { 82 | ...data, 83 | imageURL, 84 | timestampUpdated: Timestamp.now(), 85 | }, 86 | { merge: true } // Ensures fields are merged instead of overwriting 87 | ); 88 | }; -------------------------------------------------------------------------------- /lib/firestore/products/read_server.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase" 2 | import { collection, doc, getDoc, getDocs, orderBy, query, where } from "firebase/firestore" 3 | 4 | // export const getProduct = async ({ id }) => { 5 | // const data = await getDoc(doc(db, `products/${id}`)); 6 | // if (data.exists()) { 7 | // return data.data(); 8 | // } else { 9 | // return null; 10 | // } 11 | // } 12 | 13 | export const getProduct = async ({ id }) => { 14 | const docSnap = await getDoc(doc(db, `products/${id}`)); 15 | if (!docSnap.exists()) return null; 16 | 17 | return serializeFirestoreData(docSnap.data()); // ✅ Ensure timestamps are serialized 18 | }; 19 | 20 | //fetch particular 21 | 22 | 23 | 24 | import { Timestamp } from "firebase/firestore"; 25 | 26 | const serializeFirestoreData = (data) => { 27 | const serializedData = { ...data }; 28 | 29 | Object.keys(serializedData).forEach((key) => { 30 | if (serializedData[key] instanceof Timestamp) { 31 | // Convert Firestore Timestamp to ISO string 32 | serializedData[key] = serializedData[key].toDate().toISOString(); 33 | } 34 | }); 35 | 36 | return serializedData; 37 | }; 38 | 39 | export const getFeaturedProducts = async () => { 40 | try { 41 | const list = await getDocs( 42 | query(collection(db, "products"), where("isFeatured", "==", true)) 43 | ); 44 | return list.docs.map((snap) => ({ 45 | id: snap.id, 46 | ...serializeFirestoreData(snap.data()), // Ensure fields are serializable 47 | })); 48 | } catch (error) { 49 | console.error("Error fetching featured products:", error); 50 | return []; 51 | } 52 | }; 53 | 54 | export const getAllProducts = async () => { 55 | try { 56 | const list = await getDocs(collection(db, "products")); // No filtering 57 | return list.docs.map((snap) => ({ 58 | id: snap.id, 59 | ...serializeFirestoreData(snap.data()), // Ensure fields are serializable 60 | })); 61 | } catch (error) { 62 | console.error("Error fetching all products:", error); 63 | return []; 64 | } 65 | }; 66 | 67 | 68 | export const getProductsByCategory = async ({ categoryId }) => { 69 | try { 70 | const productsQuery = query( 71 | collection(db, "products"), // Reference collection 72 | where("categoryId", "==", categoryId), // Filter by categoryId 73 | orderBy("timestampCreate", "desc") // Sort by timestamp (requires index) 74 | ); 75 | 76 | const list = await getDocs(productsQuery); 77 | 78 | return list.docs.map((snap) => ({ 79 | id: snap.id, 80 | ...serializeFirestoreData(snap.data()), 81 | })); 82 | } catch (error) { 83 | console.error("Error fetching category-specific products:", error); 84 | return []; 85 | } 86 | }; 87 | 88 | -------------------------------------------------------------------------------- /lib/firestore/categories/write.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase"; 2 | import { collection, deleteDoc, doc, setDoc, Timestamp } from "firebase/firestore"; 3 | 4 | export const createNewCategory = async ({ data, image }) => { 5 | if (!image) { 6 | throw new Error("Image is Required"); 7 | } 8 | if (!data?.name) { 9 | throw new Error("Name is required"); 10 | } 11 | if (!data?.slug) { 12 | throw new Error("Slug is required"); 13 | } 14 | 15 | // Upload image to Cloudinary via API route 16 | const response = await fetch("/api/upload_category", { 17 | method: "POST", 18 | headers: { "Content-Type": "application/json" }, 19 | body: JSON.stringify({ image }), 20 | }); 21 | const result = await response.json(); 22 | console.log(result); 23 | 24 | if (!response.ok) { 25 | throw new Error(result.message || "Cloudinary upload failed"); 26 | } 27 | 28 | const imageURL = result.url; 29 | 30 | const newId = doc(collection(db, `categories`)).id; 31 | 32 | await setDoc(doc(db, `categories/${newId}`), { 33 | ...data, 34 | id: newId, 35 | imageURL, 36 | timestampCreate: Timestamp.now(), 37 | }); 38 | }; 39 | 40 | export const deleteCategory = async ({ id }) => { 41 | if (!id) { 42 | throw new Error("ID is required"); 43 | } 44 | await deleteDoc(doc(db, `categories/${id}`)); 45 | }; 46 | 47 | 48 | export const updateCategory = async ({ data, image }) => { 49 | if (!data?.name) { 50 | throw new Error("Name is required"); 51 | } 52 | if (!data?.slug) { 53 | throw new Error("Slug is required"); 54 | } 55 | if (!data?.id) { 56 | throw new Error("ID is required"); 57 | } 58 | 59 | let imageURL = data?.imageURL; // Use existing imageURL if no new image is provided 60 | 61 | if (image) { 62 | // Upload new image to Cloudinary via API route 63 | const response = await fetch("/api/upload_category", { 64 | method: "POST", 65 | headers: { "Content-Type": "application/json" }, 66 | body: JSON.stringify({ image }), 67 | }); 68 | 69 | const result = await response.json(); 70 | if (!response.ok) { 71 | throw new Error(result.message || "Cloudinary upload failed"); 72 | } 73 | 74 | imageURL = result.url; // Update imageURL with the new one 75 | } 76 | 77 | // Update the existing document with `merge: true` to avoid overwriting 78 | const categoryRef = doc(db, `categories/${data.id}`); 79 | await setDoc( 80 | categoryRef, 81 | { 82 | ...data, 83 | imageURL, 84 | timestampUpdated: Timestamp.now(), 85 | }, 86 | { merge: true } // Ensures fields are merged instead of overwriting 87 | ); 88 | }; -------------------------------------------------------------------------------- /app/components/Footer.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { Mail, Instagram } from "lucide-react"; 4 | 5 | function Footer() { 6 | return ( 7 |
8 |
9 | {/* Contact Information */} 10 |
11 |

Contact Information

12 |

Phone: +91 9421700364 / 9423100105 / 9764878007

13 |

Tel: 0712 - 2728946

14 |

Email: PareshNovelty@Gmail.Com

15 |

Address: Bohra Masjid Road, Itwari, Nagpur - 440002.

16 |

Visit Us On:

17 | 25 |
26 | 27 | {/* Quick Links */} 28 |
29 |

Quick Links

30 | 36 |
37 | 38 | {/* Company Information */} 39 |
40 |

Company Information

41 |

Your Trusted Wholesaler For Fancy Bindis, Bangles, Ladies' Novelties, General Items, And Imitation Jewelry. Quality Products At The Best Prices.

42 |
43 |
44 | 45 | {/* Bottom Section */} 46 |
47 |

© 2025 Paresh Novelty All Rights Reserved.

48 |

Designed & Developed By Akshay Padia

49 |
50 |
51 | ); 52 | } 53 | 54 | export default Footer; 55 | -------------------------------------------------------------------------------- /app/(pages)/products/[productId]/components/Details.js: -------------------------------------------------------------------------------- 1 | import { getCategory } from '@/lib/firestore/categories/read_server' 2 | import { Button } from '@nextui-org/react' 3 | import { Heart, ShoppingCart } from 'lucide-react' 4 | import Link from 'next/link' 5 | import React from 'react' 6 | 7 | function Details({ product }) { 8 | return ( 9 |
10 |
11 | 12 |
13 | {/* Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. */} 14 |

{product?.title}

15 | {/* line-clamp-3 md:line-clamp-4 */} 16 |

{product?.shortDescription}

17 |

{product.salePrice < product.price ? ( 18 | <> 19 | ₹ {product.salePrice}{" "} 20 | ₹ {product.price} 21 | 22 | ) : ( 23 | ₹ {product.price} 24 | )}

25 |
26 | 29 | 30 | 33 | 40 |
41 | {/*

42 | Description 43 |

*/} 44 |
{product?.description ?? ""}
45 |
46 |
47 |
48 | ) 49 | } 50 | 51 | export default Details 52 | 53 | 54 | async function Category({ categoryId }) { 55 | const category = await getCategory({ id: categoryId }) 56 | return ( 57 | 58 |
59 | 60 |

{category?.name}

61 |
62 | 63 | ) 64 | 65 | } -------------------------------------------------------------------------------- /lib/firestore/products/write.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase"; 2 | import { collection, deleteDoc, doc, setDoc, Timestamp } from "firebase/firestore"; 3 | 4 | // Cloudinary Upload Function 5 | const uploadToCloudinary = async (image) => { 6 | const response = await fetch("/api/upload_product", { 7 | method: "POST", 8 | headers: { "Content-Type": "application/json" }, 9 | body: JSON.stringify({ image }), 10 | }); 11 | 12 | const result = await response.json(); 13 | 14 | if (!response.ok) { 15 | throw new Error(result.message || "Cloudinary upload failed"); 16 | } 17 | 18 | return result.url; // Return uploaded image URL 19 | }; 20 | 21 | // Create New Product 22 | export const createNewProduct = async ({ data, featureImage, imageList }) => { 23 | if (!data?.title) { 24 | throw new Error("Title is required"); 25 | } 26 | if (!featureImage) { 27 | throw new Error("Feature Image is required"); 28 | } 29 | 30 | // Upload feature image to Cloudinary 31 | const featureImageUrl = await uploadToCloudinary(featureImage); 32 | 33 | // Upload additional images to Cloudinary 34 | let imageURLList = []; 35 | for (let i = 0; i < imageList?.length; i++) { 36 | const imageURL = await uploadToCloudinary(imageList[i]); 37 | imageURLList.push(imageURL); 38 | } 39 | 40 | // Generate a unique ID for the new product 41 | const newId = doc(collection(db, `products`)).id; 42 | 43 | // Save product details to Firestore 44 | await setDoc(doc(db, `products/${newId}`), { 45 | ...data, 46 | featureImageUrl, 47 | imageList: imageURLList, 48 | id: newId, 49 | timestampCreate: Timestamp.now(), 50 | }); 51 | }; 52 | 53 | // Update Existing Product 54 | export const updateProduct = async ({ data, featureImage, imageList }) => { 55 | if (!data?.title) { 56 | throw new Error("Title is required"); 57 | } 58 | if (!data?.id) { 59 | throw new Error("ID is required"); 60 | } 61 | 62 | let featureImageUrl = data?.featureImageUrl ?? ""; 63 | 64 | // Upload new feature image to Cloudinary if provided 65 | if (featureImage) { 66 | featureImageUrl = await uploadToCloudinary(featureImage); 67 | } 68 | 69 | // Update image list if new images are provided, otherwise keep existing 70 | let imageURLList = imageList?.length === 0 ? data?.imageList : []; 71 | for (let i = 0; i < imageList?.length; i++) { 72 | const imageURL = await uploadToCloudinary(imageList[i]); 73 | imageURLList.push(imageURL); 74 | } 75 | 76 | // Update product details in Firestore 77 | await setDoc(doc(db, `products/${data?.id}`), { 78 | ...data, 79 | featureImageUrl:featureImageUrl, 80 | imageList: imageURLList, 81 | timestampUpdate: Timestamp.now(), 82 | }); 83 | }; 84 | 85 | // Delete Product 86 | export const deleteProduct = async ({ id }) => { 87 | if (!id) { 88 | throw new Error("ID is required"); 89 | } 90 | 91 | // Delete product document from Firestore 92 | await deleteDoc(doc(db, `products/${id}`)); 93 | }; 94 | -------------------------------------------------------------------------------- /lib/firestore/orders/read.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { db } from "@/lib/firebase"; 4 | import { 5 | collection, 6 | doc, 7 | limit, 8 | onSnapshot, 9 | orderBy, 10 | query, 11 | startAfter, 12 | where, 13 | } from "firebase/firestore"; 14 | import useSWRSubscription from "swr/subscription"; 15 | 16 | export function useOrder({ id }) { 17 | const { data, error } = useSWRSubscription( 18 | ["orders", id], 19 | ([path, id], { next }) => { 20 | const ref = doc(db, `orders/${id}`); 21 | const unsub = onSnapshot( 22 | ref, 23 | (snapshot) => next(null, snapshot.data()), 24 | (err) => next(err, null) 25 | ); 26 | return () => unsub(); 27 | } 28 | ); 29 | 30 | if (error) { 31 | console.log(error?.message); 32 | } 33 | 34 | return { data, error: error?.message, isLoading: data === undefined }; 35 | } 36 | 37 | export function useOrders({ uid }) { 38 | const { data, error } = useSWRSubscription( 39 | ["orders", uid], 40 | ([path, uid], { next }) => { 41 | const ref = query( 42 | collection(db, path), 43 | where("uid", "==", uid), 44 | orderBy("timestampCreate", "desc") 45 | ); 46 | const unsub = onSnapshot( 47 | ref, 48 | (snapshot) => 49 | next( 50 | null, 51 | snapshot.docs.length === 0 52 | ? null 53 | : snapshot.docs.map((snap) => snap.data()) 54 | ), 55 | (err) => next(err, null) 56 | ); 57 | return () => unsub(); 58 | } 59 | ); 60 | 61 | if (error) { 62 | console.log(error?.message); 63 | } 64 | 65 | return { data, error: error?.message, isLoading: data === undefined }; 66 | } 67 | 68 | export function useAllOrders({ pageLimit, lastSnapDoc }) { 69 | const { data, error } = useSWRSubscription( 70 | ["orders", pageLimit, lastSnapDoc], 71 | ([path, pageLimit, lastSnapDoc], { next }) => { 72 | const ref = collection(db, path); 73 | let q = query( 74 | ref, 75 | limit(pageLimit ?? 10), 76 | orderBy("timestampCreate", "desc") 77 | ); 78 | 79 | if (lastSnapDoc) { 80 | q = query(q, startAfter(lastSnapDoc)); 81 | } 82 | 83 | const unsub = onSnapshot( 84 | q, 85 | (snapshot) => 86 | next(null, { 87 | list: 88 | snapshot.docs.length === 0 89 | ? null 90 | : snapshot.docs.map((snap) => snap.data()), 91 | lastSnapDoc: 92 | snapshot.docs.length === 0 93 | ? null 94 | : snapshot.docs[snapshot.docs.length - 1], 95 | }), 96 | (err) => next(err, null) 97 | ); 98 | return () => unsub(); 99 | } 100 | ); 101 | 102 | return { 103 | data: data?.list, 104 | lastSnapDoc: data?.lastSnapDoc, 105 | error: error?.message, 106 | isLoading: data === undefined, 107 | }; 108 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 🛍️ Paresh Novelty – E-Commerce Platform 2 | 3 | **Paresh Novelty** is a modern e-commerce website developed for a wholesaler specializing in fancy bindis, bangles, ladies’ novelties, general items, and imitation jewelry. Built using powerful web technologies, the platform ensures a seamless shopping experience, secure payments, and efficient inventory management. 4 | 5 | > 👨‍💻 **Developer**: Akshay H. Padia 6 | 7 | --- 8 | 9 | ## 🚀 Tech Stack 10 | 11 | - **Frontend:** Next.js, Tailwind CSS, lucide-react 12 | - **Authentication:** Firebase Auth, NextAuth.js 13 | - **Storage:** Cloudinary (for image uploads), FireBase (information) 14 | - **Deployment:** Vercel 15 | 16 | --- 17 | 18 | ## 💡 Key Features 19 | 20 | ### 🧑‍💼 User Features 21 | - ✅ **Modern UI** using Tailwind CSS and Lucide Icons 22 | - 🛒 **Dynamic product catalog** with filtering and category-wise browsing 23 | - 🔐 **User authentication** via Firebase and NextAuth 24 | - 💳 **Secure Stripe payments** with live checkout integration 25 | - 📱 **Mobile responsive design** for all screen sizes 26 | 27 | ### 📦 Admin Features 28 | - 📤 **Image upload** via Cloudinary 29 | - 🧾 **Inventory and product management** dashboard 30 | - 🔐 **Admin authentication & route protection** 31 | - 📊 **Order tracking and status management** 32 | 33 | --- 34 | 35 | ## 🧰 Project Setup 36 | 37 | ### 1. Clone the Repository 38 | 39 | ```bash 40 | git clone https://github.com/your-username/paresh-novelty.git 41 | cd paresh-novelty 42 | 43 | 44 | This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 45 | 46 | ## Getting Started 47 | 48 | First, run the development server: 49 | 50 | ```bash 51 | npm run dev 52 | # or 53 | yarn dev 54 | # or 55 | pnpm dev 56 | # or 57 | bun dev 58 | ``` 59 | 60 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 61 | 62 | You can start editing the page by modifying `app/page.js`. The page auto-updates as you edit the file. 63 | 64 | This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel. 65 | 66 | ## Learn More 67 | 68 | To learn more about Next.js, take a look at the following resources: 69 | 70 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 71 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 72 | 73 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome! 74 | 75 | ## Deploy on Vercel 76 | 77 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 78 | 79 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details. 80 | -------------------------------------------------------------------------------- /app/admin/components/AdminLayout.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import React, { useEffect, useRef, useState } from 'react' 4 | import Sidebar from './Sidebar' 5 | import Header from './Header' 6 | import { usePathname } from 'next/navigation'; 7 | import { useAuth } from '@/context/AuthContext'; 8 | import { useAdmin } from '@/lib/firestore/admins/read'; 9 | import { Button, CircularProgress } from '@nextui-org/react'; 10 | import { signOut } from 'firebase/auth'; 11 | 12 | function AdminLayout({ children }) { 13 | const [isOpen, setIsOpen] = useState(false); 14 | const pathname = usePathname(); 15 | const sidebarRef = useRef(null); 16 | const { user } = useAuth(); 17 | const { data: admin, error, isLoading } = useAdmin({ email: user?.email }); 18 | 19 | const toogleSidebar = () => { 20 | setIsOpen(!isOpen); 21 | }; 22 | 23 | useEffect(() => { 24 | toogleSidebar(); 25 | }, [pathname]); 26 | 27 | //side par click krne par band 28 | useEffect(() => { 29 | function handleClickSideOutEvent(event) { 30 | if (sidebarRef.current && !sidebarRef?.current?.contains(event.target)) { 31 | setIsOpen(false); 32 | } 33 | } 34 | document.addEventListener("mousedown", handleClickSideOutEvent); 35 | return () => { 36 | document.removeEventListener("mousedown", handleClickSideOutEvent); 37 | } 38 | }, []); 39 | 40 | if (isLoading) { 41 | return
42 | 43 |
44 | } 45 | 46 | if (!admin) { 47 | return
48 |

You are not Admin.

49 |

{user?.email}

50 | 53 |
54 | } 55 | 56 | if (error) { 57 | return
58 |

{error}

59 |
60 | } 61 | 62 | return ( 63 |
64 |
65 | 66 |
67 |
71 | 72 |
73 | {/* sidebar here bec we need for all page */} 74 |
75 |
76 |
{children}
77 | 78 |
79 |
80 | ) 81 | } 82 | 83 | export default AdminLayout -------------------------------------------------------------------------------- /app/components/AddToCardButton.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | 4 | import { Button } from "@nextui-org/react"; 5 | import { useState } from "react"; 6 | import toast from "react-hot-toast"; 7 | 8 | import { useRouter } from "next/navigation"; 9 | import { useAuth } from "@/context/AuthContext"; 10 | import { useUser } from "@/lib/firestore/users/read"; 11 | import { updateCarts, updateFavorites } from "@/lib/firestore/users/write"; 12 | import AddShoppingCartIcon from '@mui/icons-material/AddShoppingCart'; 13 | import ShoppingCartIcon from '@mui/icons-material/ShoppingCart'; 14 | 15 | 16 | /*/users/{uid} 17 | //favorites:[] 18 | //carts:[ 19 | //{ 20 | id:"dmekndl", 21 | quantity:3, 22 | //} 23 | //]*/ 24 | 25 | export default function AddToCartButton({ productId }) { 26 | const { user } = useAuth(); 27 | const { data } = useUser({ uid: user?.uid }); 28 | const [isLoading, setIsLoading] = useState(false); 29 | const router = useRouter(); 30 | 31 | const isAdded = data?.carts?.find((item) => item?.id === productId); 32 | 33 | const handleClick = async () => { 34 | setIsLoading(true); 35 | try { 36 | if (!user?.uid) { 37 | router.push("/login"); 38 | throw new Error("Please Logged In First!"); 39 | } 40 | // if (data?.carts?.includes(productId)) { 41 | // const newList = data?.carts?.filter((item) => item != productId); 42 | // await updateCarts({ list: newList, uid: user?.uid }); 43 | // } else { 44 | // await updateCarts({ 45 | // list: [...(data?.carts ?? []), { id: productId, quantity: 1 }], 46 | // uid: user?.uid, 47 | // }); 48 | // } 49 | if (data?.carts?.some((item) => item.id === productId)) { 50 | const newList = data?.carts?.filter((item) => item.id !== productId); 51 | await updateCarts({ list: newList, uid: user?.uid }); 52 | } else { 53 | await updateCarts({ 54 | list: [...(data?.carts ?? []), { id: productId, quantity: 1 }], 55 | uid: user?.uid, 56 | }); 57 | } 58 | } catch (error) { 59 | toast.error(error?.message); 60 | } 61 | setIsLoading(false); 62 | }; 63 | 64 | 65 | return ( 66 | 85 | ); 86 | } -------------------------------------------------------------------------------- /lib/firestore/products/read.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { db } from "@/lib/firebase"; 4 | import { collection, doc, limit, onSnapshot, query, startAfter, where } from "firebase/firestore"; 5 | import useSWRSubscription from "swr/subscription"; 6 | 7 | export function useProducts({ pageLimit, lastSnapDoc }) { 8 | const { data, error } = useSWRSubscription( 9 | //key 10 | ["products", pageLimit, lastSnapDoc], 11 | ([path, pageLimit, lastSnapDoc], { next }) => { 12 | const ref = collection(db, path); 13 | //limit 14 | let q = query(ref, limit(pageLimit ?? 10)); 15 | 16 | //firebase paginate 17 | if (lastSnapDoc) { 18 | q = query(q, startAfter(lastSnapDoc)); 19 | } 20 | 21 | const unsub = onSnapshot( 22 | q, 23 | (snapshot) => 24 | next( 25 | null, 26 | { 27 | list: 28 | snapshot.docs.length === 0 29 | ? null 30 | : snapshot.docs.map((snap) => snap.data()), 31 | lastSnapDoc: 32 | snapshot.docs.length === 0 33 | ? null 34 | : snapshot.docs[snapshot.docs.length - 1], 35 | } 36 | ), 37 | (err) => next(err, null) 38 | ); 39 | return () => unsub(); 40 | } 41 | ); 42 | 43 | return { 44 | data: data?.list, 45 | lastSnapDoc: data?.lastSnapDoc, 46 | error: error?.message, 47 | isLoading: data === undefined 48 | }; 49 | } 50 | 51 | 52 | //signgle product 53 | export function useProduct({ productId }) { 54 | const { data, error } = useSWRSubscription( 55 | ["products", productId], 56 | ([path, productId], { next }) => { 57 | const ref = doc(db, `${path}/${productId}`); 58 | 59 | const unsub = onSnapshot( 60 | ref, 61 | (snapshot) => next(null, snapshot.data()), 62 | (err) => next(err, null) 63 | ); 64 | return () => unsub(); 65 | } 66 | ); 67 | 68 | return { 69 | data: data, 70 | error: error?.message, 71 | isLoading: data === undefined, 72 | }; 73 | } 74 | 75 | 76 | export function useProductsByIds({ idsList }) { 77 | const { data, error } = useSWRSubscription( 78 | ["products", idsList], 79 | ([path, idsList], { next }) => { 80 | if (!idsList || idsList.length === 0) { 81 | next(null, []); // Return empty array if idsList is empty 82 | return () => {}; 83 | } 84 | 85 | const ref = collection(db, path); 86 | const q = query(ref, where("id", "in", idsList)); 87 | 88 | const unsub = onSnapshot( 89 | q, 90 | (snapshot) => 91 | next( 92 | null, 93 | snapshot.docs.length === 0 94 | ? [] 95 | : snapshot.docs.map((snap) => snap.data()) 96 | ), 97 | (err) => next(err, null) 98 | ); 99 | 100 | return () => unsub(); 101 | } 102 | ); 103 | 104 | return { 105 | data: data, 106 | error: error?.message, 107 | isLoading: data === undefined 108 | }; 109 | } 110 | -------------------------------------------------------------------------------- /lib/firestore/admins/write.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase"; 2 | import { collection, deleteDoc, doc, setDoc, Timestamp } from "firebase/firestore"; 3 | 4 | export const createNewAdmin = async ({ data , image }) => { 5 | if (!image) { 6 | throw new Error("Image is Required"); 7 | } 8 | if (!data?.name) { 9 | throw new Error("Name is required"); 10 | } 11 | if (!data?.email) { 12 | throw new Error("Email is required"); 13 | } 14 | 15 | // Upload image to Cloudinary via API route 16 | const response = await fetch("/api/upload_admin", { 17 | method: "POST", 18 | headers: { "Content-Type": "application/json" }, 19 | body: JSON.stringify({ image }), 20 | }); 21 | const result = await response.json(); 22 | console.log(result); 23 | 24 | if (!response.ok) { 25 | throw new Error(result.message || "Cloudinary upload failed"); 26 | } 27 | 28 | const imageURL = result.url; 29 | 30 | const newId = data?.email; 31 | 32 | await setDoc(doc(db, `admins/${newId}`), { 33 | ...data, 34 | id: newId, 35 | imageURL, 36 | timestampCreate: Timestamp.now(), 37 | }); 38 | }; 39 | 40 | export const deleteAdmin = async ({ id }) => { 41 | if (!id) { 42 | throw new Error("ID is required"); 43 | } 44 | await deleteDoc(doc(db, `admins/${id}`)); 45 | }; 46 | 47 | 48 | export const updateAdmin = async ({ data, image }) => { 49 | if (!data?.name) { 50 | throw new Error("Name is required"); 51 | } 52 | if (!data?.id) { 53 | throw new Error("ID is required"); 54 | } 55 | if (!data?.email) { 56 | throw new Error("Email is required"); 57 | } 58 | 59 | let imageURL = data?.imageURL; // Use existing imageURL if no new image is provided 60 | 61 | if (image) { 62 | // Upload new image to Cloudinary via API route 63 | const response = await fetch("/api/upload_admin", { 64 | method: "POST", 65 | headers: { "Content-Type": "application/json" }, 66 | body: JSON.stringify({ image }), 67 | }); 68 | 69 | const result = await response.json(); 70 | if (!response.ok) { 71 | throw new Error(result.message || "Cloudinary upload failed"); 72 | } 73 | 74 | imageURL = result.url; // Update imageURL with the new one 75 | } 76 | 77 | 78 | // Update the existing document with `merge: true` to avoid overwriting 79 | const id=data.id; 80 | 81 | if (id === data?.email) { 82 | const adminRef = doc(db, `admins/${data.id}`); 83 | await setDoc( 84 | adminRef, 85 | { 86 | ...data, 87 | imageURL, 88 | timestampUpdated: Timestamp.now(), 89 | }, 90 | { merge: true } // Ensures fields are merged instead of overwriting 91 | ); 92 | } else { 93 | const newId = data?.email; 94 | await deleteDoc(doc(db, `admins/${data.id}`)); 95 | const adminRef = doc(db, `admins/${newId}`); 96 | await setDoc( 97 | adminRef, 98 | { 99 | ...data, 100 | id: newId, 101 | imageURL, 102 | timestampUpdated: Timestamp.now(), 103 | }, 104 | { merge: true } // Ensures fields are merged instead of overwriting 105 | ); 106 | } 107 | 108 | 109 | }; -------------------------------------------------------------------------------- /app/(auth)/forget-passward/page.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useAuth } from '@/context/AuthContext'; 4 | import { auth } from '@/lib/firebase' 5 | import { createUser } from '@/lib/firestore/users/write'; 6 | import { Button } from '@nextui-org/react' 7 | import { createUserWithEmailAndPassword, GoogleAuthProvider, sendPasswordResetEmail, signInWithPopup, updateProfile } from 'firebase/auth'; 8 | import Link from 'next/link' 9 | import { useRouter } from 'next/navigation'; 10 | import React, { useEffect, useState } from 'react' 11 | import toast from 'react-hot-toast'; 12 | 13 | function page() { 14 | const { user } = useAuth(); 15 | const router = useRouter(); 16 | const [isLoading, setIsLoading] = useState(false); 17 | const [data, setData] = useState({}); 18 | 19 | const handleData = (key, value) => { 20 | setData({ 21 | ...data, 22 | [key]: value, 23 | }); 24 | }; 25 | 26 | const handleSendEmail = async () => { 27 | setIsLoading(true); 28 | try { 29 | await sendPasswordResetEmail(auth,data?.email); 30 | toast.success("Reset Link has been sent to yout Email"); 31 | } catch (error) { 32 | toast.error(error?.message); 33 | } 34 | setIsLoading(false); 35 | } 36 | 37 | 38 | return ( 39 | // w-full flex justify-center items-center bg-gray-300 p-24 min-h-screen //center a div 40 |
41 |
42 |
43 | logo 44 |
45 |
46 |

Forgot Password

47 | { 48 | e.preventDefault(); 49 | handleSendEmail(); 50 | }} className='flex flex-col gap-3'> 51 | { 58 | handleData('email', e.target.value); 59 | }} 60 | className='px-3 py-2 rounded-xl border focus:outline-none w-full' />{" "} 61 | 62 | 63 |
64 | 65 | 66 | 67 |
68 |
69 |
70 |
71 | ) 72 | } 73 | 74 | 75 | export default page -------------------------------------------------------------------------------- /lib/firestore/checkout/write.js: -------------------------------------------------------------------------------- 1 | import { db } from "@/lib/firebase"; 2 | import { collection, doc, setDoc, Timestamp } from "firebase/firestore"; 3 | import axios from "axios"; 4 | 5 | 6 | // Razorpay API call (use your backend for security if needed) 7 | const createRazorpayOrder = async (amount, currency = "INR") => { 8 | const res = await axios.post("/api/razorpay/order", { 9 | amount, 10 | currency, 11 | }); 12 | return res.data; // should return { id, amount, currency, ... } 13 | }; 14 | 15 | export const createCheckoutAndGetRazorpayDetails = async ({ uid, products, address }) => { 16 | const checkoutId = doc(collection(db, "ids")).id; 17 | 18 | const ref = doc(db, `users/${uid}/checkout_sessions/${checkoutId}`); 19 | 20 | let line_items = []; 21 | let totalAmount = 0; 22 | 23 | products.forEach((item) => { 24 | const price = item?.product?.salePrice * 100; 25 | line_items.push({ 26 | name: item?.product?.title ?? "", 27 | description: item?.product?.shortDescription ?? "", 28 | image: item?.product?.featureImageURL ?? `${process.env.NEXT_PUBLIC_DOMAIN}/logo.png`, 29 | amount: price, 30 | quantity: item?.quantity ?? 1, 31 | productId: item?.id, 32 | }); 33 | totalAmount += price * (item?.quantity ?? 1); 34 | }); 35 | 36 | // 1. Create Razorpay order (you can move this to a backend route for security) 37 | const razorpayOrder = await createRazorpayOrder(totalAmount); 38 | 39 | // 2. Save session details in Firestore 40 | await setDoc(ref, { 41 | id: checkoutId, 42 | razorpay_order_id: razorpayOrder.id, 43 | amount: razorpayOrder.amount, 44 | currency: razorpayOrder.currency, 45 | line_items, 46 | metadata: { 47 | uid, 48 | checkoutId, 49 | address: JSON.stringify(address), 50 | }, 51 | status: "created", 52 | createdAt: Timestamp.now(), 53 | }); 54 | 55 | return { 56 | orderId: razorpayOrder.id, 57 | amount: razorpayOrder.amount, 58 | currency: razorpayOrder.currency, 59 | checkoutId, 60 | }; 61 | }; 62 | 63 | export const createCheckoutCODAndGetId = async ({ uid, products, address }) => { 64 | const checkoutId = `cod_${doc(collection(db, `ids`)).id}`; 65 | 66 | const ref = doc(db, `users/${uid}/checkout_sessions_cod/${checkoutId}`); 67 | 68 | let line_items = []; 69 | 70 | products.forEach((item) => { 71 | line_items.push({ 72 | price_data: { 73 | currency: "inr", 74 | product_data: { 75 | name: item?.product?.title ?? "", 76 | description: item?.product?.shortDescription ?? "", 77 | images: [ 78 | item?.product?.featureImageURL ?? 79 | `${process.env.NEXT_PUBLIC_DOMAIN}/logo.png`, 80 | ], 81 | metadata: { 82 | productId: item?.id, 83 | }, 84 | }, 85 | unit_amount: item?.product?.salePrice * 100, 86 | }, 87 | quantity: item?.quantity ?? 1, 88 | }); 89 | }); 90 | 91 | await setDoc(ref, { 92 | id: checkoutId, 93 | line_items: line_items, 94 | metadata: { 95 | checkoutId: checkoutId, 96 | uid: uid, 97 | address: JSON.stringify(address), 98 | }, 99 | createdAt: Timestamp.now(), 100 | }); 101 | 102 | return checkoutId; 103 | }; -------------------------------------------------------------------------------- /app/admin/products/form/components/Description.js: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Editor, EditorState, RichUtils } from 'draft-js'; 3 | import 'draft-js/dist/Draft.css'; 4 | 5 | const Description = ({ data, handleData }) => { 6 | const [editorState, setEditorState] = useState( 7 | EditorState.createEmpty() // Initialize with an empty editor state 8 | ); 9 | 10 | // Handle editor state changes 11 | const handleChange = (newState) => { 12 | setEditorState(newState); 13 | const content = newState.getCurrentContent().getPlainText(); 14 | handleData('description', content); // Pass the plain text content to the parent component 15 | }; 16 | 17 | // Apply rich text formatting 18 | const handleKeyCommand = (command) => { 19 | const newState = RichUtils.handleKeyCommand(editorState, command); 20 | if (newState) { 21 | handleChange(newState); 22 | return 'handled'; 23 | } 24 | return 'not-handled'; 25 | }; 26 | 27 | // Apply inline style (e.g., bold, italic, underline) 28 | const applyInlineStyle = (style) => { 29 | handleChange(RichUtils.toggleInlineStyle(editorState, style)); 30 | }; 31 | 32 | // Apply block style (e.g., headers, lists) 33 | const applyBlockType = (blockType) => { 34 | handleChange(RichUtils.toggleBlockType(editorState, blockType)); 35 | }; 36 | 37 | return ( 38 |
39 |

Description

40 |
41 | {/* Toolbar */} 42 |
43 | 49 | 55 | 61 | {/* */} 67 | 73 | 79 |
80 | {/* Draft.js Editor */} 81 |
82 | 88 |
89 | 90 |
91 |
92 | ); 93 | }; 94 | 95 | export default Description; 96 | -------------------------------------------------------------------------------- /app/(user)/account/page.js: -------------------------------------------------------------------------------- 1 | // "use client" 2 | // import React from 'react' 3 | 4 | // function page() { 5 | // return ( 6 | //
7 | //

Account

8 | // Admin 9 | //
10 | // ) 11 | // } 12 | 13 | // export default page 14 | 15 | "use client"; 16 | 17 | import { useAuth } from "@/context/AuthContext"; 18 | import { useOrders } from "@/lib/firestore/orders/read"; 19 | import { CircularProgress } from "@nextui-org/react"; 20 | 21 | export default function Page() { 22 | const { user } = useAuth(); 23 | 24 | const { data: orders, error, isLoading } = useOrders({ uid: user?.uid }); 25 | 26 | if (isLoading) { 27 | return ( 28 |
29 | 30 |
31 | ); 32 | } 33 | 34 | if (error) { 35 | return <>{error}; 36 | } 37 | 38 | return ( 39 |
40 | Admin 41 |

My Orders

42 | {(!orders || orders?.length === 0) && ( 43 |
44 |
45 | {/* */} 46 |
47 |

You have no order

48 |
49 | )} 50 |
51 | {orders?.map((item, orderIndex) => { 52 | const totalAmount = item?.checkout?.line_items?.reduce( 53 | (prev, curr) => { 54 | return ( 55 | prev + (curr?.price_data?.unit_amount / 100) * curr?.quantity 56 | ); 57 | }, 58 | 0 59 | ); 60 | return ( 61 |
62 |
63 |
64 |

{orderIndex + 1}

65 |

66 | {item?.paymentMode} 67 |

68 |

69 | {item?.status ?? "pending"} 70 |

71 |

₹ {totalAmount}

72 |
73 |

74 | {item?.timestampCreate?.toDate()?.toString()} 75 |

76 |
77 |
78 | {item?.checkout?.line_items?.map((product) => { 79 | return ( 80 |
81 | Product Image 86 |
87 |

88 | {product?.price_data?.product_data?.name} 89 |

90 |

91 | ₹ {product?.price_data?.unit_amount / 100}{" "} 92 | X{" "} 93 | {product?.quantity?.toString()} 94 |

95 |
96 |
97 | ); 98 | })} 99 |
100 |
101 | ); 102 | })} 103 |
104 |
105 | ); 106 | } -------------------------------------------------------------------------------- /pages/api/razorpay/order.js: -------------------------------------------------------------------------------- 1 | // pages/api/razorpay/order.js 2 | import Razorpay from "razorpay"; 3 | 4 | const razorpay = new Razorpay({ 5 | key_id: process.env.NEXT_PUBLIC_RAZORPAY_KEY_ID, 6 | key_secret: process.env.NEXT_PUBLIC_RAZORPAY_SECRET, 7 | }); 8 | 9 | export default async function handler(req, res) { 10 | if (req.method !== "POST") return res.status(405).end(); 11 | 12 | const { amount, currency = "INR" } = req.body; 13 | 14 | try { 15 | // ✅ Convert rupees to paise 16 | const amountInPaise = Math.round(amount * 100); 17 | 18 | const order = await razorpay.orders.create({ 19 | amount: amountInPaise, 20 | currency, 21 | payment_capture: 1, 22 | }); 23 | 24 | res.status(200).json(order); 25 | } catch (err) { 26 | console.error(err); 27 | res.status(500).json({ error: "Failed to create Razorpay order" }); 28 | } 29 | } 30 | // import Razorpay from "razorpay"; 31 | // import { adminDB } from "@/lib/firebase_admin"; 32 | 33 | // export default async function handler(req, res) { 34 | // if (req.method !== "POST") return res.status(405).end(); 35 | 36 | // try { 37 | // // Log the entire request body to see what's coming in 38 | // console.log("Request body:", JSON.stringify(req.body)); 39 | 40 | // // Check if req.body exists and is not empty 41 | // if (!req.body || Object.keys(req.body).length === 0) { 42 | // console.error("Request body is empty or undefined"); 43 | // return res.status(400).json({ error: "Request body is empty" }); 44 | // } 45 | 46 | // const { amount, uid, address, productList } = req.body; 47 | 48 | // // Validate required fields 49 | // if (!amount) { 50 | // console.error("Amount is missing from request"); 51 | // return res.status(400).json({ error: "Amount is required" }); 52 | // } 53 | 54 | // // ✅ Convert rupees to paise 55 | // const amountInPaise = Math.round(amount * 100); 56 | 57 | // console.log("Creating Razorpay order with amount:", amountInPaise); 58 | 59 | // // Initialize Razorpay with your credentials 60 | // const razorpay = new Razorpay({ 61 | // key_id: process.env.RAZORPAY_KEY_ID, 62 | // key_secret: process.env.RAZORPAY_SECRET, 63 | // }); 64 | 65 | // // Create the order object explicitly 66 | // const orderOptions = { 67 | // amount: amountInPaise, 68 | // currency: "INR", 69 | // receipt: `receipt_${Date.now()}`, 70 | // payment_capture: 1, 71 | // }; 72 | 73 | // console.log("Order options:", orderOptions); 74 | 75 | // // Create a Razorpay order 76 | // const order = await razorpay.orders.create(orderOptions); 77 | 78 | // console.log("Razorpay order created:", order); 79 | 80 | // // Prepare the session data to save in Firestore 81 | // const checkoutData = { 82 | // id: order.id, 83 | // amount: amountInPaise, 84 | // currency: "INR", 85 | // status: "pending", 86 | // uid, 87 | // address, 88 | // productList, 89 | // retry_url: `https://checkout.razorpay.com/v1/checkout/embedded/${order.id}`, 90 | // createdAt: new Date().toISOString(), 91 | // }; 92 | 93 | // // Save order details to Firestore 94 | // await adminDB 95 | // .collection("razorpay_checkout_sessions") 96 | // .doc(order.id) 97 | // .set(checkoutData); 98 | 99 | // // Respond with the order details 100 | // res.status(200).json({ 101 | // order_id: order.id, 102 | // amount: amountInPaise, 103 | // retryUrl: checkoutData.retry_url, 104 | // }); 105 | // } catch (err) { 106 | // console.error("Error creating Razorpay order:", err); 107 | // // Log the full error stack 108 | // console.error(err.stack); 109 | // res.status(500).json({ error: err?.message || "Failed to create Razorpay order" }); 110 | // } 111 | // } -------------------------------------------------------------------------------- /app/(pages)/products/[productId]/components/ProductCard.js: -------------------------------------------------------------------------------- 1 | import AddToCartButton from "@/app/components/AddToCardButton"; 2 | import FavoriteButton from "@/app/components/FavoriteButton"; 3 | import AuthContextProvider from "@/context/AuthContext"; 4 | import { Rating } from "@mui/material"; 5 | import { Button } from "@nextui-org/react"; 6 | import { ShoppingCart, Heart } from "lucide-react"; 7 | import Link from "next/link"; 8 | 9 | export function ProductCard({ product }) { 10 | return ( 11 |
12 | {/*

All Products

*/} 13 |
14 |
15 |
16 | {product.title} 21 | {product.imageList?.[0] && ( 22 | {product.title} 27 | )} 28 |
29 |

{product.title}

30 |

{product.shortDescription}

31 |
32 | 33 |

(0)

34 |
35 |
36 | {product.salePrice < product.price ? ( 37 | <> 38 | ₹ {product.salePrice}{" "} 39 | ₹ {product.price} 40 | 41 | ) : ( 42 | ₹ {product.price} 43 | )} 44 |
45 |
46 |
47 | 48 | 51 | 52 | {/* 55 | */} 58 | 59 | 60 | 61 | 62 | 63 | 64 |
65 |
66 |
67 |
68 |
69 | ); 70 | } -------------------------------------------------------------------------------- /app/components/FavoriteButton.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | 4 | import { Button } from "@nextui-org/react"; 5 | import { useState } from "react"; 6 | import toast from "react-hot-toast"; 7 | import FavoriteBorderOutlinedIcon from "@mui/icons-material/FavoriteBorderOutlined"; 8 | import FavoriteIcon from "@mui/icons-material/Favorite"; 9 | import { useRouter } from "next/navigation"; 10 | import { useAuth } from "@/context/AuthContext"; 11 | import { useUser } from "@/lib/firestore/users/read"; 12 | import { updateFavorites } from "@/lib/firestore/users/write"; 13 | 14 | export default function FavoriteButton({ productId }) { 15 | const { user } = useAuth(); 16 | const { data } = useUser({ uid: user?.uid }); 17 | const [isLoading, setIsLoading] = useState(false); 18 | const router = useRouter(); 19 | 20 | const handleClick = async () => { 21 | setIsLoading(true); 22 | try { 23 | if (!user?.uid) { 24 | router.push("/login"); 25 | throw new Error("Please Logged In First!"); 26 | } 27 | if (data?.favorites?.includes(productId)) { 28 | const newList = data?.favorites?.filter((item) => item != productId); 29 | await updateFavorites({ list: newList, uid: user?.uid }); 30 | } else { 31 | await updateFavorites({ 32 | list: [...(data?.favorites ?? []), productId], 33 | uid: user?.uid, 34 | }); 35 | } 36 | } catch (error) { 37 | toast.error(error?.message); 38 | } 39 | setIsLoading(false); 40 | }; 41 | 42 | const isLiked = data?.favorites?.includes(productId); 43 | 44 | return ( 45 | 64 | ); 65 | } 66 | 67 | 68 | 69 | 70 | 71 | //className="border border-[#FEC4C7] text-[#FEC4C7] bg-transparent hover:bg-[#fbe1e3] transition-all" 72 | 73 | // import { useAuth } from "@/contexts/AuthContext"; 74 | // import { useUser } from "@/lib/firestore/user/read"; 75 | // import { updateFavorites } from "@/lib/firestore/user/write"; 76 | // import { Button } from "@nextui-org/react"; 77 | // import { useState } from "react"; 78 | // import toast from "react-hot-toast"; 79 | // import FavoriteBorderOutlinedIcon from "@mui/icons-material/FavoriteBorderOutlined"; 80 | // import FavoriteIcon from "@mui/icons-material/Favorite"; 81 | // import { useRouter } from "next/navigation"; 82 | 83 | // export default function FavoriteButton({ productId }) { 84 | // const { user } = useAuth(); 85 | // const { data } = useUser({ uid: user?.uid }); 86 | // const [isLoading, setIsLoading] = useState(false); 87 | // const router = useRouter(); 88 | 89 | // const handlClick = async () => { 90 | // setIsLoading(true); 91 | // try { 92 | // if (!user?.uid) { 93 | // router.push("/login"); 94 | // throw new Error("Please Logged In First!"); 95 | // } 96 | // if (data?.favorites?.includes(productId)) { 97 | // const newList = data?.favorites?.filter((item) => item != productId); 98 | // await updateFavorites({ list: newList, uid: user?.uid }); 99 | // } else { 100 | // await updateFavorites({ 101 | // list: [...(data?.favorites ?? []), productId], 102 | // uid: user?.uid, 103 | // }); 104 | // } 105 | // } catch (error) { 106 | // toast.error(error?.message); 107 | // } 108 | // setIsLoading(false); 109 | // }; 110 | 111 | // const isLiked = data?.favorites?.includes(productId); 112 | 113 | // return ( 114 | // 127 | // ); 128 | // } 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /app/admin/brands/components/ListView.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | 4 | import { db } from '@/lib/firebase'; 5 | import { useBrands } from '@/lib/firestore/brands/read'; 6 | import { deleteBrand } from '@/lib/firestore/brands/write'; 7 | import { Button, CircularProgress } from '@nextui-org/react'; 8 | import { doc, getDoc } from 'firebase/firestore'; 9 | import { Edit2, Trash2 } from 'lucide-react'; 10 | import { useRouter } from 'next/navigation'; 11 | import React, { useState } from 'react' 12 | import toast from 'react-hot-toast'; 13 | 14 | function ListView() { 15 | const { data: brands, error, isLoading } = useBrands(); 16 | 17 | if (isLoading) { 18 | return
19 | 20 |
21 | } 22 | 23 | if (error) { 24 | return
{error}
25 | } 26 | 27 | return ( 28 |
29 |

Brands

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {brands?.map((item, index) => { 41 | return ( 42 | 43 | ) 44 | })} 45 | 46 | 47 |
SrNo.ImageNameActions
48 |
49 | ) 50 | } 51 | 52 | export default ListView 53 | 54 | 55 | function Row({ item, index }) { 56 | 57 | const [isDeleting, setIsDeleting] = useState(false); 58 | const router = useRouter(); 59 | 60 | const handleDelete = async () => { 61 | if (!confirm("Are you sure?")) return; 62 | 63 | setIsDeleting(true); 64 | try { 65 | // Get the category document from Firestore 66 | const brandRef = doc(db, `brands/${item?.id}`); 67 | const brandDoc = await getDoc(brandRef); 68 | 69 | if (!brandDoc.exists()) { 70 | toast.error("Brand not found"); 71 | setIsDeleting(false); 72 | return; 73 | } 74 | 75 | const brandData = brandDoc.data(); 76 | const imageURL = brandData?.imageURL; 77 | 78 | if (imageURL) { 79 | // Extract the public_id from the image URL (Cloudinary URL format) 80 | // Assuming the URL is in the format: https://res.cloudinary.com/dcvc04m9h/image/upload/v1735724898/categories/ipgl0cv2gntmjgt3vnbq.jpg 81 | const publicId = imageURL.split('/').slice(-2).join('/').split('.')[0]; 82 | 83 | // Call API route to delete the image from Cloudinary 84 | const response = await fetch('/api/deleteImage', { 85 | method: 'POST', 86 | headers: { 87 | 'Content-Type': 'application/json', 88 | }, 89 | body: JSON.stringify({ publicId }), 90 | }); 91 | 92 | if (!response.ok) { 93 | throw new Error("Failed to delete image from Cloudinary"); 94 | } 95 | } 96 | 97 | // Delete the category from Firestore 98 | await deleteBrand({ id: item?.id }); 99 | 100 | toast.success("Successfully Deleted"); 101 | } catch (error) { 102 | toast.error(error.message); 103 | } 104 | setIsDeleting(false); 105 | }; 106 | 107 | const handleUpdate = () => { 108 | router.push(`/admin/brands?id=${item?.id}`); 109 | } 110 | 111 | return ( 112 | 113 | {index + 1} 114 | 115 |
116 | 117 |
118 | 119 | {item.name} 120 | 121 |
122 | 125 | 128 |
129 | 130 | 131 | ); 132 | } -------------------------------------------------------------------------------- /app/components/ProductsList.js: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import AboveNav from "./AboveNav"; 4 | import Header from "./Header"; 5 | import { Rating } from "@mui/material"; 6 | import { Button } from "@nextui-org/react"; 7 | import { Heart, ShoppingCart } from "lucide-react"; 8 | import Link from "next/link"; 9 | import FavoriteButton from "./FavoriteButton"; 10 | import AuthContextProvider from "@/context/AuthContext"; 11 | import AddToCartButton from "./AddToCardButton"; 12 | 13 | export default function ProductsList({ products }) { 14 | return ( 15 | <> 16 | 17 |
18 |
19 |

All Products

20 |
21 | {products.map((product, index) => ( 22 |
26 |
27 | {product.title} 32 | {product.imageList[0] && ( 33 | {product.title} 38 | )} 39 |
40 | 41 |

{product.title}

42 |

{product.shortDescription}

43 |
44 | 45 |

(0)

46 |
47 |
48 | {product.salePrice < product.price ? ( 49 | <> 50 | ₹ {product.salePrice}{" "} 51 | ₹ {product.price} 52 | 53 | ) : ( 54 | ₹ {product.price} 55 | )} 56 |
57 |
58 |
59 | 60 | 63 | 64 | {/* */} 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 |
75 |
76 | ))} 77 |
78 |
79 | 80 | ); 81 | } 82 | -------------------------------------------------------------------------------- /app/admin/admins/components/ListView.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | 4 | import { db } from '@/lib/firebase'; 5 | import { useAdmins } from '@/lib/firestore/admins/read'; 6 | import { deleteAdmin } from '@/lib/firestore/admins/write'; 7 | import { Button, CircularProgress } from '@nextui-org/react'; 8 | import { doc, getDoc } from 'firebase/firestore'; 9 | import { Edit2, Trash2 } from 'lucide-react'; 10 | import { useRouter } from 'next/navigation'; 11 | import React, { useState } from 'react' 12 | import toast from 'react-hot-toast'; 13 | 14 | function ListView() { 15 | const { data: admins, error, isLoading } = useAdmins(); 16 | 17 | if (isLoading) { 18 | return
19 | 20 |
21 | } 22 | 23 | if (error) { 24 | return
{error}
25 | } 26 | 27 | return ( 28 |
29 |

Admin

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {admins?.map((item, index) => { 41 | return ( 42 | 43 | ) 44 | })} 45 | 46 | 47 |
SrNo.ImageNameActions
48 |
49 | ) 50 | } 51 | 52 | export default ListView 53 | 54 | 55 | function Row({ item, index }) { 56 | 57 | const [isDeleting, setIsDeleting] = useState(false); 58 | const router = useRouter(); 59 | 60 | const handleDelete = async () => { 61 | if (!confirm("Are you sure?")) return; 62 | 63 | setIsDeleting(true); 64 | try { 65 | // Get the category document from Firestore 66 | const adminRef = doc(db, `admins/${item?.id}`); 67 | const adminDoc = await getDoc(adminRef); 68 | 69 | if (!adminDoc.exists()) { 70 | toast.error("Admin not found"); 71 | setIsDeleting(false); 72 | return; 73 | } 74 | 75 | const adminData = adminDoc.data(); 76 | const imageURL = adminData?.imageURL; 77 | 78 | if (imageURL) { 79 | // Extract the public_id from the image URL (Cloudinary URL format) 80 | // Assuming the URL is in the format: https://res.cloudinary.com/dcvc04m9h/image/upload/v1735724898/categories/ipgl0cv2gntmjgt3vnbq.jpg 81 | const publicId = imageURL.split('/').slice(-2).join('/').split('.')[0]; 82 | 83 | // Call API route to delete the image from Cloudinary 84 | const response = await fetch('/api/deleteImage', { 85 | method: 'POST', 86 | headers: { 87 | 'Content-Type': 'application/json', 88 | }, 89 | body: JSON.stringify({ publicId }), 90 | }); 91 | 92 | if (!response.ok) { 93 | throw new Error("Failed to delete image from Cloudinary"); 94 | } 95 | } 96 | 97 | // Delete the category from Firestore 98 | await deleteAdmin({ id: item?.id }); 99 | 100 | toast.success("Successfully Deleted"); 101 | } catch (error) { 102 | toast.error(error.message); 103 | } 104 | setIsDeleting(false); 105 | }; 106 | 107 | const handleUpdate = () => { 108 | router.push(`/admin/admins?id=${item?.id}`); 109 | } 110 | 111 | return ( 112 | 113 | {index + 1} 114 | 115 |
116 | 117 |
118 | 119 | 120 |
121 |

{item.name}

122 |

{item?.email}

123 |
124 | 125 | 126 |
127 | 130 | 133 |
134 | 135 | 136 | ); 137 | } -------------------------------------------------------------------------------- /app/admin/categories/components/ListView.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | 4 | import { db } from '@/lib/firebase'; 5 | import { useCategories } from '@/lib/firestore/categories/read' 6 | import { deleteCategory } from '@/lib/firestore/categories/write'; 7 | import { Button, CircularProgress } from '@nextui-org/react'; 8 | import { doc, getDoc } from 'firebase/firestore'; 9 | import { Edit2, Trash2 } from 'lucide-react'; 10 | import { useRouter } from 'next/navigation'; 11 | import React, { useState } from 'react' 12 | import toast from 'react-hot-toast'; 13 | 14 | function ListView() { 15 | const { data: categories, error, isLoading } = useCategories(); 16 | 17 | if (isLoading) { 18 | return
19 | 20 |
21 | } 22 | 23 | if (error) { 24 | return
{error}
25 | } 26 | 27 | return ( 28 |
29 |

Categories

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | {categories?.map((item, index) => { 41 | return ( 42 | 43 | ) 44 | })} 45 | 46 | 47 |
SrNo.ImageNameActions
48 |
49 | ) 50 | } 51 | 52 | export default ListView 53 | 54 | 55 | function Row({ item, index }) { 56 | 57 | const [isDeleting, setIsDeleting] = useState(false); 58 | const router = useRouter(); 59 | 60 | const handleDelete = async () => { 61 | if (!confirm("Are you sure?")) return; 62 | 63 | setIsDeleting(true); 64 | try { 65 | // Get the category document from Firestore 66 | const categoryRef = doc(db, `categories/${item?.id}`); 67 | const categoryDoc = await getDoc(categoryRef); 68 | 69 | if (!categoryDoc.exists()) { 70 | toast.error("Category not found"); 71 | setIsDeleting(false); 72 | return; 73 | } 74 | 75 | const categoryData = categoryDoc.data(); 76 | const imageURL = categoryData?.imageURL; 77 | 78 | if (imageURL) { 79 | // Extract the public_id from the image URL (Cloudinary URL format) 80 | // Assuming the URL is in the format: https://res.cloudinary.com/dcvc04m9h/image/upload/v1735724898/categories/ipgl0cv2gntmjgt3vnbq.jpg 81 | const publicId = imageURL.split('/').slice(-2).join('/').split('.')[0]; 82 | 83 | // Call API route to delete the image from Cloudinary 84 | const response = await fetch('/api/deleteImage', { 85 | method: 'POST', 86 | headers: { 87 | 'Content-Type': 'application/json', 88 | }, 89 | body: JSON.stringify({ publicId }), 90 | }); 91 | 92 | if (!response.ok) { 93 | throw new Error("Failed to delete image from Cloudinary"); 94 | } 95 | } 96 | 97 | // Delete the category from Firestore 98 | await deleteCategory({ id: item?.id }); 99 | 100 | toast.success("Successfully Deleted"); 101 | } catch (error) { 102 | toast.error(error.message); 103 | } 104 | setIsDeleting(false); 105 | }; 106 | 107 | const handleUpdate = () => { 108 | router.push(`/admin/categories?id=${item?.id}`); 109 | } 110 | 111 | return ( 112 | 113 | {index + 1} 114 | 115 |
116 | 117 |
118 | 119 | {item.name} {item?.isFeatured === true && Featured } 120 | 121 |
122 | 125 | 128 |
129 | 130 | 131 | ); 132 | } -------------------------------------------------------------------------------- /app/(auth)/sign-up/page.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useAuth } from '@/context/AuthContext'; 4 | import { auth } from '@/lib/firebase' 5 | import { createUser } from '@/lib/firestore/users/write'; 6 | import { Button } from '@nextui-org/react' 7 | import { createUserWithEmailAndPassword, GoogleAuthProvider, signInWithPopup, updateProfile } from 'firebase/auth'; 8 | import Link from 'next/link' 9 | import { useRouter } from 'next/navigation'; 10 | import React, { useEffect, useState } from 'react' 11 | import toast from 'react-hot-toast'; 12 | 13 | function page() { 14 | const { user } = useAuth(); 15 | const router = useRouter(); 16 | const [isLoading, setIsLoading] = useState(false); 17 | const [data, setData] = useState({}); 18 | 19 | const handleData = (key, value) => { 20 | setData({ 21 | ...data, 22 | [key]: value, 23 | }); 24 | }; 25 | 26 | const handleSignUp = async () => { 27 | setIsLoading(true); 28 | try { 29 | //signUp 30 | const credential = await createUserWithEmailAndPassword( 31 | auth, 32 | data?.email, 33 | data?.password 34 | ); 35 | await updateProfile(credential.user,{ 36 | displayName:data?.name, 37 | }); 38 | const user=credential?.user; 39 | await createUser({ 40 | uid:user?.uid, 41 | displayName:data?.name, 42 | photoURL:user?.photoURL, 43 | }); 44 | toast.success("SuccessFully Signed Up.") 45 | router.push('/account'); 46 | } catch (error) { 47 | toast.error(error?.message); 48 | } 49 | setIsLoading(false); 50 | } 51 | 52 | 53 | return ( 54 | // w-full flex justify-center items-center bg-gray-300 p-24 min-h-screen //center a div 55 |
56 |
57 |
58 | logo 59 |
60 |
61 |

Sign Up with Email

62 |
{ 63 | e.preventDefault(); 64 | handleSignUp(); 65 | }} className='flex flex-col gap-3'> 66 | { 73 | handleData('name', e.target.value); 74 | }} 75 | className='px-3 py-2 rounded-xl border focus:outline-none w-full' /> 76 | { 83 | handleData('email', e.target.value); 84 | }} 85 | className='px-3 py-2 rounded-xl border focus:outline-none w-full' />{" "} 86 | { 93 | handleData('password', e.target.value); 94 | }} 95 | className='px-3 py-2 rounded-xl border focus:outline-none w-full' /> 96 | 97 |
98 |
99 | 100 | 101 | 102 |
103 |
104 |
105 |
106 | ) 107 | } 108 | 109 | 110 | export default page -------------------------------------------------------------------------------- /app/admin/collections/components/ListView.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | 4 | import { db } from '@/lib/firebase'; 5 | import { useCollections } from '@/lib/firestore/collections/read'; 6 | import { deleteCollection } from '@/lib/firestore/collections/write'; 7 | import { Button, CircularProgress } from '@nextui-org/react'; 8 | import { doc, getDoc } from 'firebase/firestore'; 9 | import { Edit2, Trash2 } from 'lucide-react'; 10 | import { useRouter } from 'next/navigation'; 11 | import React, { useState } from 'react' 12 | import toast from 'react-hot-toast'; 13 | 14 | function ListView() { 15 | const { data: collections, error, isLoading } = useCollections(); 16 | 17 | if (isLoading) { 18 | return
19 | 20 |
21 | } 22 | 23 | if (error) { 24 | return
{error}
25 | } 26 | 27 | return ( 28 |
29 |

Collections

30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | {collections?.map((item, index) => { 42 | return ( 43 | 44 | ) 45 | })} 46 | 47 | 48 |
SrNo.ImageTitleProductsActions
49 |
50 | ) 51 | } 52 | 53 | export default ListView 54 | 55 | 56 | function Row({ item, index }) { 57 | 58 | const [isDeleting, setIsDeleting] = useState(false); 59 | const router = useRouter(); 60 | 61 | const handleDelete = async () => { 62 | if (!confirm("Are you sure?")) return; 63 | 64 | setIsDeleting(true); 65 | try { 66 | // Get the category document from Firestore 67 | const collectionRef = doc(db, `collections/${item?.id}`); 68 | const collectionDoc = await getDoc(collectionRef); 69 | 70 | if (!collectionDoc.exists()) { 71 | toast.error("Collection not found"); 72 | setIsDeleting(false); 73 | return; 74 | } 75 | 76 | const collectionData = collectionDoc.data(); 77 | const imageURL = collectionData?.imageURL; 78 | 79 | if (imageURL) { 80 | // Extract the public_id from the image URL (Cloudinary URL format) 81 | // Assuming the URL is in the format: https://res.cloudinary.com/dcvc04m9h/image/upload/v1735724898/categories/ipgl0cv2gntmjgt3vnbq.jpg 82 | const publicId = imageURL.split('/').slice(-2).join('/').split('.')[0]; 83 | 84 | // Call API route to delete the image from Cloudinary 85 | const response = await fetch('/api/deleteImage', { 86 | method: 'POST', 87 | headers: { 88 | 'Content-Type': 'application/json', 89 | }, 90 | body: JSON.stringify({ publicId }), 91 | }); 92 | 93 | if (!response.ok) { 94 | throw new Error("Failed to delete image from Cloudinary"); 95 | } 96 | } 97 | 98 | // Delete the category from Firestore 99 | await deleteCollection({ id: item?.id }); 100 | 101 | toast.success("Successfully Deleted"); 102 | } catch (error) { 103 | toast.error(error.message); 104 | } 105 | setIsDeleting(false); 106 | }; 107 | 108 | const handleUpdate = () => { 109 | router.push(`/admin/collections?id=${item?.id}`); 110 | } 111 | 112 | return ( 113 | 114 | {index + 1} 115 | 116 |
117 | 118 |
119 | 120 | {item?.title} 121 | {item?.products?.length} 122 | 123 |
124 | 127 | 130 |
131 | 132 | 133 | ); 134 | } 135 | 136 | -------------------------------------------------------------------------------- /app/(user)/cart/page.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useAuth } from "@/context/AuthContext" 4 | import { useProduct } from "@/lib/firestore/products/read"; 5 | import { useUser } from "@/lib/firestore/users/read"; 6 | import { updateCarts } from "@/lib/firestore/users/write"; 7 | import { Button, CircularProgress } from "@nextui-org/react"; 8 | import { Minus, Plus, X } from "lucide-react"; 9 | import Link from "next/link"; 10 | import { useState } from "react"; 11 | 12 | 13 | export default function Page() { 14 | const { user } = useAuth(); 15 | const { data, isLoading } = useUser({ uid: user?.uid }); 16 | if (isLoading) { 17 | return ( 18 |
19 | 20 |
21 | ); 22 | } 23 | return ( 24 |
25 |

Cart

26 | {(!data?.carts || data?.carts?.length === 0) && ( 27 |
28 |
29 | 30 |
31 |

32 | Please Add Products To Cart 33 |

34 |
35 | )} 36 |
37 | {data?.carts?.map((item, key) => { 38 | return ; 39 | })} 40 |
41 |
42 | 43 | 46 | 47 |
48 |
49 | ) 50 | } 51 | 52 | function ProductItem({ item }) { 53 | const { user } = useAuth(); 54 | const { data } = useUser({ uid: user?.uid }); 55 | 56 | const [isRemoving, setIsRemoving] = useState(false); 57 | const [isUpdating, setIsUpdating] = useState(false); 58 | 59 | const { data: product } = useProduct({ productId: item?.id }); 60 | 61 | const handleRemove = async () => { 62 | if (!confirm("Are you sure?")) return; 63 | setIsRemoving(true); 64 | try { 65 | const newList = data?.carts?.filter((d) => d?.id != item?.id); 66 | await updateCarts({ list: newList, uid: user?.uid }); 67 | } catch (error) { 68 | toast.error(error?.message); 69 | } 70 | setIsRemoving(false); 71 | } 72 | const handleUpdate = async (quantity) => { 73 | setIsUpdating(true); 74 | try { 75 | const newList = data?.carts?.map((d) => { 76 | if (d?.id === item?.id) { 77 | return { 78 | ...d, 79 | quantity: parseInt(quantity), 80 | }; 81 | } else { 82 | return d; 83 | } 84 | }); 85 | await updateCarts({ list: newList, uid: user?.uid }); 86 | } catch (error) { 87 | toast.error(error?.message); 88 | } 89 | setIsUpdating(false); 90 | }; 91 | 92 | return ( 93 |
94 |
95 | 100 |
101 |
102 |

{product?.title}

103 |

104 | ₹ {product?.salePrice}{" "} 105 | 106 | ₹ {product?.price} 107 | 108 |

109 |
110 | 121 |

{item?.quantity}

122 | 133 |
134 |
135 |
136 | 146 |
147 |
148 | ); 149 | } -------------------------------------------------------------------------------- /app/(auth)/login/page.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useAuth } from '@/context/AuthContext'; 4 | import { auth } from '@/lib/firebase' 5 | import { createUser } from '@/lib/firestore/users/write'; 6 | import { Button } from '@nextui-org/react' 7 | import { GoogleAuthProvider, signInWithEmailAndPassword, signInWithPopup } from 'firebase/auth'; 8 | import Link from 'next/link' 9 | import { useRouter } from 'next/navigation'; 10 | import React, { useEffect, useState } from 'react' 11 | import toast from 'react-hot-toast'; 12 | 13 | function page() { 14 | const { user } = useAuth(); 15 | const router = useRouter(); 16 | const [isLoading, setIsLoading] = useState(false); 17 | const [data, setData] = useState({}); 18 | 19 | const handleLogin = async () => { 20 | setIsLoading(true); 21 | try { 22 | await signInWithEmailAndPassword(auth, data?.email, data?.password); 23 | toast.success("Logged In SuccessFully."); 24 | } catch (error) { 25 | toast.error(error?.message); 26 | } 27 | setIsLoading(false); 28 | } 29 | 30 | const handleData = (key, value) => { 31 | setData({ 32 | ...data, 33 | [key]: value, 34 | }); 35 | }; 36 | 37 | useEffect(() => { 38 | if (user) { 39 | router.push("/account"); 40 | } 41 | }, [user]); 42 | 43 | return ( 44 | // w-full flex justify-center items-center bg-gray-300 p-24 min-h-screen //center a div 45 |
46 |
47 |
48 | logo 49 |
50 |
51 |

Login with Email

52 |
{ 53 | e.preventDefault(); 54 | handleLogin(); 55 | }} className='flex flex-col gap-3'> 56 | { 63 | handleData('email', e.target.value); 64 | }} 65 | className='px-3 py-2 rounded-xl border focus:outline-none w-full' />{" "} 66 | { 73 | handleData('password', e.target.value); 74 | }} 75 | className='px-3 py-2 rounded-xl border focus:outline-none w-full' /> 76 | 77 |
78 |
79 | 80 | 81 | 82 | 83 | 84 | 85 |
86 |
87 | 88 |
89 |
90 |
91 | ) 92 | } 93 | 94 | function SignWithGoogleComponent() { 95 | const [isLoading, setIsLoading] = useState(false); 96 | const handleLogin = async () => { 97 | setIsLoading(true); 98 | try { 99 | const credential = await signInWithPopup(auth, new GoogleAuthProvider()); 100 | const user = credential?.user; 101 | await createUser({ 102 | uid: user?.uid, 103 | displayName: user?.displayName, 104 | photoURL: user?.photoURL, 105 | }); 106 | } catch (error) { 107 | toast.error(error?.message); 108 | } 109 | setIsLoading(false); 110 | 111 | }; 112 | return <> 113 | 116 | 117 | 118 | } 119 | 120 | export default page -------------------------------------------------------------------------------- /app/admin/products/form/components/Images.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | function Images({ data, featureImage, setFeatureImage, imageList, setImageList }) { 4 | return ( 5 |
6 |

Images

7 |
8 | {/* Feature Image Section */} 9 |
10 | {data?.featureImageUrl && !featureImage && ( 11 |
12 | 16 |
17 | )} 18 | {featureImage && ( 19 |
20 | 24 |
25 | )} 26 | 29 | { 34 | if (e.target.files.length > 0) { 35 | setFeatureImage(e.target.files[0]); 36 | } 37 | }} 38 | className='border px-4 py-2 rounded-lg w-full outline-none'/> 39 |
40 | 41 | {/* Image List Section */} 42 |
43 | {(imageList?.length === 0) && data?.imageList?.length!=0 && ( 44 |
45 | {data?.imageList?.map((item, index) => ( 46 | 47 | ))} 48 |
49 | )} 50 | {imageList?.length > 0 && ( 51 |
52 | {imageList?.map((item, index) => ( 53 | 54 | ))} 55 |
56 | )} 57 | 60 | { 66 | const newFiles = []; 67 | for (let i = 0; i < e.target.files.length; i++) { 68 | newFiles.push(e.target.files[i]); 69 | } 70 | setImageList(newFiles); 71 | }} 72 | className='border px-4 py-2 rounded-lg w-full outline-none'/> 73 |
74 |
75 |
76 | ); 77 | } 78 | 79 | export default Images; 80 | 81 | 82 | 83 | 84 | // import React from 'react' 85 | 86 | // function Images({ data, featureImage, setFeatureImage, imageList, setImageList }) { 87 | // return ( 88 | // // flex-1 divide equal basic details and image 89 | //
90 | //

Images

91 | //
92 | // {featureImage && ( 93 | //
94 | // 95 | //
96 | // )} 97 | // 98 | // { 103 | // if (e.target.files.length > 0) { 104 | // setFeatureImage(e.target.files[0]); 105 | // } 106 | // }} 107 | // className='border px-4 py-2 rounded-lg w-full outline-none' required /> 108 | //
109 | //
110 | // {imageList?.length > 0 && 111 | //
112 | // {imageList?.map((item) => { 113 | // return 114 | // })} 115 | //
} 116 | // 117 | // { 123 | // const newFiles = []; 124 | // for(let i=0;i 130 | //
131 | //
132 | // ) 133 | // } 134 | 135 | // export default Images -------------------------------------------------------------------------------- /app/admin/products/form/components/BasicDetails.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { useBrands } from '@/lib/firestore/brands/read'; 3 | import { useCategories } from '@/lib/firestore/categories/read'; 4 | import React from 'react' 5 | 6 | function BasicDetails({ data, handleData }) { 7 | const { data: brands } = useBrands(); 8 | const { data: categories } = useCategories(); 9 | return ( 10 |
11 |

Basic Details

12 |
13 | 14 | { 21 | handleData("title", e.target.value); 22 | }} 23 | className='border px-4 py-2 rounded-lg w-full outline-none' required /> 24 |
25 |
26 | 27 | { 34 | handleData("shortDescription", e.target.value); 35 | }} 36 | className='border px-4 py-2 rounded-lg w-full outline-none' required /> 37 |
38 | {/* not required */} 39 |
40 | 41 | 55 |
56 |
57 | 58 | 72 |
73 |
74 | 75 | { 82 | handleData("stock", e.target.valueAsNumber); 83 | }} 84 | className='border px-4 py-2 rounded-lg w-full outline-none' required /> 85 |
86 |
87 | 88 | { 95 | handleData("price", e.target.valueAsNumber); 96 | }} 97 | className='border px-4 py-2 rounded-lg w-full outline-none' required /> 98 |
99 |
100 | 101 | { 108 | handleData("salePrice", e.target.valueAsNumber); 109 | }} 110 | className='border px-4 py-2 rounded-lg w-full outline-none' required /> 111 |
112 |
113 | 114 | 127 |
128 |
129 | ) 130 | } 131 | 132 | export default BasicDetails -------------------------------------------------------------------------------- /app/admin/brands/components/Form.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { Button } from '@nextui-org/react' 3 | import React, { use, useEffect, useState } from 'react' 4 | import toast from 'react-hot-toast'; 5 | import { useRouter, useSearchParams } from 'next/navigation'; 6 | import { getBrand } from '@/lib/firestore/brands/read_server'; 7 | import { createNewBrand, updateBrand } from '@/lib/firestore/brands/write'; 8 | 9 | 10 | 11 | function Form() { 12 | const [data, setData] = useState(null); 13 | const [image, setImage] = useState(null); 14 | const [isLoading, setIsLoading] = useState(false); 15 | 16 | const searchParams = useSearchParams(); 17 | const id = searchParams.get("id"); 18 | 19 | const router = useRouter(); 20 | 21 | const fetchData = async () => { 22 | try { 23 | const res = await getBrand({ id: id }); 24 | if (!res) { 25 | toast.error("Brand Not Found!"); 26 | } else { 27 | setData(res); 28 | } 29 | } catch (error) { 30 | console.error(error); 31 | toast.error(error?.message); 32 | } 33 | } 34 | 35 | useEffect(() => { 36 | if (id) { 37 | fetchData(); 38 | } 39 | }, [id]); 40 | 41 | const handleData = (key, value) => { 42 | setData((preData) => { 43 | return { 44 | ...(preData ?? {}), 45 | [key]: value, 46 | }; 47 | }); 48 | }; 49 | 50 | const handleCreate = async () => { 51 | setIsLoading(true); 52 | if (!image) { 53 | alert("Image is required"); 54 | return; 55 | } 56 | 57 | const reader = new FileReader(); 58 | reader.readAsDataURL(image); 59 | reader.onload = async () => { 60 | try { 61 | await createNewBrand({ 62 | data, 63 | image: reader.result, // Pass the base64 string 64 | }); 65 | // alert("Category created successfully!"); 66 | toast.success("Successfully Created."); 67 | setData(null); 68 | setImage(null); 69 | } catch (error) { 70 | console.error(error); 71 | //alert("Error creating category: " + error.message); 72 | toast.error(error?.message); 73 | } 74 | }; 75 | setIsLoading(false); 76 | }; 77 | 78 | const handleUpdate = async (event) => { 79 | event?.preventDefault(); // Prevent default form behavior 80 | if (isLoading) { 81 | console.log("Update already in progress"); 82 | return; 83 | } 84 | 85 | console.log("handleUpdate triggered"); 86 | 87 | if (!data?.name) { 88 | toast.error("Name is required"); 89 | return; 90 | } 91 | 92 | if (!data?.id) { 93 | toast.error("Invalid brand ID"); 94 | return; 95 | } 96 | 97 | setIsLoading(true); 98 | 99 | try { 100 | let base64Image = null; 101 | 102 | if (image) { 103 | const reader = new FileReader(); 104 | const base64Promise = new Promise((resolve, reject) => { 105 | reader.onload = () => resolve(reader.result); 106 | reader.onerror = () => reject("Failed to read image"); 107 | }); 108 | 109 | reader.readAsDataURL(image); 110 | base64Image = await base64Promise; 111 | } 112 | 113 | console.log("Updating brand with:", data); 114 | 115 | await updateBrand({ 116 | data, 117 | image: base64Image, 118 | }); 119 | 120 | toast.success("Brand updated successfully."); 121 | setData(null); 122 | setImage(null); 123 | router.push(`/admin/brands`); 124 | } catch (error) { 125 | console.error("Error updating brand:", error); 126 | toast.error(error.message || "Failed to update brand."); 127 | } finally { 128 | setIsLoading(false); 129 | console.log("Update process completed"); 130 | } 131 | }; 132 | 133 | 134 | 135 | 136 | 137 | return ( 138 |
139 |

{id ? "Update" : "Create"} Brand

140 |
{ 142 | e.preventDefault(); 143 | if (id) { 144 | handleUpdate(); 145 | }else{ 146 | handleCreate(); 147 | } 148 | }}> 149 |
150 | 151 | { 158 | handleData(("name"), e.target.value); 159 | }} 160 | className='border px-4 py-2 rounded-lg w-full focus:outline-none' 161 | /> 162 |
163 |
164 | 165 | {image && ( 166 |
167 | 168 |
169 | )} 170 | { 172 | if (e.target.files.length > 0) { 173 | setImage(e.target.files[0]); 174 | } 175 | }} 176 | id='brand-image' 177 | name='brand-image' 178 | type="file" 179 | className='border px-4 py-2 rounded-lg w-full focus:outline-none' 180 | /> 181 |
182 | 185 |
186 |
187 | ) 188 | } 189 | 190 | export default Form -------------------------------------------------------------------------------- /app/admin/admins/components/Form.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { Button } from '@nextui-org/react' 3 | import React, { use, useEffect, useState } from 'react' 4 | import toast from 'react-hot-toast'; 5 | import { useRouter, useSearchParams } from 'next/navigation'; 6 | import { getAdmin } from '@/lib/firestore/admins/read_server'; 7 | import { createNewAdmin, updateAdmin } from '@/lib/firestore/admins/write'; 8 | 9 | 10 | 11 | function Form() { 12 | const [data, setData] = useState(null); 13 | const [image, setImage] = useState(null); 14 | const [isLoading, setIsLoading] = useState(false); 15 | 16 | const searchParams = useSearchParams(); 17 | const id = searchParams.get("id"); 18 | 19 | const router = useRouter(); 20 | 21 | const fetchData = async () => { 22 | try { 23 | const res = await getAdmin({ id: id }); 24 | if (!res) { 25 | toast.error("Admin Not Found!"); 26 | } else { 27 | setData(res); 28 | } 29 | } catch (error) { 30 | console.error(error); 31 | toast.error(error?.message); 32 | } 33 | } 34 | 35 | useEffect(() => { 36 | if (id) { 37 | fetchData(); 38 | } 39 | }, [id]); 40 | 41 | const handleData = (key, value) => { 42 | setData((preData) => { 43 | return { 44 | ...(preData ?? {}), 45 | [key]: value, 46 | }; 47 | }); 48 | }; 49 | 50 | const handleCreate = async () => { 51 | setIsLoading(true); 52 | if (!image) { 53 | alert("Image is required"); 54 | return; 55 | } 56 | 57 | const reader = new FileReader(); 58 | reader.readAsDataURL(image); 59 | reader.onload = async () => { 60 | try { 61 | await createNewAdmin({ 62 | data, 63 | image: reader.result, // Pass the base64 string 64 | }); 65 | // alert("Category created successfully!"); 66 | toast.success("Successfully Created."); 67 | setData(null); 68 | setImage(null); 69 | } catch (error) { 70 | console.error(error); 71 | //alert("Error creating category: " + error.message); 72 | toast.error(error?.message); 73 | } 74 | }; 75 | setIsLoading(false); 76 | }; 77 | 78 | const handleUpdate = async (event) => { 79 | event?.preventDefault(); // Prevent default form behavior 80 | if (isLoading) { 81 | console.log("Update already in progress"); 82 | return; 83 | } 84 | 85 | console.log("handleUpdate triggered"); 86 | 87 | if (!data?.name) { 88 | toast.error("Name is required"); 89 | return; 90 | } 91 | 92 | if (!data?.id) { 93 | toast.error("Invalid admin ID"); 94 | return; 95 | } 96 | 97 | setIsLoading(true); 98 | 99 | try { 100 | let base64Image = null; 101 | 102 | if (image) { 103 | const reader = new FileReader(); 104 | const base64Promise = new Promise((resolve, reject) => { 105 | reader.onload = () => resolve(reader.result); 106 | reader.onerror = () => reject("Failed to read image"); 107 | }); 108 | 109 | reader.readAsDataURL(image); 110 | base64Image = await base64Promise; 111 | } 112 | 113 | console.log("Updating admin with:", data); 114 | 115 | await updateAdmin({ 116 | data, 117 | image: base64Image, 118 | }); 119 | 120 | toast.success("Admin updated successfully."); 121 | setData(null); 122 | setImage(null); 123 | router.push(`/admin/admins`); 124 | } catch (error) { 125 | console.error("Error updating admin:", error); 126 | toast.error(error.message || "Failed to update admin."); 127 | } finally { 128 | setIsLoading(false); 129 | console.log("Update process completed"); 130 | } 131 | }; 132 | 133 | 134 | 135 | 136 | 137 | return ( 138 |
139 |

{id ? "Update" : "Create"} Admin

140 |
{ 142 | e.preventDefault(); 143 | if (id) { 144 | handleUpdate(); 145 | }else{ 146 | handleCreate(); 147 | } 148 | }}> 149 |
150 | 151 | { 158 | handleData(("name"), e.target.value); 159 | }} 160 | className='border px-4 py-2 rounded-lg w-full focus:outline-none' 161 | /> 162 |
163 |
164 | 165 | { 172 | handleData(("email"), e.target.value); 173 | }} 174 | className='border px-4 py-2 rounded-lg w-full focus:outline-none' 175 | /> 176 |
177 |
178 | 179 | {image && ( 180 |
181 | 182 |
183 | )} 184 | { 186 | if (e.target.files.length > 0) { 187 | setImage(e.target.files[0]); 188 | } 189 | }} 190 | id='admin-image' 191 | name='admin-image' 192 | type="file" 193 | className='border px-4 py-2 rounded-lg w-full focus:outline-none' 194 | /> 195 |
196 | 199 |
200 |
201 | ) 202 | } 203 | 204 | export default Form -------------------------------------------------------------------------------- /app/admin/components/Sidebar.js: -------------------------------------------------------------------------------- 1 | // "use client" 2 | 3 | // import { auth } from '@/lib/firebase' 4 | // import { signOut } from 'firebase/auth' 5 | // import { ChartColumnStacked, CircleUserRound, Gem, LayoutDashboard, LogOut, PackageOpen, ShieldCheck, ShoppingCart, SquareLibrary, Star } from 'lucide-react' 6 | // import Link from 'next/link' 7 | // import { usePathname } from 'next/navigation' 8 | // import React from 'react' 9 | // import toast from 'react-hot-toast' 10 | 11 | // function Sidebar() { 12 | // const menuList = [ 13 | // { 14 | // name: "Dashboard", 15 | // link: "/admin", 16 | // icon: 17 | // }, 18 | // { 19 | // name: "Products", 20 | // link: "/admin/products", 21 | // icon: 22 | 23 | // }, 24 | // { 25 | // name: "Categories", 26 | // link: "/admin/categories", 27 | // icon: 28 | // }, 29 | // { 30 | // name: "Brands", 31 | // link: "/admin/brands", 32 | // icon: 33 | // }, 34 | // { 35 | // name: "Orders", 36 | // link: "/admin/orders", 37 | // icon: 38 | // }, 39 | // { 40 | // name: "Customers", 41 | // link: "/admin/customers", 42 | // icon: 43 | // }, 44 | // { 45 | // name: "Reviews", 46 | // link: "/admin/reviews", 47 | // icon: 48 | // }, 49 | // { 50 | // name: "Collections", 51 | // link: "/admin/collections", 52 | // icon: 53 | // }, 54 | // { 55 | // name: "Admins", 56 | // link: "/admin/admins", 57 | // icon: 58 | // }, 59 | // ] 60 | // return ( 61 | // // md:w-[210px] w-[180px] 62 | //
63 | //
64 | // logo 65 | //
66 | //
    67 | // {menuList?.map((item, key) => { 68 | // return ( 69 | // 70 | // ); 71 | // })} 72 | //
73 | //
74 | // 89 | //
90 | //
91 | // ) 92 | // } 93 | 94 | // export default Sidebar 95 | 96 | // function Tab({ item }) { 97 | // const pathname = usePathname(); 98 | // const isSelected = pathname == item?.link; 99 | // return ( 100 | // 101 | //
  • {item?.icon}{item?.name}
  • 103 | // 104 | // ); 105 | // } 106 | 107 | "use client" 108 | import { auth } from '@/lib/firebase' 109 | import { signOut } from 'firebase/auth' 110 | import { ChartColumnStacked, CircleUserRound, Gem, LayoutDashboard, LogOut, PackageOpen, ShieldCheck, ShoppingCart, SquareLibrary, Star } from 'lucide-react' 111 | import Link from 'next/link' 112 | import { usePathname } from 'next/navigation' 113 | import React from 'react' 114 | import toast from 'react-hot-toast' 115 | 116 | function Sidebar() { 117 | const menuList = [ 118 | { name: "Dashboard", link: "/admin", icon: }, 119 | { name: "Products", link: "/admin/products", icon: }, 120 | { name: "Categories", link: "/admin/categories", icon: }, 121 | // { name: "Brands", link: "/admin/brands", icon: }, 122 | { name: "Orders", link: "/admin/orders", icon: }, 123 | { name: "Customers", link: "/admin/customers", icon: }, 124 | { name: "Reviews", link: "/admin/reviews", icon: }, 125 | { name: "Collections", link: "/admin/collections", icon: }, 126 | { name: "Admins", link: "/admin/admins", icon: }, 127 | ]; 128 | 129 | return ( 130 |
    131 |
    132 | 133 | logo 134 | 135 |
    136 |
      137 | {menuList?.map((item, key) => { 138 | return ( ); 139 | })} 140 |
    141 |
    142 | 157 |
    158 |
    159 | ); 160 | } 161 | 162 | export default Sidebar 163 | 164 | function Tab({ item }) { 165 | const pathname = usePathname(); 166 | const isSelected = pathname == item?.link; 167 | return ( 168 | 169 |
  • 170 | {item?.icon}{item?.name} 171 |
  • 172 | 173 | ); 174 | } 175 | -------------------------------------------------------------------------------- /app/admin/categories/components/Form.js: -------------------------------------------------------------------------------- 1 | "use client" 2 | import { Button } from '@nextui-org/react' 3 | import React, { use, useEffect, useState } from 'react' 4 | import { createNewCategory, updateCategory } from "@/lib/firestore/categories/write"; 5 | import toast from 'react-hot-toast'; 6 | import { useRouter, useSearchParams } from 'next/navigation'; 7 | import { getCategory } from '@/lib/firestore/categories/read_server'; 8 | 9 | 10 | 11 | function Form() { 12 | const [data, setData] = useState(null); 13 | const [image, setImage] = useState(null); 14 | const [isLoading, setIsLoading] = useState(false); 15 | 16 | const searchParams = useSearchParams(); 17 | const id = searchParams.get("id"); 18 | 19 | const router = useRouter(); 20 | 21 | const fetchData = async () => { 22 | try { 23 | const res = await getCategory({ id: id }); 24 | if (!res) { 25 | toast.error("Category Not Found!"); 26 | } else { 27 | setData(res); 28 | } 29 | } catch (error) { 30 | console.error(error); 31 | toast.error(error?.message); 32 | } 33 | } 34 | 35 | useEffect(() => { 36 | if (id) { 37 | fetchData(); 38 | } 39 | }, [id]); 40 | 41 | const handleData = (key, value) => { 42 | setData((preData) => { 43 | return { 44 | ...(preData ?? {}), 45 | [key]: value, 46 | }; 47 | }); 48 | }; 49 | 50 | const handleCreate = async () => { 51 | setIsLoading(true); 52 | if (!image) { 53 | alert("Image is required"); 54 | return; 55 | } 56 | 57 | const reader = new FileReader(); 58 | reader.readAsDataURL(image); 59 | reader.onload = async () => { 60 | try { 61 | await createNewCategory({ 62 | data, 63 | image: reader.result, // Pass the base64 string 64 | }); 65 | // alert("Category created successfully!"); 66 | toast.success("Successfully Created."); 67 | setData(null); 68 | setImage(null); 69 | } catch (error) { 70 | console.error(error); 71 | //alert("Error creating category: " + error.message); 72 | toast.error(error?.message); 73 | } 74 | }; 75 | setIsLoading(false); 76 | }; 77 | 78 | const handleUpdate = async (event) => { 79 | event?.preventDefault(); // Prevent default form behavior 80 | if (isLoading) { 81 | console.log("Update already in progress"); 82 | return; 83 | } 84 | 85 | console.log("handleUpdate triggered"); 86 | 87 | if (!data?.name || !data?.slug) { 88 | toast.error("Name and slug are required"); 89 | return; 90 | } 91 | 92 | if (!data?.id) { 93 | toast.error("Invalid category ID"); 94 | return; 95 | } 96 | 97 | setIsLoading(true); 98 | 99 | try { 100 | let base64Image = null; 101 | 102 | if (image) { 103 | const reader = new FileReader(); 104 | const base64Promise = new Promise((resolve, reject) => { 105 | reader.onload = () => resolve(reader.result); 106 | reader.onerror = () => reject("Failed to read image"); 107 | }); 108 | 109 | reader.readAsDataURL(image); 110 | base64Image = await base64Promise; 111 | } 112 | 113 | console.log("Updating category with:", data); 114 | 115 | await updateCategory({ 116 | data, 117 | image: base64Image, 118 | }); 119 | 120 | toast.success("Category updated successfully."); 121 | setData(null); 122 | setImage(null); 123 | router.push(`/admin/categories`); 124 | } catch (error) { 125 | console.error("Error updating category:", error); 126 | toast.error(error.message || "Failed to update category."); 127 | } finally { 128 | setIsLoading(false); 129 | console.log("Update process completed"); 130 | } 131 | }; 132 | 133 | 134 | 135 | 136 | 137 | return ( 138 |
    139 |

    {id ? "Update" : "Create"} Category

    140 |
    { 142 | e.preventDefault(); 143 | if (id) { 144 | handleUpdate(); 145 | } else { 146 | handleCreate(); 147 | } 148 | }}> 149 |
    150 | 151 | { 158 | handleData(("name"), e.target.value); 159 | }} 160 | className='border px-4 py-2 rounded-lg w-full focus:outline-none' 161 | /> 162 |
    163 |
    164 | 165 | { 172 | handleData(("slug"), e.target.value); 173 | }} 174 | className='border px-4 py-2 rounded-lg w-full focus:outline-none' 175 | /> 176 |
    177 |
    178 | 179 | {image && ( 180 |
    181 | 182 |
    183 | )} 184 | { 186 | if (e.target.files.length > 0) { 187 | setImage(e.target.files[0]); 188 | } 189 | }} 190 | id='category-image' 191 | name='category-image' 192 | type="file" 193 | className='border px-4 py-2 rounded-lg w-full focus:outline-none' 194 | /> 195 |
    196 |
    197 | 198 | 211 |
    212 | 215 |
    216 |
    217 | ) 218 | } 219 | 220 | export default Form -------------------------------------------------------------------------------- /app/(user)/favorites/page.js: -------------------------------------------------------------------------------- 1 | // "use client"; 2 | 3 | // import { ProductCard } from "@/app/components/ProductsList"; 4 | // import { useAuth } from "@/context/AuthContext"; 5 | // import { useProduct } from "@/lib/firestore/products/read"; 6 | // import { useUser } from "@/lib/firestore/users/read"; 7 | // import { CircularProgress } from "@nextui-org/react"; 8 | 9 | 10 | 11 | // export default function Page() { 12 | // const { user } = useAuth(); 13 | // const { data, isLoading } = useUser({ uid: user?.uid }); 14 | // if (isLoading) { 15 | // return ( 16 | //
    17 | // 18 | //
    19 | // ); 20 | // } 21 | // return ( 22 | //
    23 | //

    Favorites

    24 | // {(!data?.favorites || data?.favorites?.length === 0) && ( 25 | //
    26 | //
    27 | // 28 | //
    29 | //

    30 | // Please Add Products To Favorites 31 | //

    32 | //
    33 | // )} 34 | //
    35 | // {data?.favorites?.map((productId) => { 36 | // return ; 37 | // })} 38 | //
    39 | //
    40 | // ); 41 | // } 42 | 43 | // function ProductItem({ productId }) { 44 | // const { data: product } = useProduct({ productId: productId }); 45 | // //console.log(product); 46 | // return ( 47 | //
    48 | // {/*

    All Products

    */} 49 | //
    50 | //
    51 | //
    52 | // {product.title} 57 | // {product.imageList?.[0] && ( 58 | // {product.title} 63 | // )} 64 | //
    65 | //

    {product.title}

    66 | //

    {product.shortDescription}

    67 | //
    68 | // 69 | //

    (0)

    70 | //
    71 | //
    72 | // {product.salePrice < product.price ? ( 73 | // <> 74 | // ₹ {product.salePrice}{" "} 75 | // ₹ {product.price} 76 | // 77 | // ) : ( 78 | // ₹ {product.price} 79 | // )} 80 | //
    81 | //
    82 | //
    83 | // 86 | // 89 | // 92 | //
    93 | //
    94 | //
    95 | //
    96 | //
    97 | // ) 98 | // } 99 | 100 | 101 | "use client" 102 | 103 | import FavoriteButton from "@/app/components/FavoriteButton"; 104 | import AuthContextProvider, { useAuth } from "@/context/AuthContext" 105 | import { useProduct } from "@/lib/firestore/products/read"; 106 | import { useUser } from "@/lib/firestore/users/read"; 107 | import { Rating } from "@mui/material"; 108 | import { Button } from "@nextui-org/react"; 109 | import { Heart, ShoppingCart } from "lucide-react"; 110 | 111 | export default function Page() { 112 | const { user } = useAuth(); 113 | const { data } = useUser({ uid: user?.uid }); 114 | return ( 115 |
    116 |

    Favorites

    117 | {(!data?.favorites || data?.favorites?.length === 0) && ( 118 |
    119 |
    120 | 121 |
    122 |

    123 | Please Add Products To Favorites 124 |

    125 |
    126 | )} 127 |
    128 | {data?.favorites?.map((productId) => { 129 | return ; 130 | })} 131 |
    132 |
    133 | ) 134 | } 135 | 136 | function ProductItem({ productId }) { 137 | const { data: product, isLoading, error } = useProduct({ productId }); 138 | 139 | console.log("Fetching product:", productId, "Data:", product, "Error:", error); 140 | 141 | if (isLoading) return

    Loading...

    ; 142 | if (error) return

    Error fetching product

    ; 143 | if (!product) return

    Product not found

    ; 144 | 145 | return ( 146 |
    147 | {/*

    All Products

    */} 148 |
    149 |
    150 |
    151 | {product.title} 156 | {product.imageList?.[0] && ( 157 | {product.title} 162 | )} 163 |
    164 |

    {product.title}

    165 |

    {product.shortDescription}

    166 |
    167 | 168 |

    (0)

    169 |
    170 |
    171 | {product.salePrice < product.price ? ( 172 | <> 173 | ₹ {product.salePrice}{" "} 174 | ₹ {product.price} 175 | 176 | ) : ( 177 | ₹ {product.price} 178 | )} 179 |
    180 |
    181 |
    182 | 185 | 188 | 189 | 190 | 191 |
    192 |
    193 |
    194 |
    195 |
    196 | ) 197 | } --------------------------------------------------------------------------------