Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Feature/nice to have polish#24

Merged
11 commits merged into
mainfrom
feature/nice-to-have-polish
Apr 24, 2026
Merged

Feature/nice to have polish#24
11 commits merged into
mainfrom
feature/nice-to-have-polish

Conversation

@NowwarAbuRashed

@NowwarAbuRashed NowwarAbuRashed commented Apr 18, 2026

Copy link
Copy Markdown
Owner

it is sum 3 branchesFrontEnd_Module_2, feature/admin-operational, feature/admin-medium-impact

Summary by CodeRabbit

  • New Features

    • Admin dashboard, activity log, real-time admin notifications, system settings UI, and manager performance reporting
    • Bulk catalog tools (status & price), image upload, and cash collection workflow
  • Enhancements

    • Multilingual support (English + Arabic) with RTL handling
    • Improved inventory & payment status visibility; expanded admin user and revenue reporting
  • UI/UX Improvements

    • Redesigned layouts, responsive sidebars, new shared components, stronger form validation and error handling

@coderabbitai

coderabbitai Bot commented Apr 18, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds admin/system features (activity logs, notifications, settings), bulk product operations, inventory+order/payment integrations, SignalR real-time notifications, EF Core entities/migrations, many backend repositories/services/controllers, and a large i18n/localization and UI overhaul across the frontend.

Changes

Cohort / File(s) Summary
Project Docs
ProjectInstructions.txt
New local setup and run guide for backend (.NET 8), frontend (React+Vite), and database config.
Admin DTOs & Domain
backend/.../Admin/DTOs/Requests/AdminRequests.cs, .../Responses/AdminOrderDto.cs, .../Responses/AdminUserDto.cs, .../Responses/DashboardResponse.cs, .../Responses/ManagerPerformanceDto.cs, .../Responses/RevenueReportResponse.cs
New admin request/response DTOs for dashboard, users, orders, revenue, and manager performance.
Admin Interfaces
backend/.../Admin/Interfaces/*
Added interfaces for admin repository/service, activity logs, notifications, settings, and a notification dispatcher.
Admin Services
backend/.../Admin/Services/*
Implementations for activity logging, admin business logic (dashboard, user/order updates), system notifications (persist + broadcast), and settings persistence.
Domain Entities & EF Config
backend/.../Domain/Entities/*, .../Infrastructure/Data/.../Configurations/*, ApplicationDbContext.cs
New entities: SystemActivityLog, SystemNotification, SystemSetting; OrderItem property renamed to ProductId; Product.OrderItemId removed; EF configurations and DbSet additions.
Migrations & Snapshot
backend/.../Migrations/*
Three migrations and updated model snapshot to add system_activity_logs, system_notifications, system_settings and OrderItem/ProductId mapping.
Infrastructure Repositories
backend/.../Infrastructure/Repositories/*
New repository implementations: ActivityLogRepository, AdminRepository (many aggregates), SystemNotificationRepository, SystemSettingsRepository; ProductRepository bulk/update and GetByIds implemented.
Product & Inventory Logic
backend/.../Inventorys/Services/InventoryService.cs, .../Products/Services/ProductService.cs, .../Products/Interfaces/*
Inventory adjustments now auto-manage product status and throw ArgumentException on insufficient stock; product service gains bulk status/price operations and activity logging; product repo supports bulk update.
Orders & Payments
backend/.../Orders/*, backend/.../Payments/*
OrderService wired to log actions and send notifications; order status methods now accept performedByUserId; PaymentService uses notification service and MarkCashPaidAsync returns PaymentResponse.
Admin API Controllers
backend/.../OrderSystemApi/Controllers/Admin/*
New admin controllers for dashboard, users, orders, revenue, catalog bulk ops, activity logs, notifications, and system settings (role-protected endpoints).
Manager & Upload Controllers
backend/.../Controllers/Manager/*
Versioned routes, role authorization, cash-collected endpoint, and new upload endpoint with validation and file storage to wwwroot/uploads.
SignalR & Dispatcher
backend/.../Hubs/AdminHub.cs, .../Services/AdminNotificationDispatcher.cs, Program.cs
Added AdminHub (Authorize roles), dispatcher broadcasting via SignalR, DI registrations, hub mapping /hub/admin, static file serving, and uploads directory creation.
Project Dependencies
backend/.../OrderSystem.Api.csproj, frontend/package.json
Added Bogus (backend) and frontend deps: MUI, emotion, @microsoft/signalr, date-fns.
Frontend i18n & Providers
frontend/src/app/i18n/*, AppProviders.tsx, index.html
Full i18n stack (en/ar), direction helpers, messages, I18nProvider, and provider wiring; HTML lang/dir and fonts updated.
Frontend Core & Styles
frontend/src/index.css, frontend/src/App.css, frontend/src/app/api/http.ts, auth-context.tsx
Design tokens overhaul, RTL support, new utilities/animations, removed old App.css, 401 handler refined to ignore auth endpoints, auth context adds updateUser.
Routing & Layouts
frontend/src/app/router/index.tsx, frontend/src/layouts/*
Router restructured (NotFound, role-based branches), layouts updated for i18n, responsive sidebars, language switcher, cart badge, search, and mobile behavior.
Admin Frontend (API/Hooks/Pages/Components)
frontend/src/modules/admin/*, frontend/src/shared/components/NotificationBell.tsx
Admin API client + hooks, NotificationBell with SignalR, many admin pages (dashboard, users, orders, revenue, activity, catalog, settings, inventory, low stock, manager performance) with CSV export and bulk actions.
Manager Frontend
frontend/src/modules/manager/*
Manager pages updated (i18n, forms with react-hook-form + zod), image upload flow, cash collection action, API/hook additions for uploads and cash marking.
Customer & Catalog Frontend
frontend/src/modules/customer/*, frontend/src/modules/catalog/*, cart/*
Customer flows updated with i18n, quantity stepper, improved galleries, timelines, and form validation; products list/detail updated to ProductCard usage.
Shared UI Components & Utilities
frontend/src/shared/components/*, frontend/src/shared/utils/*
Many new/updated reusable components (DetailList, ProductCard, QuantityStepper, StatCard, TableToolbar, PageContainer, LanguageSwitcher, Footer, etc.), CSV export util, enhanced date formatting and API error mapping, OrderDetails type extended with paymentStatus.

Sequence Diagram(s)

sequenceDiagram
    participant Admin as Admin/Manager
    participant AdminClient as Admin Client (Browser)
    participant AdminHub as SignalR Hub
    participant Dispatcher as AdminNotificationDispatcher
    participant NotificationService as SystemNotificationService
    participant NotificationRepo as SystemNotificationRepository
    participant Database as Database

    AdminClient->>AdminHub: Connect to /hub/admin (SignalR)
    Admin->>NotificationService: SendNotificationAsync(type,title,message,...)
    NotificationService->>NotificationRepo: AddAsync(notification)
    NotificationRepo->>Database: INSERT system_notifications
    Database-->>NotificationRepo: new Id
    NotificationRepo-->>NotificationService: return Id
    NotificationService->>Dispatcher: BroadcastNotificationAsync(notification)
    Dispatcher->>AdminHub: SendAsync("ReceiveNotification", notification)
    AdminHub->>AdminClient: ReceiveNotification event
    AdminClient->>AdminClient: Invalidate cache / refresh UI
Loading
sequenceDiagram
    participant Manager as Manager
    participant Controller as ManagerOrdersController
    participant OrderSvc as OrderService
    participant ActivitySvc as ActivityLogService
    participant PaymentSvc as PaymentService
    participant NotificationSvc as SystemNotificationService

    Manager->>Controller: PUT mark-ready/{orderId}
    Controller->>OrderSvc: MarkReadyAsync(orderId, performedByUserId)
    OrderSvc->>OrderSvc: Update order status in DB
    OrderSvc->>ActivitySvc: LogActionAsync("ORDER_STATUS_CHANGED", ...)
    ActivitySvc->>Database: INSERT system_activity_logs
    Manager->>Controller: POST {orderId}/cash-collected
    Controller->>PaymentSvc: MarkCashPaidAsync(orderId)
    PaymentSvc->>PaymentSvc: Update payment status = PAID
    PaymentSvc->>NotificationSvc: SendNotificationAsync("PAYMENT_RECEIVED", ...)
    NotificationSvc->>Dispatcher: BroadcastNotificationAsync(...)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~150 minutes

Possibly related PRs

Poem

🐰 I hopped through code both day and night,
Logs and bells now glow in SignalR light,
Settings, pages, and bulk edits too—
Orders march on and translations grew.
A tiny rabbit cheers this feature view! 🥕

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/nice-to-have-polish

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Note

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

Caution

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

⚠️ Outside diff range comments (5)
backend/OrderSystemApi/OrderSystemApi/Controllers/Manager/ManagerProductImagesController.cs (1)

36-54: ⚠️ Potential issue | 🟠 Major

Enforce the product scope on image mutations.

productId is bound from the nested route but ignored for update/delete, so /products/{productId}/images/{imageId} can mutate an image that belongs to a different product if the imageId is known. Pass productId into the service or verify ownership before mutating.

Suggested direction
- var updated = await _productImageService.UpdateImageAsync(imageId, request, ct);
+ var updated = await _productImageService.UpdateImageAsync(productId, imageId, request, ct);
@@
- var deleted = await _productImageService.DeleteImageAsync(imageId, ct);
+ var deleted = await _productImageService.DeleteImageAsync(productId, imageId, ct);

The service should return not found/false when the image does not belong to the route product.

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

In
`@backend/OrderSystemApi/OrderSystemApi/Controllers/Manager/ManagerProductImagesController.cs`
around lines 36 - 54, The UpdateImage and DeleteImage actions ignore the route
productId and thus can mutate images across products; modify UpdateImage and
DeleteImage to enforce product-scoped mutations by including the route productId
in the validation/service call (e.g. call
_productImageService.UpdateImageAsync(imageId, productId, request, ct) and
_productImageService.DeleteImageAsync(imageId, productId, ct) or first verify
ownership via a method like _productImageService.IsImageForProductAsync(imageId,
productId) and return NotFound() when it does not belong to the specified
product), ensuring the service returns false/NotFound when imageId is not owned
by productId.
backend/OrderSystemApi/OrderSystemApi/Program.cs (1)

152-173: ⚠️ Potential issue | 🟡 Minor

Uploads folder is served publicly via UseStaticFiles().

wwwroot/uploads is exposed anonymously to the internet once UseStaticFiles() is enabled. This is fine for product images, but if the same folder ever stores admin/manager-uploaded content that isn't intended to be public (receipts, invoices, user-uploaded documents), URL guessing/enumeration becomes a risk. Consider (a) serving uploads from a dedicated request path with content-type and size restrictions, and/or (b) storing sensitive artifacts outside wwwroot behind an authorized controller action.

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

In `@backend/OrderSystemApi/OrderSystemApi/Program.cs` around lines 152 - 173, The
uploads folder is currently created under wwwroot and becomes publicly
accessible via app.UseStaticFiles(), so change the design: stop serving
sensitive uploads from wwwroot and either (A) move the uploadsPath out of the
public webroot (e.g., set uploadsPath =
Path.Combine(app.Environment.ContentRootPath, "uploads") instead of
"wwwroot/uploads") and remove any StaticFile exposure for that folder, then
implement an authorized endpoint (e.g., an UploadsController with a GetFile
action) that locates files via the same uploadsPath/FileProvider, validates
permissions and content-type/size, and returns a FileStreamResult; or (B) if you
must serve statically, configure app.UseStaticFiles(new StaticFileOptions {
FileProvider = new PhysicalFileProvider(uploadsPath), RequestPath = "/uploads",
OnPrepareResponse = ctx => { /* reject anonymous or enforce headers */ } }) but
ensure OnPrepareResponse enforces authentication/authorization and strict
content-type checks — update references to uploadsPath and the call to
app.UseStaticFiles() in Program.cs accordingly.
backend/OrderSystemApi/OrderSystem.Application/Inventorys/Services/InventoryService.cs (1)

42-66: ⚠️ Potential issue | 🔴 Critical

Fix atomicity violation: wrap product update and movement insert in a single transaction.

Lines 66 and 77 execute two separate SaveChangesAsync() calls. If _productRepository.Update() succeeds but _movementRepository.AddAsync() fails (or vice versa), the product quantity/status update persists without a matching InventoryMovement record, corrupting audit history and leaving the inventory in an inconsistent state.

Refactor to use explicit transaction control (e.g., _context.Database.BeginTransactionAsync()) or implement a Unit of Work pattern to ensure both operations commit atomically within a single database transaction.

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

In
`@backend/OrderSystemApi/OrderSystem.Application/Inventorys/Services/InventoryService.cs`
around lines 42 - 66, Currently InventoryService performs
_productRepository.Update(product) and _movementRepository.AddAsync(movement) in
separate save operations causing an atomicity violation; modify the method to
wrap both the product update and the movement insert in a single database
transaction (e.g., use _context.Database.BeginTransactionAsync() or the
project’s UnitOfWork) so that both _productRepository.Update(product) and
_movementRepository.AddAsync(...) are committed together and rolled back
together on failure, ensuring SaveChangesAsync is called only within the
enclosing transaction scope.
frontend/src/modules/manager/hooks/useManagerOrders.ts (1)

26-28: ⚠️ Potential issue | 🟠 Major

Invalidate all orders list queries using a root prefix key.

On line 27, invalidateOrders() uses managerOrderKeys.orders() which resolves to ['manager', 'orders', undefined]. This does not prefix-match parameterized list queries like ['manager', 'orders', { status: 'pending' }], leaving filtered orders lists stale after mutations succeed.

Create a root key for prefix-based invalidation:

Proposed fix
 export const managerOrderKeys = {
+  ordersRoot: () => ['manager', 'orders'] as const,
   orders: (params?: OrderQuery) => ['manager', 'orders', params] as const,
   order: (id: number) => ['manager', 'orders', id] as const,
 };

 function invalidateOrders(orderId: number) {
-  queryClient.invalidateQueries({ queryKey: managerOrderKeys.orders() });
+  queryClient.invalidateQueries({ queryKey: managerOrderKeys.ordersRoot() });
   queryClient.invalidateQueries({ queryKey: managerOrderKeys.order(orderId) });
 }

Affects all mutations: useMarkOrderReady, useMarkOrderOutForDelivery, useMarkOrderDelivered, and useMarkCashCollected (line 55).

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

In `@frontend/src/modules/manager/hooks/useManagerOrders.ts` around lines 26 - 28,
invalidateOrders currently calls queryClient.invalidateQueries with
managerOrderKeys.orders() which resolves to a fully parameterized key and won’t
prefix-match filtered lists; update invalidateOrders (the function in
useManagerOrders.ts) to call queryClient.invalidateQueries with prefix matching
by passing exact: false (e.g., queryClient.invalidateQueries({ queryKey:
managerOrderKeys.orders(), exact: false })) so all parameterized list queries
(like ['manager','orders',{...}]) get invalidated, leaving the per-order
invalidation (managerOrderKeys.order(orderId)) intact for the single-order
cache.
backend/OrderSystemApi/OrderSystem.Application/Orders/Services/OrderService.cs (1)

270-329: ⚠️ Potential issue | 🟠 Major

