├── 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 | Create
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 | c o n f i g / f i r e b a s e - s e r v i c e - a c c o u n t . j s o n
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 |
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 | {
17 | if(!confirm("Are you sure?")) return;
18 | try {
19 | await toast.promise(signOut(auth), {
20 | error: (e) => e?.message,
21 | loading: "Loading...",
22 | success: "Successfully Logged Out"
23 | })
24 | } catch (error) {
25 | toast.error(error?.message);
26 | }
27 | }}
28 | className='h-8 w-8 flex justify-center items-center rounded-full hover:bg-[#FEC4C7]'>
29 |
30 |
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 | //
19 | // {/* Description & Reviews */}
20 | //
24 | // {/* Related Products */}
25 | //
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 |
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 |
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 |
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 | Login
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 |
38 |
39 | {categories.map((category) => (
40 |
41 |
42 |
43 |
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 |
25 |
26 |
27 | {menuList?.map((item, index) => (
28 |
29 | {item?.name}
30 |
31 | ))}
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
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 |
27 |
28 |
29 |
30 | )}
31 | {(data?.favorites?.length ?? 0) === 0 && (
32 |
36 |
37 |
38 | )}
39 |
40 |
41 | {(data?.carts?.length ?? 0) != 0 && (
42 |
48 |
52 |
53 |
54 |
55 | )}
56 | {(data?.carts?.length ?? 0) === 0 && (
57 |
61 |
62 |
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 |
27 | Buy Now
28 |
29 |
30 |
31 | Add to Cart
32 |
33 |
38 |
39 |
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 | {
51 | await signOut(auth);
52 | }} className='bg-[#fbe1e3]'>LogOut
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 |
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 |
79 | {isAdded ? (
80 |
81 | ) : (
82 |
83 | )}
84 |
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 |
44 |
45 |
46 |
Forgot Password
47 |
63 |
64 |
65 | Sign In.
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 | applyInlineStyle('BOLD')}
46 | >
47 | B
48 |
49 | applyInlineStyle('ITALIC')}
52 | >
53 | I
54 |
55 | applyInlineStyle('UNDERLINE')}
58 | >
59 | U
60 |
61 | {/* applyBlockType('header-one')}
64 | >
65 | H1
66 | */}
67 | applyBlockType('unordered-list-item')}
70 | >
71 | UL
72 |
73 | applyBlockType('ordered-list-item')}
76 | >
77 | OL
78 |
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 |
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 |
21 | {product.imageList?.[0] && (
22 |
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 |
49 | Buy Now
50 |
51 |
52 | {/*
53 |
54 |
55 |
56 |
57 | */}
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 |
58 | {isLiked ? (
59 |
60 | ) : (
61 |
62 | )}
63 |
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 | //
124 | // {!isLiked && }
125 | // {isLiked && }
126 | //
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 | SrNo.
34 | Image
35 | Name
36 | Actions
37 |
38 |
39 |
40 | {brands?.map((item, index) => {
41 | return (
42 |
43 | )
44 | })}
45 |
46 |
47 |
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 |
123 |
124 |
125 |
126 |
127 |
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 |
32 | {product.imageList[0] && (
33 |
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 |
61 | Buy Now
62 |
63 |
64 | {/*
65 |
66 | */}
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 | SrNo.
34 | Image
35 | Name
36 | Actions
37 |
38 |
39 |
40 | {admins?.map((item, index) => {
41 | return (
42 |
43 | )
44 | })}
45 |
46 |
47 |
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 |
128 |
129 |
130 |
131 |
132 |
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 | SrNo.
34 | Image
35 | Name
36 | Actions
37 |
38 |
39 |
40 | {categories?.map((item, index) => {
41 | return (
42 |
43 | )
44 | })}
45 |
46 |
47 |
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 |
123 |
124 |
125 |
126 |
127 |
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 |
59 |
60 |
61 |
Sign Up with Email
62 |
98 |
99 |
100 | Already User? Sign In.
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 | SrNo.
34 | Image
35 | Title
36 | Products
37 | Actions
38 |
39 |
40 |
41 | {collections?.map((item, index) => {
42 | return (
43 |
44 | )
45 | })}
46 |
47 |
48 |
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 |
125 |
126 |
127 |
128 |
129 |
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 |
44 | Checkout
45 |
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 |
{
112 | handleUpdate(item?.quantity - 1);
113 | }}
114 | isDisabled={isUpdating || item?.quantity <= 1}
115 | isIconOnly
116 | size="sm"
117 | className="h-6 w-4"
118 | >
119 |
120 |
121 |
{item?.quantity}
122 |
{
124 | handleUpdate(item?.quantity + 1);
125 | }}
126 | isDisabled={isUpdating}
127 | isIconOnly
128 | size="sm"
129 | className="h-6 w-4"
130 | >
131 |
132 |
133 |
134 |
135 |
136 |
144 |
145 |
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 |
49 |
50 |
51 |
Login with Email
52 |
78 |
79 |
80 | Create Account
81 |
82 |
83 | Forget Password?
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 |
114 | Sign in with Google
115 |
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 |
27 | Feature Image*
28 |
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 |
58 | Images*
59 |
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 | //
Feature Image*
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 | //
Images*
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 |
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 |
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 |
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 | //
65 | //
66 | //
67 | // {menuList?.map((item, key) => {
68 | // return (
69 | //
70 | // );
71 | // })}
72 | //
73 | //
74 | // {
76 | // try{
77 | // await toast.promise(signOut(auth),{
78 | // error: (e)=> e?.message,
79 | // loading: "Loading...",
80 | // success: "Successfully Logged Out"
81 | // })
82 | // }catch(error){
83 | // toast.error(error?.message);
84 | // }
85 | // }}
86 | // className='flex gap-2 items-start px-3 py-2 hover:bg-[#fbe1e3] rounded-xl w-full justify-center ease-soft-spring duration-300 transition-all'>
87 | // Log out
88 | //
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 |
134 |
135 |
136 |
137 | {menuList?.map((item, key) => {
138 | return ( );
139 | })}
140 |
141 |
142 | {
144 | try {
145 | await toast.promise(signOut(auth), {
146 | error: (e) => e?.message,
147 | loading: "Loading...",
148 | success: "Successfully Logged Out"
149 | })
150 | } catch (error) {
151 | toast.error(error?.message);
152 | }
153 | }}
154 | className='flex gap-2 items-center px-3 py-2 hover:bg-[#fbe1e3] rounded-xl w-full justify-center ease-soft-spring duration-300 transition-all'>
155 | Log out
156 |
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 |
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 | //
57 | // {product.imageList?.[0] && (
58 | //
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 | //
84 | // Buy Now
85 | //
86 | //
87 | //
88 | //
89 | //
90 | //
91 | //
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 |
156 | {product.imageList?.[0] && (
157 |
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 |
183 | Buy Now
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 | )
197 | }
--------------------------------------------------------------------------------