Skip to content

Conversation

@CijeTheCreator
Copy link
Collaborator

@CijeTheCreator CijeTheCreator commented Aug 7, 2025

Update Shop Categories from Brand-Based to Product-Type-Based

Summary

This PR transforms the shop categorization system from brand-based categories (THRIFT, LUXURY, SENATORS) to product-type-based categories (POLOS, T-SHIRTS, JEANS, SHIRTS), focusing the entire shopping experience on thrift products organized by clothing type.

Changes Made

🛍️ Shop Navigation Component

File: src/components/pages/shop/shop-navigation-bar.component.tsx

  • Updated navigation tabs from brand-based to product-type categories
  • Changed categories: ["ALL", "THRIFT", "LUXURY", "SENATORS"]["ALL", "POLOS", "T-SHIRTS", "JEANS", "SHIRTS"]
  • Maintained existing animation and styling functionality

🏠 Home Products Carousel

File: src/app/_Home/components/Products.tsx

  • Updated SECTIONS array to use new product-type categories
  • Changed carousel sections from brand categories to product types
  • Updated imports to use new product data arrays
  • Preserved all existing animations and carousel functionality

📦 Products Data Structure

File: src/data/products.ts

  • Replaced brand-based arrays with product-type arrays:
    • polosProducts - Classic and premium polo shirts
    • tshirtsProducts - Basic tees, logo tees, vintage and graphic styles
    • jeansProducts - Slim fit, straight cut, distressed, and bootcut styles
    • shirtsProducts - Dress shirts, casual shirts, flannel, and oxford styles
  • Updated pricing to reflect thrift-focused affordable pricing (₦15,000 - ₦50,000 range)
  • Maintained legacy exports for backward compatibility
  • Updated product images to use appropriate icons for each category

Technical Details

Category Mapping

  • ALL: Shows all thrift products across categories
  • POLOS: Filter by category === "polos"
  • T-SHIRTS: Filter by category === "t-shirts"
  • JEANS: Filter by category === "jeans"
  • SHIRTS: Filter by category === "shirts"

Data Architecture

  • Static Data: Updated with thrift-focused product arrays
  • Sanity Integration: Existing queries support new category filtering without modification
  • Component Logic: Updated filtering works with both shop and home components
  • Backward Compatibility: Legacy exports ensure no breaking changes

Affected Components

  1. Shop Navigation Bar: Category tabs updated
  2. Home Products Carousel: Section labels and data sources updated
  3. Shop Page: No changes needed - existing filtering logic supports new categories
  4. Product Cards: No changes needed - display logic unchanged

Testing Checklist

  • Navigation: All category tabs work correctly with new categories
  • Filtering: Each category shows appropriate products
  • Static Data: New product arrays load correctly
  • Sanity Integration: Existing queries compatible with new category structure
  • Responsive Design: Mobile and desktop layouts maintained
  • Backward Compatibility: Legacy exports prevent breaking changes

Commits

  1. Update shop navigation from brand-based to product-type categories - Navigation component changes
  2. Update products data to focus on thrift product types - Data structure and product arrays
  3. Update home products carousel to use product-type categories - Home page carousel updates

Breaking Changes: None (backward compatibility maintained)
Database Changes: None required

Summary by CodeRabbit

  • New Features

    • Introduced a comprehensive admin dashboard with analytics, order, delivery fee, message, and user management interfaces.
    • Added interactive product carousel, real-time stock management, and horizontally scrolling fashion showcase to the home page.
    • Implemented newsletter subscription with email validation, rate limiting, and welcome emails.
    • Added about page with team, vision, and brand story sections.
    • Provided health check endpoints for system and cache status.
    • Enabled checkout session management and validation.
  • Bug Fixes

    • Improved error handling and user feedback across forms and API interactions.
  • Documentation

    • Expanded README with detailed project overview, setup instructions, and contribution guidelines.
    • Added environment variables reference and deployment checklist.
  • Chores

    • Updated dependencies, scripts, and configuration files for testing, styling, and performance optimization.
    • Added font optimization script for better web performance.

CMI-James and others added 30 commits January 9, 2025 10:03
feat: created the navbar and its components
fix: added flex to the div of the navbar
feat: created footer and its components
Feature: Build About Us Page and Remodel Typography Component
davedumto and others added 20 commits August 1, 2025 19:30
Merge branch 'main' of https://github.com/himspired1/himspired into feat-implement-flexible-logistics-pricing
…ble-logistics-pricing

Feat implement flexible logistics pricing
- Changed shop navigation categories from THRIFT/LUXURY/SENATORS to POLOS/T-SHIRTS/JEANS/SHIRTS
- This focuses the shop on thrift products with product-type-based filtering

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Replaced brand-based product arrays with product-type arrays
- Added polosProducts, tshirtsProducts, jeansProducts, shirtsProducts
- Updated product data with appropriate thrift-focused items and pricing
- Maintained legacy exports for backward compatibility

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
- Changed SECTIONS array from brand categories to product types
- Updated imports to use new product arrays
- Modified section IDs and labels to match new categorization
- Carousel now displays POLOS/T-SHIRTS/JEANS/SHIRTS instead of THRIFTS/LUXURY/VINTAGE/MODERN

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
@netlify
Copy link

netlify bot commented Aug 7, 2025

👷 Deploy request for aquamarine-meringue-a3a1e5 pending review.

Visit the deploys page to approve it

Name Link
🔨 Latest commit ff60332

@coderabbitai
Copy link

coderabbitai bot commented Aug 7, 2025

Walkthrough

This update introduces a comprehensive suite of new features, configuration files, documentation, and major backend and frontend components for a fashion e-commerce platform. It adds detailed admin dashboards, analytics, user and order management, newsletter and contact APIs, delivery fee management, testing infrastructure, and extensive documentation. Numerous React components and API endpoints are implemented, along with supporting scripts, configuration, and utility enhancements.

Changes

