Przeglądaj źródła

结账页积分抵扣功能和下单获取积分数据对接

fogwind 2 dni temu
rodzic
commit
edad6714f5

+ 10 - 4
src/app/(checkout)/checkout/_components/CheckoutAddress/CheckoutAssress.tsx

@@ -1,6 +1,6 @@
 "use client";
 
-import {Ref, useState, useEffect, useCallback, useImperativeHandle } from "react";
+import {Ref, useState, useEffect, useCallback, useImperativeHandle, useMemo } from "react";
 
 import { useForm, FormProvider } from "react-hook-form";
 import { useCustomToast } from "@/utils/hooks/useToast";
@@ -217,7 +217,9 @@ export function CheckoutAddress({
     onSaveAddress: (saveRes: {billingAddress: CartAddress; shippingAddress: CartAddress;}) => void;
 }) {
 
-    const addressFormDefaultValues = getAddressFormDataFromCart(cartData,loginEmail);
+    const addressFormDefaultValues = useMemo(() => {
+        return getAddressFormDataFromCart(cartData,loginEmail)
+    },[cartData,loginEmail]);
 
     const {getCustomerAddress} = useGetCustomerAddress();
     const { showToast } = useCustomToast();
@@ -354,7 +356,7 @@ export function CheckoutAddress({
     const noShipAddress = cartData.shippingAddress === null;
     const noBillAddress = cartData.billingAddress === null;
 
-    
+    // 登录弹窗登录后,获取用户地址列表,重置地址表单默认值
     useEffect(() => {
         if(cartData.isGuest) return;
 
@@ -372,7 +374,11 @@ export function CheckoutAddress({
             }  
         }
         loadAddress();
-    },[showToast,cartData.isGuest]);
+        const timer = window.requestAnimationFrame(() => {
+            addressForm.reset(addressFormDefaultValues);
+        });
+        return () => window.cancelAnimationFrame(timer)
+    },[showToast,cartData.isGuest,addressFormDefaultValues]);
     
     // function tt() {
     //     const arr = [

+ 12 - 0
src/app/(checkout)/checkout/_components/CheckoutWrapper.tsx

@@ -33,6 +33,7 @@ import CheckoutVipPlus from "./VipPlus/CheckoutVipPlus";
 import CheckoutProducts from "./CheckoutProducts/CheckoutProducts";
 import CheckoutCoupon from "./CheckoutCoupon/CheckoutCoupon";
 import LostInsurance from "./LostInsurance/LostInsurance";
+import RewardPointsRedeemMoney from "./RewardPointsRedeemMoney/RewardPointsRedeemMoney";
 /***
  * 保存完地址之后要重新获取运输方式和支付方式
  * 变更运输方式后重新获取支付方式
@@ -370,6 +371,17 @@ export default function CheckoutWrapper({
                     onCouponChange={onCartDataChange}
                 />
             </div>
+
+            <div className="mt-6 box-border px-4">
+                <RewardPointsRedeemMoney 
+                    isGuest={cartData.isGuest}
+                    cartUsedPoints={cartData.rewardPointsUsed}
+                    hasRewardPoints={cartData.rewardPoints}
+                    formatRewardPointsAmount={cartData.formattedRewardPointsAmount}
+                    onPointsChange={onCartDataChange}
+                />
+            </div>
+            
             
 
             {!cartData.isGuest &&

+ 12 - 0
src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx

@@ -164,6 +164,12 @@ export default function ContinueToPayOrderInfo({
                     <p className="text-ly-12 leading-ly-20">Subtotal</p>
                     <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.subTotal}</p>
                 </div>
+                {orderDetail.rewardPointsEarned > 0 &&
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-12 leading-ly-20">You Earn</p>
+                        <p className="text-ly-12 leading-ly-20">{orderDetail.rewardPointsEarned} Reward Points</p>
+                    </div>
+                }
                 {orderDetail.shippingAmount !== 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Shipping</p>
@@ -206,6 +212,12 @@ export default function ContinueToPayOrderInfo({
                         <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.vipDiscountAmount}</p>
                     </div>
                 }
+                {orderDetail.rewardPointsAmount > 0 &&
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-12 leading-ly-20">You Redeem ({orderDetail.rewardPointsUsed} Reward Points)</p>
+                        <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.rewardPointsAmount}</p>
+                    </div>
+                }
 
                 {/* <div className="flex justify-between mt-3 first:mt-0">
                     <p className="text-ly-12 leading-ly-20">You Earn</p>

+ 12 - 0
src/app/(checkout)/checkout/_components/PromotionsDetails.tsx

@@ -25,6 +25,12 @@ export default function PromotionsDetails({cartData}:{
                         <span>{cartData.formattedShippingAmount}</span>
                     </li>
                 }
+                {cartData.earnableRewardPoints > 0 &&
+                    <li className="flex justify-between items-center text-ly-12 mt-4 first:mt-0">
+                        <span>You Earn</span>
+                        <span>{cartData.earnableRewardPoints} Reward Points</span>
+                    </li>
+                }
                 {cartData.shippingInsuranceAmount !== 0 &&
                     <li className="flex justify-between items-center text-ly-12 mt-4 first:mt-0">
                         <span>{title}</span>
@@ -37,6 +43,12 @@ export default function PromotionsDetails({cartData}:{
                         <span>{cartData.formattedVipPlusAmount}</span>
                     </li>
                 }
+                {cartData.rewardPointsAmount !== 0 &&
+                    <li className="flex justify-between items-center text-ly-12 mt-4 first:mt-0">
+                        <span>You Redeem ({cartData.rewardPointsUsed} Reward Points)</span>
+                        <span className="flex items-center flex-none h-6 bg-ly-gold px-2 text-ly-14">-{cartData.formattedRewardPointsAmount}</span>
+                    </li>
+                }
                 {cartData.discountAmount !== 0 &&
                     <li className="flex justify-between items-center text-ly-12 mt-4 first:mt-0">
                         <span>Coupon</span>

+ 149 - 0
src/app/(checkout)/checkout/_components/RewardPointsRedeemMoney/RewardPointsRedeemMoney.tsx

@@ -0,0 +1,149 @@
+"use client";
+
+import { useRouter } from 'next/navigation';
+import {useState, type ChangeEvent} from "react";
+import {useSaveCheckoutCart} from "@/utils/hooks/useSaveCheckoutCart";
+import { useCustomToast } from "@/utils/hooks/useToast";
+import {useLoginModal} from "@/providers/LoginModalProvider";
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
+import { formatCartDetail } from "@/utils/cartDetailTools";
+import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
+import {CartDetail} from "@/types/cart/type";
+
+// earnableRewardPoints 判断是否开启下单送积分
+
+export default function RewardPointsRedeemMoney({
+    isGuest,
+    cartUsedPoints,
+    hasRewardPoints,
+    formatRewardPointsAmount,
+    onPointsChange
+}: {
+    isGuest: boolean,
+    cartUsedPoints: number, // 当前使用的用于抵扣的积分数
+    hasRewardPoints: number, // 账户中的总的积分数
+    formatRewardPointsAmount: string,
+    onPointsChange: (payload:CartDetail, isUpdateReduxCart?: boolean) => void;
+}) {
+    const cartUsedPointsStr = String(cartUsedPoints);
+    const router = useRouter();
+    const {openLoginModal} = useLoginModal();
+    const {saveCheckoutCart} = useSaveCheckoutCart();
+    const { showToast } = useCustomToast();
+
+    const [usedPoints, setUsedPoints] = useState(cartUsedPointsStr);
+    const amount = Number(formatRewardPointsAmount.replace(/^\D+/ig,''));
+
+
+    const pointsInputChange = (e: ChangeEvent<HTMLInputElement>) => {
+        setUsedPoints(e.target.value);
+    };
+
+    const submitPointsUse = async () => {
+        const points = cartUsedPointsStr && cartUsedPointsStr === usedPoints ? '0' : usedPoints; 
+
+        overlayLoading.start();
+        const saveRes = await saveCheckoutCart({ 
+            rewardPoints: Number(points)
+        });
+        overlayLoading.stop();
+        if(!saveRes.error) {
+            if(!Number(points)) setUsedPoints('');
+            if(saveRes.data) {
+                const newCartDetail = formatCartDetail(saveRes.data);
+                onPointsChange(newCartDetail);
+            }
+        } else {
+            showToast(saveRes.msg, 'danger');
+        }
+
+    };
+
+    const guestUserClick = () => {
+        openLoginModal({
+            onFinalResult: async (res) => {
+                if(res.mergeCartSuccess) { // 购物车合并成功
+                    if(res.cartAfterMerge) {
+                        const newCartDetail = formatCartDetail(res.cartAfterMerge);
+                        onPointsChange(newCartDetail, false);
+                    }
+                    
+                } else { // 购物车合并失败
+                    // 三种 情况: 登录成功; 注册成功;
+                    if(res.loginSuccess) {
+                        // 登录成功 需要刷新页面
+                        showToast('You have logged in successfully. But something wrong','danger');
+                        await confirmDialog({
+                            title: "Warning",
+                            content: "You have logged in successfully. But something wrong. You must refresh the page.",
+                            noCancel: true,
+                        });
+                        window.location.reload();
+                    }
+                    if(res.registerSuccess && !res.loginSuccess) {
+                        // 注册成功,去登录
+                        await confirmDialog({
+                            title: "Warning",
+                            content: "You have registered successfully. But something wrong. You have to login.",
+                            noCancel: true,
+                        });
+                        router.replace('/customer/login');
+                    }
+                }
+            }
+        });
+    };
+
+    return (
+        <>
+            {isGuest ?
+                <div className="text-ly-12 p-2.5 bg-ly-middlegray">
+                    Please <button className="underline" onClick={guestUserClick}>LOGIN</button> to use reward points
+                </div>
+            :
+            <div className="w-full">
+                <div className="relative box-border w-full flex justify-between items-center">
+                    <span className="text-ly-24 font-medium">Reward points redeem</span>
+                
+                    
+                    <div className="flex items-center">
+                        {amount > 0 &&
+                        <span className="flex items-center text-ly-14 px-2 flex-none h-6 bg-ly-gold mr-2">
+                            -{formatRewardPointsAmount}
+                        </span>
+                        }
+                        
+                        <button className="w-6 h-6 flex-none">
+                            <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>
+                        </button>
+                    </div>
+                </div>
+                
+                <div className="w-full mt-3">
+                    <div className="box-border w-full bg-ly-middlegray p-3 rounded-md">
+                        
+                        <p className="text-ly-12">
+                            You have <span className="font-bold">{hasRewardPoints} Reward Points</span>  available.  100 point = $1. Input points value below to redeem.
+                        </p>
+                        <div className="flex justify-between items-center mt-3 box-border w-full border-1 h-12 rounded-sm pl-3 pr-2 bg-white">
+                            <input className="focus:outline-none"
+                                type="text"
+                                value={usedPoints === '0' ? '' : usedPoints}
+                                placeholder="Reward Points"
+                                onChange={pointsInputChange}
+                            />
+                            <button className="flex items-center text-ly-14 rounded-sm bg-ly-green text-white px-2 h-8"
+                                onClick={submitPointsUse}
+                            >
+                                {cartUsedPoints > 0 ? 'Cancel' : 'Apply'}
+                            </button>
+                        </div>
+                    </div>
+                    
+                </div>
+            </div>
+            }
+
+        </>
+    );
+}

+ 1 - 1
src/app/(checkout)/checkout/_components/VipPlus/CheckoutVipPlus.tsx

@@ -9,7 +9,7 @@ import { formatCartDetail } from "@/utils/cartDetailTools";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import CommonModal from "@/components/theme/ui/CommonModal";
-import {useLoginModal} from "@/providers/LoginModalProvider"
+import {useLoginModal} from "@/providers/LoginModalProvider";
 
 // 问题: plus金额,折扣,时间是前端写死的。写死金额只显示美金数字,不随货币改变;
 const staticVipData = {

+ 12 - 0
src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx

@@ -179,6 +179,12 @@ export default function OrderDetailWrapper({
                     <p className="text-ly-12 leading-ly-20">Subtotal</p>
                     <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{orderDetailRes?.subTotal}</p>
                 </div>
+                {orderDetailRes?.rewardPointsEarned > 0 &&
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-12 leading-ly-20">You Earn</p>
+                        <p className="text-ly-12 leading-ly-20">{orderDetailRes?.rewardPointsEarned} Reward Points</p>
+                    </div>
+                }
                 {!!orderDetailRes?.shippingAmount &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Shipping</p>
@@ -203,6 +209,12 @@ export default function OrderDetailWrapper({
                         <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{orderDetailRes?.vipPlusAmount}</p>
                     </div>
                 }
+                {orderDetailRes?.rewardPointsAmount > 0 &&
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-12 leading-ly-20">You Redeem ({orderDetailRes.rewardPointsUsed} Reward Points)</p>
+                        <p className="text-ly-12 leading-ly-20">-{currency && currency.symbol}{orderDetailRes?.rewardPointsAmount}</p>
+                    </div>
+                }
                 {!!orderDetailRes?.discountAmount &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Discount</p>

+ 6 - 0
src/graphql/cart/mutations/AddProductToCart.ts

@@ -41,6 +41,12 @@ export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData,AddToCa
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
         useShippingInsurance
         shippingInsuranceEnabled
         shippingInsuranceName

+ 6 - 0
src/graphql/cart/mutations/CreateApplyCoupon.ts

@@ -31,6 +31,12 @@ export const CREATE_APPLY_COUPON: TypedDocumentNode<CreateApplyCouponData,{coupo
                         }
                     }
                     }
+                    earnableRewardPoints
+                    rewardPoints
+                    rewardPointsUsed
+                    rewardPointsAmount
+                    baseRewardPointsAmount
+                    formattedRewardPointsAmount
                     useShippingInsurance
                     shippingInsuranceEnabled
                     shippingInsuranceName

+ 6 - 0
src/graphql/cart/mutations/CreateMergeCart.ts

@@ -33,6 +33,12 @@ export const CREATE_MERGE_CART: TypedDocumentNode<CreateMergeCartData> = gql`
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
         useShippingInsurance
         shippingInsuranceEnabled
         shippingInsuranceName

+ 6 - 0
src/graphql/cart/mutations/CreateRemoveCoupon.ts

@@ -27,6 +27,12 @@ export const CREATE_REMOVE_COUPON: TypedDocumentNode<CreateRemoveCouponData> = g
                         }
                     }
                     }
+                    earnableRewardPoints
+                    rewardPoints
+                    rewardPointsUsed
+                    rewardPointsAmount
+                    baseRewardPointsAmount
+                    formattedRewardPointsAmount
                     useShippingInsurance
                     shippingInsuranceEnabled
                     shippingInsuranceName

+ 6 - 0
src/graphql/cart/mutations/GetCartItem.ts

@@ -27,6 +27,12 @@ export const GET_CART_ITEM: TypedDocumentNode<GetCartItemData> = gql`
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
         useShippingInsurance
         shippingInsuranceEnabled
         shippingInsuranceName

+ 6 - 0
src/graphql/cart/mutations/RemoveCartItem.ts

@@ -27,6 +27,12 @@ export const REMOVE_CART_ITEM: TypedDocumentNode<RemoveCartItemData,{cartItemId:
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
         useShippingInsurance
         shippingInsuranceEnabled
         shippingInsuranceName

+ 6 - 0
src/graphql/cart/mutations/UpdateCartItems.ts

@@ -36,6 +36,12 @@ export const UPDATE_CART_ITEM: TypedDocumentNode<UpdateCartItemData,{cartItemId:
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
         useShippingInsurance
         shippingInsuranceEnabled
         shippingInsuranceName

+ 10 - 8
src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts

@@ -1,13 +1,7 @@
 import { gql,TypedDocumentNode } from "@apollo/client";
-import { SaveCheckoutCartData } from "@/types/checkout/type";
+import { SaveCheckoutCartData,SaveCheckoutCartVariables } from "@/types/checkout/type";
 
-export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,{
-    shippingMethod: string;
-    paymentMethod: string;
-    couponCode: string;
-    giftCardNumber: string;
-    memberDiscount: string;
-}> = gql`
+export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,SaveCheckoutCartVariables> = gql`
     mutation CreateSaveCheckoutCart(
         $shippingMethod: String
         $paymentMethod: String
@@ -15,6 +9,7 @@ export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,{
         $giftCardNumber: String
         $memberDiscount: String
         $useShippingInsurance: String
+        $rewardPoints: Int
     ) {
       createSaveCheckoutCart(
         input: {
@@ -24,6 +19,7 @@ export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,{
             giftCardNumber: $giftCardNumber
             memberDiscount: $memberDiscount
             useShippingInsurance: $useShippingInsurance
+            rewardPoints: $rewardPoints
         }
         ) {
             saveCheckoutCart {
@@ -49,6 +45,12 @@ export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,{
                         }
                     }
                 }
+                earnableRewardPoints
+                rewardPoints
+                rewardPointsUsed
+                rewardPointsAmount
+                baseRewardPointsAmount
+                formattedRewardPointsAmount
                 useShippingInsurance
                 shippingInsuranceEnabled
                 shippingInsuranceName

+ 6 - 1
src/graphql/customer/query/GetOrderDetails.ts

@@ -38,7 +38,12 @@ export const GET_ORDER_DETAILS: TypedDocumentNode<OrderDetailsData> = gql`
     vipDiscountAmount
     baseVipPlusAmount
     baseVipDiscountAmount
-
+    rewardPointsUsed    
+    rewardPointsAmount
+    baseRewardPointsAmount
+    rewardPointsInvoiced
+    baseRewardPointsInvoiced
+    rewardPointsEarned
     shippingInsuranceAmount
     baseShippingInsuranceAmount
     shippingInsuranceInvoiced

+ 7 - 0
src/types/cart/type.ts

@@ -60,6 +60,13 @@ export interface CartDetail {
   shippingAmount: number;
   shippingAmountInclTax: number;
   grandTotal: number;
+  // 积分
+  earnableRewardPoints: number;
+  rewardPoints: number;
+  rewardPointsUsed: number;
+  rewardPointsAmount: number;
+  baseRewardPointsAmount: number;
+  formattedRewardPointsAmount: string;
   // 丢件险
   useShippingInsurance: boolean;
   shippingInsuranceEnabled: boolean;

+ 1 - 0
src/types/checkout/type.ts

@@ -235,6 +235,7 @@ export interface SaveCheckoutCartVariables {
   giftCardNumber?: string;
   memberDiscount?: string;
   useShippingInsurance?: string;
+  rewardPoints?: number; // -1 跳过,0 取消,大于0 使用
 }
 // Checkout Place Order
 

+ 6 - 0
src/types/customer/order.ts

@@ -117,6 +117,12 @@ export interface OrderDetails {
     giftcardNumber: string;
     giftcardAmount: string;
     baseGiftcardAmount: string;
+    rewardPointsUsed: number;    
+    rewardPointsAmount: number;
+    baseRewardPointsAmount: number;
+    rewardPointsInvoiced: number;
+    baseRewardPointsInvoiced: number;
+    rewardPointsEarned: number;
     baseCurrencyCode: string | null;
     channelCurrencyCode: string | null;
     orderCurrencyCode: string;

+ 7 - 0
src/utils/cartDetailTools.ts

@@ -9,6 +9,13 @@ export function formatCartDetail(cart:CartDetail): CartDetail {
         itemsCount: cart.itemsCount,
         items: cart.items,
 
+        earnableRewardPoints: cart.earnableRewardPoints,
+        rewardPoints: cart.rewardPoints,
+        rewardPointsUsed: cart.rewardPointsUsed,
+        rewardPointsAmount: cart.rewardPointsAmount,
+        baseRewardPointsAmount: cart.baseRewardPointsAmount,
+        formattedRewardPointsAmount: cart.formattedRewardPointsAmount,
+
         useShippingInsurance: cart.useShippingInsurance,
         shippingInsuranceEnabled: cart.shippingInsuranceEnabled,
         shippingInsuranceName: cart.shippingInsuranceName,

+ 1 - 0
src/utils/hooks/useSaveCheckoutCart.ts

@@ -21,6 +21,7 @@ export const useSaveCheckoutCart = () => {
         giftCardNumber: '-1',
         memberDiscount: '-1',
         useShippingInsurance: '-1',
+        rewardPoints: -1
     };
     const saveCheckoutCart = useCallback((params: SaveCheckoutCartVariables) => {
         const variables = Object.assign({},defaultVariables,params);