PaymentMethod.tsx 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. "use client";
  2. import { Controller, FieldValues, useForm } from "react-hook-form";
  3. import { cn } from "@heroui/theme";
  4. import { Radio, RadioGroup } from "@heroui/radio";
  5. import { useState } from "react";
  6. import { useCustomToast } from "@utils/hooks/useToast";
  7. import { useCheckout } from "@utils/hooks/useCheckout";
  8. import { ProceedToCheckout } from "../ProceedToCheckout";
  9. import { CustomRadioProps } from "@components/checkout/type";
  10. import { useDispatch } from "react-redux";
  11. import { updateCart } from "@/store/slices/cart-slice";
  12. export default function PaymentMethod({
  13. selectedPayment,
  14. methods,
  15. currentStep,
  16. }: {
  17. selectedPayment?: string;
  18. methods: any;
  19. currentStep?: string;
  20. }) {
  21. const { showToast } = useCustomToast();
  22. const { isPaymentLoading, saveCheckoutPayment } = useCheckout();
  23. const [isOpen, setIsOpen] = useState(currentStep !== "payment");
  24. const [prevValues, setPrevValues] = useState({
  25. currentStep,
  26. selectedPayment,
  27. });
  28. if (
  29. currentStep !== prevValues.currentStep ||
  30. selectedPayment !== prevValues.selectedPayment
  31. ) {
  32. setPrevValues({ currentStep, selectedPayment });
  33. if (currentStep === "payment") {
  34. setIsOpen(false);
  35. } else if (selectedPayment) {
  36. setIsOpen(true);
  37. }
  38. }
  39. const { handleSubmit, control } = useForm({
  40. mode: "onSubmit",
  41. defaultValues: {
  42. method: selectedPayment ?? "",
  43. },
  44. });
  45. const selectedMethodLabelPrior = methods?.find(
  46. (method: any) => method?.method === selectedPayment,
  47. )?.title;
  48. const dispatch = useDispatch();
  49. const onSubmit = async (data: FieldValues) => {
  50. if (!data?.method) {
  51. showToast("Please Choose the Payment Method", "warning");
  52. return;
  53. }
  54. try {
  55. const selectedMethod = methods?.find(
  56. (m: any) => m?.method === data?.method,
  57. );
  58. if (selectedMethod) {
  59. dispatch(
  60. updateCart({
  61. paymentMethod: selectedMethod?.method || "",
  62. paymentMethodTitle: selectedMethod?.title || "",
  63. }),
  64. );
  65. }
  66. await saveCheckoutPayment(data.method);
  67. } catch {
  68. showToast("Failed to save payment method. Please try again.");
  69. }
  70. };
  71. return (
  72. <>
  73. {selectedPayment ? (
  74. isOpen ? (
  75. <>
  76. <div className="mt-4 justify-between hidden sm:flex ">
  77. <div className="flex">
  78. <p className="w-auto text-base font-normal text-black/60 dark:text-white/60 sm:w-[192px]">
  79. Payment Method
  80. </p>
  81. <p className="text-base font-normal">
  82. {selectedMethodLabelPrior as string}
  83. </p>
  84. </div>
  85. <button
  86. onClick={() => {
  87. setIsOpen(false);
  88. }}
  89. className="cursor-pointer text-base font-normal text-black/60 underline dark:text-neutral-300"
  90. >
  91. Change
  92. </button>
  93. </div>
  94. <div className="mt-4 flex sm:hidden justify-between relative">
  95. <div className="flex justify-between justify-between flex-1 wrap">
  96. <p className="w-auto text-base font-normal text-black/60 dark:text-white/60 sm:w-[192px]">
  97. Payment Method
  98. </p>
  99. <p className="text-base font-normal">
  100. {selectedMethodLabelPrior as string}
  101. </p>
  102. </div>
  103. <button
  104. onClick={() => {
  105. setIsOpen(false);
  106. }}
  107. className="cursor-pointer absolute right-0 text-base font-normal text-black/60 underline dark:text-neutral-300"
  108. style={{ top: "-36px" }}
  109. >
  110. Change
  111. </button>
  112. </div>
  113. </>
  114. ) : (
  115. <div className="mt-6">
  116. <form onSubmit={handleSubmit(onSubmit)}>
  117. <Controller
  118. control={control}
  119. name="method"
  120. render={({ field }) => (
  121. <RadioGroup
  122. {...field}
  123. label=""
  124. value={field.value ?? ""}
  125. onValueChange={field.onChange}
  126. >
  127. {methods?.map((method: any) => (
  128. <CustomRadio
  129. key={method?.method}
  130. className="my-1 border border-solid border-neutral-300 dark:border-neutral-500"
  131. description={method?.description}
  132. value={method?.method}
  133. >
  134. <span className="text-neutral-700 dark:text-white">
  135. {method?.title}
  136. </span>
  137. </CustomRadio>
  138. ))}
  139. </RadioGroup>
  140. )}
  141. />
  142. <div className="my-6 justify-self-end">
  143. <ProceedToCheckout
  144. buttonName="Pay Now"
  145. pending={isPaymentLoading}
  146. />
  147. </div>
  148. </form>
  149. </div>
  150. )
  151. ) : (
  152. <div className="mt-6">
  153. <form onSubmit={handleSubmit(onSubmit)}>
  154. <Controller
  155. control={control}
  156. name="method"
  157. render={({ field }) => (
  158. <RadioGroup
  159. {...field}
  160. label=""
  161. value={field.value ?? ""}
  162. onValueChange={field.onChange}
  163. >
  164. {methods?.map((method: any) => (
  165. <CustomRadio
  166. key={method?.method}
  167. className="my-1 border border-solid border-neutral-300 dark:border-neutral-500"
  168. description={method?.description}
  169. value={method?.method}
  170. >
  171. <span className="text-neutral-700 dark:text-white">
  172. {method?.title}
  173. </span>
  174. </CustomRadio>
  175. ))}
  176. </RadioGroup>
  177. )}
  178. />
  179. <div className="my-6 justify-self-end">
  180. <ProceedToCheckout
  181. buttonName="Pay Now"
  182. pending={isPaymentLoading}
  183. />
  184. </div>
  185. </form>
  186. </div>
  187. )}
  188. </>
  189. );
  190. }
  191. const CustomRadio = (props: CustomRadioProps) => {
  192. const { children, ...otherProps } = props;
  193. return (
  194. <Radio
  195. {...otherProps}
  196. classNames={{
  197. base: cn(
  198. "inline-flex m-0 bg-transparent hover:bg-transparent items-center",
  199. "flex-row items-baseline max-w-full cursor-pointer rounded-lg gap-4 p-4 border-2 border-transparent",
  200. "data-[selected=true]:border-primary",
  201. ),
  202. hiddenInput: "peer absolute h-0 w-0 opacity-0",
  203. }}
  204. >
  205. {children}
  206. </Radio>
  207. );
  208. };