AddToCart.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. "use client";
  2. import { MinusIcon, PlusIcon } from "@heroicons/react/24/outline";
  3. import clsx from "clsx";
  4. import { useSearchParams } from "next/navigation";
  5. import { useForm, useWatch } from "react-hook-form";
  6. import { ConfigurableProductIndexData } from "@/types/types";
  7. import { useAddProduct } from "@utils/hooks/useAddToCart";
  8. import LoadingDots from "@components/common/icons/LoadingDots";
  9. import { getVariantInfo } from "@utils/hooks/useVariantInfo";
  10. import { safeParse } from "@utils/helper";
  11. import { ProductSwatchReview } from "@/types/category/type";
  12. interface AddToCartFormData {
  13. quantity: number;
  14. isBuyNow: boolean;
  15. }
  16. function SubmitButton({
  17. selectedVariantId,
  18. pending,
  19. type,
  20. isSaleable,
  21. }: {
  22. selectedVariantId: boolean;
  23. pending: boolean;
  24. type: string;
  25. isSaleable: string;
  26. }) {
  27. const buttonClasses =
  28. "relative flex w-full max-w-[16rem] cursor-pointer h-fit items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white";
  29. const disabledClasses = "cursor-wait opacity-60";
  30. if (!isSaleable || isSaleable === "") {
  31. return (
  32. <button
  33. aria-disabled
  34. aria-label="Out of stock"
  35. type="button"
  36. disabled
  37. className={clsx(buttonClasses, " opacity-60 !cursor-not-allowed")}
  38. >
  39. Out of Stock
  40. </button>
  41. );
  42. }
  43. if (!selectedVariantId && type === "configurable") {
  44. return (
  45. <button
  46. aria-disabled
  47. aria-label="Please select an option"
  48. type="button"
  49. disabled={!selectedVariantId}
  50. className={clsx(buttonClasses, " opacity-60 !cursor-not-allowed")}
  51. >
  52. Add To Cart
  53. </button>
  54. );
  55. }
  56. return (
  57. <button
  58. aria-disabled={pending}
  59. aria-label="Add to cart"
  60. type="submit"
  61. className={clsx(buttonClasses, {
  62. "hover:opacity-90": true,
  63. [disabledClasses]: pending,
  64. })}
  65. onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
  66. if (pending) e.preventDefault();
  67. }}
  68. >
  69. <div className="absolute left-0 ml-4">
  70. {pending ? <LoadingDots className="mb-3 bg-white" /> : ""}
  71. </div>
  72. Add To Cart
  73. </button>
  74. );
  75. }
  76. export function AddToCart({
  77. productSwatchReview,
  78. index,
  79. productId,
  80. userInteracted,
  81. }: {
  82. productSwatchReview: ProductSwatchReview;
  83. productId: string;
  84. index: ConfigurableProductIndexData[];
  85. userInteracted: boolean;
  86. }) {
  87. const isSaleable = productSwatchReview?.isSaleable || "";
  88. const { onAddToCart, isCartLoading } = useAddProduct();
  89. const { handleSubmit, setValue, control, register } = useForm<AddToCartFormData>({
  90. defaultValues: {
  91. quantity: 1,
  92. isBuyNow: false,
  93. },
  94. });
  95. const quantity = useWatch({
  96. control,
  97. name: "quantity",
  98. });
  99. const increment = (e: React.MouseEvent) => {
  100. e.preventDefault();
  101. e.stopPropagation();
  102. setValue("quantity", Number(quantity) + 1);
  103. };
  104. const decrement = (e: React.MouseEvent) => {
  105. e.preventDefault();
  106. e.stopPropagation();
  107. setValue("quantity", Math.max(1, Number(quantity) - 1));
  108. };
  109. const searchParams = useSearchParams();
  110. const type = productSwatchReview?.type;
  111. const superAttributes = productSwatchReview?.superAttributeOptions
  112. ? safeParse(productSwatchReview.superAttributeOptions)
  113. : productSwatchReview?.superAttributes?.edges?.map(
  114. (e) => e.node,
  115. ) || [];
  116. const isConfigurable = superAttributes.length > 0;
  117. const { productid: selectedVariantId, Instock: checkStock } = getVariantInfo(
  118. isConfigurable,
  119. searchParams.toString(),
  120. superAttributes,
  121. JSON.stringify(index),
  122. );
  123. const buttonStatus = !!selectedVariantId;
  124. const actionWithVariant = async (data: AddToCartFormData) => {
  125. const pid =
  126. type === "configurable"
  127. ? String(selectedVariantId)
  128. : (String(productId).split("/").pop() ?? "");
  129. onAddToCart({
  130. productId: pid,
  131. quantity: data.quantity,
  132. });
  133. };
  134. return (
  135. <>
  136. {!checkStock && type === "configurable" && userInteracted && (
  137. <div className="gap-1 px-2 py-1 my-2 font-bold text-red-500 dark:text-red-400">
  138. <h1>NO STOCK AVAILABLE</h1>
  139. </div>
  140. )}
  141. <form className="flex gap-x-4" onSubmit={handleSubmit(actionWithVariant)}>
  142. <div className="flex items-center justify-center">
  143. <div className="flex items-center rounded-full border-2 border-blue-500">
  144. <div
  145. aria-label="Decrease quantity"
  146. role="button"
  147. className="flex h-12 w-12 cursor-pointer items-center justify-center rounded-l-full text-gray-600 transition-colors hover:text-gray-800 dark:text-white hover:dark:text-white/[80%]"
  148. onClick={decrement}
  149. >
  150. <MinusIcon className="h-4 w-4" />
  151. </div>
  152. <input
  153. type="hidden"
  154. {...register("quantity", { valueAsNumber: true })}
  155. />
  156. <div className="flex h-12 min-w-[4rem] items-center justify-center px-2 font-medium text-gray-800 dark:text-white">
  157. {quantity}
  158. </div>
  159. <div
  160. aria-label="Increase quantity"
  161. role="button"
  162. className="flex h-12 w-12 cursor-pointer items-center justify-center rounded-r-full text-gray-600 transition-colors hover:text-gray-800 dark:text-white hover:dark:text-white/[80%]"
  163. onClick={increment}
  164. >
  165. <PlusIcon className="h-4 w-4" />
  166. </div>
  167. </div>
  168. </div>
  169. <SubmitButton
  170. pending={isCartLoading}
  171. selectedVariantId={buttonStatus}
  172. type={type || ""}
  173. isSaleable={isSaleable}
  174. />
  175. </form>
  176. </>
  177. );
  178. }