Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,19 @@ import { zodResolver } from '@hookform/resolvers/zod';
import { Trans, useLingui } from '@lingui/react/macro';
import { useMutation } from '@tanstack/react-query';
import { Plus, Save } from 'lucide-react';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { addOptionGroupToProductDocument, createProductOptionGroupDocument } from '../products.graphql.js';
import { OptionGroup, optionGroupSchema, SingleOptionGroupEditor } from './option-groups-editor.js';

export function AddOptionGroupDialog({
productId,
existingGroupNames,
onSuccess,
}: Readonly<{
productId: string;
existingGroupNames: string[];
onSuccess?: () => void;
}>) {
const [open, setOpen] = useState(false);
Expand All @@ -38,6 +40,16 @@ export function AddOptionGroupDialog({
mode: 'onChange',
});

// Reset form when dialog opens
useEffect(() => {
if (open) {
form.reset({
name: '',
values: [],
});
}
}, [open, form]);

const createOptionGroupMutation = useMutation({
mutationFn: api.mutate(createProductOptionGroupDocument),
});
Expand All @@ -48,7 +60,17 @@ export function AddOptionGroupDialog({

const handleSave = async () => {
const formValue = form.getValues();
if (!formValue.name || formValue.values.length === 0) return;
const trimmedName = formValue.name.trim();

if (!trimmedName || formValue.values.length === 0) {
toast.error(t`Please fill in all required fields`);
return;
}
const existingNames = existingGroupNames ?? [];
if (existingNames.some(name => name.toLowerCase() === trimmedName.toLowerCase())) {
toast.error(t`An option group with this name already exists`);
return;
}

try {
const createResult = await createOptionGroupMutation.mutateAsync({
Expand All @@ -57,7 +79,7 @@ export function AddOptionGroupDialog({
translations: [
{
languageCode: 'en',
name: formValue.name,
name: trimmedName,
},
],
options: formValue.values.map(value => ({
Expand All @@ -81,6 +103,7 @@ export function AddOptionGroupDialog({

toast.success(t`Successfully created option group`);
setOpen(false);
form.reset();
onSuccess?.();
} catch (error) {
toast.error(t`Failed to create option group`, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,12 @@ type Variant = NonNullable<ResultOf<typeof productDetailWithVariantsDocument>['p
function AddOptionValueDialog({
groupId,
groupName,
existingValues,
onSuccess,
}: Readonly<{
groupId: string;
groupName: string;
existingValues: string[];
onSuccess?: () => void;
}>) {
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -107,6 +109,14 @@ function AddOptionValueDialog({
});

const onSubmit = (values: AddOptionValueFormValues) => {
const trimmedValue = values.name.trim();

// Prevent duplicates
if (existingValues.includes(trimmedValue)) {
toast.error(t`Option value already exists`);
return;
}

createOptionMutation.mutate({
input: {
productOptionGroupId: groupId,
Expand Down Expand Up @@ -283,15 +293,40 @@ function ManageProductVariants() {
<AddOptionValueDialog
groupId={group.id}
groupName={group.name}
existingValues={group.options.map(o => o.name)}
onSuccess={() => refetch()}
/>
</div>
</div>
<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>
Comment on lines +301 to +322
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.

</div>
))
)}
</div>
<AddOptionGroupDialog productId={id} onSuccess={() => refetch()} />
<AddOptionGroupDialog productId={id}
existingGroupNames={productData.product.optionGroups.map(g => g.name)}
onSuccess={() => refetch()} />
</PageBlock>

<PageBlock column="main" blockId="product-variants" title={<Trans>Variants</Trans>}>
Expand Down Expand Up @@ -329,7 +364,7 @@ function ManageProductVariants() {
<Select
value={
optionsToAddToVariant[variant.id]?.[
group.id
group.id
] || ''
}
onValueChange={value =>
Expand Down Expand Up @@ -365,7 +400,7 @@ function ManageProductVariants() {
}
disabled={
!optionsToAddToVariant[variant.id]?.[
group.id
group.id
]
}
onClick={() => addOptionToVariant(variant)}
Expand Down