Skip to content

Auth & Vendor API - #11

Merged
shakibdshy merged 29 commits into
mainfrom
dev
Jan 31, 2026
Merged

Auth & Vendor API#11
shakibdshy merged 29 commits into
mainfrom
dev

Conversation

@shakibdshy

@shakibdshy shakibdshy commented Jan 31, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

Release Notes

  • New Features

    • Added comprehensive authentication system with sign-in, sign-up, and two-factor authentication support
    • Introduced database-driven data management infrastructure with proper schema configuration
    • Added form validation and error handling across all admin and vendor interfaces
    • Implemented user security features including password management and 2FA enablement
    • Created reusable dialog components for entity management (products, brands, categories, attributes, coupons, etc.)
  • Documentation

    • Completely redesigned README with improved organization, quick-start guide, and feature descriptions for customers, vendors, and admins
  • Chores

    • Standardized code formatting and quote styles throughout codebase
    • Updated dependencies with database, authentication, email, and file upload libraries
    • Added configuration files for database migrations and environment setup

✏️ Tip: You can customize this high-level summary in your review settings.

…d vendor registration

This commit introduces a comprehensive authentication system including:
- Sign-in and sign-up pages with form validation
- Vendor registration flow
- Database schema and migrations for auth
- API routes for auth handlers
- Client-side auth utilities and hooks
- Password validation and security features
- Social login integration (GitHub, Google)
- Session management
- Add 2FA support with email verification codes
- Create security settings UI for enabling/disabling 2FA
- Implement OTP input component and email templates
- Add user menu dropdown with profile links
- Update auth schema to support 2FA configuration
- Move toast notifications to bottom-right
…reation

- Implement vendor registration flow with personal and store info steps
- Add vendor and shop schemas to database
- Create slug utilities for shop URLs
- Add admin plugin for role-based access control
- Implement vendor registration server function with validation
- Replace mock user data with actual session user from auth client
- Update VendorUserMenu to handle nullable user fields and implement sign out
- Add conditional rendering to show sign-in button when user is not authenticated
- Use auth client's signOut function for proper session termination
Protect vendor dashboard routes by requiring a valid session.
Unauthenticated users are redirected to the sign-in page.
…tions

- Add @uploadcare/react-uploader dependency for image uploads
- Update shop types and schema to support real data operations
- Create vendor helper functions for user role and vendor lookup
- Implement comprehensive shop CRUD functions with server-side validation
- Add React Query hooks for shops data fetching and mutations
- Replace mock data with real API calls in my-shops page
- Enhance shop card with edit dialog and image upload capabilities
- Add empty state and shop count badge to vendor dashboard sidebar
- Improve shop form with better validation and file upload integration
- Add admin middleware to protect admin routes and server functions
- Create admin error component for access denied and error handling
- Add admin shop query validator and server function for shop management
- Move hooks to common directory and extract shared CRUD hook
- Fix header console log removal and update import paths
- Add forbidden route for admin access denied scenarios
…a fetching

- Add uuid dependency for category ID generation
- Create category database schema with hierarchical support
- Implement server-side pagination, sorting, and filtering for categories
- Add vendor category management UI with reusable components
- Create shared category validators and query helpers for DRY compliance
- Implement category CRUD operations with TanStack Query mutations
- Add server fetcher factory for consistent data fetching patterns
- Create reusable confirm delete dialog and page skeleton components
- Update sidebar to use real session data for user display
- Fix type definitions and filter column generics for better type safety
- Add brand schema to database and include in drizzle config
- Create server functions for brand operations with auth middleware
- Implement vendor brand management UI with data table and dialogs
- Add form validation using Zod schemas for create/update operations
- Set up TanStack Query for data fetching and mutation state management
- Move Uploadcare CSS import to root layout for global availability
- Remove unused CSS import from add-category-dialog component
…alog

- Replace NormalizedBrand with BrandItem as the single brand type
- Extract shared form logic into EntityFormDialog component
- Add product count validation before brand deletion
- Remove unused product count queries and FIXME comments
This change enforces consistent quote style by updating all single quotes in imports to double quotes, aligning with the project's biome.json configuration. The update improves code consistency and readability without affecting functionality.
- Refactor category table into separate admin and vendor components for better maintainability
- Fix empty string validation in optional field validator to properly handle empty values
- Update category deletion to check actual product count before allowing deletion
- Simplify category query helpers by removing unused product count queries
- Update build scripts to exclude generated route tree file from formatting/linting
- Fix slug auto-generation in entity form dialog to use onChange instead of onBlur
Improve code maintainability by moving inline fields definition to a separate constant. This reduces duplication and makes the component structure clearer.
…operations

- Add attribute database schema with tables for attributes and attribute values
- Implement vendor attribute CRUD operations with server functions and validation
- Create attribute management UI components with data table integration
- Add attribute query helpers for optimized batch fetching and normalization
- Integrate attribute management into vendor shop dashboard
…CRUD operations

- Add tags database schema with shop-scoped table and relations
- Create shared tag query validators and helper functions for data fetching
- Implement vendor tag CRUD server functions with proper authorization
- Add vendor tags page with data table, filtering, and mutation handling
- Create reusable tag components (header, table, dialog) for vendor interface
- Refactor brand and category validators to use shared query schemas
- Integrate tags into vendor entity fetchers for consistent data fetching patterns
- Add database schema for tax rates with shop scoping and regional support
- Create comprehensive tax rate CRUD operations with validation
- Implement vendor tax rate management UI with data table integration
- Add server functions for tax rate queries, creation, updates, and deletion
- Create reusable tax rate form dialog and table column components
- Integrate tax rate management into vendor shop routes
- Update types and hooks to support new tax rate functionality
- Set Toaster theme to dark for consistent UI appearance
- Replace AddAttributeDialog with inline form in EntityFormDialog for better UX
- Add proper validation and dynamic form fields for attribute values
- Fix type safety in data table components with generic FilterableColumn
- Ensure default values for attribute values array to prevent runtime errors
- Update attribute header to use simple button instead of dialog trigger
- Update BrandFormValues, TagFormValues, and TaxRateFormValues interfaces to make non-essential fields optional for flexible form handling
- Add missing TaxPermissions and Taxes interfaces to complete tax type definitions
- Extend mock data to include all required fields from updated BrandItem and TagItem interfaces
- Refactor admin components to use proper form value types and pass full objects to delete handlers
- Consolidate table components by removing separate permission props in favor of mode-based rendering
…h button

- Remove internal dialog state and AddTagDialog component
- Replace with a simple button that triggers parent-provided onAddTag callback
- Update description text to be more concise
- Simplify props interface by removing TagFormValues dependency
- Replace individual header components (TagHeader, TaxHeader, etc.) with a factory function
- Unify prop naming from onAdd{Entity} to consistent onAdd callback
- Reduce code duplication and improve maintainability
- Update all template files to use the new prop naming convention
…ports

Consolidate the shared entity header factory function into the base/common directory to improve code organization and maintainability. Update all component imports to use the new location instead of relative paths.
…t types

Move type definitions from hook to shared validator file to centralize type definitions and improve maintainability.
…pagination

- Add product database schema with relations to categories, brands, tags, and attributes
- Create server-side pagination fetcher for vendor products with filtering and sorting
- Replace mock product table with TanStack Query-based data fetching and mutations
- Implement product CRUD operations with proper error handling and loading states
- Add product form dialog with image upload support and attribute selection
- Migrate products route to use real data instead of mock data
- Update entity header component for reusable product headers
- Add product table columns with action dropdown for edit/delete/toggle
- Integrate with existing vendor entity fetchers pattern
…gination

- Add coupon database schema with product/category restrictions and usage tracking
- Create vendor coupon management pages with server-side data fetching
- Implement shared coupon table components for admin and vendor dashboards
- Add coupon validation and mutation hooks with TanStack Query integration
- Replace mock data with real API calls for coupon CRUD operations
- Introduce date picker component for coupon validity period selection
- Refactor coupon types and validators into centralized modules
Prevents form validation errors by ensuring the applicableTo select field has a default value of "all" when the dialog opens.
…tion

Extract coupon data creation into a shared object to eliminate repetitive code in create and update operations. This improves maintainability by having a single source of truth for the coupon data structure.
@coderabbitai

coderabbitai Bot commented Jan 31, 2026

Copy link
Copy Markdown

Important

Review skipped

Too many files!

This PR contains 299 files, which is 149 over the limit of 150.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

  • ✅ Full review completed - (🔄 Check again to review again)
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch dev

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

❤️ Share

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

@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

Note

Due to the large number of review comments, Critical, Major 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 (16)
src/components/base/store/checkout/shipping-address-form.tsx (1)

37-41: ⚠️ Potential issue | 🟠 Major

Call form.handleSubmit(); current handler is a no-op.

form.handleSubmit; only references the function, so submission/validation never runs after preventDefault. This breaks the form.

✅ Suggested fix
       onSubmit={(e) => {
         e.preventDefault();
         e.stopPropagation();
-        form.handleSubmit;
+        form.handleSubmit();
       }}
src/components/containers/store/accounts/wishlist/wishlist-list.tsx (1)

9-21: ⚠️ Potential issue | 🔴 Critical

Return the fallback UI to avoid null .map() crash.
Currently the NotFound block is evaluated but never returned, so the code continues and will throw if mockWishlists is null/undefined.