Keep activity logging from turning successful status changes into failed API calls.

Each status method updates the order before awaiting LogActionAsync. If logging fails, the API reports failure even though the status changed. Persist the status and log atomically, or make the log write best-effort/outbox-backed.

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

In
`@backend/OrderSystemApi/OrderSystem.Application/Orders/Services/OrderService.cs`
around lines 270 - 329, The activity logging currently runs after updating the
order and, if it throws, will surface as an API failure even though the order
status was persisted; in the status-change methods (e.g.,
MarkOutForDeliveryAsync, MarkDeliveredAsync, and the Ready method where
_orderRepository.Update(order) is followed by
_activityLogService.LogActionAsync) make logging best-effort by ensuring the
order update is persisted first and wrapping the LogActionAsync call in a
non-failing path (e.g., try/catch that captures/logs the logging error without
rethrowing) or replace the direct LogActionAsync call with an outbox enqueue
that is transactional with the order update so failures to record activity do
not cause the API to fail.
🧹 Nitpick comments (25)
frontend/src/shared/components/SectionHeading.tsx (1)

17-17: Prefer shrink-0 over flex-shrink-0 for Tailwind v4.

In Tailwind CSS v4, flex-shrink-* utilities were renamed to shrink-*. The legacy name may still resolve, but the idiomatic v4 class is shrink-0.

♻️ Proposed change
-      {action && <div className="flex-shrink-0">{action}</div>}
+      {action && <div className="shrink-0">{action}</div>}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/shared/components/SectionHeading.tsx` at line 17, The JSX in the
SectionHeading component uses the legacy Tailwind utility "flex-shrink-0" on the
action wrapper ({action && <div className="flex-shrink-0">{action}</div>});
update that class to the Tailwind v4 idiomatic "shrink-0" so the action
container uses the new utility name (change the className on the action div in
SectionHeading.tsx).
backend/OrderSystemApi/OrderSystemApi/OrderSystem.Api.csproj (1)

10-10: Verify the intended use case for Bogus in the API project.

The Bogus library is typically used for generating fake/test data and is commonly added to test projects rather than production API projects. Adding it here raises questions:

  • If used for development/testing only: Consider moving this dependency to a separate test project or seeder tool to keep the production API lean.
  • If used for production data seeding: Ensure this is intentional and that Bogus data generation is properly isolated to startup/migration scenarios, not active request handling.

Adding development/test dependencies to the main API increases bundle size and production surface area unnecessarily.

📦 Alternative approaches for test data generation

Option 1: Move to a separate test or seeder project:

  • Create OrderSystem.DataSeeder project
  • Reference Bogus only there
  • Run as a separate tool or startup configuration

Option 2: Use conditional compilation if seeding is needed:

-    <PackageReference Include="Bogus" Version="35.6.5" />
+    <PackageReference Include="Bogus" Version="35.6.5" Condition="'$(Configuration)' == 'Debug'" />

This ensures Bogus is only included in development builds.

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

In `@backend/OrderSystemApi/OrderSystemApi/OrderSystem.Api.csproj` at line 10, The
csproj currently references the Bogus package via PackageReference
Include="Bogus", which is intended for test/data seeding rather than runtime API
usage; remove this PackageReference from the API project and either (A) move
Bogus into a dedicated seeder/test project (e.g., create OrderSystem.DataSeeder
and add Bogus there) and perform seeding from that tool, or (B) if you must keep
it in the repo, limit inclusion to non-production builds by adding Bogus only to
a test/seeder project or use conditional inclusion so Bogus is only
referenced/used during startup seeding paths and never in active request
handling (also audit any code in Startup/Program that references Bogus to ensure
it’s isolated to development/seeding flows).
frontend/src/shared/components/PageContainer.tsx (1)

1-1: Consider using a type-only import for ReactNode.

ReactNode is used only as a type in the interface. While your project has verbatimModuleSyntax disabled in tsconfig.app.json, using import type is still a best practice for clarity and future-proofs the code should configs change.

Suggested improvement
-import { ReactNode } from 'react';
+import type { ReactNode } from 'react';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/shared/components/PageContainer.tsx` at line 1, The import for
ReactNode in PageContainer.tsx is only used as a type; change it to a type-only
import to make the intent explicit and future-proof the file: replace the
current import of ReactNode with a type import (import type { ReactNode } from
'react') so the symbol is only retained for type-checking and not as a runtime
import; ensure any other uses in the file still reference the ReactNode type in
the component props/interface.
backend/OrderSystemApi/OrderSystem.Application/Products/Interfaces/IProductRepository.cs (1)

22-22: Thread cancellation through the bulk update contract.

UpdateBulk can persist many products, but it cannot observe request cancellation while the read methods above can. Consider adding a CancellationToken while this API is new.

♻️ Proposed contract adjustment
-        Task<bool> UpdateBulk(IEnumerable<Product> products);
+        Task<bool> UpdateBulk(IEnumerable<Product> products, CancellationToken ct);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/OrderSystemApi/OrderSystem.Application/Products/Interfaces/IProductRepository.cs`
at line 22, The UpdateBulk contract lacks cancellation support—change the
IProductRepository.UpdateBulk signature to accept a CancellationToken (e.g.,
Task<bool> UpdateBulk(IEnumerable<Product> products, CancellationToken
cancellationToken)) and update all implementations and call sites to pass the
token through (propagate token from controller/service entry points and honor it
in any loops/async operations inside implementations such as repository classes
that perform DB or batch work), and ensure cancellations are observed (throw
OperationCanceledException or return early) where appropriate.
backend/OrderSystemApi/OrderSystemApi/Program.cs (1)

166-170: CORS: consider reading allowed origins from configuration.

Origins are hardcoded to localhost dev ports, which will need a code change for staging/prod. Moving them to appsettings (or env vars) avoids a code edit per environment and reduces the chance of deploying a dev-only CORS policy. Non-blocking.

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

In `@backend/OrderSystemApi/OrderSystemApi/Program.cs` around lines 166 - 170, The
app.UseCors call currently hardcodes origins; change it to read allowed origins
from configuration instead: add a configuration key (e.g. "Cors:AllowedOrigins")
in appsettings or an env var, load it via the IConfiguration instance in
Program.cs, parse into a string[] (or split a comma-separated env var) and pass
that array to builder.WithOrigins(...) in the existing app.UseCors block
(referencing the same app.UseCors builder lambda); keep
AllowAnyHeader/AllowAnyMethod/AllowCredentials as-is and validate the config
value for null/empty before applying.
frontend/src/modules/manager/api/products.api.ts (1)

51-62: Optional: type the POST response.

http.post is invoked without a generic, so data is inferred as any and addProductImage returns Promise<any>. Consider specifying a response type (e.g. { id: number } or a ProductImage DTO) for safer consumers.

Proposed tweak
-  const { data } = await http.post(
+  const { data } = await http.post<{ id: number }>(
     `/api/v1/manager/products/${payload.productId}/images`,
     { imageUrl, altText, sortOrder, isPrimary }
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/manager/api/products.api.ts` around lines 51 - 62, The
addProductImage function currently returns Promise<any> because http.post is
called without a generic; update addProductImage to declare and return a
concrete response type (for example a small DTO like ProductImage or { id:
number }) and pass that type as the generic to http.post so TypeScript infers
data correctly; update the function signature (addProductImage(payload:
CreateProductImageRequest): Promise<YourResponseType>) and adjust any callers if
needed. Ensure the change is applied in the addProductImage implementation that
constructs the POST to `/api/v1/manager/products/${payload.productId}/images`
(no change needed for deleteProductImage).
backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418180805_AddSystemNotification.cs (1)

14-31: Consider indexing unread/recent notification access.

If the new notification endpoints query unread items and recent activity, this table will scan as it grows. Add an index that matches the read-status/recent-sort access pattern.

Possible migration addition
                 constraints: table =>
                 {
                     table.PrimaryKey("PK_system_notifications", x => x.Id);
                 });
+
+            migrationBuilder.CreateIndex(
+                name: "IX_system_notifications_IsRead_CreatedAt",
+                table: "system_notifications",
+                columns: new[] { "IsRead", "CreatedAt" });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418180805_AddSystemNotification.cs`
around lines 14 - 31, Add a composite index to speed unread/recent queries by
calling migrationBuilder.CreateIndex after the CreateTable for
"system_notifications"; create an index named like
"IX_system_notifications_IsRead_CreatedAt" on the columns IsRead and CreatedAt
(e.g., columns: new[] { "IsRead", "CreatedAt" }) so queries filtering IsRead and
ordering by CreatedAt will use the index.
backend/OrderSystemApi/OrderSystem.Infrastructure/Data/Configurations/SystemNotificationConfiguration.cs (1)

14-37: Consider adding indexes for common notification query patterns.

Admin notification listings typically filter/sort by fields like IsRead, NotificationType, or CreatedAt. If the repository queries on these, adding indexes here (e.g., builder.HasIndex(x => x.CreatedAt), builder.HasIndex(x => new { x.IsRead, x.CreatedAt })) will prevent full table scans as the table grows.

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

In
`@backend/OrderSystemApi/OrderSystem.Infrastructure/Data/Configurations/SystemNotificationConfiguration.cs`
around lines 14 - 37, Add indexes on the SystemNotification entity to support
common query patterns: update the Configure method in
SystemNotificationConfiguration to add builder.HasIndex(x => x.CreatedAt),
builder.HasIndex(x => x.NotificationType), and a composite index
builder.HasIndex(x => new { x.IsRead, x.CreatedAt }) so queries that filter/sort
by CreatedAt, NotificationType, or IsRead+CreatedAt use indexes instead of full
table scans; ensure these index calls are placed after the Property(...)
declarations in the Configure(EntityTypeBuilder<SystemNotification> builder)
method.
frontend/src/modules/customer/pages/CustomerOrdersListPage.tsx (1)

54-77: Dead emptyMessage prop now that empty state is handled via early return.

Since the component early-returns the EmptyState whenever !isLoading && data.items.length === 0, the AppTable's emptyMessage branch is unreachable. Either drop the prop for clarity, or (preferred) consolidate and let AppTable render the shared EmptyState so loading→empty transitions stay inside one render path without an extra unmount/remount of PageHeader.

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

In `@frontend/src/modules/customer/pages/CustomerOrdersListPage.tsx` around lines
54 - 77, The early-return in CustomerOrdersListPage that renders EmptyState
makes the AppTable.emptyMessage branch dead; remove the early return and instead
let AppTable handle empty rendering so PageHeader stays mounted across
loading→empty transitions, or if you prefer to keep the early-return, remove the
unused emptyMessage prop from the AppTable call; to implement the preferred
consolidation, keep PageHeader rendered, pass either the EmptyState component or
the t.orders.noOrders/noOrdersDesc strings into AppTable (via emptyMessage or an
emptyComponent prop), and rely on AppTable to check isLoading and
data?.items.length to render the same EmptyState UI.
backend/OrderSystemApi/OrderSystem.Application/Products/Interfaces/IProductService.cs (1)

18-19: Document the percentageChange unit and consider bounds.

percentageChange as decimal is ambiguous — callers can't tell whether 10 means +10% or +1000% (i.e., 0.10). Please clarify via XML docs on the interface (and enforce the contract in the implementation, e.g., reject values that would make prices non-positive, and cap the allowed range to avoid catastrophic bulk mutations from an admin typo).

Suggested doc
+        /// <summary>
+        /// Applies a percentage change to the price of the specified products.
+        /// </summary>
+        /// <param name="percentageChange">Percentage expressed as a whole number, e.g. 10 = +10%, -5 = -5%.</param>
         Task<int> BulkUpdatePriceAsync(List<long> productIds, decimal percentageChange, CancellationToken ct);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/OrderSystemApi/OrderSystem.Application/Products/Interfaces/IProductService.cs`
around lines 18 - 19, Add XML documentation to
IProductService.BulkUpdatePriceAsync clarifying that percentageChange is a
fractional multiplier (e.g., 0.10 = +10%, -0.10 = -10%) and whether the method
applies price = price * (1 + percentageChange). In the implementing class for
BulkUpdatePriceAsync validate percentageChange against safe bounds (e.g.,
disallow values that would make any resulting price non-positive and cap extreme
inputs to a configured range such as [-0.9, 1.0] or similar), and throw
ArgumentOutOfRangeException (or return a clear error) when the argument is
outside policy; also explicitly handle/validate each product’s computed new
price before persisting to avoid catastrophic bulk mutations.
frontend/src/app/i18n/i18n-context.tsx (1)

35-38: Wrap localStorage.setItem in try/catch for consistency.

getSavedLocale already guards against storage errors (Safari private mode, quota, disabled cookies), but setLocale doesn't. If setItem throws, the user's locale flip will fail loudly even though the in-memory state updated successfully. Match the defensive pattern used above.

   const setLocale = useCallback((newLocale: Locale) => {
     setLocaleState(newLocale);
-    localStorage.setItem(LOCALE_STORAGE_KEY, newLocale);
+    try {
+      localStorage.setItem(LOCALE_STORAGE_KEY, newLocale);
+    } catch {
+      // Ignored — in-memory state still reflects the change
+    }
   }, []);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/i18n/i18n-context.tsx` around lines 35 - 38, The setLocale
callback currently calls localStorage.setItem without guarding against storage
errors; update the setLocale function (which calls setLocaleState and uses
LOCALE_STORAGE_KEY) to wrap the localStorage.setItem(LOCALE_STORAGE_KEY,
newLocale) call in a try/catch so failures (e.g., Safari private mode) don't
throw; ensure you still call setLocaleState(newLocale) before/around the try and
handle the catch by logging or silently ignoring to match the defensive pattern
used by getSavedLocale.
frontend/src/layouts/ManagerLayout.tsx (1)

29-79: Avoid redeclaring SidebarContent on every render.

Defining SidebarContent inside ManagerLayout means React sees a brand-new component type on every parent render. Every state update (e.g., toggling sidebarOpen, auth/i18n context changes) forces the sidebar subtree to unmount and remount, discarding any internal state inside LanguageSwitcher or future children (focus, uncontrolled input values, etc.). Extract it as a sibling component, or inline the JSX into a const sidebarContent = (...) expression so it isn't a new component identity on each render.

♻️ Suggested minimal refactor
-  const SidebarContent = () => (
-    <>
+  const sidebarContent = (
+    <>
       ...
-    </>
-  );
+    </>
+  );
@@
-        <SidebarContent />
+        {sidebarContent}
@@
-            <SidebarContent />
+            {sidebarContent}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/layouts/ManagerLayout.tsx` around lines 29 - 79, SidebarContent
is declared as a component inside ManagerLayout which creates a new component
type on each render causing unmount/remount of its subtree (affecting
LanguageSwitcher and other internal state); move SidebarContent out of the
ManagerLayout function (make it a sibling/top-level component) or replace it
with a const sidebarContent = ( ... ) JSX value so its identity is stable, and
pass in any needed props/state (user, navLinks, location, setSidebarOpen,
handleLogout, t) to the extracted component instead of capturing them from
closure.
frontend/src/modules/catalog/pages/ProductsListPage.tsx (1)

31-39: Search is debounced-free; consider debouncing before writing to the URL.

setSearch runs on every keystroke and writes to the URL + triggers a new query. Even with replace: true, this spams react-router updates and upstream API calls. A small debounce (e.g., 300 ms) on the URL write — while keeping the input value in local state — would reduce churn without changing behavior on submit.

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

In `@frontend/src/modules/catalog/pages/ProductsListPage.tsx` around lines 31 -
39, setSearch currently writes to URL on every keystroke causing excessive
router updates and queries; debounce the URL write while keeping the immediate
input in local state. Change the component (ProductsListPage) to store the raw
input in a local state (e.g., searchInput) updated on every keystroke, and
debounce the call that calls setSearchParams (the inner updater that
sets/deletes 'search' and deletes 'page') by ~300ms using a stable debounced
function or a timeout stored in a ref; ensure you still call setSearchParams
with { replace: true } and clear the timeout on unmount to avoid leaks. Use the
existing setSearchParams/setSearch logic as the target for the debounced
invocation so behavior on form submit can still immediately sync the URL.
backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418174733_AddSystemActivityLog.cs (1)

38-41: Consider additional indexes for common activity-log queries.

Activity logs are typically queried by timestamp (descending for recency) and by entity_type+entity_id (to show history for a given entity). As the table grows, the single user_id index may not be sufficient. A follow-up migration adding e.g. IX_system_activity_logs_timestamp and a composite IX_system_activity_logs_entity_type_entity_id would help once usage patterns are clear.

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

In
`@backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418174733_AddSystemActivityLog.cs`
around lines 38 - 41, Add additional indexes to the migration to support common
queries: create an index on the timestamp column (e.g., index name
"IX_system_activity_logs_timestamp" and consider a descending order for recency
if your EF/DB supports it) and create a composite index on entity_type +
entity_id (e.g., "IX_system_activity_logs_entity_type_entity_id"); update the
AddSystemActivityLog migration by adding two migrationBuilder.CreateIndex calls
(matching the existing migrationBuilder.CreateIndex usage for
"IX_system_activity_logs_user_id") to create these indexes so queries filtering
by timestamp or by entity_type/entity_id are optimized.
frontend/src/modules/auth/pages/LoginPage.tsx (1)

2-2: Consider using type-only import for Path from react-hook-form.

Path is only used as a type (line 39: key as Path<LoginForm>). While not strictly required with the current configuration (verbatimModuleSyntax: false), using a type-only import improves clarity:

Suggested change
-import { useForm, Path } from 'react-hook-form';
+import { useForm } from 'react-hook-form';
+import type { Path } from 'react-hook-form';
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/auth/pages/LoginPage.tsx` at line 2, The import
currently brings in Path from 'react-hook-form' as a value import; change it to
a type-only import so Path is imported only for types (leave useForm as a normal
import). Update the top-level import that references Path to a type import and
keep the runtime import for useForm unchanged; this affects the statement that
casts keys with "key as Path<LoginForm>" and ensures Path is erased at runtime.
backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs (1)

502-510: Add an index for recent activity-log reads.

The activity API returns recent logs and supports filters, but only PerformedByUserId is indexed. Add an index such as Timestamp descending, or composite indexes aligned with entityType/userId + Timestamp, in the source EF configuration and regenerate the migration.

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

In
`@backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs`
around lines 502 - 510, The snapshot shows only an index on PerformedByUserId;
update the EF Core entity configuration for the SystemActivityLog entity (e.g.,
in the EntityTypeBuilder for SystemActivityLog inside OnModelCreating or the
SystemActivityLogConfiguration class) to add an index on the Timestamp property
(preferably as descending) and/or a composite index such as (EntityType,
PerformedByUserId, Timestamp) to support recent reads and common filters, using
HasIndex(...).Then regenerate the migration so the
ApplicationDbContextModelSnapshot and migration files include the new index
definitions and apply the migration.
backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/ActivityLogRepository.cs (1)

21-54: Repository eagerly commits, and count is unbounded.

  • AddAsync calls SaveChangesAsync internally. If the caller is part of a larger unit of work (e.g., status change + activity log from OrderService.MarkReadyAsync), the log is persisted even if subsequent operations fail — or, conversely, the caller's prior un-flushed changes get flushed alongside the log. Consider letting the caller own SaveChangesAsync so the log row participates in the same transaction as the action it describes.
  • GetRecentLogsAsync(int count, …) does not validate/cap count. A large or negative value will either thrash the DB or (for negatives) produce an EF runtime error. Clamp, e.g., count = Math.Clamp(count, 1, 500);.
♻️ Suggested fix for the count bound
         public async Task<IEnumerable<SystemActivityLog>> GetRecentLogsAsync(int count, string? entityType = null, long? userId = null, CancellationToken cancellationToken = default)
         {
+            if (count <= 0) count = 50;
+            count = System.Math.Min(count, 500);
             var query = _context.SystemActivityLogs
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/ActivityLogRepository.cs`
around lines 21 - 54, The AddAsync method currently calls
_context.SaveChangesAsync causing eager commits; change
AddAsync(SystemActivityLog log, ...) to only call
_context.SystemActivityLogs.AddAsync(log, cancellationToken) and remove the
SaveChangesAsync call so callers control transaction/commit (e.g.,
OrderService.MarkReadyAsync can flush once). In GetRecentLogsAsync(int count,
...) validate and clamp count (e.g., count = Math.Clamp(count, 1, 500)) before
applying Take(count) to prevent negative or excessive values; keep existing
filtering and ordering logic intact.
backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/ISystemNotificationRepository.cs (1)

14-14: UpdateAsync missing CancellationToken — inconsistent with the rest of the interface.

Every other method on ISystemNotificationRepository accepts a CancellationToken ct. Add one here too for API consistency and to allow cancellation to flow through the service/controller layers.

-        Task<bool> UpdateAsync(SystemNotification notification);
+        Task<bool> UpdateAsync(SystemNotification notification, CancellationToken ct);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/ISystemNotificationRepository.cs`
at line 14, The UpdateAsync method on ISystemNotificationRepository is missing a
CancellationToken parameter; update its signature to Task<bool>
UpdateAsync(SystemNotification notification, CancellationToken ct) so it matches
the other methods and allows cancellation to flow; ensure you update any
implementations of ISystemNotificationRepository (classes implementing
UpdateAsync) and any call sites (service/controller layers) to pass the
CancellationToken through.
frontend/src/modules/admin/pages/RevenueReportPage.tsx (1)

60-82: Currency formatting & CSV button/headers bypass i18n and hardcode USD.

  • `$${row.amount.toFixed(2)}`, `$${d.totalRevenue.toFixed(2)}`, etc., hardcode the $ symbol. The codebase already uses PriceText (used in CartPage) for consistent currency rendering — prefer that so locale/currency changes stay centralized.
  • Export CSV button label and the CSV headers ['Date', 'Orders', 'Revenue'] are not translated. At minimum translate the button label; the CSV headers can stay English if that's a deliberate "machine-readable export" choice, but document it.
  • Minor: exporting raw row.date into CSV (line 73) while displaying it via formatDate(..., dateLocale) means CSV and UI diverge — consider exporting the formatted string if the CSV is intended for end-users.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/admin/pages/RevenueReportPage.tsx` around lines 60 - 82,
Replace hardcoded USD symbols and raw date export: use the existing PriceText
component to render amounts (replace references to row.amount and d.totalRevenue
in the table and header export) so currency formatting follows app
locale/currency; for CSV export, pass the same formatted date string (use
formatDate(date, dateLocale) or the same formatting helper used in the UI)
instead of raw r.date. Also make the export button label translatable (use
t.admin.exportCsv or appropriate i18n key) and document that CSV headers
['Date','Orders','Revenue'] are intentionally kept machine-readable if that is
desired; update downloadCSV usage in the Export button click handler
accordingly.
frontend/src/modules/cart/pages/CartPage.tsx (1)

19-19: Avoid any for CartItemRow props.

The updateItem, deleteItem, and t props are typed as any, which defeats TS checking in an otherwise typed component. Prefer the actual mutate signatures returned by the hooks and the t type exported from the i18n context.

♻️ Suggested typing
-function CartItemRow({ item, updateItem, deleteItem, t }: { item: CartItem; updateItem: any; deleteItem: any; t: any }) {
+type UpdateItemFn = (vars: { itemId: number; quantity: number }) => void;
+type DeleteItemFn = (itemId: number) => void;
+
+function CartItemRow({ item, updateItem, deleteItem, t }: {
+  item: CartItem;
+  updateItem: UpdateItemFn;
+  deleteItem: DeleteItemFn;
+  t: ReturnType<typeof useI18n>['t'];
+}) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/cart/pages/CartPage.tsx` at line 19, CartItemRow
currently uses `any` for updateItem, deleteItem, and t which loses type safety;
replace those `any` types with the actual mutate function signatures from your
cart hooks and the i18n T-function type. Concretely, change the prop types on
function CartItemRow to use the mutate types returned by your hooks (e.g. the
`mutate`/`mutateAsync` type from your useUpdateCartItem / useDeleteCartItem
hooks or React Query/SWR MutateFunction return types) and import the i18n
TFunction (or the `T`/`TFunction` type exported by your i18n setup) for `t`;
keep CartItem as the item type. This ensures updateItem/deleteItem match the
real hook signatures (mutate/mutateAsync) and `t` uses the i18n TFunction.
backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/SystemSettingsRepository.cs (1)

31-52: Remove redundant .Update() call and document null description semantics.

  • _context.SystemSettings.Update(existing) is unnecessary — existing is already tracked after FirstOrDefaultAsync, so EF change tracking will pick up property mutations. Calling Update marks all properties as modified, which can bloat the generated UPDATE statement.
  • if (description != null) existing.Description = description; silently preserves the existing description when callers pass null. If this partial-update behavior is intentional, document it; otherwise, callers have no way to clear a description via this upsert.
♻️ Proposed tweak
         if (existing != null)
         {
             existing.Value = value;
             if (description != null) existing.Description = description;
             existing.UpdatedAt = DateTime.UtcNow;
-            _context.SystemSettings.Update(existing);
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/SystemSettingsRepository.cs`
around lines 31 - 52, Remove the unnecessary
_context.SystemSettings.Update(existing) call in
SystemSettingsRepository.UpsertAsync (existing is already tracked after
FirstOrDefaultAsync) and add documentation on the UpsertAsync method explaining
that passing description == null preserves the current Description (i.e., null
means "do not modify"); if clearing the description should be supported instead,
adjust the method contract (e.g., use an explicit flag or sentinel) before
changing behavior.
frontend/src/modules/customer/pages/CheckoutPage.tsx (1)

26-34: Migrate from deprecated message to error parameter in Zod 4.

In Zod 4, the message option on z.nativeEnum() (and other schema methods) is still honored for backward compatibility, but has been deprecated in favor of the unified error parameter. While the current code works, migrating to error is recommended for future compatibility. The same pattern should be updated in RegisterPage.tsx and InventoryManagePage.tsx.

Proposed change
-    paymentMethod: z.preprocess(
-      (val) => Number(val),
-      z.nativeEnum(PaymentMethod, {
-        message: t.validation?.required || 'Payment method is required'
-      })
-    ),
+    paymentMethod: z.preprocess(
+      (val) => Number(val),
+      z.nativeEnum(PaymentMethod, {
+        error: t.validation?.required || 'Payment method is required',
+      }),
+    ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/customer/pages/CheckoutPage.tsx` around lines 26 - 34,
Update the Zod nativeEnum option to use the new unified error parameter: in the
checkoutSchema where z.nativeEnum(PaymentMethod, { message: ... }) is used,
replace the deprecated message option with error: { message:
t.validation?.required || 'Payment method is required' }; apply the same change
for the other occurrences referenced (e.g., RegisterPage.tsx and
InventoryManagePage.tsx) so all z.nativeEnum calls use error: { message: ... }
instead of message.
frontend/src/modules/customer/pages/RegisterPage.tsx (1)

18-42: All validation keys are defined in both locales; consider consistent safe fallbacks.

The referenced validation messages (minLength, required, invalidEmail, passwordStrength, passwordNoMatch) all exist in both en.ts and ar.ts. However, the as string casts suppress type awareness at compile time. For consistency across the codebase, adopt the safer pattern already used in CheckoutPage.tsx:

  • Use t.validation?.<key> ?? 'fallback' instead of as string
  • This makes the fallback behavior explicit and maintains type safety

A small helper within the useMemo can reduce repetition:

const msg = (k: keyof typeof t.validation, fb: string) => t.validation?.[k] ?? fb;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/customer/pages/RegisterPage.tsx` around lines 18 - 42,
The registerSchema useMemo currently uses unsafe "as string" casts for
translation keys; replace those casts with explicit safe fallbacks using the
nullish coalescing pattern (t.validation?.<key> ?? '<fallback>') and reduce
repetition by adding a small helper inside the same useMemo (e.g., const msg =
(k, fb) => t.validation?.[k] ?? fb) and then call msg('minLength','Minimum
length is 2'), msg('required','Required'), msg('invalidEmail','Invalid email'),
msg('passwordStrength','Password must meet strength requirements') and
msg('passwordNoMatch','Passwords do not match') when constructing registerSchema
(including inside superRefine) so type safety and clear defaults mirror the
CheckoutPage.tsx approach.
backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/SystemNotificationRepository.cs (1)

56-68: Prefer ExecuteUpdateAsync for bulk updates in MarkAllAsReadAsync.

The current implementation loads all unread notifications into memory, mutates them, and saves—inefficient for large backlogs. EF Core 8.0.24 (confirmed in the project file) supports ExecuteUpdateAsync for server-side UPDATE statements, which is faster and avoids memory overhead.

Suggested refactor
-        public async Task<int> MarkAllAsReadAsync(CancellationToken ct)
-        {
-            var unread = await _context.SystemNotifications.Where(n => !n.IsRead).ToListAsync(ct);
-            if (!unread.Any()) return 0;
-            
-            foreach (var item in unread)
-            {
-                item.IsRead = true;
-            }
-            
-            _context.SystemNotifications.UpdateRange(unread);
-            return await _context.SaveChangesAsync(ct);
-        }
+        public Task<int> MarkAllAsReadAsync(CancellationToken ct)
+        {
+            return _context.SystemNotifications
+                .Where(n => !n.IsRead)
+                .ExecuteUpdateAsync(s => s.SetProperty(n => n.IsRead, true), ct);
+        }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/SystemNotificationRepository.cs`
around lines 56 - 68, Replace the in-memory load-and-update in
MarkAllAsReadAsync with a server-side update: call
_context.SystemNotifications.Where(n => !n.IsRead).ExecuteUpdateAsync(...) to
set IsRead = true (use SetProperty to assign true) and pass the
CancellationToken; return the integer result from ExecuteUpdateAsync instead of
calling ToListAsync/UpdateRange and SaveChangesAsync. Ensure you remove the
unread variable/loop and keep using the existing CancellationToken (ct) when
invoking ExecuteUpdateAsync.
frontend/src/modules/manager/pages/OrderDetailsPage.tsx (1)

120-145: Derive timeline progress from statusSteps indexes instead of enum values.

The current implementation using order.status / 3 and order.status >= step.key depends on OrderStatus having exactly the values 0..3 in timeline order, which is fragile. While the enum currently matches this (PROCESSING=0, READY=1, OUT_FOR_DELIVERY=2, DELIVERED=3), deriving progress from statusSteps array indexes would be more maintainable and prevent breakage if enum values change.

♻️ Proposed refactoring
   const statusSteps = [
     { key: OrderStatus.PROCESSING, label: t.orders.processing, icon: Clock },
     { key: OrderStatus.READY, label: t.orders.ready, icon: Package },
     { key: OrderStatus.OUT_FOR_DELIVERY, label: t.orders.outForDelivery, icon: Truck },
     { key: OrderStatus.DELIVERED, label: t.orders.delivered, icon: CheckCircle2 },
   ];
+  const currentStepIndex = statusSteps.findIndex((step) => step.key === order.status);
+  const progress =
+    currentStepIndex <= 0 ? 0 : (currentStepIndex / (statusSteps.length - 1)) * 100;

@@
-          <div className="absolute top-4 left-0 h-0.5 bg-primary-500 z-0 transition-all" style={{ width: `${(order.status / 3) * 100}%` }} />
-          {statusSteps.map((step) => {
+          <div className="absolute top-4 left-0 h-0.5 bg-primary-500 z-0 transition-all" style={{ width: `${progress}%` }} />
+          {statusSteps.map((step, stepIndex) => {
             const Icon = step.icon;
-            const isCompleted = order.status >= step.key;
+            const isCompleted = currentStepIndex >= stepIndex;
             const isCurrent = order.status === step.key;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/manager/pages/OrderDetailsPage.tsx` around lines 120 -
145, The timeline progress should be computed from the statusSteps array indexes
instead of assuming numeric enum values; find the currentStepIndex by locating
the index in statusSteps where step.key === order.status, compute
progressPercent = (currentStepIndex / (statusSteps.length - 1)) * 100 and use
that for the width style instead of (order.status / 3) * 100, and replace the
isCompleted check to compare indices (currentStepIndex >= stepIndex) when
mapping statusSteps (refer to statusSteps, step.key, order.status and the inline
style width and isCompleted logic).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c3022f52-c713-4897-ae86-63dd60387f23

📥 Commits

Reviewing files that changed from the base of the PR and between 8efb5c9 and ec5572f.

⛔ Files ignored due to path filters (9)
  • backend/OrderSystemApi/OrderSystemApi/wwwroot/uploads/2f882db4-81a8-4d6f-9908-111b10653e1c.jpeg is excluded by !**/*.jpeg
  • backend/OrderSystemApi/OrderSystemApi/wwwroot/uploads/4ae09ddc-a55b-4898-a259-486efab33f1b.jpeg is excluded by !**/*.jpeg
  • backend/OrderSystemApi/OrderSystemApi/wwwroot/uploads/639ee342-8d80-4e55-9a76-b6e6ddbeb8e4.jpg is excluded by !**/*.jpg
  • backend/OrderSystemApi/OrderSystemApi/wwwroot/uploads/a7a1b5dd-9d1b-4d20-9abd-44159d484670.jpeg is excluded by !**/*.jpeg
  • backend/OrderSystemApi/OrderSystemApi/wwwroot/uploads/c0e3001d-baa5-4f45-ba42-e68b4296146e.png is excluded by !**/*.png
  • backend/OrderSystemApi/OrderSystemApi/wwwroot/uploads/ce9909aa-3b84-44ce-8e92-8a870edffd39.jpeg is excluded by !**/*.jpeg
  • backend/OrderSystemApi/OrderSystemApi/wwwroot/uploads/d3a0b2aa-0ded-4465-83bf-7957a17a7ca7.jpeg is excluded by !**/*.jpeg
  • frontend/package-lock.json is excluded by !**/package-lock.json
  • frontend/public/default-product.png is excluded by !**/*.png
📒 Files selected for processing (134)
  • ProjectInstructions.txt
  • backend/OrderSystemApi/OrderSystem.Application/Admin/DTOs/Requests/AdminRequests.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/DTOs/Responses/AdminOrderDto.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/DTOs/Responses/AdminUserDto.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/DTOs/Responses/DashboardResponse.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/DTOs/Responses/ManagerPerformanceDto.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/DTOs/Responses/RevenueReportResponse.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/IActivityLogRepository.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/IActivityLogService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/IAdminNotificationDispatcher.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/IAdminRepository.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/IAdminService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/ISystemNotificationRepository.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/ISystemNotificationService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/ISystemSettingsRepository.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Interfaces/ISystemSettingsService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Services/ActivityLogService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Services/AdminService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Services/SystemNotificationService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Admin/Services/SystemSettingsService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Inventorys/Services/InventoryService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Orders/DTOs/Responses/OrderDetailsResponse.cs
  • backend/OrderSystemApi/OrderSystem.Application/Orders/Interfaces/IOrderService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Orders/Services/OrderService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Payments/Interfaces/IPaymentService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Payments/Services/PaymentService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Products/Interfaces/IProductRepository.cs
  • backend/OrderSystemApi/OrderSystem.Application/Products/Interfaces/IProductService.cs
  • backend/OrderSystemApi/OrderSystem.Application/Products/Services/ProductService.cs
  • backend/OrderSystemApi/OrderSystem.Domain/Entities/OrderItem.cs
  • backend/OrderSystemApi/OrderSystem.Domain/Entities/Product.cs
  • backend/OrderSystemApi/OrderSystem.Domain/Entities/SystemActivityLog.cs
  • backend/OrderSystemApi/OrderSystem.Domain/Entities/SystemNotification.cs
  • backend/OrderSystemApi/OrderSystem.Domain/Entities/SystemSetting.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Data/ApplicationDbContext.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Data/Configurations/OrderItemConfiguration.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Data/Configurations/SystemActivityLogConfiguration.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Data/Configurations/SystemNotificationConfiguration.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Data/Configurations/SystemSettingConfiguration.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418174733_AddSystemActivityLog.Designer.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418174733_AddSystemActivityLog.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418180805_AddSystemNotification.Designer.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418180805_AddSystemNotification.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418192929_AddSystemSettings.Designer.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/20260418192929_AddSystemSettings.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Migrations/ApplicationDbContextModelSnapshot.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/ActivityLogRepository.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/AdminRepository.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/ProductRepository.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/SystemNotificationRepository.cs
  • backend/OrderSystemApi/OrderSystem.Infrastructure/Repositories/SystemSettingsRepository.cs
  • backend/OrderSystemApi/OrderSystemApi/Controllers/Admin/AdminActivityController.cs
  • backend/OrderSystemApi/OrderSystemApi/Controllers/Admin/AdminCatalogController.cs
  • backend/OrderSystemApi/OrderSystemApi/Controllers/Admin/AdminController.cs
  • backend/OrderSystemApi/OrderSystemApi/Controllers/Admin/AdminNotificationsController.cs
  • backend/OrderSystemApi/OrderSystemApi/Controllers/Admin/AdminSettingsController.cs
  • backend/OrderSystemApi/OrderSystemApi/Controllers/Manager/ManagerCategoriesController.cs
  • backend/OrderSystemApi/OrderSystemApi/Controllers/Manager/ManagerOrdersController.cs
  • backend/OrderSystemApi/OrderSystemApi/Controllers/Manager/ManagerProductImagesController.cs
  • backend/OrderSystemApi/OrderSystemApi/Controllers/Manager/UploadController.cs
  • backend/OrderSystemApi/OrderSystemApi/Hubs/AdminHub.cs
  • backend/OrderSystemApi/OrderSystemApi/OrderSystem.Api.csproj
  • backend/OrderSystemApi/OrderSystemApi/Program.cs
  • backend/OrderSystemApi/OrderSystemApi/Services/AdminNotificationDispatcher.cs
  • backend/OrderSystemApi/OrderSystemApi/crash_log.txt
  • frontend/index.html
  • frontend/package.json
  • frontend/src/App.css
  • frontend/src/app/api/http.ts
  • frontend/src/app/i18n/direction.ts
  • frontend/src/app/i18n/i18n-context.tsx
  • frontend/src/app/i18n/messages/ar.ts
  • frontend/src/app/i18n/messages/en.ts
  • frontend/src/app/providers/AppProviders.tsx
  • frontend/src/app/router/index.tsx
  • frontend/src/app/store/auth-context.tsx
  • frontend/src/index.css
  • frontend/src/layouts/AdminLayout.tsx
  • frontend/src/layouts/CustomerLayout.tsx
  • frontend/src/layouts/ManagerLayout.tsx
  • frontend/src/layouts/PublicLayout.tsx
  • frontend/src/modules/admin/api/admin.api.ts
  • frontend/src/modules/admin/components/NotificationBell.tsx
  • frontend/src/modules/admin/hooks/useAdmin.ts
  • frontend/src/modules/admin/pages/ActivityLogPage.tsx
  • frontend/src/modules/admin/pages/AdminCatalogPage.tsx
  • frontend/src/modules/admin/pages/AdminSystemSettingsPage.tsx
  • frontend/src/modules/admin/pages/DashboardPage.tsx
  • frontend/src/modules/admin/pages/InventoryStatusPage.tsx
  • frontend/src/modules/admin/pages/LowStockPage.tsx
  • frontend/src/modules/admin/pages/ManagerPerformancePage.tsx
  • frontend/src/modules/admin/pages/OrderOverviewPage.tsx
  • frontend/src/modules/admin/pages/RevenueReportPage.tsx
  • frontend/src/modules/admin/pages/UsersPage.tsx
  • frontend/src/modules/auth/pages/LoginPage.tsx
  • frontend/src/modules/auth/pages/SettingsPage.tsx
  • frontend/src/modules/cart/pages/CartPage.tsx
  • frontend/src/modules/catalog/pages/ProductDetailsPage.tsx
  • frontend/src/modules/catalog/pages/ProductsListPage.tsx
  • frontend/src/modules/customer/pages/CheckoutPage.tsx
  • frontend/src/modules/customer/pages/CustomerOrderDetailsPage.tsx
  • frontend/src/modules/customer/pages/CustomerOrdersListPage.tsx
  • frontend/src/modules/customer/pages/PaymentPage.tsx
  • frontend/src/modules/customer/pages/RegisterPage.tsx
  • frontend/src/modules/error/pages/NotFoundPage.tsx
  • frontend/src/modules/manager/api/orders.api.ts
  • frontend/src/modules/manager/api/products.api.ts
  • frontend/src/modules/manager/api/upload.api.ts
  • frontend/src/modules/manager/hooks/useManagerOrders.ts
  • frontend/src/modules/manager/hooks/useManagerProducts.ts
  • frontend/src/modules/manager/pages/CategoriesPage.tsx
  • frontend/src/modules/manager/pages/InventoryListPage.tsx
  • frontend/src/modules/manager/pages/InventoryManagePage.tsx
  • frontend/src/modules/manager/pages/OrderDetailsPage.tsx
  • frontend/src/modules/manager/pages/OrdersPage.tsx
  • frontend/src/modules/manager/pages/ProductEditPage.tsx
  • frontend/src/modules/manager/pages/ProductsPage.tsx
  • frontend/src/shared/components/AppTable.tsx
  • frontend/src/shared/components/DetailList.tsx
  • frontend/src/shared/components/EmptyState.tsx
  • frontend/src/shared/components/Footer.tsx
  • frontend/src/shared/components/FormSection.tsx
  • frontend/src/shared/components/ImageFallback.tsx
  • frontend/src/shared/components/LanguageSwitcher.tsx
  • frontend/src/shared/components/PageContainer.tsx
  • frontend/src/shared/components/ProductCard.tsx
  • frontend/src/shared/components/QuantityStepper.tsx
  • frontend/src/shared/components/SectionHeading.tsx
  • frontend/src/shared/components/StatCard.tsx
  • frontend/src/shared/components/TableToolbar.tsx
  • frontend/src/shared/types/orders.ts
  • frontend/src/shared/utils/date.ts
  • frontend/src/shared/utils/error.ts
  • frontend/src/shared/utils/exportUtils.ts

Comment on lines +35 to +45
public class BulkPriceRequest
{
public List<long> ProductIds { get; set; } = new();
public decimal PercentageChange { get; set; }
}

[HttpPut("bulk-price")]
public async Task<IActionResult> BulkPrice([FromBody] BulkPriceRequest request, CancellationToken ct)
{
var count = await _productService.BulkUpdatePriceAsync(request.ProductIds, request.PercentageChange, ct);
return Ok(new { message = $"Successfully updated {count} products." });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Make bulk price adjustments safe against retries and invalid ranges.

PUT bulk-price applies a relative change, so a retry can apply the percentage again and corrupt prices. It also accepts unbounded percentages. Use a non-idempotent action endpoint plus validation, or require an idempotency key.

🐛 Proposed guardrails
-        [HttpPut("bulk-price")]
+        [HttpPost("bulk-price-adjustments")]
         public async Task<IActionResult> BulkPrice([FromBody] BulkPriceRequest request, CancellationToken ct)
         {
+            if (request.ProductIds.Count == 0)
+                return BadRequest(new { message = "At least one product must be selected." });
+
+            if (request.PercentageChange <= -100m || request.PercentageChange > 100m)
+                return BadRequest(new { message = "Percentage change must be greater than -100 and at most 100." });
+
             var count = await _productService.BulkUpdatePriceAsync(request.ProductIds, request.PercentageChange, ct);
             return Ok(new { message = $"Successfully updated {count} products." });
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/OrderSystemApi/OrderSystemApi/Controllers/Admin/AdminCatalogController.cs`
around lines 35 - 45, The BulkPrice endpoint is unsafe for retries and accepts
unbounded percentages; update the BulkPrice action and BulkPriceRequest handling
to require and validate an idempotency key and sensible percentage ranges: add a
mandatory string idempotencyKey (from header or body) to the BulkPrice request
handling and call an idempotency guard (e.g.,
IIdempotencyService.EnsureNotProcessedAsync(idempotencyKey)) before performing
the update using _productService.BulkUpdatePriceAsync, validate
request.ProductIds is not empty and validate PercentageChange is within a safe
range (e.g., -100 to 100) and reject invalid requests with BadRequest; ensure
the idempotency key is passed through or recorded so repeated requests with the
same key are no-ops and include reference to BulkPriceRequest, the BulkPrice
controller action, and _productService.BulkUpdatePriceAsync.

Comment on lines +6 to +9
[ApiController]
[Route("api/v1/manager/[controller]")]
[Authorize]
public class UploadController : ControllerBase

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Confirm other manager controllers use role-based auth so we align with existing pattern.
rg -nP -C2 '\[Authorize' -g 'backend/**/Controllers/Manager/*.cs'

Repository: NowwarAbuRashed/OrderSystem

Length of output: 3725


Missing role-based authorization on a manager-only endpoint.

[Authorize] only requires any authenticated principal, so a regular customer token can POST to /api/v1/manager/upload and drop files into wwwroot/uploads. All other manager controllers enforce role-based authorization — this endpoint should match that pattern.

-    [Authorize] 
+    [Authorize(Roles = "MANAGER,ADMIN")]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@backend/OrderSystemApi/OrderSystemApi/Controllers/Manager/UploadController.cs`
around lines 6 - 9, The UploadController is protected only by [Authorize],
allowing any authenticated user to access manager-only endpoints; update the
class-level authorization to require the manager role (match other manager
controllers) by replacing or augmenting [Authorize] on the UploadController with
the role-based attribute used in the project (e.g., [Authorize(Roles =
"Manager")] or the equivalent policy name) so POST /api/v1/manager/upload is
restricted to managers only; ensure the attribute is applied to the
UploadController class declaration and matches the project's existing
role/policy convention.

Comment on lines +7 to +9
const { data } = await http.post<{ url: string }>('/api/v1/manager/upload', formData, {
headers: { 'Content-Type': 'multipart/form-data' }
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "upload.api.ts" -type f

Repository: NowwarAbuRashed/OrderSystem

Length of output: 119


🏁 Script executed:

cat -n frontend/src/modules/manager/api/upload.api.ts | head -30

Repository: NowwarAbuRashed/OrderSystem

Length of output: 479


🌐 Web query:

axios FormData multipart/form-data boundary issue Content-Type header

💡 Result:

The common issue with axios, FormData, and multipart/form-data boundary occurs when developers manually set the Content-Type header to 'multipart/form-data' without including the required boundary parameter. This leads to servers rejecting requests because the boundary (which delimits parts in the multipart body) is missing from the header, causing errors like "Multipart: Boundary not found". Key points from official docs and issues: - Do not manually set Content-Type: 'multipart/form-data' when using FormData. Axios (in browser/XHR adapter) automatically detects FormData, sets the correct Content-Type with boundary generated by the browser, and sends the request properly. Manual override bypasses this, omitting the boundary. Example (correct): const formData = new FormData; formData.append('file', file); axios.post('/upload', formData); // Auto-handles Content-Type: multipart/form-data; boundary=----WebKitFormBoundary... - In Node.js, axios uses the form-data package, which also auto-generates boundary. Same rule applies: omit manual Content-Type. - Historical bugs: In v0.27.0, removing auto-deletion of manual Content-Type broke browser FormData (fixed in v0.27.2 by partial revert). Recent fetch adapter issue (#7054, closed Apr 2026) had similar problems when manually setting header. - Workarounds if needed (e.g., non-standard env): Use formData.getBoundary to append manually, but avoid if possible: headers: { 'Content-Type': multipart/form-data; boundary=${formData.getBoundary} } But official recommendation: let axios/browser handle it. - Shortcut methods like axios.postForm preset the header correctly for multipart. This behavior aligns with browser Fetch/XMLHttpRequest spec: manual Content-Type without boundary prevents auto-addition. Use latest axios (v1.7+ as of 2026) for best FormData support, including fetch adapter improvements.

Citations:


Don't hard-code Content-Type: multipart/form-data — the boundary is lost.

When you pass FormData to axios, it automatically sets Content-Type with the required ; boundary=... parameter. Manually overriding it with the bare string 'multipart/form-data' bypasses this auto-detection and omits the boundary. Most servers (including Kestrel/ASP.NET Core multipart parsers) will reject the request with a parsing error like "Multipart: Boundary not found". Let axios infer the header:

🛠 Proposed fix
-  const { data } = await http.post<{ url: string }>('/api/v1/manager/upload', formData, {
-    headers: { 'Content-Type': 'multipart/form-data' }
-  });
+  const { data } = await http.post<{ url: string }>('/api/v1/manager/upload', formData);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/manager/api/upload.api.ts` around lines 7 - 9, The
request is manually setting the 'Content-Type' header which strips the multipart
boundary axios would normally add; update the upload call (the http.post usage
that posts formData in upload.api.ts) to remove the explicit 'Content-Type':
'multipart/form-data' header so axios can auto-set the header including the
boundary (if you must keep a headers object for other headers, ensure it does
not include Content-Type). Locate the http.post call that returns { data } and
delete or omit the Content-Type entry only.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/modules/manager/api/categories.api.ts (1)

4-12: ⚠️ Potential issue | 🔴 Critical

getManagerCategoryById() calls a non-existent backend endpoint.

The review's premise about moving reads to versioned manager endpoints is incorrect—ManagerCategoriesController has no GET methods and the architectural split is intentional. However, the frontend function getManagerCategoryById(id) calls GET /api/Categories/{id}, which doesn't exist: CategoriesController only implements GetCategories() with no by-ID variant. This function will fail at runtime.

Either add a GetCategoryById(id) endpoint to CategoriesController, or use a different approach (e.g., fetch all categories and filter client-side, or add the endpoint to ManagerCategoriesController if this is manager-specific data).

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

In `@frontend/src/modules/manager/api/categories.api.ts` around lines 4 - 12,
getManagerCategoryById calls a non-existent backend endpoint; either implement a
GET /api/Categories/{id} (add GetCategoryById(id) to CategoriesController or add
equivalent to ManagerCategoriesController) or change the frontend to not call
that endpoint—update getManagerCategoryById to call getManagerCategories() and
filter the returned array for the matching id (or remove the function and use
getManagerCategories where needed). Ensure the chosen fix updates references to
getManagerCategoryById and documents the new backend method name GetCategoryById
if added.
🧹 Nitpick comments (6)
frontend/src/modules/catalog/pages/ProductDetailsPage.tsx (1)

109-109: Avoid any for product images.

product.images already has a type from useProductQuery; typing img as any here discards it and risks silent drift if the backend shape changes (e.g., imageUrl, altText, id). Prefer the inferred element type (or an explicit ProductImage type) so the compiler catches accidental misuse in this JSX block, in product.images?.[selectedImage]?.imageUrl, and in the arrow-nav handlers.

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

In `@frontend/src/modules/catalog/pages/ProductDetailsPage.tsx` at line 109, The
map callback currently types the image parameter as any which discards the real
type from useProductQuery; change the map to use the inferred element type
(remove : any) or explicitly use the product image type (e.g., ProductImage) so
TypeScript can validate properties like imageUrl/altText/id; update occurrences
that reference img (inside product.images.map(...),
product.images?.[selectedImage]?.imageUrl, and the arrow-nav handlers) to rely
on the correct type instead of any so the compiler will catch mismatches.
frontend/src/modules/catalog/pages/ProductsListPage.tsx (2)

17-19: Harden URL param parsing for categoryId.

If the URL carries a non-numeric categoryId (manual edit, stale link, bookmark), Number('abc') yields NaN, which is then passed into useProductsQuery({ ..., categoryId: NaN }) and ultimately into the backend request. Minor nit: searchParams.get('categoryId') is also called twice.

♻️ Proposed fix
-  const page = Number(searchParams.get('page')) || 1;
-  const search = searchParams.get('search') || '';
-  const categoryId = searchParams.get('categoryId') ? Number(searchParams.get('categoryId')) : undefined;
+  const page = Number(searchParams.get('page')) || 1;
+  const search = searchParams.get('search') || '';
+  const rawCategoryId = searchParams.get('categoryId');
+  const parsedCategoryId = rawCategoryId ? Number(rawCategoryId) : undefined;
+  const categoryId = Number.isFinite(parsedCategoryId) ? parsedCategoryId : undefined;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/catalog/pages/ProductsListPage.tsx` around lines 17 -
19, The categoryId parsing is fragile: calling searchParams.get('categoryId')
twice and passing Number(...) can produce NaN which then flows into
useProductsQuery; change parsing to call searchParams.get('categoryId') once,
store it in a local variable (e.g., rawCategoryId), validate it with
Number.isInteger or parseInt and isFinite, and only set categoryId when it is a
valid number (otherwise leave undefined); update the variable used in
useProductsQuery({..., categoryId}) so the backend never receives NaN.

147-156: All "Add to cart" buttons disable together during any in-flight add.

useAddCartItem is called once at the page level, so isAddingToCart is a single boolean shared across every rendered ProductCard. The moment a user clicks Add on one card, every other card's button also becomes disabled until the mutation resolves. Works, but may feel laggy on slower networks; if per-card disable is desired, track the pending productId locally (e.g., const [pendingId, setPendingId] = useState<number | null>(null)) and pass isAddingToCart={pendingId === prod.id}.

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

In `@frontend/src/modules/catalog/pages/ProductsListPage.tsx` around lines 147 -
156, The page-level boolean from useAddCartItem causes every ProductCard's
isAddingToCart prop to disable all buttons simultaneously; change to track the
pending product id locally (e.g., add state pendingId with useState<number |
null>(null)) and update handleAddToCart to setPendingId(prod.id) before calling
the mutation and clear it (setPendingId(null)) in the mutation's
finally/onSettled handler so each ProductCard receives isAddingToCart={pendingId
=== prod.id}; keep using useAddCartItem for the mutation itself but only derive
per-card disable state from pendingId.
frontend/src/layouts/CustomerLayout.tsx (1)

55-66: Header search replaces, rather than merges, existing filter params.

When a user is already on /products?categoryId=3&page=2 and submits the header search, navigate('/products?search=...') drops categoryId (and page, which is probably desired). Consider preserving categoryId so the header search behaves like an additional filter rather than a full reset.

♻️ Possible approach
-              onSubmit={(e) => {
-                e.preventDefault();
-                const search = new FormData(e.currentTarget).get('search') as string;
-                if (search) {
-                  navigate(`/products?search=${encodeURIComponent(search)}`);
-                } else {
-                  navigate('/products');
-                }
-              }}
+              onSubmit={(e) => {
+                e.preventDefault();
+                const search = (new FormData(e.currentTarget).get('search') as string) ?? '';
+                const params = new URLSearchParams(window.location.pathname === '/products' ? window.location.search : '');
+                if (search) params.set('search', search);
+                else params.delete('search');
+                params.delete('page');
+                const qs = params.toString();
+                navigate(qs ? `/products?${qs}` : '/products');
+              }}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/layouts/CustomerLayout.tsx` around lines 55 - 66, The header
search form's onSubmit handler currently calls navigate('/products?search=...')
which replaces existing query params (dropping categoryId); update the handler
in CustomerLayout's form onSubmit so it reads the current location.search (via
useLocation or the existing router location), parses query params, sets/updates
the "search" param while preserving existing params like "categoryId", then
builds the new query string and calls navigate(`/products?${newQuery}`); ensure
that when the search is empty the "search" param is removed (not set to empty)
so other filters remain intact.
frontend/src/modules/manager/pages/InventoryManagePage.tsx (2)

127-152: Card title doesn't match its contents.

The overview card's CardTitle is t.manager.productQuantity (rendered as "Current Stock"), but the card body renders both the current stock and the min-quantity tile. Consider a more general title key (e.g. a new t.manager.stockOverview) so the header accurately represents the section.

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

In `@frontend/src/modules/manager/pages/InventoryManagePage.tsx` around lines 127
- 152, The Card header currently uses t.manager.productQuantity (CardTitle)
which mislabels the section because the body displays both product.quantity and
product.minQuantity; change the title to a more general key (e.g., add and use
t.manager.stockOverview) and update the CardTitle to use that new key (replace
t.manager.productQuantity with t.manager.stockOverview) so the header accurately
reflects the CardContent that includes the current stock, minQuantity tiles and
the isLow warning.

68-81: Defensive mapping of unknown server error keys.

The fallback branch casts any server field name to keyof InventoryFormType:

setError(key as keyof InventoryFormType, { type: 'server', message: msg });

The only form fields are qty and action, so keys like minQuantity (present on AdjustInventoryRequest) or any other DTO field will register an error against a non-existent input — the message is effectively swallowed and the user sees no feedback. Safer to surface unknown keys on root.serverError instead.

♻️ Proposed fix
       onError: (err: any) => {
         const map = getApiErrorMap(err);
         if (Object.keys(map).length > 0) {
+          const knownFields: Array<keyof InventoryFormType> = ['qty', 'action'];
           for (const [key, msg] of Object.entries(map)) {
             if (key === 'quantityDelta' || key === 'quantity') {
               setError('qty', { type: 'server', message: msg });
-            } else {
-              setError(key as keyof InventoryFormType, { type: 'server', message: msg });
+            } else if ((knownFields as string[]).includes(key)) {
+              setError(key as keyof InventoryFormType, { type: 'server', message: msg });
+            } else {
+              setError('root.serverError', { type: 'server', message: msg });
             }
           }
         } else {
           setError('root.serverError', { type: 'server', message: getApiErrorMessage(err) });
         }
       }

Also consider typing err as unknown rather than any.

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

In `@frontend/src/modules/manager/pages/InventoryManagePage.tsx` around lines 68 -
81, The handler maps server error keys directly to form fields causing messages
for unknown DTO fields to be lost; update the onError logic in
InventoryManagePage to only call setError for actual form fields (e.g., map
'quantity' and 'quantityDelta' to the form field 'qty', and allow 'action' if
present) and for any other unknown keys call setError('root.serverError', {
type: 'server', message: msg }) so the user sees the message; use getApiErrorMap
and setError as shown, and consider typing the err parameter as unknown instead
of any.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/layouts/CustomerLayout.tsx`:
- Around line 97-101: The mobile drawer badge in CustomerLayout.tsx is rendering
cartItemCount uncapped; mirror the desktop behavior by capping the displayed
value to '9+' when cartItemCount > 9. Locate the mobile badge rendering where
cartItemCount is directly output (the badge near the drawer markup) and replace
the direct value with a conditional expression similar to the desktop badge (use
cartItemCount > 9 ? '9+' : cartItemCount) so both desktop and mobile show the
same capped label.
- Around line 130-135: The mobile menu toggle button in CustomerLayout.tsx is
unlabeled and lacks aria-expanded/aria-controls; update the button that uses
setMobileMenuOpen and mobileMenuOpen to include an accessible label (use i18n
via t, e.g., t('nav.openMenu') / t('nav.closeMenu')), add
aria-expanded={mobileMenuOpen} and aria-controls pointing to the drawer
container id, and give the drawer container a matching id; add the two new
strings to the i18n nav namespace so the button label reflects the Menu/X state
for screen readers.

In `@frontend/src/modules/catalog/pages/ProductDetailsPage.tsx`:
- Around line 86-99: Replace the hardcoded English aria-labels on the image
navigation buttons with the i18n translation keys used elsewhere (e.g.
t('products.previousImage') and t('products.nextImage')); locate the two buttons
that call setSelectedImage (the previous/next handlers referencing
product.images!.length) and change their aria-label props to use the translation
function (ensuring the component has access to t from useTranslation or the
existing i18n hook) so the labels become localized keys consistent with the rest
of the app.
- Around line 49-50: The hardcoded error strings in ProductDetailsPage (the
isNaN(id) check and the error/product null branch that returns <ErrorState ...
/> using getApiErrorMessage) must be localized: import/use the useI18n hook and
replace the literal messages with translation keys (e.g.
t.products.invalidProductId and t.products.notFound or similar), and add those
keys to both en.ts and ar.ts; keep getApiErrorMessage for API errors but wrap or
fallback to t.products.notFound when product is missing.
- Around line 197-214: The trust strip labels/descriptions in ProductDetailsPage
are hardcoded English; move each string into the translations under t.products
in both en.ts and ar.ts (e.g. keys like products.fastDelivery.label,
products.fastDelivery.desc, products.qualityGuarantee.label,
products.qualityGuarantee.desc, products.easyReturns.label,
products.easyReturns.desc), update the map in the JSX (the array mapped over
that uses Truck/Shield/RefreshCw and Icon) to reference t.products.<key>.label
and t.products.<key>.desc via the i18n `t` function, and ensure the new keys are
added to both locale files so Arabic renders correctly.
- Around line 37-46: The thumbnail auto-scroll logic in the useEffect uses
offsetLeft and scrollTo which break in RTL; update the effect that references
thumbnailsRef and selectedImage so that instead of computing scrollLeft you call
scrollIntoView on the active thumbnail element (i.e., replace the activeThumb
offsetLeft/scrollTo block inside the useEffect with
activeThumb?.scrollIntoView({ inline: 'center', behavior: 'smooth', block:
'nearest' })) to ensure consistent centering in both LTR and RTL.

In `@frontend/src/modules/catalog/pages/ProductsListPage.tsx`:
- Around line 29-36: The debounce effect in ProductsListPage reads `search` and
calls `setSearch` without listing them in deps; replace the dual-effect pattern
with a single debounce effect that uses a `useRef` (add to the React import) to
track the last-applied search value so you can safely include all real
dependencies. Specifically: add a ref like `lastAppliedSearchRef`, initialize it
to `search`, update it when you call `setSearch`, and change the existing
`useEffect` (the one that debounces `localSearch`) to depend on `localSearch`,
`search`, and `setSearch` (or simply `localSearch` if the ref eliminates the
need to read `search`), using the ref to avoid stale writes; remove the separate
sync effect that previously kept `localSearch` in step with `search`. Ensure
`lastAppliedSearchRef` is updated whenever `setSearch` runs.

In `@frontend/src/modules/manager/pages/InventoryManagePage.tsx`:
- Around line 56-57: Several user-facing strings in InventoryManagePage are
still hardcoded; replace them with i18n keys using the t function (e.g.,
t('products.loadError'), t('products.invalid'), t('products.stockBelowMin'),
t('products.addStock'), t('products.remove'), t('products.noMovementHistory'))
wherever loadError, product/id validation, the low-stock label, Add Stock
button, Remove button, and the empty history message are used; add the
corresponding keys to the locale files (en.ts/ar.ts) and ensure the
InventoryManagePage imports/uses the t hook/prop already present in the file.
- Around line 62-67: The onSuccess handler in the opts object uses setTimeout to
clear setSuccessMessage after 3s but never clears the timer on unmount or before
scheduling a new one; create a ref (e.g., successTimeoutRef) to store the timer
id, clear any existing timeout before setting a new one, and add a useEffect
cleanup that clears successTimeoutRef.current on component unmount so
setSuccessMessage is not called on an unmounted component; update references in
the onSuccess block that call setSuccessMessage/reset to use this timeout
management.
- Around line 168-182: The stale validation error persists because
setValue('action', ...) is called without triggering validation; update the two
button handlers that call setValue('action', 'add') and setValue('action',
'remove') to pass shouldValidate: true so the qty field's superRefine check is
re-run when switching actions (i.e., call setValue('action', 'add', {
shouldValidate: true }) and setValue('action', 'remove', { shouldValidate: true
})); also add aria-pressed={actionValue === 'add'} / aria-pressed={actionValue
=== 'remove'} to the respective buttons for improved screen-reader
accessibility.

---

Outside diff comments:
In `@frontend/src/modules/manager/api/categories.api.ts`:
- Around line 4-12: getManagerCategoryById calls a non-existent backend
endpoint; either implement a GET /api/Categories/{id} (add GetCategoryById(id)
to CategoriesController or add equivalent to ManagerCategoriesController) or
change the frontend to not call that endpoint—update getManagerCategoryById to
call getManagerCategories() and filter the returned array for the matching id
(or remove the function and use getManagerCategories where needed). Ensure the
chosen fix updates references to getManagerCategoryById and documents the new
backend method name GetCategoryById if added.

---

Nitpick comments:
In `@frontend/src/layouts/CustomerLayout.tsx`:
- Around line 55-66: The header search form's onSubmit handler currently calls
navigate('/products?search=...') which replaces existing query params (dropping
categoryId); update the handler in CustomerLayout's form onSubmit so it reads
the current location.search (via useLocation or the existing router location),
parses query params, sets/updates the "search" param while preserving existing
params like "categoryId", then builds the new query string and calls
navigate(`/products?${newQuery}`); ensure that when the search is empty the
"search" param is removed (not set to empty) so other filters remain intact.

In `@frontend/src/modules/catalog/pages/ProductDetailsPage.tsx`:
- Line 109: The map callback currently types the image parameter as any which
discards the real type from useProductQuery; change the map to use the inferred
element type (remove : any) or explicitly use the product image type (e.g.,
ProductImage) so TypeScript can validate properties like imageUrl/altText/id;
update occurrences that reference img (inside product.images.map(...),
product.images?.[selectedImage]?.imageUrl, and the arrow-nav handlers) to rely
on the correct type instead of any so the compiler will catch mismatches.

In `@frontend/src/modules/catalog/pages/ProductsListPage.tsx`:
- Around line 17-19: The categoryId parsing is fragile: calling
searchParams.get('categoryId') twice and passing Number(...) can produce NaN
which then flows into useProductsQuery; change parsing to call
searchParams.get('categoryId') once, store it in a local variable (e.g.,
rawCategoryId), validate it with Number.isInteger or parseInt and isFinite, and
only set categoryId when it is a valid number (otherwise leave undefined);
update the variable used in useProductsQuery({..., categoryId}) so the backend
never receives NaN.
- Around line 147-156: The page-level boolean from useAddCartItem causes every
ProductCard's isAddingToCart prop to disable all buttons simultaneously; change
to track the pending product id locally (e.g., add state pendingId with
useState<number | null>(null)) and update handleAddToCart to
setPendingId(prod.id) before calling the mutation and clear it
(setPendingId(null)) in the mutation's finally/onSettled handler so each
ProductCard receives isAddingToCart={pendingId === prod.id}; keep using
useAddCartItem for the mutation itself but only derive per-card disable state
from pendingId.

In `@frontend/src/modules/manager/pages/InventoryManagePage.tsx`:
- Around line 127-152: The Card header currently uses t.manager.productQuantity
(CardTitle) which mislabels the section because the body displays both
product.quantity and product.minQuantity; change the title to a more general key
(e.g., add and use t.manager.stockOverview) and update the CardTitle to use that
new key (replace t.manager.productQuantity with t.manager.stockOverview) so the
header accurately reflects the CardContent that includes the current stock,
minQuantity tiles and the isLow warning.
- Around line 68-81: The handler maps server error keys directly to form fields
causing messages for unknown DTO fields to be lost; update the onError logic in
InventoryManagePage to only call setError for actual form fields (e.g., map
'quantity' and 'quantityDelta' to the form field 'qty', and allow 'action' if
present) and for any other unknown keys call setError('root.serverError', {
type: 'server', message: msg }) so the user sees the message; use getApiErrorMap
and setError as shown, and consider typing the err parameter as unknown instead
of any.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e389ea30-c42a-4a01-a04a-e2901c36ef85

📥 Commits

Reviewing files that changed from the base of the PR and between ec5572f and 4887444.

📒 Files selected for processing (7)
  • frontend/src/layouts/CustomerLayout.tsx
  • frontend/src/modules/cart/pages/CartPage.tsx
  • frontend/src/modules/catalog/hooks/useCatalog.ts
  • frontend/src/modules/catalog/pages/ProductDetailsPage.tsx
  • frontend/src/modules/catalog/pages/ProductsListPage.tsx
  • frontend/src/modules/manager/api/categories.api.ts
  • frontend/src/modules/manager/pages/InventoryManagePage.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/modules/cart/pages/CartPage.tsx

Comment on lines +97 to +101
{cartItemCount > 0 && (
<span className="absolute -top-0.5 -right-0.5 w-5 h-5 bg-accent-500 text-white text-[10px] font-bold rounded-full flex items-center justify-center ring-2 ring-white">
{cartItemCount > 9 ? '9+' : cartItemCount}
</span>
)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Cart-badge cap is inconsistent between desktop and mobile.

Desktop (line 99) shows 9+ once the count exceeds 9, but the mobile drawer badge (line 168) renders cartItemCount uncapped — so a count of 27 would display tightly inside a small pill. Apply the same cap for visual consistency.

🛠 Proposed fix
-                {cartItemCount > 0 && (
-                  <span className="ml-auto bg-accent-500 text-white text-xs font-bold px-2 py-0.5 rounded-full">
-                    {cartItemCount}
-                  </span>
-                )}
+                {cartItemCount > 0 && (
+                  <span className="ml-auto bg-accent-500 text-white text-xs font-bold px-2 py-0.5 rounded-full">
+                    {cartItemCount > 9 ? '9+' : cartItemCount}
+                  </span>
+                )}

Also applies to: 166-170

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

In `@frontend/src/layouts/CustomerLayout.tsx` around lines 97 - 101, The mobile
drawer badge in CustomerLayout.tsx is rendering cartItemCount uncapped; mirror
the desktop behavior by capping the displayed value to '9+' when cartItemCount >
9. Locate the mobile badge rendering where cartItemCount is directly output (the
badge near the drawer markup) and replace the direct value with a conditional
expression similar to the desktop badge (use cartItemCount > 9 ? '9+' :
cartItemCount) so both desktop and mobile show the same capped label.

Comment on lines +130 to +135
<button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="p-2 text-slate-500 hover:text-primary-600 hover:bg-primary-50 rounded-xl transition-all md:hidden"
>
{mobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
</button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

A11y: mobile menu toggle is unlabeled and missing aria-expanded/aria-controls.

Screen readers will announce it only as "button"; there's no way for AT users to know it controls a menu or whether it's open. Since the icon swaps between Menu and X, the state must be conveyed via ARIA.

🛠 Proposed fix
             <button
               onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
               className="p-2 text-slate-500 hover:text-primary-600 hover:bg-primary-50 rounded-xl transition-all md:hidden"
+              aria-label={mobileMenuOpen ? t.nav.closeMenu : t.nav.openMenu}
+              aria-expanded={mobileMenuOpen}
+              aria-controls="customer-mobile-drawer"
             >
               {mobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
             </button>

And on the drawer container (line 141):

-          <div className="md:hidden border-t border-slate-100 bg-white shadow-lg">
+          <div id="customer-mobile-drawer" className="md:hidden border-t border-slate-100 bg-white shadow-lg">

Add the two strings to your i18n nav namespace.

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

In `@frontend/src/layouts/CustomerLayout.tsx` around lines 130 - 135, The mobile
menu toggle button in CustomerLayout.tsx is unlabeled and lacks
aria-expanded/aria-controls; update the button that uses setMobileMenuOpen and
mobileMenuOpen to include an accessible label (use i18n via t, e.g.,
t('nav.openMenu') / t('nav.closeMenu')), add aria-expanded={mobileMenuOpen} and
aria-controls pointing to the drawer container id, and give the drawer container
a matching id; add the two new strings to the i18n nav namespace so the button
label reflects the Menu/X state for screen readers.

Comment on lines +37 to +46
useEffect(() => {
if (thumbnailsRef.current) {
const container = thumbnailsRef.current;
const activeThumb = container.children[selectedImage] as HTMLElement;
if (activeThumb) {
const scrollLeft = activeThumb.offsetLeft - container.offsetWidth / 2 + activeThumb.offsetWidth / 2;
container.scrollTo({ left: scrollLeft, behavior: 'smooth' });
}
}
}, [selectedImage]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Search for Arabic/i18n/RTL configuration
find . -type f \( -name "*.ts" -o -name "*.tsx" -o -name "*.js" -o -name "*.json" \) | head -20

Repository: NowwarAbuRashed/OrderSystem

Length of output: 860


🏁 Script executed:

# Look for i18n or locale configuration files
fd -e json -e ts -e tsx | grep -E "(i18n|locale|translation|arabic|rtl)" | head -20

Repository: NowwarAbuRashed/OrderSystem

Length of output: 219


🏁 Script executed:

# Search for RTL-related code in the codebase
rg -i "rtl|arabic|dir=" --type ts --type tsx | head -30

Repository: NowwarAbuRashed/OrderSystem

Length of output: 99


🏁 Script executed:

# Check the ProductDetailsPage component for context
cat -n frontend/src/modules/catalog/pages/ProductDetailsPage.tsx | head -60

Repository: NowwarAbuRashed/OrderSystem

Length of output: 3158


🏁 Script executed:

cat -n frontend/src/app/i18n/direction.ts

Repository: NowwarAbuRashed/OrderSystem

Length of output: 414


🏁 Script executed:

cat -n frontend/src/app/i18n/i18n-context.tsx

Repository: NowwarAbuRashed/OrderSystem

Length of output: 2173


🏁 Script executed:

# Look for where dir attribute is set in the app
rg "dir\s*=" frontend/src --type tsx --type ts -A 2 -B 2

Repository: NowwarAbuRashed/OrderSystem

Length of output: 99


🏁 Script executed:

# Check the App.tsx root component
cat -n frontend/src/App.tsx

Repository: NowwarAbuRashed/OrderSystem

Length of output: 303


🏁 Script executed:

# Search for the thumbnail container rendering
rg "thumbnailsRef" frontend/src --type tsx -B 5 -A 5

Repository: NowwarAbuRashed/OrderSystem

Length of output: 99


🏁 Script executed:

# Search for thumbnailsRef usage in the entire ProductDetailsPage
rg "thumbnailsRef" frontend/src --type ts -B 3 -A 8

Repository: NowwarAbuRashed/OrderSystem

Length of output: 3805


🏁 Script executed:

# Get more of the ProductDetailsPage to see thumbnail rendering
cat -n frontend/src/modules/catalog/pages/ProductDetailsPage.tsx | tail -150

Repository: NowwarAbuRashed/OrderSystem

Length of output: 9057


Thumbnail auto-scroll will malfunction in RTL mode.

The app applies dir="rtl" to the document element when Arabic is selected, and the thumbnail container inherits this context. The current code uses offsetLeft and container.scrollTo({ left }), which have inconsistent browser behavior in RTL—scrollLeft calculations differ between Chromium, Firefox, and Safari when the container has dir="rtl". This will cause the "center the active thumbnail" effect to scroll incorrectly or fail to center properly in Arabic.

Replace with activeThumb?.scrollIntoView({ inline: 'center', behavior: 'smooth', block: 'nearest' }), which handles RTL consistently:

Diff
   useEffect(() => {
-    if (thumbnailsRef.current) {
-      const container = thumbnailsRef.current;
-      const activeThumb = container.children[selectedImage] as HTMLElement;
-      if (activeThumb) {
-        const scrollLeft = activeThumb.offsetLeft - container.offsetWidth / 2 + activeThumb.offsetWidth / 2;
-        container.scrollTo({ left: scrollLeft, behavior: 'smooth' });
-      }
-    }
+    const container = thumbnailsRef.current;
+    const activeThumb = container?.children[selectedImage] as HTMLElement | undefined;
+    activeThumb?.scrollIntoView({ inline: 'center', behavior: 'smooth', block: 'nearest' });
   }, [selectedImage]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/catalog/pages/ProductDetailsPage.tsx` around lines 37 -
46, The thumbnail auto-scroll logic in the useEffect uses offsetLeft and
scrollTo which break in RTL; update the effect that references thumbnailsRef and
selectedImage so that instead of computing scrollLeft you call scrollIntoView on
the active thumbnail element (i.e., replace the activeThumb offsetLeft/scrollTo
block inside the useEffect with activeThumb?.scrollIntoView({ inline: 'center',
behavior: 'smooth', block: 'nearest' })) to ensure consistent centering in both
LTR and RTL.

Comment on lines +49 to 50
if (isNaN(id)) return <ErrorState message="Invalid product reference" />;
if (error || !product) return <ErrorState message={error ? getApiErrorMessage(error) : 'Product not found'} />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize error messages via useI18n.

These user-facing error strings are hardcoded English while the rest of the page was just converted to t.*. They will not translate when the locale is Arabic. Consider adding keys (e.g. t.products.invalidProductId, t.products.notFound) to both en.ts and ar.ts and consuming them here.

-  if (isNaN(id)) return <ErrorState message="Invalid product reference" />;
-  if (error || !product) return <ErrorState message={error ? getApiErrorMessage(error) : 'Product not found'} />;
+  if (isNaN(id)) return <ErrorState message={t.products.invalidProductId} />;
+  if (error || !product) return <ErrorState message={error ? getApiErrorMessage(error) : t.products.notFound} />;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/catalog/pages/ProductDetailsPage.tsx` around lines 49 -
50, The hardcoded error strings in ProductDetailsPage (the isNaN(id) check and
the error/product null branch that returns <ErrorState ... /> using
getApiErrorMessage) must be localized: import/use the useI18n hook and replace
the literal messages with translation keys (e.g. t.products.invalidProductId and
t.products.notFound or similar), and add those keys to both en.ts and ar.ts;
keep getApiErrorMessage for API errors but wrap or fallback to
t.products.notFound when product is missing.

Comment on lines +86 to +99
<button
onClick={() => setSelectedImage((prev) => prev === 0 ? product.images!.length - 1 : prev - 1)}
className="absolute start-2 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/90 border border-slate-200 shadow-lg flex items-center justify-center text-slate-600 hover:bg-primary-50 hover:text-primary-600 hover:border-primary-200 transition-all backdrop-blur-sm"
aria-label="Previous image"
>
<ChevronLeft className="w-5 h-5" />
</button>
<button
onClick={() => setSelectedImage((prev) => prev === product.images!.length - 1 ? 0 : prev + 1)}
className="absolute end-2 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/90 border border-slate-200 shadow-lg flex items-center justify-center text-slate-600 hover:bg-primary-50 hover:text-primary-600 hover:border-primary-200 transition-all backdrop-blur-sm"
aria-label="Next image"
>
<ChevronRight className="w-5 h-5" />
</button>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize the image navigation aria-labels.

"Previous image" / "Next image" are hardcoded and will be read in English by Arabic screen readers. Add dedicated keys (e.g. t.products.previousImage, t.products.nextImage) and use them here for consistency with the rest of the i18n overhaul.

-                    aria-label="Previous image"
+                    aria-label={t.products.previousImage}
...
-                    aria-label="Next image"
+                    aria-label={t.products.nextImage}
📝 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
<button
onClick={() => setSelectedImage((prev) => prev === 0 ? product.images!.length - 1 : prev - 1)}
className="absolute start-2 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/90 border border-slate-200 shadow-lg flex items-center justify-center text-slate-600 hover:bg-primary-50 hover:text-primary-600 hover:border-primary-200 transition-all backdrop-blur-sm"
aria-label="Previous image"
>
<ChevronLeft className="w-5 h-5" />
</button>
<button
onClick={() => setSelectedImage((prev) => prev === product.images!.length - 1 ? 0 : prev + 1)}
className="absolute end-2 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/90 border border-slate-200 shadow-lg flex items-center justify-center text-slate-600 hover:bg-primary-50 hover:text-primary-600 hover:border-primary-200 transition-all backdrop-blur-sm"
aria-label="Next image"
>
<ChevronRight className="w-5 h-5" />
</button>
<button
onClick={() => setSelectedImage((prev) => prev === 0 ? product.images!.length - 1 : prev - 1)}
className="absolute start-2 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/90 border border-slate-200 shadow-lg flex items-center justify-center text-slate-600 hover:bg-primary-50 hover:text-primary-600 hover:border-primary-200 transition-all backdrop-blur-sm"
aria-label={t.products.previousImage}
>
<ChevronLeft className="w-5 h-5" />
</button>
<button
onClick={() => setSelectedImage((prev) => prev === product.images!.length - 1 ? 0 : prev + 1)}
className="absolute end-2 top-1/2 -translate-y-1/2 w-10 h-10 rounded-full bg-white/90 border border-slate-200 shadow-lg flex items-center justify-center text-slate-600 hover:bg-primary-50 hover:text-primary-600 hover:border-primary-200 transition-all backdrop-blur-sm"
aria-label={t.products.nextImage}
>
<ChevronRight className="w-5 h-5" />
</button>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/catalog/pages/ProductDetailsPage.tsx` around lines 86 -
99, Replace the hardcoded English aria-labels on the image navigation buttons
with the i18n translation keys used elsewhere (e.g. t('products.previousImage')
and t('products.nextImage')); locate the two buttons that call setSelectedImage
(the previous/next handlers referencing product.images!.length) and change their
aria-label props to use the translation function (ensuring the component has
access to t from useTranslation or the existing i18n hook) so the labels become
localized keys consistent with the rest of the app.

Comment on lines +197 to +214
{/* Trust Strip */}
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
{[
{ icon: Truck, label: 'Fast Delivery', desc: 'Same-day delivery available' },
{ icon: Shield, label: 'Quality Guarantee', desc: '100% fresh products' },
{ icon: RefreshCw, label: 'Easy Returns', desc: 'Hassle-free return policy' },
].map(({ icon: Icon, label, desc }) => (
<div key={label} className="flex items-center gap-3 bg-white p-4 rounded-xl border border-slate-200/60 shadow-sm">
<div className="p-2 bg-primary-50 rounded-lg text-primary-600">
<Icon className="w-5 h-5" />
</div>
<div>
<p className="text-sm font-semibold text-slate-900">{label}</p>
<p className="text-xs text-slate-500">{desc}</p>
</div>
</div>
))}
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Trust Strip labels/descriptions are not localized.

The six strings 'Fast Delivery', 'Same-day delivery available', 'Quality Guarantee', '100% fresh products', 'Easy Returns', 'Hassle-free return policy' are hardcoded English. Given this PR's explicit i18n overhaul, Arabic users will see untranslated English here — this is the single largest gap in the page's localization coverage. Move these into t.products (both en.ts and ar.ts) and reference them from the map.

🌐 Proposed fix
       {/* Trust Strip */}
       <div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
         {[
-          { icon: Truck, label: 'Fast Delivery', desc: 'Same-day delivery available' },
-          { icon: Shield, label: 'Quality Guarantee', desc: '100% fresh products' },
-          { icon: RefreshCw, label: 'Easy Returns', desc: 'Hassle-free return policy' },
+          { icon: Truck, label: t.products.trust.fastDelivery.label, desc: t.products.trust.fastDelivery.desc },
+          { icon: Shield, label: t.products.trust.qualityGuarantee.label, desc: t.products.trust.qualityGuarantee.desc },
+          { icon: RefreshCw, label: t.products.trust.easyReturns.label, desc: t.products.trust.easyReturns.desc },
         ].map(({ icon: Icon, label, desc }) => (
           <div key={label} className="flex items-center gap-3 bg-white p-4 rounded-xl border border-slate-200/60 shadow-sm">
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/catalog/pages/ProductDetailsPage.tsx` around lines 197 -
214, The trust strip labels/descriptions in ProductDetailsPage are hardcoded
English; move each string into the translations under t.products in both en.ts
and ar.ts (e.g. keys like products.fastDelivery.label,
products.fastDelivery.desc, products.qualityGuarantee.label,
products.qualityGuarantee.desc, products.easyReturns.label,
products.easyReturns.desc), update the map in the JSX (the array mapped over
that uses Truck/Shield/RefreshCw and Icon) to reference t.products.<key>.label
and t.products.<key>.desc via the i18n `t` function, and ensure the new keys are
added to both locale files so Arabic renders correctly.

Comment on lines +29 to +36
useEffect(() => {
const timer = setTimeout(() => {
if (localSearch !== search) {
setSearch(localSearch);
}
}, 400);
return () => clearTimeout(timer);
}, [localSearch]);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check eslint-plugin-react-hooks version and any exhaustive-deps config
fd -e json -H package.json -x cat {} | jq -r '. | (.dependencies["eslint-plugin-react-hooks"] // .devDependencies["eslint-plugin-react-hooks"] // empty) | select(.!=null)'
fd -H -t f 'eslint*' -x cat {}

Repository: NowwarAbuRashed/OrderSystem

Length of output: 694


🏁 Script executed:

cd frontend/src/modules/catalog/pages && head -160 ProductsListPage.tsx | cat -n

Repository: NowwarAbuRashed/OrderSystem

Length of output: 7614


Debounce effect violates exhaustive-deps lint rule and will fail with eslint-plugin-react-hooks v7.0.1.

The effect at lines 29-36 reads search (line 31) and calls setSearch (line 32) without listing them as dependencies. Your project has eslint-plugin-react-hooks v7.0.1 installed with the recommended config (which includes exhaustive-deps), so this will trigger a lint error.

While the code works behaviorally because the first sync effect (lines 25-27) keeps localSearch in step with search, the closure is fragile. The linter is correct—if the two effects ever decouple, stale values would silently reappear.

A useRef to track the last written value makes the dependency set explicit and eliminates the dual-effect pattern:

♻️ Refactor — single debounce effect with a ref
-  const [localSearch, setLocalSearch] = useState(search);
-
-  useEffect(() => {
-    setLocalSearch(search);
-  }, [search]);
-
-  useEffect(() => {
-    const timer = setTimeout(() => {
-      if (localSearch !== search) {
-        setSearch(localSearch);
-      }
-    }, 400);
-    return () => clearTimeout(timer);
-  }, [localSearch]);
+  const [localSearch, setLocalSearch] = useState(search);
+  const lastWrittenRef = useRef(search);
+
+  // Sync from URL → input when URL changes externally.
+  useEffect(() => {
+    if (search !== lastWrittenRef.current) {
+      setLocalSearch(search);
+      lastWrittenRef.current = search;
+    }
+  }, [search]);
+
+  // Debounced write from input → URL.
+  useEffect(() => {
+    if (localSearch === search) return;
+    const timer = setTimeout(() => {
+      lastWrittenRef.current = localSearch;
+      setSearch(localSearch);
+    }, 400);
+    return () => clearTimeout(timer);
+  }, [localSearch, search]);

(Add useRef to the React import on line 1.)

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

In `@frontend/src/modules/catalog/pages/ProductsListPage.tsx` around lines 29 -
36, The debounce effect in ProductsListPage reads `search` and calls `setSearch`
without listing them in deps; replace the dual-effect pattern with a single
debounce effect that uses a `useRef` (add to the React import) to track the
last-applied search value so you can safely include all real dependencies.
Specifically: add a ref like `lastAppliedSearchRef`, initialize it to `search`,
update it when you call `setSearch`, and change the existing `useEffect` (the
one that debounces `localSearch`) to depend on `localSearch`, `search`, and
`setSearch` (or simply `localSearch` if the ref eliminates the need to read
`search`), using the ref to avoid stale writes; remove the separate sync effect
that previously kept `localSearch` in step with `search`. Ensure
`lastAppliedSearchRef` is updated whenever `setSearch` runs.

Comment on lines 56 to +57
if (loadError) return <ErrorState message="Could not load product." />;
if (!product) return null;

const handleAdjust = (e: React.FormEvent) => {
e.preventDefault();
const amount = parseInt(qty, 10);
if (!amount || amount <= 0 || !reason.trim()) return;
if (!product || isNaN(id)) return <ErrorState message="Invalid product." />;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Hardcoded English strings left behind in the i18n overhaul.

Several user-facing strings remain non-translated even though the stated goal of this change is to move copy onto t:

  • Line 56: "Could not load product."
  • Line 57: "Invalid product."
  • Line 148: "Stock is below minimum threshold" (next to the translated t.products.lowStock)
  • Line 173: "Add Stock"
  • Line 180: "Remove"
  • Line 214: "No movement history for this product."

In RTL/Arabic these will render untranslated, which breaks the localization flow introduced elsewhere in this file.

🌐 Suggested direction (keys can be added to en.ts/ar.ts)
-  if (loadError) return <ErrorState message="Could not load product." />;
-  if (!product || isNaN(id)) return <ErrorState message="Invalid product." />;
+  if (loadError) return <ErrorState message={t.manager.loadProductError} />;
+  if (!product || isNaN(id)) return <ErrorState message={t.manager.invalidProduct} />;
...
-                ⚠️ {t.products.lowStock} — Stock is below minimum threshold
+                ⚠️ {t.products.lowStock} — {t.products.lowStockDesc}
...
-                  <Plus className="w-4 h-4" /> Add Stock
+                  <Plus className="w-4 h-4" /> {t.manager.addStock}
...
-                  <Minus className="w-4 h-4" /> Remove
+                  <Minus className="w-4 h-4" /> {t.manager.removeStock}
...
-          emptyMessage="No movement history for this product."
+          emptyMessage={t.manager.noMovementHistory}

Also applies to: 148-148, 173-173, 180-180, 214-214

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

In `@frontend/src/modules/manager/pages/InventoryManagePage.tsx` around lines 56 -
57, Several user-facing strings in InventoryManagePage are still hardcoded;
replace them with i18n keys using the t function (e.g., t('products.loadError'),
t('products.invalid'), t('products.stockBelowMin'), t('products.addStock'),
t('products.remove'), t('products.noMovementHistory')) wherever loadError,
product/id validation, the low-stock label, Add Stock button, Remove button, and
the empty history message are used; add the corresponding keys to the locale
files (en.ts/ar.ts) and ensure the InventoryManagePage imports/uses the t
hook/prop already present in the file.

Comment on lines +62 to +67
const opts = {
onSuccess: () => {
setQty('');
setReason('');
setSuccessMessage(t.common.success);
reset();
setTimeout(() => setSuccessMessage(null), 3000);
},

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

setTimeout not cleared on unmount.

If the user navigates away within the 3-second window after a successful adjust, React will warn about a state update on an unmounted component (and the timer keeps a reference alive). Track the handle and clear it on unmount / before scheduling a new one.

🧹 Proposed fix
+import { useEffect, useMemo, useRef, useState } from 'react';
...
   const [successMessage, setSuccessMessage] = useState<string | null>(null);
+  const successTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
+  useEffect(() => () => {
+    if (successTimerRef.current) clearTimeout(successTimerRef.current);
+  }, []);
...
       onSuccess: () => {
         setSuccessMessage(t.common.success);
         reset();
-        setTimeout(() => setSuccessMessage(null), 3000);
+        if (successTimerRef.current) clearTimeout(successTimerRef.current);
+        successTimerRef.current = setTimeout(() => setSuccessMessage(null), 3000);
       },
📝 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 opts = {
onSuccess: () => {
setQty('');
setReason('');
setSuccessMessage(t.common.success);
reset();
setTimeout(() => setSuccessMessage(null), 3000);
},
const opts = {
onSuccess: () => {
setSuccessMessage(t.common.success);
reset();
if (successTimerRef.current) clearTimeout(successTimerRef.current);
successTimerRef.current = setTimeout(() => setSuccessMessage(null), 3000);
},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/manager/pages/InventoryManagePage.tsx` around lines 62 -
67, The onSuccess handler in the opts object uses setTimeout to clear
setSuccessMessage after 3s but never clears the timer on unmount or before
scheduling a new one; create a ref (e.g., successTimeoutRef) to store the timer
id, clear any existing timeout before setting a new one, and add a useEffect
cleanup that clears successTimeoutRef.current on component unmount so
setSuccessMessage is not called on an unmounted component; update references in
the onSuccess block that call setSuccessMessage/reset to use this timeout
management.

Comment on lines +168 to 182
<button
type="button"
onClick={() => setValue('action', 'add')}
className={`flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-semibold border-2 transition-all ${actionValue === 'add' ? 'border-success-500 bg-success-50 text-success-700' : 'border-slate-200 text-slate-500 hover:border-slate-300'}`}
>
<Plus className="w-4 h-4" /> Add Stock
</button>
<button
type="button"
onClick={() => setValue('action', 'remove')}
className={`flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-semibold border-2 transition-all ${actionValue === 'remove' ? 'border-danger-500 bg-danger-50 text-danger-700' : 'border-slate-200 text-slate-500 hover:border-slate-300'}`}
>
<Minus className="w-4 h-4" /> Remove
</button>
</div>

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "InventoryManagePage.tsx" -type f

Repository: NowwarAbuRashed/OrderSystem

Length of output: 132


🏁 Script executed:

fd -i "inventorymanagepage" --type f

Repository: NowwarAbuRashed/OrderSystem

Length of output: 130


🏁 Script executed:

git ls-files "frontend/src/modules/manager/pages/" | head -20

Repository: NowwarAbuRashed/OrderSystem

Length of output: 454


🏁 Script executed:

git ls-files | head -30 | cat

Repository: NowwarAbuRashed/OrderSystem

Length of output: 2502


🏁 Script executed:

rg "superRefine" --type ts --type tsx -i | head -30

Repository: NowwarAbuRashed/OrderSystem

Length of output: 99


🏁 Script executed:

fd "inventory" -i --type f | head -20

Repository: NowwarAbuRashed/OrderSystem

Length of output: 1736


🏁 Script executed:

rg "setValue\(" --type ts --type tsx -B 2 -A 2 | head -50

Repository: NowwarAbuRashed/OrderSystem

Length of output: 99


🏁 Script executed:

ls -la | head -20

Repository: NowwarAbuRashed/OrderSystem

Length of output: 545


🌐 Web query:

react-hook-form setValue shouldValidate default behavior

💡 Result:

The default behavior of setValue in react-hook-form for the shouldValidate option is false. When calling setValue without specifying options, or with options where shouldValidate is not explicitly set to true, validation is not triggered for the field. To trigger validation, you must explicitly pass { shouldValidate: true } in the options object. This is confirmed by the official documentation, which shows examples like setValue("firstName", "Bill") without validation, and setValue("firstName", "Luo", { shouldValidate: true, shouldDirty: true }) to trigger it. GitHub issues also discuss needing to set shouldValidate: true explicitly to run validation, as setValue does not validate by default unlike user input.

Citations:


🌐 Web query:

react-hook-form clearing validation errors when switching form state

💡 Result:

To clear validation errors in React Hook Form when switching form state (such as tabs, modes, or conditional rendering), use the clearErrors function from useForm. This manually removes errors without affecting validation rules or formState.isValid. Key approaches: 1. Call clearErrors on state change: Invoke clearErrors (clears all errors) or clearErrors('fieldName') when your form state changes, like in a useEffect watching the state or on tab switch handlers. Example: const { clearErrors } = useForm; const [activeTab, setActiveTab] = useState('tab1'); useEffect( => { clearErrors; // Clear all errors when tab changes }, [activeTab, clearErrors]); 2. For tabbed/conditional forms: Use shouldUnregister: false in useForm to retain field state across unmounts/remounts (common in tabs). Errors may persist if fields had validation failures; pair with clearErrors to reset them. Example: const { control, clearErrors } = useForm({ shouldUnregister: false }); 3. Alternative: reset with options: Use reset(undefined, { keepErrors: false }) to reset form while clearing errors, or resetField('fieldName', { keepError: false }) for specific fields. From official docs: clearErrors is designed exactly for manually clearing errors in scenarios like this. Older versions auto-cleared on unmount, but current behavior requires explicit calls for control. This prevents stale errors from previous states blocking submission or UX.

Citations:


🏁 Script executed:

wc -l frontend/src/modules/manager/pages/InventoryManagePage.tsx

Repository: NowwarAbuRashed/OrderSystem

Length of output: 134


🏁 Script executed:

sed -n '160,190p' frontend/src/modules/manager/pages/InventoryManagePage.tsx

Repository: NowwarAbuRashed/OrderSystem

Length of output: 1809


🏁 Script executed:

cat -n frontend/src/modules/manager/pages/InventoryManagePage.tsx | sed -n '160,190p'

Repository: NowwarAbuRashed/OrderSystem

Length of output: 2026


🏁 Script executed:

head -200 frontend/src/modules/manager/pages/InventoryManagePage.tsx | tail -50

Repository: NowwarAbuRashed/OrderSystem

Length of output: 2455


🏁 Script executed:

cat frontend/src/modules/manager/pages/InventoryManagePage.tsx

Repository: NowwarAbuRashed/OrderSystem

Length of output: 10261


🏁 Script executed:

rg "superRefine|adjustInventorySchema" -A 10 -B 2 frontend/src/modules/manager/

Repository: NowwarAbuRashed/OrderSystem

Length of output: 1397


🏁 Script executed:

find frontend/src/modules/manager -type f \( -name "*.ts" -o -name "*.tsx" \) -exec rg -l "adjustInventory|superRefine" {} \;

Repository: NowwarAbuRashed/OrderSystem

Length of output: 238


🏁 Script executed:

rg "action.*qty|qty.*action|max.*check" frontend/src/modules/manager -A 5 -B 5

Repository: NowwarAbuRashed/OrderSystem

Length of output: 2365


🏁 Script executed:

rg "z\\.object|z\\.string|z\\.number" frontend/src/modules/manager -l | head -10

Repository: NowwarAbuRashed/OrderSystem

Length of output: 239


🏁 Script executed:

find frontend/src/modules/manager -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs rg -l "z\."

Repository: NowwarAbuRashed/OrderSystem

Length of output: 239


🏁 Script executed:

grep -r "superRefine" frontend/src/modules/manager/

Repository: NowwarAbuRashed/OrderSystem

Length of output: 164


🏁 Script executed:

grep -r "const.*= z\." frontend/src/modules/manager/ | head -10

Repository: NowwarAbuRashed/OrderSystem

Length of output: 53


🏁 Script executed:

ls -la frontend/src/modules/manager/

Repository: NowwarAbuRashed/OrderSystem

Length of output: 340


🏁 Script executed:

find frontend/src/modules/manager -type f \( -name "*.ts" -o -name "*.tsx" \) | xargs grep -l "z\."

Repository: NowwarAbuRashed/OrderSystem

Length of output: 239


🏁 Script executed:

grep -n "action" frontend/src/modules/manager/pages/InventoryManagePage.tsx | head -20

Repository: NowwarAbuRashed/OrderSystem

Length of output: 1100


🏁 Script executed:

python3 << 'EOF'
with open('frontend/src/modules/manager/pages/InventoryManagePage.tsx', 'r') as f:
    lines = f.readlines()
    
# Print lines 160-190
for i in range(159, min(190, len(lines))):
    print(f"{i+1:4d}: {lines[i]}", end='')
EOF

Repository: NowwarAbuRashed/OrderSystem

Length of output: 1995


🏁 Script executed:

python3 << 'EOF'
with open('frontend/src/modules/manager/pages/InventoryManagePage.tsx', 'r') as f:
    content = f.read()
    
# Find the validation schema/form setup
if 'superRefine' in content:
    print("=== Found superRefine ===")
    idx = content.find('superRefine')
    print(content[max(0, idx-500):idx+1000])
else:
    print("No superRefine found")
    
# Find the buttons
if 'Add Stock' in content:
    print("\n=== Found Add Stock button ===")
    idx = content.find('Add Stock')
    print(content[max(0, idx-1000):idx+500])
EOF

Repository: NowwarAbuRashed/OrderSystem

Length of output: 3131


🏁 Script executed:

find frontend/src -type f \( -name "*.ts" -o -name "*.tsx" \) -exec grep -l "superRefine" {} \;

Repository: NowwarAbuRashed/OrderSystem

Length of output: 284


🏁 Script executed:

python3 << 'EOF'
import os

# Search for any validation or schema files
for root, dirs, files in os.walk('frontend/src'):
    for file in files:
        if file.endswith(('.ts', '.tsx')):
            filepath = os.path.join(root, file)
            with open(filepath, 'r') as f:
                try:
                    content = f.read()
                    if 'superRefine' in content:
                        print(f"Found in: {filepath}")
                except:
                    pass
EOF

Repository: NowwarAbuRashed/OrderSystem

Length of output: 324


🏁 Script executed:

cat frontend/src/modules/manager/pages/InventoryManagePage.tsx > /tmp/inventory.txt && head -n 200 /tmp/inventory.txt | tail -n 50

Repository: NowwarAbuRashed/OrderSystem

Length of output: 2455


🏁 Script executed:

python3 << 'EOF'
with open('frontend/src/modules/manager/pages/InventoryManagePage.tsx') as f:
    lines = f.readlines()
    total = len(lines)
    
print(f"Total lines: {total}\n")

# Show around line 168-182
start = max(0, 167)
end = min(len(lines), 185)

for i in range(start, end):
    print(f"{i+1:4d}: {lines[i]}", end='')

# Also search for Add Stock and Remove buttons
print("\n\n=== Searching for buttons ===\n")
for i, line in enumerate(lines):
    if 'Add Stock' in line or 'Remove' in line and 'button' in lines[max(0, i-2):i+1]:
        start_ctx = max(0, i-5)
        end_ctx = min(len(lines), i+3)
        for j in range(start_ctx, end_ctx):
            print(f"{j+1:4d}: {lines[j]}", end='')
        print()
EOF

Repository: NowwarAbuRashed/OrderSystem

Length of output: 1853


🏁 Script executed:

grep -n "setValue.*action" frontend/src/modules/manager/pages/InventoryManagePage.tsx

Repository: NowwarAbuRashed/OrderSystem

Length of output: 202


🏁 Script executed:

rg "setValue" frontend/src/modules/manager/pages/InventoryManagePage.tsx -n

Repository: NowwarAbuRashed/OrderSystem

Length of output: 328


🏁 Script executed:

rg "Add Stock" frontend/src/modules/manager/pages/InventoryManagePage.tsx -B 10 -A 2 -n

Repository: NowwarAbuRashed/OrderSystem

Length of output: 859


🏁 Script executed:

awk '/Add Stock/{print NR": "$0}' frontend/src/modules/manager/pages/InventoryManagePage.tsx

Repository: NowwarAbuRashed/OrderSystem

Length of output: 133


🏁 Script executed:

python3 << 'EOF'
with open('frontend/src/modules/manager/pages/InventoryManagePage.tsx') as f:
    lines = f.readlines()

for i, line in enumerate(lines):
    if 'Add Stock' in line:
        print(f"Line {i+1}: Found 'Add Stock'")
        # Print context
        for j in range(max(0, i-15), min(len(lines), i+5)):
            print(f"{j+1:4d}: {lines[j]}", end='')
        break
EOF

Repository: NowwarAbuRashed/OrderSystem

Length of output: 1389


Call setValue with shouldValidate: true to clear stale validation errors when switching actions.

The superRefine check attached to the qty field only applies when action='remove'. When a user exceeds the product quantity with action='remove', an error persists on the input. Clicking Add Stock switches action to 'add', but because setValue defaults to shouldValidate: false, the stale error remains visible on the quantity input even though the max-stock check no longer applies (the opposite occurs when switching from 'add' to 'remove').

♻️ Proposed fix
-                  onClick={() => setValue('action', 'add')}
+                  onClick={() => setValue('action', 'add', { shouldValidate: true, shouldDirty: true })}
...
-                  onClick={() => setValue('action', 'remove')}
+                  onClick={() => setValue('action', 'remove', { shouldValidate: true, shouldDirty: true })}

Also consider adding aria-pressed={actionValue === 'add' ? true : false} to both buttons for screen-reader accessibility.

📝 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
<button
type="button"
onClick={() => setValue('action', 'add')}
className={`flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-semibold border-2 transition-all ${actionValue === 'add' ? 'border-success-500 bg-success-50 text-success-700' : 'border-slate-200 text-slate-500 hover:border-slate-300'}`}
>
<Plus className="w-4 h-4" /> Add Stock
</button>
<button
type="button"
onClick={() => setValue('action', 'remove')}
className={`flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-semibold border-2 transition-all ${actionValue === 'remove' ? 'border-danger-500 bg-danger-50 text-danger-700' : 'border-slate-200 text-slate-500 hover:border-slate-300'}`}
>
<Minus className="w-4 h-4" /> Remove
</button>
</div>
<button
type="button"
onClick={() => setValue('action', 'add', { shouldValidate: true, shouldDirty: true })}
className={`flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-semibold border-2 transition-all ${actionValue === 'add' ? 'border-success-500 bg-success-50 text-success-700' : 'border-slate-200 text-slate-500 hover:border-slate-300'}`}
>
<Plus className="w-4 h-4" /> Add Stock
</button>
<button
type="button"
onClick={() => setValue('action', 'remove', { shouldValidate: true, shouldDirty: true })}
className={`flex items-center justify-center gap-2 py-3 rounded-xl text-sm font-semibold border-2 transition-all ${actionValue === 'remove' ? 'border-danger-500 bg-danger-50 text-danger-700' : 'border-slate-200 text-slate-500 hover:border-slate-300'}`}
>
<Minus className="w-4 h-4" /> Remove
</button>
</div>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/modules/manager/pages/InventoryManagePage.tsx` around lines 168
- 182, The stale validation error persists because setValue('action', ...) is
called without triggering validation; update the two button handlers that call
setValue('action', 'add') and setValue('action', 'remove') to pass
shouldValidate: true so the qty field's superRefine check is re-run when
switching actions (i.e., call setValue('action', 'add', { shouldValidate: true
}) and setValue('action', 'remove', { shouldValidate: true })); also add
aria-pressed={actionValue === 'add'} / aria-pressed={actionValue === 'remove'}
to the respective buttons for improved screen-reader accessibility.

@NowwarAbuRashed NowwarAbuRashed closed this pull request by merging all changes into main in 78a1f91 Apr 24, 2026
@NowwarAbuRashed NowwarAbuRashed deleted the feature/nice-to-have-polish branch April 24, 2026 20:04
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.

1 participant