├── server ├── dist │ ├── logs │ │ └── app-error.log │ ├── utils │ │ ├── CustomError.js │ │ ├── GenerateTokenAndCookie.js │ │ ├── AsyncWrapper.js │ │ ├── CustomLogger.js │ │ ├── ValidationSchema.js │ │ ├── index.js │ │ └── helper.js │ ├── middleware │ │ ├── RouteNotFoundMiddleware.js │ │ ├── RateLimiterMiddleware.js │ │ ├── ErrorMiddleware.js │ │ ├── ValidationMiddleware.js │ │ ├── index.js │ │ └── AuthMiddleware.js │ ├── routes │ │ ├── payment.route.js │ │ ├── index.js │ │ ├── cart.route.js │ │ ├── auth.route.js │ │ ├── admin.route.js │ │ └── food.route.js │ ├── types.def.js │ ├── models │ │ ├── index.js │ │ ├── cart.model.js │ │ ├── food.model.js │ │ └── user.model.js │ ├── index.js │ ├── db │ │ └── MongoConnection.js │ ├── app.js │ └── controllers │ │ ├── payment.controller.js │ │ ├── auth.controller.js │ │ ├── food.controller.js │ │ ├── admin.controller.js │ │ └── cart.controller.js ├── src │ ├── models │ │ ├── index.ts │ │ ├── cart.model.ts │ │ ├── user.model.ts │ │ └── food.model.ts │ ├── routes │ │ ├── index.ts │ │ ├── payment.route.ts │ │ ├── cart.route.ts │ │ ├── auth.route.ts │ │ ├── admin.route.ts │ │ └── food.route.ts │ ├── utils │ │ ├── AsyncWrapper.ts │ │ ├── CustomError.ts │ │ ├── index.ts │ │ ├── GenerateTokenAndCookie.ts │ │ ├── CustomLogger.ts │ │ ├── helper.ts │ │ └── ValidationSchema.ts │ ├── middleware │ │ ├── index.ts │ │ ├── RouteNotFoundMiddleware.ts │ │ ├── ValidationMiddleware.ts │ │ ├── RateLimiterMiddleware.ts │ │ ├── ErrorMiddleware.ts │ │ └── AuthMiddleware.ts │ ├── db │ │ └── MongoConnection.ts │ ├── index.ts │ ├── controllers │ │ ├── payment.controller.ts │ │ ├── auth.controller.ts │ │ ├── food.controller.ts │ │ ├── admin.controller.ts │ │ └── cart.controller.ts │ ├── app.ts │ └── types.def.ts └── package.json ├── .gitignore ├── client ├── src │ ├── vite-env.d.ts │ ├── assets │ │ ├── menu_1.png │ │ ├── menu_2.png │ │ ├── menu_3.png │ │ ├── menu_4.png │ │ ├── menu_5.png │ │ ├── menu_6.png │ │ ├── menu_7.png │ │ ├── menu_8.png │ │ ├── rating_starts.png │ │ └── react.svg │ ├── components │ │ ├── Site │ │ │ ├── SiteBanner │ │ │ │ └── SiteBanner.tsx │ │ │ ├── SiteTopVeg │ │ │ │ └── SiteTopVeg.tsx │ │ │ ├── SiteTopNonVeg │ │ │ │ └── SiteTopNonVeg.tsx │ │ │ ├── SiteHero │ │ │ │ └── SiteHero.tsx │ │ │ ├── SiteRecentlyAdded │ │ │ │ └── SiteRecentlyAdded.tsx │ │ │ ├── SiteMenu │ │ │ │ └── SiteMenu.tsx │ │ │ ├── SiteFoodList │ │ │ │ └── SiteFoodList.tsx │ │ │ └── SiteProductCard │ │ │ │ └── SiteProductCard.tsx │ │ ├── Admin │ │ │ ├── AdminHeader.tsx │ │ │ └── AdminSidebar.tsx │ │ ├── ui │ │ │ ├── skeleton.tsx │ │ │ ├── label.tsx │ │ │ ├── input.tsx │ │ │ ├── tooltip.tsx │ │ │ ├── badge.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── table.tsx │ │ │ ├── dialog.tsx │ │ │ ├── sheet.tsx │ │ │ └── form.tsx │ │ ├── common │ │ │ ├── LoadingSpinner.tsx │ │ │ └── CustomToolTip.tsx │ │ ├── tables │ │ │ ├── customer │ │ │ │ ├── mutation.tsx │ │ │ │ ├── customer-columns.tsx │ │ │ │ └── DropDownMenu.tsx │ │ │ └── product │ │ │ │ ├── mutation.tsx │ │ │ │ ├── DropDownMenu.tsx │ │ │ │ └── product-columns.tsx │ │ └── Forms │ │ │ ├── CheckoutForm.tsx │ │ │ ├── SigninForm.tsx │ │ │ └── SignupForm.tsx │ ├── lib │ │ └── utils.ts │ ├── Layout │ │ ├── SiteFooter │ │ │ ├── SiteFooter.tsx │ │ │ ├── SiteBottomFooter │ │ │ │ └── SiteBottomFooter.tsx │ │ │ └── SiteTopFooter │ │ │ │ └── SiteTopFooter.tsx │ │ ├── SiteLayout.tsx │ │ ├── AdminLayout.tsx │ │ ├── SEO.tsx │ │ └── Header │ │ │ ├── SiteHeader.tsx │ │ │ ├── CartSheet │ │ │ ├── CartProducts.tsx │ │ │ ├── UpdateCart.tsx │ │ │ └── CartSheet.tsx │ │ │ ├── Navigation │ │ │ └── Navigation.tsx │ │ │ ├── UserProfile │ │ │ └── UserProfile.tsx │ │ │ └── SearchCommand │ │ │ └── SearchCommand.tsx │ ├── pages │ │ ├── Menupage │ │ │ └── Menupage.tsx │ │ ├── Homepage │ │ │ └── Homepage.tsx │ │ ├── Admin │ │ │ ├── AdminProductList.tsx │ │ │ └── AdminCustomerList.tsx │ │ ├── Authpage │ │ │ ├── SigninPage.tsx │ │ │ ├── SignupPage.tsx │ │ │ └── _component │ │ │ │ └── AuthCard.tsx │ │ ├── Checkoutpage │ │ │ └── Checkoutpage.tsx │ │ └── Cartpage │ │ │ └── Cartpage.tsx │ ├── hooks │ │ └── useDebounce.tsx │ ├── services │ │ ├── interface.ts │ │ ├── food.api.ts │ │ ├── api.ts │ │ ├── cart.api.ts │ │ └── admin.api.ts │ ├── utils │ │ ├── data.ts │ │ └── Schema.ts │ ├── main.tsx │ ├── context │ │ └── AuthContext.tsx │ ├── index.css │ └── App.tsx ├── netlify.toml ├── public │ ├── hero2.png │ ├── payment.png │ └── vite.svg ├── postcss.config.js ├── .env ├── tsconfig.node.json ├── vite.config.ts ├── .gitignore ├── components.json ├── index.html ├── .eslintrc.cjs ├── tsconfig.json ├── README.md ├── package.json └── tailwind.config.js ├── LICENSE └── readme.md /server/dist/logs/app-error.log: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /server/node_modules 2 | /server/.env -------------------------------------------------------------------------------- /client/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /client/netlify.toml: -------------------------------------------------------------------------------- 1 | [[redirects]] 2 | from = "/*" 3 | to = "/index.html" 4 | status = 200 5 | -------------------------------------------------------------------------------- /client/public/hero2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/public/hero2.png -------------------------------------------------------------------------------- /client/postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/public/payment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/public/payment.png -------------------------------------------------------------------------------- /client/src/assets/menu_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/src/assets/menu_1.png -------------------------------------------------------------------------------- /client/src/assets/menu_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/src/assets/menu_2.png -------------------------------------------------------------------------------- /client/src/assets/menu_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/src/assets/menu_3.png -------------------------------------------------------------------------------- /client/src/assets/menu_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/src/assets/menu_4.png -------------------------------------------------------------------------------- /client/src/assets/menu_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/src/assets/menu_5.png -------------------------------------------------------------------------------- /client/src/assets/menu_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/src/assets/menu_6.png -------------------------------------------------------------------------------- /client/src/assets/menu_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/src/assets/menu_7.png -------------------------------------------------------------------------------- /client/src/assets/menu_8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/src/assets/menu_8.png -------------------------------------------------------------------------------- /client/src/assets/rating_starts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/imcrazysteven/Food-Delivery-Application/HEAD/client/src/assets/rating_starts.png -------------------------------------------------------------------------------- /client/src/components/Site/SiteBanner/SiteBanner.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | export default function SiteBanner() { 4 | return ( 5 |
SiteBanner
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /server/src/models/index.ts: -------------------------------------------------------------------------------- 1 | export { UserModel, IUserModel } from "./user.model"; 2 | export { FoodModel } from "./food.model"; 3 | export { CartModel } from './cart.model' -------------------------------------------------------------------------------- /client/.env: -------------------------------------------------------------------------------- 1 | VITE_BASE_URL = http://localhost:3000/api 2 | VITE_STRIPE = pk_test_51P4tUKSDdtdbVxdOXH75NmqcXkdBkLUWFDWzQa1Fwr4iW2NkcTZtlm0HxRu167soI8tg539KxdfIIX3QcXNXqOod00bhS1xi7Y -------------------------------------------------------------------------------- /client/src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /server/src/routes/index.ts: -------------------------------------------------------------------------------- 1 | export { AuthRoute } from "./auth.route"; 2 | export { FoodRoute } from "./food.route"; 3 | export { CartRoute } from "./cart.route"; 4 | export { AdminRoute } from "./admin.route"; 5 | export { PaymentRoute } from "./payment.route"; -------------------------------------------------------------------------------- /client/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "bundler", 7 | "allowSyntheticDefaultImports": true, 8 | "strict": true 9 | }, 10 | "include": ["vite.config.ts"] 11 | } 12 | -------------------------------------------------------------------------------- /client/vite.config.ts: -------------------------------------------------------------------------------- 1 | import path from "path" 2 | import react from "@vitejs/plugin-react" 3 | import { defineConfig } from "vite" 4 | 5 | export default defineConfig({ 6 | plugins: [react()], 7 | resolve: { 8 | alias: { 9 | "@": path.resolve(__dirname, "./src"), 10 | }, 11 | }, 12 | }) -------------------------------------------------------------------------------- /server/src/routes/payment.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import { AuthMiddleware } from '../middleware' 3 | import { CreatePaymentIntent } from '../controllers/payment.controller' 4 | 5 | export const PaymentRoute = Router() 6 | 7 | PaymentRoute.post('/create-payment-intent', AuthMiddleware, CreatePaymentIntent) -------------------------------------------------------------------------------- /server/src/utils/AsyncWrapper.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction } from 'express' 2 | 3 | export const AsyncWrapper = (fn: CallableFunction) => async (req: Request, res: Response, next: NextFunction) => { 4 | try { 5 | await fn(req, res, next) 6 | } catch (error) { 7 | next(error) 8 | } 9 | } -------------------------------------------------------------------------------- /client/src/components/Admin/AdminHeader.tsx: -------------------------------------------------------------------------------- 1 | import UserProfile from "@/Layout/Header/UserProfile/UserProfile"; 2 | 3 | export default function AdminHeader() { 4 | return ( 5 |
6 | 7 |
8 | ) 9 | } 10 | -------------------------------------------------------------------------------- /server/src/utils/CustomError.ts: -------------------------------------------------------------------------------- 1 | import { HttpStatusCode } from "./helper" 2 | 3 | export class CustomError extends Error { 4 | statusCode: HttpStatusCode 5 | constructor(message: string, statusCode: HttpStatusCode) { 6 | super(message) 7 | this.message = message 8 | this.statusCode = statusCode 9 | } 10 | } -------------------------------------------------------------------------------- /server/src/middleware/index.ts: -------------------------------------------------------------------------------- 1 | export { ErrorMiddleware } from "./ErrorMiddleware"; 2 | export { ValidationMiddleware } from "./ValidationMiddleware"; 3 | export { RateLimiterMiddleware } from "./RateLimiterMiddleware"; 4 | export { AuthMiddleware, AdminMiddleware } from "./AuthMiddleware"; 5 | export { RouteNotFoundMiddleware } from "./RouteNotFoundMiddleware"; 6 | -------------------------------------------------------------------------------- /server/src/middleware/RouteNotFoundMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { NextFunction, Response, Request } from "express"; 2 | import { ErrorMessage, HttpStatusCode } from "../utils"; 3 | 4 | export const RouteNotFoundMiddleware = (req: Request, res: Response, next: NextFunction) => { 5 | res.status(HttpStatusCode.NOT_FOUND).json({ message: ErrorMessage.ROUTE_NOT_FOUND }) 6 | } 7 | 8 | -------------------------------------------------------------------------------- /client/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils" 2 | 3 | function Skeleton({ 4 | className, 5 | ...props 6 | }: React.HTMLAttributes) { 7 | return ( 8 |
12 | ) 13 | } 14 | 15 | export { Skeleton } 16 | -------------------------------------------------------------------------------- /client/src/Layout/SiteFooter/SiteFooter.tsx: -------------------------------------------------------------------------------- 1 | import SiteBottomFooter from "./SiteBottomFooter/SiteBottomFooter"; 2 | import SiteTopFooter from "./SiteTopFooter/SiteTopFooter"; 3 | 4 | export default function SiteFooter() { 5 | return ( 6 |
7 | 8 | 9 |
10 | ); 11 | } 12 | -------------------------------------------------------------------------------- /server/src/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { CustomLogger } from "./CustomLogger"; 2 | export { CustomError } from "./CustomError"; 3 | export { AsyncWrapper } from "./AsyncWrapper"; 4 | export { SigninSchema, SignupSchema } from "./ValidationSchema"; 5 | export { HttpStatusCode, SuccessMessage, ErrorMessage } from "./helper"; 6 | export { GenerateTokenAndCookie } from "./GenerateTokenAndCookie"; 7 | -------------------------------------------------------------------------------- /client/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | .env 15 | .vite 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | -------------------------------------------------------------------------------- /server/dist/utils/CustomError.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.CustomError = void 0; 4 | class CustomError extends Error { 5 | constructor(message, statusCode) { 6 | super(message); 7 | this.message = message; 8 | this.statusCode = statusCode; 9 | } 10 | } 11 | exports.CustomError = CustomError; 12 | -------------------------------------------------------------------------------- /client/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.js", 8 | "css": "src/index.css", 9 | "baseColor": "zinc", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils" 16 | } 17 | } -------------------------------------------------------------------------------- /server/src/middleware/ValidationMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { AnyZodObject } from "zod"; 2 | import { Request, Response, NextFunction } from 'express' 3 | 4 | export const ValidationMiddleware = (schema: AnyZodObject) => async (req: Request, res: Response, next: NextFunction) => { 5 | try { 6 | await schema.parseAsync({ 7 | body: req.body 8 | }) 9 | next() 10 | } catch (error) { 11 | next(error) 12 | } 13 | } -------------------------------------------------------------------------------- /client/src/Layout/SiteLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import SiteFooter from "./SiteFooter/SiteFooter"; 3 | import SiteHeader from "./Header/SiteHeader"; 4 | 5 | 6 | export default function SiteLayout() { 7 | return ( 8 | <> 9 | 10 |
11 | 12 |
13 | 14 | 15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | FoodZone | Food Delivery App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /server/dist/middleware/RouteNotFoundMiddleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.RouteNotFoundMiddleware = void 0; 4 | const utils_1 = require("../utils"); 5 | const RouteNotFoundMiddleware = (req, res, next) => { 6 | res.status(utils_1.HttpStatusCode.NOT_FOUND).json({ message: utils_1.ErrorMessage.ROUTE_NOT_FOUND }); 7 | }; 8 | exports.RouteNotFoundMiddleware = RouteNotFoundMiddleware; 9 | -------------------------------------------------------------------------------- /server/src/middleware/RateLimiterMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { rateLimit, ValueDeterminingMiddleware } from 'express-rate-limit' 2 | 3 | export const RateLimiterMiddleware = (message: string, limits: number | ValueDeterminingMiddleware | undefined) => { 4 | return rateLimit({ 5 | windowMs: 5 * 60 * 1000, 6 | limit: limits, 7 | standardHeaders: 'draft-7', 8 | legacyHeaders: false, 9 | message: `${message}` 10 | }) 11 | } 12 | 13 | -------------------------------------------------------------------------------- /server/src/utils/GenerateTokenAndCookie.ts: -------------------------------------------------------------------------------- 1 | import { Response } from "express"; 2 | import jwt from "jsonwebtoken"; 3 | 4 | export const GenerateTokenAndCookie = (userId: unknown, res: Response) => { 5 | const token = jwt.sign({ userId }, process.env.SECRET_KEY, { 6 | expiresIn: "2h", 7 | }); 8 | 9 | res.cookie("foodZone", token, { 10 | maxAge: 24 * 60 * 60 * 1000, 11 | secure: false, 12 | httpOnly: true 13 | }); 14 | }; 15 | 16 | 17 | -------------------------------------------------------------------------------- /client/src/components/common/LoadingSpinner.tsx: -------------------------------------------------------------------------------- 1 | import { LoaderCircle } from "lucide-react"; 2 | 3 | export default function LoadingSpinner() { 4 | return ( 5 |
6 |
7 |
8 | 9 |
10 |
11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /client/src/pages/Menupage/Menupage.tsx: -------------------------------------------------------------------------------- 1 | import SiteFoodList from "@/components/Site/SiteFoodList/SiteFoodList"; 2 | import SiteMenu from "@/components/Site/SiteMenu/SiteMenu"; 3 | import { useState } from "react"; 4 | 5 | 6 | export default function Menupage() { 7 | const [selected, setSelected] = useState('All') 8 | return ( 9 | <> 10 | 11 | 12 | 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /client/src/Layout/AdminLayout.tsx: -------------------------------------------------------------------------------- 1 | import AdminHeader from "@/components/Admin/AdminHeader"; 2 | import AdminSidebar from "@/components/Admin/AdminSidebar"; 3 | import { Outlet } from "react-router-dom"; 4 | 5 | 6 | 7 | export default function AdminLayout() { 8 | return ( 9 |
10 | 11 |
12 | 13 | 14 |
15 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /server/dist/routes/payment.route.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.PaymentRoute = void 0; 4 | const express_1 = require("express"); 5 | const middleware_1 = require("../middleware"); 6 | const payment_controller_1 = require("../controllers/payment.controller"); 7 | exports.PaymentRoute = (0, express_1.Router)(); 8 | exports.PaymentRoute.post('/create-payment-intent', middleware_1.AuthMiddleware, payment_controller_1.CreatePaymentIntent); 9 | -------------------------------------------------------------------------------- /client/src/hooks/useDebounce.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | 3 | export default function useDebounce(value: T, delay: number): T { 4 | const [debounceValue, setDebounceValue] = useState(value); 5 | 6 | useEffect(() => { 7 | const timer = setTimeout(() => { 8 | setDebounceValue(value); 9 | }, delay ?? 500); 10 | 11 | return () => { 12 | clearTimeout(timer); 13 | }; 14 | }, [value, delay]); 15 | 16 | return debounceValue; 17 | } 18 | -------------------------------------------------------------------------------- /client/src/Layout/SEO.tsx: -------------------------------------------------------------------------------- 1 | import { Helmet } from "react-helmet-async"; 2 | 3 | interface SeoProps { 4 | title: string; 5 | description: string; 6 | } 7 | 8 | export default function SEO({ title, description }: SeoProps) { 9 | return ( 10 | 11 | {title} 12 | 13 | 14 | 15 | 16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /server/src/routes/cart.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import * as cart from '../controllers/cart.controller' 3 | import { AuthMiddleware } from '../middleware' 4 | 5 | export const CartRoute = Router() 6 | 7 | CartRoute.get('/user-cart', AuthMiddleware, cart.GetAllCartItems) 8 | CartRoute.post('/add-cart', AuthMiddleware, cart.CreateUserCart) 9 | CartRoute.delete('/remove-cart/:productId', AuthMiddleware, cart.RemoveFromCart) 10 | CartRoute.put('/update-cart/:productId', AuthMiddleware, cart.UpdateCartItem) 11 | -------------------------------------------------------------------------------- /client/src/Layout/SiteFooter/SiteBottomFooter/SiteBottomFooter.tsx: -------------------------------------------------------------------------------- 1 | import payment from '/payment.png' 2 | 3 | export default function SiteBottomFooter() { 4 | return ( 5 |
6 |
7 |

© 2024 FoodZone - Fast Food Delivery Service

8 | payment-options 9 |
10 |
11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /client/src/pages/Homepage/Homepage.tsx: -------------------------------------------------------------------------------- 1 | import SiteHero from "@/components/Site/SiteHero/SiteHero"; 2 | import SiteRecentlyAdded from "@/components/Site/SiteRecentlyAdded/SiteRecentlyAdded"; 3 | import SiteTopNonVeg from "@/components/Site/SiteTopNonVeg/SiteTopNonVeg"; 4 | import SiteTopVeg from "@/components/Site/SiteTopVeg/SiteTopVeg"; 5 | 6 | 7 | export default function Homepage() { 8 | return ( 9 | <> 10 | 11 | 12 | 13 | 14 | 15 | ) 16 | } 17 | -------------------------------------------------------------------------------- /client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | "plugin:tailwindcss/recommended" 9 | ], 10 | ignorePatterns: ['dist', '.eslintrc.cjs'], 11 | parser: '@typescript-eslint/parser', 12 | plugins: ['react-refresh'], 13 | rules: { 14 | 'react-refresh/only-export-components': [ 15 | 'warn', 16 | { allowConstantExport: true }, 17 | ], 18 | }, 19 | } 20 | -------------------------------------------------------------------------------- /server/dist/types.def.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | const zod_1 = require("zod"); 4 | const EnvVariables = zod_1.z.object({ 5 | PORT: zod_1.z.string().min(1).max(4), 6 | MONGO_URI: zod_1.z.string().min(1), 7 | SECRET_KEY: zod_1.z.string().min(10).max(20), 8 | ORIGIN: zod_1.z.string().min(1), 9 | CLOUDINARY_CLOUD_NAME: zod_1.z.string().min(1), 10 | CLOUDINARY_API_KEY: zod_1.z.string().min(1), 11 | CLOUDINARY_API_SECRET: zod_1.z.string().min(1), 12 | STRIPE_API_KEY: zod_1.z.string().min(1), 13 | }); 14 | -------------------------------------------------------------------------------- /server/dist/middleware/RateLimiterMiddleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.RateLimiterMiddleware = void 0; 4 | const express_rate_limit_1 = require("express-rate-limit"); 5 | const RateLimiterMiddleware = (message, limits) => { 6 | return (0, express_rate_limit_1.rateLimit)({ 7 | windowMs: 5 * 60 * 1000, 8 | limit: limits, 9 | standardHeaders: 'draft-7', 10 | legacyHeaders: false, 11 | message: `${message}` 12 | }); 13 | }; 14 | exports.RateLimiterMiddleware = RateLimiterMiddleware; 15 | -------------------------------------------------------------------------------- /server/src/db/MongoConnection.ts: -------------------------------------------------------------------------------- 1 | import mongoose, { MongooseError } from "mongoose"; 2 | import { CustomLogger } from "../utils"; 3 | 4 | 5 | const MONGO_URL = process.env.MONGO_URI as string; 6 | 7 | async function MongoConnection() { 8 | await mongoose 9 | .connect(MONGO_URL) 10 | .then((success) => { 11 | CustomLogger.info(`[Mongo]: Mongo Connected ${success.connection.host}`); 12 | }) 13 | .catch((error: MongooseError) => { 14 | CustomLogger.error(`[Mongo]: Mongo connection failed ${error.message}`); 15 | }); 16 | } 17 | 18 | export default MongoConnection; 19 | -------------------------------------------------------------------------------- /server/src/index.ts: -------------------------------------------------------------------------------- 1 | import { app } from "./app"; 2 | import MongoConnection from "./db/MongoConnection"; 3 | import { CustomLogger } from "./utils"; 4 | import { v2 as cloudinary } from 'cloudinary' 5 | 6 | const port = process.env.PORT || 3001 7 | 8 | cloudinary.config({ 9 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME, 10 | api_key: process.env.CLOUDINARY_API_KEY, 11 | api_secret: process.env.CLOUDINARY_API_SECRET, 12 | }); 13 | 14 | MongoConnection(); 15 | console.log("Connected to MongoDB"); 16 | 17 | app.listen(port, () => { 18 | CustomLogger.info(`Server is up and running on ${port}`) 19 | }) -------------------------------------------------------------------------------- /server/src/models/cart.model.ts: -------------------------------------------------------------------------------- 1 | import { Schema, model } from "mongoose"; 2 | import { CartModelType } from "../types.def"; 3 | 4 | const cartSchema = new Schema( 5 | { 6 | products: [ 7 | { 8 | product: { 9 | type: Schema.Types.ObjectId, 10 | ref: "Food", 11 | }, 12 | count: Number, 13 | price: Number, 14 | }, 15 | ], 16 | cartTotal: Number, 17 | orderBy: { 18 | type: Schema.Types.ObjectId, 19 | ref: "User", 20 | }, 21 | }, 22 | { timestamps: true } 23 | ); 24 | 25 | 26 | export const CartModel = model('Cart', cartSchema) 27 | 28 | -------------------------------------------------------------------------------- /client/src/components/common/CustomToolTip.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Tooltip, 3 | TooltipContent, 4 | TooltipProvider, 5 | TooltipTrigger, 6 | } from "@/components/ui/tooltip"; 7 | 8 | interface ToolTipProps { 9 | children: React.ReactNode; 10 | title: string; 11 | } 12 | 13 | export default function CustomToolTip({ children, title }: ToolTipProps) { 14 | return ( 15 | 16 | 17 | {children} 18 | 19 |

{title}

20 |
21 |
22 |
23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /client/src/pages/Admin/AdminProductList.tsx: -------------------------------------------------------------------------------- 1 | import { columns } from "@/components/tables/product/product-columns"; 2 | import { DataTable } from "@/components/tables/product/product-table"; 3 | import { GetAllProductList } from "@/services/admin.api"; 4 | import { useQuery } from "@tanstack/react-query"; 5 | 6 | 7 | export default function AdminProductList() { 8 | 9 | const { data } = useQuery({ 10 | queryKey: ["all-products"], 11 | queryFn: GetAllProductList 12 | }); 13 | 14 | return ( 15 |
16 | 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /client/src/Layout/Header/SiteHeader.tsx: -------------------------------------------------------------------------------- 1 | import CartSheet from "./CartSheet/CartSheet"; 2 | import Navigation from "./Navigation/Navigation"; 3 | import SearchCommand from "./SearchCommand/SearchCommand"; 4 | import UserProfile from "./UserProfile/UserProfile"; 5 | 6 | 7 | export default function SiteHeader() { 8 | return ( 9 |
10 | 18 |
19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /client/src/pages/Admin/AdminCustomerList.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { columns } from "@/components/tables/customer/customer-columns"; 3 | import { DataTable } from "@/components/tables/customer/customer-table"; 4 | import { GetAllCustomersList } from "@/services/admin.api"; 5 | import { useQuery } from "@tanstack/react-query"; 6 | 7 | 8 | 9 | export default function AdminCustomerList() { 10 | const { data } = useQuery({ 11 | queryKey: ["all-users"], 12 | queryFn: GetAllCustomersList, 13 | }); 14 | return ( 15 |
16 | 17 |
18 | ) 19 | } 20 | -------------------------------------------------------------------------------- /server/dist/models/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.CartModel = exports.FoodModel = exports.UserModel = void 0; 4 | var user_model_1 = require("./user.model"); 5 | Object.defineProperty(exports, "UserModel", { enumerable: true, get: function () { return user_model_1.UserModel; } }); 6 | var food_model_1 = require("./food.model"); 7 | Object.defineProperty(exports, "FoodModel", { enumerable: true, get: function () { return food_model_1.FoodModel; } }); 8 | var cart_model_1 = require("./cart.model"); 9 | Object.defineProperty(exports, "CartModel", { enumerable: true, get: function () { return cart_model_1.CartModel; } }); 10 | -------------------------------------------------------------------------------- /server/src/routes/auth.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import * as auth from "../controllers/auth.controller"; 3 | import { ErrorMessage, SigninSchema, SignupSchema } from "../utils"; 4 | import { AuthMiddleware, RateLimiterMiddleware, ValidationMiddleware } from "../middleware"; 5 | 6 | export const AuthRoute = Router(); 7 | 8 | AuthRoute.post("/SignUp", 9 | RateLimiterMiddleware(ErrorMessage.RATE_LIMIT_ERROR, 2), 10 | ValidationMiddleware(SignupSchema), auth.SignUpUser); 11 | 12 | AuthRoute.post("/SignIn", ValidationMiddleware(SigninSchema), auth.SignInUser); 13 | AuthRoute.post("/SignOutUser", auth.SignOutUser); 14 | AuthRoute.get("/verifyAuth", AuthMiddleware, auth.VerifyUser); 15 | -------------------------------------------------------------------------------- /server/src/routes/admin.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from "express"; 2 | import * as admin from "../controllers/admin.controller"; 3 | import { AdminMiddleware, AuthMiddleware } from "../middleware"; 4 | 5 | export const AdminRoute = Router(); 6 | 7 | AdminRoute.get('/get-users', admin.GetAllCustomersList) 8 | AdminRoute.get('/get-products', admin.GetAllProductsList) 9 | AdminRoute.put('/enable-disable/:productId', admin.EnableDisableProduct) 10 | AdminRoute.delete('/delete/:productId', admin.DeleteProduct) 11 | AdminRoute.delete('/user-delete/:userId', admin.DeleteUser) 12 | AdminRoute.put('/block-unblock-user/:userId', admin.BlockUnBlockUser) 13 | AdminRoute.get('/verifyAdminApi', AuthMiddleware, AdminMiddleware, admin.VerifyAdmin) -------------------------------------------------------------------------------- /server/src/routes/food.route.ts: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | import * as food from '../controllers/food.controller' 3 | import multer from 'multer' 4 | 5 | const storage = multer.memoryStorage() 6 | 7 | const upload = multer({ 8 | storage, 9 | limits: { 10 | fileSize: 5 * 1024 * 1204 11 | } 12 | }) 13 | 14 | export const FoodRoute = Router() 15 | 16 | FoodRoute.post('/create-food', upload.single("imageFile"), food.CreateProduct) 17 | FoodRoute.get('/all-foodlist', food.GetAllProducts) 18 | FoodRoute.get('/search/:searchTerm', food.SearchFood) 19 | FoodRoute.get('/recent-food', food.GetRecentlyAdded) 20 | FoodRoute.get('/veg-food', food.GetVegFoods) 21 | FoodRoute.get('/nonveg-food', food.GetNonVegFoods) -------------------------------------------------------------------------------- /server/dist/models/cart.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.CartModel = void 0; 4 | const mongoose_1 = require("mongoose"); 5 | const cartSchema = new mongoose_1.Schema({ 6 | products: [ 7 | { 8 | product: { 9 | type: mongoose_1.Schema.Types.ObjectId, 10 | ref: "Food", 11 | }, 12 | count: Number, 13 | price: Number, 14 | }, 15 | ], 16 | cartTotal: Number, 17 | orderBy: { 18 | type: mongoose_1.Schema.Types.ObjectId, 19 | ref: "User", 20 | }, 21 | }, { timestamps: true }); 22 | exports.CartModel = (0, mongoose_1.model)('Cart', cartSchema); 23 | -------------------------------------------------------------------------------- /client/src/components/tables/customer/mutation.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { BlockUnBlockUser, Deleteuser } from "@/services/admin.api"; 3 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 4 | 5 | export function UseBlockUnBlockUser() { 6 | const queryClient = useQueryClient(); 7 | return useMutation({ 8 | mutationFn: (id: string) => BlockUnBlockUser(id), 9 | onSuccess: () => { 10 | queryClient.invalidateQueries({ queryKey: ["all-users"] }); 11 | }, 12 | }); 13 | } 14 | 15 | export function UseDeleteUser() { 16 | const queryClient = useQueryClient(); 17 | return useMutation({ 18 | mutationFn: (id: string) => Deleteuser(id), 19 | onSuccess: () => { 20 | queryClient.invalidateQueries({ queryKey: ["all-users"] }); 21 | }, 22 | }); 23 | } -------------------------------------------------------------------------------- /server/dist/utils/GenerateTokenAndCookie.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.GenerateTokenAndCookie = void 0; 7 | const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); 8 | const GenerateTokenAndCookie = (userId, res) => { 9 | const token = jsonwebtoken_1.default.sign({ userId }, process.env.SECRET_KEY, { 10 | expiresIn: "2h", 11 | }); 12 | res.cookie("foodZone", token, { 13 | maxAge: 24 * 60 * 60 * 1000, 14 | secure: false, 15 | httpOnly: true 16 | }); 17 | }; 18 | exports.GenerateTokenAndCookie = GenerateTokenAndCookie; 19 | -------------------------------------------------------------------------------- /client/src/components/Site/SiteTopVeg/SiteTopVeg.tsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import SiteProductCard from "../SiteProductCard/SiteProductCard"; 3 | import { GetVegFoodApi } from "@/services/food.api"; 4 | 5 | 6 | export default function SiteTopVeg() { 7 | 8 | const {data: products} = useQuery({ 9 | queryKey: ['top-veg'], 10 | queryFn: GetVegFoodApi 11 | }) 12 | 13 | 14 | return ( 15 |
16 |

Top Veg Dishes

17 |
18 | {products?.map((item) => ( 19 | 20 | ))} 21 |
22 |
23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /client/src/components/tables/product/mutation.tsx: -------------------------------------------------------------------------------- 1 | import { Deleteproduct, EnableDisable } from "@/services/admin.api"; 2 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 3 | 4 | export function UseProductenableDisabled() { 5 | const queryClient = useQueryClient(); 6 | return useMutation({ 7 | mutationFn: (id: string) => EnableDisable(id), 8 | onSuccess: () => { 9 | queryClient.invalidateQueries({ queryKey: ["all-products"] }); 10 | }, 11 | }); 12 | } 13 | 14 | export function UseDeleteProduct() { 15 | const queryClient = useQueryClient(); 16 | return useMutation({ 17 | mutationFn: (id: string) => Deleteproduct(id), 18 | onSuccess: () => { 19 | queryClient.invalidateQueries({ queryKey: ["all-products"] }); 20 | }, 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /client/src/components/Site/SiteTopNonVeg/SiteTopNonVeg.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { GetNonVegFoodApi } from "@/services/food.api" 3 | import SiteProductCard from "../SiteProductCard/SiteProductCard" 4 | import { useQuery } from "@tanstack/react-query" 5 | 6 | 7 | export default function SiteTopNonVeg() { 8 | 9 | const {data: products} = useQuery({ 10 | queryKey: ['top-non-veg'], 11 | queryFn: GetNonVegFoodApi 12 | }) 13 | 14 | 15 | return ( 16 |
17 |

Top Non-Veg Dishes

18 |
19 | {products?.map((item) => ( 20 | 21 | ))} 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /client/src/components/Site/SiteHero/SiteHero.tsx: -------------------------------------------------------------------------------- 1 | import { buttonVariants } from "@/components/ui/button"; 2 | import { cn } from "@/lib/utils"; 3 | import { Link } from "react-router-dom"; 4 | 5 | export default function SiteHero() { 6 | return ( 7 |
8 |
9 |

The food you love right in front of your door

10 | 11 | View More 12 | 13 |
14 |
15 | ); 16 | } 17 | -------------------------------------------------------------------------------- /client/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const labelVariants = cva( 8 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 9 | ) 10 | 11 | const Label = React.forwardRef< 12 | React.ElementRef, 13 | React.ComponentPropsWithoutRef & 14 | VariantProps 15 | >(({ className, ...props }, ref) => ( 16 | 21 | )) 22 | Label.displayName = LabelPrimitive.Root.displayName 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2020", 4 | "useDefineForClassFields": true, 5 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 6 | "module": "ESNext", 7 | "skipLibCheck": true, 8 | "baseUrl": ".", 9 | "paths": { 10 | "@/*": [ 11 | "./src/*" 12 | ] 13 | }, 14 | 15 | /* Bundler mode */ 16 | "moduleResolution": "bundler", 17 | "allowImportingTsExtensions": true, 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx", 22 | 23 | /* Linting */ 24 | "strict": true, 25 | "noUnusedLocals": true, 26 | "noUnusedParameters": true, 27 | "noFallthroughCasesInSwitch": true 28 | }, 29 | "include": ["src"], 30 | "references": [{ "path": "./tsconfig.node.json" }] 31 | } 32 | -------------------------------------------------------------------------------- /server/src/utils/CustomLogger.ts: -------------------------------------------------------------------------------- 1 | import winston from "winston"; 2 | import path from 'path' 3 | 4 | const { combine, colorize, timestamp, align, printf, json } = winston.format; 5 | 6 | winston.addColors({ 7 | error: "red", 8 | warn: "yellow", 9 | info: "cyan", 10 | debug: "green", 11 | }); 12 | 13 | export const CustomLogger = winston.createLogger({ 14 | level: "http", 15 | format: combine( 16 | colorize({ all: true }), 17 | timestamp({ 18 | format: "YY-MM-DD hh:mm A", 19 | }), 20 | align(), 21 | printf((info) => `[${info.timestamp}] ${info.level}: ${info.message}`) 22 | ), 23 | transports: [ 24 | new winston.transports.Console(), 25 | new winston.transports.File({ 26 | filename: path.join(__dirname, "../logs/app-error.log"), 27 | level: "error" 28 | }), 29 | ], 30 | }); 31 | -------------------------------------------------------------------------------- /server/dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const app_1 = require("./app"); 7 | const MongoConnection_1 = __importDefault(require("./db/MongoConnection")); 8 | const utils_1 = require("./utils"); 9 | const cloudinary_1 = require("cloudinary"); 10 | const port = process.env.PORT || 3001; 11 | cloudinary_1.v2.config({ 12 | cloud_name: process.env.CLOUDINARY_CLOUD_NAME, 13 | api_key: process.env.CLOUDINARY_API_KEY, 14 | api_secret: process.env.CLOUDINARY_API_SECRET, 15 | }); 16 | (0, MongoConnection_1.default)(); 17 | app_1.app.listen(port, () => { 18 | utils_1.CustomLogger.info(`Server is up and running on ${port}`); 19 | }); 20 | -------------------------------------------------------------------------------- /client/src/components/Site/SiteRecentlyAdded/SiteRecentlyAdded.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { useQuery } from "@tanstack/react-query" 3 | import SiteProductCard from "../SiteProductCard/SiteProductCard" 4 | import { GetRecentlyAddedFoodApi } from "@/services/food.api" 5 | 6 | 7 | export default function SiteRecentlyAdded() { 8 | 9 | const {data: products} = useQuery({ 10 | queryKey: ['recent-added'], 11 | queryFn: GetRecentlyAddedFoodApi 12 | }) 13 | 14 | 15 | return ( 16 |
17 |

Recently Added Dishes

18 |
19 | {products?.map((item) => ( 20 | 21 | ))} 22 |
23 |
24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /server/src/controllers/payment.controller.ts: -------------------------------------------------------------------------------- 1 | 2 | import Stripe from "stripe"; 3 | import {Request, Response} from 'express' 4 | import { AsyncWrapper } from "../utils"; 5 | import { CartModel } from "../models"; 6 | 7 | 8 | const stripe = new Stripe(process.env.STRIPE_API_KEY as string) 9 | 10 | 11 | 12 | export const CreatePaymentIntent = AsyncWrapper(async(req:Request, res:Response) => { 13 | const {amount} = req.body; 14 | 15 | 16 | const paymentIntent = await stripe.paymentIntents.create({ 17 | description: "foodZone app", 18 | amount: amount, 19 | currency: 'inr', 20 | payment_method_types: ['card'] 21 | }) 22 | 23 | if(paymentIntent) { 24 | await CartModel.findOneAndDelete({orderBy: req.userId}) 25 | 26 | } 27 | 28 | res.send({ 29 | clientSecret: paymentIntent.client_secret 30 | }) 31 | }) -------------------------------------------------------------------------------- /client/src/pages/Authpage/SigninPage.tsx: -------------------------------------------------------------------------------- 1 | import SigninForm from "@/components/Forms/SigninForm"; 2 | import AuthCard from "./_component/AuthCard"; 3 | import { Link } from "react-router-dom"; 4 | import SEO from "@/Layout/SEO"; 5 | 6 | 7 | export default function SigninPage() { 8 | return ( 9 | <> 10 | 11 |
12 | 13 | 14 |
15 | Forgot your password? 16 | 17 | Are you new? Sign In 18 | 19 |
20 |
21 |
22 | 23 | ) 24 | } 25 | -------------------------------------------------------------------------------- /client/src/pages/Authpage/SignupPage.tsx: -------------------------------------------------------------------------------- 1 | import AuthCard from "./_component/AuthCard"; 2 | import { Link } from "react-router-dom"; 3 | import SignupForm from "@/components/Forms/SignupForm"; 4 | import SEO from "@/Layout/SEO"; 5 | 6 | 7 | 8 | export default function SignupPage() { 9 | return ( 10 | <> 11 | 12 |
13 | 14 | 15 |
16 | Forgot your password? 17 | 18 | Already have account? Sign In 19 | 20 |
21 |
22 |
23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /client/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | export interface InputProps 6 | extends React.InputHTMLAttributes {} 7 | 8 | const Input = React.forwardRef( 9 | ({ className, type, ...props }, ref) => { 10 | return ( 11 | 20 | ) 21 | } 22 | ) 23 | Input.displayName = "Input" 24 | 25 | export { Input } 26 | -------------------------------------------------------------------------------- /client/src/components/tables/customer/customer-columns.tsx: -------------------------------------------------------------------------------- 1 | import { ColumnDef } from "@tanstack/react-table"; 2 | import DropDownMenu from "./DropDownMenu"; 3 | 4 | export type Customer = { 5 | firstname: string; 6 | email: string; 7 | role: string; 8 | mobile: string; 9 | blocked: boolean; 10 | _id:string 11 | }; 12 | 13 | export const columns: ColumnDef[] = [ 14 | { 15 | accessorKey: "firstname", 16 | header: "Firstname", 17 | }, 18 | { 19 | accessorKey: "email", 20 | header: "Email", 21 | }, 22 | { 23 | accessorKey: "role", 24 | header: "Role", 25 | }, 26 | { 27 | accessorKey: "mobile", 28 | header: "Mobile", 29 | }, 30 | { 31 | accessorKey: "blocked", 32 | header: "Blocked", 33 | }, 34 | { 35 | id: "actions", 36 | enableHiding: false, 37 | header: "Actions", 38 | cell: ({row}) => { 39 | return ( 40 | 41 | ); 42 | }, 43 | }, 44 | ]; 45 | -------------------------------------------------------------------------------- /client/src/services/interface.ts: -------------------------------------------------------------------------------- 1 | export const base_url = import.meta.env.VITE_BASE_URL; 2 | 3 | export interface ProductProps { 4 | name: string; 5 | description: string; 6 | price: number; 7 | category: string; 8 | image: string; 9 | available: boolean; 10 | vegetarian: boolean; 11 | ingredients: []; 12 | discount: number; 13 | createdAt: Date; 14 | starRating: number; 15 | _id: string; 16 | } 17 | 18 | export type CartProductProps = { 19 | product: ProductProps; 20 | count: number; 21 | price: number; 22 | _id?: string; 23 | }; 24 | 25 | export interface CartItemProps { 26 | products: CartProductProps[]; 27 | count: number; 28 | price: number; 29 | cartTotal: number; 30 | orderBy: string; 31 | _id?: string; 32 | } 33 | 34 | export interface MessageProps { 35 | message: string 36 | } 37 | 38 | export interface SignInResponse { 39 | message: string; 40 | role: string 41 | } -------------------------------------------------------------------------------- /client/src/utils/data.ts: -------------------------------------------------------------------------------- 1 | import menu_1 from '../assets/menu_1.png' 2 | import menu_2 from '../assets/menu_2.png' 3 | import menu_3 from '../assets/menu_3.png' 4 | import menu_4 from '../assets/menu_4.png' 5 | import menu_5 from '../assets/menu_5.png' 6 | import menu_6 from '../assets/menu_6.png' 7 | import menu_7 from '../assets/menu_7.png' 8 | import menu_8 from '../assets/menu_8.png' 9 | 10 | export const menulist = [ 11 | { 12 | name: "Pizza", 13 | image: menu_1 14 | }, 15 | { 16 | name: "Rolls", 17 | image: menu_2 18 | }, 19 | { 20 | name: "Deserts", 21 | image: menu_3 22 | }, 23 | { 24 | name: "Sandwich", 25 | image: menu_4 26 | }, 27 | { 28 | name: "Cake", 29 | image: menu_5 30 | }, 31 | { 32 | name: "Pure Veg", 33 | image: menu_6 34 | }, 35 | { 36 | name: "Pasta", 37 | image: menu_7 38 | }, 39 | { 40 | name: "Noodles", 41 | image: menu_8 42 | }] -------------------------------------------------------------------------------- /client/src/components/Site/SiteMenu/SiteMenu.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { menulist } from "@/utils/data"; 3 | 4 | 5 | interface SiteMenuProps { 6 | setSelectedMenu: React.Dispatch> 7 | } 8 | 9 | 10 | export default function SiteMenu({setSelectedMenu}: SiteMenuProps) { 11 | return ( 12 |
13 |

Select from our best menu's

14 |
15 | {menulist.map((item) => ( 16 |
setSelectedMenu((prev) => (prev === item.name ? "All" : item.name))}> 17 | {item.name} 22 |

{item.name}

23 |
24 | ))} 25 |
26 |
27 | ) 28 | } 29 | -------------------------------------------------------------------------------- /server/dist/routes/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.PaymentRoute = exports.AdminRoute = exports.CartRoute = exports.FoodRoute = exports.AuthRoute = void 0; 4 | var auth_route_1 = require("./auth.route"); 5 | Object.defineProperty(exports, "AuthRoute", { enumerable: true, get: function () { return auth_route_1.AuthRoute; } }); 6 | var food_route_1 = require("./food.route"); 7 | Object.defineProperty(exports, "FoodRoute", { enumerable: true, get: function () { return food_route_1.FoodRoute; } }); 8 | var cart_route_1 = require("./cart.route"); 9 | Object.defineProperty(exports, "CartRoute", { enumerable: true, get: function () { return cart_route_1.CartRoute; } }); 10 | var admin_route_1 = require("./admin.route"); 11 | Object.defineProperty(exports, "AdminRoute", { enumerable: true, get: function () { return admin_route_1.AdminRoute; } }); 12 | var payment_route_1 = require("./payment.route"); 13 | Object.defineProperty(exports, "PaymentRoute", { enumerable: true, get: function () { return payment_route_1.PaymentRoute; } }); 14 | -------------------------------------------------------------------------------- /server/src/app.ts: -------------------------------------------------------------------------------- 1 | import express, { Application } from 'express' 2 | import cors from 'cors' 3 | import helmet from 'helmet' 4 | import cookieParser from 'cookie-parser' 5 | import 'dotenv/config' 6 | import ExpressMongoSanitize from 'express-mongo-sanitize' 7 | import morgan from 'morgan' 8 | 9 | import { AdminRoute, AuthRoute, CartRoute, FoodRoute, PaymentRoute } from './routes' 10 | import { ErrorMiddleware, RouteNotFoundMiddleware } from './middleware' 11 | 12 | export const app: Application = express() 13 | 14 | app.use(morgan('dev')) 15 | app.use(express.json()) 16 | app.use(cors({ 17 | origin: process.env.ORIGIN || 'http://localhost:5173', 18 | credentials: true, 19 | })) 20 | 21 | app.use(cookieParser()) 22 | app.use(helmet()) 23 | app.use(ExpressMongoSanitize()) 24 | app.disable('x-powered-by') 25 | 26 | app.use('/api/auth', AuthRoute) 27 | app.use('/api/food', FoodRoute) 28 | app.use('/api/cart', CartRoute) 29 | app.use('/api/admin', AdminRoute) 30 | app.use('/api/payment', PaymentRoute) 31 | 32 | app.use(RouteNotFoundMiddleware) 33 | app.use(ErrorMiddleware) 34 | 35 | -------------------------------------------------------------------------------- /client/src/Layout/Header/CartSheet/CartProducts.tsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import UpdateCart from "./UpdateCart"; 3 | import { ScrollArea } from "@/components/ui/scroll-area"; 4 | import { CartItemProps, CartProductProps } from "@/services/interface"; 5 | 6 | export default function CartProducts() { 7 | const { data} = useQuery({queryKey: ["cart-items"]}); 8 | 9 | return ( 10 | 11 | {data?.products?.map((product:CartProductProps) => ( 12 |
13 |
14 | img 15 |
16 |

{product?.product.name}

17 |

Rs {product?.product.price}

18 |
19 |
20 |
21 | 22 |
23 |
24 | ))} 25 |
26 | ); 27 | } 28 | -------------------------------------------------------------------------------- /client/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App.tsx"; 4 | import "./index.css"; 5 | import { BrowserRouter } from "react-router-dom"; 6 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 7 | import { AuthContextProvider } from "./context/AuthContext.tsx"; 8 | import { Toaster } from "sonner"; 9 | import { HelmetProvider } from "react-helmet-async"; 10 | 11 | 12 | const queryClient = new QueryClient({ 13 | defaultOptions: { 14 | queries: { 15 | refetchOnWindowFocus: false, 16 | retry: false, 17 | }, 18 | }, 19 | }); 20 | 21 | ReactDOM.createRoot(document.getElementById("root")!).render( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | ); 35 | -------------------------------------------------------------------------------- /server/dist/utils/AsyncWrapper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.AsyncWrapper = void 0; 13 | const AsyncWrapper = (fn) => (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 14 | try { 15 | yield fn(req, res, next); 16 | } 17 | catch (error) { 18 | next(error); 19 | } 20 | }); 21 | exports.AsyncWrapper = AsyncWrapper; 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Steven Leal 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /client/src/components/Site/SiteFoodList/SiteFoodList.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { useQuery } from "@tanstack/react-query" 3 | import SiteProductCard from "../SiteProductCard/SiteProductCard" 4 | import { GetAllFoodListApi } from "@/services/food.api" 5 | 6 | 7 | interface SiteFoodListProps { 8 | selected: string 9 | } 10 | 11 | export default function SiteFoodList({selected}: SiteFoodListProps) { 12 | 13 | const {data:products} = useQuery({ 14 | queryKey: ['food-list'], 15 | queryFn: GetAllFoodListApi 16 | }) 17 | 18 | 19 | return ( 20 |
21 |

Top Dishes for you

22 |
23 | { products?.map((item) => { 24 | if(selected === 'All' || selected === item.category) { 25 | return ( 26 | 27 | ) 28 | } 29 | })} 30 | {products?.map((item) => ( 31 | 32 | ))} 33 |
34 |
35 | ) 36 | } 37 | -------------------------------------------------------------------------------- /server/src/middleware/ErrorMiddleware.ts: -------------------------------------------------------------------------------- 1 | import { ErrorRequestHandler } from "express"; 2 | import { CustomError, ErrorMessage, HttpStatusCode } from "../utils"; 3 | import { ZodError } from "zod"; 4 | 5 | export const ErrorMiddleware: ErrorRequestHandler = (error, req, res, next) => { 6 | const statusCode = error.statusCode ?? 500; 7 | 8 | if (error instanceof ZodError) { 9 | return res.status(statusCode).json({ message: error.flatten().fieldErrors }); 10 | } 11 | 12 | if (error instanceof CustomError) { 13 | return res.status(statusCode).json({ message: error.message }); 14 | } 15 | 16 | if (error.name === 'TokenExpiredError') { 17 | res.cookie("foodZone", "", { 18 | expires: new Date(0), 19 | httpOnly: true, 20 | secure: false, 21 | maxAge: 0 22 | }) 23 | return res.status(HttpStatusCode.UNAUTHORIZED).json({ message: error.message }) 24 | } 25 | 26 | if (error.name === 'JsonWebTokenError') { 27 | return res.status(HttpStatusCode.UNAUTHORIZED).json({ message: ErrorMessage.NOT_AUTHORIZED }); 28 | } 29 | 30 | return res.status(statusCode).json({ message: ErrorMessage.DEFAULT_ERROR_MESSAGE }); 31 | }; 32 | -------------------------------------------------------------------------------- /server/dist/middleware/ErrorMiddleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ErrorMiddleware = void 0; 4 | const utils_1 = require("../utils"); 5 | const zod_1 = require("zod"); 6 | const ErrorMiddleware = (error, req, res, next) => { 7 | var _a; 8 | const statusCode = (_a = error.statusCode) !== null && _a !== void 0 ? _a : 500; 9 | if (error instanceof zod_1.ZodError) { 10 | return res.status(statusCode).json({ message: error.flatten().fieldErrors }); 11 | } 12 | if (error instanceof utils_1.CustomError) { 13 | return res.status(statusCode).json({ message: error.message }); 14 | } 15 | if (error.name === 'TokenExpiredError') { 16 | res.cookie("foodZone", "", { 17 | expires: new Date(0), 18 | httpOnly: true, 19 | secure: false, 20 | maxAge: 0 21 | }); 22 | return res.status(utils_1.HttpStatusCode.UNAUTHORIZED).json({ message: error.message }); 23 | } 24 | return res.status(statusCode).json({ message: utils_1.ErrorMessage.DEFAULT_ERROR_MESSAGE }); 25 | }; 26 | exports.ErrorMiddleware = ErrorMiddleware; 27 | -------------------------------------------------------------------------------- /server/src/models/user.model.ts: -------------------------------------------------------------------------------- 1 | import { UserModelType } from './../types.def'; 2 | import { model, Schema } from 'mongoose' 3 | import bcrypt, { genSalt } from 'bcryptjs' 4 | 5 | export interface IUserModel extends UserModelType, Document { 6 | _id: string 7 | } 8 | 9 | const userSchema = new Schema({ 10 | email: { 11 | type: String, 12 | required: true, 13 | }, 14 | password: { 15 | type: String, 16 | required: true, 17 | }, 18 | firstname: { 19 | type: String, 20 | required: true, 21 | }, 22 | lastname: { 23 | type: String, 24 | required: true, 25 | }, 26 | mobile: { 27 | type: String, 28 | required: true, 29 | }, 30 | role: { 31 | type: String, 32 | enum: ['user', 'admin'], 33 | default: 'user' 34 | }, 35 | blocked: { 36 | type: Boolean, 37 | default: false 38 | } 39 | }) 40 | 41 | userSchema.pre('save', async function (next) { 42 | if (!this.isModified('password')) { 43 | next() 44 | } 45 | const salt = bcrypt.genSaltSync(10) 46 | this.password = await bcrypt.hash(this.password, salt) 47 | }) 48 | 49 | export const UserModel = model('User', userSchema) -------------------------------------------------------------------------------- /server/src/models/food.model.ts: -------------------------------------------------------------------------------- 1 | import { model, Schema } from 'mongoose' 2 | import { FoodModelType } from '../types.def' 3 | 4 | const foodSchema = new Schema({ 5 | name: { 6 | type: String, 7 | required: true 8 | }, 9 | description: { 10 | type: String, 11 | required: true 12 | }, 13 | price: { 14 | type: Number, 15 | required: true 16 | }, 17 | category: { 18 | type: String, 19 | required: true 20 | }, 21 | image: { 22 | type: String 23 | 24 | }, 25 | starRating: { 26 | type: Number, 27 | default: 1, 28 | max: 5 29 | }, 30 | available: { 31 | type: Boolean, 32 | default: true 33 | }, 34 | vegetarian: { 35 | type: Boolean, 36 | default: false 37 | 38 | }, 39 | discount: { 40 | type: Number, 41 | }, 42 | ingredients: { 43 | type: [String], 44 | required: true, 45 | default: [] 46 | }, 47 | createdAt: { 48 | type: Date, 49 | default: Date.now 50 | } 51 | }) 52 | 53 | export const FoodModel = model('Food', foodSchema) -------------------------------------------------------------------------------- /client/src/components/tables/customer/DropDownMenu.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"; 3 | import { Ellipsis } from "lucide-react"; 4 | import { UseBlockUnBlockUser, UseDeleteUser } from "./mutation"; 5 | 6 | 7 | interface DropProps { 8 | id: string 9 | } 10 | 11 | export default function DropDownMenu({id}: DropProps) { 12 | const blockunblock = UseBlockUnBlockUser() 13 | const deleteUser = UseDeleteUser() 14 | 15 | return ( 16 | 17 | 18 | 22 | 23 | 24 | blockunblock.mutate(id)}>Block User 25 | 26 | deleteUser.mutate(id)}>Delete User 27 | 28 | 29 | ) 30 | } 31 | -------------------------------------------------------------------------------- /server/dist/utils/CustomLogger.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.CustomLogger = void 0; 7 | const winston_1 = __importDefault(require("winston")); 8 | const path_1 = __importDefault(require("path")); 9 | const { combine, colorize, timestamp, align, printf, json } = winston_1.default.format; 10 | winston_1.default.addColors({ 11 | error: "red", 12 | warn: "yellow", 13 | info: "cyan", 14 | debug: "green", 15 | }); 16 | exports.CustomLogger = winston_1.default.createLogger({ 17 | level: "http", 18 | format: combine(colorize({ all: true }), timestamp({ 19 | format: "YY-MM-DD hh:mm A", 20 | }), align(), printf((info) => `[${info.timestamp}] ${info.level}: ${info.message}`)), 21 | transports: [ 22 | new winston_1.default.transports.Console(), 23 | new winston_1.default.transports.File({ 24 | filename: path_1.default.join(__dirname, "../logs/app-error.log"), 25 | level: "error" 26 | }), 27 | ], 28 | }); 29 | -------------------------------------------------------------------------------- /server/dist/middleware/ValidationMiddleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.ValidationMiddleware = void 0; 13 | const ValidationMiddleware = (schema) => (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 14 | try { 15 | yield schema.parseAsync({ 16 | body: req.body 17 | }); 18 | next(); 19 | } 20 | catch (error) { 21 | next(error); 22 | } 23 | }); 24 | exports.ValidationMiddleware = ValidationMiddleware; 25 | -------------------------------------------------------------------------------- /server/dist/models/food.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.FoodModel = void 0; 4 | const mongoose_1 = require("mongoose"); 5 | const foodSchema = new mongoose_1.Schema({ 6 | name: { 7 | type: String, 8 | required: true 9 | }, 10 | description: { 11 | type: String, 12 | required: true 13 | }, 14 | price: { 15 | type: Number, 16 | required: true 17 | }, 18 | category: { 19 | type: String, 20 | required: true 21 | }, 22 | image: { 23 | type: String 24 | }, 25 | starRating: { 26 | type: Number, 27 | default: 1, 28 | max: 5 29 | }, 30 | available: { 31 | type: Boolean, 32 | default: true 33 | }, 34 | vegetarian: { 35 | type: Boolean, 36 | default: false 37 | }, 38 | discount: { 39 | type: Number, 40 | }, 41 | ingredients: { 42 | type: [String], 43 | required: true, 44 | default: [] 45 | }, 46 | createdAt: { 47 | type: Date, 48 | default: Date.now 49 | } 50 | }); 51 | exports.FoodModel = (0, mongoose_1.model)('Food', foodSchema); 52 | -------------------------------------------------------------------------------- /client/src/utils/Schema.ts: -------------------------------------------------------------------------------- 1 | import { z } from 'zod'; 2 | 3 | export const SignupSchema = z.object({ 4 | email: z.string({ required_error: 'Email is required' }).email({ message: 'Invalid email address' }), 5 | password: z 6 | .string({ required_error: 'Password is required' }) 7 | .min(6, { message: 'Password must be 6 or more characters long' }) 8 | .max(20, { message: 'Password must be 16 or fewer characters long' }), 9 | firstname: z 10 | .string({ required_error: 'Firstname is required' }) 11 | .min(4, { message: 'Firstname must be 4 or more characters long' }), 12 | lastname: z 13 | .string({ required_error: 'Lastname is required' }) 14 | .min(4, { message: 'Lastname must be 4 or more characters long' }), 15 | mobile: z.string({ required_error: 'Mobile number is required' }).min(10, { message: 'Mobile number must be Valid' }), 16 | }); 17 | 18 | export const SigninSchema = z.object({ 19 | email: z.string({ required_error: 'Email is required' }).email({ message: 'Invalid email address' }), 20 | password: z 21 | .string({ required_error: 'Password is required' }) 22 | .min(6, { message: 'Password must be 6 or more characters long' }) 23 | .max(20, { message: 'Password must be 16 or fewer characters long' }), 24 | }); -------------------------------------------------------------------------------- /client/src/components/ui/tooltip.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as TooltipPrimitive from "@radix-ui/react-tooltip" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const TooltipProvider = TooltipPrimitive.Provider 7 | 8 | const Tooltip = TooltipPrimitive.Root 9 | 10 | const TooltipTrigger = TooltipPrimitive.Trigger 11 | 12 | const TooltipContent = React.forwardRef< 13 | React.ElementRef, 14 | React.ComponentPropsWithoutRef 15 | >(({ className, sideOffset = 4, ...props }, ref) => ( 16 | 25 | )) 26 | TooltipContent.displayName = TooltipPrimitive.Content.displayName 27 | 28 | export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider } 29 | -------------------------------------------------------------------------------- /client/src/components/tables/product/DropDownMenu.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { Ellipsis } from "lucide-react"; 3 | import { UseDeleteProduct, UseProductenableDisabled } from "./mutation"; 4 | import { 5 | DropdownMenu, 6 | DropdownMenuContent, 7 | DropdownMenuItem, 8 | DropdownMenuSeparator, 9 | DropdownMenuTrigger, 10 | } from "@/components/ui/dropdown-menu"; 11 | 12 | interface DropProps { 13 | id: string; 14 | } 15 | 16 | export default function DropDownMenu({ id }: DropProps) { 17 | const EnableDisableProduct = UseProductenableDisabled(); 18 | const DeleteProduct = UseDeleteProduct() 19 | return ( 20 | 21 | 22 | 26 | 27 | 28 | DeleteProduct.mutate(id)}>Delete Product 29 | 30 | EnableDisableProduct.mutate(id)}>Disable Product 31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /client/src/Layout/Header/Navigation/Navigation.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from "@/lib/utils"; 2 | import { Cookie } from "lucide-react"; 3 | import { useState } from "react"; 4 | import { Link } from "react-router-dom"; 5 | 6 | const NavLinks = [ 7 | { 8 | name: "Home", 9 | href: '/' 10 | }, 11 | { 12 | name: "Menu", 13 | href: "/menu" 14 | } 15 | ]; 16 | 17 | export default function Navigation() { 18 | const [selected, setSelected] = useState("Home"); 19 | return ( 20 |
21 | setSelected("Home")} 25 | > 26 | {" "} 27 | FoodZone 28 | 29 |
    30 | {NavLinks.map((link) => ( 31 | setSelected(link.name)} 34 | className={cn( 35 | selected === link.name && "border-b-2 border-primary", 36 | "cursor-pointer" 37 | )} 38 | > 39 | {link.name} 40 | 41 | ))} 42 |
43 |
44 | ); 45 | } -------------------------------------------------------------------------------- /client/src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Food Delivery app built with mern stack 2 | 3 | ## Tech Stack 4 | 5 | - **Client:** React, Typescript 6 | - **Server:** Node, Typescript, Express, Mongo(mongoose) 7 | 8 | ## 🔗 Links 9 | - Live: https://foodzonenewapp.netlify.app/ 10 | - Code: https://github.com/imcrazysteven/Food-Delivery-Application.git 11 | 12 | ## Table of Contents 13 | 14 | - [Features](#features) 15 | - [Technologies](#technologies) 16 | 17 | 18 | ## Features 19 | 20 | - [x] Login/Register 21 | - [x] Admin Dashboard(Add product/delete product/disable product) 22 | - [x] Product List 23 | - [x] Category List 24 | - [x] User Cart 25 | - [x] Search 26 | 27 | 28 | ## 🛠 Technologies 29 | 30 | - React 31 | - Tailwind 32 | - Tanstack-Query 33 | - Shadcn ui 34 | - React-hook-form 35 | - Vite 36 | - Node 37 | - Express 38 | - Typescript 39 | - Zod 40 | - Moongose 41 | - cloudinary 42 | - Stripe 43 | - Render 44 | - Netlify 45 | 46 | ## Contribution 47 | 48 | 1. Fork the repository 49 | 2. Create your feature branch (`git checkout -b feature/AmazingFeature`) 50 | 3. Commit your changes (`git commit -m 'Add some AmazingFeature'`) 51 | 4. Push to the branch (`git push origin feature/AmazingFeature`) 52 | 5. Open a pull request 53 | 54 | ## License 55 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details. -------------------------------------------------------------------------------- /server/src/middleware/AuthMiddleware.ts: -------------------------------------------------------------------------------- 1 | import jwt, { JwtPayload } from 'jsonwebtoken' 2 | import { Response, Request, NextFunction } from "express"; 3 | import { AsyncWrapper, ErrorMessage, HttpStatusCode } from "../utils"; 4 | import { UserModelType } from '../types.def'; 5 | import { UserModel } from '../models'; 6 | 7 | export const AuthMiddleware = AsyncWrapper(async (req: Request, res: Response, next: NextFunction) => { 8 | const token = req.cookies['foodZone'] 9 | 10 | if (!token) { 11 | return res.status(HttpStatusCode.UNAUTHORIZED).json({ message: ErrorMessage.NOT_AUTHORIZED }); 12 | } 13 | 14 | const decodeToken = await jwt.verify(token, process.env.SECRET_KEY); 15 | 16 | if (!decodeToken) { 17 | return res.status(HttpStatusCode.UNAUTHORIZED).json({ message: ErrorMessage.NOT_AUTHORIZED }); 18 | } 19 | 20 | req.userId = (decodeToken as JwtPayload).userId; 21 | next(); 22 | }) 23 | 24 | export const AdminMiddleware = async (req: Request, res: Response, next: NextFunction) => { 25 | try { 26 | const findAdmin: UserModelType | null = await UserModel.findById(req.userId); 27 | if (findAdmin?.role !== 'admin') { 28 | return res.status(401).json({ message: "Unauthorized" }); 29 | } 30 | next() 31 | } catch (error) { 32 | return res.status(401).json({ message: 'Unauthorized' }); 33 | } 34 | } -------------------------------------------------------------------------------- /server/src/utils/helper.ts: -------------------------------------------------------------------------------- 1 | export enum HttpStatusCode { 2 | OK = 200, 3 | CREATED = 201, 4 | BAD_REQUEST = 400, 5 | NOT_FOUND = 404, 6 | INTERNAL_SERVER = 500, 7 | UNAUTHORIZED = 401, 8 | FORBIDDEN = 403, 9 | } 10 | 11 | export const SuccessMessage = { 12 | USER_REGISTER_SUCCESS: 'Your Account has been Created', 13 | USER_LOGIN_SUCCESS: "Your have Logged In", 14 | USER_LOGOUT_SUCCESS: 'You have been Successfully logged out', 15 | PRODUCT_ADDED_SUCCESSFULLY: 'Product added successfully', 16 | PRODUCT_UPDATED_SUCCESSFULLY: 'Product updated successfully', 17 | PRODUCT_DELETED_SUCCESSFULLY: 'Product deleted successfully', 18 | 19 | } 20 | 21 | export const ErrorMessage = { 22 | DEFAULT_ERROR_MESSAGE: "Something went wrong", 23 | ROUTE_NOT_FOUND: "Route not found", 24 | USER_ALREADY_EXIST: "Email already exists", 25 | USER_NOT_FOUND: "Invalid Email or Password", 26 | INVALID_PASSWORD: "Invalid Email or Password", 27 | INVALID_EMAIL: "Invalid email", 28 | NOT_AUTHORIZED: "You are not authorized to access this route", 29 | INVALID_CREDENTIALS: "Invalid credentials", 30 | RATE_LIMIT_ERROR: 'You have already tried registering twice. Please wait for a while', 31 | PRODUCT_ALREADY_EXIST: 'Product already exist', 32 | PRODUCT_NOT_FOUND: 'Product not found', 33 | PRODUCT_COUNT: 'Count must be greater than zero', 34 | } -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default { 18 | // other rules... 19 | parserOptions: { 20 | ecmaVersion: 'latest', 21 | sourceType: 'module', 22 | project: ['./tsconfig.json', './tsconfig.node.json'], 23 | tsconfigRootDir: __dirname, 24 | }, 25 | } 26 | ``` 27 | 28 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 29 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 31 | -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "server", 3 | "version": "1.0.0", 4 | "main": "./dist/index.js", 5 | "scripts": { 6 | "build": "tsc", 7 | "clean": "rimraf dist", 8 | "dev": "concurrently \"npx tsc --watch\" \"nodemon -q dist/index.js", 9 | "start": "npm run build && node dist/index.js" 10 | }, 11 | "keywords": [], 12 | "author": "", 13 | "license": "ISC", 14 | "description": "", 15 | "dependencies": { 16 | "bcryptjs": "^2.4.3", 17 | "cloudinary": "^2.2.0", 18 | "cookie-parser": "^1.4.6", 19 | "cors": "^2.8.5", 20 | "dotenv": "^16.4.5", 21 | "express": "^4.19.2", 22 | "express-mongo-sanitize": "^2.2.0", 23 | "express-rate-limit": "^7.2.0", 24 | "helmet": "^7.1.0", 25 | "jsonwebtoken": "^9.0.2", 26 | "mongoose": "^8.3.3", 27 | "multer": "^1.4.5-lts.1", 28 | "slugify": "^1.6.6", 29 | "stripe": "^15.7.0", 30 | "winston": "^3.13.0", 31 | "zod": "^3.23.6" 32 | }, 33 | "devDependencies": { 34 | "@types/bcryptjs": "^2.4.6", 35 | "@types/cookie-parser": "^1.4.7", 36 | "@types/cors": "^2.8.17", 37 | "@types/express": "^4.17.21", 38 | "@types/jsonwebtoken": "^9.0.6", 39 | "@types/morgan": "^1.9.9", 40 | "@types/multer": "^1.4.11", 41 | "concurrently": "^8.2.2", 42 | "morgan": "^1.10.0", 43 | "nodemon": "^3.1.0", 44 | "rimraf": "^5.0.5", 45 | "ts-node": "^10.9.2", 46 | "typescript": "^5.4.5" 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /server/src/utils/ValidationSchema.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | export const SignupSchema = z.object({ 4 | body: z.object({ 5 | email: z 6 | .string({ required_error: "Email is required" }) 7 | .email({ message: "Invalid email address" }), 8 | password: z 9 | .string({ required_error: "Password is required" }) 10 | .min(6, { message: "Password must be 6 or more characters long" }) 11 | .max(16, { message: "password must be 16 or fewer characters long" }), 12 | firstname: z 13 | .string({ required_error: 'firstname is required' }) 14 | .min(4, { message: 'firstname must be 4 or more characters long' }), 15 | lastname: z 16 | .string({ required_error: 'lastname is required' }) 17 | .min(4, { message: 'lastname must be 4 or more characters long' }), 18 | mobile: z 19 | .string({ required_error: "mobile number is required" }) 20 | .min(10, { message: "mobile number must be 10 or more characters long" }) 21 | }), 22 | }); 23 | 24 | export const SigninSchema = z.object({ 25 | body: z.object({ 26 | email: z 27 | .string({ required_error: "Email is required" }) 28 | .email({ message: "Invalid email address" }), 29 | password: z 30 | .string({ required_error: "Password is required" }) 31 | .min(6, { message: "Password must be 6 or more characters long" }) 32 | .max(16, { message: "Password must be 16 or fewer characters long" }), 33 | }), 34 | }); -------------------------------------------------------------------------------- /client/src/pages/Checkoutpage/Checkoutpage.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from "react"; 2 | import { loadStripe } from "@stripe/stripe-js"; 3 | import { Elements } from "@stripe/react-stripe-js"; 4 | import CheckoutForm from "@/components/Forms/CheckoutForm"; 5 | import { base_url, CartItemProps } from "@/services/interface"; 6 | import { useQuery } from "@tanstack/react-query"; 7 | 8 | export default function Checkoutpage() { 9 | const [clientSecret, setClientSecret] = useState(""); 10 | const { data } = useQuery({ queryKey: ["cart-items"] }); 11 | const stripePromise = loadStripe(import.meta.env.VITE_STRIPE); 12 | 13 | useEffect(() => { 14 | fetch(`${base_url}/payment/create-payment-intent`, { 15 | method: "POST", 16 | credentials: "include", 17 | headers: { "Content-Type": "application/json" }, 18 | body: JSON.stringify({ amount: data?.cartTotal }), 19 | }) 20 | .then((res) => res.json()) 21 | .then((data) => setClientSecret(data.clientSecret)); 22 | }, []); 23 | 24 | const appearance = { 25 | theme: "stripe", 26 | }; 27 | // eslint-disable-next-line @typescript-eslint/no-explicit-any 28 | const options: any = { 29 | clientSecret, 30 | appearance, 31 | }; 32 | return ( 33 |
34 | {clientSecret && ( 35 | 36 | 37 | 38 | )} 39 |
40 | ); 41 | } 42 | -------------------------------------------------------------------------------- /client/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/src/types.def.ts: -------------------------------------------------------------------------------- 1 | import { z } from "zod"; 2 | 3 | const EnvVariables = z.object({ 4 | PORT: z.string().min(1).max(4), 5 | MONGO_URI: z.string().min(1), 6 | SECRET_KEY: z.string().min(10).max(20), 7 | ORIGIN: z.string().min(1), 8 | CLOUDINARY_CLOUD_NAME: z.string().min(1), 9 | CLOUDINARY_API_KEY: z.string().min(1), 10 | CLOUDINARY_API_SECRET: z.string().min(1), 11 | STRIPE_API_KEY: z.string().min(1), 12 | }); 13 | 14 | declare global { 15 | namespace Express { 16 | interface Request { 17 | userId: string; 18 | } 19 | } 20 | } 21 | 22 | declare global { 23 | namespace NodeJS { 24 | export interface ProcessEnv extends z.infer {} 25 | } 26 | } 27 | 28 | export type UserModelType = { 29 | email: string; 30 | password: string; 31 | firstname: string; 32 | lastname: string; 33 | mobile: string; 34 | role: string; 35 | blocked: boolean 36 | }; 37 | 38 | export type FoodModelType = { 39 | name: string; 40 | description: string; 41 | price: number; 42 | category: string; 43 | image: string; 44 | available: boolean; 45 | vegetarian: boolean; 46 | ingredients: []; 47 | discount: number; 48 | createdAt: Date; 49 | starRating: number 50 | }; 51 | 52 | export type CartProduct = { 53 | product: FoodModelType; 54 | count: number; 55 | price: number; 56 | } 57 | 58 | 59 | export type CartModelType = { 60 | products: CartProduct[] 61 | cartTotal: number 62 | orderBy: UserModelType 63 | } -------------------------------------------------------------------------------- /client/src/components/Admin/AdminSidebar.tsx: -------------------------------------------------------------------------------- 1 | import { Package, Users, CookieIcon } from "lucide-react"; 2 | import { Link } from "react-router-dom"; 3 | 4 | 5 | const SidebarLinks = [ 6 | { 7 | name: 'Products', 8 | href: '/dashboard/products', 9 | icon: 10 | }, 11 | { 12 | name: 'Customers', 13 | href: '/dashboard/customers', 14 | icon: 15 | } 16 | ] 17 | 18 | 19 | export default function AdminSidebar() { 20 | return ( 21 |
22 |
23 | 24 | 25 | FoodZone 26 | 27 |
28 |
29 | 41 |
42 |
43 | ) 44 | } 45 | -------------------------------------------------------------------------------- /server/dist/middleware/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.RouteNotFoundMiddleware = exports.AdminMiddleware = exports.AuthMiddleware = exports.RateLimiterMiddleware = exports.ValidationMiddleware = exports.ErrorMiddleware = void 0; 4 | var ErrorMiddleware_1 = require("./ErrorMiddleware"); 5 | Object.defineProperty(exports, "ErrorMiddleware", { enumerable: true, get: function () { return ErrorMiddleware_1.ErrorMiddleware; } }); 6 | var ValidationMiddleware_1 = require("./ValidationMiddleware"); 7 | Object.defineProperty(exports, "ValidationMiddleware", { enumerable: true, get: function () { return ValidationMiddleware_1.ValidationMiddleware; } }); 8 | var RateLimiterMiddleware_1 = require("./RateLimiterMiddleware"); 9 | Object.defineProperty(exports, "RateLimiterMiddleware", { enumerable: true, get: function () { return RateLimiterMiddleware_1.RateLimiterMiddleware; } }); 10 | var AuthMiddleware_1 = require("./AuthMiddleware"); 11 | Object.defineProperty(exports, "AuthMiddleware", { enumerable: true, get: function () { return AuthMiddleware_1.AuthMiddleware; } }); 12 | Object.defineProperty(exports, "AdminMiddleware", { enumerable: true, get: function () { return AuthMiddleware_1.AdminMiddleware; } }); 13 | var RouteNotFoundMiddleware_1 = require("./RouteNotFoundMiddleware"); 14 | Object.defineProperty(exports, "RouteNotFoundMiddleware", { enumerable: true, get: function () { return RouteNotFoundMiddleware_1.RouteNotFoundMiddleware; } }); 15 | -------------------------------------------------------------------------------- /client/src/context/AuthContext.tsx: -------------------------------------------------------------------------------- 1 | import LoadingSpinner from "@/components/common/LoadingSpinner"; 2 | import { verifyAdminApi } from "@/services/admin.api"; 3 | import { VerifyUserApi } from "@/services/api"; 4 | import { useQuery } from "@tanstack/react-query"; 5 | import { createContext, ReactNode, useContext, useState } from "react"; 6 | 7 | 8 | 9 | export type AuthContext = { 10 | isAuth: boolean; 11 | isAdmin: boolean 12 | }; 13 | 14 | const initialValue = { 15 | isAuth: false, 16 | isAdmin :false 17 | }; 18 | 19 | export const AuthContext = createContext(initialValue); 20 | 21 | export const AuthContextProvider = ({ children }: { children: ReactNode }) => { 22 | const [adminValue] = useState(() => JSON.parse(localStorage.getItem("checkwho")!)); 23 | const { isError, isLoading } = useQuery({ 24 | queryKey: ["authuser"], 25 | queryFn: VerifyUserApi, 26 | }); 27 | 28 | const {isError:admin} = useQuery({ 29 | queryKey: ['authadmin'], 30 | queryFn: verifyAdminApi, 31 | retry: false, 32 | enabled: !!adminValue?.value 33 | }) 34 | 35 | if (isLoading) { 36 | return ; 37 | } 38 | 39 | 40 | return {children}; 41 | }; 42 | 43 | // eslint-disable-next-line react-refresh/only-export-components 44 | export const useAuthContext = () => { 45 | const context = useContext(AuthContext); 46 | if (!context) { 47 | throw new Error("useAuthContext must be used within a AuthContextProvider"); 48 | } 49 | return context as AuthContext; 50 | }; 51 | -------------------------------------------------------------------------------- /client/src/components/tables/product/product-columns.tsx: -------------------------------------------------------------------------------- 1 | import { ColumnDef } from "@tanstack/react-table"; 2 | import DropDownMenu from "./DropDownMenu"; 3 | 4 | 5 | 6 | export type Product = { 7 | name: string; 8 | price: string; 9 | category: string; 10 | image: string 11 | vegetarian: string 12 | }; 13 | 14 | export type ProductType = { 15 | _id: string; 16 | name: string; 17 | description: string; 18 | price: number; 19 | image?: string; 20 | category: string; 21 | vegetarian?: string; 22 | available: boolean 23 | }; 24 | 25 | 26 | 27 | 28 | export const columns: ColumnDef[] = [ 29 | { 30 | accessorKey: "name", 31 | header: "Name", 32 | }, 33 | { 34 | accessorKey: "price", 35 | header: "Price", 36 | }, 37 | { 38 | accessorKey: "category", 39 | header: "Category", 40 | }, 41 | { 42 | accessorKey: "image", 43 | header: "Image", 44 | cell: ({ row }) => { 45 | const imageurl = row.getValue("image") 46 | 47 | return
48 | {'product-img'} 49 |
50 | }, 51 | }, 52 | { 53 | accessorKey: "vegetarian", 54 | header: "Vegetarian", 55 | }, 56 | { 57 | accessorKey: "available", 58 | header: "Available", 59 | }, 60 | { 61 | id: "actions", 62 | enableHiding: false, 63 | header: "Actions", 64 | cell: ({row}) => { 65 | return ( 66 | 67 | ); 68 | }, 69 | }, 70 | ]; 71 | 72 | -------------------------------------------------------------------------------- /server/dist/db/MongoConnection.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const mongoose_1 = __importDefault(require("mongoose")); 16 | const utils_1 = require("../utils"); 17 | const MONGO_URL = process.env.MONGO_URI; 18 | function MongoConnection() { 19 | return __awaiter(this, void 0, void 0, function* () { 20 | yield mongoose_1.default 21 | .connect(MONGO_URL) 22 | .then((success) => { 23 | utils_1.CustomLogger.info(`[Mongo]: Mongo Connected ${success.connection.host}`); 24 | }) 25 | .catch((error) => { 26 | utils_1.CustomLogger.error(`[Mongo]: Mongo connection failed ${error.message}`); 27 | }); 28 | }); 29 | } 30 | exports.default = MongoConnection; 31 | -------------------------------------------------------------------------------- /client/src/services/food.api.ts: -------------------------------------------------------------------------------- 1 | import { base_url, ProductProps } from "./interface"; 2 | 3 | export const SearchApi = async (searchTerm: string): Promise => { 4 | const response = await fetch(`${base_url}/food/search/${searchTerm}`); 5 | 6 | const data = await response.json(); 7 | 8 | if (!response.ok) { 9 | throw new Error(data.message); 10 | } 11 | 12 | return data; 13 | }; 14 | 15 | export const GetAllFoodListApi = async (): Promise => { 16 | const response = await fetch(`${base_url}/food/all-foodlist`); 17 | 18 | const data = await response.json(); 19 | 20 | if (!response.ok) { 21 | throw new Error(data.message); 22 | } 23 | 24 | return data; 25 | }; 26 | 27 | export const GetVegFoodApi = async (): Promise => { 28 | const response = await fetch(`${base_url}/food/veg-food`); 29 | 30 | const data = await response.json(); 31 | 32 | if (!response.ok) { 33 | throw new Error(data.message); 34 | } 35 | 36 | return data; 37 | }; 38 | 39 | export const GetNonVegFoodApi = async (): Promise => { 40 | const response = await fetch(`${base_url}/food/nonveg-food`); 41 | 42 | const data = await response.json(); 43 | 44 | if (!response.ok) { 45 | throw new Error(data.message); 46 | } 47 | 48 | return data; 49 | }; 50 | 51 | export const GetRecentlyAddedFoodApi = async (): Promise => { 52 | const response = await fetch(`${base_url}/food/recent-food`); 53 | 54 | const data = await response.json(); 55 | 56 | if (!response.ok) { 57 | throw new Error(data.message); 58 | } 59 | 60 | return data; 61 | }; 62 | -------------------------------------------------------------------------------- /server/dist/app.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | exports.app = void 0; 7 | const express_1 = __importDefault(require("express")); 8 | const cors_1 = __importDefault(require("cors")); 9 | const helmet_1 = __importDefault(require("helmet")); 10 | const cookie_parser_1 = __importDefault(require("cookie-parser")); 11 | require("dotenv/config"); 12 | const express_mongo_sanitize_1 = __importDefault(require("express-mongo-sanitize")); 13 | const morgan_1 = __importDefault(require("morgan")); 14 | const routes_1 = require("./routes"); 15 | const middleware_1 = require("./middleware"); 16 | exports.app = (0, express_1.default)(); 17 | exports.app.use((0, morgan_1.default)('dev')); 18 | exports.app.use(express_1.default.json()); 19 | exports.app.use((0, cors_1.default)({ 20 | origin: process.env.ORIGIN || 'http://localhost:5173', 21 | credentials: true, 22 | })); 23 | exports.app.use((0, cookie_parser_1.default)()); 24 | exports.app.use((0, helmet_1.default)()); 25 | exports.app.use((0, express_mongo_sanitize_1.default)()); 26 | exports.app.disable('x-powered-by'); 27 | exports.app.use('/api/auth', routes_1.AuthRoute); 28 | exports.app.use('/api/food', routes_1.FoodRoute); 29 | exports.app.use('/api/cart', routes_1.CartRoute); 30 | exports.app.use('/api/admin', routes_1.AdminRoute); 31 | exports.app.use('/api/payment', routes_1.PaymentRoute); 32 | exports.app.use(middleware_1.RouteNotFoundMiddleware); 33 | exports.app.use(middleware_1.ErrorMiddleware); 34 | -------------------------------------------------------------------------------- /client/src/pages/Authpage/_component/AuthCard.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 3 | import { Facebook, Twitter } from "lucide-react"; 4 | 5 | interface AuthCardProps { 6 | children: React.ReactNode 7 | authTitle : string 8 | 9 | } 10 | 11 | export default function AuthCard({children, authTitle}: AuthCardProps) { 12 | return ( 13 | 14 | 15 | {authTitle} 16 | 17 | By continuing, you agree to our User Agreement and acknowledge that you understand the Privacy Policy. 18 | 19 | 20 | 21 |
22 | 25 | 28 |
29 |
30 |
31 | 32 |
33 |
34 | Or continue with 35 |
36 |
37 | {children} 38 |
39 |
40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /server/dist/utils/ValidationSchema.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.SigninSchema = exports.SignupSchema = void 0; 4 | const zod_1 = require("zod"); 5 | exports.SignupSchema = zod_1.z.object({ 6 | body: zod_1.z.object({ 7 | email: zod_1.z 8 | .string({ required_error: "Email is required" }) 9 | .email({ message: "Invalid email address" }), 10 | password: zod_1.z 11 | .string({ required_error: "Password is required" }) 12 | .min(6, { message: "Password must be 6 or more characters long" }) 13 | .max(16, { message: "password must be 16 or fewer characters long" }), 14 | firstname: zod_1.z 15 | .string({ required_error: 'firstname is required' }) 16 | .min(4, { message: 'firstname must be 4 or more characters long' }), 17 | lastname: zod_1.z 18 | .string({ required_error: 'lastname is required' }) 19 | .min(4, { message: 'lastname must be 4 or more characters long' }), 20 | mobile: zod_1.z 21 | .string({ required_error: "mobile number is required" }) 22 | .min(10, { message: "mobile number must be 10 or more characters long" }) 23 | }), 24 | }); 25 | exports.SigninSchema = zod_1.z.object({ 26 | body: zod_1.z.object({ 27 | email: zod_1.z 28 | .string({ required_error: "Email is required" }) 29 | .email({ message: "Invalid email address" }), 30 | password: zod_1.z 31 | .string({ required_error: "Password is required" }) 32 | .min(6, { message: "Password must be 6 or more characters long" }) 33 | .max(16, { message: "Password must be 16 or fewer characters long" }), 34 | }), 35 | }); 36 | -------------------------------------------------------------------------------- /server/dist/controllers/payment.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.CreatePaymentIntent = void 0; 16 | const stripe_1 = __importDefault(require("stripe")); 17 | const utils_1 = require("../utils"); 18 | const models_1 = require("../models"); 19 | const stripe = new stripe_1.default(process.env.STRIPE_API_KEY); 20 | exports.CreatePaymentIntent = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 21 | const { amount } = req.body; 22 | const paymentIntent = yield stripe.paymentIntents.create({ 23 | description: "foodZone app", 24 | amount: amount, 25 | currency: 'inr', 26 | payment_method_types: ['card'] 27 | }); 28 | if (paymentIntent) { 29 | yield models_1.CartModel.findOneAndDelete({ orderBy: req.userId }); 30 | } 31 | res.send({ 32 | clientSecret: paymentIntent.client_secret 33 | }); 34 | })); 35 | -------------------------------------------------------------------------------- /server/dist/utils/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.GenerateTokenAndCookie = exports.ErrorMessage = exports.SuccessMessage = exports.HttpStatusCode = exports.SignupSchema = exports.SigninSchema = exports.AsyncWrapper = exports.CustomError = exports.CustomLogger = void 0; 4 | var CustomLogger_1 = require("./CustomLogger"); 5 | Object.defineProperty(exports, "CustomLogger", { enumerable: true, get: function () { return CustomLogger_1.CustomLogger; } }); 6 | var CustomError_1 = require("./CustomError"); 7 | Object.defineProperty(exports, "CustomError", { enumerable: true, get: function () { return CustomError_1.CustomError; } }); 8 | var AsyncWrapper_1 = require("./AsyncWrapper"); 9 | Object.defineProperty(exports, "AsyncWrapper", { enumerable: true, get: function () { return AsyncWrapper_1.AsyncWrapper; } }); 10 | var ValidationSchema_1 = require("./ValidationSchema"); 11 | Object.defineProperty(exports, "SigninSchema", { enumerable: true, get: function () { return ValidationSchema_1.SigninSchema; } }); 12 | Object.defineProperty(exports, "SignupSchema", { enumerable: true, get: function () { return ValidationSchema_1.SignupSchema; } }); 13 | var helper_1 = require("./helper"); 14 | Object.defineProperty(exports, "HttpStatusCode", { enumerable: true, get: function () { return helper_1.HttpStatusCode; } }); 15 | Object.defineProperty(exports, "SuccessMessage", { enumerable: true, get: function () { return helper_1.SuccessMessage; } }); 16 | Object.defineProperty(exports, "ErrorMessage", { enumerable: true, get: function () { return helper_1.ErrorMessage; } }); 17 | var GenerateTokenAndCookie_1 = require("./GenerateTokenAndCookie"); 18 | Object.defineProperty(exports, "GenerateTokenAndCookie", { enumerable: true, get: function () { return GenerateTokenAndCookie_1.GenerateTokenAndCookie; } }); 19 | -------------------------------------------------------------------------------- /client/src/components/ui/scroll-area.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const ScrollArea = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, children, ...props }, ref) => ( 10 | 15 | 16 | {children} 17 | 18 | 19 | 20 | 21 | )) 22 | ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName 23 | 24 | const ScrollBar = React.forwardRef< 25 | React.ElementRef, 26 | React.ComponentPropsWithoutRef 27 | >(({ className, orientation = "vertical", ...props }, ref) => ( 28 | 41 | 42 | 43 | )) 44 | ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName 45 | 46 | export { ScrollArea, ScrollBar } 47 | -------------------------------------------------------------------------------- /server/dist/routes/cart.route.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | exports.CartRoute = void 0; 27 | const express_1 = require("express"); 28 | const cart = __importStar(require("../controllers/cart.controller")); 29 | const middleware_1 = require("../middleware"); 30 | exports.CartRoute = (0, express_1.Router)(); 31 | exports.CartRoute.get('/user-cart', middleware_1.AuthMiddleware, cart.GetAllCartItems); 32 | exports.CartRoute.post('/add-cart', middleware_1.AuthMiddleware, cart.CreateUserCart); 33 | exports.CartRoute.delete('/remove-cart/:productId', middleware_1.AuthMiddleware, cart.RemoveFromCart); 34 | exports.CartRoute.put('/update-cart/:productId', middleware_1.AuthMiddleware, cart.UpdateCartItem); 35 | -------------------------------------------------------------------------------- /client/src/services/api.ts: -------------------------------------------------------------------------------- 1 | import { SigninSchema, SignupSchema } from "@/utils/Schema"; 2 | import { z } from "zod"; 3 | import { base_url, MessageProps, SignInResponse } from "./interface"; 4 | 5 | 6 | export const SignupUserApi = async (SignupData: z.infer): Promise => { 7 | const response = await fetch(`${base_url}/auth/SignUp`, { 8 | method: "POST", 9 | credentials: "include", 10 | headers: { 11 | "Content-Type": "application/json", 12 | }, 13 | body: JSON.stringify(SignupData), 14 | }); 15 | 16 | const data = await response.json(); 17 | if (!response.ok) { 18 | throw new Error(data.message); 19 | } 20 | 21 | return data; 22 | }; 23 | 24 | 25 | 26 | export const SigninUserApi = async (SigninData: z.infer): Promise => { 27 | const response = await fetch(`${base_url}/auth/SignIn`, { 28 | method: "POST", 29 | credentials: "include", 30 | headers: { 31 | "Content-Type": "application/json", 32 | }, 33 | body: JSON.stringify(SigninData), 34 | }); 35 | 36 | const data = await response.json(); 37 | if (!response.ok) { 38 | throw new Error(data.message); 39 | } 40 | 41 | return data; 42 | }; 43 | 44 | export const VerifyUserApi = async () => { 45 | const response = await fetch(`${base_url}/auth/verifyAuth`, { 46 | credentials: "include", 47 | }); 48 | 49 | const data = await response.json(); 50 | 51 | if (!response.ok) { 52 | throw new Error(data.message); 53 | } 54 | 55 | return data; 56 | }; 57 | 58 | export const LogoutUserApi = async (): Promise => { 59 | const response = await fetch(`${base_url}/auth/SignOutUser`, { 60 | method: "POST", 61 | credentials: "include", 62 | }); 63 | 64 | const data = await response.json(); 65 | 66 | if (!response.ok) { 67 | throw new Error(data.message); 68 | } 69 | 70 | return data; 71 | }; 72 | -------------------------------------------------------------------------------- /server/dist/utils/helper.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.ErrorMessage = exports.SuccessMessage = exports.HttpStatusCode = void 0; 4 | var HttpStatusCode; 5 | (function (HttpStatusCode) { 6 | HttpStatusCode[HttpStatusCode["OK"] = 200] = "OK"; 7 | HttpStatusCode[HttpStatusCode["CREATED"] = 201] = "CREATED"; 8 | HttpStatusCode[HttpStatusCode["BAD_REQUEST"] = 400] = "BAD_REQUEST"; 9 | HttpStatusCode[HttpStatusCode["NOT_FOUND"] = 404] = "NOT_FOUND"; 10 | HttpStatusCode[HttpStatusCode["INTERNAL_SERVER"] = 500] = "INTERNAL_SERVER"; 11 | HttpStatusCode[HttpStatusCode["UNAUTHORIZED"] = 401] = "UNAUTHORIZED"; 12 | HttpStatusCode[HttpStatusCode["FORBIDDEN"] = 403] = "FORBIDDEN"; 13 | })(HttpStatusCode || (exports.HttpStatusCode = HttpStatusCode = {})); 14 | exports.SuccessMessage = { 15 | USER_REGISTER_SUCCESS: 'Your Account has been Created', 16 | USER_LOGIN_SUCCESS: "Your have Logged In", 17 | USER_LOGOUT_SUCCESS: 'You have been Successfully logged out', 18 | PRODUCT_ADDED_SUCCESSFULLY: 'Product added successfully', 19 | PRODUCT_UPDATED_SUCCESSFULLY: 'Product updated successfully', 20 | PRODUCT_DELETED_SUCCESSFULLY: 'Product deleted successfully', 21 | }; 22 | exports.ErrorMessage = { 23 | DEFAULT_ERROR_MESSAGE: "Something went wrong", 24 | ROUTE_NOT_FOUND: "Route not found", 25 | USER_ALREADY_EXIST: "Email already exists", 26 | USER_NOT_FOUND: "Invalid Email or Password", 27 | INVALID_PASSWORD: "Invalid Email or Password", 28 | INVALID_EMAIL: "Invalid email", 29 | NOT_AUTHORIZED: "You are not authorized to access this route", 30 | INVALID_CREDENTIALS: "Invalid credentials", 31 | RATE_LIMIT_ERROR: 'You have already tried registering twice. Please wait for a while', 32 | PRODUCT_ALREADY_EXIST: 'Product already exist', 33 | PRODUCT_NOT_FOUND: 'Product not found', 34 | PRODUCT_COUNT: 'Count must be greater than zero', 35 | }; 36 | -------------------------------------------------------------------------------- /server/dist/routes/auth.route.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | exports.AuthRoute = void 0; 27 | const express_1 = require("express"); 28 | const auth = __importStar(require("../controllers/auth.controller")); 29 | const utils_1 = require("../utils"); 30 | const middleware_1 = require("../middleware"); 31 | exports.AuthRoute = (0, express_1.Router)(); 32 | exports.AuthRoute.post("/SignUp", (0, middleware_1.RateLimiterMiddleware)(utils_1.ErrorMessage.RATE_LIMIT_ERROR, 2), (0, middleware_1.ValidationMiddleware)(utils_1.SignupSchema), auth.SignUpUser); 33 | exports.AuthRoute.post("/SignIn", (0, middleware_1.ValidationMiddleware)(utils_1.SigninSchema), auth.SignInUser); 34 | exports.AuthRoute.post("/SignOutUser", auth.SignOutUser); 35 | exports.AuthRoute.get("/verifyAuth", middleware_1.AuthMiddleware, auth.VerifyUser); 36 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Sedan+SC&display=swap'); 2 | @tailwind base; 3 | @tailwind components; 4 | @tailwind utilities; 5 | 6 | 7 | @layer base { 8 | :root { 9 | --background: 0 0% 100%; 10 | --foreground: 20 14.3% 4.1%; 11 | --card: 0 0% 100%; 12 | --card-foreground: 20 14.3% 4.1%; 13 | --popover: 0 0% 100%; 14 | --popover-foreground: 20 14.3% 4.1%; 15 | --primary: 24.6 95% 53.1%; 16 | --primary-foreground: 60 9.1% 97.8%; 17 | --secondary: 60 4.8% 95.9%; 18 | --secondary-foreground: 24 9.8% 10%; 19 | --muted: 60 4.8% 95.9%; 20 | --muted-foreground: 25 5.3% 44.7%; 21 | --accent: 60 4.8% 95.9%; 22 | --accent-foreground: 24 9.8% 10%; 23 | --destructive: 0 84.2% 60.2%; 24 | --destructive-foreground: 60 9.1% 97.8%; 25 | --border: 20 5.9% 90%; 26 | --input: 20 5.9% 90%; 27 | --ring: 24.6 95% 53.1%; 28 | --radius: 0.5rem; 29 | } 30 | 31 | .dark { 32 | --background: 20 14.3% 4.1%; 33 | --foreground: 60 9.1% 97.8%; 34 | --card: 20 14.3% 4.1%; 35 | --card-foreground: 60 9.1% 97.8%; 36 | --popover: 20 14.3% 4.1%; 37 | --popover-foreground: 60 9.1% 97.8%; 38 | --primary: 20.5 90.2% 48.2%; 39 | --primary-foreground: 60 9.1% 97.8%; 40 | --secondary: 12 6.5% 15.1%; 41 | --secondary-foreground: 60 9.1% 97.8%; 42 | --muted: 12 6.5% 15.1%; 43 | --muted-foreground: 24 5.4% 63.9%; 44 | --accent: 12 6.5% 15.1%; 45 | --accent-foreground: 60 9.1% 97.8%; 46 | --destructive: 0 72.2% 50.6%; 47 | --destructive-foreground: 60 9.1% 97.8%; 48 | --border: 12 6.5% 15.1%; 49 | --input: 12 6.5% 15.1%; 50 | --ring: 20.5 90.2% 48.2%; 51 | } 52 | } 53 | 54 | 55 | @layer base { 56 | * { 57 | @apply border-border; 58 | } 59 | body { 60 | @apply bg-background text-foreground; 61 | font-family: "Sedan SC", serif; 62 | font-weight: 400; 63 | font-style: normal; 64 | } 65 | } -------------------------------------------------------------------------------- /client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Navigate, Route, Routes } from "react-router-dom"; 2 | import SiteLayout from "./Layout/SiteLayout"; 3 | import Homepage from "./pages/Homepage/Homepage"; 4 | import SignupPage from "./pages/Authpage/SignupPage"; 5 | import SigninPage from "./pages/Authpage/SigninPage"; 6 | import { useAuthContext } from "./context/AuthContext"; 7 | import Menupage from "./pages/Menupage/Menupage"; 8 | import Cartpage from "./pages/Cartpage/Cartpage"; 9 | import Checkoutpage from "./pages/Checkoutpage/Checkoutpage"; 10 | import AdminLayout from "./Layout/AdminLayout"; 11 | import AdminCustomerList from "./pages/Admin/AdminCustomerList"; 12 | import AdminProductList from "./pages/Admin/AdminProductList"; 13 | 14 | 15 | export default function App() { 16 | const { isAuth, isAdmin } = useAuthContext(); 17 | 18 | return ( 19 | <> 20 | 21 | }> 22 | } /> 23 | : } /> 24 | : } /> 25 | : } /> 26 | : } /> 27 | } /> 28 | 29 | : }> 30 | : } 33 | /> 34 | : } 37 | /> 38 | 39 | }/> 40 | 41 | 42 | ); 43 | } 44 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "client", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@hookform/resolvers": "^3.3.4", 14 | "@radix-ui/react-dialog": "^1.0.5", 15 | "@radix-ui/react-dropdown-menu": "^2.0.6", 16 | "@radix-ui/react-icons": "^1.3.0", 17 | "@radix-ui/react-label": "^2.0.2", 18 | "@radix-ui/react-scroll-area": "^1.0.5", 19 | "@radix-ui/react-select": "^2.0.0", 20 | "@radix-ui/react-slot": "^1.0.2", 21 | "@radix-ui/react-tooltip": "^1.0.7", 22 | "@stripe/react-stripe-js": "^2.7.1", 23 | "@stripe/stripe-js": "^3.4.1", 24 | "@tanstack/react-query": "^5.35.1", 25 | "@tanstack/react-table": "^8.17.3", 26 | "class-variance-authority": "^0.7.0", 27 | "clsx": "^2.1.1", 28 | "cmdk": "^1.0.0", 29 | "eslint-plugin-tailwindcss": "^3.15.1", 30 | "lucide-react": "^0.378.0", 31 | "react": "^18.2.0", 32 | "react-dom": "^18.2.0", 33 | "react-helmet-async": "^2.0.4", 34 | "react-hook-form": "^7.51.4", 35 | "react-router-dom": "^6.23.0", 36 | "sonner": "^1.4.41", 37 | "tailwind-merge": "^2.3.0", 38 | "tailwindcss-animate": "^1.0.7", 39 | "zod": "^3.23.6" 40 | }, 41 | "devDependencies": { 42 | "@types/node": "^20.12.8", 43 | "@types/react": "^18.2.66", 44 | "@types/react-dom": "^18.2.22", 45 | "@typescript-eslint/eslint-plugin": "^7.2.0", 46 | "@typescript-eslint/parser": "^7.2.0", 47 | "@vitejs/plugin-react": "^4.2.1", 48 | "autoprefixer": "^10.4.19", 49 | "eslint": "^8.57.0", 50 | "eslint-plugin-react-hooks": "^4.6.0", 51 | "eslint-plugin-react-refresh": "^0.4.6", 52 | "postcss": "^8.4.38", 53 | "tailwindcss": "^3.4.3", 54 | "typescript": "^5.2.2", 55 | "vite": "^5.2.0" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /server/dist/routes/admin.route.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | Object.defineProperty(exports, "__esModule", { value: true }); 26 | exports.AdminRoute = void 0; 27 | const express_1 = require("express"); 28 | const admin = __importStar(require("../controllers/admin.controller")); 29 | const middleware_1 = require("../middleware"); 30 | exports.AdminRoute = (0, express_1.Router)(); 31 | exports.AdminRoute.get('/get-users', admin.GetAllCustomersList); 32 | exports.AdminRoute.get('/get-products', admin.GetAllProductsList); 33 | exports.AdminRoute.put('/enable-disable/:productId', admin.EnableDisableProduct); 34 | exports.AdminRoute.delete('/delete/:productId', admin.DeleteProduct); 35 | exports.AdminRoute.delete('/user-delete/:userId', admin.DeleteUser); 36 | exports.AdminRoute.put('/block-unblock-user/:userId', admin.BlockUnBlockUser); 37 | exports.AdminRoute.get('/verifyAdminApi', middleware_1.AuthMiddleware, middleware_1.AdminMiddleware, admin.VerifyAdmin); 38 | -------------------------------------------------------------------------------- /client/src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 16 | outline: 17 | "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 20 | ghost: "hover:bg-accent hover:text-accent-foreground", 21 | link: "text-primary underline-offset-4 hover:underline", 22 | }, 23 | size: { 24 | default: "h-9 px-4 py-2", 25 | sm: "h-8 rounded-md px-3 text-xs", 26 | lg: "h-10 rounded-md px-8", 27 | icon: "h-9 w-9", 28 | }, 29 | }, 30 | defaultVariants: { 31 | variant: "default", 32 | size: "default", 33 | }, 34 | } 35 | ) 36 | 37 | export interface ButtonProps 38 | extends React.ButtonHTMLAttributes, 39 | VariantProps { 40 | asChild?: boolean 41 | } 42 | 43 | const Button = React.forwardRef( 44 | ({ className, variant, size, asChild = false, ...props }, ref) => { 45 | const Comp = asChild ? Slot : "button" 46 | return ( 47 | 52 | ) 53 | } 54 | ) 55 | Button.displayName = "Button" 56 | 57 | export { Button, buttonVariants } 58 | -------------------------------------------------------------------------------- /client/src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Card = React.forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
17 | )) 18 | Card.displayName = "Card" 19 | 20 | const CardHeader = React.forwardRef< 21 | HTMLDivElement, 22 | React.HTMLAttributes 23 | >(({ className, ...props }, ref) => ( 24 |
29 | )) 30 | CardHeader.displayName = "CardHeader" 31 | 32 | const CardTitle = React.forwardRef< 33 | HTMLParagraphElement, 34 | React.HTMLAttributes 35 | >(({ className, ...props }, ref) => ( 36 |

41 | )) 42 | CardTitle.displayName = "CardTitle" 43 | 44 | const CardDescription = React.forwardRef< 45 | HTMLParagraphElement, 46 | React.HTMLAttributes 47 | >(({ className, ...props }, ref) => ( 48 |

53 | )) 54 | CardDescription.displayName = "CardDescription" 55 | 56 | const CardContent = React.forwardRef< 57 | HTMLDivElement, 58 | React.HTMLAttributes 59 | >(({ className, ...props }, ref) => ( 60 |

61 | )) 62 | CardContent.displayName = "CardContent" 63 | 64 | const CardFooter = React.forwardRef< 65 | HTMLDivElement, 66 | React.HTMLAttributes 67 | >(({ className, ...props }, ref) => ( 68 |
73 | )) 74 | CardFooter.displayName = "CardFooter" 75 | 76 | export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent } 77 | -------------------------------------------------------------------------------- /client/src/services/cart.api.ts: -------------------------------------------------------------------------------- 1 | import { base_url, CartItemProps, MessageProps } from "./interface"; 2 | 3 | 4 | 5 | 6 | export const GetUserCartItems = async (): Promise => { 7 | const response = await fetch(`${base_url}/cart/user-cart`, { 8 | credentials: "include", 9 | }); 10 | 11 | const data = await response.json(); 12 | 13 | if (!response.ok) { 14 | return null 15 | } 16 | 17 | return data; 18 | }; 19 | 20 | 21 | export const AddToCart = async (productId: string) : Promise => { 22 | const response = await fetch(`${base_url}/cart/add-cart`, { 23 | method: "POST", 24 | credentials: "include", 25 | headers: { 26 | "Content-Type": "application/json", 27 | }, 28 | body: JSON.stringify({productId, count: 1}), 29 | }); 30 | 31 | const data = await response.json(); 32 | if (!response.ok) { 33 | throw new Error(data.message); 34 | } 35 | 36 | return data; 37 | }; 38 | 39 | export const UpdateCartApi = async (productId: string, count: number) : Promise => { 40 | const response = await fetch(`${base_url}/cart/update-cart/${productId}`, { 41 | method: "PUT", 42 | credentials: "include", 43 | headers: { 44 | "Content-Type": "application/json", 45 | }, 46 | body: JSON.stringify({count}), 47 | }); 48 | 49 | const data = await response.json(); 50 | if (!response.ok) { 51 | throw new Error(data.message); 52 | } 53 | 54 | return data; 55 | }; 56 | 57 | 58 | export const RemoveFromCartApi = async (productId: string) : Promise => { 59 | const response = await fetch(`${base_url}/cart/remove-cart/${productId}`, { 60 | method: "DELETE", 61 | credentials: "include", 62 | headers: { 63 | "Content-Type": "application/json", 64 | } 65 | }); 66 | 67 | const data = await response.json(); 68 | if (!response.ok) { 69 | throw new Error(data.message); 70 | } 71 | 72 | return data; 73 | }; 74 | -------------------------------------------------------------------------------- /server/dist/models/user.model.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.UserModel = void 0; 16 | const mongoose_1 = require("mongoose"); 17 | const bcryptjs_1 = __importDefault(require("bcryptjs")); 18 | const userSchema = new mongoose_1.Schema({ 19 | email: { 20 | type: String, 21 | required: true, 22 | }, 23 | password: { 24 | type: String, 25 | required: true, 26 | }, 27 | firstname: { 28 | type: String, 29 | required: true, 30 | }, 31 | lastname: { 32 | type: String, 33 | required: true, 34 | }, 35 | mobile: { 36 | type: String, 37 | required: true, 38 | }, 39 | role: { 40 | type: String, 41 | enum: ['user', 'admin'], 42 | default: 'user' 43 | }, 44 | blocked: { 45 | type: Boolean, 46 | default: false 47 | } 48 | }); 49 | userSchema.pre('save', function (next) { 50 | return __awaiter(this, void 0, void 0, function* () { 51 | if (!this.isModified('password')) { 52 | next(); 53 | } 54 | const salt = bcryptjs_1.default.genSaltSync(10); 55 | this.password = yield bcryptjs_1.default.hash(this.password, salt); 56 | }); 57 | }); 58 | exports.UserModel = (0, mongoose_1.model)('User', userSchema); 59 | -------------------------------------------------------------------------------- /server/dist/routes/food.route.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | var desc = Object.getOwnPropertyDescriptor(m, k); 5 | if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { 6 | desc = { enumerable: true, get: function() { return m[k]; } }; 7 | } 8 | Object.defineProperty(o, k2, desc); 9 | }) : (function(o, m, k, k2) { 10 | if (k2 === undefined) k2 = k; 11 | o[k2] = m[k]; 12 | })); 13 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 14 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 15 | }) : function(o, v) { 16 | o["default"] = v; 17 | }); 18 | var __importStar = (this && this.__importStar) || function (mod) { 19 | if (mod && mod.__esModule) return mod; 20 | var result = {}; 21 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 22 | __setModuleDefault(result, mod); 23 | return result; 24 | }; 25 | var __importDefault = (this && this.__importDefault) || function (mod) { 26 | return (mod && mod.__esModule) ? mod : { "default": mod }; 27 | }; 28 | Object.defineProperty(exports, "__esModule", { value: true }); 29 | exports.FoodRoute = void 0; 30 | const express_1 = require("express"); 31 | const food = __importStar(require("../controllers/food.controller")); 32 | const multer_1 = __importDefault(require("multer")); 33 | const storage = multer_1.default.memoryStorage(); 34 | const upload = (0, multer_1.default)({ 35 | storage, 36 | limits: { 37 | fileSize: 5 * 1024 * 1204 38 | } 39 | }); 40 | exports.FoodRoute = (0, express_1.Router)(); 41 | exports.FoodRoute.post('/create-food', upload.single("imageFile"), food.CreateProduct); 42 | exports.FoodRoute.get('/all-foodlist', food.GetAllProducts); 43 | exports.FoodRoute.get('/search/:searchTerm', food.SearchFood); 44 | exports.FoodRoute.get('/recent-food', food.GetRecentlyAdded); 45 | exports.FoodRoute.get('/veg-food', food.GetVegFoods); 46 | exports.FoodRoute.get('/nonveg-food', food.GetNonVegFoods); 47 | -------------------------------------------------------------------------------- /client/src/Layout/Header/UserProfile/UserProfile.tsx: -------------------------------------------------------------------------------- 1 | import { Button, buttonVariants } from "@/components/ui/button" 2 | import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuSeparator, DropdownMenuTrigger } from "@/components/ui/dropdown-menu" 3 | import { useAuthContext } from "@/context/AuthContext" 4 | import { cn } from "@/lib/utils" 5 | import { LogoutUserApi } from "@/services/api" 6 | import { useMutation, useQueryClient } from "@tanstack/react-query" 7 | import { CircleUser } from "lucide-react" 8 | import { Link } from "react-router-dom" 9 | import { toast } from "sonner" 10 | 11 | export default function UserProfile() { 12 | const queryClient = useQueryClient() 13 | const {isAuth, isAdmin} = useAuthContext() 14 | 15 | 16 | 17 | const {mutate, isPending} = useMutation({ 18 | mutationKey: ["logoutuser"], 19 | mutationFn: LogoutUserApi, 20 | onSuccess: (data) => { 21 | toast.success(data?.message) 22 | queryClient.invalidateQueries({ queryKey: ["authuser"] }); 23 | queryClient.invalidateQueries({ queryKey: ["cart-items"] }); 24 | localStorage.removeItem('checkwho') 25 | }, 26 | onError: (error) => { 27 | toast.error(error.message) 28 | } 29 | }) 30 | 31 | const LogoutUser = () => { 32 | mutate() 33 | } 34 | 35 | 36 | return ( 37 | <> 38 | {isAuth ? ( 39 | 40 | 41 | 44 | 45 | 46 | {isAdmin && ( 47 | 48 | Dashboard 49 | 50 | )} 51 | 52 | LogoutUser()} disabled={isPending}>Logout 53 | 54 | 55 | ) : ( 56 | 57 | Sign In 58 | 59 | )} 60 | 61 | ) 62 | } 63 | -------------------------------------------------------------------------------- /server/src/controllers/auth.controller.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response } from "express"; 2 | import { AsyncWrapper, CustomError, ErrorMessage, GenerateTokenAndCookie, HttpStatusCode, SuccessMessage } from "../utils"; 3 | import { UserModel, IUserModel } from "../models"; 4 | import bcrypt from "bcryptjs"; 5 | 6 | export const SignUpUser = AsyncWrapper(async (req: Request, res: Response) => { 7 | const { email, password, mobile, firstname, lastname } = req.body; 8 | 9 | const findUser: IUserModel | null = await UserModel.findOne({ email }); 10 | 11 | if (findUser) { 12 | throw new CustomError(ErrorMessage.USER_ALREADY_EXIST, HttpStatusCode.BAD_REQUEST); 13 | } 14 | 15 | const newUser = new UserModel({ 16 | email, 17 | password, 18 | mobile, 19 | firstname, 20 | lastname, 21 | }); 22 | const user = await newUser.save(); 23 | 24 | if (user) { 25 | GenerateTokenAndCookie(user._id, res); 26 | res.status(HttpStatusCode.OK).json({ message: SuccessMessage.USER_REGISTER_SUCCESS }); 27 | } 28 | }); 29 | 30 | export const SignInUser = AsyncWrapper(async (req: Request, res: Response) => { 31 | const { email, password } = req.body; 32 | 33 | const findUser: IUserModel | null = await UserModel.findOne({ email }); 34 | 35 | if (!findUser) { 36 | throw new CustomError(ErrorMessage.USER_NOT_FOUND, HttpStatusCode.BAD_REQUEST); 37 | } 38 | 39 | const comparePassword = await bcrypt.compare(password, findUser?.password); 40 | 41 | if (!comparePassword) { 42 | throw new CustomError(ErrorMessage.INVALID_PASSWORD, HttpStatusCode.BAD_REQUEST); 43 | } 44 | 45 | GenerateTokenAndCookie(findUser._id, res); 46 | res.status(HttpStatusCode.OK).json({ message: SuccessMessage.USER_LOGIN_SUCCESS, role: findUser.role }); 47 | }); 48 | 49 | export const SignOutUser = AsyncWrapper(async (req: Request, res: Response) => { 50 | res.cookie("foodZone", "", { 51 | expires: new Date(0), 52 | httpOnly: true, 53 | secure: false, 54 | maxAge: 0 55 | }) 56 | 57 | res.status(HttpStatusCode.OK).json({ message: SuccessMessage.USER_LOGOUT_SUCCESS }); 58 | }); 59 | 60 | export const VerifyUser = AsyncWrapper(async (req: Request, res: Response) => { 61 | res.status(200).send({ userId: req.userId }); 62 | }); 63 | -------------------------------------------------------------------------------- /client/src/components/Site/SiteProductCard/SiteProductCard.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { Card, CardContent, CardFooter, CardHeader } from "@/components/ui/card"; 3 | import starRating from "@/assets/rating_starts.png"; 4 | 5 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 6 | import { toast } from "sonner"; 7 | import { useAuthContext } from "@/context/AuthContext"; 8 | import { AddToCart } from "@/services/cart.api"; 9 | import { ProductProps } from "@/services/interface"; 10 | 11 | interface SiteProductCardProps { 12 | item: ProductProps 13 | } 14 | 15 | export default function SiteProductCard({item}: SiteProductCardProps) { 16 | const {isAuth} = useAuthContext() 17 | const queryClient = useQueryClient() 18 | const mutation = useMutation({ 19 | mutationKey: ["add-to-cart"], 20 | mutationFn: AddToCart, 21 | onSuccess: (data) => { 22 | queryClient.invalidateQueries({queryKey: ['cart-items']}) 23 | toast.success(data.message); 24 | }, 25 | onError: (error) => { 26 | toast.error(error.message); 27 | } 28 | }); 29 | 30 | const AddToCarts = () => { 31 | if(isAuth) { 32 | mutation.mutate(item._id) 33 | } else { 34 | toast.error("Please login to add to cart") 35 | } 36 | } 37 | 38 | 39 | return ( 40 | 41 | 42 | {item.name} 43 | 44 | 45 |

{item.name}

46 |
47 | 48 | {"rating"} 49 | 50 | 51 |

{item.description}

52 |
53 | 54 |

$ {item.price}

55 |

{item.category}

56 |
57 | 58 | 61 | 62 |
63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /server/dist/middleware/AuthMiddleware.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.AdminMiddleware = exports.AuthMiddleware = void 0; 16 | const jsonwebtoken_1 = __importDefault(require("jsonwebtoken")); 17 | const utils_1 = require("../utils"); 18 | const models_1 = require("../models"); 19 | exports.AuthMiddleware = (0, utils_1.AsyncWrapper)((req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 20 | const token = req.cookies['foodZone']; 21 | if (!token) { 22 | return res.status(utils_1.HttpStatusCode.UNAUTHORIZED).json({ message: utils_1.ErrorMessage.NOT_AUTHORIZED }); 23 | } 24 | const decodeToken = yield jsonwebtoken_1.default.verify(token, process.env.SECRET_KEY); 25 | if (!decodeToken) { 26 | return res.status(utils_1.HttpStatusCode.UNAUTHORIZED).json({ message: utils_1.ErrorMessage.NOT_AUTHORIZED }); 27 | } 28 | req.userId = decodeToken.userId; 29 | next(); 30 | })); 31 | const AdminMiddleware = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () { 32 | try { 33 | const findAdmin = yield models_1.UserModel.findById(req.userId); 34 | if ((findAdmin === null || findAdmin === void 0 ? void 0 : findAdmin.role) !== 'admin') { 35 | return res.status(401).json({ message: "Unauthorized" }); 36 | } 37 | next(); 38 | } 39 | catch (error) { 40 | return res.status(401).json({ message: 'Unauthorized' }); 41 | } 42 | }); 43 | exports.AdminMiddleware = AdminMiddleware; 44 | -------------------------------------------------------------------------------- /client/src/services/admin.api.ts: -------------------------------------------------------------------------------- 1 | import { base_url } from "./interface"; 2 | 3 | 4 | 5 | export const GetAllCustomersList = async () => { 6 | const response = await fetch(`${base_url}/admin/get-users`); 7 | 8 | const data = await response.json(); 9 | if (!response.ok) { 10 | throw new Error(data.message); 11 | } 12 | 13 | return data; 14 | }; 15 | 16 | 17 | 18 | export const GetAllProductList = async () => { 19 | const response = await fetch(`${base_url}/admin/get-products`); 20 | 21 | const data = await response.json(); 22 | if (!response.ok) { 23 | throw new Error(data.message); 24 | } 25 | 26 | return data; 27 | }; 28 | 29 | 30 | export const verifyAdminApi = async() => { 31 | const response = await fetch(`${base_url}/admin/verifyAdminApi`, { 32 | credentials: "include" 33 | }); 34 | 35 | const data = await response.json(); 36 | if (!response.ok) { 37 | throw new Error(data.message); 38 | } 39 | 40 | return data; 41 | } 42 | 43 | export const EnableDisable = async(id:string) => { 44 | const response = await fetch(`${base_url}/admin/enable-disable/${id}`, { 45 | method: 'PUT' 46 | }); 47 | 48 | const data = await response.json(); 49 | if (!response.ok) { 50 | throw new Error(data.message); 51 | } 52 | 53 | return data; 54 | } 55 | export const Deleteproduct = async(id:string) => { 56 | const response = await fetch(`${base_url}/admin/delete/${id}`, { 57 | method: 'Delete' 58 | }); 59 | 60 | const data = await response.json(); 61 | if (!response.ok) { 62 | throw new Error(data.message); 63 | } 64 | 65 | return data; 66 | } 67 | 68 | export const BlockUnBlockUser = async(id:string) => { 69 | const response = await fetch(`${base_url}/admin/block-unblock-user/${id}`, { 70 | method: 'PUT' 71 | }); 72 | 73 | const data = await response.json(); 74 | if (!response.ok) { 75 | throw new Error(data.message); 76 | } 77 | 78 | return data; 79 | } 80 | 81 | 82 | export const Deleteuser = async(id:string) => { 83 | const response = await fetch(`${base_url}/admin/user-delete/${id}`, { 84 | method: 'Delete' 85 | }); 86 | 87 | const data = await response.json(); 88 | if (!response.ok) { 89 | throw new Error(data.message); 90 | } 91 | 92 | return data; 93 | } 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /client/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | ], 10 | prefix: "", 11 | theme: { 12 | container: { 13 | center: true, 14 | padding: "2rem", 15 | screens: { 16 | "2xl": "1400px", 17 | }, 18 | }, 19 | extend: { 20 | backgroundImage: { 21 | 'hero-pattern': "url('/hero2.png')", 22 | }, 23 | colors: { 24 | border: "hsl(var(--border))", 25 | input: "hsl(var(--input))", 26 | ring: "hsl(var(--ring))", 27 | background: "hsl(var(--background))", 28 | foreground: "hsl(var(--foreground))", 29 | primary: { 30 | DEFAULT: "hsl(var(--primary))", 31 | foreground: "hsl(var(--primary-foreground))", 32 | }, 33 | secondary: { 34 | DEFAULT: "hsl(var(--secondary))", 35 | foreground: "hsl(var(--secondary-foreground))", 36 | }, 37 | destructive: { 38 | DEFAULT: "hsl(var(--destructive))", 39 | foreground: "hsl(var(--destructive-foreground))", 40 | }, 41 | muted: { 42 | DEFAULT: "hsl(var(--muted))", 43 | foreground: "hsl(var(--muted-foreground))", 44 | }, 45 | accent: { 46 | DEFAULT: "hsl(var(--accent))", 47 | foreground: "hsl(var(--accent-foreground))", 48 | }, 49 | popover: { 50 | DEFAULT: "hsl(var(--popover))", 51 | foreground: "hsl(var(--popover-foreground))", 52 | }, 53 | card: { 54 | DEFAULT: "hsl(var(--card))", 55 | foreground: "hsl(var(--card-foreground))", 56 | }, 57 | }, 58 | borderRadius: { 59 | lg: "var(--radius)", 60 | md: "calc(var(--radius) - 2px)", 61 | sm: "calc(var(--radius) - 4px)", 62 | }, 63 | keyframes: { 64 | "accordion-down": { 65 | from: { height: "0" }, 66 | to: { height: "var(--radix-accordion-content-height)" }, 67 | }, 68 | "accordion-up": { 69 | from: { height: "var(--radix-accordion-content-height)" }, 70 | to: { height: "0" }, 71 | }, 72 | }, 73 | animation: { 74 | "accordion-down": "accordion-down 0.2s ease-out", 75 | "accordion-up": "accordion-up 0.2s ease-out", 76 | }, 77 | }, 78 | }, 79 | plugins: [require("tailwindcss-animate")], 80 | } -------------------------------------------------------------------------------- /client/src/Layout/Header/CartSheet/UpdateCart.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import { Button } from "@/components/ui/button"; 3 | import { RemoveFromCartApi, UpdateCartApi } from "@/services/cart.api"; 4 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 5 | import { MinusIcon, PlusIcon, TrashIcon } from "lucide-react"; 6 | import { useState } from "react"; 7 | 8 | 9 | interface UpdateCartProps { 10 | count: number; 11 | productId: string; 12 | } 13 | 14 | 15 | export default function UpdateCart({count: initialCount, productId}: UpdateCartProps) { 16 | const [count, setCount] = useState(initialCount); 17 | const queryClient = useQueryClient() 18 | 19 | 20 | const { mutate } = useMutation({ 21 | mutationFn: (newCount: number) => UpdateCartApi(productId, newCount), 22 | onSuccess: () => { 23 | queryClient.invalidateQueries({ queryKey: ['cart-items'] }); 24 | }, 25 | }); 26 | 27 | const { mutate: RemovefromCart } = useMutation({ 28 | mutationFn: (productId:string) => RemoveFromCartApi(productId), 29 | onSuccess: () => { 30 | queryClient.invalidateQueries({ queryKey: ['cart-items'] }); 31 | }, 32 | }); 33 | 34 | const handleRemovefromCart = () => { 35 | RemovefromCart(productId) 36 | } 37 | 38 | 39 | const handleDecrement = () => { 40 | if (count > 1) { 41 | const newCount = count - 1; 42 | setCount(newCount); 43 | mutate(newCount); 44 | } 45 | }; 46 | 47 | const handleIncrement = () => { 48 | const newCount = count + 1; 49 | setCount(newCount); 50 | mutate(newCount); 51 | }; 52 | 53 | return ( 54 |
55 |
56 | 64 | {count} 65 | 73 |
74 | 77 |
78 | ); 79 | } 80 | -------------------------------------------------------------------------------- /client/src/components/Forms/CheckoutForm.tsx: -------------------------------------------------------------------------------- 1 | import { FormEvent, useEffect, useState } from "react"; 2 | import { PaymentElement, useStripe, useElements } from "@stripe/react-stripe-js"; 3 | import { Button } from "@/components/ui/button"; 4 | import { toast } from "sonner"; 5 | 6 | // refernece : 7 | //https://docs.stripe.com/payments/quickstart?lang=node&client=react 8 | 9 | 10 | export default function CheckoutForm() { 11 | const stripe = useStripe(); 12 | const elements = useElements(); 13 | 14 | const [message, setMessage] = useState(""); 15 | const [isLoading, setIsLoading] = useState(false); 16 | 17 | useEffect(() => { 18 | if (!stripe) { 19 | return; 20 | } 21 | 22 | const clientSecret = new URLSearchParams(window.location.search).get("payment_intent_client_secret"); 23 | 24 | if (!clientSecret) { 25 | return; 26 | } 27 | 28 | stripe.retrievePaymentIntent(clientSecret).then(({ paymentIntent }) => { 29 | switch (paymentIntent?.status) { 30 | case "succeeded": 31 | setMessage("Payment succeeded!"); 32 | break; 33 | case "processing": 34 | setMessage("Your payment is processing."); 35 | break; 36 | case "requires_payment_method": 37 | setMessage("Your payment was not successful, please try again."); 38 | break; 39 | default: 40 | setMessage("Something went wrong."); 41 | break; 42 | } 43 | }); 44 | }, [stripe]); 45 | 46 | const handleSubmit = async (e:FormEvent) => { 47 | e.preventDefault(); 48 | 49 | if (!stripe || !elements) { 50 | return; 51 | } 52 | 53 | setIsLoading(true); 54 | 55 | const { error } = await stripe.confirmPayment({ 56 | elements, 57 | confirmParams: { 58 | return_url: "https://foodzonenewapp.netlify.app", 59 | } 60 | }); 61 | 62 | 63 | 64 | if (error.type === "card_error" || error.type === "validation_error") { 65 | setMessage(error.message); 66 | } else { 67 | toast.error("Payment failed. Please again later.") 68 | setMessage("An unexpected error occurred."); 69 | } 70 | 71 | setIsLoading(false); 72 | 73 | }; 74 | 75 | 76 | 77 | return ( 78 |
79 |
80 |