✅ Suggested fix
-  if (!mockWishlists) {
-    <div className="@container container mx-auto px-4 py-8">
+  if (!mockWishlists) {
+    return (
+      <div className="@container container mx-auto px-4 py-8">
         <NotFound
           title="Category not found"
           description="The category you're looking for doesn't exist or has been removed."
           icon={<ShoppingBag className="h-10 w-10 text-muted-foreground" />}
         >
           <Link to="/product">
             <Button variant="outline">Start Shopping</Button>
           </Link>
         </NotFound>
-    </div>;
+      </div>
+    );
   }
src/components/templates/store/category/category-detail-template.tsx (1)

45-56: ⚠️ Potential issue | 🟠 Major

Return the NotFound UI for missing categories.

Right now the JSX in the !category branch is not returned, so the NotFound view never renders and the component continues with category null. That yields a broken/blank experience.

✅ Suggested fix
   if (!category) {
-    <div className="@container container mx-auto px-4 py-8">
+    return (
+      <div className="@container container mx-auto px-4 py-8">
         <NotFound
           title="Category not found"
           description="The category you're looking for doesn't exist or has been removed."
           icon={<ShoppingBag className="h-10 w-10 text-muted-foreground" />}
         >
           <Link to="/category">
             <Button variant="outline">Browse All Categories</Button>
           </Link>
         </NotFound>
-    </div>;
+      </div>
+    );
   }
src/components/containers/shared/orders/order-table.tsx (2)

161-168: ⚠️ Potential issue | 🟡 Minor

onUpdateStatus receives current status, not a new status.

The callback is invoked with row.original.status (the current status), but the prop signature names this parameter newStatus. This is either a naming issue or a logic bug—typically "Update Status" would open a dialog to select a new status, then call the handler with that selection.

If the intent is to pass the current status for context, consider renaming the parameter to currentStatus in the interface to avoid confusion.

Option 1: Rename parameter if intent is to pass current status
-  onUpdateStatus?: (orderId: string, newStatus: string) => void;
+  onUpdateStatus?: (orderId: string, currentStatus: string) => void;

170-170: ⚠️ Potential issue | 🟡 Minor

"Download Invoice" has no click handler.

This menu item is rendered but does nothing when clicked—likely incomplete functionality.

Would you like me to open an issue to track implementing the invoice download functionality?

src/components/containers/store/checkout/checkout-order-summary.tsx (1)

19-36: ⚠️ Potential issue | 🔴 Critical

Critical: Missing return statement prevents empty state from rendering.

The empty cart condition creates a JSX element but doesn't return it. Execution continues to line 37, causing the full cart UI to render even when items.length === 0. Users will never see the empty state.

Note: This is a pre-existing bug not introduced by this PR, but it critically affects the user experience and should be fixed.

🐛 Proposed fix: Add return statement
   if (items.length === 0) {
-    <div className="rounded-lg border bg-background p-6 shadow-sm">
+    return (
+      <div className="rounded-lg border bg-background p-6 shadow-sm">
-      <h2 className="mb-6 font-semibold text-xl">Your Cart</h2>
-      <EmptyState
-        icon={<ShoppingBag className="h-10 w-10 text-muted-foreground" />}
-        title="No items yet"
-        description="Add items to your cart to continue with checkout."
-        action={
-          <Link to="/product">
-            <Button variant="outline" className="rounded-full">
-              Browse Products
-            </Button>
-          </Link>
-        }
-        className="py-8"
-      />
-    </div>;
+        <h2 className="mb-6 font-semibold text-xl">Your Cart</h2>
+        <EmptyState
+          icon={<ShoppingBag className="h-10 w-10 text-muted-foreground" />}
+          title="No items yet"
+          description="Add items to your cart to continue with checkout."
+          action={
+            <Link to="/product">
+              <Button variant="outline" className="rounded-full">
+                Browse Products
+              </Button>
+            </Link>
+          }
+          className="py-8"
+        />
+      </div>
+    );
   }
src/components/containers/shared/shipping/shipping-table.tsx (1)

70-106: ⚠️ Potential issue | 🟡 Minor

Empty dropdown menu when no actions are available.

When both canEdit and canDelete are false, the actions button still renders but opens an empty dropdown menu—this is confusing UX. Consider hiding the entire actions column or the menu trigger when no actions are permitted.

🛠️ Proposed fix: conditionally render actions cell
    {
      id: "actions",
      enableHiding: false,
      cell: ({ row }) => {
        const shippingMethod = row.original;
+        const hasActions = permissions.canEdit || permissions.canDelete;
+
+        if (!hasActions) return null;

        return (
          <DropdownMenu>
src/components/containers/shared/users/user-table.tsx (1)

153-155: ⚠️ Potential issue | 🟡 Minor

"View Details" menu item has no handler.

The item is rendered when permissions.canView is true but clicking it does nothing. Consider adding an onViewUser callback prop similar to onEditUser and onDeleteUser.

src/components/base/error/default-error-component.tsx (1)

84-88: ⚠️ Potential issue | 🟡 Minor

Verify Tailwind supports h-37.5 in production builds.

The h-37.5 class is not a standard Tailwind utility and won't be recognized without a custom theme extension. In production, this class will be dropped, causing the scroll area to expand unexpectedly. Use an arbitrary value instead:

✅ Recommended fix
-                    <ScrollArea className="mt-2 h-37.5 w-full rounded border bg-background/50 p-2 text-muted-foreground">
+                    <ScrollArea className="mt-2 h-[2.34375rem] w-full rounded border bg-background/50 p-2 text-muted-foreground">
biome.json (1)

39-56: ⚠️ Potential issue | 🟠 Major

Keep noDocumentCookie enabled to avoid normalizing unsafe patterns.
Turning this rule off makes it easier for document.cookie usage to creep in, which is a common XSS/CSRF risk vector. Prefer keeping the rule on and using a targeted suppression only where absolutely required.

Suggested adjustment
-        "noDocumentCookie": "off"
+        "noDocumentCookie": "warn"
src/components/containers/shared/users/add-user-dialog.tsx (1)

43-55: ⚠️ Potential issue | 🟠 Major

Missing validation for required fields.

The name and email fields are marked as required in the UI (with asterisks), but no validation is configured in the form. The form will submit successfully with empty values since @tanstack/react-form requires explicit validators.

🛡️ Proposed fix to add validation
   const form = useForm({
     defaultValues: {
       name: "",
       email: "",
       status: "active" as "active" | "inactive" | "suspended",
       avatar: null as FileList | null,
     },
+    validators: {
+      onSubmit: ({ value }) => {
+        const errors: Record<string, string> = {};
+        if (!value.name.trim()) {
+          errors.name = "Name is required";
+        }
+        if (!value.email.trim()) {
+          errors.email = "Email is required";
+        } else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value.email)) {
+          errors.email = "Invalid email format";
+        }
+        return Object.keys(errors).length ? errors : undefined;
+      },
+    },
     onSubmit: async ({ value }) => {

Alternatively, add field-level validators for immediate feedback:

<form.Field 
  name="name"
  validators={{
    onChange: ({ value }) => !value.trim() ? "Name is required" : undefined,
  }}
>
src/components/templates/admin/admin-taxes-template.tsx (1)

36-40: ⚠️ Potential issue | 🔴 Critical

Missing import and incorrect component name for TaxTable.

The component used on line 36 is named TaxesTable (which doesn't exist), but the actual exported component from tax-table.tsx is named TaxTable. Additionally, the props passed to the component don't match its expected interface—TaxTable expects onDelete, onEdit, and onToggleActive callbacks, not permissions and onDeleteTax.

Correct the import and component name:

🐛 Import fix
 import { Plus } from "lucide-react";
 import { useState } from "react";
 import { AddTaxDialog } from "@/components/containers/shared/taxes/add-tax-dialog";
 import TaxHeader from "@/components/containers/shared/taxes/tax-header";
+import { TaxTable } from "@/components/containers/shared/taxes/tax-table";
 import { Button } from "@/components/ui/button";

Then update the component usage to match TaxTable's props signature.

src/components/containers/shared/transactions/transaction-table.tsx (1)

111-135: ⚠️ Potential issue | 🟡 Minor

Hide action items when handlers are missing.
With default permissions, users can see menu items that no-op if the handler isn’t provided.

🔧 Suggested guard
-              {permissions.canView && (
+              {permissions.canView && onViewTransaction && (
                 <DropdownMenuItem
                   onClick={() => onViewTransaction?.(transaction.id)}
                 >
                   <Eye className="mr-2 h-4 w-4" />
                   View Details
                 </DropdownMenuItem>
               )}
-              {permissions.canRefund &&
+              {permissions.canRefund &&
+                onRefundTransaction &&
                 transaction.paymentStatus === "paid" && (
                   <DropdownMenuItem
                     onClick={() => onRefundTransaction?.(transaction.id)}
                   >
                     <RefreshCcw className="mr-2 h-4 w-4" />
                     Refund
                   </DropdownMenuItem>
                 )}
-              {permissions.canDelete && (
+              {permissions.canDelete && onDeleteTransaction && (
                 <DropdownMenuItem
                   onClick={() => onDeleteTransaction?.(transaction.id)}
                   className="text-destructive"
                 >
                   <Trash2 className="mr-2 h-4 w-4" />
                   Delete
                 </DropdownMenuItem>
               )}
src/components/containers/shared/shops/add-shop-dialog.tsx (2)

49-64: ⚠️ Potential issue | 🟠 Major

Submission result not awaited — dialog closes prematurely on async errors.

The onSubmit callback supports Promise<void>, but its result is not awaited. If the parent's submission fails (e.g., network error), the dialog will have already closed and reset the form, preventing error feedback to the user.

🐛 Proposed fix
     onSubmit: async ({ value, formApi }) => {
       await formApi.validateAllFields("blur");
       await formApi.validateAllFields("change");

       const hasErrors = Object.values(formApi.state.fieldMeta).some(
         (meta) => meta?.errors && meta.errors.length > 0
       );

       if (hasErrors) {
         return;
       }

-      onSubmit(value);
-      onOpenChange(false);
-      form.reset();
+      await onSubmit(value);
+      onOpenChange(false);
+      form.reset();
     },

28-36: ⚠️ Potential issue | 🟡 Minor

Unused isSubmitting prop.

The isSubmitting prop is declared in the interface but never destructured or used. Either remove it from the interface or wire it into the form's submission state.

🧹 Option A: Remove unused prop
 interface AddShopDialogProps {
   open: boolean;
   onOpenChange: (open: boolean) => void;
   onSubmit: (data: ShopFormValues) => void | Promise<void>;
-  isSubmitting?: boolean;
 }
src/components/base/data-table/data-table-container.tsx (1)

93-104: ⚠️ Potential issue | 🟡 Minor

Handle empty array filter values (Line 100).
Empty multi-selects (e.g., []) will still be pushed as active filters. Consider treating empty arrays as “no filter” to prevent unintended filtering.

💡 Suggested fix
-        if (value !== "" && value !== undefined && value !== null) {
+        const isEmpty =
+          value === "" ||
+          value === undefined ||
+          value === null ||
+          (Array.isArray(value) && value.length === 0);
+        if (!isEmpty) {
           filtered.push({ id: columnId, value });
         }
🤖 Fix all issues with AI agents
In `@src/components/base/error/admin-error-component.tsx`:
- Around line 53-63: AdminErrorComponent currently reads error.message directly
which can throw if ErrorComponentProps is parameterized with a non-Error shape;
add a type guard like the pattern used in DefaultErrorComponent to ensure error
is an Error (e.g., error instanceof Error or typeof error?.message === "string")
before checking message, and only render AdminAccessDenied when the guard passes
and message.includes("Admin access required"); otherwise fall back to rendering
DefaultErrorComponent with the original error, reset, and info.

In `@src/components/base/forms/entity-form-dialog.tsx`:
- Around line 123-153: The effect currently lists form in its dependency array
which can cause re-renders loops because useForm may return a new object
reference; remove form from the dependency array and instead reference a stable
ref for the form methods (e.g., create a formRef = useRef(form) and update
formRef.current = form on each render, then call formRef.current.setFieldValue
and formRef.current.reset inside the useEffect), and change the dependency array
to only [open, initialValues, fields]; keep the existing logic around
prevOpenRef, initialValues, and defaults but use the stable formRef methods to
avoid infinite loops.
- Around line 96-121: The dialog currently closes and the form resets
unconditionally after awaiting onSubmit in the useForm onSubmit handler; wrap
the await onSubmit(value as T) call in a try/catch so that onOpenChange(false)
and form.reset() only run on successful completion, and in the catch log the
error and show a user-facing error (e.g., toast.error) so the dialog remains
open when onSubmit throws; update the onSubmit handler in the useForm invocation
(the onSubmit function, form.reset and onOpenChange calls) to implement this
error handling.

In `@src/components/base/vendors/my-shop/edit-shop-dialog.tsx`:
- Around line 55-57: handleSubmit currently calls onSubmit(data) but doesn't
await the Promise; make handleSubmit async and await onSubmit(data) so
submission completion and errors are propagated; locate the handleSubmit
function and change it to async, await onSubmit(data) and optionally wrap the
await in try/catch to handle/log/rethrow submission errors as appropriate for
the component's error handling flow (referencing handleSubmit, onSubmit, and
UpdateShopInput).

In `@src/components/containers/auth/auth-form.tsx`:
- Around line 227-234: The Checkbox handler is incorrect: Radix Checkbox
(Checkbox / CheckboxPrimitive.Root) uses onCheckedChange(boolean) not
onChange(event). Replace the onChange usage on the Checkbox with onCheckedChange
and pass the boolean directly to field.handleChange (keep id/name as field.name
and checked as Boolean(field.state.value)); remove the event casting to
HTMLInputElement to avoid the runtime failure.

In `@src/components/containers/shared/coupons/add-coupon-dialog.tsx`:
- Line 1: The component AddCouponDialog currently uses
toISOString().split("T")[0] to derive default/initial date strings (three places
around the existing toISOString usage), which forces UTC and shifts local dates;
replace those uses with date-fns format(...) producing a local-date string like
format(someDate, "yyyy-MM-dd") (you already import format and parseISO), e.g.
when building defaultStart/defaultEnd and initial form values call format(new
Date(valueOrToday), "yyyy-MM-dd") instead of toISOString().split("T")[0], and
ensure any parseISO usages remain for converting back to Date objects where
needed (update references in AddCouponDialog and related form initialization
code).

In `@src/components/containers/shared/taxes/add-tax-dialog.tsx`:
- Around line 8-82: The dialog doesn’t provide shopId required by
createTaxRateSchema, so update AddTaxDialog to always supply shopId to
EntityFormDialog: ensure initialValues passed into EntityFormDialog includes a
shopId (fallback to a required prop or context value) or intercept onSubmit to
merge { shopId } into the submitted TaxRateFormValues before validation;
reference AddTaxDialog, initialValues, EntityFormDialog, createTaxRateSchema and
onSubmit when making the change so every create/update operation includes
shopId.

In `@src/components/containers/store/accounts/enable-2fa-dialog.tsx`:
- Around line 22-105: The dialog leaves the password in state when closed via
overlay/escape; update Enable2FADialog to clear password whenever the dialog
closes by either (a) wrapping the passed onOpenChange with a local handler that
calls onOpenChange(value) and when value is false calls setPassword("") before
returning, and/or (b) adding a useEffect that watches the open prop and calls
setPassword("") when open becomes false; apply the same fix to the other dialog
component referenced (the similar dialog at lines 113-197) so any close path
clears the password state for privacy.

In `@src/components/containers/vendors/products/product-table-columns.tsx`:
- Around line 22-34: Rename the local type ProductMutationState to
ProductTableMutationState in the product-table-columns.tsx file and update all
usages and imports that reference it (e.g., in ProductColumnConfig, and where
product-table.tsx and shop-products-template.tsx import or refer to
ProductMutationState) so they now import/accept ProductTableMutationState;
ensure you update the type name in product-table.tsx (where the
columns/mutationState props are typed) and in shop-products-template.tsx (where
the component prop types or imports reference the old name) to avoid colliding
with the other ProductMutationState type.

In `@src/components/templates/admin/admin-coupons-template.tsx`:
- Around line 1-3: The AdminCouponTable import is pointing at the wrong module
that only exports VendorCouponTable; update the import of AdminCouponTable in
this file to import from the correct module
"@/components/containers/admin/coupons/admin-coupon-table" (and update any other
identical imports in this file around the other occurrences) so that the named
export AdminCouponTable is resolved from admin-coupon-table instead of
coupon-table.
🟡 Minor comments (18)
src/components/containers/store/marquee.tsx-31-35 (1)

31-35: ⚠️ Potential issue | 🟡 Minor

Duplicate keys when el.key is missing.

key: \${el.key ?? "dup"}`yields"dup"` for all duplicates when keys are missing, which causes React key collisions. Make the duplicate keys unique (e.g., include the index or a suffix).

✅ Suggested fix
-          ...items.map((el) => ({ el, key: `${el.key ?? "dup"}` })),
+          ...items.map((el, i) => ({ el, key: `${el.key ?? i}-dup` })),
src/components/containers/shared/shipping/shipping-table.tsx-45-47 (1)

45-47: ⚠️ Potential issue | 🟡 Minor

Potential runtime error if price is nullish.

The type assertion as number doesn't provide runtime safety. If price is null or undefined, calling .toFixed(2) will throw.

🛡️ Proposed fix with defensive fallback
      cell: ({ row }) => {
        const price = row.getValue("price") as number;
-        return <div className="font-medium">${price.toFixed(2)}</div>;
+        return <div className="font-medium">${(price ?? 0).toFixed(2)}</div>;
      },
src/components/containers/shared/users/user-table.tsx-57-62 (1)

57-62: ⚠️ Potential issue | 🟡 Minor

Potential runtime error if user.name is undefined or empty.

If user.name is undefined, null, or an empty string, calling .split(" ") will throw or produce unexpected fallback text (e.g., empty initials).

🛡️ Suggested defensive handling
             <AvatarFallback>
-              {user.name
-                .split(" ")
-                .map((n) => n[0])
-                .join("")
-                .toUpperCase()}
+              {(user.name || "U")
+                .split(" ")
+                .map((n) => n[0])
+                .filter(Boolean)
+                .join("")
+                .toUpperCase() || "U"}
             </AvatarFallback>
src/components/containers/shared/users/user-table.tsx-125-129 (1)

125-129: ⚠️ Potential issue | 🟡 Minor

Date parsing may fail if createdAt is a string from API response.

If the data originates from a JSON API, createdAt will be a string, not a Date object. The format() function from date-fns can handle ISO strings, but the type cast is misleading and could cause issues with other date operations.

🔧 Suggested explicit parsing
       cell: ({ row }) => {
-        const createdAt = row.getValue("createdAt") as Date;
+        const createdAt = new Date(row.getValue("createdAt") as string | Date);
         return (
src/components/base/forms/subscribe-form.tsx-46-49 (1)

46-49: ⚠️ Potential issue | 🟡 Minor

Ensure max-w-119.5 is a valid Tailwind class.
If not configured in Tailwind, it won’t apply. Consider bracket syntax.

🎯 Possible fix (adjust value as intended)
-            <Field
-              data-invalid={isInvalid}
-              className="relative w-full max-w-119.5"
-            >
+            <Field
+              data-invalid={isInvalid}
+              className="relative w-full max-w-[119.5px]"
+            >
src/components/base/common/collection-item.tsx-24-24 (1)

24-24: ⚠️ Potential issue | 🟡 Minor

Fix typo in Tailwind class (relativea).
This class won’t apply; likely intended relative.

🐛 Proposed fix
-    <div className={cn("relativea border-dashed `@6xl`:p-[30px] p-5", className)}>
+    <div className={cn("relative border-dashed `@6xl`:p-[30px] p-5", className)}>
drizzle.config.ts-4-4 (1)

4-4: ⚠️ Potential issue | 🟡 Minor

Add early validation for DATABASE_URL environment variable.

The non-null assertion (!) on process.env.DATABASE_URL will result in undefined being passed to drizzle-kit if the variable is missing, potentially causing confusing downstream errors. Fail fast with a clear message.

🛡️ Proposed fix to add validation
-const connectionString = process.env.DATABASE_URL!;
+const connectionString = process.env.DATABASE_URL;
+
+if (!connectionString) {
+  throw new Error("DATABASE_URL environment variable is required");
+}
src/components/base/vendors/my-shop/edit-shop-dialog.tsx-133-133 (1)

133-133: ⚠️ Potential issue | 🟡 Minor

Non-null assertion on environment variable may cause runtime error.

Using import.meta.env.VITE_UPLOADCARE_PUB_KEY! assumes the environment variable is always defined. If it's missing, the uploader will receive undefined, potentially causing a cryptic error.

Consider adding a fallback or validating the key exists, or centralizing this config.

🛡️ Suggested approach
// In a config file or at module level
const UPLOADCARE_PUB_KEY = import.meta.env.VITE_UPLOADCARE_PUB_KEY;
if (!UPLOADCARE_PUB_KEY) {
  console.warn("VITE_UPLOADCARE_PUB_KEY is not configured");
}

// Then use UPLOADCARE_PUB_KEY in the component

Also applies to: 180-180

src/components/base/forms/form.tsx-51-58 (1)

51-58: ⚠️ Potential issue | 🟡 Minor

Async function called without await may cause unexpected behavior.

handleStepSubmit is an async function, but it's called without await in the onSubmit handler. This means errors won't be caught properly and the submission flow may complete before validation finishes.

Additionally, event.preventDefault() is called both in handleStepSubmit (line 24) and in the else branch (line 56), but if multiStep is truthy, the else branch's preventDefault won't run - this is fine, but the async call without await is the main concern.

🔧 Proposed fix
       onSubmit={(event) => {
+        event.preventDefault();
         onSubmit?.(event);
         if (multiStep) {
-          handleStepSubmit(event);
+          void handleStepSubmit(event);
         } else {
-          event.preventDefault();
           form.handleSubmit();
         }
       }}
src/components/containers/auth/two-factor-form.tsx-93-107 (1)

93-107: ⚠️ Potential issue | 🟡 Minor

Potential double submission when OTP auto-completes.

The onComplete callback triggers handleVerify automatically when 6 digits are entered. If the user then clicks the "Verify Code" button while the first request is still in flight, handleVerify could be called twice. The loading check at line 25 won't prevent the onComplete trigger.

Consider disabling OTP input during loading or adding a guard in onComplete.

🛡️ Proposed fix
         <InputOTP
           maxLength={6}
           value={otp}
           onChange={setOtp}
-          onComplete={handleVerify}
+          onComplete={() => {
+            if (!loading) {
+              handleVerify();
+            }
+          }}
+          disabled={loading}
         >
src/components/containers/admin/admin-dashboard-sidebar.tsx-48-61 (1)

48-61: ⚠️ Potential issue | 🟡 Minor

Avoid nesting a Button inside a Link (invalid interactive element nesting).
A <button> inside an <a> is invalid HTML and breaks accessibility/keyboard behavior. The Button component supports asChild — use it to render the Link as the button element instead.

Suggested fix
-        ) : (
-          <Link to="/auth/sign-in">
-            <Button
-              variant="default"
-              className="w-full"
-              type="button"
-              size="lg"
-            >
-              Sign In
-            </Button>
-          </Link>
-        )}
+        ) : (
+          <Button variant="default" className="w-full" size="lg" asChild>
+            <Link to="/auth/sign-in">Sign In</Link>
+          </Button>
+        )}
src/components/base/vendors/my-shop/shop-card.tsx-32-39 (1)

32-39: ⚠️ Potential issue | 🟡 Minor

Add user feedback when shop update fails.

The error is only logged to console. Users won't know why the update failed.

💡 Proposed fix
+import { toast } from "sonner";
+
 const handleUpdateShop = async (data: UpdateShopInput) => {
   try {
     await updateShop(data);
     setIsDialogOpen(false);
+    toast.success("Shop updated successfully");
   } catch (error) {
     console.error("Failed to update shop:", error);
+    toast.error(error instanceof Error ? error.message : "Failed to update shop");
   }
 };
README.md-51-51 (1)

51-51: ⚠️ Potential issue | 🟡 Minor

Fix typo: "bearbones" should be "barebones".

✏️ Proposed fix
-- **[Zustand](https://zustand-demo.pmnd.rs/)**: A small, fast, and scalable bearbones state-management solution.
+- **[Zustand](https://zustand-demo.pmnd.rs/)**: A small, fast, and scalable barebones state-management solution.
src/components/containers/shared/attributes/attribute-header.tsx-3-8 (1)

3-8: ⚠️ Potential issue | 🟡 Minor

Fix description grammar (use plural “Attributes”).
The UI descriptions read awkwardly against the plural header title.

💬 Proposed copy update
-  adminDescription: "Manage product Attribute across the platform",
-  vendorDescription: "Manage your product Attribute and organization",
+  adminDescription: "Manage product Attributes across the platform",
+  vendorDescription: "Manage your product Attributes and organization",
src/components/base/forms/entity-form-dialog.tsx-203-204 (1)

203-204: ⚠️ Potential issue | 🟡 Minor

Environment variable accessed without fallback handling.

Line 204 uses a non-null assertion (!) on import.meta.env.VITE_UPLOADCARE_PUB_KEY. If this variable is missing, the uploader will fail silently or produce cryptic errors.

🛡️ Proposed defensive handling
-                pubkey={import.meta.env.VITE_UPLOADCARE_PUB_KEY!}
+                pubkey={import.meta.env.VITE_UPLOADCARE_PUB_KEY ?? ""}

Or add a runtime check:

const uploadcareKey = import.meta.env.VITE_UPLOADCARE_PUB_KEY;
if (!uploadcareKey) {
  console.warn("VITE_UPLOADCARE_PUB_KEY is not configured");
}
src/components/containers/shared/categories/add-category-dialog.tsx-29-32 (1)

29-32: ⚠️ Potential issue | 🟡 Minor

"None (Root Category)" option uses string "none" which may cause validation issues.

The parentId field has a "none" string value for the root category option, but createCategorySchema defines parentId as string, optional and nullable. If the form submits "none" as the parentId, it won't be treated as null/undefined, potentially causing backend issues.

🐛 Proposed fix: use empty string or handle "none" in submission

Option 1 - Use empty string:

   const categoryOptions: EntityFormField["selectOptions"] = [
-    { label: "None (Root Category)", value: "none" },
+    { label: "None (Root Category)", value: "" },
     ...categories.map((cat) => ({ label: cat.name, value: cat.id })),
   ];

Option 2 - Transform on submit (in parent component):

const handleSubmit = (data: CategoryFormValues) => {
  const payload = {
    ...data,
    parentId: data.parentId === "none" ? null : data.parentId,
  };
  // submit payload
};
src/components/containers/shared/coupons/coupon-table-columns.tsx-46-60 (1)

46-60: ⚠️ Potential issue | 🟡 Minor

Use the caller-provided status options.

getSharedCouponFilters ignores options.statusOptions and hardcodes values, so custom labels/values won’t take effect.

♻️ Suggested fix
     {
       id: "isActive",
       label: "Status",
       type: "select",
-      options: [
-        { label: "Active", value: "true" },
-        { label: "Inactive", value: "false" },
-      ],
+      options: options.statusOptions,
       placeholder: "Filter by status",
     },
src/components/containers/shared/attributes/attribute-table.tsx-58-79 (1)

58-79: ⚠️ Potential issue | 🟡 Minor

Client mode drops filter/search configuration.
filterableColumns is computed but only used in the server branch, so local data loses filters and search UI. Consider passing the same props in the client path for parity.

Suggested fix
   return (
     <DataTable
       columns={columns}
       data={attributes || []}
+      filterableColumns={filterableColumns}
+      globalFilterPlaceholder="Search attributes..."
       className={className}
     />
   );

Comment on lines +53 to +63
export default function AdminErrorComponent({
error,
reset,
info,
}: ErrorComponentProps) {
if (error.message.includes("Admin access required")) {
return <AdminAccessDenied />;
}

return <DefaultErrorComponent error={error} reset={reset} info={info} />;
}

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:

# First, let's look at the file mentioned in the review
cat -n src/components/base/error/admin-error-component.tsx | head -70

Repository: FullStack-Flow/shop-stack

Length of output: 2652


🏁 Script executed:

# Find where ErrorComponentProps is defined
fd . src --type f -name "*.ts" -o -name "*.tsx" | xargs rg "ErrorComponentProps" -B 2 -A 5

Repository: FullStack-Flow/shop-stack

Length of output: 2715


🏁 Script executed:

# Check for imports and type definitions in the error component directory
ls -la src/components/base/error/
cat -n src/components/base/error/*.tsx src/components/base/error/*.ts 2>/dev/null | head -200

Repository: FullStack-Flow/shop-stack

Length of output: 8054


🌐 Web query:

TanStack React Router ErrorComponentProps type definition error parameter

💡 Result:

In TanStack React Router, the errorComponent receives ErrorComponentProps<TError = Error>, defined as:

export type ErrorComponentProps<TError = Error> = {
  error: TError
  info?: { componentStack: string }
  reset: () => void
}

So the error parameter is generic (defaults to Error). If you throw something else (e.g. a custom error shape), type it like:

function MyErrorComponent({ error }: ErrorComponentProps<MyCustomError>) {
  // ...
}

[1], [2]


Guard against non-Error shapes before reading error.message.

TanStack React Router's ErrorComponentProps is a generic type that defaults to Error but can be parameterized with any custom type. Without a type guard, accessing error.message may fail if a non-Error type is used. The DefaultErrorComponent in this codebase already implements this pattern—follow the same approach for consistency.

🛠️ Suggested guard
 export default function AdminErrorComponent({
   error,
   reset,
   info,
 }: ErrorComponentProps) {
-  if (error.message.includes("Admin access required")) {
+  const message = error instanceof Error ? error.message : String(error);
+  if (message.includes("Admin access required")) {
     return <AdminAccessDenied />;
   }
 
   return <DefaultErrorComponent error={error} reset={reset} info={info} />;
 }
🤖 Prompt for AI Agents
In `@src/components/base/error/admin-error-component.tsx` around lines 53 - 63,
AdminErrorComponent currently reads error.message directly which can throw if
ErrorComponentProps is parameterized with a non-Error shape; add a type guard
like the pattern used in DefaultErrorComponent to ensure error is an Error
(e.g., error instanceof Error or typeof error?.message === "string") before
checking message, and only render AdminAccessDenied when the guard passes and
message.includes("Admin access required"); otherwise fall back to rendering
DefaultErrorComponent with the original error, reset, and info.

Comment on lines +96 to +121
const form = useForm({
defaultValues,
onSubmit: async ({ value, formApi }) => {
if (validationSchema) {
await formApi.validateAllFields("blur");
await formApi.validateAllFields("change");

const errors = Object.entries(formApi.state.fieldMeta)
.filter(([_key, meta]) => meta?.errors && meta.errors.length > 0)
.map(([key, meta]) => ({
field: key,
errors: meta?.errors,
}));

if (errors.length > 0) {
console.error("Form validation failed:", errors);
toast.error("Please fix the errors in the form before submitting.");
return;
}
}

await onSubmit(value as T);
onOpenChange(false);
form.reset();
},
});

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

Potential issue: Dialog closes even when onSubmit throws an error.

The submission handler calls onSubmit(value), then unconditionally closes the dialog and resets the form (lines 117-119). If onSubmit throws an exception, the catch block is missing, so the dialog will still close and form will reset despite the failure.

🐛 Proposed fix to handle submission errors properly
       await onSubmit(value as T);
-      onOpenChange(false);
-      form.reset();
+    } catch (error) {
+      // Let the caller handle the error - don't close the dialog
+      throw error;
+    }
+    onOpenChange(false);
+    form.reset();

Alternatively, wrap the entire success path:

-      await onSubmit(value as T);
-      onOpenChange(false);
-      form.reset();
+      try {
+        await onSubmit(value as T);
+        onOpenChange(false);
+        form.reset();
+      } catch (error) {
+        // Re-throw or handle - dialog stays open on failure
+        throw error;
+      }
📝 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 form = useForm({
defaultValues,
onSubmit: async ({ value, formApi }) => {
if (validationSchema) {
await formApi.validateAllFields("blur");
await formApi.validateAllFields("change");
const errors = Object.entries(formApi.state.fieldMeta)
.filter(([_key, meta]) => meta?.errors && meta.errors.length > 0)
.map(([key, meta]) => ({
field: key,
errors: meta?.errors,
}));
if (errors.length > 0) {
console.error("Form validation failed:", errors);
toast.error("Please fix the errors in the form before submitting.");
return;
}
}
await onSubmit(value as T);
onOpenChange(false);
form.reset();
},
});
const form = useForm({
defaultValues,
onSubmit: async ({ value, formApi }) => {
if (validationSchema) {
await formApi.validateAllFields("blur");
await formApi.validateAllFields("change");
const errors = Object.entries(formApi.state.fieldMeta)
.filter(([_key, meta]) => meta?.errors && meta.errors.length > 0)
.map(([key, meta]) => ({
field: key,
errors: meta?.errors,
}));
if (errors.length > 0) {
console.error("Form validation failed:", errors);
toast.error("Please fix the errors in the form before submitting.");
return;
}
}
try {
await onSubmit(value as T);
onOpenChange(false);
form.reset();
} catch (error) {
// Re-throw or handle - dialog stays open on failure
throw error;
}
},
});
🤖 Prompt for AI Agents
In `@src/components/base/forms/entity-form-dialog.tsx` around lines 96 - 121, The
dialog currently closes and the form resets unconditionally after awaiting
onSubmit in the useForm onSubmit handler; wrap the await onSubmit(value as T)
call in a try/catch so that onOpenChange(false) and form.reset() only run on
successful completion, and in the catch log the error and show a user-facing
error (e.g., toast.error) so the dialog remains open when onSubmit throws;
update the onSubmit handler in the useForm invocation (the onSubmit function,
form.reset and onOpenChange calls) to implement this error handling.

Comment on lines +123 to +153
const prevOpenRef = useRef(open);

useEffect(() => {
if (open && !prevOpenRef.current) {
if (initialValues) {
Object.entries(initialValues).forEach(([key, value]) => {
form.setFieldValue(key as any, value);
});
// Also set defaults for fields not in initialValues
fields.forEach((field) => {
if (!(field.name in initialValues)) {
form.setFieldValue(field.name as any, field.defaultValue ?? "");
}
});
} else {
form.reset();
fields.forEach((field) => {
const value = field.defaultValue ?? "";
if (value !== "") {
form.setFieldValue(field.name, value);
}
});
}
}

if (!open && prevOpenRef.current) {
form.reset();
}

prevOpenRef.current = open;
}, [open, initialValues, fields, form]);

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

The form object in useEffect dependencies may cause infinite loops.

Including form in the dependency array (line 153) can cause repeated re-renders because useForm may return a new object reference on each render. The effect sets field values which triggers state changes, potentially causing a loop.

🔧 Proposed fix: remove `form` from dependencies or memoize form methods
-  }, [open, initialValues, fields, form]);
+  }, [open, initialValues, fields]);

If form methods are needed, consider using refs or extracting only the stable methods needed:

+  const setFieldValue = form.setFieldValue;
+  const reset = form.reset;
+
   useEffect(() => {
     if (open && !prevOpenRef.current) {
       if (initialValues) {
         Object.entries(initialValues).forEach(([key, value]) => {
-          form.setFieldValue(key as any, value);
+          setFieldValue(key as any, value);
         });
         // ...
       }
     }
     // ...
-  }, [open, initialValues, fields, form]);
+  }, [open, initialValues, fields, setFieldValue, reset]);
📝 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 prevOpenRef = useRef(open);
useEffect(() => {
if (open && !prevOpenRef.current) {
if (initialValues) {
Object.entries(initialValues).forEach(([key, value]) => {
form.setFieldValue(key as any, value);
});
// Also set defaults for fields not in initialValues
fields.forEach((field) => {
if (!(field.name in initialValues)) {
form.setFieldValue(field.name as any, field.defaultValue ?? "");
}
});
} else {
form.reset();
fields.forEach((field) => {
const value = field.defaultValue ?? "";
if (value !== "") {
form.setFieldValue(field.name, value);
}
});
}
}
if (!open && prevOpenRef.current) {
form.reset();
}
prevOpenRef.current = open;
}, [open, initialValues, fields, form]);
const prevOpenRef = useRef(open);
useEffect(() => {
if (open && !prevOpenRef.current) {
if (initialValues) {
Object.entries(initialValues).forEach(([key, value]) => {
form.setFieldValue(key as any, value);
});
// Also set defaults for fields not in initialValues
fields.forEach((field) => {
if (!(field.name in initialValues)) {
form.setFieldValue(field.name as any, field.defaultValue ?? "");
}
});
} else {
form.reset();
fields.forEach((field) => {
const value = field.defaultValue ?? "";
if (value !== "") {
form.setFieldValue(field.name, value);
}
});
}
}
if (!open && prevOpenRef.current) {
form.reset();
}
prevOpenRef.current = open;
}, [open, initialValues, fields]);
🤖 Prompt for AI Agents
In `@src/components/base/forms/entity-form-dialog.tsx` around lines 123 - 153, The
effect currently lists form in its dependency array which can cause re-renders
loops because useForm may return a new object reference; remove form from the
dependency array and instead reference a stable ref for the form methods (e.g.,
create a formRef = useRef(form) and update formRef.current = form on each
render, then call formRef.current.setFieldValue and formRef.current.reset inside
the useEffect), and change the dependency array to only [open, initialValues,
fields]; keep the existing logic around prevOpenRef, initialValues, and defaults
but use the stable formRef methods to avoid infinite loops.

Comment on lines +55 to +57
const handleSubmit = (data: UpdateShopInput) => {
onSubmit(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 | 🟠 Major

onSubmit returns a Promise but is not awaited.

The handleSubmit function calls onSubmit(data) without awaiting it. Since onSubmit is typed as Promise<void>, any errors thrown during submission won't be caught, and the form won't know when submission completes.

🐛 Proposed fix
-  const handleSubmit = (data: UpdateShopInput) => {
-    onSubmit(data);
+  const handleSubmit = async (data: UpdateShopInput) => {
+    await onSubmit(data);
   };
📝 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 handleSubmit = (data: UpdateShopInput) => {
onSubmit(data);
};
const handleSubmit = async (data: UpdateShopInput) => {
await onSubmit(data);
};
🤖 Prompt for AI Agents
In `@src/components/base/vendors/my-shop/edit-shop-dialog.tsx` around lines 55 -
57, handleSubmit currently calls onSubmit(data) but doesn't await the Promise;
make handleSubmit async and await onSubmit(data) so submission completion and
errors are propagated; locate the handleSubmit function and change it to async,
await onSubmit(data) and optionally wrap the await in try/catch to
handle/log/rethrow submission errors as appropriate for the component's error
handling flow (referencing handleSubmit, onSubmit, and UpdateShopInput).

Comment on lines +227 to +234
<Checkbox
id={field.name}
name={field.name}
checked={Boolean(field.state.value)}
onChange={(e) =>
field.handleChange((e.target as HTMLInputElement).checked)
}
/>

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
# Check the Checkbox component's onChange signature
rg -A 15 'export.*Checkbox' src/components/ui/checkbox.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 89


🏁 Script executed:

cat src/components/ui/checkbox.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 1284


🏁 Script executed:

#!/bin/bash
# Search for other usages of the Checkbox component with onChange handler
rg -B 3 -A 3 'Checkbox.*onChange' src/ --type tsx

Repository: FullStack-Flow/shop-stack

Length of output: 96


🏁 Script executed:

#!/bin/bash
# Search for Checkbox component usages with onChange handler
rg -B 3 -A 3 'Checkbox.*onChange' src/

Repository: FullStack-Flow/shop-stack

Length of output: 51


🏁 Script executed:

#!/bin/bash
# Read the auth-form.tsx file to see the Checkbox onChange handler
sed -n '220,240p' src/components/containers/auth/auth-form.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 782


🏁 Script executed:

#!/bin/bash
# Check package.json for Radix version
cat package.json | grep -A 2 -B 2 "@radix-ui/react-checkbox"

Repository: FullStack-Flow/shop-stack

Length of output: 290


🏁 Script executed:

#!/bin/bash
# Search for all Checkbox component usages in the codebase
rg 'Checkbox' src/ -A 5 | head -100

Repository: FullStack-Flow/shop-stack

Length of output: 6809


🌐 Web query:

Radix UI checkbox onChange event signature React v1.3

💡 Result:

In @radix-ui/react-checkbox v1.3.x, Checkbox.Root (and Checkbox) does not use React’s onChange prop. It exposes:

onCheckedChange?(checked: boolean | "indeterminate"): void

So your handler receives the next checked state as boolean | "indeterminate", not a DOM event. [1][2]

Sources: Radix Checkbox docs (v1.3.3) [1], published type definitions for v1.3.3 [2].


Fix Checkbox handler: use onCheckedChange instead of onChange.

The Checkbox component is based on Radix UI's CheckboxPrimitive.Root (v1.3.3), which does not have an onChange prop. It uses onCheckedChange that receives the checked state as a boolean directly, not a DOM event. The current code attempting to cast e.target to HTMLInputElement will fail at runtime.

Change to:

<Checkbox
  id={field.name}
  name={field.name}
  checked={Boolean(field.state.value)}
  onCheckedChange={(checked) =>
    field.handleChange(checked)
  }
/>
🤖 Prompt for AI Agents
In `@src/components/containers/auth/auth-form.tsx` around lines 227 - 234, The
Checkbox handler is incorrect: Radix Checkbox (Checkbox /
CheckboxPrimitive.Root) uses onCheckedChange(boolean) not onChange(event).
Replace the onChange usage on the Checkbox with onCheckedChange and pass the
boolean directly to field.handleChange (keep id/name as field.name and checked
as Boolean(field.state.value)); remove the event casting to HTMLInputElement to
avoid the runtime failure.

@@ -1,243 +1,369 @@
import { useForm } from '@tanstack/react-form';
import { Button } from '@/components/ui/button';
import { format, parseISO } from "date-fns";

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:

cat -n src/components/containers/shared/coupons/add-coupon-dialog.tsx | head -50

Repository: FullStack-Flow/shop-stack

Length of output: 1853


🏁 Script executed:

# Check the file size first
wc -l src/components/containers/shared/coupons/add-coupon-dialog.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 135


🏁 Script executed:

# Look at the specific lines mentioned (1, 138-188, 331-343)
sed -n '1,15p' src/components/containers/shared/coupons/add-coupon-dialog.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 665


🏁 Script executed:

# Check lines 138-188 for date handling
sed -n '138,188p' src/components/containers/shared/coupons/add-coupon-dialog.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 1740


🏁 Script executed:

# Check lines 331-343
sed -n '331,343p' src/components/containers/shared/coupons/add-coupon-dialog.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 670


🏁 Script executed:

# Find CouponItem type definition
rg "interface CouponItem|type CouponItem" --type typescript -A 10

Repository: FullStack-Flow/shop-stack

Length of output: 103


🏁 Script executed:

# Check DatePicker component implementation
find . -type f -name "date-picker*" | head -5

Repository: FullStack-Flow/shop-stack

Length of output: 104


🏁 Script executed:

# Look at DatePicker component
cat src/components/ui/date-picker.tsx | head -80

Repository: FullStack-Flow/shop-stack

Length of output: 1383


🏁 Script executed:

# Find CouponItem type definition
rg "CouponItem" --type tsx --type ts -A 15 | head -60

Repository: FullStack-Flow/shop-stack

Length of output: 96


🏁 Script executed:

# Search for CouponFormValues type
rg "CouponFormValues" --type tsx --type ts -A 10 | head -50

Repository: FullStack-Flow/shop-stack

Length of output: 96


🏁 Script executed:

# Look at coupons types file directly
find . -type f -path "*/types/coupons*"

Repository: FullStack-Flow/shop-stack

Length of output: 91


🏁 Script executed:

# Read the coupons types file
cat src/types/coupons.ts

Repository: FullStack-Flow/shop-stack

Length of output: 6528


🏁 Script executed:

# Find the Coupon validator definition
find . -type f -path "*/validators/*coupon*"

Repository: FullStack-Flow/shop-stack

Length of output: 143


🏁 Script executed:

# Check the coupon-query validator
cat src/lib/validators/shared/coupon-query.ts | head -150

Repository: FullStack-Flow/shop-stack

Length of output: 4013


🏁 Script executed:

# Check the coupon validator for CouponFormValues definition
cat src/lib/validators/coupon.ts | grep -A 50 "CouponFormValues\|couponFormSchema"

Repository: FullStack-Flow/shop-stack

Length of output: 192


🏁 Script executed:

# Get more of the coupon-query file to find the form schema
cat src/lib/validators/shared/coupon-query.ts | tail -200

Repository: FullStack-Flow/shop-stack

Length of output: 6237


🏁 Script executed:

# Find the couponFormSchema definition in the file
rg "couponFormSchema\s*=" src/lib/validators/shared/coupon-query.ts -A 30

Repository: FullStack-Flow/shop-stack

Length of output: 1250


Replace UTC toISOString() with local-date formatting for defaults and initial values.

Using toISOString().split("T")[0] forces UTC conversion, which shifts the displayed day for users in non-UTC timezones. For example, a user in UTC+5 selecting 2024-01-15 will see the date stored as 2024-01-14 due to UTC offset. Use format() with local dates instead, matching the DatePicker's expected format.

✅ Suggested fix
-import { format, parseISO } from "date-fns";
+import { addDays, format, parseISO } from "date-fns";
 ...
-      defaultValue: new Date().toISOString().split("T")[0],
+      defaultValue: format(new Date(), "yyyy-MM-dd"),
 ...
-      defaultValue: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000)
-        .toISOString()
-        .split("T")[0],
+      defaultValue: format(addDays(new Date(), 30), "yyyy-MM-dd"),
 ...
-        activeFrom: initialValues.activeFrom
-          ? new Date(initialValues.activeFrom).toISOString().split("T")[0]
-          : "",
-        activeTo: initialValues.activeTo
-          ? new Date(initialValues.activeTo).toISOString().split("T")[0]
-          : "",
+        activeFrom: initialValues.activeFrom
+          ? format(new Date(initialValues.activeFrom), "yyyy-MM-dd")
+          : "",
+        activeTo: initialValues.activeTo
+          ? format(new Date(initialValues.activeTo), "yyyy-MM-dd")
+          : "",

Lines 138-140, 180-182, 331-343

🤖 Prompt for AI Agents
In `@src/components/containers/shared/coupons/add-coupon-dialog.tsx` at line 1,
The component AddCouponDialog currently uses toISOString().split("T")[0] to
derive default/initial date strings (three places around the existing
toISOString usage), which forces UTC and shifts local dates; replace those uses
with date-fns format(...) producing a local-date string like format(someDate,
"yyyy-MM-dd") (you already import format and parseISO), e.g. when building
defaultStart/defaultEnd and initial form values call format(new
Date(valueOrToday), "yyyy-MM-dd") instead of toISOString().split("T")[0], and
ensure any parseISO usages remain for converting back to Date objects where
needed (update references in AddCouponDialog and related form initialization
code).

Comment on lines 8 to +82
interface AddTaxDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSubmit: (data: TaxFormValues) => void;
onSubmit: (data: TaxRateFormValues) => void;
isSubmitting?: boolean;
initialValues?: Partial<TaxRateFormValues> | null;
}

export function AddTaxDialog({
open,
onOpenChange,
onSubmit,
isSubmitting = false,
initialValues,
}: AddTaxDialogProps) {
const form = useForm({
defaultValues: {
name: '',
rate: 0,
country: '',
state: '',
zip: '',
priority: 1,
const fields: EntityFormField[] = [
{
name: "name",
label: "Name",
required: true,
placeholder: "Enter tax rate name",
description: "Name for your tax rate (e.g., VAT, Sales Tax)",
},
{
name: "rate",
label: "Rate (%)",
required: true,
placeholder: "0.00",
description: "Tax rate percentage (between 0.01 and 100)",
},
{
name: "country",
label: "Country",
required: true,
placeholder: "US, UK, CA",
description: "ISO country code (2 characters)",
},
{
name: "state",
label: "State",
placeholder: "NY, CA, TX",
description: "State/Province code (optional)",
},
{
name: "zip",
label: "ZIP Code",
placeholder: "12345",
description: "ZIP/Postal code pattern (optional)",
},
onSubmit: async ({ value }) => {
onSubmit(value);
onOpenChange(false);
form.reset();
{
name: "priority",
label: "Priority",
required: true,
placeholder: "1",
description:
"Priority for tax calculation (lower number = higher priority)",
},
});
];

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-150">
<DialogHeader>
<DialogTitle>Add New Tax Rate</DialogTitle>
<DialogDescription>
Add a new tax rate for your shop.
</DialogDescription>
</DialogHeader>

<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
className="space-y-6"
>
<FieldGroup>
<div className="grid gap-4">
{/* Name Field */}
<form.Field name="name">
{(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid;
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Name*</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="e.g. VAT Standard"
aria-invalid={isInvalid}
/>
{isInvalid && (
<FieldError
errors={getFieldErrors(field.state.meta.errors)}
/>
)}
</Field>
);
}}
</form.Field>

{/* Rate Field */}
<form.Field name="rate">
{(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid;
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Rate (%)*</FieldLabel>
<Input
id={field.name}
name={field.name}
type="number"
step="0.01"
min="0"
max="100"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) =>
field.handleChange(parseFloat(e.target.value))
}
placeholder="0.00"
aria-invalid={isInvalid}
/>
{isInvalid && (
<FieldError
errors={getFieldErrors(field.state.meta.errors)}
/>
)}
</Field>
);
}}
</form.Field>

{/* Country Field */}
<form.Field name="country">
{(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid;
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Country*</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="e.g. US, UK, CA"
aria-invalid={isInvalid}
/>
{isInvalid && (
<FieldError
errors={getFieldErrors(field.state.meta.errors)}
/>
)}
</Field>
);
}}
</form.Field>

{/* State Field */}
<form.Field name="state">
{(field) => {
return (
<Field>
<FieldLabel htmlFor={field.name}>
State (Optional)
</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="e.g. NY, CA (optional)"
/>
</Field>
);
}}
</form.Field>

{/* ZIP Field */}
<form.Field name="zip">
{(field) => {
return (
<Field>
<FieldLabel htmlFor={field.name}>
ZIP Code (Optional)
</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="e.g. 10001 (optional)"
/>
</Field>
);
}}
</form.Field>

{/* Priority Field */}
<form.Field name="priority">
{(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid;
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Priority*</FieldLabel>
<Input
id={field.name}
name={field.name}
type="number"
min="1"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) =>
field.handleChange(parseInt(e.target.value, 10))
}
placeholder="1"
aria-invalid={isInvalid}
/>
{isInvalid && (
<FieldError
errors={getFieldErrors(field.state.meta.errors)}
/>
)}
</Field>
);
}}
</form.Field>
</div>
</FieldGroup>

<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
>
Cancel
</Button>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<Button type="submit" disabled={!canSubmit || isSubmitting}>
{isSubmitting ? 'Adding...' : 'Add Tax Rate'}
</Button>
)}
</form.Subscribe>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
<EntityFormDialog<TaxRateFormValues>
open={open}
onOpenChange={onOpenChange}
onSubmit={onSubmit}
isSubmitting={isSubmitting}
initialValues={initialValues}
title="Tax Rate"
description="Create or update a tax rate for your shop."
validationSchema={createTaxRateSchema}
submitButtonText={{
create: "Create Tax Rate",
update: "Update Tax Rate",
}}
fields={fields}
/>

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

Ensure shopId is always provided to satisfy the schema.
createTaxRateSchema requires shopId, but this dialog does not collect or inject it. New tax rates will fail validation unless initialValues.shopId is always set.

✅ One way to guarantee `shopId`
 interface AddTaxDialogProps {
   open: boolean;
   onOpenChange: (open: boolean) => void;
   onSubmit: (data: TaxRateFormValues) => void;
   isSubmitting?: boolean;
   initialValues?: Partial<TaxRateFormValues> | null;
+  shopId: string;
 }

 export function AddTaxDialog({
   open,
   onOpenChange,
   onSubmit,
   isSubmitting = false,
   initialValues,
+  shopId,
 }: AddTaxDialogProps) {
   ...
   return (
     <EntityFormDialog<TaxRateFormValues>
       open={open}
       onOpenChange={onOpenChange}
       onSubmit={onSubmit}
       isSubmitting={isSubmitting}
-      initialValues={initialValues}
+      initialValues={{ ...(initialValues ?? {}), shopId }}
       title="Tax Rate"
       ...
     />
   );
 }
📝 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
interface AddTaxDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSubmit: (data: TaxFormValues) => void;
onSubmit: (data: TaxRateFormValues) => void;
isSubmitting?: boolean;
initialValues?: Partial<TaxRateFormValues> | null;
}
export function AddTaxDialog({
open,
onOpenChange,
onSubmit,
isSubmitting = false,
initialValues,
}: AddTaxDialogProps) {
const form = useForm({
defaultValues: {
name: '',
rate: 0,
country: '',
state: '',
zip: '',
priority: 1,
const fields: EntityFormField[] = [
{
name: "name",
label: "Name",
required: true,
placeholder: "Enter tax rate name",
description: "Name for your tax rate (e.g., VAT, Sales Tax)",
},
{
name: "rate",
label: "Rate (%)",
required: true,
placeholder: "0.00",
description: "Tax rate percentage (between 0.01 and 100)",
},
{
name: "country",
label: "Country",
required: true,
placeholder: "US, UK, CA",
description: "ISO country code (2 characters)",
},
{
name: "state",
label: "State",
placeholder: "NY, CA, TX",
description: "State/Province code (optional)",
},
{
name: "zip",
label: "ZIP Code",
placeholder: "12345",
description: "ZIP/Postal code pattern (optional)",
},
onSubmit: async ({ value }) => {
onSubmit(value);
onOpenChange(false);
form.reset();
{
name: "priority",
label: "Priority",
required: true,
placeholder: "1",
description:
"Priority for tax calculation (lower number = higher priority)",
},
});
];
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="max-h-[90vh] overflow-y-auto sm:max-w-150">
<DialogHeader>
<DialogTitle>Add New Tax Rate</DialogTitle>
<DialogDescription>
Add a new tax rate for your shop.
</DialogDescription>
</DialogHeader>
<form
onSubmit={(e) => {
e.preventDefault();
e.stopPropagation();
form.handleSubmit();
}}
className="space-y-6"
>
<FieldGroup>
<div className="grid gap-4">
{/* Name Field */}
<form.Field name="name">
{(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid;
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Name*</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="e.g. VAT Standard"
aria-invalid={isInvalid}
/>
{isInvalid && (
<FieldError
errors={getFieldErrors(field.state.meta.errors)}
/>
)}
</Field>
);
}}
</form.Field>
{/* Rate Field */}
<form.Field name="rate">
{(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid;
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Rate (%)*</FieldLabel>
<Input
id={field.name}
name={field.name}
type="number"
step="0.01"
min="0"
max="100"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) =>
field.handleChange(parseFloat(e.target.value))
}
placeholder="0.00"
aria-invalid={isInvalid}
/>
{isInvalid && (
<FieldError
errors={getFieldErrors(field.state.meta.errors)}
/>
)}
</Field>
);
}}
</form.Field>
{/* Country Field */}
<form.Field name="country">
{(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid;
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Country*</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="e.g. US, UK, CA"
aria-invalid={isInvalid}
/>
{isInvalid && (
<FieldError
errors={getFieldErrors(field.state.meta.errors)}
/>
)}
</Field>
);
}}
</form.Field>
{/* State Field */}
<form.Field name="state">
{(field) => {
return (
<Field>
<FieldLabel htmlFor={field.name}>
State (Optional)
</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="e.g. NY, CA (optional)"
/>
</Field>
);
}}
</form.Field>
{/* ZIP Field */}
<form.Field name="zip">
{(field) => {
return (
<Field>
<FieldLabel htmlFor={field.name}>
ZIP Code (Optional)
</FieldLabel>
<Input
id={field.name}
name={field.name}
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) => field.handleChange(e.target.value)}
placeholder="e.g. 10001 (optional)"
/>
</Field>
);
}}
</form.Field>
{/* Priority Field */}
<form.Field name="priority">
{(field) => {
const isInvalid =
field.state.meta.isTouched && !field.state.meta.isValid;
return (
<Field data-invalid={isInvalid}>
<FieldLabel htmlFor={field.name}>Priority*</FieldLabel>
<Input
id={field.name}
name={field.name}
type="number"
min="1"
value={field.state.value}
onBlur={field.handleBlur}
onChange={(e) =>
field.handleChange(parseInt(e.target.value, 10))
}
placeholder="1"
aria-invalid={isInvalid}
/>
{isInvalid && (
<FieldError
errors={getFieldErrors(field.state.meta.errors)}
/>
)}
</Field>
);
}}
</form.Field>
</div>
</FieldGroup>
<DialogFooter>
<Button
type="button"
variant="outline"
onClick={() => onOpenChange(false)}
>
Cancel
</Button>
<form.Subscribe
selector={(state) => [state.canSubmit, state.isSubmitting]}
>
{([canSubmit, isSubmitting]) => (
<Button type="submit" disabled={!canSubmit || isSubmitting}>
{isSubmitting ? 'Adding...' : 'Add Tax Rate'}
</Button>
)}
</form.Subscribe>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
<EntityFormDialog<TaxRateFormValues>
open={open}
onOpenChange={onOpenChange}
onSubmit={onSubmit}
isSubmitting={isSubmitting}
initialValues={initialValues}
title="Tax Rate"
description="Create or update a tax rate for your shop."
validationSchema={createTaxRateSchema}
submitButtonText={{
create: "Create Tax Rate",
update: "Update Tax Rate",
}}
fields={fields}
/>
interface AddTaxDialogProps {
open: boolean;
onOpenChange: (open: boolean) => void;
onSubmit: (data: TaxRateFormValues) => void;
isSubmitting?: boolean;
initialValues?: Partial<TaxRateFormValues> | null;
shopId: string;
}
export function AddTaxDialog({
open,
onOpenChange,
onSubmit,
isSubmitting = false,
initialValues,
shopId,
}: AddTaxDialogProps) {
const fields: EntityFormField[] = [
{
name: "name",
label: "Name",
required: true,
placeholder: "Enter tax rate name",
description: "Name for your tax rate (e.g., VAT, Sales Tax)",
},
{
name: "rate",
label: "Rate (%)",
required: true,
placeholder: "0.00",
description: "Tax rate percentage (between 0.01 and 100)",
},
{
name: "country",
label: "Country",
required: true,
placeholder: "US, UK, CA",
description: "ISO country code (2 characters)",
},
{
name: "state",
label: "State",
placeholder: "NY, CA, TX",
description: "State/Province code (optional)",
},
{
name: "zip",
label: "ZIP Code",
placeholder: "12345",
description: "ZIP/Postal code pattern (optional)",
},
{
name: "priority",
label: "Priority",
required: true,
placeholder: "1",
description:
"Priority for tax calculation (lower number = higher priority)",
},
];
return (
<EntityFormDialog<TaxRateFormValues>
open={open}
onOpenChange={onOpenChange}
onSubmit={onSubmit}
isSubmitting={isSubmitting}
initialValues={{ ...(initialValues ?? {}), shopId }}
title="Tax Rate"
description="Create or update a tax rate for your shop."
validationSchema={createTaxRateSchema}
submitButtonText={{
create: "Create Tax Rate",
update: "Update Tax Rate",
}}
fields={fields}
/>
);
}
🤖 Prompt for AI Agents
In `@src/components/containers/shared/taxes/add-tax-dialog.tsx` around lines 8 -
82, The dialog doesn’t provide shopId required by createTaxRateSchema, so update
AddTaxDialog to always supply shopId to EntityFormDialog: ensure initialValues
passed into EntityFormDialog includes a shopId (fallback to a required prop or
context value) or intercept onSubmit to merge { shopId } into the submitted
TaxRateFormValues before validation; reference AddTaxDialog, initialValues,
EntityFormDialog, createTaxRateSchema and onSubmit when making the change so
every create/update operation includes shopId.

Comment on lines +22 to +105
export function Enable2FADialog({
open,
onOpenChange,
onSuccess,
}: Enable2FADialogProps) {
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);

const handleEnable = async () => {
if (!password) {
toast.error("Please enter your password");
return;
}

setLoading(true);
try {
const { data, error } = await twoFactor.enable({
password,
});

if (error) {
toast.error(error.message || "Failed to enable 2FA");
return;
}

if (data?.backupCodes) {
toast.success("Two-factor authentication enabled!");
onSuccess?.(data.backupCodes);
onOpenChange(false);
setPassword("");
}
} catch (err) {
toast.error(err instanceof Error ? err.message : "Failed to enable 2FA");
} finally {
setLoading(false);
}
};

return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent className="sm:max-w-md">
<DialogHeader>
<DialogTitle>Enable Two-Factor Authentication</DialogTitle>
<DialogDescription>
Enter your password to confirm and enable 2FA on your account.
You'll receive backup codes after enabling.
</DialogDescription>
</DialogHeader>

<div className="space-y-4 py-4">
<div className="space-y-2">
<Label htmlFor="password">Current Password</Label>
<Input
id="password"
type="password"
placeholder="Enter your password"
value={password}
onChange={(e) => setPassword(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
handleEnable();
}
}}
/>
</div>
</div>

<DialogFooter>
<Button
variant="outline"
onClick={() => {
onOpenChange(false);
setPassword("");
}}
>
Cancel
</Button>
<Button onClick={handleEnable} disabled={loading || !password}>
{loading ? "Enabling…" : "Enable 2FA"}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);

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

Clear password state when the dialog is closed (privacy).
If users dismiss the dialog via overlay/escape, the password remains in state and can reappear on reopen.

🛡️ Proposed fix (reset on any close)
 export function Enable2FADialog({
   open,
   onOpenChange,
   onSuccess,
 }: Enable2FADialogProps) {
   const [password, setPassword] = useState("");
   const [loading, setLoading] = useState(false);
+  const handleOpenChange = (nextOpen: boolean) => {
+    if (!nextOpen) setPassword("");
+    onOpenChange(nextOpen);
+  };

   return (
-    <Dialog open={open} onOpenChange={onOpenChange}>
+    <Dialog open={open} onOpenChange={handleOpenChange}>
       <DialogContent className="sm:max-w-md">
         ...
       </DialogContent>
     </Dialog>
   );
 }

 export function Disable2FADialog({
   open,
   onOpenChange,
   onSuccess,
 }: Disable2FADialogProps) {
   const [password, setPassword] = useState("");
   const [loading, setLoading] = useState(false);
+  const handleOpenChange = (nextOpen: boolean) => {
+    if (!nextOpen) setPassword("");
+    onOpenChange(nextOpen);
+  };

   return (
-    <Dialog open={open} onOpenChange={onOpenChange}>
+    <Dialog open={open} onOpenChange={handleOpenChange}>
       <DialogContent className="sm:max-w-md">
         ...
       </DialogContent>
     </Dialog>
   );
 }

Also applies to: 113-197

🤖 Prompt for AI Agents
In `@src/components/containers/store/accounts/enable-2fa-dialog.tsx` around lines
22 - 105, The dialog leaves the password in state when closed via
overlay/escape; update Enable2FADialog to clear password whenever the dialog
closes by either (a) wrapping the passed onOpenChange with a local handler that
calls onOpenChange(value) and when value is false calls setPassword("") before
returning, and/or (b) adding a useEffect that watches the open prop and calls
setPassword("") when open becomes false; apply the same fix to the other dialog
component referenced (the similar dialog at lines 113-197) so any close path
clears the password state for privacy.

Comment on lines +22 to +34
export interface ProductMutationState {
deletingId?: string | null;
togglingId?: string | null;
updatingId?: string | null;
creatingId?: string | null;
}

export interface ProductColumnConfig {
mode?: "vendor" | "customer";
actions: ProductTableActions;
mutationState?: ProductMutationState;
isMutating?: (id: string) => boolean;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find all occurrences of ProductMutationState with context
rg -n --type=ts --type=tsx -C3 '\bProductMutationState\b'

Repository: FullStack-Flow/shop-stack

Length of output: 96


🏁 Script executed:

#!/bin/bash
# Find all occurrences of ProductMutationState
rg -n 'ProductMutationState' --type ts

Repository: FullStack-Flow/shop-stack

Length of output: 1031


🏁 Script executed:

#!/bin/bash
# Read both type definitions to compare shapes
echo "=== Type from src/types/products.ts (lines around 286) ==="
sed -n '280,310p' src/types/products.ts

echo ""
echo "=== Type from product-table-columns.tsx (lines 22-26) ==="
sed -n '22,26p' src/components/containers/vendors/products/product-table-columns.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 1177


🏁 Script executed:

#!/bin/bash
# Check the full type definitions and their usages more clearly
echo "=== Full view of product-table-columns.tsx lines 22-34 ==="
sed -n '22,34p' src/components/containers/vendors/products/product-table-columns.tsx

echo ""
echo "=== Check shop-products-template.tsx import and usage ==="
sed -n '1,30p' src/components/templates/vendor/products/shop-products-template.tsx

echo ""
echo "=== Check product-table.tsx import and usage ==="
sed -n '1,30p' src/components/containers/vendors/products/product-table.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 2439


🏁 Script executed:

#!/bin/bash
# Verify the complete picture - check what use-products.tsx actually uses
echo "=== use-products.tsx mutation state usage ==="
sed -n '20,140p' src/hooks/vendors/use-products.tsx | grep -A 20 "const mutationState"

echo ""
echo "=== Check if types are actually incompatible by structure ==="
echo "Shared type fields: creatingId (required), deletingId (required), updatingId (required), isCreating (boolean), isAnyMutating (boolean)"
echo "Local type fields: all optional, includes togglingId instead"

Repository: FullStack-Flow/shop-stack

Length of output: 1031


🏁 Script executed:

#!/bin/bash
# Final verification - check all the sites that use the local ProductMutationState
echo "=== All usages of local ProductMutationState from product-table-columns ==="
rg -n "ProductMutationState" src/components/templates/vendor/products/shop-products-template.tsx src/components/containers/vendors/products/product-table.tsx

echo ""
echo "=== Confirm the import chain ==="
echo "shop-products-template.tsx imports from product-table-columns:"
grep "ProductMutationState" src/components/templates/vendor/products/shop-products-template.tsx | head -1
echo ""
echo "product-table.tsx imports from product-table-columns:"
grep "ProductMutationState" src/components/containers/vendors/products/product-table.tsx | head -1

Repository: FullStack-Flow/shop-stack

Length of output: 919


Avoid type name collision with existing ProductMutationState.
There's another ProductMutationState defined in src/types/products.ts with a fundamentally different shape (required fields including isCreating and isAnyMutating, no togglingId), creating confusion and risk of accidental mis-imports. Rename this local type to ProductTableMutationState and update its usages in product-table.tsx and shop-products-template.tsx.

♻️ Suggested changes

product-table-columns.tsx:

-export interface ProductMutationState {
+export interface ProductTableMutationState {
   deletingId?: string | null;
   togglingId?: string | null;
   updatingId?: string | null;
   creatingId?: string | null;
 }

 export interface ProductColumnConfig {
   mode?: "vendor" | "customer";
   actions: ProductTableActions;
-  mutationState?: ProductMutationState;
+  mutationState?: ProductTableMutationState;
   isMutating?: (id: string) => boolean;
 }

product-table.tsx (line 10):

-  type ProductMutationState,
+  type ProductTableMutationState,

product-table.tsx (line 22):

-  mutationState?: ProductMutationState;
+  mutationState?: ProductTableMutationState;

shop-products-template.tsx (line 7):

-import type { ProductMutationState } from "@/components/containers/vendors/products/product-table-columns";
+import type { ProductTableMutationState } from "@/components/containers/vendors/products/product-table-columns";

shop-products-template.tsx (line 18):

-  mutationState?: ProductMutationState;
+  mutationState?: ProductTableMutationState;
🤖 Prompt for AI Agents
In `@src/components/containers/vendors/products/product-table-columns.tsx` around
lines 22 - 34, Rename the local type ProductMutationState to
ProductTableMutationState in the product-table-columns.tsx file and update all
usages and imports that reference it (e.g., in ProductColumnConfig, and where
product-table.tsx and shop-products-template.tsx import or refer to
ProductMutationState) so they now import/accept ProductTableMutationState;
ensure you update the type name in product-table.tsx (where the
columns/mutationState props are typed) and in shop-products-template.tsx (where
the component prop types or imports reference the old name) to avoid colliding
with the other ProductMutationState type.

Comment on lines +1 to +3
import CouponHeader from "@/components/containers/shared/coupons/coupon-header";
import { AdminCouponTable } from "@/components/containers/shared/coupons/coupon-table";
import { ADMIN_COUPON_PERMISSIONS } from "@/lib/config/coupon-permissions";

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
# Find all relevant coupon-table files and their exports
echo "=== Looking for coupon-table.tsx files ==="
fd 'coupon-table\.tsx' src/components/containers

echo -e "\n=== Looking for admin-coupon-table.tsx ==="
fd 'admin-coupon-table\.tsx' src/components/containers

echo -e "\n=== Checking exports in shared/coupons/coupon-table.tsx ==="
if [ -f "src/components/containers/shared/coupons/coupon-table.tsx" ]; then
  rg -n "export.*AdminCouponTable" src/components/containers/shared/coupons/coupon-table.tsx
fi

echo -e "\n=== Checking exports in admin/coupons/admin-coupon-table.tsx ==="
if [ -f "src/components/containers/admin/coupons/admin-coupon-table.tsx" ]; then
  rg -n "export.*AdminCouponTable" src/components/containers/admin/coupons/admin-coupon-table.tsx
fi

echo -e "\n=== Checking all files in shared/coupons directory ==="
fd . src/components/containers/shared/coupons --type f

echo -e "\n=== Checking all files in admin/coupons directory ==="
fd . src/components/containers/admin/coupons --type f

Repository: FullStack-Flow/shop-stack

Length of output: 924


🏁 Script executed:

#!/bin/bash
echo "=== Content of shared/coupons/coupon-table.tsx ==="
cat -n src/components/containers/shared/coupons/coupon-table.tsx | head -50

echo -e "\n=== Checking for any AdminCouponTable references in shared/coupons/coupon-table.tsx ==="
rg -i "admincoupontable" src/components/containers/shared/coupons/coupon-table.tsx

Repository: FullStack-Flow/shop-stack

Length of output: 2049


Fix incorrect AdminCouponTable import path.

The import attempts to get AdminCouponTable from @/components/containers/shared/coupons/coupon-table, which exports only VendorCouponTable. The correct source is @/components/containers/admin/coupons/admin-coupon-table.

Fix
-import { AdminCouponTable } from "@/components/containers/shared/coupons/coupon-table";
+import { AdminCouponTable } from "@/components/containers/admin/coupons/admin-coupon-table";

Also applies to: 33-37

🤖 Prompt for AI Agents
In `@src/components/templates/admin/admin-coupons-template.tsx` around lines 1 -
3, The AdminCouponTable import is pointing at the wrong module that only exports
VendorCouponTable; update the import of AdminCouponTable in this file to import
from the correct module
"@/components/containers/admin/coupons/admin-coupon-table" (and update any other
identical imports in this file around the other occurrences) so that the named
export AdminCouponTable is resolved from admin-coupon-table instead of
coupon-table.

@shakibdshy
shakibdshy merged commit 203626f into main Jan 31, 2026
1 check passed
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