{
  "name": "product-variant-04-block",
  "type": "registry:block",
  "dependencies": [
    "lucide-react"
  ],
  "registryDependencies": [
    "button",
    "https://ui.stackzero.co/r/image-viewer-basic.json",
    "https://ui.stackzero.co/r/price-format-basic.json",
    "https://ui.stackzero.co/r/variant-selector-basic.json",
    "https://ui.stackzero.co/r/variant-selector-multiple.json",
    "https://ui.stackzero.co/r/star-rating-fractions.json",
    "https://ui.stackzero.co/r/quantity-input-basic.json"
  ],
  "files": [
    {
      "type": "registry:block",
      "content": "\"use client\";\n\nimport ImageViewer from \"@/components/commerce-ui/image-viewer-basic\";\nimport PriceFormat from \"@/components/commerce-ui/price-format-basic\";\nimport QuantityInputBasic from \"@/components/commerce-ui/quantity-input-basic\";\nimport StarRating_Fractions from \"@/components/commerce-ui/star-rating-fractions\";\nimport VariantSelectorBasic, {\n  VariantItem as BaseVariantItem,\n} from \"@/components/commerce-ui/variant-selector-basic\";\nimport VariantSelectorMultiple from \"@/components/commerce-ui/variant-selector-multiple\";\nimport { Button } from \"@/components/ui/button\";\nimport {\n  Check,\n  ChevronLeft,\n  ChevronRight,\n  MessageSquareText,\n} from \"lucide-react\";\nimport { useState } from \"react\";\n\ninterface StorageVariant extends BaseVariantItem {\n  price: number;\n  salePrice?: number;\n  isInStock?: boolean;\n  availableQuantity?: number | null;\n}\n\ninterface AccessoryVariant extends BaseVariantItem {\n  price: number;\n  salePrice?: number;\n}\n\ninterface Testimonial {\n  name: string;\n  role?: string;\n  rating: number;\n  text: string;\n  date?: string;\n}\n\ninterface VariantSelectionPayload {\n  storageVariantId: string;\n  storageVariantLabel: string;\n  storagePrice: number;\n  accessoryIds: string[];\n  accessoryLabels: string[];\n  accessoriesPrice: number;\n  quantity: number;\n  totalPrice: number;\n}\n\ninterface ProductVariant04Props {\n  imageUrl?: string;\n  title?: string;\n  description?: string;\n  badge?: string | null;\n  features?: string[];\n  storageVariants: StorageVariant[];\n  accessoryVariants?: AccessoryVariant[];\n  testimonials?: Testimonial[];\n  initialStorage?: string;\n  initialAccessories?: string[];\n  defaultImage?: string;\n  onAddToCart?: (payload: VariantSelectionPayload) => void;\n  onBuyNow?: (payload: VariantSelectionPayload) => void;\n  selectedStorage?: string;\n  onStorageChange?: (storage: string) => void;\n  selectedAccessories?: string[];\n  onAccessoriesChange?: (accessories: string[]) => void;\n  quantity?: number;\n  onQuantityChange?: (quantity: number) => void;\n  isLoading?: boolean;\n  errorMessage?: string | null;\n  currencyPrefix?: string;\n}\n\nfunction ProductVariant_04({\n  accessoryVariants = [],\n  badge = \"In Stock\",\n  currencyPrefix = \"$\",\n  defaultImage,\n  description = \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\",\n  errorMessage = null,\n  features = [\"Feature 1\", \"Feature 2\", \"Feature 3\", \"Feature 4\"],\n  imageUrl = defaultImage,\n  initialAccessories = [],\n  initialStorage,\n  isLoading = false,\n  onAccessoriesChange,\n  onAddToCart = () => {},\n  onBuyNow = () => {},\n  onQuantityChange,\n  onStorageChange,\n  quantity: controlledQuantity,\n  selectedAccessories: controlledAccessories,\n  selectedStorage: controlledStorage,\n  storageVariants = [],\n  testimonials = [\n    {\n      date: \"3 weeks ago\",\n      name: \"John Doe\",\n      rating: 5,\n      role: \"Role\",\n      text: \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\",\n    },\n    {\n      date: \"2 weeks ago\",\n      name: \"John Doe\",\n      rating: 4,\n      role: \"Role\",\n      text: \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\",\n    },\n    {\n      date: \"1 week ago\",\n      name: \"John Doe\",\n      rating: 4.5,\n      role: \"Role\",\n      text: \"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.\",\n    },\n  ],\n  title = \"Product Title\",\n}: ProductVariant04Props) {\n  // Ensure storage variants array is not empty\n  if (!storageVariants.length) {\n    throw new Error(\"At least one storage variant must be provided\");\n  }\n\n  const defaultInitialStorage = initialStorage || storageVariants[0].value;\n\n  const [activeTestimonial, setActiveTestimonial] = useState(0);\n  const [internalSelectedStorage, setInternalSelectedStorage] = useState(\n    defaultInitialStorage\n  );\n  const [internalSelectedAccessories, setInternalSelectedAccessories] =\n    useState<string[]>(initialAccessories);\n  const [internalQuantity, setInternalQuantity] = useState(1);\n\n  // Determine if we're in controlled or uncontrolled mode\n  const isStorageControlled = controlledStorage !== undefined;\n  const isAccessoriesControlled = controlledAccessories !== undefined;\n  const isQuantityControlled = controlledQuantity !== undefined;\n\n  const selectedStorageId = isStorageControlled\n    ? controlledStorage\n    : internalSelectedStorage;\n  const selectedAccessoryIds = isAccessoriesControlled\n    ? controlledAccessories\n    : internalSelectedAccessories;\n  const quantity = isQuantityControlled ? controlledQuantity : internalQuantity;\n\n  const handleStorageChange = (newStorage: string) => {\n    if (isStorageControlled) {\n      onStorageChange?.(newStorage);\n    } else {\n      setInternalSelectedStorage(newStorage);\n    }\n  };\n\n  const handleAccessoriesChange = (newAccessories: string[]) => {\n    if (isAccessoriesControlled) {\n      onAccessoriesChange?.(newAccessories);\n    } else {\n      setInternalSelectedAccessories(newAccessories);\n    }\n  };\n\n  const handleQuantityChange = (newQuantity: number) => {\n    if (isQuantityControlled) {\n      onQuantityChange?.(newQuantity);\n    } else {\n      setInternalQuantity(newQuantity);\n    }\n  };\n\n  const selectedStorage =\n    storageVariants.find((v) => v.value === selectedStorageId) ||\n    storageVariants[0];\n\n  const isInStock =\n    selectedStorage.isInStock !== undefined ? selectedStorage.isInStock : true;\n  const availableQuantity = selectedStorage.availableQuantity;\n  const storagePrice = selectedStorage.price;\n  const accessoriesPrice = selectedAccessoryIds.reduce((total, accId) => {\n    const accessory = accessoryVariants.find((a) => a.value === accId);\n    return total + (accessory?.price || 0);\n  }, 0);\n\n  const itemPrice = storagePrice + accessoriesPrice;\n  const totalPrice = itemPrice * quantity;\n\n  const handleAddToCart = () => {\n    onAddToCart({\n      accessoriesPrice,\n      accessoryIds: selectedAccessoryIds,\n      accessoryLabels: selectedAccessoryIds.map((id) => {\n        const accessory = accessoryVariants.find((a) => a.value === id);\n        return accessory?.label || \"\";\n      }),\n      quantity,\n      storagePrice,\n      storageVariantId: selectedStorageId,\n      storageVariantLabel: selectedStorage?.label || \"\",\n      totalPrice,\n    });\n  };\n\n  const handleBuyNow = () => {\n    onBuyNow({\n      accessoriesPrice,\n      accessoryIds: selectedAccessoryIds,\n      accessoryLabels: selectedAccessoryIds.map((id) => {\n        const accessory = accessoryVariants.find((a) => a.value === id);\n        return accessory?.label || \"\";\n      }),\n      quantity,\n      storagePrice,\n      storageVariantId: selectedStorageId,\n      storageVariantLabel: selectedStorage?.label || \"\",\n      totalPrice,\n    });\n  };\n\n  if (errorMessage) {\n    return (\n      <div className=\"my-6 rounded-lg border border-red-200 bg-red-50 p-6 text-red-600 dark:border-red-900 dark:bg-red-900/20 dark:text-red-400\">\n        <p className=\"font-medium\">Error loading product</p>\n        <p className=\"text-sm\">{errorMessage}</p>\n      </div>\n    );\n  }\n\n  // Add visual indicator for out of stock items in storage selector\n  const storageVariantsWithStockIndicator = storageVariants.map((variant) => {\n    const isVariantInStock =\n      variant.isInStock !== undefined ? variant.isInStock : true;\n    return {\n      ...variant,\n      disabled: !isVariantInStock,\n      label: variant.label + (isVariantInStock ? \"\" : \" (Out of Stock)\"),\n    };\n  });\n\n  return (\n    <div className=\"flex max-w-screen-xl flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-sm dark:border-gray-800 dark:bg-gray-900\">\n      {/* Main product card */}\n      <div className=\"group align-start flex flex-col sm:h-auto sm:flex-row\">\n        <div className=\"relative h-52 w-full bg-gradient-to-br from-gray-50 via-green-50 to-emerald-50 sm:h-[350px] sm:w-2/5 dark:from-gray-900 dark:via-green-950/10 dark:to-emerald-950/10\">\n          <div className=\"absolute inset-0\">\n            <div className=\"absolute -top-10 -right-10 h-32 w-32 rounded-full bg-emerald-300/20 blur-2xl\"></div>\n            <div className=\"absolute -bottom-8 left-0 h-28 w-28 rounded-full bg-green-400/20 blur-xl\"></div>\n          </div>\n\n          {/* Product label */}\n          {badge && (\n            <div className=\"absolute top-3 left-3 z-10\">\n              <div className=\"rounded-md bg-emerald-500 px-2 py-1 text-xs font-bold tracking-wider text-white uppercase shadow-md\">\n                {badge}\n              </div>\n            </div>\n          )}\n\n          {/* Image */}\n          <div className=\"flex h-full items-center justify-center p-4\">\n            {isLoading ? (\n              <div className=\"flex h-full items-center justify-center\">\n                <div className=\"h-10 w-10 animate-spin rounded-full border-4 border-emerald-200 border-t-emerald-600\"></div>\n              </div>\n            ) : (\n              <ImageViewer\n                imageUrl={imageUrl || \"\"}\n                classNameThumbnailViewer=\"rounded-lg h-full object-contain drop-shadow-lg max-h-[300px]\"\n              />\n            )}\n          </div>\n        </div>\n\n        {/* Content section */}\n        <div className=\"flex flex-1 flex-col justify-between p-5\">\n          <div>\n            {/* Product name, price and description */}\n            <div className=\"mb-3\">\n              <div className=\"flex items-center justify-between\">\n                <h3 className=\"text-xl font-bold text-gray-900 dark:text-white\">\n                  {title}\n                </h3>\n                <PriceFormat\n                  prefix={currencyPrefix}\n                  value={itemPrice}\n                  className=\"text-xl font-bold text-emerald-600 dark:text-emerald-400\"\n                />\n              </div>\n              <p className=\"mt-1 line-clamp-2 text-sm text-gray-600 dark:text-gray-400\">\n                {description}\n              </p>\n            </div>\n\n            {/* Variants selection */}\n            <div className=\"mb-4 space-y-4\">\n              <div>\n                <label className=\"mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300\">\n                  Storage\n                </label>\n                <VariantSelectorBasic\n                  value={selectedStorageId}\n                  onValueChange={handleStorageChange}\n                  variants={storageVariantsWithStockIndicator}\n                  itemClassName=\"bg-gray-50 border-gray-200 hover:border-emerald-300 dark:bg-gray-800 dark:border-gray-700\n                              data-[state=checked]:border-emerald-500 data-[state=checked]:bg-emerald-50 \n                              data-[state=checked]:text-emerald-700 dark:data-[state=checked]:bg-gray-700 \n                              dark:data-[state=checked]:border-emerald-500 dark:data-[state=checked]:text-emerald-300\n                              focus:ring-2 focus:ring-emerald-500/50 focus:ring-offset-2 focus:border-emerald-400\n                              dark:focus:ring-emerald-500/40 dark:focus:ring-offset-gray-900\"\n                />\n              </div>\n\n              {accessoryVariants.length > 0 && (\n                <div>\n                  <label className=\"mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300\">\n                    Accessories\n                  </label>\n                  <VariantSelectorMultiple\n                    values={selectedAccessoryIds}\n                    onValuesChange={handleAccessoriesChange}\n                    variants={accessoryVariants}\n                    className=\"flex-wrap\"\n                    itemClassName=\"bg-gray-50 border-gray-200 hover:border-emerald-300 dark:bg-gray-800 dark:border-gray-700\n                                data-[state=on]:border-emerald-500 data-[state=on]:bg-emerald-50 \n                                data-[state=on]:text-emerald-700 dark:data-[state=on]:bg-gray-700 \n                                dark:data-[state=on]:border-emerald-500 dark:data-[state=on]:text-emerald-300\n                                focus:ring-2 focus:ring-emerald-500/50 focus:ring-offset-2\n                                dark:focus:ring-emerald-500/40 dark:focus:ring-offset-gray-900\"\n                  />\n                </div>\n              )}\n            </div>\n\n            {/* Accessories details if selected */}\n            {selectedAccessoryIds.length > 0 && (\n              <div className=\"mb-4 rounded-md bg-emerald-50 p-3 dark:bg-emerald-950/20\">\n                <h4 className=\"mb-2 text-sm font-medium text-gray-800 dark:text-gray-200\">\n                  Selected accessories:\n                </h4>\n                <ul className=\"space-y-1\">\n                  {selectedAccessoryIds.map((accId) => {\n                    const accessory = accessoryVariants.find(\n                      (a) => a.value === accId\n                    );\n                    return (\n                      <li\n                        key={accId}\n                        className=\"flex items-center justify-between text-sm\"\n                      >\n                        <span className=\"text-gray-700 dark:text-gray-300\">\n                          {accessory?.label}\n                        </span>\n                        <PriceFormat\n                          prefix={currencyPrefix}\n                          value={accessory?.price || 0}\n                          className=\"text-emerald-600 dark:text-emerald-400\"\n                        />\n                      </li>\n                    );\n                  })}\n                </ul>\n              </div>\n            )}\n\n            {/* Stock status */}\n            {isInStock ? (\n              availableQuantity !== null &&\n              availableQuantity !== undefined && (\n                <div className=\"mb-3 rounded-md bg-green-50 p-2 text-green-800 dark:bg-green-900/20 dark:text-green-300\">\n                  <p className=\"text-sm font-bold\">\n                    In Stock: {availableQuantity} available\n                  </p>\n                </div>\n              )\n            ) : (\n              <div className=\"mb-3 rounded-md bg-amber-50 p-2 text-amber-800 dark:bg-amber-900/20 dark:text-amber-300\">\n                <p className=\"text-sm font-bold\">Currently out of stock</p>\n              </div>\n            )}\n\n            {/* Quantity selection */}\n            <div className=\"mb-4\">\n              <label className=\"mb-2 block text-sm font-medium text-gray-700 dark:text-gray-300\">\n                Quantity\n              </label>\n              <div className=\"flex items-center justify-between\">\n                <QuantityInputBasic\n                  quantity={quantity}\n                  onChange={handleQuantityChange}\n                  max={\n                    availableQuantity !== null &&\n                    availableQuantity !== undefined\n                      ? availableQuantity\n                      : 10\n                  }\n                  className=\"w-[120px] border-gray-300 dark:border-gray-700\"\n                  disabled={!isInStock}\n                />\n                <div className=\"text-sm font-medium\">\n                  Total:{\" \"}\n                  <PriceFormat prefix={currencyPrefix} value={totalPrice} />\n                </div>\n              </div>\n            </div>\n\n            {/* Features list */}\n            <div className=\"mb-3\">\n              <h4 className=\"mb-2 font-medium text-gray-800 dark:text-gray-200\">\n                Key Features\n              </h4>\n              <ul className=\"grid grid-cols-2 gap-x-4 gap-y-1\">\n                {features.map((feature, index) => (\n                  <li key={index} className=\"flex items-center text-sm\">\n                    <Check className=\"mr-1.5 h-4 w-4 text-emerald-500\" />\n                    <span className=\"text-gray-700 dark:text-gray-300\">\n                      {feature}\n                    </span>\n                  </li>\n                ))}\n              </ul>\n            </div>\n          </div>\n\n          {/* Action buttons */}\n          <div className=\"flex space-x-2\">\n            <Button\n              variant=\"outline\"\n              onClick={handleAddToCart}\n              className=\"flex-1 border-gray-300 bg-white text-gray-700 hover:border-emerald-500 hover:bg-emerald-50 hover:text-emerald-600 dark:border-gray-700 dark:bg-gray-800 dark:text-gray-200\"\n              disabled={!isInStock || isLoading}\n            >\n              {isLoading ? \"Loading...\" : \"Add to cart\"}\n            </Button>\n            <Button\n              onClick={handleBuyNow}\n              className=\"flex-1 bg-emerald-600 text-white hover:bg-emerald-700\"\n              disabled={!isInStock || isLoading}\n            >\n              {isLoading ? \"Loading...\" : \"Buy now\"}\n            </Button>\n          </div>\n        </div>\n      </div>\n\n      {/* Testimonials section */}\n      {testimonials.length > 0 && (\n        <div className=\"border-t border-gray-200 bg-gray-50 p-4 dark:border-gray-800 dark:bg-gray-900/50\">\n          <div className=\"mb-3 flex items-center justify-between\">\n            <h4 className=\"flex items-center text-base font-medium text-gray-900 dark:text-white\">\n              <MessageSquareText className=\"mr-2 h-5 w-5 text-emerald-500 dark:text-emerald-400\" />\n              Customer Reviews\n            </h4>\n            <div className=\"text-sm text-gray-500 dark:text-gray-400\">\n              {testimonials.length} reviews\n            </div>\n          </div>\n\n          {/* Current testimonial */}\n          <div className=\"mb-3 rounded-lg border border-gray-200 bg-white p-3 shadow-sm dark:border-gray-700 dark:bg-gray-800\">\n            <div className=\"mb-2 flex items-center justify-between\">\n              <div className=\"flex items-center\">\n                <div className=\"mr-2 flex h-7 w-7 items-center justify-center rounded-full bg-gradient-to-br from-emerald-400 to-green-500 text-center font-medium text-white\">\n                  {testimonials[activeTestimonial].name.charAt(0)}\n                </div>\n                <div>\n                  <p className=\"font-medium text-gray-800 dark:text-gray-200\">\n                    {testimonials[activeTestimonial].name}\n                  </p>\n                  {testimonials[activeTestimonial].role && (\n                    <p className=\"text-xs text-gray-500 dark:text-gray-400\">\n                      {testimonials[activeTestimonial].role}\n                    </p>\n                  )}\n                </div>\n              </div>\n\n              <StarRating_Fractions\n                value={testimonials[activeTestimonial].rating}\n                readOnly={true}\n                iconSize={16}\n                color=\"#F59E0B\" // amber-400 color\n                className=\"flex-shrink-0\"\n              />\n            </div>\n\n            <p className=\"text-sm text-gray-600 dark:text-gray-300\">\n              {testimonials[activeTestimonial].text}\n            </p>\n\n            <p className=\"mt-1 text-xs text-gray-500 dark:text-gray-400\">\n              {testimonials[activeTestimonial].date}\n            </p>\n          </div>\n\n          {/* Testimonial navigation */}\n          <div className=\"flex items-center justify-between\">\n            <div className=\"flex space-x-1\">\n              {testimonials.map((_, index) => (\n                <button\n                  key={index}\n                  onClick={() => setActiveTestimonial(index)}\n                  className={`h-2 w-6 rounded-full transition-colors ${\n                    activeTestimonial === index\n                      ? \"bg-emerald-500\"\n                      : \"bg-gray-300 hover:bg-gray-400 dark:bg-gray-700 dark:hover:bg-gray-600\"\n                  }`}\n                  aria-label={`View review ${index + 1}`}\n                />\n              ))}\n            </div>\n\n            <div className=\"flex space-x-1\">\n              <button\n                onClick={() =>\n                  setActiveTestimonial((prev) =>\n                    prev === 0 ? testimonials.length - 1 : prev - 1\n                  )\n                }\n                className=\"rounded p-1 text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700\"\n                aria-label=\"Previous review\"\n              >\n                <ChevronLeft className=\"h-5 w-5\" />\n              </button>\n              <button\n                onClick={() =>\n                  setActiveTestimonial((prev) =>\n                    prev === testimonials.length - 1 ? 0 : prev + 1\n                  )\n                }\n                className=\"rounded p-1 text-gray-500 hover:bg-gray-200 dark:hover:bg-gray-700\"\n                aria-label=\"Next review\"\n              >\n                <ChevronRight className=\"h-5 w-5\" />\n              </button>\n            </div>\n          </div>\n        </div>\n      )}\n    </div>\n  );\n}\n\nexport default ProductVariant_04;\nexport type {\n  AccessoryVariant,\n  ProductVariant04Props,\n  StorageVariant,\n  Testimonial,\n  VariantSelectionPayload,\n};\n",
      "path": "/components/commerce-ui/blocks/product-variants/product-variants-04.tsx",
      "target": "components/commerce-ui/product-variants-04.tsx"
    }
  ]
}