CheckoutGiftCardGate.tsx 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. "use client";
  2. import {useState, useRef, useEffect, useCallback} from "react";
  3. import clsx from "clsx";
  4. import {CartDetail} from "@/types/cart/type";
  5. import {clientFetch} from "@/lib/restApiClient";
  6. import { useCustomToast } from "@/utils/hooks/useToast";
  7. import {useSaveCheckoutCart} from "@/utils/hooks/useSaveCheckoutCart";
  8. import { formatCartDetail } from "@/utils/cartDetailTools";
  9. import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
  10. import {GiftCardItem, GiftCardRespBody} from "@/types/api/gift/my-gift-cards";
  11. import CommonModal from "@/components/theme/ui/CommonModal";
  12. import { LoadingSpinner } from "@components/common/LoadingSpinner";
  13. export default function CheckoutGiftCardGate({
  14. giftcardNumber,
  15. formattedGiftcardAmount,
  16. onChangeGiftcard
  17. }: {
  18. giftcardNumber: string;
  19. formattedGiftcardAmount: string;
  20. onChangeGiftcard: (payload:CartDetail) => void;
  21. }) {
  22. const [isShow, setIsShow] = useState(false);
  23. const [giftCardsList, setGiftCardsList] = useState<GiftCardItem[]>([]);
  24. const [hasMore, setHasMore] = useState(true);
  25. const [loadingList, setLoadingList] = useState(false);
  26. const currentPageRef = useRef(1);
  27. const scrollerFooterRef = useRef<HTMLDivElement>(null);
  28. const { showToast } = useCustomToast();
  29. const {saveCheckoutCart} = useSaveCheckoutCart();
  30. const amount = Number(formattedGiftcardAmount.replace(/^\D+/ig,''));
  31. const getMyGiftCard = useCallback(async (page: number) => {
  32. // overlayLoading.start();
  33. setLoadingList(true);
  34. const params = new URLSearchParams({
  35. page: String(page),
  36. per_page: '20'
  37. });
  38. const res = await clientFetch<GiftCardRespBody>(`/api/gift/my-gift-cards?${params.toString()}`,{
  39. method: 'GET',
  40. });
  41. console.log(res);
  42. // overlayLoading.stop();
  43. setLoadingList(false);
  44. if(res.success) {
  45. setGiftCardsList((preList) => {
  46. return preList.concat(res.data);
  47. });
  48. setHasMore(res.pagination.has_more);
  49. currentPageRef.current = res.pagination.current_page;
  50. } else {
  51. showToast(res.message,"danger");
  52. }
  53. },[showToast]);
  54. useEffect(()=>{
  55. const element = scrollerFooterRef.current;
  56. if(!element) return;
  57. const observer = new IntersectionObserver(entries=>{
  58. // console.log('IntersectionObserver --- ', entries);
  59. // if (entries[0].intersectionRatio <= 0) return;
  60. if(entries[0].intersectionRatio > 0 && !loadingList && hasMore){
  61. getMyGiftCard(currentPageRef.current + 1);
  62. }
  63. });
  64. observer.observe(element);
  65. return ()=>observer.disconnect();
  66. },[loadingList,hasMore, getMyGiftCard]);
  67. const toggleModal = (bool: boolean) => {
  68. setIsShow(bool);
  69. if(bool && giftCardsList.length === 0) {
  70. getMyGiftCard(1);
  71. }
  72. };
  73. const applyGiftCard = async (code: string) => {
  74. const paramCode = code === giftcardNumber ? '' : code; // '' 表示取消应用
  75. overlayLoading.start();
  76. const saveRes = await saveCheckoutCart({
  77. // shippingMethod: '-1',
  78. // paymentMethod: '-1',
  79. // couponCode: '-1',
  80. giftCardNumber: paramCode,
  81. // memberDiscount: '-1'
  82. });
  83. overlayLoading.stop();
  84. if(!saveRes.error) {
  85. if(saveRes.data) {
  86. const newCartDetail = formatCartDetail(saveRes.data);
  87. onChangeGiftcard(newCartDetail);
  88. }
  89. } else {
  90. showToast(saveRes.msg, 'danger');
  91. }
  92. toggleModal(false);
  93. };
  94. return (
  95. <>
  96. <div className="relative box-border w-full flex justify-between items-center">
  97. <span className="text-ly-24 font-medium">Gift Card</span>
  98. <div className="flex items-center">
  99. {amount > 0 &&
  100. <span className="flex items-center text-ly-14 px-2 flex-none h-6 bg-ly-gold mr-2">
  101. -{formattedGiftcardAmount}
  102. </span>
  103. }
  104. <button className="w-6 h-6 flex-none" onClick={() => {toggleModal(true)}}>
  105. <svg className="block w-full h-full" 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="0" y="0" width="16" height="16" fill="#FFFFFF" fillOpacity="0"></rect><path stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square" d="M6.32031 4.27051L10.4202 8.37036L6.32031 12.4702"></path></svg>
  106. </button>
  107. </div>
  108. </div>
  109. <CommonModal
  110. isOpen={isShow}
  111. onClose={toggleModal}
  112. header={
  113. <div className="w-full box-border p-3.75 h-14.5 flex justify-between items-center shadow-md">
  114. <span className="text-ly-16 font-medium">
  115. My Gift Card
  116. </span>
  117. <button className="w-6 h-6" onClick={() => toggleModal(false)}>
  118. <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon" className="h-6 transition-all ease-in-out hover:scale-110"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12"></path></svg>
  119. </button>
  120. </div>
  121. }
  122. body={
  123. <div className="relative box-border w-full h-auto px-3.75 pt-3.75 pb-8">
  124. {giftCardsList.length > 0 &&
  125. giftCardsList.map((item) => {
  126. const amountArr = item.formatted_remaining_giftcard_amount.split('.');
  127. return (
  128. <div key={item.id} onClick={() => {applyGiftCard(item.giftcard_number)}}
  129. className={clsx("relative w-full box-border mt-3 p-3 rounded-sm overflow-hidden first:mt-0",
  130. [
  131. item.giftcard_number === giftcardNumber ? "border-2 border-ly-green" : "border-1",
  132. ]
  133. )}>
  134. <div className="text-ly-16 font-medium">
  135. CODE: {item.giftcard_number}
  136. </div>
  137. <div className="mt-4 text-ly-14 text-ly-placeholder">
  138. Expire Date: {item.expirationdate}
  139. </div>
  140. <div className="absolute text-ly-36 font-bold right-3 top-1/2 -translate-y-1/2">
  141. {Number(amountArr[1]) === 0 ? amountArr[0] : item.formatted_remaining_giftcard_amount }
  142. </div>
  143. <span className={clsx("absolute top-0 right-0 w-8 h-8",{
  144. "hidden": item.giftcard_number !== giftcardNumber
  145. })}>
  146. <svg className="block w-full h-full fill-ly-green" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="34024" width="200" height="200"><path d="M0 0l1024 1024V0H0z m901.888 247.68l-200.234667 200.234667a28.288 28.288 0 0 1-20.096 8.32 28.288 28.288 0 0 1-20.096-8.32l-112.64-112.597334a28.501333 28.501333 0 0 1 40.234667-40.234666l92.544 92.501333 180.138667-180.096c11.093333-11.093333 29.098667-11.093333 40.234666 0a28.501333 28.501333 0 0 1-0.085333 40.192z" p-id="34025"></path></svg>
  147. </span>
  148. </div>
  149. );
  150. })
  151. }
  152. <div className="flex justify-center items-center w-full h-9" ref={scrollerFooterRef}>
  153. {!hasMore && <p className="text-ly-14 text-ly-placeholder">No More Data</p> }
  154. {(hasMore && loadingList) && <LoadingSpinner size={24} />}
  155. </div>
  156. </div>
  157. }
  158. />
  159. </>
  160. );
  161. }