Skip to content

feat: Custom Pizza Feature and Code Optimizations#2

Open
itxSaaad wants to merge 19 commits intomainfrom
dev
Open

feat: Custom Pizza Feature and Code Optimizations#2
itxSaaad wants to merge 19 commits intomainfrom
dev

Conversation

@itxSaaad
Copy link
Owner

@itxSaaad itxSaaad commented Feb 22, 2026

Pull Request Summary: Custom Pizza Feature & Code Optimizations

Core Changes

Payment System Migration & Custom Pizza Introduction

  • Replaces Razorpay payment flow with Stripe Checkout Session integration
  • Introduces custom pizza creation feature allowing users to select bases, sauces, cheeses, and veggies with dynamic pricing
  • Implements size-aware pricing multipliers (Small 1.0x, Medium 1.2x, Large 1.5x, Extra Large 1.8x)
  • Adds COD (Cash on Delivery) payment status tracking with admin management

Constants Centralization

  • Establishes synchronized constants architecture between frontend (client/src/constants/) and backend (server/constants/)
  • Replaces 100+ hard-coded strings with frozen objects and utility functions (USER_ROLES, ORDER_STATUS, PIZZA_SIZES, INVENTORY_TYPES, PAYMENT_METHODS, PAYMENT_STATUS, ERROR_CODES)
  • Provides helper utilities (getPizzaSizeMultiplier, getOrderStatusColor, isAdminRole)

Architecture Impact

Security & Middleware Enhancements

  • Implements rate limiting (5/15min auth, 3/hour registration, 10/15min payments)
  • Adds comprehensive input validation via express-validator with per-route middleware chains
  • Integrates helmet for CSP and security headers
  • Adds NoSQL injection protection (express-mongo-sanitize)
  • Implements request-scoped requestId for error tracing

Error Handling Standardization

  • Introduces ApiError class with static factory methods (validation, unauthorized, notFound, duplicate, emailExists, insufficientInventory, paymentError)
  • Centralizes error code mapping with structured responses (code, message, details, timestamp, requestId)
  • Adds comprehensive error utilities (extractErrorMessage, getFieldErrors, isAuthError, isValidationError)
  • Normalizes legacy and new error formats across validation, authentication, and database errors

Route Protection & Authentication

  • Adds three route guard components (ProtectedRoute, UserRoute, AdminRoute) for role-based access control
  • Wraps sensitive routes appropriately (custom pizza creation behind UserRoute, admin dashboard behind AdminRoute)

State & Data Flow

  • Refactors cartSlice to support size-aware cart items with updateCartItemQuantity action
  • Replaces RazorPay order state with Stripe session fields (stripeSessionId, stripeCheckoutUrl, stripeOrderId)
  • Normalizes Redux thunk responses across user, admin, pizza, order, and inventory modules

Key Features & Fixes

Custom Pizza Creation Screen

  • Dynamic ingredient selection with per-category grouping (bases, sauces, cheeses, veggies)
  • Base price calculation from ingredient costs
  • Size multiplier application with real-time price updates
  • Client-side validation requiring bases, sauces, cheeses, and size selection
  • Automatic cart insertion post-creation

Payment Processing

  • Stripe Checkout Session endpoint with line item transformation
  • Webhook handler for checkout.session.completed and payment_failed events
  • COD payment status management (PENDING, PAID, FAILED) with email notifications
  • Payment status persisted to order.payment.status with stripeSessionId and stripePaymentIntentId fields

Inventory Management

  • Inventory availability pre-check before order creation
  • Atomic deduction with rollback capability on failure
  • Per-item deduction tracking (baseMap, sauceMap, cheeseMap, veggieMap lookups)
  • Low-stock alerting with email notifications to approved admins
  • Enhanced inventory schema with indexes on type and quantity

Analytics & Reporting

  • Dashboard summary endpoint returning orders, popular pizzas, low stock, new users concurrently
  • Order statistics aggregation by status with revenue breakdown
  • Popular pizzas ranking by order count and revenue
  • Daily revenue reports with configurable window
  • Low-inventory alerts with per-category breakdowns
  • New user growth tracking

Email Verification & Password Reset

  • Email verification flow with 6-digit code validation and reset link generation
  • Password reset with token expiry and verification
  • Status change notifications for orders (6 different status scenarios)
  • Low-inventory alerts sent to approved admins

Security & Performance Considerations

Positive

  • Input validation on all user-facing endpoints with express-validator
  • Rate limiting prevents brute force attacks on auth and payment endpoints
  • Helmet CSP prevents cross-site scripting
  • MongoDB indexes on frequently queried fields (user, status, createdAt, email)
  • Request tracing via requestId for audit logging
  • Inventory deduction rollback prevents partial failures

Concerns

  • Stripe webhook secret not visible in diff; ensure proper verification implementation
  • Custom pizza pricing calculation on frontend (basePrice + ingredient costs) must validate server-side
  • Inventory rollback only on atomic failures; no distributed transaction handling across pizza creation and payment
  • Error details exposure in non-production environment could leak sensitive information

Breaking Changes

Database Schema

  • Order items now require size field (enum: small/medium/large/extra-large)
  • Order payment schema extended (stripeSessionId, stripePaymentIntentId, status defaults to PENDING)
  • Pizza schema removes size field; pricing now size-agnostic
  • Order status enum values updated to ORDER_STATUS_VALUES constant

