ShippingMethodCheckout.tsx 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. "use client";
  2. import clsx from "clsx";
  3. import { useEffect, useImperativeHandle, type Ref } from "react";
  4. import { useForm, get } from "react-hook-form";
  5. import type {CheckoutShippingRate} from "@/types/checkout/type";
  6. import CheckoutShippingMethodLoading from "./CheckoutShippingMethodLoading";
  7. /**
  8. * 关于如何确定默认选中哪个运输方式:
  9. * 1. 从购物车详情中获取运输方式 A,运输方式列表中有A,那么把A标记为默认选中;如果运输方式列表中没有A,看运输方式列表中有没有免费的,如果有那么把免费的运输方式标记为选中,如果没有,不标记选中,让用户自己选
  10. * 2. 购物车详情中没有运输方式,那么看运输方式列表中有没有免费的,如果有那么把免费的运输方式标记为选中,如果没有,不标记选中,让用户自己选
  11. *
  12. */
  13. export interface RefShippingMethodsHandle {
  14. getSelectShipMethod: () => string;
  15. validateShipMethod: () => Promise<boolean>;
  16. }
  17. export function ShippingMethodCheckout({
  18. ref,
  19. errorMsg,
  20. shipMethods,
  21. selectedShippingRate,
  22. shippingMethodLoading,
  23. onShipMethodChange
  24. }: {
  25. ref: Ref<RefShippingMethodsHandle>;
  26. errorMsg: string | null;
  27. shipMethods: CheckoutShippingRate[];
  28. selectedShippingRate: string | null;
  29. shippingMethodLoading: boolean;
  30. onShipMethodChange: (value: string) => void;
  31. }) {
  32. const {
  33. register,
  34. reset,
  35. getValues,
  36. trigger,
  37. formState: { errors }
  38. } = useForm({
  39. mode: "onChange",
  40. reValidateMode: "onChange",
  41. });
  42. // 参考 https://github.com/react-hook-form/error-message/blob/master/src/ErrorMessage.tsx
  43. const shippingMethodError = get(errors, "shippingMethod");
  44. useImperativeHandle(ref, () => {
  45. return {
  46. // 获取用户选择的运输方式
  47. getSelectShipMethod: () => {
  48. return getValues('shippingMethod');
  49. },
  50. // 触发校验
  51. validateShipMethod: () => {
  52. return trigger();
  53. }
  54. }
  55. },[]);
  56. useEffect(() => {
  57. if(shipMethods.length > 0 ) {
  58. let defaultValue = '';
  59. let findItem: CheckoutShippingRate | undefined;
  60. if(selectedShippingRate) {
  61. findItem = shipMethods.find((item) => item.method === selectedShippingRate);
  62. if(findItem !== undefined) {
  63. defaultValue = selectedShippingRate;
  64. }
  65. }
  66. if(defaultValue) {
  67. reset({ shippingMethod: defaultValue });
  68. }
  69. /*
  70. if(!defaultValue) {
  71. findItem = shipMethods.find((item) => item.price === 0) || shipMethods[0];
  72. defaultValue = findItem.method;
  73. }
  74. console.log('ShippingMethodCheckout' + ' useEffect ++++++++++++++++++++++++++++ ', defaultValue,selectedShippingRate);
  75. reset({ shippingMethod: defaultValue });
  76. if(defaultValue !== selectedShippingRate) {
  77. onShipMethodChange(defaultValue);
  78. }
  79. */
  80. }
  81. }, [shipMethods,selectedShippingRate]);
  82. return (<>
  83. {shippingMethodLoading && <CheckoutShippingMethodLoading />}
  84. <form name="shipping method" className={clsx("w-full mt-3",{
  85. "hidden": shippingMethodLoading
  86. })}>
  87. {errorMsg && <p className="text-red-500 text-ly-12">{errorMsg}</p>}
  88. {shipMethods.length === 0 && <p className="text-red-500 text-ly-12">Please complete address data to get shipping methods list.</p>}
  89. <div className="w-full">
  90. {shipMethods.map((method) => {
  91. return (
  92. <label htmlFor={"shippingmethod-" + method._id} key={method._id}
  93. className="group block relative box-border mt-3 p-3 border-1 rounded-sm has-checked:border-ly-green"
  94. >
  95. <p className="text-ly-13">
  96. { method.method === 'free_free' && <span className="text-ly-13 text-ly-green font-bold mr-1">FREE</span>}
  97. <span>{method.label}</span>
  98. </p>
  99. <p className="text-ly-12 text-ly-placeholder mt-1">{method.description}</p>
  100. <div className="absolute top-1/2 -translate-y-1/2 right-3 flex items-center">
  101. <span className="text-ly-14 font-medium mr-4">{method.formattedPrice}</span>
  102. <svg className="check w-4 h-4 hidden group-has-[:checked]:block" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="1" width="14" height="14" rx="1" fill="#000000" ></rect><path stroke="rgba(255, 255, 255, 1)" strokeWidth="1.5" strokeLinecap="square" d="M5.3335 8L7.3335 10L11.3335 6"></path></svg>
  103. <svg className="uncheck w-4 h-4 block group-has-[:checked]:hidden" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16" fill="none"><rect x="1" y="1" width="14" height="14" rx="1" fill="#FFFFFF" ></rect><path fill="rgba(0, 0, 0, 1)" d="M2 0.5L14 0.5C14.8284 0.5 15.5 1.17157 15.5 2L15.5 14C15.5 14.8284 14.8284 15.5 14 15.5L2 15.5C1.17157 15.5 0.5 14.8284 0.5 14L0.5 2C0.5 1.17157 1.17157 0.5 2 0.5ZM2 1.5C1.72386 1.5 1.5 1.72386 1.5 2L1.5 14C1.5 14.2761 1.72386 14.5 2 14.5L14 14.5C14.2761 14.5 14.5 14.2761 14.5 14L14.5 2C14.5 1.72386 14.2761 1.5 14 1.5L2 1.5Z"></path></svg>
  104. </div>
  105. <input id={"shippingmethod-" + method._id}
  106. className="absolute w-6 h-6 opacity-0"
  107. type="radio"
  108. value={method.method}
  109. {...register('shippingMethod', {
  110. required: 'Please select a shipping method.',
  111. onChange: (event) => {
  112. console.log(event.target.value);
  113. onShipMethodChange(event.target.value);
  114. },
  115. })}
  116. />
  117. </label>
  118. )
  119. })}
  120. {shippingMethodError && <p className="text-red-500 text-ly-12">{shippingMethodError.message}</p>}
  121. </div>
  122. </form>
  123. </>);
  124. }