Cohort / File(s) Change Summary
Documentation & Config
.gitignore, README.md, components.json, docs/ENVIRONMENT_VARIABLES.md, jest.config.js, jest.setup.js, next.config.ts, package.json, postcss.config.mjs, scripts/optimize-fonts.sh
Adds and expands ignore patterns, rewrites and extends project documentation, introduces component and testing configurations, documents environment variables, enhances Next.js and PostCSS configs, and adds a font optimization script.
Home Page Components
src/app/_Home/components/Fashion.tsx, .../ImageStack.tsx, .../Newsletter.tsx, .../Owners.tsx, .../Products.tsx, .../products/components/ProductSection.tsx
Introduces and refactors homepage UI components: animated fashion section, image grid, async newsletter form, owners section, product carousel with real-time stock and reservation logic, and a detailed product section with extensive state management.
About Page
src/app/about/about-page.tsx, .../page.tsx
Adds a new "About Us" page component and corresponding page entry, with SEO metadata and brand story.
Admin Dashboard & Pages
src/app/admin/page.tsx, .../analytics/page.tsx, .../delivery-fees/page.tsx, .../login/page.tsx, .../messages/page.tsx, .../orders/page.tsx, .../orders/[orderId]/page.tsx, .../users/page.tsx
Implements admin dashboard, analytics, delivery fee management, login, message management, orders listing and detail, and admin user management pages, each with authentication, data fetching, UI, and interactivity.
Admin API: Analytics & Auth
src/app/api/admin/analytics/cache/route.ts, .../customers/route.ts, .../delivery/route.ts, .../orders/route.ts, .../revenue/route.ts, .../auth/route.ts, .../force-cleanup/route.ts, .../users/route.ts, .../users/[id]/route.ts, .../verify/route.ts
Adds API endpoints for admin analytics (revenue, orders, delivery, customers, cache), authentication and session management, admin user CRUD, and force cleanup with rate limiting and validation.
Checkout & Newsletter APIs
src/app/api/checkout/session/route.ts, .../validate/route.ts, .../newsletter/route.ts, .../newsletter/unsubscribe/route.ts
Implements checkout session management and validation endpoints, newsletter subscription and unsubscription with rate limiting and email confirmation.
Contact & Orders APIs
src/app/api/contact/route.ts, .../[messageId]/route.ts, .../orders/route.ts, .../[orderId]/route.ts, .../send-confirmation/route.ts, .../send-custom-email/route.ts, .../send-email/route.ts
Adds endpoints for contact message CRUD and replies, order creation and retrieval with file upload, order status updates, confirmation and custom email sending, and payment issue notifications.
Delivery Fees APIs
src/app/api/delivery-fees/route.ts, .../[state]/route.ts
Provides endpoints for CRUD and retrieval of delivery fees per Nigerian state, with validation and Redis-backed rate limiting.
Health Check APIs
src/app/api/health/route.ts, .../cache/route.ts
Introduces endpoints for system and cache health checks, reporting on database, Redis, and memory usage.

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant HomePage as HomePage Components
  participant API
  participant AdminPanel
  participant Database
  participant Cache
  participant EmailService

  User->>HomePage: Visit homepage
  HomePage->>API: Fetch products, newsletter, etc.
  API->>Database: Query product data / stock
  Database-->>API: Return product data
  API-->>HomePage: Return products, stock, etc.

  User->>HomePage: Submit newsletter form
  HomePage->>API: POST /api/newsletter
  API->>Cache: Check rate limits
  API->>EmailService: Send welcome email
  API-->>HomePage: Success or error response

  User->>AdminPanel: Login as admin
  AdminPanel->>API: POST /api/admin/auth
  API->>Database: Verify credentials
  Database-->>API: Auth result
  API-->>AdminPanel: Set auth cookie, respond

  AdminPanel->>API: GET /api/admin/analytics/*
  API->>Cache: Check analytics cache
  API->>Database: Aggregate analytics data
  Database-->>API: Analytics results
  API->>Cache: Store analytics cache
  API-->>AdminPanel: Return analytics data

  AdminPanel->>API: PATCH /api/delivery-fees/[state]
  API->>Database: Update delivery fee
  Database-->>API: Update result
  API-->>AdminPanel: Success or error response

  User->>HomePage: Add product to cart, checkout
  HomePage->>API: POST /api/orders
  API->>Database: Create order, update stock
  API->>EmailService: Send order confirmation
  API-->>HomePage: Order confirmation
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120+ minutes

Possibly related PRs

  • chore: improving seo #40: Adds and restructures the AboutPage component and its usage, directly overlapping with the new about page components in this PR.
  • Feat implement flexible logistics pricing #41: Implements delivery fees and logistics pricing, including scripts, dependencies, and admin UI, which matches the delivery fee management features in this PR.
  • Feat making prod fixes1 #35: Enhances the ProductSection.tsx component with real-time stock polling and reservation logic, which is also a key part of this PR's product management features.

Poem

In the garden of code where new features bloom,
Rabbits hop swiftly from dashboard to room.
APIs sprout, analytics grow tall,
Admins and users—there’s something for all!
With newsletters, orders, and stock that’s alive,
This fluffy release helps e-commerce thrive.
🐇✨

Note

⚡️ Unit Test Generation is now available in beta!

Learn more here, or try it out under "Finishing Touches" below.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 55

🧹 Nitpick comments (41)
src/app/about/about-page.tsx (1)

11-11: Consider extracting magic numbers to constants.

The hardcoded margin values lg:mt-[13em] mt-[7em] and pb-[120.93px] could be extracted to named constants for better maintainability.

const LAYOUT_MARGINS = {
  topDesktop: 'lg:mt-[13em]',
  topMobile: 'mt-[7em]',
  bottom: 'pb-[120.93px]'
} as const;

// Then use:
<div className={`w-full min-h-screen ${LAYOUT_MARGINS.topDesktop} ${LAYOUT_MARGINS.topMobile} ${LAYOUT_MARGINS.bottom}`}>
src/app/api/newsletter/unsubscribe/route.ts (1)

4-52: Consider adding rate limiting for security.

While the implementation is solid with proper validation and error handling, consider adding rate limiting similar to the subscription endpoint to prevent abuse of the unsubscribe functionality.

The subscription route includes IP-based rate limiting. Consider implementing similar protection:

// Add rate limiting logic similar to subscription route
const ip = getClientIp(req);
// ... rate limiting implementation
src/app/api/admin/verify/route.ts (1)

32-32: Consider making the role configurable.

The hard-coded role "admin" may limit flexibility if you need different admin role types in the future.

Consider making the role configurable:

-        role: "admin",
+        role: user.role || "admin",
src/app/api/admin/force-cleanup/route.ts (1)

6-12: Consider more robust IP extraction

The IP extraction could handle additional headers for better compatibility with various proxy configurations.

Consider adding support for additional headers:

function getClientIp(req: NextRequest): string {
  return (
    req.headers.get("x-forwarded-for") ||
    req.headers.get("x-real-ip") ||
+   req.headers.get("cf-connecting-ip") || // Cloudflare
+   req.headers.get("x-client-ip") ||
    "unknown"
  );
}
src/app/_Home/components/ImageStack.tsx (1)

13-44: Extract repeated CSS classes

Consider extracting common CSS classes to reduce repetition and improve maintainability.

+const imageBaseClasses = "w-full object-cover";
+const constrainedImageClasses = `${imageBaseClasses} max-h-[421px] md:max-h-[1024px] md:h-full object-top`;
+const fullImageClasses = `${imageBaseClasses} h-full`;

// Then use in components:
-          className="w-full h-full object-cover"
+          className={fullImageClasses}
src/app/_Home/components/Products.tsx (3)

142-150: Simplify loading state condition

The loading check is redundant. If loading is true, show the skeleton regardless of the array length.

-  if (loading && clothesByCategory.length === 0) {
+  if (loading) {

57-57: Remove commented code

Remove unused commented code for better code cleanliness.

-  // const { clothes, loading, error, refetch } = useClothes();

60-63: Remove empty useEffect

This empty useEffect with just a comment serves no purpose.

-  // Calculate items to show based on screen width
-  useEffect(() => {
-    // Removed itemsToShow logic; not needed with ProductCard
-  }, []);
src/app/admin/users/page.tsx (1)

6-11: Consider using shared type definition

The AdminUser interface duplicates the structure from src/models/admin.ts. Consider importing and using a shared type to maintain consistency.

You could create a shared client-side type that extends or picks from the backend model, excluding server-only fields like passwordHash.

src/app/admin/login/page.tsx (1)

33-36: Improve error handling in authentication check

The catch block only logs to console. Consider handling network errors more gracefully.

 } catch {
   // Not authenticated, stay on login page
-  console.log("Not authenticated, staying on login page");
+  // Silently handle - user is not authenticated
 }

If network errors need to be distinguished from authentication failures, consider checking the error type.

jest.config.js (1)

35-35: Consider using a more maintainable approach for transformIgnorePatterns

The hardcoded list of packages in transformIgnorePatterns may become difficult to maintain as dependencies evolve.

Consider extracting this to a constant or configuration:

+const esmPackages = ["jose", "@jose", "@panva", "@peculiar"];
+
 const customJestConfig = {
   // ... other config
-  transformIgnorePatterns: ["node_modules/(?!(jose|@jose|@panva|@peculiar)/)"],
+  transformIgnorePatterns: [`node_modules/(?!(${esmPackages.join("|")})/)`],
src/app/admin/orders/[orderId]/page.tsx (1)

4-6: Move export statement to follow React conventions

The export const dynamic = "force-dynamic" is placed between the main imports and should be moved to maintain conventional import organization.

 "use client";
 import { useState, useEffect, useCallback } from "react";
-
-// Force dynamic rendering to avoid build issues
-export const dynamic = "force-dynamic";
 import { useParams, useRouter } from "next/navigation";
 import { motion } from "framer-motion";
+
+// Force dynamic rendering to avoid build issues
+export const dynamic = "force-dynamic";
src/app/_Home/components/Fashion.tsx (2)

30-37: Remove unused viewport tracking logic

The onUpdate callback calculates and stores viewport segments but this value is never used. Consider removing it to simplify the code.

         scrub: 0.6,
         pin: true,
-        onUpdate: (self) => {
-          const scrollPosition = Math.round(self.progress * 300); // 300vw is the total width
-          const currentViewport = Math.floor(scrollPosition / 100); // Determine the current 100vw scroll step
-
-          if (currentViewport !== lastLoggedViewport.current) {
-            lastLoggedViewport.current = currentViewport;
-          }
-        },
       },

If this tracking is intended for future use, add a comment explaining its purpose.


55-56: Improve text responsiveness

The text sizing uses fixed breakpoint classes but the word spacing is hardcoded. Consider making it responsive as well.

 <h1
-  className="text-7xl md:text-40 lg:text-50 xl:text-62.5 font-bold whitespace-nowrap font-moon space-x-20 ml-[3em] md:ml-0"
-  style={{ wordSpacing: "10rem" }}
+  className="text-7xl md:text-40 lg:text-50 xl:text-62.5 font-bold whitespace-nowrap font-moon ml-[3em] md:ml-0"
+  style={{ wordSpacing: "clamp(2rem, 10vw, 10rem)" }}
 >
src/app/admin/orders/page.tsx (2)

4-6: Move export statement to follow React conventions

Same issue as in the order details page - the export statement should be moved for better import organization.

 "use client";
 import { useState, useEffect, useCallback } from "react";
-
-// Force dynamic rendering to avoid build issues
-export const dynamic = "force-dynamic";
 import { useRouter } from "next/navigation";
+
+// Force dynamic rendering to avoid build issues
+export const dynamic = "force-dynamic";

121-129: Optimize useCallback dependencies

The fetchOrders callback includes setter functions in its dependency array which are stable and don't need to be included.

 },
 [
   router,
-  setOrders,
-  setPagination,
   calculateStats,
   filterOrdersByStatus,
   filter,
-  setLoading,
 ]
src/app/_Home/components/Newsletter.tsx (1)

19-19: Redundant submission check.

The if (isSubmitting) return; check is redundant since the form elements are disabled when isSubmitting is true.

Remove the redundant check:

-    if (isSubmitting) return;
-
src/app/api/checkout/session/route.ts (1)

77-79: Session management architecture concern.

The comments indicate session management is client-side only, which raises security concerns for checkout processes.

Consider implementing server-side session storage for checkout processes to prevent tampering with cart contents and ensure data integrity during the checkout flow.

src/app/api/health/route.ts (2)

12-12: Potential information disclosure.

Exposing the NODE_ENV environment variable in health check responses could provide attackers with information about the deployment environment.

Consider removing or conditionally including environment information:

-    environment: process.env.NODE_ENV,
+    environment: process.env.NODE_ENV === "production" ? "production" : "non-production",

46-53: Memory usage information could aid attacks.

Exposing detailed memory usage information could help attackers plan memory exhaustion attacks or understand system capacity.

Consider limiting memory information in production:

     // Check memory usage
     const memUsage = process.memoryUsage();
+    const isProduction = process.env.NODE_ENV === "production";
     healthChecks.checks.memory = {
-      rss: Math.round(memUsage.rss / 1024 / 1024), // MB
-      heapUsed: Math.round(memUsage.heapUsed / 1024 / 1024), // MB
-      heapTotal: Math.round(memUsage.heapTotal / 1024 / 1024), // MB
-      external: Math.round(memUsage.external / 1024 / 1024), // MB
+      rss: isProduction ? "hidden" : Math.round(memUsage.rss / 1024 / 1024), // MB
+      heapUsed: isProduction ? "hidden" : Math.round(memUsage.heapUsed / 1024 / 1024), // MB
+      heapTotal: isProduction ? "hidden" : Math.round(memUsage.heapTotal / 1024 / 1024), // MB
+      external: isProduction ? "hidden" : Math.round(memUsage.external / 1024 / 1024), // MB
     };
src/app/admin/delivery-fees/page.tsx (1)

131-136: Consider using a modal for delete confirmation

The native confirm() dialog works but doesn't match the modern UI design. Consider using a modal component for better UX consistency.

You could integrate a confirmation modal library or create a custom modal component for a more polished user experience.

src/app/api/admin/auth/route.ts (2)

7-9: In-memory rate limiting has scalability limitations

The in-memory Map won't work across multiple server instances and loses data on restart. Consider using Redis (as mentioned in other routes) for consistent rate limiting.

For production deployments with multiple instances, implement Redis-based rate limiting or use a dedicated rate limiting service to ensure consistent protection across all instances.


108-114: Consider dynamic role assignment

The role is hardcoded as "admin". If the system supports multiple roles, retrieve this from the token payload or user data.

    return NextResponse.json({
      authenticated: true,
      user: {
        username: user.username,
-        role: "admin",
+        role: user.role || "admin",
      },
    });
src/app/admin/page.tsx (1)

94-94: Add missing dependency to useCallback

The fetchMessages callback in line 94 unnecessarily includes state setters in its dependency array. State setters from useState are stable and don't need to be included.

-    [router, setMessages, setStats, setPagination, setLoading]
+    [router]
src/app/api/delivery-fees/[state]/route.ts (1)

120-130: Consider using a constant for max delivery fee

The hard-coded value of 10000 for maximum delivery fee should be extracted to a configuration constant for better maintainability.

Add a constant at the top of the file:

const MAX_DELIVERY_FEE = 10000;

Then update the validation:

-        body.deliveryFee > 10000)
+        body.deliveryFee > MAX_DELIVERY_FEE)
     ) {
       return NextResponse.json(
-        { error: "Delivery fee must be a number between 0 and 10,000." },
+        { error: `Delivery fee must be a number between 0 and ${MAX_DELIVERY_FEE.toLocaleString()}.` },
docs/ENVIRONMENT_VARIABLES.md (2)

101-104: Add language specifier to fenced code block

The error message code block should specify a language for better syntax highlighting.

-```
+```text
 Error: SITE_URL environment variable is required in production.
 Please set NEXT_PUBLIC_URL, BASE_URL, NEXT_PUBLIC_BASE_URL, SITE_URL, or VERCEL_URL.

---

`119-119`: **Security consideration for JWT_SECRET documentation**

Consider adding a note about the importance of JWT secret strength and uniqueness.

```diff
 JWT_SECRET=your-super-secret-jwt-key-at-least-32-characters
+# Note: Use a cryptographically secure random string in production
src/app/api/contact/[messageId]/route.ts (1)

144-144: Type assertion could hide type errors

The as object type assertion bypasses TypeScript's type checking and could hide potential type mismatches.

Consider defining a proper type for the update operation or using a more specific assertion:

-          $push: { replies: newReply } as object,
+          $push: { replies: newReply },
src/app/admin/messages/page.tsx (3)

94-94: Remove unnecessary dependencies from useCallback

State setters are stable and don't need to be in the dependency array.

-    [router, setMessages, setStats, setPagination, setLoading]
+    [router]

206-206: Use a better confirmation dialog

The native confirm() blocks the main thread and provides poor UX. Consider using a custom modal dialog.

Would you like me to implement a custom confirmation modal component that matches your design system?


486-486: Improve close button accessibility

The modal close button uses a multiplication sign character which may not be accessible to screen readers.

-                      ×
+                      <span aria-label="Close">×</span>
src/app/api/admin/analytics/customers/route.ts (1)

39-42: Remove redundant variable assignment

The days variable on line 41 is redundant since range is already available and used directly on line 42.

 const end = new Date();
 const start = new Date();
-const days = range;
-start.setDate(start.getDate() - days);
+start.setDate(start.getDate() - range);
src/app/api/newsletter/route.ts (2)

6-10: Consider distributed rate limiting for production

The in-memory Map-based rate limiting won't work correctly in serverless or multi-instance deployments. Consider using Redis or a similar distributed cache for production environments.


108-108: Validate NEXT_PUBLIC_URL environment variable

The URL construction should validate the environment variable to ensure it's a valid URL format.

Consider validating the URL:

const baseUrl = process.env.NEXT_PUBLIC_URL || "https://himspired.com";
// Ensure URL doesn't have trailing slash for consistent concatenation
const shopUrl = baseUrl.replace(/\/$/, '') + '/shop';
src/app/api/orders/[orderId]/route.ts (1)

172-230: Consider using database transactions for atomicity

The stock updates and order status changes should ideally be wrapped in a database transaction to ensure atomicity. If any stock update fails, the entire operation should roll back.

src/app/api/orders/route.ts (2)

102-109: Implement virus scanning for uploaded files

The virus scanning placeholder should be implemented before production deployment to prevent malicious file uploads.

Would you like me to help implement virus scanning using ClamAV or integrate with a cloud-based virus scanning API?


158-166: Extract email validation to a utility function

The email validation function should be extracted to a shared utility for reuse across the codebase.

Consider moving this to a shared utilities file:

// In @/lib/validation.ts
export const isValidEmail = (email: string): boolean => {
  if (!email || typeof email !== "string" || email.trim() === "") {
    return false;
  }
  const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
  return emailRegex.test(email.trim());
};
src/app/_Home/components/products/components/ProductSection.tsx (4)

47-60: Simplify grid items creation using array methods.

The manual loop can be replaced with a more declarative approach using slice() and map().

-  // Create grid items based on itemsToShow
-  const gridItems = [];
-  for (let i = 0; i < itemsToShow; i++) {
-    if (i < products.length) {
-      const product = products[i];
-      gridItems.push(
-        <ProductItem
-          key={product._id}
-          product={product}
-          itemsToShow={itemsToShow}
-          variants={itemVariants}
-        />
-      );
-    }
-  }
+  // Create grid items based on itemsToShow
+  const gridItems = products
+    .slice(0, itemsToShow)
+    .map((product) => (
+      <ProductItem
+        key={product._id}
+        product={product}
+        itemsToShow={itemsToShow}
+        variants={itemVariants}
+      />
+    ));

335-346: Remove console.error from production code.

Console errors should be handled through proper logging mechanisms in production.

-      console.error("Reservation error:", error);
+      // Use proper error logging service in production
+      if (process.env.NODE_ENV === 'development') {
+        console.error("Reservation error:", error);
+      }

396-397: Extract inline style calculation for better maintainability.

The complex width calculation should be extracted to improve readability.

+  const itemWidth = useMemo(() => ({
+    width: `calc(${100 / itemsToShow}% - ${itemsToShow > 1 ? "2rem" : "0rem"})`
+  }), [itemsToShow]);
+
   return (
     <motion.div
       ref={cardRef}
       onClick={handleClick}
       className="flex flex-col gap-y-2 items-center cursor-pointer"
       variants={variants}
-      style={{
-        width: `calc(${100 / itemsToShow}% - ${itemsToShow > 1 ? "2rem" : "0rem"})`,
-      }}
+      style={itemWidth}
     >

424-425: Extract complex conditional styling for better readability.

The stock message color logic should be extracted to a utility function.

+  const getStockMessageColor = (stock: number) => {
+    if (stock <= 0) return "text-red-600";
+    if (stock <= 3) return "text-orange-600";
+    return "text-green-600";
+  };
+
   {stockMessage && (
     <span
-      className={`text-xs font-medium ${currentStock <= 0 ? "text-red-600" : currentStock <= 3 ? "text-orange-600" : "text-green-600"}`}
+      className={`text-xs font-medium ${getStockMessageColor(currentStock)}`}
     >
       {stockMessage}
     </span>
   )}

Comment on lines +37 to +41
globals: {
"ts-jest": {
useESM: true,
},
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update deprecated ts-jest configuration syntax

The globals configuration for ts-jest is deprecated. The useESM option should be configured through the preset or transform options instead.

Consider updating to the modern configuration:

-  extensionsToTreatAsEsm: [".ts", ".tsx"],
-  globals: {
-    "ts-jest": {
-      useESM: true,
-    },
-  },
+  extensionsToTreatAsEsm: [".ts", ".tsx"],
+  preset: "ts-jest/presets/default-esm",
+  transform: {
+    "^.+\\.tsx?$": ["ts-jest", {
+      useESM: true,
+    }],
+  },
📝 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
globals: {
"ts-jest": {
useESM: true,
},
},
extensionsToTreatAsEsm: [".ts", ".tsx"],
preset: "ts-jest/presets/default-esm",
transform: {
"^.+\\.tsx?$": ["ts-jest", {
useESM: true,
}],
},
🤖 Prompt for AI Agents
In jest.config.js around lines 37 to 41, the ts-jest configuration uses the
deprecated globals syntax to set useESM. Remove the useESM option from globals
and instead configure it properly via the preset or transform options according
to the latest ts-jest documentation, ensuring the ESM support is enabled in the
recommended way.

Comment on lines +4 to +8
images: {
domains: ["cdn.sanity.io", "res.cloudinary.com"],
formats: ["image/webp", "image/avif"],
minimumCacheTTL: 60,
},
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Update deprecated image configuration

The domains configuration for images is deprecated in Next.js 12.3.0+. Use remotePatterns for better security and flexibility.

 images: {
-  domains: ["cdn.sanity.io", "res.cloudinary.com"],
+  remotePatterns: [
+    {
+      protocol: 'https',
+      hostname: 'cdn.sanity.io',
+      pathname: '/images/**',
+    },
+    {
+      protocol: 'https',
+      hostname: 'res.cloudinary.com',
+      pathname: '/**',
+    },
+  ],
   formats: ["image/webp", "image/avif"],
   minimumCacheTTL: 60,
 },

🌐 Web query:

What is the latest Next.js image configuration syntax for remote patterns?

💡 Result:

The latest (mid-2025) Next.js image configuration syntax for remote patterns uses the remotePatterns property in your next.config.js file, allowing you to specify exactly which remote images can be optimized by the <Image /> component. Wildcard support is included for both hostnames and pathnames, offering fine-grained control.

Example: Remote Patterns Syntax (as of Next.js 15.x+)

module.exports = {
  images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'example.com',
        port: '',                   // Optional: leave empty for default ports
        pathname: '/account123/**', // Supports * or ** wildcards
        search: '',                 // Optional: restricts query string
      },
    ],
  },
}
  • The above will allow images from https://example.com/account123/*
  • pathname and hostname support wildcards:
    • * matches a single segment or subdomain
    • ** matches any number of segments at the end or subdomains at the beginning16.

Wildcard Examples:

  • Match all subdomains:

    hostname: '**.example.com'

    Matches img1.example.com, foo.bar.example.com, etc.[4][8]

  • Match all image paths under a prefix:

    pathname: '/images/**'

    Matches /images/cat.png, /images/profile/xyz.jpg, etc.1

Updated Syntax Note:
As of Next.js v15.3.0 and later, you can also use a URL object for remotePatterns, such as:

module.exports = {
  images: {
    remotePatterns: [new URL('https://example.com/account123/**')],
  },
}

This is an alternative to the object-syntax above and is newer1.

Security Consideration:

  • Be as specific as possible with remotePatterns to prevent malicious usage. Overly broad patterns (e.g., hostname: '**') may expose your optimization API to abuse110.
  • The older domains array is deprecated in favor of the more powerful remotePatterns3.

Summary Table:

Property Description Supports Wildcards Example Value
protocol URL protocol, typically 'https' No 'https'
hostname Remote host; supports wildcards Yes 's3.amazonaws.com', '**.example.com'
port Port, default empty for standard No '', '443'
pathname Path on the host; supports wildcards Yes '/bucket/**', '/images/*'
search Query string; restricts by ?key=value No '?v=1'

Reference: For official and always up-to-date examples, see the Next.js Image docs4.

Citations:


Update deprecated image configuration

The domains array is deprecated in Next.js 12.3+. Replace it with remotePatterns to explicitly whitelist remote sources:

// next.config.ts
 images: {
-  domains: ["cdn.sanity.io", "res.cloudinary.com"],
+  remotePatterns: [
+    {
+      protocol: 'https',
+      hostname: 'cdn.sanity.io',
+      port: '',            // optional, default ports
+      pathname: '/images/**', 
+      search: '',          // optional, restrict query strings
+    },
+    {
+      protocol: 'https',
+      hostname: 'res.cloudinary.com',
+      port: '',
+      pathname: '/**',
+      search: '',
+    },
+  ],
   formats: ["image/webp", "image/avif"],
   minimumCacheTTL: 60,
 },

• Adjust pathname (and optionally port/search) to match only the paths you need.
• This change improves security and unlocking wildcard support for hostnames and paths.

📝 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
images: {
domains: ["cdn.sanity.io", "res.cloudinary.com"],
formats: ["image/webp", "image/avif"],
minimumCacheTTL: 60,
},
images: {
remotePatterns: [
{
protocol: 'https',
hostname: 'cdn.sanity.io',
port: '', // optional, default ports
pathname: '/images/**',
search: '', // optional, restrict query strings
},
{
protocol: 'https',
hostname: 'res.cloudinary.com',
port: '',
pathname: '/**',
search: '',
},
],
formats: ["image/webp", "image/avif"],
minimumCacheTTL: 60,
},
🤖 Prompt for AI Agents
In next.config.ts around lines 4 to 8, the images configuration uses the
deprecated 'domains' array. Replace 'domains' with 'remotePatterns' by
specifying an array of objects that include 'protocol', 'hostname', and
'pathname' to whitelist remote image sources explicitly. Adjust 'pathname' to
match only necessary paths and optionally include 'port' and 'search' fields for
finer control, improving security and enabling wildcard support.

Comment on lines +10 to +13
experimental: {
optimizeCss: true,
optimizePackageImports: ["lucide-react", "framer-motion"],
},
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove invalid experimental configuration

The optimizeCss option is not a valid experimental feature in Next.js. CSS optimization is handled automatically by Next.js.

 experimental: {
-  optimizeCss: true,
   optimizePackageImports: ["lucide-react", "framer-motion"],
 },
📝 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
experimental: {
optimizeCss: true,
optimizePackageImports: ["lucide-react", "framer-motion"],
},
experimental: {
optimizePackageImports: ["lucide-react", "framer-motion"],
},
🤖 Prompt for AI Agents
In next.config.ts around lines 10 to 13, remove the invalid experimental option
`optimizeCss` from the experimental configuration object, as Next.js handles CSS
optimization automatically and this option is not recognized.

Comment on lines +115 to +118

- Filter by category (All, Thrift, Luxury, Senators)
- View product details
- Add products to cart
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Outdated category references conflict with PR objectives

Line 116 still references the old brand-based categories (Thrift, Luxury, Senators) which should be updated to the new product-type categories (Polos, T-Shirts, Jeans, Shirts) according to the PR objectives.

Apply this diff to align with the stated category changes:

- Filter by category (All, Thrift, Luxury, Senators)
+ Filter by category (All, Polos, T-Shirts, Jeans, Shirts)
📝 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
- Filter by category (All, Thrift, Luxury, Senators)
- View product details
- Add products to cart
- Filter by category (All, Polos, T-Shirts, Jeans, Shirts)
- View product details
- Add products to cart
🤖 Prompt for AI Agents
In README.md around lines 115 to 118, update the category references from the
old brand-based categories (Thrift, Luxury, Senators) to the new product-type
categories (Polos, T-Shirts, Jeans, Shirts) to align with the PR objectives.
Replace the outdated category list with the new one in the relevant bullet
point.

Comment on lines +52 to +66
ttf_size=$(stat -f%z "$ttf_file")
woff2_size=$(stat -f%z "$woff2_file")
savings=$((ttf_size - woff2_size))
savings_percent=$((savings * 100 / ttf_size))

echo "$(basename "$woff2_file"): ${woff2_size} bytes (${savings_percent}% smaller than TTF)"
elif [ -f "$otf_file" ]; then
otf_size=$(stat -f%z "$otf_file")
woff2_size=$(stat -f%z "$woff2_file")
savings=$((otf_size - woff2_size))
savings_percent=$((savings * 100 / otf_size))

echo "$(basename "$woff2_file"): ${woff2_size} bytes (${savings_percent}% smaller than OTF)"
fi
done
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve portability across Unix systems.

The script uses macOS-specific stat -f%z command, which won't work on Linux systems.

Consider making the script more portable:

-        ttf_size=$(stat -f%z "$ttf_file")
-        woff2_size=$(stat -f%z "$woff2_file")
+        if [[ "$OSTYPE" == "darwin"* ]]; then
+            ttf_size=$(stat -f%z "$ttf_file")
+            woff2_size=$(stat -f%z "$woff2_file")
+        else
+            ttf_size=$(stat -c%s "$ttf_file")
+            woff2_size=$(stat -c%s "$woff2_file")
+        fi
📝 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
ttf_size=$(stat -f%z "$ttf_file")
woff2_size=$(stat -f%z "$woff2_file")
savings=$((ttf_size - woff2_size))
savings_percent=$((savings * 100 / ttf_size))
echo "$(basename "$woff2_file"): ${woff2_size} bytes (${savings_percent}% smaller than TTF)"
elif [ -f "$otf_file" ]; then
otf_size=$(stat -f%z "$otf_file")
woff2_size=$(stat -f%z "$woff2_file")
savings=$((otf_size - woff2_size))
savings_percent=$((savings * 100 / otf_size))
echo "$(basename "$woff2_file"): ${woff2_size} bytes (${savings_percent}% smaller than OTF)"
fi
done
if [[ "$OSTYPE" == "darwin"* ]]; then
ttf_size=$(stat -f%z "$ttf_file")
woff2_size=$(stat -f%z "$woff2_file")
else
ttf_size=$(stat -c%s "$ttf_file")
woff2_size=$(stat -c%s "$woff2_file")
fi
savings=$((ttf_size - woff2_size))
savings_percent=$((savings * 100 / ttf_size))
echo "$(basename "$woff2_file"): ${woff2_size} bytes (${savings_percent}% smaller than TTF)"
🤖 Prompt for AI Agents
In scripts/optimize-fonts.sh around lines 52 to 66, the script uses the
macOS-specific stat command with the -f%z option to get file sizes, which is not
portable to Linux. Modify the script to detect the operating system and use the
appropriate stat command: on Linux, use stat -c%s, and on macOS, keep using stat
-f%z. Implement a conditional check for the OS type at the start of the script
and assign the correct stat command to a variable, then use that variable to get
file sizes throughout the script.

Comment on lines +8 to +14
function getClientIp(req: NextRequest) {
return (
req.headers.get("x-forwarded-for") ||
req.headers.get("x-real-ip") ||
"unknown"
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Handle multiple IPs in x-forwarded-for header

The x-forwarded-for header can contain multiple comma-separated IPs when requests pass through multiple proxies. Consider extracting only the first IP.

function getClientIp(req: NextRequest) {
-  return (
-    req.headers.get("x-forwarded-for") ||
-    req.headers.get("x-real-ip") ||
-    "unknown"
-  );
+  const forwarded = req.headers.get("x-forwarded-for");
+  if (forwarded) {
+    // Take the first IP if multiple are present
+    return forwarded.split(',')[0].trim();
+  }
+  return req.headers.get("x-real-ip") || "unknown";
}
📝 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
function getClientIp(req: NextRequest) {
return (
req.headers.get("x-forwarded-for") ||
req.headers.get("x-real-ip") ||
"unknown"
);
}
function getClientIp(req: NextRequest) {
const forwarded = req.headers.get("x-forwarded-for");
if (forwarded) {
// Take the first IP if multiple are present
return forwarded.split(',')[0].trim();
}
return req.headers.get("x-real-ip") || "unknown";
}
🤖 Prompt for AI Agents
In src/app/api/delivery-fees/route.ts between lines 8 and 14, the getClientIp
function currently returns the entire x-forwarded-for header value, which may
contain multiple comma-separated IPs. Modify the function to split the
x-forwarded-for header by commas and return only the first IP address from the
list to correctly identify the client IP.

Comment on lines +88 to +93
if (!body.state || !states.includes(body.state)) {
return NextResponse.json(
{ error: "Invalid state. Please provide a valid Nigerian state." },
{ status: 400 }
);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider case-insensitive state validation

The current validation may reject valid states due to case sensitivity or whitespace issues.

    // Validation
-    if (!body.state || !states.includes(body.state)) {
+    const normalizedState = body.state?.trim();
+    const validState = states.find(s => s.toLowerCase() === normalizedState?.toLowerCase());
+    if (!normalizedState || !validState) {
      return NextResponse.json(
        { error: "Invalid state. Please provide a valid Nigerian state." },
        { status: 400 }
      );
    }
+    // Use the correctly cased state name
+    body.state = validState;
📝 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
if (!body.state || !states.includes(body.state)) {
return NextResponse.json(
{ error: "Invalid state. Please provide a valid Nigerian state." },
{ status: 400 }
);
}
// Validation
const normalizedState = body.state?.trim();
const validState = states.find(s => s.toLowerCase() === normalizedState?.toLowerCase());
if (!normalizedState || !validState) {
return NextResponse.json(
{ error: "Invalid state. Please provide a valid Nigerian state." },
{ status: 400 }
);
}
// Use the correctly cased state name
body.state = validState;
🤖 Prompt for AI Agents
In src/app/api/delivery-fees/route.ts around lines 88 to 93, the state
validation is case-sensitive and does not handle extra whitespace, which may
incorrectly reject valid states. To fix this, normalize the input state by
trimming whitespace and converting it to a consistent case (e.g., lowercase)
before checking if it exists in the states list, which should also be normalized
to the same case for comparison.

Comment on lines +30 to +38
const itemsList = items
.map(
(item) =>
`<li style="margin: 10px 0; padding: 10px; background: #f9f9f9; border-radius: 4px;">
<strong>${item.title}</strong><br>
<span style="color: #666; font-size: 14px;">Size: ${item.size || "N/A"} | Qty: ${item.quantity} | ₦${item.price.toLocaleString()}</span>
</li>`
)
.join("");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Potential XSS vulnerability in email template.

The email template directly interpolates user data without sanitization, which could lead to XSS if the data contains malicious content.

Sanitize the data before inserting into HTML:

+import DOMPurify from 'isomorphic-dompurify';

     const itemsList = items
       .map(
         (item) =>
           `<li style="margin: 10px 0; padding: 10px; background: #f9f9f9; border-radius: 4px;">
-       <strong>${item.title}</strong><br>
+       <strong>${DOMPurify.sanitize(item.title)}</strong><br>
-       <span style="color: #666; font-size: 14px;">Size: ${item.size || "N/A"} | Qty: ${item.quantity} | ₦${item.price.toLocaleString()}</span>
+       <span style="color: #666; font-size: 14px;">Size: ${DOMPurify.sanitize(item.size) || "N/A"} | Qty: ${item.quantity} | ₦${item.price.toLocaleString()}</span>
      </li>`
       )
       .join("");
🤖 Prompt for AI Agents
In src/app/api/orders/send-confirmation/route.ts around lines 30 to 38, the
email template directly inserts user data into HTML without sanitization,
risking XSS attacks. To fix this, sanitize all user-provided fields like
item.title, item.size, and item.quantity before embedding them in the HTML
string. Use a proper HTML escaping function to replace special characters with
safe entities to prevent malicious code execution.

<div style="background: #f8f8f8; padding: 25px; border-radius: 8px;">
<h2 style="color: #68191E; margin-top: 0;">Order Received Successfully!</h2>
<p style="color: #333; line-height: 1.6;">Dear ${customerInfo.name},</p>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Sanitize customer name in email template.

The customer name is directly interpolated into the HTML email without sanitization.

Apply sanitization:

-           <p style="color: #333; line-height: 1.6;">Dear ${customerInfo.name},</p>
+           <p style="color: #333; line-height: 1.6;">Dear ${DOMPurify.sanitize(customerInfo.name)},</p>
📝 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
<p style="color: #333; line-height: 1.6;">Dear ${customerInfo.name},</p>
<p style="color: #333; line-height: 1.6;">Dear ${DOMPurify.sanitize(customerInfo.name)},</p>
🤖 Prompt for AI Agents
In src/app/api/orders/send-confirmation/route.ts at line 53, the customer name
is directly inserted into the HTML email template without sanitization, which
can lead to XSS vulnerabilities. Fix this by sanitizing the customerInfo.name
value before interpolation, using a suitable HTML escaping or sanitization
function to ensure any special characters are safely encoded.

Comment on lines +10 to +27
const { orderId, email, message, updateStatus } = await req.json();
if (!orderId || !email || !message) {
return NextResponse.json(
{ error: "Missing required fields" },
{ status: 400 }
);
}

// Optionally update order status
if (updateStatus && isValidOrderStatus(updateStatus)) {
await orderService.updateOrderStatus(
orderId,
updateStatus as OrderStatus
);
}

// Send the custom email
await sendCustomOrderEmail(email, "Order Update from Himspired", message);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation and rate limiting

The endpoint lacks important security measures:

  1. Email format validation is missing
  2. Message content should be sanitized to prevent injection attacks
  3. Rate limiting should be applied to prevent abuse

Consider adding these validations:

 const { orderId, email, message, updateStatus } = await req.json();
+
+// Validate email format
+const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
+if (!emailRegex.test(email)) {
+  return NextResponse.json(
+    { error: "Invalid email format" },
+    { status: 400 }
+  );
+}
+
+// Sanitize message content (basic HTML escape)
+const sanitizedMessage = message.replace(/[<>]/g, '');
+
 if (!orderId || !email || !message) {

Also consider implementing rate limiting using a middleware or service like @/lib/rate-limiter.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/api/orders/send-custom-email/route.ts around lines 10 to 27, add
validation to check that the email field contains a valid email format and
sanitize the message content to prevent injection attacks before processing.
Additionally, implement rate limiting by integrating a middleware or service
such as '@/lib/rate-limiter' to restrict the number of requests to this endpoint
and prevent abuse.

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.

Review continued from previous batch...

Comment on lines +75 to +508
const ProductItem = ({
product,
itemsToShow,
variants,
}: {
product: Product;
itemsToShow: number;
variants: Variants | undefined;
}) => {
const [showSizes, setShowSizes] = useState(false);
const [addedToCart, setAddedToCart] = useState<string | null>(null);
const [currentStock, setCurrentStock] = useState(product.stock || 0);
const [stockMessage, setStockMessage] = useState("");
const [isAvailable, setIsAvailable] = useState(true);
const [lastKnownStock, setLastKnownStock] = useState(product.stock || 0);
const [isReserving, setIsReserving] = useState(false);
const cardRef = useRef<HTMLDivElement>(null);
const router = useRouter();
const dispatch = useAppDispatch();
const fetchStockRef = useRef<(() => Promise<void>) | undefined>(undefined);

// Get cart quantities for this product
const cartQuantity = useAppSelector((state) =>
selectCartItemQuantity(state, product._id, product.size?.[0] || "")
);

// Get all cart items for this product
const cartItems = useAppSelector((state) =>
state.persistedReducer.cart.items.filter(
(item) => item.originalProductId === product._id
)
);

// Memoized size-specific quantities to prevent unnecessary rerenders
const sizeQuantities = useMemo(() => {
const quantities: Record<string, number> = {};
cartItems.forEach((item: CartItem) => {
if (item.size) {
quantities[item.size] =
(quantities[item.size] || 0) + (item.quantity || 1);
}
});
return quantities;
}, [cartItems]);

// Check product availability
const checkAvailability = useCallback(async () => {
try {
const sessionId = SessionManager.getSessionId();
const res = await fetch(
`/api/products/availability/${product._id}?sessionId=${sessionId}`
);
if (!res.ok) return;
const data = await res.json();
setIsAvailable(data.isAvailable);
} catch (error) {
console.error("Error checking availability:", error);
}
}, [product._id]);

// Real-time polling for stock and reservation status
useEffect(() => {
SessionManager.refreshSessionIfNeeded();
let isMounted = true;
const sessionId = SessionManager.getSessionId();
const fetchStock = async () => {
try {
const res = await fetch(
`/api/products/stock/${product._id}?sessionId=${sessionId}`
);
if (!res.ok) return;
const data = await res.json();
if (isMounted) {
const newStock = data.stock || 0;
const availableStock = data.availableStock || 0;
const reservedByCurrentUser = data.reservedByCurrentUser || 0;
const reservedByOthers = data.reservedByOthers || 0;

// Check if stock changed significantly (admin intervention)
if (Math.abs(newStock - lastKnownStock) > 1) {
console.warn(
`Significant stock change detected for ${product.title}: ${lastKnownStock} -> ${newStock}`
);
toast.info(`Stock updated for ${product.title}`);
}

setCurrentStock(newStock);
setLastKnownStock(newStock);

// Use the enhanced stock message from API
setStockMessage(data.stockMessage || "");

// Update availability based on available stock
if (availableStock <= 0) {
setIsAvailable(false);
} else {
setIsAvailable(true);
}

// Log reservation info for debugging
if (reservedByCurrentUser > 0 || reservedByOthers > 0) {
console.log(
`${product.title}: You reserved ${reservedByCurrentUser}, Others reserved ${reservedByOthers}, Available: ${availableStock}`
);
}
}
} catch {
// Ignore errors for polling
}
};

// Initial fetch
fetchStock();

// Also fetch after a short delay to catch any recent changes
const immediateRefresh = setTimeout(fetchStock, 2000);

// Poll every 5 seconds for faster responsiveness
const interval = setInterval(fetchStock, 5000);

// Listen for cart changes from other tabs
const handleStorageChange = (event: StorageEvent) => {
if (
event.key === CACHE_KEYS.CART_SYNC ||
event.key === CACHE_KEYS.STOCK_UPDATE
) {
// Refetch stock when cart changes or stock updates
fetchStock();
}
};

// Listen for visibility changes to refresh stock when tab becomes active
const handleVisibilityChange = () => {
if (!document.hidden) {
fetchStock();
}
};

// Listen for window focus to refresh stock when user switches back to tab
const handleWindowFocus = () => {
fetchStock();
};

window.addEventListener("storage", handleStorageChange);
document.addEventListener("visibilitychange", handleVisibilityChange);
window.addEventListener("focus", handleWindowFocus);

// Store fetchStock in a ref for instant update after reservation
fetchStockRef.current = fetchStock;

return () => {
isMounted = false;
clearInterval(interval);
clearTimeout(immediateRefresh);
window.removeEventListener("storage", handleStorageChange);
document.removeEventListener("visibilitychange", handleVisibilityChange);
window.removeEventListener("focus", handleWindowFocus);
fetchStockRef.current = undefined;
};
}, [product._id, lastKnownStock, product.title]);

// Initialize session and check availability
useEffect(() => {
SessionManager.refreshSessionIfNeeded();
checkAvailability();

// Also fetch stock immediately when component mounts
if (fetchStockRef.current) {
fetchStockRef.current();
}
}, [product._id, checkAvailability]);

const handleClick = () => {
router.push(`/shop/${product._id}/${product.slug?.current}`);
};

const handleAddToCart = async (
selectedSize?: string,
e?: React.MouseEvent
) => {
if (e) e.stopPropagation();

// Check if product is available
if (!isAvailable) {
toast.error(stockMessage || "Product is not available");
return;
}

setIsReserving(true);
SessionManager.refreshSessionIfNeeded();
const sessionId = SessionManager.getSessionId();

// Get current cart quantity for this specific product and size
const currentSize = selectedSize || product.size?.[0] || "";
const currentCartQuantity = sizeQuantities[currentSize] || 0;

// Calculate new total quantity (current + 1)
const newTotalQuantity = currentCartQuantity + 1;

let reservationResult: {
success: boolean;
reservationId?: string;
error?: string;
} | null = null;

// Reservation logic
try {
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 15000); // 15 second timeout

const reservationData = {
sessionId,
quantity: newTotalQuantity, // Reserve the new total quantity
size: selectedSize || product.size?.[0] || "",
isUpdate: currentCartQuantity > 0, // Mark as update if item already in cart
};

const response = await fetch(`/api/products/reserve/${product._id}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(reservationData),
signal: controller.signal,
});

clearTimeout(timeoutId);
reservationResult = await response.json();

if (!reservationResult?.success) {
// Check if it's a reservation conflict
if (
reservationResult?.error &&
reservationResult.error.includes("already reserved")
) {
toast.error(
"This product is currently being purchased by another user"
);
} else if (
reservationResult?.error &&
(reservationResult.error.includes("timeout") ||
reservationResult.error.includes("connection"))
) {
toast.error(
"This product is currently being purchased by another user"
);
} else {
toast.error(reservationResult?.error || "Failed to reserve product");
}
return;
}

toast.success("Product reserved and added to cart");

// Instantly update stock/reservation status after reservation
if (fetchStockRef.current) {
await fetchStockRef.current();
}

// Broadcast stock update to other tabs
localStorage.setItem(CACHE_KEYS.STOCK_UPDATE, Date.now().toString());
} catch (error) {
console.error("Reservation error:", error);

if (error instanceof Error) {
if (error.name === "AbortError") {
toast.error("Reservation timed out. Please try again.");
} else {
toast.error("Failed to reserve product");
}
} else {
toast.error("Failed to reserve product");
}
return;
} finally {
setIsReserving(false);
}

// Fixed: Updated to match new CartItem interface
const data: Omit<
CartItem,
"quantity" | "originalPrice" | "originalProductId"
> = {
_id: product._id,
title: product.title,
category: product.category,
mainImage: product.mainImage,
price: product.price,
size: selectedSize || product.size?.[0] || "",
stock: product.stock || 0, // Include stock information
reservationId: reservationResult?.reservationId, // Include reservation ID
};

dispatch(addItem(data));

// Show feedback animation
setAddedToCart(selectedSize || "default");
setTimeout(() => setAddedToCart(null), 1500);
};

useEffect(() => {
const handleClickOutside = (event: globalThis.MouseEvent) => {
if (cardRef.current && !cardRef.current.contains(event.target as Node)) {
setShowSizes(false);
}
};

if (showSizes) {
document.addEventListener("mousedown", handleClickOutside);
}

return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, [showSizes]);

return (
<motion.div
ref={cardRef}
onClick={handleClick}
className="flex flex-col gap-y-2 items-center cursor-pointer"
variants={variants}
style={{
width: `calc(${100 / itemsToShow}% - ${itemsToShow > 1 ? "2rem" : "0rem"})`,
}}
>
<motion.div
whileHover={{ scale: 1.05 }}
transition={{ duration: 0.3 }}
className="relative group cursor-pointer"
>
<SanityImageComponent
image={product.mainImage || "/placeholder.svg"}
alt={product.title}
width={0}
height={0}
className="w-auto h-auto md:px-7 py-3 md:py-4.5"
/>
<div className="absolute inset-0 bg-black/0 transition-all duration-300 rounded-lg"></div>
</motion.div>

<div className="flex flex-col gap-y-2.5">
<p className="text-gray-850/50 text-xs">{product.category}</p>
<h3 className="text-gray-850 text-sm md:text-base whitespace-nowrap overflow-hidden text-ellipsis w-40">
{product.title}
</h3>
<p className="text-gray-850 text-xs md:text-base">
NGN {product.price.toLocaleString()}
</p>
{stockMessage && (
<span
className={`text-xs font-medium ${currentStock <= 0 ? "text-red-600" : currentStock <= 3 ? "text-orange-600" : "text-green-600"}`}
>
{stockMessage}
</span>
)}
</div>

{!showSizes && (
<motion.button
className={`mt-1.5 p-3 md:p-4 w-fit rounded-full transition-all duration-300 ${
addedToCart === "default"
? "bg-green-100 border-2 border-green-500"
: !isAvailable || isReserving
? "bg-gray-300 cursor-not-allowed"
: "bg-white-200 hover:bg-gray-200"
}`}
whileHover={isAvailable && !isReserving ? { scale: 1.1 } : undefined}
whileTap={isAvailable && !isReserving ? { scale: 0.95 } : undefined}
onClick={(e) => {
if (!isAvailable || isReserving) return;
if (!product.size?.length) {
handleAddToCart(undefined, e);
} else {
e.stopPropagation();
setShowSizes(true);
}
}}
disabled={!isAvailable || isReserving}
>
{addedToCart === "default" ? <Check color="#22c55e" /> : <Plus />}
</motion.button>
)}

{!product.size?.length && cartQuantity > 0 && (
<p className="text-xs text-[#68191E] font-medium">
In cart: {cartQuantity}
</p>
)}

<AnimatePresence>
{showSizes && (
<motion.div
className="flex gap-4 flex-wrap justify-center"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: 20 }}
transition={{ duration: 0.4 }}
>
{product.size?.map((sizeOption) => {
const sizeCartQuantity = sizeQuantities[sizeOption] || 0;

return (
<motion.div
key={sizeOption}
onClick={(e) => {
if (isReserving) return;
e.stopPropagation();
handleAddToCart(sizeOption, e);
setShowSizes(false);
}}
className={`px-4 py-2 font-medium rounded-full flex flex-col items-center justify-center text-sm font-activo uppercase cursor-pointer transition-all duration-300 ${
addedToCart === sizeOption
? "bg-green-100 border-2 border-green-500 text-green-700"
: isReserving
? "bg-gray-300 cursor-not-allowed text-gray-500"
: "bg-[#F4F4F4] hover:bg-[#DADADA] text-[#1E1E1E]"
}`}
whileHover={!isReserving ? { scale: 1.1 } : undefined}
whileTap={!isReserving ? { scale: 0.95 } : undefined}
>
<span>{sizeOption}</span>
{sizeCartQuantity > 0 && (
<span className="text-xs text-[#68191E] font-medium">
({sizeCartQuantity})
</span>
)}
</motion.div>
);
})}
</motion.div>
)}
</AnimatePresence>
</motion.div>
);
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider decomposing the ProductItem component for better maintainability.

The ProductItem component is quite large (400+ lines) and handles multiple responsibilities. Consider extracting logic into custom hooks and smaller components.

Suggested refactoring:

  1. Extract stock polling logic into a useProductStock hook
  2. Extract reservation logic into a useProductReservation hook
  3. Extract size selector into a separate ProductSizeSelector component
  4. Extract cart management into a useProductCart hook

Example structure:

// Custom hooks
const useProductStock = (productId: string) => {
  // Stock polling and management logic
};

const useProductReservation = (product: Product) => {
  // Reservation logic
};

// Smaller component
const ProductSizeSelector = ({ sizes, onSelect, cartQuantities }) => {
  // Size selection UI
};

This would improve:

  • Code organization and readability
  • Unit testing capabilities
  • Reusability of logic
  • Component maintainability
🤖 Prompt for AI Agents
In src/app/_Home/components/products/components/ProductSection.tsx around lines
75 to 508, the ProductItem component is too large and handles multiple
responsibilities, making it hard to maintain. Refactor by extracting the stock
polling logic into a custom hook named useProductStock, the reservation logic
into useProductReservation, the size selector UI into a separate
ProductSizeSelector component, and cart management logic into useProductCart
hook. This modularization will improve code organization, readability, and
testability.

Comment on lines +154 to +159
if (Math.abs(newStock - lastKnownStock) > 1) {
console.warn(
`Significant stock change detected for ${product.title}: ${lastKnownStock} -> ${newStock}`
);
toast.info(`Stock updated for ${product.title}`);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove console statements and improve stock change detection.

Console statements should be removed from production code. Also, the stock comparison might have floating-point precision issues.

-          // Check if stock changed significantly (admin intervention)
-          if (Math.abs(newStock - lastKnownStock) > 1) {
-            console.warn(
-              `Significant stock change detected for ${product.title}: ${lastKnownStock} -> ${newStock}`
-            );
+          // Check if stock changed significantly (admin intervention)
+          const stockDifference = Math.abs(newStock - lastKnownStock);
+          if (stockDifference > 1 && Number.isInteger(stockDifference)) {
             toast.info(`Stock updated for ${product.title}`);
           }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/_Home/components/products/components/ProductSection.tsx around lines
154 to 159, remove the console.warn statement to avoid logging in production.
Replace the stock change detection logic to handle floating-point precision
issues by using a method like comparing the absolute difference against a small
epsilon value instead of a fixed threshold of 1. Keep the toast.info
notification as is.

Comment on lines +175 to +179
if (reservedByCurrentUser > 0 || reservedByOthers > 0) {
console.log(
`${product.title}: You reserved ${reservedByCurrentUser}, Others reserved ${reservedByOthers}, Available: ${availableStock}`
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Remove debug console.log statement.

Debug logging should not be present in production code.

-          // Log reservation info for debugging
-          if (reservedByCurrentUser > 0 || reservedByOthers > 0) {
-            console.log(
-              `${product.title}: You reserved ${reservedByCurrentUser}, Others reserved ${reservedByOthers}, Available: ${availableStock}`
-            );
-          }

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In src/app/_Home/components/products/components/ProductSection.tsx between lines
175 and 179, remove the console.log statement used for debugging that outputs
reservation details. This debug logging should not be present in production
code, so simply delete the entire console.log call to clean up the code.

Comment on lines +186 to +194
// Initial fetch
fetchStock();

// Also fetch after a short delay to catch any recent changes
const immediateRefresh = setTimeout(fetchStock, 2000);

// Poll every 5 seconds for faster responsiveness
const interval = setInterval(fetchStock, 5000);

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider optimizing the polling strategy to reduce API calls.

Multiple polling mechanisms (initial fetch, 2s delay, 5s interval) combined with event-based refetches could lead to excessive API calls, especially with multiple product cards.

Consider implementing:

  1. A shared polling context to coordinate fetches across multiple ProductItem instances
  2. Debouncing for event-based refetches
  3. Exponential backoff for failed requests
  4. Rate limiting to prevent API overload

Example debounced fetch:

const debouncedFetchStock = useMemo(
  () => debounce(fetchStock, 1000),
  [fetchStock]
);
🤖 Prompt for AI Agents
In src/app/_Home/components/products/components/ProductSection.tsx around lines
186 to 194, the current polling strategy uses multiple timers causing excessive
API calls when multiple product cards are rendered. To fix this, refactor to
implement a shared polling context that coordinates fetchStock calls across all
ProductItem instances, add debouncing to event-based fetches to limit rapid
repeated calls, introduce exponential backoff logic for handling failed fetch
attempts, and apply rate limiting to prevent API overload. This will optimize
API usage and improve performance.

Comment on lines +71 to +150
const loadAnalyticsData = useCallback(async () => {
try {
setLoading(true);
const params = new URLSearchParams();
params.append("range", dateRange);
if (dateRange === "custom" && customRange.start && customRange.end) {
params.append("start", customRange.start);
params.append("end", customRange.end);
}

const [revenueRes, ordersRes, deliveryRes, customersRes] =
await Promise.all([
fetch(`/api/admin/analytics/revenue?${params}`),
fetch(`/api/admin/analytics/orders?${params}`),
fetch(`/api/admin/analytics/delivery?${params}`),
fetch(`/api/admin/analytics/customers?${params}`),
]);

// Check each response individually and handle errors gracefully
const responses = [
{ name: "revenue", response: revenueRes },
{ name: "orders", response: ordersRes },
{ name: "delivery", response: deliveryRes },
{ name: "customers", response: customersRes },
];

const errors = [];
for (const { name, response } of responses) {
if (!response.ok) {
errors.push(`${name}: ${response.status} ${response.statusText}`);
}
}

if (errors.length > 0) {
console.error("Analytics API errors:", errors);
toast.error(`Failed to load some analytics data: ${errors.join(", ")}`);
// Continue with partial data if some endpoints work
}

// Parse responses with error handling
const parseResponse = async (response: Response, name: string) => {
try {
if (response.ok) {
return await response.json();
} else {
console.error(
`${name} API failed:`,
response.status,
response.statusText
);
return null;
}
} catch (error) {
console.error(`Failed to parse ${name} response:`, error);
return null;
}
};

const [revenueData, ordersData, deliveryData, customersData] =
await Promise.all([
parseResponse(revenueRes, "revenue"),
parseResponse(ordersRes, "orders"),
parseResponse(deliveryRes, "delivery"),
parseResponse(customersRes, "customers"),
]);

// Set data with fallbacks for failed requests
setData({
revenue: revenueData,
orders: ordersData,
delivery: deliveryData,
customers: customersData,
});
} catch (error) {
console.error("Failed to load analytics:", error);
toast.error("Failed to load analytics data");
} finally {
setLoading(false);
}
}, [dateRange, customRange]);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add request cancellation to prevent memory leaks

The parallel fetch requests should be cancellable to prevent state updates after component unmount.

 const loadAnalyticsData = useCallback(async () => {
+  const abortController = new AbortController();
+  
   try {
     setLoading(true);
     const params = new URLSearchParams();
     params.append("range", dateRange);
     if (dateRange === "custom" && customRange.start && customRange.end) {
       params.append("start", customRange.start);
       params.append("end", customRange.end);
     }

     const [revenueRes, ordersRes, deliveryRes, customersRes] =
       await Promise.all([
-        fetch(`/api/admin/analytics/revenue?${params}`),
-        fetch(`/api/admin/analytics/orders?${params}`),
-        fetch(`/api/admin/analytics/delivery?${params}`),
-        fetch(`/api/admin/analytics/customers?${params}`),
+        fetch(`/api/admin/analytics/revenue?${params}`, { signal: abortController.signal }),
+        fetch(`/api/admin/analytics/orders?${params}`, { signal: abortController.signal }),
+        fetch(`/api/admin/analytics/delivery?${params}`, { signal: abortController.signal }),
+        fetch(`/api/admin/analytics/customers?${params}`, { signal: abortController.signal }),
       ]);

Also return the abort controller so it can be called on cleanup.

📝 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 loadAnalyticsData = useCallback(async () => {
try {
setLoading(true);
const params = new URLSearchParams();
params.append("range", dateRange);
if (dateRange === "custom" && customRange.start && customRange.end) {
params.append("start", customRange.start);
params.append("end", customRange.end);
}
const [revenueRes, ordersRes, deliveryRes, customersRes] =
await Promise.all([
fetch(`/api/admin/analytics/revenue?${params}`),
fetch(`/api/admin/analytics/orders?${params}`),
fetch(`/api/admin/analytics/delivery?${params}`),
fetch(`/api/admin/analytics/customers?${params}`),
]);
// Check each response individually and handle errors gracefully
const responses = [
{ name: "revenue", response: revenueRes },
{ name: "orders", response: ordersRes },
{ name: "delivery", response: deliveryRes },
{ name: "customers", response: customersRes },
];
const errors = [];
for (const { name, response } of responses) {
if (!response.ok) {
errors.push(`${name}: ${response.status} ${response.statusText}`);
}
}
if (errors.length > 0) {
console.error("Analytics API errors:", errors);
toast.error(`Failed to load some analytics data: ${errors.join(", ")}`);
// Continue with partial data if some endpoints work
}
// Parse responses with error handling
const parseResponse = async (response: Response, name: string) => {
try {
if (response.ok) {
return await response.json();
} else {
console.error(
`${name} API failed:`,
response.status,
response.statusText
);
return null;
}
} catch (error) {
console.error(`Failed to parse ${name} response:`, error);
return null;
}
};
const [revenueData, ordersData, deliveryData, customersData] =
await Promise.all([
parseResponse(revenueRes, "revenue"),
parseResponse(ordersRes, "orders"),
parseResponse(deliveryRes, "delivery"),
parseResponse(customersRes, "customers"),
]);
// Set data with fallbacks for failed requests
setData({
revenue: revenueData,
orders: ordersData,
delivery: deliveryData,
customers: customersData,
});
} catch (error) {
console.error("Failed to load analytics:", error);
toast.error("Failed to load analytics data");
} finally {
setLoading(false);
}
}, [dateRange, customRange]);
const loadAnalyticsData = useCallback(async () => {
const abortController = new AbortController();
try {
setLoading(true);
const params = new URLSearchParams();
params.append("range", dateRange);
if (dateRange === "custom" && customRange.start && customRange.end) {
params.append("start", customRange.start);
params.append("end", customRange.end);
}
const [revenueRes, ordersRes, deliveryRes, customersRes] =
await Promise.all([
fetch(`/api/admin/analytics/revenue?${params}`, { signal: abortController.signal }),
fetch(`/api/admin/analytics/orders?${params}`, { signal: abortController.signal }),
fetch(`/api/admin/analytics/delivery?${params}`, { signal: abortController.signal }),
fetch(`/api/admin/analytics/customers?${params}`, { signal: abortController.signal }),
]);
// Check each response individually and handle errors gracefully
const responses = [
{ name: "revenue", response: revenueRes },
{ name: "orders", response: ordersRes },
{ name: "delivery", response: deliveryRes },
{ name: "customers", response: customersRes },
];
const errors = [];
for (const { name, response } of responses) {
if (!response.ok) {
errors.push(`${name}: ${response.status} ${response.statusText}`);
}
}
if (errors.length > 0) {
console.error("Analytics API errors:", errors);
toast.error(`Failed to load some analytics data: ${errors.join(", ")}`);
// Continue with partial data if some endpoints work
}
// Parse responses with error handling
const parseResponse = async (response: Response, name: string) => {
try {
if (response.ok) {
return await response.json();
} else {
console.error(
`${name} API failed:`,
response.status,
response.statusText
);
return null;
}
} catch (error) {
console.error(`Failed to parse ${name} response:`, error);
return null;
}
};
const [revenueData, ordersData, deliveryData, customersData] =
await Promise.all([
parseResponse(revenueRes, "revenue"),
parseResponse(ordersRes, "orders"),
parseResponse(deliveryRes, "delivery"),
parseResponse(customersRes, "customers"),
]);
// Set data with fallbacks for failed requests
setData({
revenue: revenueData,
orders: ordersData,
delivery: deliveryData,
customers: customersData,
});
} catch (error) {
console.error("Failed to load analytics:", error);
toast.error("Failed to load analytics data");
} finally {
setLoading(false);
}
}, [dateRange, customRange]);
🤖 Prompt for AI Agents
In src/app/admin/analytics/page.tsx around lines 71 to 150, the fetch requests
in loadAnalyticsData lack cancellation support, risking memory leaks if the
component unmounts before completion. To fix this, create an AbortController
instance before the fetch calls, pass its signal to each fetch request, and
return a cleanup function from useCallback that calls abort() on the controller.
This ensures requests are cancelled on unmount, preventing state updates after
unmount.

Comment on lines +11 to +17
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add environment variable validation

The transporter creation should validate that required environment variables exist to fail fast with clear error messages.

+if (!process.env.EMAIL_USER || !process.env.EMAIL_PASS) {
+  throw new Error('Email configuration missing: EMAIL_USER and EMAIL_PASS environment variables are required');
+}
+
 const transporter = nodemailer.createTransport({
   service: "gmail",
   auth: {
     user: process.env.EMAIL_USER,
     pass: process.env.EMAIL_PASS,
   },
 });
📝 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 transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
if (!process.env.EMAIL_USER || !process.env.EMAIL_PASS) {
throw new Error(
'Email configuration missing: EMAIL_USER and EMAIL_PASS environment variables are required'
);
}
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});
🤖 Prompt for AI Agents
In src/app/api/newsletter/route.ts around lines 11 to 17, the code creates a
nodemailer transporter using environment variables without validating their
presence. Add checks before transporter creation to verify that
process.env.EMAIL_USER and process.env.EMAIL_PASS are defined. If either is
missing, throw an error with a clear message indicating which environment
variable is not set, so the application fails fast and provides helpful
feedback.

Comment on lines +27 to +42
export async function POST(req: NextRequest) {
// Rate limiting logic
const ip = getClientIp(req);
const now = Date.now();
let entry = newsletterAttempts.get(ip);
if (!entry || now - entry.firstAttempt > WINDOW_MS) {
entry = { count: 0, firstAttempt: now };
}
entry.count++;
newsletterAttempts.set(ip, entry);
if (entry.count > MAX_SUBSCRIPTIONS) {
return NextResponse.json(
{ error: "Too many newsletter subscriptions. Please try again later." },
{ status: 429 }
);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Memory leak: Rate limit entries are never cleaned up

The newsletterAttempts Map will grow indefinitely as entries are never removed, causing a memory leak. Old entries should be periodically cleaned up.

Consider implementing a cleanup mechanism:

+// Periodically clean up old entries
+setInterval(() => {
+  const now = Date.now();
+  for (const [key, entry] of newsletterAttempts.entries()) {
+    if (now - entry.firstAttempt > WINDOW_MS) {
+      newsletterAttempts.delete(key);
+    }
+  }
+}, WINDOW_MS); // Run cleanup every window period
+
 export async function POST(req: NextRequest) {
   // Rate limiting logic
   const ip = getClientIp(req);
📝 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 async function POST(req: NextRequest) {
// Rate limiting logic
const ip = getClientIp(req);
const now = Date.now();
let entry = newsletterAttempts.get(ip);
if (!entry || now - entry.firstAttempt > WINDOW_MS) {
entry = { count: 0, firstAttempt: now };
}
entry.count++;
newsletterAttempts.set(ip, entry);
if (entry.count > MAX_SUBSCRIPTIONS) {
return NextResponse.json(
{ error: "Too many newsletter subscriptions. Please try again later." },
{ status: 429 }
);
}
// Periodically clean up old entries
setInterval(() => {
const now = Date.now();
for (const [key, entry] of newsletterAttempts.entries()) {
if (now - entry.firstAttempt > WINDOW_MS) {
newsletterAttempts.delete(key);
}
}
}, WINDOW_MS); // Run cleanup every window period
export async function POST(req: NextRequest) {
// Rate limiting logic
const ip = getClientIp(req);
const now = Date.now();
let entry = newsletterAttempts.get(ip);
if (!entry || now - entry.firstAttempt > WINDOW_MS) {
entry = { count: 0, firstAttempt: now };
}
entry.count++;
newsletterAttempts.set(ip, entry);
if (entry.count > MAX_SUBSCRIPTIONS) {
return NextResponse.json(
{ error: "Too many newsletter subscriptions. Please try again later." },
{ status: 429 }
);
}
// …rest of handler…
}
🤖 Prompt for AI Agents
In src/app/api/newsletter/route.ts between lines 27 and 42, the
newsletterAttempts Map grows indefinitely because old rate limit entries are
never removed, causing a memory leak. To fix this, implement a cleanup mechanism
that removes entries from newsletterAttempts when their firstAttempt timestamp
is older than the WINDOW_MS duration before adding or updating entries. This can
be done by iterating over the Map and deleting stale entries to keep memory
usage bounded.

Comment on lines +9 to +14
// In-memory rate limiting per session or IP
const orderAttempts = new Map(); // key: sessionId or IP, value: { count, firstAttempt }
const MAX_ORDERS_PER_SESSION = 3;
const MAX_ORDERS_PER_IP = 100;
const WINDOW_MS = 30 * 60 * 1000; // 30 minutes

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Memory leak: Rate limit entries are never cleaned up

Similar to the newsletter route, the orderAttempts Map will grow indefinitely. Implement periodic cleanup of expired entries.

Add a cleanup mechanism:

setInterval(() => {
  const now = Date.now();
  for (const [key, entry] of orderAttempts.entries()) {
    if (now - entry.firstAttempt > WINDOW_MS) {
      orderAttempts.delete(key);
    }
  }
}, WINDOW_MS);
🤖 Prompt for AI Agents
In src/app/api/orders/route.ts around lines 9 to 14, the orderAttempts Map used
for rate limiting grows indefinitely, causing a memory leak. Fix this by adding
a setInterval cleanup function that runs every WINDOW_MS milliseconds, iterating
over orderAttempts entries and deleting those whose firstAttempt timestamp is
older than WINDOW_MS to remove expired entries and prevent unbounded memory
growth.

const address = formData.get("address") as string;
const state = formData.get("state") as string;
const message = formData.get("message") as string;
const items = JSON.parse(formData.get("items") as string);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for JSON parsing

The JSON.parse call could throw an error if the items field contains invalid JSON.

-    const items = JSON.parse(formData.get("items") as string);
+    let items;
+    try {
+      items = JSON.parse(formData.get("items") as string);
+    } catch (error) {
+      return NextResponse.json(
+        { error: "Invalid items format. Expected valid JSON." },
+        { status: 400 }
+      );
+    }
📝 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 items = JSON.parse(formData.get("items") as string);
// … earlier validation logic …
let items;
try {
items = JSON.parse(formData.get("items") as string);
} catch (error) {
return NextResponse.json(
{ error: "Invalid items format. Expected valid JSON." },
{ status: 400 }
);
}
// … continue processing items …
🤖 Prompt for AI Agents
In src/app/api/orders/route.ts at line 71, the JSON.parse call on
formData.get("items") can throw an error if the input is invalid JSON. Wrap the
JSON.parse call in a try-catch block to catch any parsing errors. In the catch
block, handle the error appropriately by returning a response indicating invalid
input or throwing a controlled error to prevent the server from crashing.

Comment on lines +214 to +219
return [
"payment_pending",
"payment_confirmed",
"shipped",
"complete",
].includes(status);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Missing "canceled" status in validation

The status validation is missing "canceled" which is handled in the order update endpoint. This inconsistency could cause issues.

     return [
       "payment_pending",
       "payment_confirmed",
       "shipped",
       "complete",
+      "canceled",
     ].includes(status);
📝 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
return [
"payment_pending",
"payment_confirmed",
"shipped",
"complete",
].includes(status);
return [
"payment_pending",
"payment_confirmed",
"shipped",
"complete",
"canceled",
].includes(status);
🤖 Prompt for AI Agents
In src/app/api/orders/route.ts around lines 214 to 219, the status validation
array is missing the "canceled" status, which is handled elsewhere in the order
update endpoint. To fix this, add "canceled" to the list of valid statuses in
the includes check to ensure consistency and proper validation of all handled
statuses.

@davedumto
Copy link
Contributor

hi @CijeTheCreator can you resolve the conflicts?

@CijeTheCreator
Copy link
Collaborator Author

I'll look into it ASAP

@bbdr51646-lab
Copy link

My phone full system app changed .how . Please help me

@davedumto
Copy link
Contributor

davedumto commented Sep 10, 2025 via email

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants