Skip to content

fix(dashboard): fix validation issues and improve option group management UX#4360

Open
latifniz wants to merge 7 commits intovendurehq:masterfrom
latifniz:fix/dashboard-add-option-value-ux
Open

fix(dashboard): fix validation issues and improve option group management UX#4360
latifniz wants to merge 7 commits intovendurehq:masterfrom
latifniz:fix/dashboard-add-option-value-ux

Conversation

@latifniz
Copy link

** Description**

While testing variant option group management in the Dashboard, several UX and validation issues were observed that could lead to invalid or confusing product data. This PR fixes these issues in the Dashboard UI.

Affected area: Product Variant Option Groups in the Dashboard React/Admin UI.

Observed Issues

  1. Option groups cannot be deleted

    • Once added, there is no way to remove an option group via the Dashboard UI.
    • Workarounds (e.g., recreating variants or using GraphQL directly) were required.
  2. Previous option group data persists when adding a new group

    • Creating a new option group pre-fills inputs with the previously added group’s name and values.
    • Expected behavior: inputs should be empty for a new group.
  3. Duplicate option values are allowed

    • Duplicate values (e.g., “Small”, “Small”) can be added to a group.
    • This can lead to ambiguous or invalid variant combinations.
    • This PR prevents duplicate values in the Dashboard UI, but the backend may still allow duplicates. The backend should be updated to enforce uniqueness as well.
  4. Empty option group names are allowed

    • The option group name field accepts an empty string.
    • Names should be required and trimmed.

Fixes Implemented

  • Added delete button for option groups with a confirmation dialog.
  • Cleared input fields when creating a new option group.
  • Added UI validation to prevent duplicate option values.
  • Added validation to require non-empty, trimmed option group names.
  • Updated mutation calls and error handling (toast messages) to support UX improvements.

Steps to Test

  1. Open the Dashboard → navigate to a product with variants.

  2. Add a new option group with a name and multiple values.

  3. Test:

    • Removing the option group.
    • Adding another option group.
    • Attempting to add duplicate values.
    • Leaving the option group name empty.
  4. Verify all behave as expected in the UI.


Additional Context

dashboard_group_options.mp4

@vercel
Copy link

vercel bot commented Feb 16, 2026

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

Project Deployment Actions Updated (UTC)
vendure-storybook Ready Ready Preview, Comment Feb 16, 2026 3:50pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 16, 2026

📝 Walkthrough

Walkthrough

The PR adds trimmed-input validation, required-field and duplicate-name checks to AddOptionGroupDialog and AddOptionValueDialog, shows localized error toasts for validation failures, resets dialog form state when opened and after successful creation, threads existing group/value name lists into the dialogs for duplicate detection, triggers creation mutations and appends new groups/values to products on success, and adds UI for removing option groups with confirmation and error handling.

Possibly related issues

  • vendurehq/vendure issue 4325 — Matches: this PR implements duplicate-name/value validation, form reset on open, and an option-group delete control as described in the issue.
🚥 Pre-merge checks | ✅ 2 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Merge Conflict Detection ⚠️ Warning ❌ Merge conflicts detected (282 files):

⚔️ .github/workflows/build_and_test.yml (content)
⚔️ .github/workflows/cla.yml (content)
⚔️ .github/workflows/codegen.yml (content)
⚔️ .github/workflows/publish_and_install.yml (content)
⚔️ .github/workflows/scripts/dashboard-tests.js (content)
⚔️ docs/docs/guides/core-concepts/auth/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/channels/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/collections/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/customers/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/email/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/images-assets/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/money/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/orders/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/payment/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/products/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/promotions/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/shipping/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/stock-control/index.mdx (content)
⚔️ docs/docs/guides/core-concepts/taxes/index.mdx (content)
⚔️ docs/docs/guides/deployment/deploy-to-digital-ocean-app-platform/index.mdx (content)
⚔️ docs/docs/guides/deployment/deploy-to-google-cloud-run/index.mdx (content)
⚔️ docs/docs/guides/deployment/deploy-to-northflank/index.mdx (content)
⚔️ docs/docs/guides/deployment/deploy-to-railway/index.mdx (content)
⚔️ docs/docs/guides/deployment/deploy-to-render/index.mdx (content)
⚔️ docs/docs/guides/deployment/deploying-admin-ui.mdx (content)
⚔️ docs/docs/guides/deployment/getting-data-into-production.mdx (content)
⚔️ docs/docs/guides/deployment/horizontal-scaling.mdx (content)
⚔️ docs/docs/guides/deployment/production-configuration/index.mdx (content)
⚔️ docs/docs/guides/deployment/server-resource-requirements.mdx (content)
⚔️ docs/docs/guides/deployment/using-docker.mdx (content)
⚔️ docs/docs/guides/developer-guide/cache/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/channel-aware/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/cli/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/configuration/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/custom-fields/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/custom-permissions/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/custom-strategies-in-plugins/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/database-entity/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/dataloaders/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/db-subscribers/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/error-handling/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/events/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/extend-graphql-api/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/has-custom-fields/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/importing-data/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/logging/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/migrating-from-v1/breaking-api-changes.mdx (content)
⚔️ docs/docs/guides/developer-guide/migrating-from-v1/database-migration.mdx (content)
⚔️ docs/docs/guides/developer-guide/migrating-from-v1/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/migrating-from-v1/storefront-migration.mdx (content)
⚔️ docs/docs/guides/developer-guide/migrations/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/nest-devtools/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/overview/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/plugins/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/rest-endpoint/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/scheduled-tasks/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/security/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/settings-store/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/stand-alone-scripts/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/strategies-configurable-operations/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/testing/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/the-api-layer/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/the-service-layer/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/translatable/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/translations/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/updating/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/uploading-files/index.mdx (content)
⚔️ docs/docs/guides/developer-guide/worker-job-queue/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/add-actions-to-pages/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/adding-ui-translations/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/admin-ui-theming-branding/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/alerts/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/bulk-actions/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/creating-detail-views/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/creating-list-views/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/custom-data-table-components/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/custom-detail-components/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/custom-form-inputs/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/custom-timeline-components/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/dashboard-widgets/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/defining-routes/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/getting-started/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/nav-menu/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/page-tabs/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/ui-library/index.mdx (content)
⚔️ docs/docs/guides/extending-the-admin-ui/using-other-frameworks/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/alerts/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/creating-pages/detail-pages.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/creating-pages/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/creating-pages/list-pages.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/creating-pages/tabbed-pages.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/custom-form-components/form-component-examples.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/custom-form-components/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/custom-form-components/relation-selectors.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/customizing-pages/action-bar-items.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/customizing-pages/customizing-detail-pages.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/customizing-pages/customizing-list-pages.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/customizing-pages/customizing-login-page.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/customizing-pages/history-entries.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/customizing-pages/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/customizing-pages/insights-widgets.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/customizing-pages/page-blocks.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/data-fetching/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/deployment/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/extending-overview/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/getting-started/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/localization/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/migration/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/navigation/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/tech-stack/index.mdx (content)
⚔️ docs/docs/guides/extending-the-dashboard/theming/index.mdx (content)
⚔️ docs/docs/guides/getting-started/graphql-intro/index.mdx (content)
⚔️ docs/docs/guides/getting-started/installation/index.mdx (content)
⚔️ docs/docs/guides/getting-started/try-the-api/index.mdx (content)
⚔️ docs/docs/guides/how-to/cms-integration-plugin/index.mdx (content)
⚔️ docs/docs/guides/how-to/codegen/index.mdx (content)
⚔️ docs/docs/guides/how-to/configurable-products/index.mdx (content)
⚔️ docs/docs/guides/how-to/digital-products/index.mdx (content)
⚔️ docs/docs/guides/how-to/github-oauth-authentication/index.mdx (content)
⚔️ docs/docs/guides/how-to/google-oauth-authentication/index.mdx (content)
⚔️ docs/docs/guides/how-to/multi-vendor-marketplaces/index.mdx (content)
⚔️ docs/docs/guides/how-to/paginated-list/index.mdx (content)
⚔️ docs/docs/guides/how-to/publish-plugin/index.mdx (content)
⚔️ docs/docs/guides/how-to/s3-asset-storage/index.mdx (content)
⚔️ docs/docs/guides/how-to/telemetry/index.mdx (content)
⚔️ docs/docs/guides/storefront/active-order/index.mdx (content)
⚔️ docs/docs/guides/storefront/checkout-flow/index.mdx (content)
⚔️ docs/docs/guides/storefront/codegen/index.mdx (content)
⚔️ docs/docs/guides/storefront/connect-api/index.mdx (content)
⚔️ docs/docs/guides/storefront/customer-accounts/index.mdx (content)
⚔️ docs/docs/guides/storefront/listing-products/index.mdx (content)
⚔️ docs/docs/guides/storefront/navigation-menu/index.mdx (content)
⚔️ docs/docs/guides/storefront/order-workflow/index.mdx (content)
⚔️ docs/docs/guides/storefront/product-detail/index.mdx (content)
⚔️ docs/docs/guides/storefront/storefront-starters/index.mdx (content)
⚔️ docs/docs/reference/admin-ui-api/action-bar/action-bar-dropdown-menu-item.mdx (content)
⚔️ docs/docs/reference/admin-ui-api/action-bar/action-bar-item.mdx (content)
⚔️ docs/docs/reference/admin-ui-api/action-bar/router-link-definition.mdx (content)
⚔️ docs/docs/reference/admin-ui-api/custom-input-components/register-form-input-component.mdx (content)
⚔️ docs/docs/reference/admin-ui-api/ui-devkit/admin-ui-extension.mdx (content)
⚔️ docs/docs/reference/admin-ui-api/ui-devkit/ui-extension-build-command.mdx (content)
⚔️ docs/docs/reference/admin-ui-api/ui-devkit/ui-extension-compiler-options.mdx (content)
⚔️ docs/docs/reference/admin-ui-api/ui-devkit/ui-extension-compiler-process-argument.mdx (content)
⚔️ docs/docs/reference/core-plugins/elasticsearch-plugin/index.mdx (content)
⚔️ docs/docs/reference/core-plugins/job-queue-plugin/pub-sub-plugin.mdx (content)
⚔️ docs/docs/reference/core-plugins/payments-plugin/braintree-plugin.mdx (content)
⚔️ docs/docs/reference/core-plugins/payments-plugin/mollie-plugin.mdx (content)
⚔️ docs/docs/reference/core-plugins/payments-plugin/stripe-plugin.mdx (content)
⚔️ docs/docs/reference/core-plugins/sentry-plugin/index.mdx (content)
⚔️ docs/docs/reference/core-plugins/stellate-plugin/index.mdx (content)
⚔️ docs/docs/reference/dashboard/detail-views/detail-page.mdx (content)
⚔️ docs/docs/reference/dashboard/extensions-api/form-components.mdx (content)
⚔️ docs/docs/reference/dashboard/list-views/list-page.mdx (content)
⚔️ docs/docs/reference/dashboard/list-views/paginated-list-data-table.mdx (content)
⚔️ docs/docs/reference/dashboard/vite-plugin/vendure-dashboard-plugin.mdx (content)
⚔️ docs/docs/reference/graphql-api/admin/enums.mdx (content)
⚔️ docs/docs/reference/graphql-api/shop/enums.mdx (content)
⚔️ docs/docs/reference/typescript-api/auth/native-authentication-strategy.mdx (content)
⚔️ docs/docs/reference/typescript-api/common/bootstrap.mdx (content)
⚔️ docs/docs/reference/typescript-api/configuration/product-variant-price-update-strategy.mdx (content)
⚔️ docs/docs/reference/typescript-api/data-access/entity-hydrator.mdx (content)
⚔️ docs/docs/reference/typescript-api/orders/order-interceptor.mdx (content)
⚔️ docs/docs/reference/typescript-api/services/order-service.mdx (content)
⚔️ docs/docs/reference/typescript-api/services/promotion-service.mdx (content)
⚔️ docs/docs/reference/typescript-api/tax/address-based-tax-zone-strategy.mdx (content)
⚔️ docs/docs/reference/typescript-api/telemetry/instrument.mdx (content)
⚔️ docs/docs/reference/typescript-api/worker/bootstrap-worker.mdx (content)
⚔️ docs/docs/user-guide/catalog/collections.mdx (content)
⚔️ docs/docs/user-guide/catalog/facets.mdx (content)
⚔️ docs/docs/user-guide/catalog/index.mdx (content)
⚔️ docs/docs/user-guide/catalog/products.mdx (content)
⚔️ docs/docs/user-guide/customers/index.mdx (content)
⚔️ docs/docs/user-guide/index.mdx (content)
⚔️ docs/docs/user-guide/localization/index.mdx (content)
⚔️ docs/docs/user-guide/orders/draft-orders.mdx (content)
⚔️ docs/docs/user-guide/orders/index.mdx (content)
⚔️ docs/docs/user-guide/orders/orders.mdx (content)
⚔️ docs/docs/user-guide/promotions/index.mdx (content)
⚔️ docs/docs/user-guide/settings/administrators-roles.mdx (content)
⚔️ docs/docs/user-guide/settings/channels.mdx (content)
⚔️ docs/docs/user-guide/settings/countries-zones.mdx (content)
⚔️ docs/docs/user-guide/settings/global-settings.mdx (content)
⚔️ docs/docs/user-guide/settings/index.mdx (content)
⚔️ docs/docs/user-guide/settings/payment-methods.mdx (content)
⚔️ docs/docs/user-guide/settings/shipping-methods.mdx (content)
⚔️ docs/docs/user-guide/settings/taxes.mdx (content)
⚔️ docs/package-lock.json (content)
⚔️ docs/src/manifest.ts (content)
⚔️ license/signatures/version1/cla.json (content)
⚔️ packages/admin-ui-plugin/src/constants.ts (content)
⚔️ packages/admin-ui/i18n-coverage.json (content)
⚔️ packages/admin-ui/src/lib/core/src/extension/register-form-input-component.ts (content)
⚔️ packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts (content)
⚔️ packages/admin-ui/src/lib/static/vendure-ui-config.json (content)
⚔️ packages/core/e2e/order-modification.e2e-spec.ts (content)
⚔️ packages/core/src/bootstrap.ts (content)
⚔️ packages/core/src/common/constants.ts (content)
⚔️ packages/core/src/common/index.ts (content)
⚔️ packages/core/src/common/instrument-decorator.ts (content)
⚔️ packages/core/src/config/catalog/default-product-variant-price-update-strategy.ts (content)
⚔️ packages/core/src/config/order/order-interceptor.ts (content)
⚔️ packages/core/src/config/tax/address-based-tax-zone-strategy.ts (content)
⚔️ packages/core/src/service/helpers/entity-hydrator/entity-hydrator.service.ts (content)
⚔️ packages/core/src/service/helpers/order-calculator/order-calculator.ts (content)
⚔️ packages/core/src/service/helpers/order-modifier/order-modifier.ts (content)
⚔️ packages/core/src/service/helpers/product-price-applicator/product-price-applicator.ts (content)
⚔️ packages/core/src/service/services/administrator.service.ts (content)
⚔️ packages/core/src/service/services/order.service.ts (content)
⚔️ packages/core/src/service/services/payment-method.service.ts (content)
⚔️ packages/core/src/service/services/promotion.service.ts (content)
⚔️ packages/core/src/service/services/shipping-method.service.ts (content)
⚔️ packages/create/src/helpers.ts (content)
⚔️ packages/create/templates/vite.config.hbs (content)
⚔️ packages/dashboard/.gitignore (content)
⚔️ packages/dashboard/package.json (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_collections/collections_.$id.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_collections/components/collection-filters-selector.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_orders/components/fulfill-order-dialog.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_orders/components/order-detail-shared.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_payment-methods/components/payment-eligibility-checker-selector.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_payment-methods/components/payment-handler-selector.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_payment-methods/payment-methods_.$id.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_products/components/add-option-group-dialog.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_promotions/components/promotion-actions-selector.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_promotions/components/promotion-conditions-selector.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_promotions/promotions_.$id.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_shipping-methods/components/shipping-calculator-selector.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_shipping-methods/components/shipping-eligibility-checker-selector.tsx (content)
⚔️ packages/dashboard/src/app/routes/_authenticated/_shipping-methods/shipping-methods_.$id.tsx (content)
⚔️ packages/dashboard/src/i18n/locales/ar.po (content)
⚔️ packages/dashboard/src/i18n/locales/bg.po (content)
⚔️ packages/dashboard/src/i18n/locales/cs.po (content)
⚔️ packages/dashboard/src/i18n/locales/de.po (content)
⚔️ packages/dashboard/src/i18n/locales/en.po (content)
⚔️ packages/dashboard/src/i18n/locales/es.po (content)
⚔️ packages/dashboard/src/i18n/locales/fa.po (content)
⚔️ packages/dashboard/src/i18n/locales/fr.po (content)
⚔️ packages/dashboard/src/i18n/locales/he.po (content)
⚔️ packages/dashboard/src/i18n/locales/hr.po (content)
⚔️ packages/dashboard/src/i18n/locales/it.po (content)
⚔️ packages/dashboard/src/i18n/locales/ja.po (content)
⚔️ packages/dashboard/src/i18n/locales/nb.po (content)
⚔️ packages/dashboard/src/i18n/locales/ne.po (content)
⚔️ packages/dashboard/src/i18n/locales/pl.po (content)
⚔️ packages/dashboard/src/i18n/locales/pt_BR.po (content)
⚔️ packages/dashboard/src/i18n/locales/pt_PT.po (content)
⚔️ packages/dashboard/src/i18n/locales/ru.po (content)
⚔️ packages/dashboard/src/i18n/locales/sv.po (content)
⚔️ packages/dashboard/src/i18n/locales/tr.po (content)
⚔️ packages/dashboard/src/i18n/locales/uk.po (content)
⚔️ packages/dashboard/src/i18n/locales/zh_Hans.po (content)
⚔️ packages/dashboard/src/i18n/locales/zh_Hant.po (content)
⚔️ packages/dashboard/src/lib/components/data-input/facet-value-input.tsx (content)
⚔️ packages/dashboard/src/lib/components/shared/configurable-operation-input.tsx (content)
⚔️ packages/dashboard/src/lib/components/shared/configurable-operation-multi-selector.tsx (content)
⚔️ packages/dashboard/src/lib/components/shared/configurable-operation-selector.tsx (content)
⚔️ packages/dashboard/src/lib/framework/form-engine/form-engine-types.ts (content)
⚔️ packages/dashboard/src/lib/framework/form-engine/utils.spec.ts (content)
⚔️ packages/dashboard/src/lib/framework/form-engine/utils.ts (content)
⚔️ packages/dashboard/tsconfig.check.json (content)
⚔️ packages/dashboard/vite.config.mts (content)
⚔️ packages/dashboard/vite/tests/esm-path-alias.spec.ts (content)
⚔️ packages/dashboard/vite/tests/fixtures-esm-path-alias/vendure-config.ts (content)
⚔️ packages/dashboard/vite/utils/compiler.ts (content)
⚔️ packages/dashboard/vite/utils/path-transformer.ts (content)
⚔️ packages/elasticsearch-plugin/README.md (content)
⚔️ packages/elasticsearch-plugin/src/plugin.ts (content)
⚔️ packages/graphiql-plugin/src/ui/main.tsx (content)
⚔️ packages/graphiql-plugin/src/ui/overrides.css (content)
⚔️ packages/graphiql-plugin/src/ui/styles.css (content)
⚔️ packages/job-queue-plugin/README.md (content)
⚔️ packages/job-queue-plugin/src/pub-sub/plugin.ts (content)
⚔️ packages/payments-plugin/README.md (content)
⚔️ packages/payments-plugin/src/braintree/braintree.plugin.ts (content)
⚔️ packages/payments-plugin/src/mollie/mollie.plugin.ts (content)
⚔️ packages/payments-plugin/src/stripe/stripe.plugin.ts (content)
⚔️ packages/sentry-plugin/README.md (content)
⚔️ packages/sentry-plugin/src/sentry-plugin.ts (content)
⚔️ packages/stellate-plugin/README.md (content)
⚔️ packages/stellate-plugin/src/stellate-plugin.ts (content)
⚔️ packages/ui-devkit/src/compiler/types.ts (content)

These conflicts must be resolved before merging into master.
Resolve conflicts locally and push changes to this branch.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main changes: fixing validation issues and improving option group management UX in the dashboard.
Description check ✅ Passed The description provides comprehensive coverage of observed issues, implemented fixes, testing steps, and context, matching the template's requirements well.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
⚔️ Resolve merge conflicts (beta)
  • Auto-commit resolved conflicts to branch fix/dashboard-add-option-value-ux
  • Post resolved changes as copyable diffs in a comment

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

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
packages/dashboard/src/app/routes/_authenticated/_products/components/add-option-group-dialog.tsx (1)

61-95: ⚠️ Potential issue | 🟠 Major

Untrimmed name used in mutation payload.

trimmedName is computed for validation (Lines 63–73), but the mutation on Lines 78, 82, and 86 still uses the original formValue.name (which may contain leading/trailing whitespace). The code and name fields sent to the backend will retain that whitespace.

Proposed fix
         try {
             const createResult = await createOptionGroupMutation.mutateAsync({
                 input: {
-                    code: formValue.name.toLowerCase().replace(/\s+/g, '-'),
+                    code: trimmedName.toLowerCase().replace(/\s+/g, '-'),
                     translations: [
                         {
                             languageCode: 'en',
-                            name: formValue.name,
+                            name: trimmedName,
                         },
                     ],
                     options: formValue.values.map(value => ({
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx (1)

117-140: ⚠️ Potential issue | 🟠 Major

Same untrimmed-name bug as in AddOptionGroupDialog.

trimmedValue is used for the duplicate check (Line 124) but the mutation payload on Lines 132 and 136 still sends the original values.name with potential whitespace.

Also, the duplicate check here uses case-sensitive includes (Line 124), while AddOptionGroupDialog uses case-insensitive comparison. This inconsistency means "Red" and "red" would be treated as duplicates for groups but allowed for values.

Proposed fix
     const onSubmit = (values: AddOptionValueFormValues) => {
         const trimmedValue = values.name.trim();
 
-
         // Prevent duplicates
         const currentValues = existingValues ?? [];
-
-        if (currentValues.includes(trimmedValue)) {
+        if (currentValues.some(v => v.toLowerCase() === trimmedValue.toLowerCase())) {
             toast.error(t`Option value already exists`);
             return;
         }
 
         createOptionMutation.mutate({
             input: {
                 productOptionGroupId: groupId,
-                code: values.name.toLowerCase().replace(/\s+/g, '-'),
+                code: trimmedValue.toLowerCase().replace(/\s+/g, '-'),
                 translations: [
                     {
                         languageCode: 'en',
-                        name: values.name,
+                        name: trimmedValue,
                     },
                 ],
             },
         });
     };
🤖 Fix all issues with AI agents
In
`@packages/dashboard/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx`:
- Around line 120-132: The onKeyDown handler currently uses Number(input) and
defaultValue so invalid (NaN) or fractional quantities can be sent and the input
won't reflect server-side updates; modify the Enter handler in the input tied to
row.original.quantity to validate and sanitize before calling onAdjustLine
(e.g., trim, ensure it's a finite integer >= 0 using parseInt or rounding and
reject non-numeric or fractional values), and prevent calling onAdjustLine when
validation fails; also keep the input in sync after mutations by either making
it controlled (bind value to row.original.quantity) or add a key prop (e.g.,
key={`qty-${row.original.id}-${row.original.quantity}`}) to force remount when
quantity changes.

In
`@packages/dashboard/src/app/routes/_authenticated/_products/products_`.$id_.variants.tsx:
- Line 337: The line rendering AddOptionGroupDialog is longer than the
110-character Prettier limit; split the JSX props onto multiple lines so each
prop is on its own line (or wrapped sensibly) to shorten the line length. Locate
the AddOptionGroupDialog usage and break out productId={id},
existingGroupNames={productData.product.optionGroups.map(g => g.name)}, and
onSuccess={() => refetch()} onto separate lines (or wrap the map expression
across lines) so the full JSX element conforms to the 110-character rule.
🧹 Nitpick comments (3)
packages/dashboard/src/app/routes/_authenticated/_products/components/add-option-group-dialog.tsx (1)

43-51: form in the dependency array may cause unnecessary resets.

useForm returns a new object reference on every render in some React Hook Form versions, which would re-trigger this effect and reset the form while the user is typing. Consider using form.reset directly (stable ref) or moving the reset into the onOpenChange callback instead.

Safer alternative
-    useEffect(() => {
-        if (open) {
-            form.reset({
-                name: '',
-                values: [],
-            });
-        }
-    }, [open, form]);
+    useEffect(() => {
+        if (open) {
+            form.reset({
+                name: '',
+                values: [],
+            });
+        }
+        // eslint-disable-next-line react-hooks/exhaustive-deps
+    }, [open]);
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx (2)

96-100: Same form dependency concern as the other dialog.

Same recommendation as in AddOptionGroupDialog: consider dropping form from the dep array to avoid potential re-reset on every render.


311-332: Delete handler: consider disabling button while mutation is pending.

The delete button doesn't guard against double-clicks while removeOptionGroupMutation is in progress. A rapid second click would fire another confirm + mutation call.

Proposed fix
                                     <Button
                                         size="icon"
                                         variant="ghost"
+                                        disabled={removeOptionGroupMutation.isPending}
                                         onClick={() => {

Comment on lines 120 to 132
defaultValue={row.original.quantity}
placeholder="1"
onKeyDown={e => {
if (e.key !== 'Enter') return;
const input = e.currentTarget.value.trim();
const quantity = Number(input);

onAdjustLine({
lineId: row.original.id,
quantity: value,
quantity,
customFields: row.original.customFields,
});
}}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing input validation — NaN and fractional quantities can be submitted.

Number(input) can produce NaN (e.g. non-numeric text) or fractional values (e.g. 1.5), both of which are sent straight to onAdjustLine. Guard against invalid values before dispatching.

Additionally, switching from controlled value to defaultValue means the input won't reflect server-side quantity changes after a successful mutation (e.g. if the backend clamps or rejects the value). Consider using a key prop tied to the quantity to force re-render, or reverting to a controlled input.

Proposed fix for input validation
 onKeyDown={e => {
     if (e.key !== 'Enter') return;
     const input = e.currentTarget.value.trim();
-    const quantity = Number(input);
+    const quantity = parseInt(input, 10);
+    if (isNaN(quantity) || quantity < 0) return;

     onAdjustLine({
         lineId: row.original.id,
         quantity,
         customFields: row.original.customFields,
     });
 }}

To keep defaultValue in sync after mutations, add a key to force remount:

 <Input
+    key={`${row.original.id}-${row.original.quantity}`}
     type="number"
     min={0}
     defaultValue={row.original.quantity}
🤖 Prompt for AI Agents
In
`@packages/dashboard/src/app/routes/_authenticated/_orders/components/edit-order-table.tsx`
around lines 120 - 132, The onKeyDown handler currently uses Number(input) and
defaultValue so invalid (NaN) or fractional quantities can be sent and the input
won't reflect server-side updates; modify the Enter handler in the input tied to
row.original.quantity to validate and sanitize before calling onAdjustLine
(e.g., trim, ensure it's a finite integer >= 0 using parseInt or rounding and
reject non-numeric or fractional values), and prevent calling onAdjustLine when
validation fails; also keep the input in sync after mutations by either making
it controlled (bind value to row.original.quantity) or add a key prop (e.g.,
key={`qty-${row.original.id}-${row.original.quantity}`}) to force remount when
quantity changes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx (1)

117-141: ⚠️ Potential issue | 🟠 Major

Bug: trimmed value is not used in the mutation payload.

trimmedValue is computed on line 118 and used for the duplicate check, but the mutation on lines 129–140 still sends the raw values.name (potentially with leading/trailing whitespace) for both code and translations[].name. This means:

  1. A user can submit " Red " — it passes the duplicate check against "Red", but the server receives the untrimmed string.
  2. The code is derived from the untrimmed value, producing a leading/trailing-hyphen code.

Also, the duplicate check is case-sensitive, so "Red" and "red" are not detected as duplicates.

Proposed fix
     const onSubmit = (values: AddOptionValueFormValues) => {
         const trimmedValue = values.name.trim();
 
+        if (!trimmedValue) {
+            toast.error(t`Option value name is required`);
+            return;
+        }
 
         // Prevent duplicates
-        const currentValues = existingValues ?? [];
-
-        if (currentValues.includes(trimmedValue)) {
+        if (existingValues.some(v => v.toLowerCase() === trimmedValue.toLowerCase())) {
             toast.error(t`Option value already exists`);
             return;
         }
 
         createOptionMutation.mutate({
             input: {
                 productOptionGroupId: groupId,
-                code: values.name.toLowerCase().replace(/\s+/g, '-'),
+                code: trimmedValue.toLowerCase().replace(/\s+/g, '-'),
                 translations: [
                     {
                         languageCode: 'en',
-                        name: values.name,
+                        name: trimmedValue,
                     },
                 ],
             },
         });
     };
🧹 Nitpick comments (1)
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx (1)

311-332: Multiple lines exceed the 110-character Prettier limit.

Lines 316, 321–322 are well beyond the configured line length. Consider extracting the click handler into a named function to improve readability and comply with the formatting guideline.

Proposed refactor
+                                    <div className="col-span-1 flex justify-end">
+                                        <Button
+                                            size="icon"
+                                            variant="ghost"
+                                            onClick={() => {
+                                                const msg = t`Are you sure you want to remove this option group? This will affect all variants using this option.`;
+                                                if (confirm(msg)) {
+                                                    removeOptionGroupMutation.mutate(
+                                                        {
+                                                            productId: id,
+                                                            optionGroupId: group.id,
+                                                        },
+                                                        {
+                                                            onError: (error) => {
+                                                                toast.error(
+                                                                    t`Failed to remove option group`,
+                                                                    {
+                                                                        description:
+                                                                            error instanceof Error
+                                                                                ? error.message
+                                                                                : t`Unknown error`,
+                                                                    },
+                                                                );
+                                                            },
+                                                        },
+                                                    );
+                                                }
+                                            }}
+                                        >
+                                            <Trash2 className="h-4 w-4 text-destructive" />
+                                        </Button>
+                                    </div>

As per coding guidelines, "Enforce line length of 110 characters (Prettier)".

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

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

⚠️ Outside diff range comments (1)
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx (1)

111-133: ⚠️ Potential issue | 🟠 Major

Bug: trimmed value used for duplicate check but untrimmed value sent in mutation.

trimmedValue is computed on line 112 and used for the duplicate check, but the mutation on lines 125 and 129 still uses the raw values.name. A user typing " Red " would pass the duplicate check against "Red" but submit " Red " as the name.

Additionally, the duplicate comparison is case-sensitive, so "Red" and "red" won't be caught.

Proposed fix
     const onSubmit = (values: AddOptionValueFormValues) => {
         const trimmedValue = values.name.trim();
 
         // Prevent duplicates
         const currentValues = existingValues ?? [];
 
-        if (currentValues.includes(trimmedValue)) {
+        if (currentValues.some(v => v.toLowerCase() === trimmedValue.toLowerCase())) {
             toast.error(t`Option value already exists`);
             return;
         }
 
         createOptionMutation.mutate({
             input: {
                 productOptionGroupId: groupId,
-                code: values.name.toLowerCase().replace(/\s+/g, '-'),
+                code: trimmedValue.toLowerCase().replace(/\s+/g, '-'),
                 translations: [
                     {
                         languageCode: 'en',
-                        name: values.name,
+                        name: trimmedValue,
                     },
                 ],
             },
         });
     };
🧹 Nitpick comments (3)
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx (3)

304-325: Extract the inline handler and fix line-length violations.

Several lines in this block (notably lines 309 and 314–315) exceed the 110-character Prettier limit. The dense inline onClick handler with a nested confirm, mutate, and onError callback is hard to scan. Consider extracting it into a named function.

Proposed refactor
+    const removeOptionGroup = (groupId: string) => {
+        if (
+            confirm(
+                t`Are you sure you want to remove this option group? This will affect all variants using this option.`,
+            )
+        ) {
+            removeOptionGroupMutation.mutate(
+                { productId: id, optionGroupId: groupId },
+                {
+                    onError: (error) => {
+                        toast.error(t`Failed to remove option group`, {
+                            description:
+                                error instanceof Error
+                                    ? error.message
+                                    : t`Unknown error`,
+                        });
+                    },
+                },
+            );
+        }
+    };

Then in the JSX:

                                     <div className="col-span-1 flex justify-end">
                                         <Button
                                             size="icon"
                                             variant="ghost"
-                                            onClick={() => {
-                                                if (confirm(t`Are you sure you want to remove this option group? This will affect all variants using this option.`)) {
-                                                    removeOptionGroupMutation.mutate(
-                                                        { productId: id, optionGroupId: group.id },
-                                                        {
-                                                            onError: (error) => {
-                                                                toast.error(t`Failed to remove option group`, {
-                                                                    description: error instanceof Error ? error.message : t`Unknown error`,
-                                                                });
-                                                            }
-                                                        }
-                                                    );
-                                                }
-                                            }}
+                                            onClick={() => removeOptionGroup(group.id)}
                                         >

As per coding guidelines, "Enforce line length of 110 characters (Prettier)".


136-138: Nit: trailing whitespace in Dialog opening tag.

Line 137 has a trailing space before > and line 138 is blank — both appear to be accidental.

-        <Dialog open={open} onOpenChange={setOpen} >
-
+        <Dialog open={open} onOpenChange={setOpen}>

114-115: Unnecessary fallback — existingValues is always an array.

existingValues is typed as string[] (line 83) and always passed as group.options.map(o => o.name) (line 299), so the ?? [] fallback on line 115 is dead code. Removing it keeps things clear.

-        const currentValues = existingValues ?? [];
-
-        if (currentValues.includes(trimmedValue)) {
+        if (existingValues.includes(trimmedValue)) {

@latifniz latifniz changed the title Fix/dashboard add option value ux fix(dashboard): fix validation issues and improve option group management UX Feb 16, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (2)
packages/dashboard/src/app/routes/_authenticated/_products/components/add-option-group-dialog.tsx (1)

76-78: ⚠️ Potential issue | 🟡 Minor

Bug: code field uses untrimmed name.

Line 78 generates the code from formValue.name (untrimmed), but the display name on line 82 correctly uses trimmedName. A name like " Size " would produce code "-size-" with leading/trailing dashes.

Proposed fix
-                    code: formValue.name.toLowerCase().replace(/\s+/g, '-'),
+                    code: trimmedName.toLowerCase().replace(/\s+/g, '-'),
packages/dashboard/src/app/routes/_authenticated/_products/products_.$id_.variants.tsx (1)

111-131: ⚠️ Potential issue | 🟡 Minor

Inconsistency: duplicate check is case-sensitive here but case-insensitive in AddOptionGroupDialog.

Line 115 uses existingValues.includes(trimmedValue) (exact match), while the group-name duplicate check in add-option-group-dialog.tsx line 70 uses .toLowerCase() comparison. A user could add "Red" and "red" as separate option values.

Also, values.name on lines 123 and 127 should use trimmedValue for consistency with the trimming on line 112.

Proposed fix
     const onSubmit = (values: AddOptionValueFormValues) => {
         const trimmedValue = values.name.trim();
 
         // Prevent duplicates
-        if (existingValues.includes(trimmedValue)) {
+        if (existingValues.some(v => v.toLowerCase() === trimmedValue.toLowerCase())) {
             toast.error(t`Option value already exists`);
             return;
         }
 
         createOptionMutation.mutate({
             input: {
                 productOptionGroupId: groupId,
-                code: values.name.toLowerCase().replace(/\s+/g, '-'),
+                code: trimmedValue.toLowerCase().replace(/\s+/g, '-'),
                 translations: [
                     {
                         languageCode: 'en',
-                        name: values.name,
+                        name: trimmedValue,
                     },
                 ],
             },
         });
     };
🤖 Fix all issues with AI agents
In
`@packages/dashboard/src/app/routes/_authenticated/_products/products_`.$id_.variants.tsx:
- Around line 301-322: The click handler passed to the Button violates the
110-character line length rule; extract it into a named function (e.g.,
handleRemoveOptionGroup) inside the component and call that function from
onClick to reduce line length and improve readability. In the new
handleRemoveOptionGroup function reference id, group.id and
removeOptionGroupMutation.mutate, perform the confirm check and the mutate call
with the same onError toast logic (using error instanceof Error ? error.message
: t`Unknown error`) so behavior remains identical, then replace the inline arrow
function in the Button's onClick with a call to handleRemoveOptionGroup.
🧹 Nitpick comments (1)
packages/dashboard/src/app/routes/_authenticated/_products/components/add-option-group-dialog.tsx (1)

85-93: Option value names are not trimmed before sending to the API.

The option value code (line 86) and name (line 90) both use value.value as-is. If users enter values with leading/trailing whitespace, those will be persisted untrimmed — and the duplicate check in AddOptionValueDialog (which trims before comparing) won't catch them on a future attempt.

Consider trimming consistently:

Proposed fix
                    options: formValue.values.map(value => ({
-                        code: value.value.toLowerCase().replace(/\s+/g, '-'),
+                        code: value.value.trim().toLowerCase().replace(/\s+/g, '-'),
                        translations: [
                            {
                                languageCode: 'en',
-                                name: value.value,
+                                name: value.value.trim(),
                            },
                        ],
                    })),

Comment on lines +301 to +322
<div className="col-span-1 flex justify-end">
<Button
size="icon"
variant="ghost"
onClick={() => {
if (confirm(t`Are you sure you want to remove this option group? This will affect all variants using this option.`)) {
removeOptionGroupMutation.mutate(
{ productId: id, optionGroupId: group.id },
{
onError: (error) => {
toast.error(t`Failed to remove option group`, {
description: error instanceof Error ? error.message : t`Unknown error`,
});
}
}
);
}
}}
>
<Trash2 className="h-4 w-4 text-destructive" />
</Button>
</div>
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Several lines exceed the 110-character Prettier limit.

Lines 306, 311–312 are well over the configured line length. Extract the click handler to a named function to improve readability and comply with the line-length guideline.

Proposed fix
                                     <div className="col-span-1 flex justify-end">
                                         <Button
                                             size="icon"
                                             variant="ghost"
-                                            onClick={() => {
-                                                if (confirm(t`Are you sure you want to remove this option group? This will affect all variants using this option.`)) {
-                                                    removeOptionGroupMutation.mutate(
-                                                        { productId: id, optionGroupId: group.id },
-                                                        {
-                                                            onError: (error) => {
-                                                                toast.error(t`Failed to remove option group`, {
-                                                                    description: error instanceof Error ? error.message : t`Unknown error`,
-                                                                });
-                                                            }
-                                                        }
-                                                    );
-                                                }
-                                            }}
+                                            onClick={() => {
+                                                const msg = t`Are you sure you want to remove this option group? This will affect all variants using this option.`;
+                                                if (confirm(msg)) {
+                                                    removeOptionGroupMutation.mutate(
+                                                        {
+                                                            productId: id,
+                                                            optionGroupId: group.id,
+                                                        },
+                                                        {
+                                                            onError: (error) => {
+                                                                toast.error(
+                                                                    t`Failed to remove option group`,
+                                                                    {
+                                                                        description:
+                                                                            error instanceof Error
+                                                                                ? error.message
+                                                                                : t`Unknown error`,
+                                                                    },
+                                                                );
+                                                            },
+                                                        },
+                                                    );
+                                                }
+                                            }}
                                         >

As per coding guidelines, "Enforce line length of 110 characters (Prettier)".

🤖 Prompt for AI Agents
In
`@packages/dashboard/src/app/routes/_authenticated/_products/products_`.$id_.variants.tsx
around lines 301 - 322, The click handler passed to the Button violates the
110-character line length rule; extract it into a named function (e.g.,
handleRemoveOptionGroup) inside the component and call that function from
onClick to reduce line length and improve readability. In the new
handleRemoveOptionGroup function reference id, group.id and
removeOptionGroupMutation.mutate, perform the confirm check and the mutate call
with the same onError toast logic (using error instanceof Error ? error.message
: t`Unknown error`) so behavior remains identical, then replace the inline arrow
function in the Button's onClick with a call to handleRemoveOptionGroup.

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