Payment Method

81 | 82 | 85 | {message &&
{message}
} 86 | 87 |
88 | ); 89 | } 90 | -------------------------------------------------------------------------------- /client/src/Layout/SiteFooter/SiteTopFooter/SiteTopFooter.tsx: -------------------------------------------------------------------------------- 1 | import CustomToolTip from "@/components/common/CustomToolTip"; 2 | import { FacebookIcon, InstagramIcon, Mail, MapPin, Phone, Twitter } from "lucide-react"; 3 | 4 | const socialLinks = [ 5 | { 6 | title: "Instagram", 7 | icon: , 8 | }, 9 | { 10 | title: "Twitter", 11 | icon: , 12 | }, 13 | { 14 | title: "Facebook", 15 | icon: , 16 | }, 17 | ]; 18 | 19 | export default function SiteTopFooter() { 20 | return ( 21 |
22 |
23 |
24 |

Have a Questions?

25 |

26 | Building No: 100 Near villa Beach,
Mangalore, Karnataka{" "} 27 |
pincode: 292929 28 |

29 |

30 | +91 9867231105 31 |

32 |

33 | FoodZone@gmail.com 34 |

35 |
36 | {socialLinks?.map((social) => ( 37 | 38 | {social.icon} 39 | 40 | ))} 41 |
42 |
43 | 44 |
45 |

Menu

46 |
    47 |
  • Pure Veg
  • 48 |
  • Non Veg
  • 49 |
  • Pasta
  • 50 |
  • Rolls
  • 51 |
  • Burger
  • 52 |
  • Noodles
  • 53 |
  • Deserts
  • 54 |
  • Sandwich
  • 55 |
56 |
57 | 58 |
59 |

About

60 |
    61 |
  • FAQ
  • 62 |
  • Contact
  • 63 |
64 |
65 | 66 |
67 |

Legal

68 |
    69 |
  • Disclaimer
  • 70 |
  • Terms & Conditions
  • 71 |
  • Privacy Policy
  • 72 |
73 |
74 |
75 |
76 | ); 77 | } 78 | -------------------------------------------------------------------------------- /client/src/components/Forms/SigninForm.tsx: -------------------------------------------------------------------------------- 1 | import { SigninSchema } from '@/utils/Schema' 2 | import { zodResolver } from '@hookform/resolvers/zod' 3 | import {useForm} from 'react-hook-form' 4 | import {z} from 'zod' 5 | import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '../ui/form' 6 | import { Input } from '../ui/input' 7 | import { Button } from '../ui/button' 8 | import { SigninUserApi } from '@/services/api' 9 | import { useMutation, useQueryClient } from '@tanstack/react-query' 10 | import { toast } from 'sonner' 11 | import { useNavigate } from 'react-router-dom' 12 | 13 | export default function SigninForm() { 14 | const navigate = useNavigate() 15 | const queryClient = useQueryClient(); 16 | const form = useForm>({resolver: zodResolver(SigninSchema), defaultValues: { 17 | email: '', 18 | password: '' 19 | }}) 20 | 21 | 22 | const {mutate, isPending} = useMutation({ 23 | mutationKey: ['signin'], 24 | mutationFn: SigninUserApi, 25 | onSuccess: (data) => { 26 | toast.success(data?.message) 27 | if (data.role === "admin") { 28 | const store = { 29 | value: true, 30 | }; 31 | localStorage.setItem("checkwho", JSON.stringify(store)); 32 | } 33 | }, 34 | onError: (error) => { 35 | toast.error(error.message) 36 | }, 37 | onSettled: () => { 38 | queryClient.invalidateQueries({ queryKey: ["authuser"] }); 39 | queryClient.invalidateQueries({ queryKey: ["cart-items"] }); 40 | queryClient.invalidateQueries({ queryKey: ["authadmin"] }); 41 | navigate('/') 42 | } 43 | }) 44 | 45 | const onSubmit = (formData: z.infer) => { 46 | mutate(formData) 47 | } 48 | 49 | 50 | 51 | return ( 52 |
53 | 54 | ( 55 | 56 | Email 57 | 58 | 59 | 60 | 61 | 62 | )}/> 63 | ( 64 | 65 | Password 66 | 67 | 68 | 69 | 70 | 71 | )}/> 72 | 75 | 76 | 77 | ) 78 | } 79 | -------------------------------------------------------------------------------- /server/src/controllers/food.controller.ts: -------------------------------------------------------------------------------- 1 | import {Request, Response} from 'express' 2 | import { AsyncWrapper, ErrorMessage, HttpStatusCode } from '../utils' 3 | import { FoodModel } from '../models' 4 | import {v2 as cloudinary} from 'cloudinary' 5 | 6 | 7 | /* Create product */ 8 | 9 | export const CreateProduct = AsyncWrapper(async(req:Request, res:Response) => { 10 | const {name, description, price, category, discount, ingredients, starRating} = req.body 11 | 12 | const findFood = await FoodModel.findOne({name}) 13 | 14 | if(findFood) { 15 | return res.status(400).json({message: ErrorMessage.PRODUCT_ALREADY_EXIST}) 16 | } 17 | const imageUrl = await uploadImage(req.file as Express.Multer.File); 18 | 19 | const newFood = new FoodModel({name, description, price, category, discount, starRating, ingredients, image:imageUrl}) 20 | 21 | await newFood.save() 22 | res.status(HttpStatusCode.OK).json({message: `${newFood.name} added Successfully`}) 23 | 24 | }) 25 | 26 | /* all product list */ 27 | 28 | 29 | export const GetAllProducts = AsyncWrapper(async(req:Request, res:Response) => { 30 | const foods = await FoodModel.find({}) 31 | 32 | if(foods.length === 0) { 33 | return res.status(HttpStatusCode.OK).json({message: "Products have not been added yet"}) 34 | } 35 | 36 | return res.status(HttpStatusCode.OK).json(foods) 37 | }) 38 | 39 | 40 | /* search product */ 41 | 42 | export const SearchFood = AsyncWrapper(async(req:Request, res:Response) => { 43 | const searchRegex = new RegExp(req.params.searchTerm, 'i') 44 | const foodlist = await FoodModel.find({name: {$regex: searchRegex}}) 45 | return res.status(HttpStatusCode.OK).json(foodlist) 46 | }) 47 | 48 | /* veg food product */ 49 | 50 | 51 | export const GetVegFoods = AsyncWrapper(async(req:Request, res:Response) => { 52 | const vegfoods = await FoodModel.find({vegetarian: true}).limit(8) 53 | return res.status(HttpStatusCode.OK).json(vegfoods) 54 | }) 55 | 56 | /* non veg product */ 57 | 58 | export const GetNonVegFoods = AsyncWrapper(async(req:Request, res:Response) => { 59 | const Nonvegfoods = await FoodModel.find({vegetarian: false}).limit(8) 60 | return res.status(HttpStatusCode.OK).json(Nonvegfoods) 61 | }) 62 | 63 | 64 | /* recently added */ 65 | 66 | export const GetRecentlyAdded = AsyncWrapper(async(req:Request, res:Response) => { 67 | const foods = await FoodModel.find({}).sort({createdAt: -1}).limit(8) 68 | return res.status(HttpStatusCode.OK).json(foods) 69 | }) 70 | 71 | 72 | const uploadImage = async(file:Express.Multer.File) => { 73 | const image = file; 74 | const base64Image = Buffer.from(image.buffer).toString("base64") 75 | const dataUri = `data:${image.mimetype};base64,${base64Image}` 76 | 77 | const uploadResponse = await cloudinary.uploader.upload(dataUri, {folder: 'foodZone'}) 78 | return uploadResponse.url 79 | } 80 | 81 | 82 | -------------------------------------------------------------------------------- /server/src/controllers/admin.controller.ts: -------------------------------------------------------------------------------- 1 | import { FoodModel, UserModel } from "../models"; 2 | import { AsyncWrapper, ErrorMessage } from "../utils"; 3 | import { Request, Response } from "express"; 4 | import { v2 as cloudinary } from "cloudinary"; 5 | 6 | export const GetAllCustomersList = AsyncWrapper(async (req: Request, res: Response) => { 7 | const users = await UserModel.find(); 8 | return res.status(200).json(users); 9 | }); 10 | 11 | export const GetAllProductsList = AsyncWrapper(async (req: Request, res: Response) => { 12 | const products = await FoodModel.find(); 13 | return res.status(200).json(products); 14 | }); 15 | 16 | export const VerifyAdmin = AsyncWrapper(async (req: Request, res: Response) => { 17 | res.status(200).send({ userId: req.userId }); 18 | }); 19 | 20 | export const EnableDisableProduct = AsyncWrapper(async (req: Request, res: Response) => { 21 | const { productId } = req.params; 22 | const product = await FoodModel.findById(productId); 23 | if (!product) { 24 | return res.status(400).json({ message: ErrorMessage.PRODUCT_NOT_FOUND }); 25 | } 26 | 27 | if (product.available === true) { 28 | await FoodModel.findByIdAndUpdate(productId, { 29 | available: false, 30 | }); 31 | return res.status(200).json({ message: "Product disabled" }); 32 | } else { 33 | await FoodModel.findByIdAndUpdate(productId, { 34 | available: true, 35 | }); 36 | return res.status(200).json({ message: "Product enabled" }); 37 | } 38 | }); 39 | 40 | export const DeleteProduct = AsyncWrapper(async (req: Request, res: Response) => { 41 | const { productId } = req.params; 42 | const product = await FoodModel.findById(productId); 43 | if (!product) { 44 | return res.status(400).json({ message: ErrorMessage.PRODUCT_NOT_FOUND }); 45 | } 46 | 47 | if (product.image) { 48 | const imageId = product.image.split("/").pop()?.split(".")[0] as string; 49 | await cloudinary.uploader.destroy(imageId); 50 | } 51 | 52 | await FoodModel.findByIdAndDelete(productId); 53 | return res.status(200).json({ message: "Product deleted" }); 54 | }); 55 | 56 | 57 | export const DeleteUser = AsyncWrapper(async (req: Request, res: Response) => { 58 | const { userId } = req.params; 59 | 60 | const user = await UserModel.findByIdAndDelete(userId); 61 | return res.status(200).json({ message: "Product deleted" }); 62 | }); 63 | 64 | 65 | export const BlockUnBlockUser = AsyncWrapper(async (req: Request, res: Response) => { 66 | const { userId } = req.params; 67 | 68 | const user = await UserModel.findById(userId); 69 | 70 | if(!user) { 71 | return res.status(400).json({ message: ErrorMessage.USER_NOT_FOUND }); 72 | } 73 | 74 | if (user.blocked === true) { 75 | await UserModel.findByIdAndUpdate(userId, { 76 | blocked: false, 77 | }); 78 | return res.status(200).json({ message: "User Blocked" }); 79 | } else { 80 | await UserModel.findByIdAndUpdate(userId, { 81 | blocked: true, 82 | }); 83 | return res.status(200).json({ message: "User UnBlocked" }); 84 | } 85 | }); -------------------------------------------------------------------------------- /client/src/Layout/Header/SearchCommand/SearchCommand.tsx: -------------------------------------------------------------------------------- 1 | import { Button } from "@/components/ui/button"; 2 | import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList } from "@/components/ui/command"; 3 | import { Skeleton } from "@/components/ui/skeleton"; 4 | import useDebounce from "@/hooks/useDebounce"; 5 | import { cn } from "@/lib/utils"; 6 | import { SearchApi } from "@/services/food.api"; 7 | import { ProductProps } from "@/services/interface"; 8 | import { useQuery } from "@tanstack/react-query"; 9 | import { SearchIcon } from "lucide-react"; 10 | import { useEffect, useState } from "react"; 11 | 12 | 13 | export default function SearchCommand() { 14 | const [open, setOpen] = useState(false); 15 | const [query, setQuery] = useState(""); 16 | const debounceValue = useDebounce(query, 500) 17 | 18 | const {data: searchData, isLoading:loading} = useQuery({ 19 | queryKey: ['search', debounceValue], 20 | queryFn: () => SearchApi(debounceValue), 21 | enabled: !!debounceValue 22 | }) 23 | 24 | useEffect(() => { 25 | const down = (e: KeyboardEvent) => { 26 | if (e.key === "k" && (e.metaKey || e.ctrlKey)) { 27 | e.preventDefault(); 28 | setOpen((open) => !open); 29 | } 30 | }; 31 | document.addEventListener("keydown", down); 32 | return () => document.removeEventListener("keydown", down); 33 | }, []); 34 | 35 | return ( 36 | <> 37 | 47 | { 50 | setOpen(open); 51 | if (!open) { 52 | setQuery(""); 53 | } 54 | }} 55 | > 56 | 57 | 58 | 61 | No results found. 62 | 63 | {loading ? ( 64 |
65 | 66 | 67 | 68 |
69 | ) : ( 70 | 71 | {searchData?.map((group:ProductProps) => ( 72 | 73 | {group.name} 74 | 75 | {group?.name} 76 | 77 | $ {group?.price} 78 | 79 | ))} 80 | 81 | )} 82 |
83 |
84 | 85 | ) 86 | } 87 | -------------------------------------------------------------------------------- /server/dist/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | exports.VerifyUser = exports.SignOutUser = exports.SignInUser = exports.SignUpUser = void 0; 16 | const utils_1 = require("../utils"); 17 | const models_1 = require("../models"); 18 | const bcryptjs_1 = __importDefault(require("bcryptjs")); 19 | exports.SignUpUser = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 20 | const { email, password, mobile, firstname, lastname } = req.body; 21 | const findUser = yield models_1.UserModel.findOne({ email }); 22 | if (findUser) { 23 | throw new utils_1.CustomError(utils_1.ErrorMessage.USER_ALREADY_EXIST, utils_1.HttpStatusCode.BAD_REQUEST); 24 | } 25 | const newUser = new models_1.UserModel({ 26 | email, 27 | password, 28 | mobile, 29 | firstname, 30 | lastname, 31 | }); 32 | const user = yield newUser.save(); 33 | if (user) { 34 | (0, utils_1.GenerateTokenAndCookie)(user._id, res); 35 | res.status(utils_1.HttpStatusCode.OK).json({ message: utils_1.SuccessMessage.USER_REGISTER_SUCCESS }); 36 | } 37 | })); 38 | exports.SignInUser = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 39 | const { email, password } = req.body; 40 | const findUser = yield models_1.UserModel.findOne({ email }); 41 | if (!findUser) { 42 | throw new utils_1.CustomError(utils_1.ErrorMessage.USER_NOT_FOUND, utils_1.HttpStatusCode.BAD_REQUEST); 43 | } 44 | const comparePassword = yield bcryptjs_1.default.compare(password, findUser === null || findUser === void 0 ? void 0 : findUser.password); 45 | if (!comparePassword) { 46 | throw new utils_1.CustomError(utils_1.ErrorMessage.INVALID_PASSWORD, utils_1.HttpStatusCode.BAD_REQUEST); 47 | } 48 | (0, utils_1.GenerateTokenAndCookie)(findUser._id, res); 49 | res.status(utils_1.HttpStatusCode.OK).json({ message: utils_1.SuccessMessage.USER_LOGIN_SUCCESS, role: findUser.role }); 50 | })); 51 | exports.SignOutUser = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 52 | res.cookie("foodZone", "", { 53 | expires: new Date(0), 54 | httpOnly: true, 55 | secure: false, 56 | maxAge: 0 57 | }); 58 | res.status(utils_1.HttpStatusCode.OK).json({ message: utils_1.SuccessMessage.USER_LOGOUT_SUCCESS }); 59 | })); 60 | exports.VerifyUser = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 61 | res.status(200).send({ userId: req.userId }); 62 | })); 63 | -------------------------------------------------------------------------------- /client/src/components/ui/table.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Table = React.forwardRef< 6 | HTMLTableElement, 7 | React.HTMLAttributes 8 | >(({ className, ...props }, ref) => ( 9 |
10 | 15 | 16 | )) 17 | Table.displayName = "Table" 18 | 19 | const TableHeader = React.forwardRef< 20 | HTMLTableSectionElement, 21 | React.HTMLAttributes 22 | >(({ className, ...props }, ref) => ( 23 | 24 | )) 25 | TableHeader.displayName = "TableHeader" 26 | 27 | const TableBody = React.forwardRef< 28 | HTMLTableSectionElement, 29 | React.HTMLAttributes 30 | >(({ className, ...props }, ref) => ( 31 | 36 | )) 37 | TableBody.displayName = "TableBody" 38 | 39 | const TableFooter = React.forwardRef< 40 | HTMLTableSectionElement, 41 | React.HTMLAttributes 42 | >(({ className, ...props }, ref) => ( 43 | tr]:last:border-b-0", 47 | className 48 | )} 49 | {...props} 50 | /> 51 | )) 52 | TableFooter.displayName = "TableFooter" 53 | 54 | const TableRow = React.forwardRef< 55 | HTMLTableRowElement, 56 | React.HTMLAttributes 57 | >(({ className, ...props }, ref) => ( 58 | 66 | )) 67 | TableRow.displayName = "TableRow" 68 | 69 | const TableHead = React.forwardRef< 70 | HTMLTableCellElement, 71 | React.ThHTMLAttributes 72 | >(({ className, ...props }, ref) => ( 73 |
[role=checkbox]]:translate-y-[2px]", 77 | className 78 | )} 79 | {...props} 80 | /> 81 | )) 82 | TableHead.displayName = "TableHead" 83 | 84 | const TableCell = React.forwardRef< 85 | HTMLTableCellElement, 86 | React.TdHTMLAttributes 87 | >(({ className, ...props }, ref) => ( 88 | [role=checkbox]]:translate-y-[2px]", 92 | className 93 | )} 94 | {...props} 95 | /> 96 | )) 97 | TableCell.displayName = "TableCell" 98 | 99 | const TableCaption = React.forwardRef< 100 | HTMLTableCaptionElement, 101 | React.HTMLAttributes 102 | >(({ className, ...props }, ref) => ( 103 |
108 | )) 109 | TableCaption.displayName = "TableCaption" 110 | 111 | export { 112 | Table, 113 | TableHeader, 114 | TableBody, 115 | TableFooter, 116 | TableHead, 117 | TableRow, 118 | TableCell, 119 | TableCaption, 120 | } 121 | -------------------------------------------------------------------------------- /client/src/Layout/Header/CartSheet/CartSheet.tsx: -------------------------------------------------------------------------------- 1 | import { Badge } from "@/components/ui/badge"; 2 | import { Button, buttonVariants } from "@/components/ui/button"; 3 | import { Sheet, SheetContent, SheetFooter, SheetHeader, SheetTitle, SheetTrigger } from "@/components/ui/sheet"; 4 | import { cn } from "@/lib/utils"; 5 | import { Separator } from "@radix-ui/react-dropdown-menu"; 6 | import { useQuery } from "@tanstack/react-query"; 7 | import { ShoppingCartIcon } from "lucide-react"; 8 | import { Link } from "react-router-dom"; 9 | import CartProducts from "./CartProducts"; 10 | import { GetUserCartItems } from "@/services/cart.api"; 11 | 12 | export default function CartSheet() { 13 | 14 | const {data} = useQuery({ 15 | queryKey: ['cart-items'], 16 | queryFn: GetUserCartItems 17 | }) 18 | const ItemCount = data ? data?.products?.length : 0 19 | 20 | return ( 21 | 22 | 23 | 29 | 30 | 31 | 32 | Cart {data && (`${ItemCount}`)} 33 | 34 | 35 | {data && ItemCount > 0 ? ( 36 | <> 37 | 38 |
39 | 40 |
41 |
42 | Shipping 43 | Free 44 |
45 |
46 | Taxes 47 | Calculated at checkout 48 |
49 |
50 | Total 51 | $ {data.cartTotal} 52 |
53 |
54 | 55 | 56 | 63 | View Cart Page 64 | 65 | 66 | 67 |
68 | 69 | ): ( 70 |
71 |
94 | )} 95 |
96 |
97 | ); 98 | } 99 | -------------------------------------------------------------------------------- /client/src/components/Forms/SignupForm.tsx: -------------------------------------------------------------------------------- 1 | import { SignupSchema } from "@/utils/Schema"; 2 | import { Button } from "../ui/button"; 3 | import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "../ui/form"; 4 | import { Input } from "../ui/input"; 5 | import { zodResolver } from "@hookform/resolvers/zod"; 6 | import {useForm} from 'react-hook-form' 7 | import {z} from 'zod' 8 | import { useMutation, useQueryClient } from "@tanstack/react-query"; 9 | import { SignupUserApi } from "@/services/api"; 10 | import { toast } from "sonner"; 11 | import { useNavigate } from "react-router-dom"; 12 | 13 | export default function SignupForm() { 14 | const queryClient = useQueryClient(); 15 | const navigate = useNavigate() 16 | const form = useForm>({resolver: zodResolver(SignupSchema), defaultValues: { 17 | email: '', 18 | firstname: '', 19 | lastname: '', 20 | password: '', 21 | mobile: '', 22 | }}) 23 | 24 | const {mutate, isPending} = useMutation({ 25 | mutationKey: ['signup'], 26 | mutationFn: SignupUserApi, 27 | onSuccess: (data) => { 28 | toast.success(data.message) 29 | queryClient.invalidateQueries({ queryKey: ["authuser"]}); 30 | queryClient.invalidateQueries({ queryKey: ["authadmin"] }); 31 | navigate('/') 32 | }, 33 | onError: (error) => { 34 | toast.error(error.message) 35 | } 36 | }) 37 | 38 | const onSubmit = (formData: z.infer) => { 39 | mutate(formData) 40 | } 41 | 42 | 43 | return ( 44 |
45 | 46 | ( 47 | 48 | Email 49 | 50 | 51 | 52 | 53 | 54 | )}/> 55 | ( 56 | 57 | Firstname 58 | 59 | 60 | 61 | 62 | 63 | )}/> 64 | ( 65 | 66 | Lastname 67 | 68 | 69 | 70 | 71 | 72 | )}/> 73 | ( 74 | 75 | Password 76 | 77 | 78 | 79 | 80 | 81 | )}/> 82 | ( 83 | 84 | Mobile 85 | 86 | 87 | 88 | 89 | 90 | )}/> 91 | 94 | 95 | 96 | ) 97 | } 98 | -------------------------------------------------------------------------------- /server/src/controllers/cart.controller.ts: -------------------------------------------------------------------------------- 1 | import { ErrorMessage, SuccessMessage } from './../utils/helper'; 2 | import { CartModel, FoodModel } from "../models"; 3 | import { AsyncWrapper, HttpStatusCode } from "../utils"; 4 | import { Request, Response } from "express"; 5 | 6 | export const GetAllCartItems = AsyncWrapper(async (req: Request, res: Response) => { 7 | const cart = await CartModel.findOne({ orderBy: req.userId }).populate("products.product"); 8 | res.status(HttpStatusCode.OK).json(cart); 9 | }); 10 | 11 | export const CreateUserCart = AsyncWrapper(async (req: Request, res: Response) => { 12 | const { productId, count } = req.body; 13 | 14 | const product = await FoodModel.findById(productId); 15 | 16 | if (!product) { 17 | return res.status(HttpStatusCode.BAD_REQUEST).json({ message: ErrorMessage.PRODUCT_NOT_FOUND }); 18 | } 19 | 20 | const cart = await CartModel.findOne({ orderBy: req.userId }); 21 | if (!cart) { 22 | let total = 0; 23 | total = total + product.price * count; 24 | const newCart = await CartModel.create({ 25 | orderBy: req.userId, 26 | products: [{ product: productId, count, price: product.price }], 27 | cartTotal: total, 28 | }); 29 | 30 | return res.status(HttpStatusCode.CREATED).json(newCart); 31 | } 32 | 33 | const index = cart.products.findIndex((p) => p?.product?.toString() === productId); 34 | if (index > -1) { 35 | cart.products[index].count += count; 36 | cart.cartTotal! += product.price * count; 37 | } else { 38 | cart.products.push({ 39 | product: productId, 40 | count, 41 | price: product.price, 42 | }); 43 | cart.cartTotal! += product.price * count; 44 | } 45 | 46 | await cart.save(); 47 | res.json({ message: SuccessMessage.PRODUCT_ADDED_SUCCESSFULLY }); 48 | }); 49 | 50 | export const RemoveFromCart = AsyncWrapper(async (req: Request, res: Response) => { 51 | const { productId } = req.params; 52 | 53 | const product = await FoodModel.findById(productId); 54 | 55 | if (!product) { 56 | return res.status(HttpStatusCode.BAD_REQUEST).json({ message: ErrorMessage.PRODUCT_NOT_FOUND }); 57 | } 58 | 59 | let cart = await CartModel.findOne({ orderBy: req.userId }); 60 | 61 | if (!cart) { 62 | return res.json({ message: ErrorMessage.PRODUCT_NOT_FOUND }); 63 | } 64 | const index = cart.products.findIndex((p) => p?.product?.toString() === productId); 65 | 66 | if (index !== -1) { 67 | cart.products.splice(index, 1); 68 | cart.cartTotal! -= product.price; 69 | await cart.save(); 70 | res.status(HttpStatusCode.OK).json({ message: SuccessMessage.PRODUCT_DELETED_SUCCESSFULLY }); 71 | } 72 | }); 73 | 74 | export const UpdateCartItem = AsyncWrapper(async (req: Request, res: Response) => { 75 | const { productId } = req.params; 76 | const { count } = req.body; 77 | 78 | const product = await FoodModel.findById(productId); 79 | 80 | if (!product) { 81 | return res.status(HttpStatusCode.BAD_REQUEST).json({ message: ErrorMessage.PRODUCT_NOT_FOUND }); 82 | } 83 | 84 | if (count <= 0) { 85 | return res.status(HttpStatusCode.BAD_REQUEST).json({ message: ErrorMessage.PRODUCT_COUNT }); 86 | } 87 | 88 | const cart = await CartModel.findOne({ orderBy: req.userId }); 89 | 90 | if (cart) { 91 | const item = cart.products.find((p) => p?.product?.toString() === productId); 92 | 93 | if (item) { 94 | const oldPrice = item.price; 95 | 96 | item.count = count; 97 | item.price! = product.price * count; 98 | cart.cartTotal! = cart.cartTotal! - oldPrice! + item.price; 99 | await cart.save(); 100 | res.status(HttpStatusCode.OK).json({ message: SuccessMessage.PRODUCT_UPDATED_SUCCESSFULLY}); 101 | } 102 | } 103 | }); 104 | -------------------------------------------------------------------------------- /client/src/pages/Cartpage/Cartpage.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { Button, buttonVariants } from "@/components/ui/button"; 3 | import { Input } from "@/components/ui/input"; 4 | import { ScrollArea } from "@/components/ui/scroll-area"; 5 | import { useAuthContext } from "@/context/AuthContext"; 6 | import UpdateCart from "@/Layout/Header/CartSheet/UpdateCart"; 7 | import { cn } from "@/lib/utils"; 8 | import { CartItemProps } from "@/services/interface"; 9 | import { useQuery } from "@tanstack/react-query"; 10 | import { Link } from "react-router-dom"; 11 | 12 | export default function Cartpage() { 13 | const { isAuth } = useAuthContext(); 14 | const { data } = useQuery({queryKey: ["cart-items"]}); 15 | 16 | 17 | 18 | return ( 19 |
20 |
21 |
22 | Shopping Cart 23 | Cart Item {data?.products.length} 24 |
25 | 26 |
27 | Product Details 28 | Quantity 29 | Price 30 | Total 31 |
32 | {data?.products.map((product) => ( 33 |
34 |
35 | {product.product.name} 36 |
37 | {product.product.name} 38 | {product.product.category} 39 |
40 |
41 |
42 |
43 | 44 |
45 |
46 |
47 | $ {product.product.price} 48 |
49 |
50 | $ {data.cartTotal} 51 |
52 |
53 | ))} 54 |
55 |
56 | 57 |
58 |
59 |

Order Summary

60 |
61 |

Cart Total

62 | $ {data?.cartTotal} 63 |
64 |
65 | 66 |
67 |

Promo Code

68 | 69 | 70 |
71 | 72 |
73 |
74 | Total Cost 75 | $ {data?.cartTotal} 76 |
77 | {isAuth ? ( 78 | 82 | Checkout 83 | 84 | ) : ( 85 | 89 | Please Login 90 | 91 | )} 92 |
93 |
94 |
95 | ); 96 | } 97 | -------------------------------------------------------------------------------- /server/dist/controllers/food.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.GetRecentlyAdded = exports.GetNonVegFoods = exports.GetVegFoods = exports.SearchFood = exports.GetAllProducts = exports.CreateProduct = void 0; 13 | const utils_1 = require("../utils"); 14 | const models_1 = require("../models"); 15 | const cloudinary_1 = require("cloudinary"); 16 | /* Create product */ 17 | exports.CreateProduct = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 18 | const { name, description, price, category, discount, ingredients, starRating } = req.body; 19 | const findFood = yield models_1.FoodModel.findOne({ name }); 20 | if (findFood) { 21 | return res.status(400).json({ message: utils_1.ErrorMessage.PRODUCT_ALREADY_EXIST }); 22 | } 23 | const imageUrl = yield uploadImage(req.file); 24 | const newFood = new models_1.FoodModel({ name, description, price, category, discount, starRating, ingredients, image: imageUrl }); 25 | yield newFood.save(); 26 | res.status(utils_1.HttpStatusCode.OK).json({ message: `${newFood.name} added Successfully` }); 27 | })); 28 | /* all product list */ 29 | exports.GetAllProducts = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 30 | const foods = yield models_1.FoodModel.find({}); 31 | if (foods.length === 0) { 32 | return res.status(utils_1.HttpStatusCode.OK).json({ message: "Products have not been added yet" }); 33 | } 34 | return res.status(utils_1.HttpStatusCode.OK).json(foods); 35 | })); 36 | /* search product */ 37 | exports.SearchFood = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 38 | const searchRegex = new RegExp(req.params.searchTerm, 'i'); 39 | const foodlist = yield models_1.FoodModel.find({ name: { $regex: searchRegex } }); 40 | return res.status(utils_1.HttpStatusCode.OK).json(foodlist); 41 | })); 42 | /* veg food product */ 43 | exports.GetVegFoods = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 44 | const vegfoods = yield models_1.FoodModel.find({ vegetarian: true }).limit(8); 45 | return res.status(utils_1.HttpStatusCode.OK).json(vegfoods); 46 | })); 47 | /* non veg product */ 48 | exports.GetNonVegFoods = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 49 | const Nonvegfoods = yield models_1.FoodModel.find({ vegetarian: false }).limit(8); 50 | return res.status(utils_1.HttpStatusCode.OK).json(Nonvegfoods); 51 | })); 52 | /* recently added */ 53 | exports.GetRecentlyAdded = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 54 | const foods = yield models_1.FoodModel.find({}).sort({ createdAt: -1 }).limit(8); 55 | return res.status(utils_1.HttpStatusCode.OK).json(foods); 56 | })); 57 | const uploadImage = (file) => __awaiter(void 0, void 0, void 0, function* () { 58 | const image = file; 59 | const base64Image = Buffer.from(image.buffer).toString("base64"); 60 | const dataUri = `data:${image.mimetype};base64,${base64Image}`; 61 | const uploadResponse = yield cloudinary_1.v2.uploader.upload(dataUri, { folder: 'foodZone' }); 62 | return uploadResponse.url; 63 | }); 64 | -------------------------------------------------------------------------------- /client/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /server/dist/controllers/admin.controller.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | exports.BlockUnBlockUser = exports.DeleteUser = exports.DeleteProduct = exports.EnableDisableProduct = exports.VerifyAdmin = exports.GetAllProductsList = exports.GetAllCustomersList = void 0; 13 | const models_1 = require("../models"); 14 | const utils_1 = require("../utils"); 15 | const cloudinary_1 = require("cloudinary"); 16 | exports.GetAllCustomersList = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 17 | const users = yield models_1.UserModel.find(); 18 | return res.status(200).json(users); 19 | })); 20 | exports.GetAllProductsList = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 21 | const products = yield models_1.FoodModel.find(); 22 | return res.status(200).json(products); 23 | })); 24 | exports.VerifyAdmin = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 25 | res.status(200).send({ userId: req.userId }); 26 | })); 27 | exports.EnableDisableProduct = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 28 | const { productId } = req.params; 29 | const product = yield models_1.FoodModel.findById(productId); 30 | if (!product) { 31 | return res.status(400).json({ message: utils_1.ErrorMessage.PRODUCT_NOT_FOUND }); 32 | } 33 | if (product.available === true) { 34 | yield models_1.FoodModel.findByIdAndUpdate(productId, { 35 | available: false, 36 | }); 37 | return res.status(200).json({ message: "Product disabled" }); 38 | } 39 | else { 40 | yield models_1.FoodModel.findByIdAndUpdate(productId, { 41 | available: true, 42 | }); 43 | return res.status(200).json({ message: "Product enabled" }); 44 | } 45 | })); 46 | exports.DeleteProduct = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 47 | var _a; 48 | const { productId } = req.params; 49 | const product = yield models_1.FoodModel.findById(productId); 50 | if (!product) { 51 | return res.status(400).json({ message: utils_1.ErrorMessage.PRODUCT_NOT_FOUND }); 52 | } 53 | if (product.image) { 54 | const imageId = (_a = product.image.split("/").pop()) === null || _a === void 0 ? void 0 : _a.split(".")[0]; 55 | yield cloudinary_1.v2.uploader.destroy(imageId); 56 | } 57 | yield models_1.FoodModel.findByIdAndDelete(productId); 58 | return res.status(200).json({ message: "Product deleted" }); 59 | })); 60 | exports.DeleteUser = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 61 | const { userId } = req.params; 62 | const user = yield models_1.UserModel.findByIdAndDelete(userId); 63 | return res.status(200).json({ message: "Product deleted" }); 64 | })); 65 | exports.BlockUnBlockUser = (0, utils_1.AsyncWrapper)((req, res) => __awaiter(void 0, void 0, void 0, function* () { 66 | const { userId } = req.params; 67 | const user = yield models_1.UserModel.findById(userId); 68 | if (!user) { 69 | return res.status(400).json({ message: utils_1.ErrorMessage.USER_NOT_FOUND }); 70 | } 71 | if (user.blocked === true) { 72 | yield models_1.UserModel.findByIdAndUpdate(userId, { 73 | blocked: false, 74 | }); 75 | return res.status(200).json({ message: "User Blocked" }); 76 | } 77 | else { 78 | yield models_1.UserModel.findByIdAndUpdate(userId, { 79 | blocked: true, 80 | }); 81 | return res.status(200).json({ message: "User UnBlocked" }); 82 | } 83 | })); 84 | -------------------------------------------------------------------------------- /client/src/components/ui/dialog.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as DialogPrimitive from "@radix-ui/react-dialog" 3 | import { Cross2Icon } from "@radix-ui/react-icons" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Dialog = DialogPrimitive.Root 8 | 9 | const DialogTrigger = DialogPrimitive.Trigger 10 | 11 | const DialogPortal = DialogPrimitive.Portal 12 | 13 | const DialogClose = DialogPrimitive.Close 14 | 15 | const DialogOverlay = React.forwardRef< 16 | React.ElementRef, 17 | React.ComponentPropsWithoutRef 18 | >(({ className, ...props }, ref) => ( 19 | 27 | )) 28 | DialogOverlay.displayName = DialogPrimitive.Overlay.displayName 29 | 30 | const DialogContent = React.forwardRef< 31 | React.ElementRef, 32 | React.ComponentPropsWithoutRef 33 | >(({ className, children, ...props }, ref) => ( 34 | 35 | 36 | 44 | {children} 45 | 46 | 47 | Close 48 | 49 | 50 | 51 | )) 52 | DialogContent.displayName = DialogPrimitive.Content.displayName 53 | 54 | const DialogHeader = ({ 55 | className, 56 | ...props 57 | }: React.HTMLAttributes) => ( 58 |
65 | ) 66 | DialogHeader.displayName = "DialogHeader" 67 | 68 | const DialogFooter = ({ 69 | className, 70 | ...props 71 | }: React.HTMLAttributes) => ( 72 |
79 | ) 80 | DialogFooter.displayName = "DialogFooter" 81 | 82 | const DialogTitle = React.forwardRef< 83 | React.ElementRef, 84 | React.ComponentPropsWithoutRef 85 | >(({ className, ...props }, ref) => ( 86 | 94 | )) 95 | DialogTitle.displayName = DialogPrimitive.Title.displayName 96 | 97 | const DialogDescription = React.forwardRef< 98 | React.ElementRef, 99 | React.ComponentPropsWithoutRef 100 | >(({ className, ...props }, ref) => ( 101 | 106 | )) 107 | DialogDescription.displayName = DialogPrimitive.Description.displayName 108 | 109 | export { 110 | Dialog, 111 | DialogPortal, 112 | DialogOverlay, 113 | DialogTrigger, 114 | DialogClose, 115 | DialogContent, 116 | DialogHeader, 117 | DialogFooter, 118 | DialogTitle, 119 | DialogDescription, 120 | } 121 | -------------------------------------------------------------------------------- /client/src/components/ui/sheet.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as SheetPrimitive from "@radix-ui/react-dialog" 3 | import { Cross2Icon } from "@radix-ui/react-icons" 4 | import { cva, type VariantProps } from "class-variance-authority" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Sheet = SheetPrimitive.Root 9 | 10 | const SheetTrigger = SheetPrimitive.Trigger 11 | 12 | const SheetClose = SheetPrimitive.Close 13 | 14 | const SheetPortal = SheetPrimitive.Portal 15 | 16 | const SheetOverlay = React.forwardRef< 17 | React.ElementRef, 18 | React.ComponentPropsWithoutRef 19 | >(({ className, ...props }, ref) => ( 20 | 28 | )) 29 | SheetOverlay.displayName = SheetPrimitive.Overlay.displayName 30 | 31 | const sheetVariants = cva( 32 | "fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500", 33 | { 34 | variants: { 35 | side: { 36 | top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top", 37 | bottom: 38 | "inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom", 39 | left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm", 40 | right: 41 | "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm", 42 | }, 43 | }, 44 | defaultVariants: { 45 | side: "right", 46 | }, 47 | } 48 | ) 49 | 50 | interface SheetContentProps 51 | extends React.ComponentPropsWithoutRef, 52 | VariantProps {} 53 | 54 | const SheetContent = React.forwardRef< 55 | React.ElementRef, 56 | SheetContentProps 57 | >(({ side = "right", className, children, ...props }, ref) => ( 58 | 59 | 60 | 65 | {children} 66 | 67 | 68 | Close 69 | 70 | 71 | 72 | )) 73 | SheetContent.displayName = SheetPrimitive.Content.displayName 74 | 75 | const SheetHeader = ({ 76 | className, 77 | ...props 78 | }: React.HTMLAttributes) => ( 79 |
86 | ) 87 | SheetHeader.displayName = "SheetHeader" 88 | 89 | const SheetFooter = ({ 90 | className, 91 | ...props 92 | }: React.HTMLAttributes) => ( 93 |
100 | ) 101 | SheetFooter.displayName = "SheetFooter" 102 | 103 | const SheetTitle = React.forwardRef< 104 | React.ElementRef, 105 | React.ComponentPropsWithoutRef 106 | >(({ className, ...props }, ref) => ( 107 | 112 | )) 113 | SheetTitle.displayName = SheetPrimitive.Title.displayName 114 | 115 | const SheetDescription = React.forwardRef< 116 | React.ElementRef, 117 | React.ComponentPropsWithoutRef 118 | >(({ className, ...props }, ref) => ( 119 | 124 | )) 125 | SheetDescription.displayName = SheetPrimitive.Description.displayName 126 | 127 | export { 128 | Sheet, 129 | SheetPortal, 130 | SheetOverlay, 131 | SheetTrigger, 132 | SheetClose, 133 | SheetContent, 134 | SheetHeader, 135 | SheetFooter, 136 | SheetTitle, 137 | SheetDescription, 138 | } 139 | -------------------------------------------------------------------------------- /client/src/components/ui/form.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as LabelPrimitive from "@radix-ui/react-label" 3 | import { Slot } from "@radix-ui/react-slot" 4 | import { 5 | Controller, 6 | ControllerProps, 7 | FieldPath, 8 | FieldValues, 9 | FormProvider, 10 | useFormContext, 11 | } from "react-hook-form" 12 | 13 | import { cn } from "@/lib/utils" 14 | import { Label } from "@/components/ui/label" 15 | 16 | const Form = FormProvider 17 | 18 | type FormFieldContextValue< 19 | TFieldValues extends FieldValues = FieldValues, 20 | TName extends FieldPath = FieldPath 21 | > = { 22 | name: TName 23 | } 24 | 25 | const FormFieldContext = React.createContext( 26 | {} as FormFieldContextValue 27 | ) 28 | 29 | const FormField = < 30 | TFieldValues extends FieldValues = FieldValues, 31 | TName extends FieldPath = FieldPath 32 | >({ 33 | ...props 34 | }: ControllerProps) => { 35 | return ( 36 | 37 | 38 | 39 | ) 40 | } 41 | 42 | const useFormField = () => { 43 | const fieldContext = React.useContext(FormFieldContext) 44 | const itemContext = React.useContext(FormItemContext) 45 | const { getFieldState, formState } = useFormContext() 46 | 47 | const fieldState = getFieldState(fieldContext.name, formState) 48 | 49 | if (!fieldContext) { 50 | throw new Error("useFormField should be used within ") 51 | } 52 | 53 | const { id } = itemContext 54 | 55 | return { 56 | id, 57 | name: fieldContext.name, 58 | formItemId: `${id}-form-item`, 59 | formDescriptionId: `${id}-form-item-description`, 60 | formMessageId: `${id}-form-item-message`, 61 | ...fieldState, 62 | } 63 | } 64 | 65 | type FormItemContextValue = { 66 | id: string 67 | } 68 | 69 | const FormItemContext = React.createContext( 70 | {} as FormItemContextValue 71 | ) 72 | 73 | const FormItem = React.forwardRef< 74 | HTMLDivElement, 75 | React.HTMLAttributes 76 | >(({ className, ...props }, ref) => { 77 | const id = React.useId() 78 | 79 | return ( 80 | 81 |
82 | 83 | ) 84 | }) 85 | FormItem.displayName = "FormItem" 86 | 87 | const FormLabel = React.forwardRef< 88 | React.ElementRef, 89 | React.ComponentPropsWithoutRef 90 | >(({ className, ...props }, ref) => { 91 | const { error, formItemId } = useFormField() 92 | 93 | return ( 94 |