API Endpoints

  • POST /api/orders/checkout → POST /api/orders/create-checkout-session (Stripe flow)
  • Removal of Razorpay endpoints
  • New POST /api/orders/:id/payment-status (admin-only COD payment updates)
  • New POST /api/inventory/check-alerts (admin-only low-stock trigger)
  • New GET /api/inventory/low-stock (admin-only inventory status)
  • New GET /api/analytics/* (8 admin-only analytics endpoints)

Frontend Components

  • RazorPayPaymentButton removed entirely
  • PaymentStep now renders dynamic payment methods from constants
  • CartItemList uses new updateCartItemQuantity instead of addToCart for quantity changes
  • PizzaItem now requires size selection before cart addition

Data Migration Required

  • Existing orders missing size field will fail validation
  • Existing pizzas missing new schema may cause compatibility issues
  • Razorpay order references require migration strategy

Testing Coverage

Documentation Added

  • SETUP.md: 598 lines covering full local and production setup
  • TESTING.md: 738 lines with test credentials, API flows, payment testing, and coverage goals
  • API.md: 1146 lines documenting all endpoints with request/response examples
  • ARCHITECTURE.md: 789 lines with data flow diagrams and system design
  • ERROR_HANDLING.md: 795 lines standardizing error responses
  • CONSTANTS.md: 719 lines detailing constants synchronization strategy
  • DEPLOYMENT.md: 631 lines for Vercel production deployment
  • CONTRIBUTING.md: 646 lines for contributor guidelines

Automated Testing

  • No test files (.test.js, .spec.js) found in diff
  • Gap: Unit tests for inventory deduction, error handling, analytics queries, and Stripe webhook parsing missing
  • Gap: Integration tests for custom pizza creation → inventory deduction → payment flow missing
  • Gap: E2E tests for Stripe webhook handling and payment status updates missing

Review Focus Areas

  1. Stripe Webhook Implementation

    • Verify idempotency handling for duplicate webhooks
    • Confirm signature verification with webhook secret
    • Check order state consistency on webhook retry scenarios
  2. Inventory Deduction Logic

    • Validate atomic execution and rollback (check executeInventoryDeductions error paths)
    • Confirm ingredient availability check happens before inventory updates
    • Verify partial order creation doesn't occur on inventory failure
  3. Custom Pizza Pricing

    • Ensure server-side validation of ingredient costs matches client calculations
    • Confirm size multipliers applied consistently (bases should not be size-dependent)
    • Check base price capping/minimum thresholds
  4. Database Indexes & Performance

    • Verify index selectivity for user + status queries on large order tables
    • Confirm schema migration plan for existing records without size field
    • Check aggregation pipeline efficiency in analytics queries
  5. Error Handling Consistency

    • Review ApiError factory methods for missing scenarios (race conditions, timeout)
    • Validate field error extraction across validation layers
    • Check error details exposure in stack traces for non-production environments
  6. Route Protection Coverage

    • Confirm all sensitive routes wrapped with appropriate guards
    • Verify AdminRoute validates isApproved status, not just role
    • Check UserRoute distinguishes between admin and regular user access
  7. Constants Synchronization

    • Verify frontend/backend constants remain aligned (automated sync mechanism missing)
    • Check for missed hard-coded strings still remaining in codebase
    • Confirm PIZZA_SIZE_OPTIONS includes multiplier data for pricing
  8. Payment Transition

    • Identify strategy for existing Razorpay orders during deployment
    • Confirm checkout URL redirect implementation in CheckoutSuccessScreen
    • Review COD workflow for admins updating payment status post-delivery

Added .env.example files for both server and client to simplify initial project configuration and deployment setup.
Implemented a comprehensive constants system for both frontend and backend to ensure consistency across the application. Includes order status, payment methods, user roles, inventory types, pizza sizes, currency, and error codes.
Implemented comprehensive utility functions including ApiError/ApiResponse classes for standardized responses, error code mapping, pagination helpers, inventory deduction logic, inventory alert system, analytics calculations, and environment variable validation.
Implemented express-validator based validation for all endpoints and rate limiting middleware for authentication, registration, payment, and password reset endpoints to enhance security.
Implemented error handling utilities for standardized error extraction and route protection components (ProtectedRoute, AdminRoute) for secure navigation.
Created detailed documentation covering setup, API reference, architecture, error handling, testing, deployment, constants reference, and contribution guidelines.
Implemented OrderDetailScreen for viewing order details, ResetPasswordScreen and VerifyEmailScreen for account management, and CheckoutSuccessScreen/CheckoutCancelScreen for payment flow feedback.
Implemented analytics controller and routes to provide key metrics including total users, orders, revenue, and low inventory items for the admin dashboard.
Implemented inventory alert controller with endpoints to check low stock items and send automated email alerts when inventory falls below threshold levels.
Replaced Razorpay payment integration with Stripe for better payment processing. Added Cash on Delivery (COD) option, implemented Stripe checkout session creation, webhook handling for payment confirmation, and updated all related frontend components and backend controllers.
Integrated centralized constants in schemas, added database indexes for frequently queried fields, removed size field from pizza schema (moved to order items), and updated order schema to support multiple payment methods with proper status tracking.
Simplified controller logic by removing nested conditionals, integrated ApiError/ApiResponse utilities, added pagination support, used centralized constants, and updated error middleware to handle new error format.
Integrated express-validator based validation middleware and rate limiting for authentication, registration, payment, and password reset endpoints across all routes.
Updated inventory prices to realistic values, added new ingredients (Garlic, Oregano, Eggs, Bacon, Gorgonzola), created diverse pizza options with high-quality images, improved seeder to properly link pizzas with inventory IDs, and enhanced console output with test credentials display.
Modified database configuration to export connectDb as named export for consistency with modern ES6 patterns.
Integrated error utility functions across all Redux thunks to extract and handle errors consistently, support new API response format with nested data property, and added new thunk for updating order payment status.
Integrated centralized constants across all components, enhanced Message component to display field-level validation errors with error codes, added payment status column to orders list, and improved overall component consistency.
Redesigned custom pizza creation screen with better ingredient selection UI, integrated constants for user roles and pizza sizes, improved checkout screen navigation, and updated main.jsx with new route configurations.
…stack section, and improve error handling documentation
@vercel
Copy link

vercel bot commented Feb 22, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
pizza-palette-app-mern Ready Ready Preview, Comment Feb 22, 2026 4:03pm
pizza-palette-app-mern-backend Ready Ready Preview, Comment Feb 22, 2026 4:03pm

@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Walkthrough

This pull request introduces a major system overhaul including: migrating from Razorpay to Stripe payment processing, implementing comprehensive error handling with standardized API responses, establishing centralized constants between client and server, adding route protection guards, integrating input validation and rate limiting, implementing inventory management with deduction/rollback, and adding analytics dashboards with low-stock alerts. The changes span environment configuration, React components, Redux state management, Express controllers, database schemas, and extensive project documentation.

Changes

Cohort / File(s) Summary
Environment & Configuration
.env.example, client/.env.example
Added environment variable templates for server configuration (NODE_ENV, PORT, MongoDB, JWT, email, Razorpay/Stripe keys) and client-side Vite/API configuration.
Client Constants Architecture
client/src/constants/*, client/src/constants/index.js
Established centralized constants for order statuses, pizza sizes, inventory types, payment methods/statuses, user roles, and currency. Includes helper functions (getPizzaSizeMultiplier, getOrderStatusColor, isAdminRole, getCurrencySymbol) for consistent enum usage across components.
Server Constants Architecture
server/constants/*, server/constants/index.js
Mirrored client constants on backend with additional error codes. Exports centralized ORDER_STATUS, PIZZA_SIZES, INVENTORY_TYPES, PAYMENT_METHODS, USER_ROLES, and ERROR_CODES for schema validation and controller logic.
Route Protection Components
client/src/components/route/AdminRoute.jsx, UserRoute.jsx, ProtectedRoute.jsx
Added three new route wrapper components that check Redux auth state and redirect unauthenticated/unauthorized users. ProtectedRoute guards both user and admin routes; AdminRoute restricts to admins; UserRoute restricts to regular users.
Stripe Payment Migration
client/src/components/ui/CheckoutSteps/PaymentStep.jsx, PlaceOrderStep.jsx, client/src/redux/slices/cartSlice.js, server/controllers/orderControllers.js
Replaced Razorpay integration with Stripe. Frontend now dispatches createStripeCheckoutSession and redirects to Stripe; backend handles checkout.completed and payment_failed webhook events. Removed RazorPayPaymentButton component and Razorpay state fields; added stripeSessionId, stripeCheckoutUrl, stripeOrderId fields.
Order Management & Payment Status
client/src/components/ui/Admin/Dashboard/Lists/OrdersList.jsx, server/controllers/orderControllers.js, server/schemas/orderSchema.js
Extended order schema with size field in orderItems, added stripeSessionId/stripePaymentIntentId, and payment.status field. Added updateOrderPaymentStatus endpoint for admin COD payment status updates. Frontend OrdersList now supports both status and payment status updates with separate dispatch handlers.
Inventory Management System
server/utils/inventoryDeductionUtils.js, inventoryAlertUtils.js, server/controllers/inventoryAlertControllers.js, server/routes/inventoryRoutes.js
Implemented inventory deduction on order creation with rollback on failure. Added low-stock alerts that check inventory against thresholds and email admins. Created check-alerts and low-stock endpoints. Updated Inventory schema with Cheese and Veggie model exports and field indexing.
Analytics Dashboard
server/utils/analyticsUtils.js, server/controllers/analyticsControllers.js, server/routes/analyticsRoutes.js
Added analytics endpoints (order statistics, popular pizzas, revenue reports, low-stock, new users, dashboard summary) with aggregation pipelines. Analytics routes protected by admin middleware.
Error Handling & Utilities
server/utils/ApiError.js, server/utils/ApiResponse.js, server/utils/errorCodeMapper.js, client/src/utils/errorUtils.js, server/middlewares/errorMiddlewares.js
Introduced standardized ApiError and ApiResponse classes. Backend error middleware converts Mongoose/JWT/axios errors to ApiError format with codes and details. Frontend errorUtils extracts and normalizes error messages across multiple formats. Added field-level error support and request tracing.
Input Validation & Rate Limiting
server/middlewares/validationHandler.js, server/middlewares/rateLimitMiddleware.js, server/validators/*
Added express-validator middleware for all CRUD operations across users, admins, pizzas, orders, and inventory. Implemented rate limiters for auth (5/15min), registration (3/1hr), password reset (3/1hr), and API (100/15min). Validation runs before controllers with field-level error reporting.
Client Message Component Refactor
client/src/components/ui/Message.jsx
Enhanced Message component with variant-based rendering (auto/success/warning/error/info), field-level error display, automatic 8-second dismissal, and onClose callback. Now accepts both string and object children with dynamic error code extraction.
Client Admin Components
client/src/components/ui/Admin/Dashboard/Home.jsx, Lists/OrdersList.jsx, Lists/PizzasList.jsx, Modals/PizzaCreateModal.jsx, Modals/StockCreateModal.jsx, Table.jsx
Replaced hard-coded status/role strings with constants. OrdersList now displays paymentStatus column with COD-only editing. PizzaCreateModal uses PIZZA_SIZE_OPTIONS for rendering instead of local state. StockCreateModal uses INVENTORY_TYPES constant. Table component extended for payment status select with PAYMENT_STATUS enum.
Client Pizza & Cart Management
client/src/components/ui/PizzaMenu/PizzaItem.jsx, client/src/components/ui/Cart/AddToCartButton.jsx, CartItemList.jsx
PizzaItem now displays size-aware pricing with calculatePrice based on getPizzaSizeMultiplier. AddToCartButton enhanced to accept and validate size/basePrice props with error messaging. CartItemList refactored to use updateCartItemQuantity action with composite key (id-size) instead of local state.
Client Screen Components
client/src/screens/CheckoutSuccessScreen.jsx, CheckoutCancelScreen.jsx, User/OrderDetailScreen.jsx, User/VerifyEmailScreen.jsx, User/ResetPasswordScreen.jsx, User/UserCreateCustomPizzaScreen.jsx
Added 5 new screens: CheckoutSuccessScreen (post-Stripe confirmation with countdown redirect), CheckoutCancelScreen (payment failure UI), OrderDetailScreen (detailed order view with status/payment info), VerifyEmailScreen (email verification flow), ResetPasswordScreen (password reset with token validation). Refactored UserCreateCustomPizzaScreen with ingredient categories, price calculation, and internal IngredientCard component.
Client Routes & Redux
client/src/main.jsx, client/src/redux/asyncThunks/orderThunks.js, orderThunks.js, cartSlice.js
Updated routing to protect dashboard/custom-pizza/my-orders with AdminRoute/UserRoute/ProtectedRoute. Added new routes for email verification, password reset, and checkout status pages. OrderThunks now includes new updateOrderPaymentStatus thunk. CartSlice replaced Razorpay flow with Stripe, added updateCartItemQuantity reducer, and defensive localStorage parsing.
Server Controller Refactoring
server/controllers/adminUserControllers.js, server/controllers/userControllers.js, server/controllers/pizzaControllers.js
Streamlined auth flows by removing redundant validations. AdminUserControllers now assigns role via USER_ROLES constant. UserControllers integrated pagination via parsePaginationParams/buildPaginationResponse. PizzaControllers added pagination, filtering, and sorting; removed size field; replaced base with bases. All controllers now use extractErrorMessage for unified error handling.
Database Schema Enhancements
server/schemas/orderSchema.js, server/schemas/pizzaSchema.js, server/schemas/adminUserSchema.js, server/schemas/inventorySchema.js, server/schemas/userSchema.js
OrderSchema added size in orderItems, stripeSessionId/stripePaymentIntentId, payment.status. PizzaSchema removed size field, added indexes on createdBy/price/createdAt. AdminUserSchema added role enum constraint and indexes. InventorySchema exported Cheese/Veggie models, added field indexes. UserSchema added indexes on email/verificationCode/resetPasswordToken/createdAt.
Server Dependencies & Utilities
server/package.json, server/utils/envValidator.js, server/utils/paginationUtils.js
Added dependencies: stripe, helmet, express-mongo-sanitize, express-rate-limit, express-validator, uuid, and mongodb-memory-server (dev). New envValidator ensures required env vars at startup. PaginationUtils provides parsePaginationParams, parseSortParams, buildPaginationResponse for paginated list endpoints.
Server Security & Middleware
server/index.js
Integrated helmet (CSP headers), mongo-sanitize (NoSQL injection), environment validation, and CORS with origin whitelist. Added /health and /ready endpoints for database connectivity checks using getConnectionStatus. Enhanced request security and health monitoring.
Database Seeding & Configuration
server/seeder.js, server/config/db.js, server/data/pizzas.js, server/data/inventory.js, server/data/users.js
Updated seeder to use destructured connectDb import and USER_ROLES constant; builds inventory lookups for pizza ID resolution. Added MongoDB connection caching with maxPoolSize/timeout options in db.js. Updated seed data with realistic pizzas, inventory items, and user credentials. Added getConnectionStatus export for health checks.
Documentation Suite
docs/README.md, docs/SETUP.md, docs/API.md, docs/ARCHITECTURE.md, docs/ERROR_HANDLING.md, docs/CONSTANTS.md, docs/TESTING.md, docs/DEPLOYMENT.md, docs/CONTRIBUTING.md
Comprehensive 9-file documentation covering setup, API reference, system architecture with diagrams, error handling patterns, constants synchronization, testing strategies, deployment on Vercel, and contribution guidelines. Includes code examples, test credentials, troubleshooting, and checklists.
Client Dependencies
client/package.json
Added @stripe/stripe-js for Stripe client-side integration.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (Client)
    participant Frontend as Frontend App
    participant Backend as Backend Server
    participant Stripe as Stripe API
    participant Webhook as Webhook Handler
    
    User->>Frontend: Place Order (Stripe selected)
    Frontend->>Backend: POST /orders/create-checkout-session
    Backend->>Backend: Create order (pending)
    Backend->>Backend: Check inventory availability
    Backend->>Backend: Deduct inventory
    Backend->>Stripe: Create checkout session
    Stripe-->>Backend: Session ID & URL
    Backend-->>Frontend: { stripeCheckoutUrl }
    Frontend->>Stripe: Redirect to checkout
    User->>Stripe: Complete payment
    
    Stripe->>Webhook: POST webhook (checkout.completed)
    Webhook->>Backend: handleStripeWebhook
    Backend->>Backend: Update order payment status
    Backend->>Backend: Send confirmation email
    Backend-->>Stripe: 200 OK
    
    Frontend->>Backend: GET /orders/{orderId}
    Backend-->>Frontend: Order details (payment_confirmed)
    Frontend->>User: Show success screen + redirect
Loading
sequenceDiagram
    participant Admin as Admin User
    participant Frontend as Admin Dashboard
    participant Backend as Backend Server
    participant DB as Database
    participant Email as Email Service
    
    Admin->>Frontend: Update order payment status (COD)
    Frontend->>Backend: PUT /orders/{id}/payment-status
    Backend->>Backend: Validate order exists & is COD
    Backend->>DB: Update payment status to PAID
    Backend->>Email: Send payment confirmation
    Email-->>Backend: ✓ Sent
    Backend-->>Frontend: 200 OK
    Frontend->>Admin: Show success message
    
    rect rgba(100, 150, 200, 0.5)
        Note over Backend,DB: Parallel: Low Stock Check
        Backend->>DB: Query all inventory (Bases, Sauces, Cheeses, Veggies)
        Backend->>Backend: Filter items where qty ≤ threshold
        Backend->>Email: Send low-stock alert to admins
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: Custom Pizza Feature and Code Optimizations' clearly describes the main changes: implementation of a custom pizza creation feature and various code quality improvements using constants-based refactoring.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (21)
server/utils/inventoryUtils.js (2)

10-11: ⚠️ Potential issue | 🟡 Minor

qty is never validated — zero or negative values silently corrupt stock.

item.quantity >= qty is always true when qty <= 0, so passing 0 is a no-op and passing a negative value actually increases stock.

🛡️ Proposed guard
 const updateInventoryQuantity = async (pizza, qty) => {
+  if (!qty || qty <= 0) {
+    throw new Error('Quantity must be a positive integer');
+  }
   const { bases, sauces, cheeses, veggies } = pizza;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/inventoryUtils.js` around lines 10 - 11, Validate the incoming
qty before mutating item.quantity: ensure qty is a positive integer (e.g.,
Number.isInteger(qty) && qty > 0) and reject or throw (or return a failure) for
zero/negative/non-integer values so they cannot pass the subsequent check; only
then perform the existing stock check item.quantity >= qty and decrement. Also
update any caller expectations if they relied on zero/no-op behavior.

8-45: ⚠️ Potential issue | 🟠 Major

No transaction/rollback — inventory corruption on partial failure.

The function deducts inventory across four collections (bases, sauces, cheeses, veggies) in serial without a MongoDB session/transaction. If, say, a Sauce item is out of stock after the Base was already decremented, the base quantity is permanently reduced even though the order ultimately fails. With enough concurrent orders this will silently corrupt stock levels.

Fix: wrap the entire operation in a mongoose.startSession() transaction so every item.save() is part of the same atomic unit and the whole thing rolls back on any failure.

🔒 Proposed fix — wrap in a Mongoose transaction
+const mongoose = require('mongoose');
 const { Base, Sauce, Cheese, Veggie } = require('../schemas/inventorySchema');

 const updateInventoryQuantity = async (pizza, qty) => {
   const { bases, sauces, cheeses, veggies } = pizza;

-  const updateQuantity = async (item) => {
+  const updateQuantity = async (item, session) => {
     if (item) {
       if (item.quantity >= qty) {
         item.quantity -= qty;
-        const updateditem = await item.save();
+        const updateditem = await item.save({ session });
         return updateditem;
       } else {
         throw new Error(
           `Not enough ${item.item} in inventory! Please update inventory!`
         );
       }
     } else {
       throw new Error('Item Not Found!');
     }
   };

+  const session = await mongoose.startSession();
+  session.startTransaction();
+  try {
     for (const baseId of bases) {
-      const baseItem = await Base.findById(baseId);
-      await updateQuantity(baseItem);
+      const baseItem = await Base.findById(baseId).session(session);
+      await updateQuantity(baseItem, session);
     }
     for (const sauceId of sauces) {
-      const sauceItem = await Sauce.findById(sauceId);
-      await updateQuantity(sauceItem);
+      const sauceItem = await Sauce.findById(sauceId).session(session);
+      await updateQuantity(sauceItem, session);
     }
     for (const cheeseId of cheeses) {
-      const cheeseItem = await Cheese.findById(cheeseId);
-      await updateQuantity(cheeseItem);
+      const cheeseItem = await Cheese.findById(cheeseId).session(session);
+      await updateQuantity(cheeseItem, session);
     }
     for (const veggieId of veggies) {
-      const veggieItem = await Veggie.findById(veggieId);
-      await updateQuantity(veggieItem);
+      const veggieItem = await Veggie.findById(veggieId).session(session);
+      await updateQuantity(veggieItem, session);
     }
+    await session.commitTransaction();
+  } catch (err) {
+    await session.abortTransaction();
+    throw err;
+  } finally {
+    session.endSession();
+  }
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/utils/inventoryUtils.js` around lines 8 - 45, The inventory updates
currently call updateQuantity and Model.findById on Base, Sauce, Cheese, Veggie
without a transaction, so partial failures permanently mutate stock; wrap the
entire sequence in a mongoose.startSession() and session.startTransaction(), use
session-aware reads and writes (e.g., Base.findById(id).session(session) or pass
{ session } to queries and call item.save({ session }) inside updateQuantity),
commit the transaction on success and abortTransaction() on any thrown error,
and always end the session in a finally block so all item saves (in
updateQuantity) are atomic and roll back on failure.
client/src/redux/asyncThunks/inventoryThunks.js (2)

115-136: ⚠️ Potential issue | 🔴 Critical

updateStockById passes the Axios config object as the request body — auth header is dropped and update data is never sent.

Line 129:

const { data } = await axios.put(`${import.meta.env.VITE_SERVER_URL}/stocks/${id}`, config);

axios.put(url, data, config) takes data as the second argument and config as the third. Passing config as the second argument means:

  1. The Authorization header is silently placed in the request body instead of the request headers — the server will reject this as unauthenticated.
  2. No actual stock update payload is ever sent.
  3. The thunk argument is only id (Line 117) — there is no provision for accepting update fields at all.
🐛 Proposed fix
-export const updateStockById = createAsyncThunk(
-  'inventory/updateStockById',
-  async (id, { rejectWithValue, getState }) => {
+export const updateStockById = createAsyncThunk(
+  'inventory/updateStockById',
+  async ({ id, ...updateData }, { rejectWithValue, getState }) => {
     try {
       const {
         admin: { adminUserInfo },
       } = getState();

       const config = {
         headers: {
           Authorization: `Bearer ${adminUserInfo.token}`,
         },
       };

-      const { data } = await axios.put(`${import.meta.env.VITE_SERVER_URL}/stocks/${id}`, config);
+      const { data } = await axios.put(
+        `${import.meta.env.VITE_SERVER_URL}/stocks/${id}`,
+        updateData,
+        config
+      );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/redux/asyncThunks/inventoryThunks.js` around lines 115 - 136, The
thunk updateStockById is incorrectly treating the Axios config as the request
body and doesn't accept update payloads; change the async thunk signature to
accept an object like ({ id, updates }) or (payload) so you have both id and
update data, then call axios.put with axios.put(url, updates, config) (ensure
config contains Authorization from adminUserInfo.token). Also update the
returned value to return data.data || data and keep
rejectWithValue(extractErrorMessage(error)) unchanged.

1-3: ⚠️ Potential issue | 🔴 Critical

extractErrorMessage is used but never imported — all error paths will throw ReferenceError.

Every catch block in this file calls extractErrorMessage(error) (Lines 35, 72, 109, 133, 160), but the function is not imported. The only imports are createAsyncThunk and axios. At runtime, any failed request will crash with ReferenceError: extractErrorMessage is not defined rather than dispatching the rejection.

🐛 Proposed fix — add the missing import
 import { createAsyncThunk } from '@reduxjs/toolkit';
 import axios from 'axios';
+import { extractErrorMessage } from '../../utils/errorUtils';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/redux/asyncThunks/inventoryThunks.js` around lines 1 - 3, The file
uses extractErrorMessage in every catch block (within async thunks created by
createAsyncThunk) but it is not imported, causing a ReferenceError on failures;
fix by adding an import for extractErrorMessage at the top of inventoryThunks.js
(alongside createAsyncThunk and axios) importing it from the module that exports
it (the same utils/error helper module used elsewhere in the codebase) so all
calls to extractErrorMessage(error) in the catch blocks resolve correctly.
server/schemas/inventorySchema.js (1)

3-61: 🛠️ Refactor suggestion | 🟠 Major

All four schemas are structurally identical — extract a factory function.

Every schema has the same fields, options, and index calls repeated four times. Any future field addition (e.g., unit, sku) requires four parallel changes.

♻️ Proposed refactor
 const mongoose = require('mongoose');

+function createInventorySchema() {
+  const schema = new mongoose.Schema(
+    {
+      item: { type: String, required: true },
+      quantity: { type: Number, required: true },
+      price: { type: Number, required: true },
+      threshold: { type: Number, default: 10 },
+    },
+    { timestamps: true }
+  );
+  schema.index({ item: 1 });
+  schema.index({ quantity: 1 });
+  return schema;
+}
+
-const baseSchema = new mongoose.Schema( ... );
-baseSchema.index({ item: 1 });
-baseSchema.index({ quantity: 1 });
-
-const sauceSchema = new mongoose.Schema( ... );
-sauceSchema.index({ item: 1 });
-sauceSchema.index({ quantity: 1 });
-
-const cheeseSchema = new mongoose.Schema( ... );
-cheeseSchema.index({ item: 1 });
-cheeseSchema.index({ quantity: 1 });
-
-const veggieSchema = new mongoose.Schema( ... );
-veggieSchema.index({ item: 1 });
-veggieSchema.index({ quantity: 1 });

 module.exports = {
-  Base: mongoose.model('Base', baseSchema),
-  Sauce: mongoose.model('Sauce', sauceSchema),
-  Cheese: mongoose.model('Cheese', cheeseSchema),
-  Veggie: mongoose.model('Veggie', veggieSchema),
+  Base: mongoose.model('Base', createInventorySchema()),
+  Sauce: mongoose.model('Sauce', createInventorySchema()),
+  Cheese: mongoose.model('Cheese', createInventorySchema()),
+  Veggie: mongoose.model('Veggie', createInventorySchema()),
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/schemas/inventorySchema.js` around lines 3 - 61, The four schemas
(baseSchema, sauceSchema, cheeseSchema, veggieSchema) are identical; create a
factory function (e.g., createInventorySchema or buildInventorySchema) that
returns a new mongoose.Schema with the shared fields, options (timestamps), and
index calls, and then replace the duplicated declarations with four calls to
this factory to produce baseSchema, sauceSchema, cheeseSchema, and veggieSchema
so future field changes only need to be made in one place.
server/controllers/inventoryControllers.js (1)

86-122: ⚠️ Potential issue | 🟡 Minor

updateStockById only updates quantity, ignoring other fields from the request body.

The createStock handler accepts item, price, quantity, and threshold, but updateStockById hardcodes only quantity = req.body.quantity. If an admin needs to correct a price or threshold, there's no path to do so without a delete-and-recreate. This smells like an incomplete implementation.

Also, the nested if (updatedBase) / if (updatedSauce) checks are unreachable failure paths — save() either succeeds or throws in Mongoose, so the implicit else (no response sent) is dead code that would leave the request hanging if somehow reached.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/controllers/inventoryControllers.js` around lines 86 - 122,
updateStockById currently only sets quantity and has redundant post-save
truthiness checks that are unnecessary; update it to apply all updatable fields
from req.body (at minimum item, price, quantity, threshold) to the found
document (Base, Sauce, Cheese, or Veggie) before saving, e.g. merge/assign only
allowed keys from req.body into the model instance, call await instance.save(),
then send a single res.status(200).json(...) response with an appropriate
message; remove the unreachable if (updatedX) branches and ensure the 404 path
remains when none of Base/Sauce/Cheese/Veggie is found.
client/src/components/ui/Admin/Dashboard/Modals/StockCreateModal.jsx (1)

51-54: ⚠️ Potential issue | 🟠 Major

Bug: Modal closes and inventory refreshes even when createStock fails.

With Redux Toolkit's createAsyncThunk, dispatch(createStock(stockData)) returns a promise that always resolves — even when the thunk calls rejectWithValue. The .then() callback therefore fires unconditionally, closing the modal and refreshing inventory regardless of whether the creation succeeded.

You need .unwrap() to re-throw on rejection:

🐛 Fix: use unwrap() for proper error propagation
-    dispatch(createStock(stockData)).then(() => {
-      dispatch(listInventory({}));
-      handleModalClose();
-    });
+    dispatch(createStock(stockData))
+      .unwrap()
+      .then(() => {
+        dispatch(listInventory({}));
+        handleModalClose();
+      })
+      .catch(() => {
+        // Error is already in Redux state; modal stays open for user to retry
+      });

This is a common RTK gotcha — dispatch(thunk()) fulfills with either a fulfilled or rejected action object, so .then() always runs. .unwrap() converts rejected actions into thrown errors.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/ui/Admin/Dashboard/Modals/StockCreateModal.jsx` around
lines 51 - 54, The current dispatch(createStock(stockData)).then(...) always
runs even on thunk rejection; change to await
dispatch(createStock(stockData)).unwrap() (or
dispatch(createStock(stockData)).unwrap().then(...).catch(...)) so rejected
createStock re-throws and you only call dispatch(listInventory({})) and
handleModalClose() on success; wrap in try/catch (or add .catch) to handle
errors and avoid closing the modal or refreshing inventory when createStock
fails, referencing the createStock thunk, dispatch call, listInventory dispatch,
and handleModalClose.
client/src/redux/asyncThunks/pizzaThunks.js (2)

36-45: ⚠️ Potential issue | 🟡 Minor

NPE risk if both adminUserInfo and userInfo are null/undefined.

Line 43: adminUserInfo ? adminUserInfo.token : userInfo.token — if a user is somehow logged out (both state slices cleared) and this thunk fires, userInfo.token throws a TypeError. Compare with updatePizzaById (line 109) and deletePizzaById (line 147) which only reference adminUserInfo.token without any fallback — same NPE risk if the admin session is cleared.

A defensive pattern:

const token = adminUserInfo?.token || userInfo?.token;
if (!token) {
  return rejectWithValue({ code: 'AUTH_ERROR', message: 'Not authenticated' });
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/redux/asyncThunks/pizzaThunks.js` around lines 36 - 45, The thunk
builds Authorization header using adminUserInfo ? adminUserInfo.token :
userInfo.token which can throw when both adminUserInfo and userInfo are null;
update the token acquisition to use optional chaining (e.g. adminUserInfo?.token
|| userInfo?.token) and if token is falsy call rejectWithValue({ code:
'AUTH_ERROR', message: 'Not authenticated' }) before creating the config object,
and apply the same defensive pattern to the other thunks referenced
(updatePizzaById and deletePizzaById) that currently access adminUserInfo.token
directly so they also early-reject when no token is present.

47-61: ⚠️ Potential issue | 🟠 Major

Fix field name inconsistency: updatePizzaById must send bases (plural), not base (singular).

The backend schema, controller, and validators all expect bases (plural). Line 99 destructures base (singular) and line 118 sends base, causing the update request to silently fail—req.body.bases will be undefined, and the controller will preserve the old value instead of updating it.

Change line 99 from base to bases in the destructuring, and line 118 will then correctly send the plural field name matching the createPizza behavior and backend expectations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/redux/asyncThunks/pizzaThunks.js` around lines 47 - 61, In
updatePizzaById, fix the inconsistent field name by changing the destructured
variable from base to bases (so use "bases" where you extract values from
pizzaData) and ensure the payload sent in the axios request uses bases (plural)
instead of base; update the destructuring and the object you pass in the
axios.post/put call (referencing updatePizzaById and the payload object) so the
request matches createPizza and the backend schema expecting req.body.bases.
client/src/redux/asyncThunks/userThunks.js (2)

142-175: ⚠️ Potential issue | 🟠 Major

Remove misleading multipart/form-data header—only JSON fields are sent.

Line 152 declares 'Content-Type': 'multipart/form-data', but the server's updateUserProfile handler (server/controllers/userControllers.js, lines 287–321) accepts only text fields (name, email, phoneNumber, address, password) from req.body—no file uploads or multipart parsing. Axios serializes the plain JavaScript object as JSON regardless of the header, so the request works in practice, but the incorrect header is misleading and violates HTTP semantics. Change to 'application/json' to match the actual request format and server expectations.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/redux/asyncThunks/userThunks.js` around lines 142 - 175, The
request header in the updateUserProfile async thunk incorrectly sets
'Content-Type' to 'multipart/form-data' even though the payload is a plain JSON
object; update the headers in the config object inside updateUserProfile (the
createAsyncThunk for 'user/userUpdateProfile') to use 'application/json' (or
remove the explicit Content-Type so axios sets it) so the header matches the
actual JSON payload and server expectations.

65-91: ⚠️ Potential issue | 🟠 Major

VerifyEmailScreen.jsx sends incomplete request to verifyEmail thunk—missing required email parameter.

Line 28 dispatches verifyEmail({ verificationCode: token }) without the email field. The thunk expects both parameters (line 67), and the server enforces email as required via verifyUserValidation (server/validators/userValidators.js:51–56). This request will fail server-side validation with "Email is required."

The correct pattern is shown in VerficationModal.jsx (line 38), which dispatches verifyEmail({ email, verificationCode }). Update VerifyEmailScreen to retrieve email from userInfo via getState() (as the thunk already does) or pass it explicitly.

Additionally, there's a state key mismatch: the reducer updates state.userVerifyEmailSuccess and state.userVerifyEmailError (userSlice.js:180–186), but VerifyEmailScreen destructures userVerifySuccess and userVerifyError (line 23), causing success/error states to remain undefined in the UI.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/redux/asyncThunks/userThunks.js` around lines 65 - 91,
VerifyEmailScreen.jsx is dispatching verifyEmail with only { verificationCode:
token } causing the server "Email is required" error and also destructures wrong
state keys; update VerifyEmailScreen.jsx to supply the email (either pass email
from the component by reading it from the Redux userInfo state or ensure the
thunk uses getState to fill email) when calling the verifyEmail thunk, and fix
the state destructuring to match the reducer’s keys (use userVerifyEmailSuccess
and userVerifyEmailError instead of userVerifySuccess/userVerifyError) so
success/error UI reacts correctly.
client/src/components/ui/Admin/Dashboard/Table.jsx (3)

56-59: ⚠️ Potential issue | 🟡 Minor

Remove console.log debug statement before merging.

Line 58 has a leftover debug log: console.log('Id', row._id, 'Value', e.target.value). This will pollute the browser console in production. Clean it up or replace with a proper logging utility if needed for diagnostics.

🧹 Proposed fix
                        onChange={(e) => {
                          handleChange(row._id, e.target.value);
-                          console.log('Id', row._id, 'Value', e.target.value);
                        }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/ui/Admin/Dashboard/Table.jsx` around lines 56 - 59,
Remove the leftover console.log in the onChange handler inside Table.jsx: within
the onChange that calls handleChange(row._id, e.target.value), delete the
console.log('Id', row._id, 'Value', e.target.value) (or replace it with a proper
logging utility call such as logger.debug if you need diagnostics) so the
browser console is not polluted in production; ensure only handleChange(row._id,
e.target.value) remains in that handler.

53-85: ⚠️ Potential issue | 🟡 Minor

defaultValue on <select> conflicts with selected on <option> — React anti-pattern.

Lines 55 uses defaultValue={row[column]} on the <select>, while lines 63, 69, 75, and 81 also set selected={...} on individual <option> elements. React explicitly warns against this combination — defaultValue (or value) on <select> is the idiomatic approach, and selected on <option> is the legacy HTML way. Using both is redundant and may produce console warnings.

Remove the selected attributes from all <option> elements:

♻️ Proposed fix
                        <option
                          value={ORDER_STATUS.RECEIVED}
-                          selected={row[column] === ORDER_STATUS.RECEIVED}
                        >
                          {ORDER_STATUS.RECEIVED}
                        </option>
                        <option
                          value={ORDER_STATUS.IN_KITCHEN}
-                          selected={row[column] === ORDER_STATUS.IN_KITCHEN}
                        >
                          {ORDER_STATUS.IN_KITCHEN}
                        </option>
                        <option
                          value={ORDER_STATUS.OUT_FOR_DELIVERY}
-                          selected={row[column] === ORDER_STATUS.OUT_FOR_DELIVERY}
                        >
                          {ORDER_STATUS.OUT_FOR_DELIVERY}
                        </option>
                        <option
                          value={ORDER_STATUS.DELIVERED}
-                          selected={row[column] === ORDER_STATUS.DELIVERED}
                        >
                          {ORDER_STATUS.DELIVERED}
                        </option>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/ui/Admin/Dashboard/Table.jsx` around lines 53 - 85, The
select uses defaultValue={row[column]} while each option also sets
selected={...}, which is an anti-pattern; remove all selected={...} attributes
from the option elements and rely on the select's defaultValue (or convert the
select to a controlled component by replacing defaultValue with
value={row[column]} and keeping onChange/handleChange) so ORDER_STATUS options
only use value={ORDER_STATUS.*} and selection is driven by the select element;
update the JSX in the select block that renders options referencing
ORDER_STATUS, row[column], and handleChange accordingly.

24-30: ⚠️ Potential issue | 🟠 Major

React key warning: key must be on the outermost element in .map().

The key={column} is placed on <th> (line 26), but <th> is wrapped in a shorthand Fragment <> (line 25), which is the actual outermost element returned from .map(). Shorthand fragments cannot accept props like key. This will produce a React warning about missing keys.

Either use React.Fragment with a key, or simply remove the unnecessary fragment since there's only one child:

🐛 Proposed fix
              {columns.map((column) => (
-                <>
-                  <th key={column}>
-                    {column.replace(/([A-Z])/g, ' $1').trim()}
-                  </th>
-                </>
+                <th key={column}>
+                  {column.replace(/([A-Z])/g, ' $1').trim()}
+                </th>
              ))}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/ui/Admin/Dashboard/Table.jsx` around lines 24 - 30, The
map callback that renders columns uses a shorthand fragment wrapping a single
<th>, so the key={column} placed on <th> is not on the outermost element and
causes a React key warning; fix by removing the unnecessary fragment around the
<th> so the <th key={column}> is the top-level element returned from columns.map
(or alternatively replace the shorthand fragment with <React.Fragment
key={column}> if a wrapper is required), updating the code around columns.map
and the <th> element accordingly.
server/index.js (2)

88-93: ⚠️ Potential issue | 🟠 Major

Redundant body parsing — express.json() and bodyParser.json() do the same thing.

Since Express 4.16+, express.json() is a re-export of body-parser's JSON middleware. Having both means every request body is parsed twice — unnecessary CPU overhead and a potential source of subtle bugs if options diverge.

Remove one. Since you already use express.json(), drop bodyParser.json() and either convert the URL-encoded call to express.urlencoded() or keep bodyParser for that alone.

Suggested cleanup
 // Parse incoming JSON data
 app.use(express.json());

 // Parse incoming form data
-app.use(bodyParser.urlencoded({ extended: true }));
-app.use(bodyParser.json());
+app.use(express.urlencoded({ extended: true }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 88 - 93, Remove the redundant JSON parsing
middleware: keep app.use(express.json()) and delete the
app.use(bodyParser.json()) call; for URL-encoded parsing either switch
bodyParser.urlencoded to express.urlencoded by replacing bodyParser.urlencoded({
extended: true }) with express.urlencoded({ extended: true }) or keep bodyParser
for only urlencoded usage so the code uses a single JSON parser (express.json)
and a single urlencoded parser (express.urlencoded or bodyParser.urlencoded) to
avoid double-parsing.

163-168: ⚠️ Potential issue | 🟡 Minor

console.log is the second argument to app.listen, not the callback.

console.log(...) is evaluated immediately (before the server is actually listening) and its return value (undefined) is passed as the callback. This means the log fires before the port is bound. Use an arrow function wrapper so it executes when the server is ready.

Fix
 app.listen(
   PORT,
-  console.log(
-    `Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow.bold
-  )
+  () => {
+    console.log(
+      `Server running in ${process.env.NODE_ENV} mode on port ${PORT}`.yellow.bold
+    );
+  }
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 163 - 168, The current call to app.listen uses
console.log(...) as the second argument which executes immediately and passes
undefined as the callback; change the second argument to a function so the log
runs once the server is actually listening. Locate the app.listen invocation
(symbols: app.listen, PORT, process.env.NODE_ENV, console.log) and replace the
immediate console.log(...) with an arrow function wrapper that calls console.log
with the same message so the message prints only after the server is bound.
client/src/components/ui/CheckoutSteps/PlaceOrderStep.jsx (1)

34-80: 🛠️ Refactor suggestion | 🟠 Major

cartItems.reduce(...) is computed 5 times identically — extract to a variable.

The same reduction cartItems.reduce((acc, item) => acc + item.price * item.qty, 0) is repeated across the orderSummary array. This hurts readability and does redundant work on every render. Extract it once:

♻️ Suggested refactor
+ const itemsPrice = cartItems
+   ? cartItems.reduce((acc, item) => acc + item.price * item.qty, 0)
+   : 0;
+ const deliveryCharges = itemsPrice > 100 ? 0 : 10;
+ const salesTax = Number((0.15 * itemsPrice).toFixed(2));
+ const totalPrice = Math.round((itemsPrice + deliveryCharges + salesTax) * 100) / 100;
+
  const orderSummary = [
-   {
-     name: 'Items Price',
-     value: cartItems && cartItems.reduce((acc, item) => acc + item.price * item.qty, 0),
-   },
-   // ...repeated reduce calls...
+   { name: 'Items Price', value: itemsPrice },
+   { name: 'Delivery Charges', value: deliveryCharges },
+   { name: 'Sales Tax', value: salesTax },
+   { name: 'Total', value: totalPrice },
  ];
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/ui/CheckoutSteps/PlaceOrderStep.jsx` around lines 34 -
80, The repeated computation of cartItems total inside orderSummary (the
repeated cartItems.reduce((acc, item) => acc + item.price * item.qty, 0) used
for 'Items Price', 'Delivery Charges', 'Sales Tax', and 'Total') should be
extracted to a single variable (e.g., itemsPrice or cartTotal) before the
orderSummary definition; then use that variable in the objects and compute
delivery (0 or 10), salesTax (0.15 * itemsPrice rounded/formatted), and Total
(itemsPrice + delivery + salesTax) from those values instead of re-running the
reduce each time.
server/routes/inventoryRoutes.js (1)

35-47: ⚠️ Potential issue | 🔴 Critical

Route ordering bug: GET /low-stock will never be reached — it's shadowed by GET /:id.

Express evaluates routes in registration order. router.get('/:id', ...) on line 36 matches any path segment, including low-stock. So a GET /low-stock request will be handled by getStockById with req.params.id = 'low-stock', which will likely return a 404 or a cast error — never reaching getLowInventoryItems on line 47.

Move the static paths above the parameterized /:id routes:

🐛 Proposed fix
 // Public Routes

+// Inventory Alert Routes (must be above /:id to avoid shadowing)
+router.post('/check-alerts', protect, admin, checkAndSendAlerts);
+router.get('/low-stock', protect, admin, getLowInventoryItems);
+
 // Private Routes
 router.get('/', protect, getAllStocks);
 router.get('/:id', protect, getStockById);

 // Admin + Private Routes
 router.post('/', protect, admin, createStockValidation, validationHandler, createStock);
 router
   .route('/:id')
   .put(protect, admin, updateStockValidation, validationHandler, updateStockById)
   .delete(protect, admin, deleteStockById);

-// Inventory Alert Routes
-router.post('/check-alerts', protect, admin, checkAndSendAlerts);
-router.get('/low-stock', protect, admin, getLowInventoryItems);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/inventoryRoutes.js` around lines 35 - 47, The route ordering is
wrong: the parameterized route router.get('/:id', protect, getStockById) will
catch requests like '/low-stock' before the static handler getLowInventoryItems;
move the Inventory Alert Routes (router.post('/check-alerts', protect, admin,
checkAndSendAlerts) and router.get('/low-stock', protect, admin,
getLowInventoryItems)) so they are registered before any router.get('/:id', ...)
or router.route('/:id') declarations, keeping router.get('/', protect,
getAllStocks) where it is.
client/src/components/ui/Admin/Dashboard/Lists/OrdersList.jsx (1)

112-112: ⚠️ Potential issue | 🔴 Critical

orderList.length will throw a TypeError when orderList is null or undefined.

If the API call fails (or hasn't completed yet), orderList can be falsy. The loading guard on Line 99 won't protect you if loading finishes but the fetch errored out — you'll land in the else branch with orderList still being null.

🐛 Proposed fix
-           {orderList.length > 0 ? (
+           {orderList && orderList.length > 0 ? (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/ui/Admin/Dashboard/Lists/OrdersList.jsx` at line 112,
The conditional rendering currently uses orderList.length which throws if
orderList is null/undefined; change the check to guard for non-null array (e.g.,
use Array.isArray(orderList) && orderList.length > 0 or orderList?.length > 0)
in the JSX where orderList is used, and/or ensure the state that holds orderList
is initialized to an empty array so OrdersList rendering (and any map calls)
never run on null; update the condition around the JSX (the ternary that starts
with {orderList.length > 0 ?) to use the safe check and keep the existing
empty/error branch.
client/src/redux/slices/cartSlice.js (1)

166-183: ⚠️ Potential issue | 🟠 Major

Cart quantity can exceed the 10-item cap when adding the same pizza+size.

addToCart.fulfilled (line 176) does existItem.qty += item.qty, but there's no upper-bound check. If a user adds 6 of a pizza, then adds 6 more, qty becomes 12 — exceeding the qty <= 10 validation applied during localStorage hydration (line 71) and updateCartItemQuantity (line 110).

🐛 Proposed fix — clamp the quantity
       if (existItem) {
-        // Update quantity for existing item
-        existItem.qty += item.qty;
+        // Update quantity for existing item, clamped to max 10
+        existItem.qty = Math.min(existItem.qty + item.qty, 10);
       } else {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/redux/slices/cartSlice.js` around lines 166 - 183, The
addToCart.fulfilled handler allows existItem.qty to grow unbounded by doing
existItem.qty += item.qty, which can exceed the intended cap of 10; modify the
addToCart.fulfilled branch (the existItem handling inside the reducer) to clamp
the resulting quantity to the maximum allowed (e.g., Math.min(existingQty +
item.qty, 10)) before assigning existItem.qty, then continue to persist
state.cartItems to localStorage as before so hydration and
updateCartItemQuantity invariants are preserved.
client/src/redux/asyncThunks/adminThunks.js (1)

1-3: ⚠️ Potential issue | 🔴 Critical

Critical: Missing import of extractErrorMessage — every error path will throw ReferenceError.

The function is called at lines 25, 49, 79, 103, 127, 151, 180, and 204 throughout this file, but no import exists. Any API failure will crash the thunk before the error can propagate to the UI.

Add the missing import at the top:

 import { createAsyncThunk } from '@reduxjs/toolkit';
 import axios from 'axios';
+import { extractErrorMessage } from '../../utils/errorUtils';

Follow the pattern already established in userThunks.js and orderThunks.js, which import extractErrorMessage from errorUtils. This ensures consistency across your thunk files and guarantees proper error string extraction from API responses.

Minor: Line 131 has a typo — "Admin USer Details By ID" should be "Admin User Details By ID".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/redux/asyncThunks/adminThunks.js` around lines 1 - 3, Add the
missing import for extractErrorMessage at the top of adminThunks.js (following
the same import pattern used in userThunks.js and orderThunks.js, e.g. import {
extractErrorMessage } from the module where error utilities live) so all
try/catch blocks can call extractErrorMessage without throwing ReferenceError;
also fix the typo "Admin USer Details By ID" to "Admin User Details By ID" in
the thunk/comment that contains that string.

Comment on lines +196 to 213
{PIZZA_SIZE_OPTIONS.map((option, index) => (
<label
key={index}
htmlFor={size}
htmlFor={option.value}
className="flex flex-row items-center justify-center text-white rounded-full bg-orange-500 p-2"
>
<input
type="radio"
className="mr-2"
name="size"
id={size}
value={size}
id={option.value}
value={option.value}
onChange={(e) => setSize(e.target.value)}
required
/>
{size.charAt(0).toUpperCase() + size.slice(1)}
{option.label}
</label>
))}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

setSize is undefined — runtime crash on size selection.

The size radio group calls setSize(e.target.value) on line 208, but no size state is declared in this component (lines 22–30 define all local state, and size/setSize are absent). This will throw a ReferenceError when a user clicks any radio button.

Additionally, size is not included in the pizzaData payload (lines 60–69), so the entire size section appears to be dead UI. If admin-created pizzas no longer need a size (because size is now selected per-item at order time in PizzaItem.jsx), remove this section entirely. Otherwise, add the missing state.

Option A: Remove the dead size section entirely
-                <div className="w-full flex flex-col items-center justify-center bg-orange-100 rounded-md p-4">
-                  <h1 className="text-xl font-bold text-orange-300">Size</h1>
-                  <div className="flex flex-wrap items-center justify-between gap-2">
-                    {PIZZA_SIZE_OPTIONS.map((option, index) => (
-                      <label
-                        key={index}
-                        htmlFor={option.value}
-                        className="flex flex-row items-center justify-center text-white rounded-full bg-orange-500 p-2"
-                      >
-                        <input
-                          type="radio"
-                          className="mr-2"
-                          name="size"
-                          id={option.value}
-                          value={option.value}
-                          onChange={(e) => setSize(e.target.value)}
-                          required
-                        />
-                        {option.label}
-                      </label>
-                    ))}
-                  </div>
-                </div>

And remove the unused import:

-import { PIZZA_SIZE_OPTIONS } from '../../../../../constants';
Option B: Add the missing state if size is needed
   const [imageUrl, setImageUrl] = useState('');
+  const [size, setSize] = useState('medium');

And include size in the payload:

     const pizzaData = {
       name,
       description,
+      size,
       bases,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/components/ui/Admin/Dashboard/Modals/PizzaCreateModal.jsx` around
lines 196 - 213, The radio group references setSize/size but no state exists,
causing a runtime crash; either remove the dead size UI (delete the
PIZZA_SIZE_OPTIONS mapping block and any unused import related to sizes, and
ensure pizzaData payload stays unchanged) or add the missing state and include
it in the payload (declare const [size, setSize] = useState(...) inside
PizzaCreateModal, wire setSize as currently used on the inputs, and add size to
the pizzaData object sent on submit). Locate references to setSize, size,
PIZZA_SIZE_OPTIONS and pizzaData in PizzaCreateModal.jsx to apply the chosen
fix.

import { createAsyncThunk } from '@reduxjs/toolkit';
import axios from 'axios';

import { extractErrorMessage } from '../../utils/errorUtils';
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify which thunk files have local extractErrorMessage vs. import
echo "=== Searching for extractErrorMessage references in asyncThunks ==="
rg -n "extractErrorMessage" client/src/redux/asyncThunks/ --type=js -A 2

echo -e "\n=== Checking errorUtils.js ==="
rg -n "extractErrorMessage" client/src/utils/errorUtils.js --type=js -A 5

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 8774


🏁 Script executed:

#!/bin/bash
# Check the first 15 lines of each thunk file to see import statements
echo "=== inventoryThunks.js (first 15 lines) ==="
head -n 15 client/src/redux/asyncThunks/inventoryThunks.js

echo -e "\n=== adminThunks.js (first 15 lines) ==="
head -n 15 client/src/redux/asyncThunks/adminThunks.js

echo -e "\n=== pizzaThunks.js (first 30 lines) ==="
head -n 30 client/src/redux/asyncThunks/pizzaThunks.js

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 1553


🏁 Script executed:

#!/bin/bash
# Check if inventoryThunks and adminThunks have local extractErrorMessage implementations
echo "=== Checking inventoryThunks.js for extractErrorMessage definition ==="
rg -n "const extractErrorMessage|export.*extractErrorMessage" client/src/redux/asyncThunks/inventoryThunks.js

echo -e "\n=== Checking adminThunks.js for extractErrorMessage definition ==="
rg -n "const extractErrorMessage|export.*extractErrorMessage" client/src/redux/asyncThunks/adminThunks.js

echo -e "\n=== Checking if they import it ==="
head -n 10 client/src/redux/asyncThunks/inventoryThunks.js | grep -i import
head -n 10 client/src/redux/asyncThunks/adminThunks.js | grep -i import

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 423


🏁 Script executed:

#!/bin/bash
# Verify if inventoryThunks and adminThunks actually use extractErrorMessage
echo "=== inventoryThunks.js - lines where extractErrorMessage is used ==="
rg -n "extractErrorMessage" client/src/redux/asyncThunks/inventoryThunks.js -B 2

echo -e "\n=== adminThunks.js - lines where extractErrorMessage is used ==="
rg -n "extractErrorMessage" client/src/redux/asyncThunks/adminThunks.js -B 2

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 1853


🏁 Script executed:

#!/bin/bash
# Final verification: check the actual implementation in errorUtils to confirm what should be shared
echo "=== errorUtils.js - full extractErrorMessage implementation ==="
sed -n '15,30p' client/src/utils/errorUtils.js

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 544


Fix extractErrorMessage usage across thunks—missing imports and duplicate implementation.

pizzaThunks.js duplicates the extractErrorMessage implementation that should be imported from errorUtils.js, violating DRY. More critically, inventoryThunks.js and adminThunks.js call extractErrorMessage without importing or defining it, which causes ReferenceErrors at runtime.

All three files should import extractErrorMessage from errorUtils.js:

  • pizzaThunks.js: Remove the local implementation (lines 7–26) and add the import
  • inventoryThunks.js: Add import { extractErrorMessage } from '../../utils/errorUtils';
  • adminThunks.js: Add import { extractErrorMessage } from '../../utils/errorUtils';

This ensures consistent error handling across all thunks and eliminates the code duplication.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/redux/asyncThunks/userThunks.js` at line 4, Multiple thunk files
misuse or duplicate the error helper: remove the duplicated local
extractErrorMessage implementation in pizzaThunks.js and import the shared
helper instead, and add missing imports of extractErrorMessage from
../../utils/errorUtils in inventoryThunks.js and adminThunks.js so all thunks
call the same extractErrorMessage function; update the import lines in those
modules and delete the redundant implementation block (the local function in
pizzaThunks.js) to restore DRY and prevent ReferenceErrors.

Comment on lines 121 to +138
useEffect(() => {
if (pizzaCreateSuccess) {
dispatch(addToCart({ id: pizzaInfo._id, qty }));
if (pizzaCreateSuccess && pizzaInfo) {
const multiplier = getPizzaSizeMultiplier(size);
const basePrice =
selectedBases.reduce((sum, item) => sum + (item.price || 0), 0) +
selectedSauces.reduce((sum, item) => sum + (item.price || 0), 0) +
selectedCheeses.reduce((sum, item) => sum + (item.price || 0), 0) +
selectedVeggies.reduce((sum, item) => sum + (item.price || 0), 0);
const calculatedPrice = parseFloat((basePrice * multiplier).toFixed(2));

dispatch(addToCart({
id: pizzaInfo._id,
qty,
size,
calculatedPrice
}));
}
}, [dispatch, pizzaCreateSuccess, pizzaInfo, qty]);
}, [dispatch, pizzaCreateSuccess, pizzaInfo, qty, size, selectedBases, selectedSauces, selectedCheeses, selectedVeggies]);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Unstable dependency array will cause repeated addToCart dispatches.

The selectedBases, selectedSauces, selectedCheeses, and selectedVeggies arrays are state values that get new references on every toggle (via spread/filter in handleIngredientToggle). Once pizzaCreateSuccess is truthy, any subsequent ingredient state change will re-trigger this effect and dispatch addToCart again, because the success flag remains set.

Additionally, lines 124–128 duplicate the logic of calculateBasePrice(). Just call the function.

🐛 Proposed fix: use a ref to guard single execution and reuse the helper
+  const hasAddedToCart = useRef(false);
+
   useEffect(() => {
-    if (pizzaCreateSuccess && pizzaInfo) {
-      const multiplier = getPizzaSizeMultiplier(size);
-      const basePrice = 
-        selectedBases.reduce((sum, item) => sum + (item.price || 0), 0) +
-        selectedSauces.reduce((sum, item) => sum + (item.price || 0), 0) +
-        selectedCheeses.reduce((sum, item) => sum + (item.price || 0), 0) +
-        selectedVeggies.reduce((sum, item) => sum + (item.price || 0), 0);
-      const calculatedPrice = parseFloat((basePrice * multiplier).toFixed(2));
-      
+    if (pizzaCreateSuccess && pizzaInfo && !hasAddedToCart.current) {
+      hasAddedToCart.current = true;
+      const calculatedPrice = parseFloat(calculateTotalPrice());
       dispatch(addToCart({ 
         id: pizzaInfo._id, 
         qty,
         size,
         calculatedPrice
       }));
     }
-  }, [dispatch, pizzaCreateSuccess, pizzaInfo, qty, size, selectedBases, selectedSauces, selectedCheeses, selectedVeggies]);
+  }, [dispatch, pizzaCreateSuccess, pizzaInfo]);

Don't forget to add useRef to the React import on line 1.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/screens/User/UserCreateCustomPizzaScreen.jsx` around lines 121 -
138, The effect currently re-dispatches addToCart whenever ingredient arrays
change because selectedBases/selectedSauces/selectedCheeses/selectedVeggies are
unstable references; modify the effect in UserCreateCustomPizzaScreen.jsx to run
its addToCart only once after pizzaCreateSuccess by adding a playedOnce ref
(useRef) and checking it before dispatching, mark it true after dispatch, and
remove the ingredient arrays from the dependency-trigger path (keep dispatch,
pizzaCreateSuccess, pizzaInfo, qty, size); also replace the duplicated basePrice
calculation with a call to the existing calculateBasePrice() helper and remember
to add useRef to the React import.

Comment on lines +80 to +92
) : userVerifyError ? (
<>
<FaTimesCircle className="text-red-500 text-6xl mx-auto mb-4" />
<h1 className="text-2xl font-bold text-gray-800 mb-4">Verification Failed</h1>
<Message variant="error">{userVerifyError}</Message>
<p className="text-gray-600 mt-4 mb-6">
The verification link may have expired or is invalid. Please try again or contact support.
</p>
<Link to="/login">
<Button variant="primary">Go to Login</Button>
</Link>
</>
) : null}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: userVerifyError is likely an object — rendering it as JSX children will crash React.

extractErrorMessage (used in the thunk's rejectWithValue) returns a structured object like { code, message, status, details }. On Line 84, this object is passed as children to <Message>. React will throw: "Objects are not valid as a React child."

Proposed fix
-            <Message variant="error">{userVerifyError}</Message>
+            <Message variant="error">
+              {typeof userVerifyError === 'string'
+                ? userVerifyError
+                : userVerifyError?.message || 'Verification failed'}
+            </Message>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
) : userVerifyError ? (
<>
<FaTimesCircle className="text-red-500 text-6xl mx-auto mb-4" />
<h1 className="text-2xl font-bold text-gray-800 mb-4">Verification Failed</h1>
<Message variant="error">{userVerifyError}</Message>
<p className="text-gray-600 mt-4 mb-6">
The verification link may have expired or is invalid. Please try again or contact support.
</p>
<Link to="/login">
<Button variant="primary">Go to Login</Button>
</Link>
</>
) : null}
) : userVerifyError ? (
<>
<FaTimesCircle className="text-red-500 text-6xl mx-auto mb-4" />
<h1 className="text-2xl font-bold text-gray-800 mb-4">Verification Failed</h1>
<Message variant="error">
{typeof userVerifyError === 'string'
? userVerifyError
: userVerifyError?.message || 'Verification failed'}
</Message>
<p className="text-gray-600 mt-4 mb-6">
The verification link may have expired or is invalid. Please try again or contact support.
</p>
<Link to="/login">
<Button variant="primary">Go to Login</Button>
</Link>
</>
) : null}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/screens/User/VerifyEmailScreen.jsx` around lines 80 - 92,
userVerifyError is an object (from extractErrorMessage/rejectWithValue) and
passing it directly into the Message component will crash React; update the
render in VerifyEmailScreen to pass a string (e.g., userVerifyError.message or a
safe fallback like String(userVerifyError) or
extractErrorMessage(userVerifyError).message) to <Message> so it always receives
a primitive child, and ensure any usage of userVerifyError elsewhere (e.g.,
conditional rendering around userVerifyError) handles the object safely.

Comment on lines +15 to +48
export const extractErrorMessage = (error) => {
// Handle string errors (legacy format)
if (typeof error === 'string') {
return error;
}

// Handle new standardized format with error object
if (error && error.code && error.message) {
return error.message;
}

// Handle legacy format with status and message
if (error && error.message) {
return error.message;
}

// Handle axios error response
if (error && error.response && error.response.data) {
const data = error.response.data;

// New format: { success: false, error: { code, message, details } }
if (data.error && data.error.message) {
return data.error.message;
}

// Legacy format: { message: string }
if (data.message) {
return data.message;
}
}

// Fallback
return 'An unexpected error occurred. Please try again';
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Bug: Axios errors will never reach the response-body extraction path.

The check at line 27 (error.message) will match axios errors before the axios-specific check at line 32 is reached. Axios error objects always have a .message property (e.g., "Request failed with status code 400"), so the function returns that generic string instead of the meaningful API error from error.response.data.

The axios response check (lines 32-44) must come before the generic .message check.

🐛 Proposed fix — reorder the checks
 export const extractErrorMessage = (error) => {
   // Handle string errors (legacy format)
   if (typeof error === 'string') {
     return error;
   }
 
   // Handle new standardized format with error object
   if (error && error.code && error.message) {
     return error.message;
   }
 
-  // Handle legacy format with status and message
-  if (error && error.message) {
-    return error.message;
-  }
-
   // Handle axios error response
   if (error && error.response && error.response.data) {
     const data = error.response.data;
     
     // New format: { success: false, error: { code, message, details } }
     if (data.error && data.error.message) {
       return data.error.message;
     }
     
     // Legacy format: { message: string }
     if (data.message) {
       return data.message;
     }
   }
 
+  // Handle legacy format with status and message (after axios check)
+  if (error && error.message) {
+    return error.message;
+  }
+
   // Fallback
   return 'An unexpected error occurred. Please try again';
 };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const extractErrorMessage = (error) => {
// Handle string errors (legacy format)
if (typeof error === 'string') {
return error;
}
// Handle new standardized format with error object
if (error && error.code && error.message) {
return error.message;
}
// Handle legacy format with status and message
if (error && error.message) {
return error.message;
}
// Handle axios error response
if (error && error.response && error.response.data) {
const data = error.response.data;
// New format: { success: false, error: { code, message, details } }
if (data.error && data.error.message) {
return data.error.message;
}
// Legacy format: { message: string }
if (data.message) {
return data.message;
}
}
// Fallback
return 'An unexpected error occurred. Please try again';
};
export const extractErrorMessage = (error) => {
// Handle string errors (legacy format)
if (typeof error === 'string') {
return error;
}
// Handle new standardized format with error object
if (error && error.code && error.message) {
return error.message;
}
// Handle axios error response
if (error && error.response && error.response.data) {
const data = error.response.data;
// New format: { success: false, error: { code, message, details } }
if (data.error && data.error.message) {
return data.error.message;
}
// Legacy format: { message: string }
if (data.message) {
return data.message;
}
}
// Handle legacy format with status and message (after axios check)
if (error && error.message) {
return error.message;
}
// Fallback
return 'An unexpected error occurred. Please try again';
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/src/utils/errorUtils.js` around lines 15 - 48, The extractErrorMessage
function returns axios generic messages because the generic "if (error &&
error.message)" check runs before the axios-specific branch; move the axios
response inspection (the block checking error.response and error.response.data
and its data.error/data.message branches) so it executes before the generic
"error.message" check, ensuring axios response bodies are parsed first; update
extractErrorMessage accordingly and keep the remaining fallback and string
checks unchanged.

Comment on lines 217 to 257
const resetPassword = asyncHandler(async (req, res) => {
const { email, resetToken, newPassword, confirmNewPassword } = req.body;
const { email, resetToken, newPassword } = req.body;

const user = await User.findOne({ email });

if (!email || !resetToken || !newPassword || !confirmNewPassword) {
if (!user) {
res.status(404);
throw new Error('User Not Found!');
}

if (user.resetPasswordExpire <= Date.now()) {
res.status(400);
throw new Error('All Fields Are Required!');
} else {
if (emailValidator.validate(email)) {
const user = await User.findOne({
email,
});
throw new Error('Reset Token Expired!');
}

if (user) {
if (user.resetPasswordExpire > Date.now()) {
if (user.resetPasswordToken === resetToken) {
if (newPassword !== confirmNewPassword) {
res.status(400);
throw new Error('Passwords Do Not Match!');
} else {
if (newPassword.length < 8) {
res.status(400);
throw new Error('Password Must Be At Least 8 Characters Long!');
} else {
const salt = await bcrypt.genSalt(10);
hashedPassword = await bcrypt.hash(newPassword, salt);

user.password = hashedPassword;
user.resetPasswordToken = undefined;
user.resetPasswordExpire = undefined;

const updatedUser = await user.save();

if (updatedUser) {
res.status(200).json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
phoneNumber: updatedUser.phoneNumber,
address: updatedUser.address,
orders: updatedUser.orders,
isVerified: updatedUser.isVerified,
token: generateToken(updatedUser._id),
message: 'Password Reset Successful!',
});
} else {
res.status(400);
throw new Error('Error Resetting Password!');
}
}
}
} else {
res.status(400);
throw new Error('Invalid Reset Token!');
}
} else {
res.status(400);
throw new Error('Reset Token Expired!');
}
} else {
res.status(404);
throw new Error('User Not Found!');
}
} else {
res.status(400);
throw new Error('Invalid Email Address!');
}
if (user.resetPasswordToken !== resetToken) {
res.status(400);
throw new Error('Invalid Reset Token!');
}

const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(newPassword, salt);

user.password = hashedPassword;
user.resetPasswordToken = undefined;
user.resetPasswordExpire = undefined;

const updatedUser = await user.save();

res.status(200).json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
phoneNumber: updatedUser.phoneNumber,
address: updatedUser.address,
orders: updatedUser.orders,
isVerified: updatedUser.isVerified,
token: generateToken(updatedUser._id),
message: 'Password Reset Successful!',
});
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CRITICAL: Password reset can be bypassed without a valid reset token.

When a user has no pending reset (both resetPasswordToken and resetPasswordExpire are undefined), an attacker can send { email: "victim@email.com", newPassword: "pwned" } (omitting resetToken) and successfully reset the password:

  1. Line 227: undefined <= Date.now()false (NaN comparison), so the expiry check passes
  2. Line 232: undefined !== undefinedfalse, so the token check passes
  3. The code proceeds to hash and save the new password

This means any user's password can be reset without authorization if they don't have an active reset request.

🔒 Proposed fix — guard against missing/null token state
  const user = await User.findOne({ email });

  if (!user) {
    res.status(404);
    throw new Error('User Not Found!');
  }

+ if (!user.resetPasswordToken || !user.resetPasswordExpire) {
+   res.status(400);
+   throw new Error('No password reset was requested!');
+ }
+
  if (user.resetPasswordExpire <= Date.now()) {
    res.status(400);
    throw new Error('Reset Token Expired!');
  }

+ if (!resetToken) {
+   res.status(400);
+   throw new Error('Reset token is required!');
+ }
+
  if (user.resetPasswordToken !== resetToken) {
    res.status(400);
    throw new Error('Invalid Reset Token!');
  }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const resetPassword = asyncHandler(async (req, res) => {
const { email, resetToken, newPassword, confirmNewPassword } = req.body;
const { email, resetToken, newPassword } = req.body;
const user = await User.findOne({ email });
if (!email || !resetToken || !newPassword || !confirmNewPassword) {
if (!user) {
res.status(404);
throw new Error('User Not Found!');
}
if (user.resetPasswordExpire <= Date.now()) {
res.status(400);
throw new Error('All Fields Are Required!');
} else {
if (emailValidator.validate(email)) {
const user = await User.findOne({
email,
});
throw new Error('Reset Token Expired!');
}
if (user) {
if (user.resetPasswordExpire > Date.now()) {
if (user.resetPasswordToken === resetToken) {
if (newPassword !== confirmNewPassword) {
res.status(400);
throw new Error('Passwords Do Not Match!');
} else {
if (newPassword.length < 8) {
res.status(400);
throw new Error('Password Must Be At Least 8 Characters Long!');
} else {
const salt = await bcrypt.genSalt(10);
hashedPassword = await bcrypt.hash(newPassword, salt);
user.password = hashedPassword;
user.resetPasswordToken = undefined;
user.resetPasswordExpire = undefined;
const updatedUser = await user.save();
if (updatedUser) {
res.status(200).json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
phoneNumber: updatedUser.phoneNumber,
address: updatedUser.address,
orders: updatedUser.orders,
isVerified: updatedUser.isVerified,
token: generateToken(updatedUser._id),
message: 'Password Reset Successful!',
});
} else {
res.status(400);
throw new Error('Error Resetting Password!');
}
}
}
} else {
res.status(400);
throw new Error('Invalid Reset Token!');
}
} else {
res.status(400);
throw new Error('Reset Token Expired!');
}
} else {
res.status(404);
throw new Error('User Not Found!');
}
} else {
res.status(400);
throw new Error('Invalid Email Address!');
}
if (user.resetPasswordToken !== resetToken) {
res.status(400);
throw new Error('Invalid Reset Token!');
}
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(newPassword, salt);
user.password = hashedPassword;
user.resetPasswordToken = undefined;
user.resetPasswordExpire = undefined;
const updatedUser = await user.save();
res.status(200).json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
phoneNumber: updatedUser.phoneNumber,
address: updatedUser.address,
orders: updatedUser.orders,
isVerified: updatedUser.isVerified,
token: generateToken(updatedUser._id),
message: 'Password Reset Successful!',
});
});
const resetPassword = asyncHandler(async (req, res) => {
const { email, resetToken, newPassword } = req.body;
const user = await User.findOne({ email });
if (!user) {
res.status(404);
throw new Error('User Not Found!');
}
if (!user.resetPasswordToken || !user.resetPasswordExpire) {
res.status(400);
throw new Error('No password reset was requested!');
}
if (user.resetPasswordExpire <= Date.now()) {
res.status(400);
throw new Error('Reset Token Expired!');
}
if (!resetToken) {
res.status(400);
throw new Error('Reset token is required!');
}
if (user.resetPasswordToken !== resetToken) {
res.status(400);
throw new Error('Invalid Reset Token!');
}
const salt = await bcrypt.genSalt(10);
const hashedPassword = await bcrypt.hash(newPassword, salt);
user.password = hashedPassword;
user.resetPasswordToken = undefined;
user.resetPasswordExpire = undefined;
const updatedUser = await user.save();
res.status(200).json({
_id: updatedUser._id,
name: updatedUser.name,
email: updatedUser.email,
phoneNumber: updatedUser.phoneNumber,
address: updatedUser.address,
orders: updatedUser.orders,
isVerified: updatedUser.isVerified,
token: generateToken(updatedUser._id),
message: 'Password Reset Successful!',
});
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/controllers/userControllers.js` around lines 217 - 257, In
resetPassword, ensure a reset was actually requested and a token was provided:
first validate that req.body.resetToken exists and throw a 400 if missing, then
verify that user.resetPasswordToken and user.resetPasswordExpire are set
(truthy) before doing comparisons; replace the current expiry/token checks with
explicit guards like if (!user.resetPasswordToken || !user.resetPasswordExpire)
{ res.status(400); throw new Error('No reset request found'); } and if
(user.resetPasswordExpire < Date.now()) { ... } and then compare
user.resetPasswordToken === resetToken to prevent undefined===undefined bypass.

Comment on lines +44 to +57
// Security Headers with Helmet
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'", "'unsafe-inline'", "https://checkout.razorpay.com"],
imgSrc: ["'self'", "data:", "https:"],
connectSrc: ["'self'", "https://api.razorpay.com"],
frameSrc: ["'self'", "https://api.razorpay.com"]
}
},
crossOriginEmbedderPolicy: false
}));
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

CSP references Razorpay but the project migrated to Stripe — payment checkout will break.

The scriptSrc, connectSrc, and frameSrc directives all whitelist razorpay.com domains. According to the PR, the payment processor was migrated to Stripe. When CSP is enforced by the browser, Stripe's JS SDK (js.stripe.com) and API calls (api.stripe.com) will be blocked, breaking the checkout flow.

Fix: Update CSP for Stripe
 app.use(helmet({
   contentSecurityPolicy: {
     directives: {
       defaultSrc: ["'self'"],
       styleSrc: ["'self'", "'unsafe-inline'"],
-      scriptSrc: ["'self'", "'unsafe-inline'", "https://checkout.razorpay.com"],
+      scriptSrc: ["'self'", "'unsafe-inline'", "https://js.stripe.com"],
       imgSrc: ["'self'", "data:", "https:"],
-      connectSrc: ["'self'", "https://api.razorpay.com"],
-      frameSrc: ["'self'", "https://api.razorpay.com"]
+      connectSrc: ["'self'", "https://api.stripe.com"],
+      frameSrc: ["'self'", "https://js.stripe.com"]
     }
   },
   crossOriginEmbedderPolicy: false
 }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/index.js` around lines 44 - 57, The contentSecurityPolicy in the
helmet config currently whitelists Razorpay domains; update the directives in
the helmet({ contentSecurityPolicy: { directives: { ... } } }) block to allow
Stripe instead: replace entries in scriptSrc to include "https://js.stripe.com"
(and remove Razorpay), update connectSrc to include "https://api.stripe.com"
(and remove Razorpay), and update frameSrc to include Stripe's checkout/frame
domain such as "https://checkout.stripe.com" (removing Razorpay); ensure any
other Razorpay references in directives (scriptSrc, connectSrc, frameSrc) are
removed so Stripe SDK and API calls are permitted.

"razorpay": "^2.9.2"
"razorpay": "^2.9.2",
"stripe": "^20.3.1",
"uuid": "^13.0.0"
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for uuid imports/requires in the server directory
rg -n "require.*uuid|import.*uuid" server --type=js -A 2

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 358


🌐 Web query:

uuid@13 CommonJS support ECMAScript modules

💡 Result:

uuid@13 is ESM-only. CommonJS (require) is not supported (CommonJS support was removed starting in uuid@12). [1], [2]

What to do:

  • Use ESM imports:

    import { v4 as uuidv4 } from 'uuid';

    [1]

  • If your project is CommonJS and you can’t migrate:

    • Pin to uuid@11.x (last line with CommonJS support), because uuid@12 removed it. [2]
    • Or switch your code to load it via ESM (e.g., dynamic import()), rather than require. [1]

uuid@13 drops CommonJS support — will break require() at runtime.

The server's errorMiddlewares.js uses const { v4: uuidv4 } = require('uuid'), which will fail under uuid@13 (ESM-only since v12). This is a startup blocker.

Pin to "^11.0.0" (the last CommonJS-compatible major) or migrate the server to ESM.

🐛 Proposed fix (maintain CJS compatibility)
-    "uuid": "^13.0.0"
+    "uuid": "^11.0.0"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"uuid": "^13.0.0"
"uuid": "^11.0.0"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/package.json` at line 41, The package.json currently depends on
uuid@13 which is ESM-only and will break the CommonJS usage in
errorMiddlewares.js (see the require('uuid') / const { v4: uuidv4 } =
require('uuid') statement); fix by pinning the uuid dependency to a
CommonJS-compatible major (change the package.json entry for "uuid" to
"^11.0.0") or alternatively migrate the server to ESM and update all requires
accordingly—prefer the quick fix of pinning to ^11.0.0 to restore runtime
compatibility with errorMiddlewares.js.

Comment on lines +32 to +33
// Public Routes (Stripe webhook needs raw body)
router.post('/stripe-webhook', express.raw({ type: 'application/json' }), handleStripeWebhook);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find main app files and check middleware/route setup
echo "=== Looking for main app files ==="
fd -t f -e js "^(server|app|index)\.js$" --max-results 5

echo -e "\n=== Searching for middleware setup patterns ==="
rg -n "express\.(json|raw|urlencoded)|bodyParser|orderRoutes" --type js -A 2 -B 1 | head -100

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 1690


🏁 Script executed:

#!/bin/bash
# Get more context on the exact middleware order in server/index.js
sed -n '85,160p' server/index.js | cat -n

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 2737


🏁 Script executed:

#!/bin/bash
# Verify the handleStripeWebhook function to see if it actually needs the raw body
grep -n "handleStripeWebhook\|stripe.webhooks.constructEvent" server/routes/orderRoutes.js -A 5 -B 2 | head -30

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 717


🏁 Script executed:

#!/bin/bash
# Find the controller file where handleStripeWebhook is implemented
fd -t f -name "*orderController*" -o -name "*order.controller*"

echo "=== Searching for handleStripeWebhook implementation ==="
rg "handleStripeWebhook|stripe\.webhooks\.constructEvent" --type js -A 8 -B 1

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 3270


🏁 Script executed:

#!/bin/bash
# Get the complete handleStripeWebhook implementation to understand what req.body should contain
sed -n '40,75p' server/controllers/orderControllers.js | cat -n

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 1398


🏁 Script executed:

#!/bin/bash
# Find the exact line number of handleStripeWebhook definition
rg "const handleStripeWebhook" server/controllers/orderControllers.js

# Then read that section
echo "=== Full handleStripeWebhook implementation ==="
rg "const handleStripeWebhook" -A 30 server/controllers/orderControllers.js

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 1151


🏁 Script executed:

#!/bin/bash
# Confirm the issue: stripe.webhooks.constructEvent expects a Buffer, not a parsed object
# Let's check what type req.body will be after express.json() processes it
echo "=== Checking Stripe documentation context ==="
rg "stripe.webhooks.constructEvent|req.body" server/controllers/orderControllers.js -B 2 -A 2

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 1036


🏁 Script executed:

#!/bin/bash
# Final verification: confirm req.body type after express.json() processes it
# Check if there's any special handling or if the raw middleware can override
echo "=== Verifying middleware execution order ==="
echo "Order of operations:"
echo "1. Request arrives at /api/orders/stripe-webhook"
echo "2. Global express.json() from line 89 runs (consumes & parses body)"
echo "3. Request reaches orderRoutes router (mounted at line 151)"
echo "4. Route-level express.raw() tries to read raw buffer (TOO LATE)"
echo "5. req.body is already a parsed JavaScript object"
echo ""
echo "=== Stripe.webhooks.constructEvent signature ==="
rg "constructEvent" -A 1 server/controllers/orderControllers.js

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 556


Stripe webhook signature verification will fail with current middleware ordering.

When a POST arrives at /api/orders/stripe-webhook, the global express.json() middleware (line 89 in server/index.js) consumes the request body stream before the router is mounted (line 151). This means express.raw() on this route receives an already-parsed JavaScript object in req.body, not the raw buffer that stripe.webhooks.constructEvent() requires for signature verification. The webhook will reject with a cryptic error on every call.

Restructure your app.js/index.js to handle the webhook route before global body parsers:

// Mount webhook route first (raw body needed)
app.use('/api/orders/stripe-webhook', express.raw({ type: 'application/json' }));

// Then register global parsers
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(bodyParser.json());

// Then mount the rest of the routers
app.use('/api/orders', orderRoutes); // webhook now won't conflict

Alternatively, exclude the webhook path from global parsing (less clean but works if you can't reorder):

app.use(express.json({ skip: (req) => req.path === '/api/orders/stripe-webhook' }));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/routes/orderRoutes.js` around lines 32 - 33, The Stripe webhook route
is failing signature verification because global body parsers run before the
route, so express.raw({ type: 'application/json' }) in
router.post('/stripe-webhook', ...) receives a parsed object instead of the raw
buffer; fix by registering the webhook raw middleware/path on the Express app
before mounting global parsers or by skipping the webhook path in the global
parser configuration: ensure the '/api/orders/stripe-webhook' raw middleware is
applied prior to calling express.json()/bodyParser middleware (or use
express.json({ skip: req => req.path === '/api/orders/stripe-webhook' })), and
then mount orderRoutes so handleStripeWebhook receives the original raw body for
stripe.webhooks.constructEvent().

Comment on lines +57 to +60
// Add indexes for frequently queried fields
pizzaSchema.index({ createdBy: 1 });
pizzaSchema.index({ price: 1 });
pizzaSchema.index({ createdAt: -1 });
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's check the pizzaSchema.js file to confirm the size field removal
cat -n server/schemas/pizzaSchema.js

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 1681


🏁 Script executed:

#!/bin/bash
# Search for any remaining references to pizza 'size' in controllers, routes, or frontend code
rg -n --type js -C 2 'pizza.*size|size.*pizza' \
  --glob '!**/node_modules/**' \
  --glob '!**/schemas/**'

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 4032


🏁 Script executed:

#!/bin/bash
# Search more broadly for 'size' references in non-schema files
rg -n --type js '\bsize\b' \
  --glob '!**/node_modules/**' \
  --glob '!**/schemas/**' | head -50

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 6428


🏁 Script executed:

#!/bin/bash
# Look for migration-related files
find . -type f \( -name "*migrat*" -o -name "*migration*" \) \
  ! -path '*/node_modules/*' | head -20

Repository: itxSaaad/pizza-palette-app-mern-OIBSIP-task-1

Length of output: 71


Size field removal breaks order and cart management — critical functionality failure.

The size field was removed from the schema, but active code throughout the application still depends on it:

  • Order creation (server/controllers/orderControllers.js, lines 36, 56, 347, 367, 407) directly accesses item.size when processing orders
  • Cart deduplication (client/src/redux/slices/cartSlice.js, line 172) checks x.size === item.size to prevent duplicate entries
  • Pizza filtering (server/controllers/pizzaControllers.js, lines 24-25) filters by size from query parameters
  • Frontend state (client/src/screens/User/UserCreateCustomPizzaScreen.jsx) manages size selection and validates it before submission
  • Price calculation relies on size multipliers from server/constants/pizzaSizes.js

All size accesses will now return undefined, silently breaking order placement, cart logic, and price calculations. No migration script exists to handle existing documents. Either restore the size field to the schema or remove all references to it and refactor price handling to use a different mechanism (e.g., base price + size multiplier lookup table).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/schemas/pizzaSchema.js` around lines 57 - 60, The schema no longer
defines the size field but many parts of the app still read/write it
(pizzaSchema, orderControllers.js where item.size is used, client cartSlice
deduplication, pizzaControllers query filtering, UserCreateCustomPizzaScreen,
and price calculation via server/constants/pizzaSizes.js), causing undefined
behavior; fix by either restoring a typed/enumerated size field to pizzaSchema
(with allowed values and a sensible default and keep the index if needed) and
add a one-off migration to backfill existing pizza documents with a default
size, or remove all size usages and refactor price logic to use basePrice +
explicit multiplier lookups (update orderControllers, cartSlice dedupe logic,
pizzaControllers filter parsing, and frontend UserCreateCustomPizzaScreen
validation) so every place references the new pricing mechanism consistently.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant