Vitron-Front/app/components/store/SizeSelector.tsx
fazli 83b93fe523 feat(seller-editor): 9x9 color grid, mandatory size, two-step category, robust image + discount save
- app/components/store/ColorSelector.tsx: renders as a 9-column grid sorted
  by info.sortOrder. Compact aspect-square swatches; Persian name lives in
  title/aria-label so 81 swatches breathe on mobile. Checkmark ink flips by
  swatch luminance so it stays visible on both سفید and مشکی.
- app/components/store/SizeSelector.tsx + ProductVariantDrawer.tsx: dropped
  the "بدون مشخص کردن سایز" toggle now that "FreeSize" exists in the
  palette. Every product must pick a size (or FreeSize) on save.
- app/routes/store.add.manual.$storeId.tsx:
    - two-step category picker (parent → sub) driven by category.parentId,
      with edit-mode pre-fill deriving the parent from the sub's parentId.
    - getFinalImageUrls now uploads in parallel with concurrency 3, retries
      each image once, persists successful uploads to state as they land so
      a resubmit only retries the still-failing ones. If every upload fails
      we refuse to submit rather than sending an empty gallery.
    - only ship discountPrice when it's a REAL discount (finalNum > 0 and
      < priceNum) — otherwise pass null so the backend clears any stale
      value. Closes the "phantom discount" hole where PricingForm's
      "no discount" radio still shipped the previous typed number.
    - isStep1Valid requires every variant to have a real (non-"unselected")
      size, matching the new mandatory-size rule.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-18 13:19:13 +00:00

82 lines
2.6 KiB
TypeScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { X } from "lucide-react";
import { useSizeAttributes } from "~/requestHandler/use-product-hooks";
interface SizeSelectorProps {
selectedSizes: string[];
onToggleSize: (size: string) => void;
onRemoveSize: (size: string) => void;
}
export const SizeSelector: React.FC<SizeSelectorProps> = ({
selectedSizes,
onToggleSize,
onRemoveSize,
}: SizeSelectorProps) => {
const { data: sizes, isLoading } = useSizeAttributes();
if (isLoading) {
return (
<div className="space-y-4">
<h4 className="font-medium text-right">انتخاب سایز های موجود:</h4>
<div className="grid grid-cols-4 gap-2">
{[...Array(7)].map((_, i) => (
<div
key={i}
className="py-3 px-4 rounded-lg border-2 border-WHITE2 bg-WHITE3 animate-pulse h-12"
/>
))}
</div>
</div>
);
}
return (
<div className="space-y-4">
<h4 className="font-medium text-right">انتخاب سایز های موجود:</h4>
<p className="text-xs text-GRAY text-right">
اگر محصول تکسایز است، «فری سایز» را انتخاب کنید.
</p>
<div className="grid grid-cols-4 gap-2">
{sizes.map((size) => (
<>
{size.value !== "UNSELECTED" && (
<button
key={size.id}
onClick={() => onToggleSize(size.value)}
className={`py-3 px-4 rounded-lg border-2 text-sm font-medium transition-all ${
selectedSizes.includes(size.value)
? "border-blue-500 bg-blue-50 text-blue-700"
: "border-WHITE2 hover:border-GRAY3 hover:bg-WHITE3"
}`}
>
{size.info?.persian || size.value}
</button>
)}
</>
))}
</div>
{/* Selected Sizes */}
{selectedSizes.length > 0 && (
<div className="flex flex-wrap gap-2">
{selectedSizes.map((sizeValue) => {
const sizeInfo = sizes.find((s) => s.value === sizeValue);
return (
<div
key={sizeValue}
className="flex items-center gap-1 bg-blue-100 px-3 py-1 rounded-full text-sm text-blue-700"
>
<span>{sizeInfo?.info?.persian || sizeValue}</span>
<X
onClick={() => onRemoveSize(sizeValue)}
className="w-4 h-4 cursor-pointer text-red-500"
/>
</div>
);
})}
</div>
)}
</div>
);
};