Sfoglia il codice sorgente

Merge branch 'master' into zzf-account

zhangzf 1 giorno fa
parent
commit
003e45c2bd
66 ha cambiato i file con 1040 aggiunte e 1082 eliminazioni
  1. 1 0
      README.md
  2. 75 0
      src/actions/addProductToCartAction.ts
  3. 13 0
      src/actions/createGuestCartAction.ts
  4. 48 0
      src/actions/deleteCartProductAction.ts
  5. 13 0
      src/actions/deleteGuestCookieAction.ts
  6. 7 0
      src/actions/index.ts
  7. 42 0
      src/actions/logoutAction.ts
  8. 12 11
      src/actions/mergeCartAction.ts
  9. 10 4
      src/app/(checkout)/checkout/_components/CheckoutAddress/CheckoutAssress.tsx
  10. 4 4
      src/app/(checkout)/checkout/_components/CheckoutCoupon/CheckoutCoupon.tsx
  11. 33 12
      src/app/(checkout)/checkout/_components/CheckoutWrapper.tsx
  12. 18 0
      src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx
  13. 4 4
      src/app/(checkout)/checkout/_components/GiftCard/CheckoutGiftCardGate.tsx
  14. 88 0
      src/app/(checkout)/checkout/_components/LostInsurance/LostInsurance.tsx
  15. 22 1
      src/app/(checkout)/checkout/_components/PromotionsDetails.tsx
  16. 149 0
      src/app/(checkout)/checkout/_components/RewardPointsRedeemMoney/RewardPointsRedeemMoney.tsx
  17. 5 5
      src/app/(checkout)/checkout/_components/VipPlus/CheckoutVipPlus.tsx
  18. 3 3
      src/app/(public)/customer/login/page.tsx
  19. 23 2
      src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx
  20. 4 3
      src/app/(public)/paymentresult/result/page.tsx
  21. 2 8
      src/app/(public)/product/_components/ProductAddToCart.tsx
  22. 29 26
      src/app/(public)/product/_components/ProductInformation.tsx
  23. 0 20
      src/app/api/clearCartCookie/route.ts
  24. 0 197
      src/components/cart/AddToCart.tsx
  25. 0 137
      src/components/catalog/product/ProductDescription.tsx
  26. 0 31
      src/components/catalog/product/ProductInfo.tsx
  27. 19 10
      src/components/common/AddToCartModal/AddToCartModal.tsx
  28. 3 5
      src/components/common/AddToCartModal/FooterBtnInAddToCartModal.tsx
  29. 1 1
      src/components/common/CurrencySwitch/CurrencySwitch.tsx
  30. 1 1
      src/components/common/LoginModal/LoginModal.tsx
  31. 6 4
      src/components/common/button/ReviewButton.tsx
  32. 2 2
      src/components/common/icons/cart/DeleteItemButton.tsx
  33. 14 51
      src/components/customer/LoginForm.tsx
  34. 4 8
      src/components/customer/credentials/CredentialModal.tsx
  35. 3 3
      src/components/layout/navbar/CartAndUserActions.tsx
  36. 16 2
      src/graphql/cart/mutations/AddProductToCart.ts
  37. 14 0
      src/graphql/cart/mutations/CreateApplyCoupon.ts
  38. 3 2
      src/graphql/cart/mutations/CreateCartToken.ts
  39. 14 0
      src/graphql/cart/mutations/CreateMergeCart.ts
  40. 14 0
      src/graphql/cart/mutations/CreateRemoveCoupon.ts
  41. 14 0
      src/graphql/cart/mutations/GetCartItem.ts
  42. 14 0
      src/graphql/cart/mutations/RemoveCartItem.ts
  43. 14 0
      src/graphql/cart/mutations/UpdateCartItems.ts
  44. 20 8
      src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts
  45. 10 0
      src/graphql/customer/query/GetOrderDetails.ts
  46. 3 0
      src/providers/GlobalProviders.tsx
  47. 0 11
      src/providers/NextAuthProvider.tsx
  48. 0 12
      src/providers/SessionManager.tsx
  49. 0 8
      src/providers/SessionProvider.tsx
  50. 0 3
      src/providers/index.ts
  51. 103 0
      src/server-service/guestCartTokenService.ts
  52. 17 0
      src/types/cart/type.ts
  53. 7 5
      src/types/checkout/type.ts
  54. 10 0
      src/types/customer/order.ts
  55. 8 1
      src/types/types.ts
  56. 0 4
      src/utils/actions.ts
  57. 19 51
      src/utils/bagisto/index.ts
  58. 17 0
      src/utils/cartDetailTools.ts
  59. 4 2
      src/utils/constants.ts
  60. 4 23
      src/utils/cookie-tools.ts
  61. 0 55
      src/utils/fetch-handler.ts
  62. 46 158
      src/utils/hooks/useAddToCart.ts
  63. 0 110
      src/utils/hooks/useGuestCartToken.ts
  64. 0 37
      src/utils/hooks/useMergeCart.ts
  65. 11 1
      src/utils/hooks/useSaveCheckoutCart.ts
  66. 0 36
      src/utils/signInAuth.ts

+ 1 - 0
README.md

@@ -352,6 +352,7 @@ useEffect 只会在客户端执行,具体是在浏览器绘制后执行,服
 46. 购物车详情里的subtotal和grandtotal金额不对,(一个sku产品加购多个,钱只算了一个)--- 后端已处理
 47. 请求接口时需要区分哪些接口需要token,哪些不需要(不需要的不在请求头中添加token,所以要扩展请求方法,加一个是否携带token的参数)。因为登录用户的token,需要校验有效性(设想是在nextAuth中校验)。
     对于需要token的接口:登录用户校验token; 游客只需要检查有没有token.
+48. 游客token的创建优化(想在服务端创建写入token)--- 已完成
 
 
 

+ 75 - 0
src/actions/addProductToCartAction.ts

@@ -0,0 +1,75 @@
+"use server";
+
+import {cookies} from "next/headers";
+import { GUEST_CART_TOKEN } from "@/utils/constants";
+import { CREATE_ADD_PRODUCT_IN_CART } from "@/graphql";
+import { getServerSession } from "next-auth";
+import { authOptions } from "@utils/auth";
+import { serverGraphqlFetch } from "@utils/bagisto";
+import { BagistoSession } from "@/types/types";
+import {createGuestCartToken,setGuestCookie} from "@/server-service/guestCartTokenService";
+import { AddToCartData,AddToCartVariables } from "@/types/cart/type";
+
+
+export async function addProductToCartAction(param:AddToCartVariables) {
+
+    const authSession = (await getServerSession(authOptions)) as BagistoSession | null;
+    const accessToken = authSession?.user?.accessToken;
+
+    const cookieStore = await cookies();
+    const guestCartToken = cookieStore.get(GUEST_CART_TOKEN)?.value;
+
+
+    if(!accessToken && !guestCartToken) {
+        const createRes = await createGuestCartToken();
+        if(!createRes.success) {
+            return {
+                success: false,
+                msg: createRes.msg,
+                cartData: null
+            }
+        }
+    }
+    try{
+        const response = await serverGraphqlFetch<AddToCartData,AddToCartVariables>({
+            query: CREATE_ADD_PRODUCT_IN_CART,
+            variables: param,
+            cache: "no-store",
+        });
+        if(!response.error) {
+            const responseData = response.data.createAddProductInCart?.addProductInCart;
+            /** 兜底代码 start*/
+            // 游客加购,然后清空购物车,然后刷新页面,然后再加购,然后再刷新页面,购物车会失效,所以添加兜底代码
+            // 如果之前的cartToken有效,responseCartId和responseCartToken的值是一样的都是cart id; 否则两者不相等
+            const responseCartId = responseData.id;
+            const responseCartToken = responseData.cartToken;
+            if(!accessToken && responseCartId !== responseCartToken) {
+                await setGuestCookie({
+                    guestCartId: responseCartId,
+                    guestToken: responseCartToken,
+                    isGuest: true
+                });
+            }
+            /** 兜底代码 end*/
+
+            return {
+                success: true,
+                msg: '',
+                cartData: response.data
+            }
+
+        } else {
+            return {
+                success: false,
+                msg: response.error.message,
+                cartData: null
+            }
+        }
+    } catch(error) {
+        return {
+            success: false,
+            msg: error instanceof Error ? error.message : 'Add product to cart failed!',
+            cartData: null
+        }
+    }
+};

+ 13 - 0
src/actions/createGuestCartAction.ts

@@ -0,0 +1,13 @@
+"use server";
+
+import {createGuestCartToken} from "@/server-service/guestCartTokenService";
+
+export async function createGuestCartAction() {
+
+    const res = await createGuestCartToken();
+    return {
+        success: res.success,
+        msg: res.msg
+    }
+
+}

+ 48 - 0
src/actions/deleteCartProductAction.ts

@@ -0,0 +1,48 @@
+"use server";
+
+
+import { REMOVE_CART_ITEM } from "@/graphql";
+import { serverGraphqlFetch } from "@utils/bagisto";
+import {deleteGuestCookie} from "@/server-service/guestCartTokenService";
+import { RemoveCartItemData } from "@/types/cart/type";
+
+
+export async function deleteCartProductAction(cartItemId:number) {
+
+    try{
+        const response = await serverGraphqlFetch<RemoveCartItemData,{cartItemId:number}>({
+            query: REMOVE_CART_ITEM,
+            variables: {
+                cartItemId: cartItemId
+            },
+            cache: "no-store",
+        });
+        if(!response.error) {
+            const resCatData = response.data.createRemoveCartItem?.removeCartItem ?? null;
+            
+            if(!resCatData || !resCatData?.itemsQty) {
+                // 购物车空了,删除游客cookie
+                deleteGuestCookie();
+            }
+
+            return {
+                success: true,
+                msg: '',
+                cartData: response.data
+            }
+
+        } else {
+            return {
+                success: false,
+                msg: response.error.message,
+                cartData: null
+            }
+        }
+    } catch(error) {
+        return {
+            success: false,
+            msg: error instanceof Error ? error.message : 'Delete product failed!',
+            cartData: null
+        }
+    }
+};

+ 13 - 0
src/actions/deleteGuestCookieAction.ts

@@ -0,0 +1,13 @@
+"use server";
+
+import {deleteGuestCookie} from "@/server-service/guestCartTokenService";
+
+export async function deleteGuestCookieAction() {
+        
+    await deleteGuestCookie();
+
+    return {
+        success: true,
+        msg: ""
+    };
+}

+ 7 - 0
src/actions/index.ts

@@ -0,0 +1,7 @@
+export {deleteGuestCookieAction} from "./deleteGuestCookieAction";
+export {mergeCartAction} from "./mergeCartAction";
+export {createGuestCartAction} from "./createGuestCartAction";
+export {switchCurrencyAction} from "./switchCurrencyAction";
+export {addProductToCartAction} from "./addProductToCartAction";
+export {deleteCartProductAction} from "./deleteCartProductAction";
+export {logoutAction} from "@/actions/logoutAction";

+ 42 - 0
src/actions/logoutAction.ts

@@ -0,0 +1,42 @@
+"use server";
+
+import {cookies} from "next/headers";
+import { IS_GUEST } from "@/utils/constants";
+import { serverGraphqlFetch } from "@utils/bagisto";
+import {CUSTOMER_LOGOUT} from "@/graphql/customer/mutations";
+import { LogoutData } from "@/types/types";
+
+export async function logoutAction() {
+
+    try {
+
+        const res = await serverGraphqlFetch<LogoutData>({
+            query: CUSTOMER_LOGOUT,
+        });
+        let success = true;
+        let msg = "";
+        if(res.error) {
+            success = false;
+            msg = res.error.message;
+
+        } else {
+            success = res.data?.createLogout?.logout?.success ?? false;
+            msg = res.data?.createLogout?.logout?.message ?? "";
+            if(success) {
+                const cookieStore = await cookies();
+                // 设置游客状态
+                cookieStore.set(IS_GUEST, "true");
+            }
+        }
+
+        return {
+            success,
+            message: msg,
+        };
+    } catch (error: unknown) {
+        return {
+            success: false,
+            message: error instanceof Error ? error.message : "Something went wrong",
+        };
+    }
+}

+ 12 - 11
src/actions/mergeCartAction.ts

@@ -3,7 +3,7 @@
 
 import {cookies} from "next/headers";
 import { GUEST_CART_ID, GUEST_CART_TOKEN, IS_GUEST } from "@/utils/constants";
-import { bagistoFetch } from "@utils/bagisto";
+import { serverGraphqlFetch } from "@utils/bagisto";
 import { CREATE_MERGE_CART } from "@/graphql";
 import { CreateMergeCartData } from "@/types/cart/type";
 
@@ -26,23 +26,24 @@ export async function mergeCartAction(){
     }
     try{
         // 调 Bagisto merge cart mutation
-        const response = await bagistoFetch<{
-            data: CreateMergeCartData,
-            variables: {cartId:number}
-        }>({
+        const response = await serverGraphqlFetch<CreateMergeCartData,{cartId:number}>({
             query: CREATE_MERGE_CART,
             variables: {
                 cartId: Number(guestCartId)
             },
             cache: "no-store",
         });
-        // cookieStore.delete(GUEST_CART_ID);
-        // cookieStore.delete(GUEST_CART_TOKEN);
-        // cookieStore.set(IS_GUEST,"false");
+        const cartData = response.data.createMergeCart?.mergeCart ?? null;
+        let success = true;
+        let msg = "";
+        if(response.error) {
+            success = false;
+            msg = response.error.message;
+        }
         return {
-            cartDetail: response.body.data.createMergeCart.mergeCart,
-            success:true,
-            msg: ''
+            cartDetail: cartData,
+            success: success,
+            msg: msg
         };
     } catch(e: any) {
         return {

+ 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 = [

+ 4 - 4
src/app/(checkout)/checkout/_components/CheckoutCoupon/CheckoutCoupon.tsx

@@ -34,11 +34,11 @@ export default function CheckoutCoupon({
 
         overlayLoading.start();
         const saveRes = await saveCheckoutCart({ 
-            shippingMethod: '-1',
-            paymentMethod: '-1',
+            // shippingMethod: '-1',
+            // paymentMethod: '-1',
             couponCode: code,// '' 表示取消应用
-            giftCardNumber: '-1',
-            memberDiscount: '-1'
+            // giftCardNumber: '-1',
+            // memberDiscount: '-1'
         });
         overlayLoading.stop();
         if(!saveRes.error) {

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

@@ -32,6 +32,8 @@ import PromotionsDetails from "./PromotionsDetails";
 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";
 /***
  * 保存完地址之后要重新获取运输方式和支付方式
  * 变更运输方式后重新获取支付方式
@@ -127,11 +129,11 @@ export default function CheckoutWrapper({
    
         overlayLoading.start();
         const saveRes = await saveCheckoutCart({ 
-            shippingMethod: '-1',
+            // shippingMethod: '-1',
             paymentMethod: method,
-            couponCode: '-1',
-            giftCardNumber: '-1',
-            memberDiscount: '-1'
+            // couponCode: '-1',
+            // giftCardNumber: '-1',
+            // memberDiscount: '-1'
         });
         overlayLoading.stop();
         if(!saveRes.error) {
@@ -151,10 +153,10 @@ export default function CheckoutWrapper({
         overlayLoading.start();
         const saveRes = await saveCheckoutCart({ 
             shippingMethod: method,
-            paymentMethod: '-1',
-            couponCode: '-1',
-            giftCardNumber: '-1',
-            memberDiscount: '-1'
+            // paymentMethod: '-1',
+            // couponCode: '-1',
+            // giftCardNumber: '-1',
+            // memberDiscount: '-1'
         });
         overlayLoading.stop();
         if(!saveRes.error) {
@@ -224,11 +226,11 @@ export default function CheckoutWrapper({
         if(cartData.paymentMethod === null) {
             const pmethod = methodPaymentRef.current.getSelectPaymentMethod();
             const saveRes = await saveCheckoutCart({ 
-                shippingMethod: '-1',
+                // shippingMethod: '-1',
                 paymentMethod: pmethod,
-                couponCode: '-1',
-                giftCardNumber: '-1',
-                memberDiscount: '-1'
+                // couponCode: '-1',
+                // giftCardNumber: '-1',
+                // memberDiscount: '-1'
             });
             if(!saveRes.error) {
                 if(saveRes.data) {
@@ -354,6 +356,14 @@ export default function CheckoutWrapper({
                     onShipMethodChange={handleShippingMethodChange}  
                 />
             </div>
+            {cartData.shippingInsuranceEnabled &&
+                <div className="mt-3 box-border px-4">
+                    <LostInsurance 
+                        cartData={cartData}
+                        onSwitchInsurance={onCartDataChange}
+                    />
+                </div>
+            }
             <div className="mt-6 box-border px-4">
                 <CheckoutCoupon 
                     couponCode={cartData.couponCode || ''}
@@ -361,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 &&

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

@@ -164,12 +164,24 @@ 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>
                         <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.shippingAmount}</p>
                     </div>
                 }
+                {Number(orderDetail?.shippingInsuranceAmount) > 0 &&
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-12 leading-ly-20">Shipping</p>
+                        <p className="text-ly-12 leading-ly-20">{orderSymbol}{Number(orderDetail.shippingInsuranceAmount)}</p>
+                    </div>
+                }
                 {orderDetail.taxAmount !== 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Tax</p>
@@ -200,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>

+ 4 - 4
src/app/(checkout)/checkout/_components/GiftCard/CheckoutGiftCardGate.tsx

@@ -86,11 +86,11 @@ export default function CheckoutGiftCardGate({
         const paramCode = code === giftcardNumber ? '' : code; // '' 表示取消应用
         overlayLoading.start();
         const saveRes = await saveCheckoutCart({ 
-            shippingMethod: '-1',
-            paymentMethod: '-1',
-            couponCode: '-1',
+            // shippingMethod: '-1',
+            // paymentMethod: '-1',
+            // couponCode: '-1',
             giftCardNumber: paramCode,
-            memberDiscount: '-1'
+            // memberDiscount: '-1'
         });
         overlayLoading.stop();
         if(!saveRes.error) {

+ 88 - 0
src/app/(checkout)/checkout/_components/LostInsurance/LostInsurance.tsx

@@ -0,0 +1,88 @@
+"use client";
+
+
+// import {useConfig} from "@/utils/hooks/useConfig";
+import { useCustomToast } from "@/utils/hooks/useToast";
+import {useSaveCheckoutCart} from "@/utils/hooks/useSaveCheckoutCart";
+import { formatCartDetail } from "@/utils/cartDetailTools";
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
+import {CartDetail} from "@/types/cart/type";
+
+/**
+ * 先通过 shippingInsuranceEnabled 判断网站是否开通丢件险功能
+ * 再通过 useShippingInsurance 判断用户是否勾选丢件险 
+ */
+
+export default function LostInsurance({
+    cartData,
+    onSwitchInsurance
+}: {
+    cartData: CartDetail
+    onSwitchInsurance: (payload:CartDetail) => void;
+}) {
+
+    // const {getCurrentCurrencyItem} = useConfig();
+    // const currentCurrency = getCurrentCurrencyItem();
+    // const currencySymbol = currentCurrency.symbol;
+    const { showToast } = useCustomToast();
+    const {saveCheckoutCart} = useSaveCheckoutCart();
+    const clickSwitchButton = async (bool: boolean) => {
+        const isInsurance = bool ? '1' : '';
+        await setInsurance(isInsurance);
+    };
+    const setInsurance = async (insurance: string) => {
+        overlayLoading.start();
+        const saveRes = await saveCheckoutCart({ 
+            useShippingInsurance: insurance
+        });
+        overlayLoading.stop();
+        if(!saveRes.error) {
+            if(saveRes.data) {
+                const newCartDetail = formatCartDetail(saveRes.data);
+                onSwitchInsurance(newCartDetail);
+            }
+            return true;
+        } else {
+            showToast(saveRes.msg, 'danger');
+            return false;
+        }
+    };
+
+    let title = "";
+    if(cartData.shippingInsuranceType === "percentage") {
+        title = `${cartData.shippingInsuranceValue}% Package Insurance`;
+    } else if(cartData.shippingInsuranceType === "fixed") {
+        //title = `${currencySymbol}${cartData.shippingInsuranceAmount} Package Insurance`;
+        title = `Fixed $${cartData.shippingInsuranceValue} Package Insurance`;
+    }
+    return (<>
+        <div className="relative box-border w-full bg-[#F9F9F9] p-3">
+            <div className="flex justify-between items-center">
+                <span className="text-ly-13">
+                    {title}
+                </span>
+                <span className="text-ly-12">
+                    {cartData.formattedShippingInsuranceAmount}
+                </span>
+            </div>
+            <div className="mt-2 w-57 text-ly-12 leading-3.5 text-ly-placeholder">
+                Increased compensation for courier packages issued within 30 days.
+            </div>
+            <div className="absolute bottom-3 right-3 flex justify-between items-center">
+                <label className="group relative w-9 h-5.5 flex-none">
+                    <input className="absolute w-full h-full top-0 left-0 opacity-0 z-10"
+                        type="checkbox" checked={cartData.useShippingInsurance}
+                        onChange={(e) => clickSwitchButton(e.target.checked)}
+                    />
+                    <span className="transition-all box-border block px-0.5 w-full h-full rounded-full flex items-center bg-[#c2c2c2] group-has-[:checked]:bg-[#363636]">
+                        <span className="transition-all block w-4.5 h-4.5 rounded-full bg-white group-has-[:checked]:ml-3.5"></span>
+                    </span>
+                </label>
+                
+            </div>
+
+            
+        </div>
+        
+    </>);
+}

+ 22 - 1
src/app/(checkout)/checkout/_components/PromotionsDetails.tsx

@@ -8,7 +8,10 @@ export default function PromotionsDetails({cartData}:{
     cartData: CartDetail
 }) { 
 
-
+    let title = "Insurance for Lost";
+    if(cartData.shippingInsuranceType === "percentage") {
+        title = `Insurance for Lost (${cartData.shippingInsuranceValue}%)`;
+    }
     return (
         <div className="w-full box-border">
             <ul className="w-full">
@@ -22,12 +25,30 @@ 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>
+                        <span>{cartData.formattedShippingInsuranceAmount}</span>
+                    </li>
+                }
                 {cartData.vipPlusAmount !== 0 &&
                     <li className="flex justify-between items-center text-ly-12 mt-4 first:mt-0">
                         <span>Vip Plus Fee</span>
                         <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>
+            }
+
+        </>
+    );
+}

+ 5 - 5
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 = {
@@ -65,10 +65,10 @@ export default function CheckoutVipPlus({
     const setVipPlus = async (paramVip: string) => {
         overlayLoading.start();
         const saveRes = await saveCheckoutCart({ 
-            shippingMethod: '-1',
-            paymentMethod: '-1',
-            couponCode: '-1',
-            giftCardNumber: '-1',
+            // shippingMethod: '-1',
+            // paymentMethod: '-1',
+            // couponCode: '-1',
+            // giftCardNumber: '-1',
             memberDiscount: paramVip
         });
         overlayLoading.stop();

+ 3 - 3
src/app/(public)/customer/login/page.tsx

@@ -1,7 +1,7 @@
 import LoginForm from "@components/customer/LoginForm";
 import { generateMetadataForPage } from "@utils/helper";
 import { staticSeo } from "@utils/metadata";
-import { SessionManager } from "@/providers";
+
 
 export const revalidate = 60;
 
@@ -11,8 +11,8 @@ export async function generateMetadata() {
 
 export default async function LoginPage() {
   return (
-    <SessionManager>
+
       <LoginForm />
-    </SessionManager>
+
   );
 }

+ 23 - 2
src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx

@@ -5,6 +5,7 @@ import Link from "next/link";
 import Image from "next/image";
 import {FetchGraphqlResult} from "@/types/graphqlFetch/type";
 import { OrderDetailsData,ProductItemAdditional } from "@/types/customer/type";
+import {deleteGuestCookieAction} from "@/actions";
 import { useConfig } from "@utils/hooks/useConfig";
 import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
 import Faqs from "./Faqs";
@@ -59,8 +60,10 @@ export default function OrderDetailWrapper({
         /**Cookies can only be modified in a Server Action or Route Handler. Read more: https://nextjs.org/docs/app/api-reference/functions/cookies#options */
 
         if(isGuest) {
-            fetch("/api/clearCartCookie",{
-                method:"POST"
+            deleteGuestCookieAction().then((res) => {
+                console.info(res);
+            }).catch((err) => {
+                console.error(err);
             });
         }
     }, [isGuest]);
@@ -176,12 +179,24 @@ 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>
                         <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{orderDetailRes?.shippingAmount}</p>
                     </div>
                 }
+                {Number(orderDetailRes?.shippingInsuranceAmount) > 0 &&
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-12 leading-ly-20">Insurance of Lost</p>
+                        <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{Number(orderDetailRes?.shippingInsuranceAmount)}</p>
+                    </div>
+                }
                 {!!orderDetailRes?.taxAmount &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Tax</p>
@@ -194,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>

+ 4 - 3
src/app/(public)/paymentresult/result/page.tsx

@@ -16,13 +16,14 @@ import { GUEST_CART_TOKEN } from "@/utils/constants";
 export default async function SuccessPage({searchParams}: {
   searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
 })  {
+    const session = await getServerSession(authOptions);// 游客是null
+    const isGuest = session === null;
     const cookieStore = await cookies();
     // 登录用户cookie里没有GUEST_CART_ID
-    if(!cookieStore.get(GUEST_CART_TOKEN)) {
+    if(isGuest && !cookieStore.get(GUEST_CART_TOKEN)) {
         redirect('/', RedirectType.replace);
     }
-    const session = await getServerSession(authOptions);// 游客是null
-    const isGuest = session === null;
+    
     
     const query = await searchParams; // awx_return_result
     const orderid = query?.orderId;

+ 2 - 8
src/app/(public)/product/_components/ProductAddToCart.tsx

@@ -75,14 +75,12 @@ type VariantPriceInfo = {
 
 export function ProductAddToCart({
   isAvailable,
-  isLoading,
   onAddToCart,
   onBuyNow,
   priceInfo, // 父组件传入价格数据
   currencyCode
 }: {
   isAvailable: boolean;
-  isLoading: boolean;
   onAddToCart: () => void;
   onBuyNow: () => void;
   priceInfo: VariantPriceInfo; // 价格数据源
@@ -158,9 +156,7 @@ export function ProductAddToCart({
           <button
             onClick={onBuyNow}
             type="button"
-            className={clsx(btnClass, "flex-1 bg-ly-deepgreen", {
-              "opacity-25": isLoading,
-            })}
+            className={clsx(btnClass, "flex-1 bg-ly-deepgreen")}
           >
             Buy Now
           </button>
@@ -168,9 +164,7 @@ export function ProductAddToCart({
           <button
             onClick={onAddToCart}
             type="button"
-            className={clsx(btnClass, "flex-1 bg-ly-middlegreen", {
-              "opacity-25": isLoading,
-            })}
+            className={clsx(btnClass, "flex-1 bg-ly-middlegreen")}
           >
             Add to Cart
           </button>

+ 29 - 26
src/app/(public)/product/_components/ProductInformation.tsx

@@ -23,6 +23,7 @@ import {
   findNearestAvailableVariant,
 } from "@/utils/variantTools";
 import { Price } from "@components/theme/ui/Price";
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 
 export function ProductInformation({
   name,
@@ -39,7 +40,7 @@ export function ProductInformation({
   isSaleable: string | undefined;
   shortDescription:string;
 }) {
-  const { isCartLoading, onAddToCart } = useAddProduct();
+  const { appendProductToCart } = useAddProduct();
   const { showToast } = useCustomToast();
 
   // 记录当前哪个选项组刚刚被点击(用于实现“点击组内全部可点”)
@@ -199,41 +200,44 @@ export function ProductInformation({
     }
   };
 
-  async function addProductToCart(action: string = "addtocart") {
-    const params = {
-      productId: String(productId),
-      quantity: productQty,
-      variantId: currentVariantInfo.variant?._id,
-    };
-    const res = await onAddToCart(params);
-    console.log("onAddToCart result --- ", res);
-    if (action === "buynow") {
-      if (res) {
-        const responseData = res.data?.createAddProductInCart?.addProductInCart;
-        if (responseData && responseData.success) {
-          redirect("/checkout?step=address", RedirectType.push);
+  async function addProductToCart(action:string = 'addtocart') {
+        overlayLoading.start();
+        const res = await appendProductToCart({
+            productId: productId, 
+            quantity: productQty,
+            variantId: currentVariantInfo.variant?._id,
+        });
+        console.log('onAddToCart result --- ', res);
+        overlayLoading.stop();
+        if(action === 'buynow') {
+            if(res.data) {
+                const responseData = res.data;
+                if(responseData.success) {
+                    redirect('/checkout?step=address', RedirectType.push);
+                }
+            }
         }
-      }
-    }
-  }
+  } 
 
   const addToCartHandler = async () => {
-    if (!isCurrentSelectionAvailable) {
-      showToast("The selected options are not available!", "warning");
-      return;
-    }
+   
+        if(!isCurrentSelectionAvailable) {
+            showToast("The selected options are not available!", "warning");
+            return;
+        }
 
-    if (currentVariantInfo.variant && !isCartLoading) {
-      addProductToCart();
-    }
+        if(currentVariantInfo.variant) {
+            addProductToCart();
+        }
   };
 
+
   const buyNowHandler = () => {
     if (!isCurrentSelectionAvailable) {
       showToast("The selected options are not available!", "warning");
       return;
     }
-    if (currentVariantInfo.variant && !isCartLoading) {
+    if (currentVariantInfo.variant) {
       addProductToCart("buynow");
     }
   };
@@ -428,7 +432,6 @@ export function ProductInformation({
       </div>
       <ProductAddToCart
         isAvailable={isCurrentSelectionAvailable}
-        isLoading={isCartLoading}
         onAddToCart={addToCartHandler}
         onBuyNow={buyNowHandler}
         currencyCode={currencyCode}

+ 0 - 20
src/app/api/clearCartCookie/route.ts

@@ -1,20 +0,0 @@
-import { NextResponse } from "next/server";
-import {
-  GUEST_CART_TOKEN,
-  GUEST_CART_ID,
-} from "@/utils/constants";
-
-
-export async function POST() {
-
-    const response = NextResponse.json({
-        success:true
-    });
-
-
-    response.cookies.delete(GUEST_CART_TOKEN);
-    response.cookies.delete(GUEST_CART_ID);
-
-
-    return response;
-}

+ 0 - 197
src/components/cart/AddToCart.tsx

@@ -1,197 +0,0 @@
-"use client";
-
-import { MinusIcon, PlusIcon } from "@heroicons/react/24/outline";
-import clsx from "clsx";
-import { useSearchParams } from "next/navigation";
-import { useForm, useWatch } from "react-hook-form";
-import { ConfigurableProductIndexData } from "@/types/types";
-import { useAddProduct } from "@utils/hooks/useAddToCart";
-import LoadingDots from "@components/common/icons/LoadingDots";
-import { getVariantInfo } from "@utils/hooks/useVariantInfo";
-import { safeParse } from "@utils/helper";
-import { ProductSwatchReview } from "@/types/category/type";
-
-interface AddToCartFormData {
-  quantity: number;
-  isBuyNow: boolean;
-}
-
-function SubmitButton({
-  selectedVariantId,
-  pending,
-  type,
-  isSaleable,
-}: {
-  selectedVariantId: boolean;
-  pending: boolean;
-  type: string;
-  isSaleable: string;
-}) {
-  const buttonClasses =
-    "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";
-  const disabledClasses = "cursor-wait opacity-60";
-
-  if (!isSaleable || isSaleable === "") {
-    return (
-      <button
-        aria-disabled
-        aria-label="Out of stock"
-        type="button"
-        disabled
-        className={clsx(buttonClasses, " opacity-60 !cursor-not-allowed")}
-      >
-        Out of Stock
-      </button>
-    );
-  }
-
-  if (!selectedVariantId && type === "configurable") {
-    return (
-      <button
-        aria-disabled
-        aria-label="Please select an option"
-        type="button"
-        disabled={!selectedVariantId}
-        className={clsx(buttonClasses, " opacity-60 !cursor-not-allowed")}
-      >
-        Add To Cart
-      </button>
-    );
-  }
-
-  return (
-    <button
-      aria-disabled={pending}
-      aria-label="Add to cart"
-      type="submit"
-      className={clsx(buttonClasses, {
-        "hover:opacity-90": true,
-        [disabledClasses]: pending,
-      })}
-      onClick={(e: React.MouseEvent<HTMLButtonElement>) => {
-        if (pending) e.preventDefault();
-      }}
-    >
-      <div className="absolute left-0 ml-4">
-        {pending ? <LoadingDots className="mb-3 bg-white" /> : ""}
-      </div>
-      Add To Cart
-    </button>
-  );
-}
-
-export function AddToCart({
-  productSwatchReview,
-  index,
-  productId,
-  userInteracted,
-}: {
-  productSwatchReview: ProductSwatchReview;
-  productId: string;
-  index: ConfigurableProductIndexData[];
-  userInteracted: boolean;
-}) {
-  const isSaleable = productSwatchReview?.isSaleable || "";
-  const { onAddToCart, isCartLoading } = useAddProduct();
-  const { handleSubmit, setValue, control, register } = useForm<AddToCartFormData>({
-    defaultValues: {
-      quantity: 1,
-      isBuyNow: false,
-    },
-  });
-
-  const quantity = useWatch({
-    control,
-    name: "quantity",
-  });
-
-  const increment = (e: React.MouseEvent) => {
-    e.preventDefault();
-    e.stopPropagation();
-    setValue("quantity", Number(quantity) + 1);
-  };
-
-  const decrement = (e: React.MouseEvent) => {
-    e.preventDefault();
-    e.stopPropagation();
-    setValue("quantity", Math.max(1, Number(quantity) - 1));
-  };
-
-  const searchParams = useSearchParams();
-  const type = productSwatchReview?.type;
-
-  const superAttributes = productSwatchReview?.superAttributeOptions
-    ? safeParse(productSwatchReview.superAttributeOptions)
-    : productSwatchReview?.superAttributes?.edges?.map(
-        (e) => e.node,
-      ) || [];
-
-  const isConfigurable = superAttributes.length > 0;
-
-  const { productid: selectedVariantId, Instock: checkStock } = getVariantInfo(
-    isConfigurable,
-    searchParams.toString(),
-    superAttributes,
-    JSON.stringify(index),
-  );
-  const buttonStatus = !!selectedVariantId;
-
-  const actionWithVariant = async (data: AddToCartFormData) => {
-    const pid =
-      type === "configurable"
-        ? String(selectedVariantId)
-        : (String(productId).split("/").pop() ?? "");
-    onAddToCart({
-      productId: pid,
-      quantity: data.quantity,
-    });
-  };
-
-  return (
-    <>
-      {!checkStock && type === "configurable" && userInteracted && (
-        <div className="gap-1 px-2 py-1 my-2 font-bold text-red-500 dark:text-red-400">
-          <h1>NO STOCK AVAILABLE</h1>
-        </div>
-      )}
-      <form className="flex gap-x-4" onSubmit={handleSubmit(actionWithVariant)}>
-        <div className="flex items-center justify-center">
-          <div className="flex items-center rounded-full border-2 border-blue-500">
-            <div
-              aria-label="Decrease quantity"
-              role="button"
-              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%]"
-              onClick={decrement}
-            >
-              <MinusIcon className="h-4 w-4" />
-            </div>
-
-            <input
-              type="hidden"
-              {...register("quantity", { valueAsNumber: true })}
-            />
-
-            <div className="flex h-12 min-w-[4rem] items-center justify-center px-2 font-medium text-gray-800 dark:text-white">
-              {quantity}
-            </div>
-
-            <div
-              aria-label="Increase quantity"
-              role="button"
-              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%]"
-              onClick={increment}
-            >
-              <PlusIcon className="h-4 w-4" />
-            </div>
-          </div>
-        </div>
-        <SubmitButton
-          pending={isCartLoading}
-          selectedVariantId={buttonStatus}
-          type={type || ""}
-          isSaleable={isSaleable}
-        />
-      </form>
-    </>
-  );
-}

+ 0 - 137
src/components/catalog/product/ProductDescription.tsx

@@ -1,137 +0,0 @@
-"use client";
-import { Price } from "@/components/theme/ui/Price";
-import { Rating } from "@/components/common/Rating";
-import { AddToCart } from "@/components/cart/AddToCart";
-import { VariantSelector } from "./VariantSelector";
-import { ProductMoreDetails } from "./ProductMoreDetail";
-import { useState } from "react";
-import { getVariantInfo } from "@utils/hooks/useVariantInfo";
-import { useSearchParams } from "next/navigation";
-import Prose from "@components/theme/search/Prose";
-import { ProductData , ProductReviewNode } from "../type";
-import { safeCurrencyCode, safePriceValue, safeParse } from "@utils/helper";
-import Link from "next/link";
-
-export function ProductDescription({
-  product,
-  reviews,
-  totalReview,
-  productSwatchReview,
-  avgRating
-}: {
-  product: ProductData;
-  slug: string;
-  reviews: ProductReviewNode[] ; 
-  avgRating : number ;
-  totalReview: number;
-  productSwatchReview: any;
-}) {
-  const priceValue = safePriceValue(product);
-  const currencyCode = safeCurrencyCode(product);
-  const configurableProductIndexData = (safeParse(
-    productSwatchReview?.combinations
-  ) || []) as never[];
-  const searchParams = useSearchParams();
-  const [userInteracted, setUserInteracted] = useState(false);
-
-  const superAttributes = productSwatchReview?.superAttributeOptions
-    ? safeParse(productSwatchReview.superAttributeOptions)
-    : productSwatchReview?.superAttributes?.edges?.map(
-      (e: { node: any }) => e.node
-    ) || [];
-
-  const variantInfo = getVariantInfo(
-    product?.type === "configurable",
-    searchParams.toString(),
-    superAttributes,
-    productSwatchReview?.combinations
-  );
-
-  const additionalData =
-    productSwatchReview?.attributeValues?.edges?.map(
-      (e: { node: any }) => e.node
-    ) || [];
-  const [expandedKeys, setExpandedKeys] = useState<Set<string>>(new Set());
-  const handleReviewClick = () => {
-    setExpandedKeys(new Set(["2"]));
-  };
-  
-  return (
-    <>
-      <div className="mb-2 flex flex-col pb-6">
-        {/* Breadcrumb */}
-        <div className="hidden lg:flex flex-col gap-3 shrink-0 mb-2">
-          <Link
-            href="/"
-            className="w-fit text-sm font-medium text-nowrap relative text-neutral-500 before:absolute before:bottom-0 before:left-0 before:h-px before:w-0 before:bg-current before:transition-all before:duration-300 before:content-[''] hover:text-black hover:before:w-full dark:text-neutral-400 dark:hover:text-neutral-300"
-          >
-            Home /
-          </Link>
-        </div>
-        <h1 className="font-outfit text-2xl md:text-3xl lg:text-4xl font-semibold">
-          {product?.name || ""}
-        </h1>
-
-        <div className="flex w-auto justify-between items-baseline gap-y-2 py-4 xs:flex-row xs:gap-y-0 sm:py-6 flex-wrap">
-          <div className="flex gap-4 items-baseline">
-            {product?.type === "configurable" && (
-              <p className="text-base text-gray-600 dark:text-gray-400">
-                As low as
-              </p>
-            )}
-            {product?.type === "simple" ? (
-              <>
-                <Price
-                  amount={String(product?.minimumPrice)}
-                  currencyCode={currencyCode}
-                  className="font-outfit text-xl md:text-2xl font-semibold"
-                />
-              </>
-            ) : (
-              <Price
-                amount={String(priceValue)}
-                currencyCode={currencyCode}
-                className="font-outfit text-xl md:text-2xl font-semibold"
-              />
-            )}
-          </div>
-
-          <Rating
-            length={5}
-            star={avgRating}
-            reviewCount={totalReview}
-            className="mt-2"
-            onReviewClick={handleReviewClick}
-          />
-        </div>
-      </div>
-
-      <VariantSelector
-        variants={variantInfo?.variantAttributes}
-        setUserInteracted={setUserInteracted}
-        possibleOptions={variantInfo.possibleOptions}
-      />
-
-      {product?.shortDescription ? (
-        <Prose className="mb-6 text-base text-selected-black dark:text-white font-light" html={product.shortDescription} />
-      ) : null}
-
-      <AddToCart
-        index={configurableProductIndexData}
-        productId={product?.id || ""}
-        productSwatchReview={productSwatchReview}
-        userInteracted={userInteracted}
-      />
-
-      <ProductMoreDetails
-        additionalData={additionalData}
-        description={product?.description ?? ""}
-        reviews={Array.isArray(reviews) ? reviews : []}
-        totalReview={totalReview}
-        productId={product?.id ?? ""}
-        expandedKeys={expandedKeys}
-        setExpandedKeys={setExpandedKeys}
-      />
-    </>
-  );
-}

+ 0 - 31
src/components/catalog/product/ProductInfo.tsx

@@ -1,31 +0,0 @@
-
-import { getAverageRating } from "@utils/helper";
-import { ProductData } from "../type";
-import { ProductDescription } from "./ProductDescription";
-import { getProductWithSwatchAndReview } from "@/utils/hooks/getProductSwatchAndReview";
-import { ProductReview } from "@/types/category/type";
-import { getProductReviews } from "@utils/hooks/getProductReviews";
-
-export default async function ProductInfo({
-  product,
-  slug,
-  reviews,
-}: {
-  product: ProductData;
-  slug: string;
-  reviews: ProductReview[];
-}) {
-  const productSwatchReview = await getProductWithSwatchAndReview(slug);
-  const getAllreviews = await getProductReviews(product?.id?.split("/").pop() || '')
-  
-  return (
-    <ProductDescription
-      product={product}
-      productSwatchReview={productSwatchReview}
-      slug={slug}
-      reviews={getAllreviews}
-      totalReview={reviews.length}
-      avgRating = {getAverageRating(reviews)}
-    />
-  );
-}

+ 19 - 10
src/components/common/AddToCartModal/AddToCartModal.tsx

@@ -9,6 +9,7 @@ import {
     clearAddToCartProduct 
 } from '@/store/slices/addToCartDialogSlice';
 import { useAddProduct } from "@utils/hooks/useAddToCart";
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import {
   Drawer,
   DrawerContent,
@@ -38,7 +39,7 @@ export default function AddToCartModal() {
     const dispatch = useAppDispatch();
     const {isOpen, product} = useAppSelector((state) => state.addToCartDialog);
 
-    const { isCartLoading, onAddToCart } = useAddProduct();
+    const { appendProductToCart } = useAddProduct();
     const { showToast } = useCustomToast();
 
     const images = useMemo(() => {
@@ -180,14 +181,23 @@ export default function AddToCartModal() {
     };
 
     async function addProductToCart() {
-            
+        if(!product) {
+            showToast("Get product detail failed!", "danger");
+            return;
+        }
+        overlayLoading.start(); 
         const params = { 
-            productId: String(product?._id), 
+            productId: product._id, 
             quantity: productQty,
             variantId: currentVariantInfo.variant?._id,
         };
-        const res = await onAddToCart(params);
-        console.log('onAddToCart run ----- 1');
+        const res = await appendProductToCart({
+            productId: params.productId,
+            quantity: params.quantity,
+            variantId: params.variantId
+        });
+        console.log('onAddToCart run ----- 1',res);
+        overlayLoading.stop();
         return res;
     } 
 
@@ -197,7 +207,7 @@ export default function AddToCartModal() {
             return;
         }
 
-        if(currentVariantInfo.variant && !isCartLoading) {
+        if(currentVariantInfo.variant) {
             await addProductToCart();
             callback();
         }
@@ -208,11 +218,11 @@ export default function AddToCartModal() {
             return;
         }
 
-        if(currentVariantInfo.variant && !isCartLoading) {
+        if(currentVariantInfo.variant) {
             const res = await addProductToCart();
             callback();
-            if(res) {
-                const responseData = res.data?.createAddProductInCart?.addProductInCart;
+            if(res && !res.error) {
+                const responseData = res.data;
                 if(responseData && responseData.success) {
                     redirect('/checkout', RedirectType.push);
                 }
@@ -295,7 +305,6 @@ export default function AddToCartModal() {
                             <div>
                                 <FooterBtnInAddToCartModal 
                                     isAvailable={isCurrentSelectionAvailable} 
-                                    isLoading={isCartLoading}
                                     onAddToCart={()=> addToCartHandler(onClose)}
                                     onBuyNow={()=> buyNowHandler(onClose)}
                                 />

+ 3 - 5
src/components/common/AddToCartModal/FooterBtnInAddToCartModal.tsx

@@ -1,15 +1,13 @@
 "use client";
 
 import clsx from "clsx";
-import { LoadingSpinner } from "@components/common/LoadingSpinner";
+
 export default function FooterBtnInAddToCartModal({
     isAvailable,
-    isLoading,
     onBuyNow,
     onAddToCart
 }: {
     isAvailable: boolean; 
-    isLoading: boolean;
     onBuyNow: () => void,
     onAddToCart: () => void
 }) {
@@ -22,13 +20,13 @@ export default function FooterBtnInAddToCartModal({
                     <button className={clsx(btnClass,'bg-ly-deepgreen')}
                         onClick={onBuyNow}
                     >
-                        Buy Now {isLoading && <LoadingSpinner className="ml-2" />}
+                        Buy Now
                         
                     </button>
                     <button className={clsx(btnClass, 'bg-ly-middlegreen')}
                         onClick={onAddToCart}
                     >
-                        Add to Cart {isLoading && <LoadingSpinner className="ml-2" />}
+                        Add to Cart
                     </button>
                 </>)
             :

+ 1 - 1
src/components/common/CurrencySwitch/CurrencySwitch.tsx

@@ -7,7 +7,7 @@ import Image from "next/image";
 import { useApolloClient } from "@apollo/client/react";
 // import { useCustomToast } from "@utils/hooks/useToast";
 import { useConfig } from "@utils/hooks/useConfig";
-import {switchCurrencyAction} from "@/actions/switchCurrencyAction";
+import {switchCurrencyAction} from "@/actions";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 
 function getCurrencyFlag(currencyCode: string) {

+ 1 - 1
src/components/common/LoginModal/LoginModal.tsx

@@ -6,7 +6,7 @@ import { signIn } from "next-auth/react";
 import clsx from "clsx";
 import { useForm, Controller } from "react-hook-form";
 import { EMAIL_REGEX, IS_VALID_INPUT, IS_VALID_FULL_PHONE } from "@utils/constants";
-import {mergeCartAction} from "@/actions/mergeCartAction";
+import {mergeCartAction} from "@/actions";
 import { useAppDispatch } from "@/store/hooks";
 import { updateCart } from "@/store/slices/cart-slice";
 import { useCustomToast } from "@/utils/hooks/useToast";

+ 6 - 4
src/components/common/button/ReviewButton.tsx

@@ -1,12 +1,14 @@
+"use client";
 
-import { getIsGuest } from "@utils/cookie-tools";
+import { getSession } from "next-auth/react";
 import { useRouter } from "next/navigation";
 
 export const ReviewButton = ({ setShowForm, className }: { setShowForm: (show: boolean) => void, className?: string }) => {
-    const IsGuest = getIsGuest();
+
     const router = useRouter();
-    const handleAddReview = () => {
-        if (IsGuest) {
+    const handleAddReview = async () => {
+        const session = await getSession()
+        if (!session) {
             router.push("/customer/login");
         } else {
             setShowForm(true);

+ 2 - 2
src/components/common/icons/cart/DeleteItemButton.tsx

@@ -43,10 +43,10 @@ interface CartItemEdge {
 }
 
 export function DeleteItemButton({ item }: { item: CartItemEdge }) {
-  const { onAddToRemove, isRemoveLoading } = useAddProduct();
+  const { deleteProductFromCart, isRemoveLoading } = useAddProduct();
   const itemId = item?.node?.id;
   const handleRemoveCart = () => {
-    onAddToRemove(itemId);
+    deleteProductFromCart(Number(itemId));
   };
 
   return (

+ 14 - 51
src/components/customer/LoginForm.tsx

@@ -1,7 +1,7 @@
 "use client";
 
 import clsx from "clsx";
-import { getSession, signIn } from "next-auth/react";
+import { signIn } from "next-auth/react";
 import Image from "next/image";
 import Link from "next/link";
 import { useRouter } from "next/navigation";
@@ -10,18 +10,9 @@ import { Button } from "@components/common/button/Button";
 import { EMAIL_REGEX, SIGNIN_IMG } from "@/utils/constants";
 import InputText from "@components/common/form/Input";
 import { useCustomToast } from "@/utils/hooks/useToast";
-import { useMergeCart } from "@utils/hooks/useMergeCart";
-
-import { 
-  setCookie, 
-  getGuestCartToken,
-  getGuestCartId,
-  deleteGuestCookie
-} from "@/utils/cookie-tools";
-
+import { mergeCartAction, deleteGuestCookieAction } from "@/actions";
 import { useAppSelector } from "@/store/hooks";
 import { useCartDetail } from "@utils/hooks/useCartDetail";
-import { IS_GUEST } from "@/utils/constants";
 
 type LoginFormInputs = {
   username: string;
@@ -32,7 +23,7 @@ export default function LoginForm() {
   const router = useRouter();
   const { showToast } = useCustomToast();
   const { getCartDetail } = useCartDetail()
-  const { mergeCart } = useMergeCart();
+
   const cart = useAppSelector((state) => state.cartDetail.cart);
   const {
     register,
@@ -45,14 +36,7 @@ export default function LoginForm() {
 
   const onSubmit: SubmitHandler<LoginFormInputs> = async (data) => {
     try {
-      // First, handle cart merging before sign in
-      const guestCartId = getGuestCartId();
-      const guestCartToken = getGuestCartToken();
-
-      /**
-       * @todo 使用 signInAuth 重写
-       * result: {error: null,ok: true,status: 200,url: "http://localhost:3001/"}
-       */
+
       const result = await signIn("credentials", {
         redirect: false,
         ...data,
@@ -67,38 +51,17 @@ export default function LoginForm() {
       showToast("Welcome! Successfully logged in.", "success");
 
 
-      const session = await getSession();
-      const userToken: string | undefined = session?.user?.accessToken;
-
-      if (!userToken) {
-        console.warn("No API token available in session after login");
-        console.error('userToken is required');
-      }
-
-
-      // Only merge cart if user had a guest cart before login
-      if (userToken && guestCartId && guestCartToken) {
-        try {
-          if(cart) {
-            await mergeCart({ variables: { token: userToken, cartId: parseInt(guestCartId, 10) } });
-          }
-          
-        } catch (err) {
-          console.error("mergeCart failed:", err);
+        if(cart) {
+          await mergeCartAction();
+        } else {
+          await deleteGuestCookieAction();
+          await getCartDetail();
         }
-        // 登录成功后不应该保存用户的token
-        setCookie(IS_GUEST, "false");
-        deleteGuestCookie();
-        await getCartDetail();
-      } else if (userToken) {
-        // User logged in without a guest cart, just set the token
-        setCookie(IS_GUEST, "false");
-        deleteGuestCookie();
-      }
-      setTimeout(() => {
-        router.push("/");
-        router.refresh();
-      }, 100);
+
+        setTimeout(() => {
+          router.push("/");
+          router.refresh();
+        }, 100);
 
 
     } catch (error) {

+ 4 - 8
src/components/customer/credentials/CredentialModal.tsx

@@ -13,14 +13,10 @@ import { useBodyScrollLock } from "@utils/hooks/useBodyScrollLock";
 import OpenAuth from "../OpenAuth";
 import { isObject } from '@/utils/type-guards';
 import LoadingDots from "@components/common/icons/LoadingDots";
-import { logoutAction } from "@utils/actions";
+import { logoutAction } from "@/actions";
 import { useAppDispatch } from "@/store/hooks";
 import { clearCart } from "@/store/slices/cart-slice";
-import { 
-  setCookie, 
-  deleteGuestCookie
-} from "@utils/cookie-tools";
-import { IS_GUEST } from "@/utils/constants";
+
 
 export default function CredentialModal({
   children,
@@ -65,10 +61,12 @@ export default function CredentialModal({
 
   const onSubmit = async () => {
     try {
+  
       const res = await logoutAction();
 
       if (!res.success) {
         showToast(res.message, "danger");
+        return;
       }
 
       await signOut({
@@ -76,8 +74,6 @@ export default function CredentialModal({
         redirect: false,
       });
 
-      deleteGuestCookie(); // 这里本质是要删除登录用户的token
-      setCookie(IS_GUEST, 'true');
       dispatch(clearCart());
       showToast("You are logged out successfully!", "success");
       setTimeout(() => {

+ 3 - 3
src/components/layout/navbar/CartAndUserActions.tsx

@@ -2,7 +2,7 @@ import { Suspense } from "react";
 import Cart from "@/components/cart";
 import UserAccount from "@components/customer/credentials";
 import { IconSkeleton } from "@/components/common/skeleton/IconSkeleton";
-import { SessionManager } from "@/providers";
+
 
 export function CartAndUserActions() {
   return (
@@ -13,9 +13,9 @@ export function CartAndUserActions() {
       
       <Suspense fallback={<IconSkeleton />}>
         
-          <SessionManager>
+
             <UserAccount />
-          </SessionManager>
+
        
       </Suspense>
     </div>

+ 16 - 2
src/graphql/cart/mutations/AddProductToCart.ts

@@ -1,7 +1,7 @@
 import { gql, TypedDocumentNode } from "@apollo/client";
-import { AddToCartData } from "@/types/cart/type";
+import { AddToCartData,AddToCartVariables } from "@/types/cart/type";
 
-export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData> = gql`
+export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData,AddToCartVariables> = gql`
   mutation createAddProductInCart(
     $cartId: Int
     $productId: Int!
@@ -41,6 +41,20 @@ export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData> = gql`
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
+        useShippingInsurance
+        shippingInsuranceEnabled
+        shippingInsuranceName
+        shippingInsuranceType
+        shippingInsuranceValue
+        shippingInsuranceAmount
+        baseShippingInsuranceAmount
+        formattedShippingInsuranceAmount
         isVip
         vipDiscountAmount
         formattedVipDiscountAmount

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

@@ -31,6 +31,20 @@ export const CREATE_APPLY_COUPON: TypedDocumentNode<CreateApplyCouponData,{coupo
                         }
                     }
                     }
+                    earnableRewardPoints
+                    rewardPoints
+                    rewardPointsUsed
+                    rewardPointsAmount
+                    baseRewardPointsAmount
+                    formattedRewardPointsAmount
+                    useShippingInsurance
+                    shippingInsuranceEnabled
+                    shippingInsuranceName
+                    shippingInsuranceType
+                    shippingInsuranceValue
+                    shippingInsuranceAmount
+                    baseShippingInsuranceAmount
+                    formattedShippingInsuranceAmount
                     isVip
                     vipDiscountAmount
                     formattedVipDiscountAmount

+ 3 - 2
src/graphql/cart/mutations/CreateCartToken.ts

@@ -1,6 +1,7 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import { CreateCartTokenData } from "@/types/cart/type";
 
-export const CREATE_CART_TOKEN = gql`
+export const CREATE_CART_TOKEN: TypedDocumentNode<CreateCartTokenData> = gql`
   mutation CreateCart {
     createCartToken(input: {}) {
       cartToken {

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

@@ -33,6 +33,20 @@ export const CREATE_MERGE_CART: TypedDocumentNode<CreateMergeCartData> = gql`
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
+        useShippingInsurance
+        shippingInsuranceEnabled
+        shippingInsuranceName
+        shippingInsuranceType
+        shippingInsuranceValue
+        shippingInsuranceAmount
+        baseShippingInsuranceAmount
+        formattedShippingInsuranceAmount
         isVip
         vipDiscountAmount
         formattedVipDiscountAmount

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

@@ -27,6 +27,20 @@ export const CREATE_REMOVE_COUPON: TypedDocumentNode<CreateRemoveCouponData> = g
                         }
                     }
                     }
+                    earnableRewardPoints
+                    rewardPoints
+                    rewardPointsUsed
+                    rewardPointsAmount
+                    baseRewardPointsAmount
+                    formattedRewardPointsAmount
+                    useShippingInsurance
+                    shippingInsuranceEnabled
+                    shippingInsuranceName
+                    shippingInsuranceType
+                    shippingInsuranceValue
+                    shippingInsuranceAmount
+                    baseShippingInsuranceAmount
+                    formattedShippingInsuranceAmount
                     isVip
                     vipDiscountAmount
                     formattedVipDiscountAmount

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

@@ -27,6 +27,20 @@ export const GET_CART_ITEM: TypedDocumentNode<GetCartItemData> = gql`
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
+        useShippingInsurance
+        shippingInsuranceEnabled
+        shippingInsuranceName
+        shippingInsuranceType
+        shippingInsuranceValue
+        shippingInsuranceAmount
+        baseShippingInsuranceAmount
+        formattedShippingInsuranceAmount
         isVip
         vipDiscountAmount
         formattedVipDiscountAmount

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

@@ -27,6 +27,20 @@ export const REMOVE_CART_ITEM: TypedDocumentNode<RemoveCartItemData,{cartItemId:
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
+        useShippingInsurance
+        shippingInsuranceEnabled
+        shippingInsuranceName
+        shippingInsuranceType
+        shippingInsuranceValue
+        shippingInsuranceAmount
+        baseShippingInsuranceAmount
+        formattedShippingInsuranceAmount
         isVip
         vipDiscountAmount
         formattedVipDiscountAmount

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

@@ -36,6 +36,20 @@ export const UPDATE_CART_ITEM: TypedDocumentNode<UpdateCartItemData,{cartItemId:
             }
           }
         }
+        earnableRewardPoints
+        rewardPoints
+        rewardPointsUsed
+        rewardPointsAmount
+        baseRewardPointsAmount
+        formattedRewardPointsAmount
+        useShippingInsurance
+        shippingInsuranceEnabled
+        shippingInsuranceName
+        shippingInsuranceType
+        shippingInsuranceValue
+        shippingInsuranceAmount
+        baseShippingInsuranceAmount
+        formattedShippingInsuranceAmount
         isVip
         vipDiscountAmount
         formattedVipDiscountAmount

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

@@ -1,19 +1,15 @@
 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
         $couponCode: String
         $giftCardNumber: String
         $memberDiscount: String
+        $useShippingInsurance: String
+        $rewardPoints: Int
     ) {
       createSaveCheckoutCart(
         input: {
@@ -22,6 +18,8 @@ export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,{
             couponCode: $couponCode
             giftCardNumber: $giftCardNumber
             memberDiscount: $memberDiscount
+            useShippingInsurance: $useShippingInsurance
+            rewardPoints: $rewardPoints
         }
         ) {
             saveCheckoutCart {
@@ -47,6 +45,20 @@ export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,{
                         }
                     }
                 }
+                earnableRewardPoints
+                rewardPoints
+                rewardPointsUsed
+                rewardPointsAmount
+                baseRewardPointsAmount
+                formattedRewardPointsAmount
+                useShippingInsurance
+                shippingInsuranceEnabled
+                shippingInsuranceName
+                shippingInsuranceType
+                shippingInsuranceValue
+                shippingInsuranceAmount
+                baseShippingInsuranceAmount
+                formattedShippingInsuranceAmount
                 isVip
                 vipDiscountAmount
                 formattedVipDiscountAmount

+ 10 - 0
src/graphql/customer/query/GetOrderDetails.ts

@@ -38,6 +38,16 @@ export const GET_ORDER_DETAILS: TypedDocumentNode<OrderDetailsData> = gql`
     vipDiscountAmount
     baseVipPlusAmount
     baseVipDiscountAmount
+    rewardPointsUsed    
+    rewardPointsAmount
+    baseRewardPointsAmount
+    rewardPointsInvoiced
+    baseRewardPointsInvoiced
+    rewardPointsEarned
+    shippingInsuranceAmount
+    baseShippingInsuranceAmount
+    shippingInsuranceInvoiced
+    baseShippingInsuranceInvoiced
     giftcardNumber
     giftcardAmount
     baseGiftcardAmount

+ 3 - 0
src/providers/GlobalProviders.tsx

@@ -1,6 +1,7 @@
 "use client";
 
 import { ReactNode } from "react";
+import { SessionProvider } from "next-auth/react";
 import { ThemeProvider } from "./ThemeProvider";
 import { ToastProvider } from "./ToastProvider";
 import { ApolloWrapper } from "./ApolloWrapper";
@@ -9,6 +10,7 @@ import { LoginModalProvider } from "./LoginModalProvider";
 
 export function GlobalProviders({ children }: { children: ReactNode }) {
   return (
+    <SessionProvider>
     <ThemeProvider>
         <ToastProvider>
           <ApolloWrapper>
@@ -20,5 +22,6 @@ export function GlobalProviders({ children }: { children: ReactNode }) {
           </ApolloWrapper>
         </ToastProvider>
     </ThemeProvider>
+    </SessionProvider>
   );
 }

+ 0 - 11
src/providers/NextAuthProvider.tsx

@@ -1,11 +0,0 @@
-"use client";
-import { SessionProvider } from "next-auth/react";
-import { ReactNode } from "react";
-
-export const NextAuthProvider = ({
-  children,
-}: {
-  children: ReactNode;
-}) => {
-  return <SessionProvider>{children}</SessionProvider>;
-}

+ 0 - 12
src/providers/SessionManager.tsx

@@ -1,12 +0,0 @@
-"use client";
-
-import { SessionProvider } from "next-auth/react";
-import { ReactNode } from "react";
-
-export function SessionManager({ children }: { children: ReactNode }) {
-  return (
-    <SessionProvider>
-      {children}
-    </SessionProvider>
-  );
-}

+ 0 - 8
src/providers/SessionProvider.tsx

@@ -1,8 +0,0 @@
-"use client";
-
-import { SessionProvider as NextAuthSessionProvider } from "next-auth/react";
-import { ReactNode } from "react";
-
-export function SessionProvider({ children }: { children: ReactNode }) {
-  return <NextAuthSessionProvider>{children}</NextAuthSessionProvider>;
-}

+ 0 - 3
src/providers/index.ts

@@ -1,11 +1,8 @@
 export { ApolloWrapper } from "./ApolloWrapper";
 export { GlobalContextProvider } from "./GlobalContextProvider";
 export { GlobalProviders } from "./GlobalProviders";
-export { NextAuthProvider } from "./NextAuthProvider";
 export { ReduxProvider } from "./ReduxProvider";
-export { SessionManager } from "./SessionManager";
 export { ThemeProvider } from "./ThemeProvider";
 export { ToastProvider, useToast } from "./ToastProvider";
-export { SessionProvider } from "./SessionProvider";
 export { PayPalWrapper } from "./PaypalWrapper";
 export { ConfigProvider } from "./ConfigProvider";

+ 103 - 0
src/server-service/guestCartTokenService.ts

@@ -0,0 +1,103 @@
+import {cookies} from "next/headers";
+import { getServerSession } from "next-auth";
+import { authOptions } from "@utils/auth";
+import { BagistoSession } from "@/types/types";
+import { serverGraphqlFetch } from "@utils/bagisto";
+import { CREATE_CART_TOKEN } from "@/graphql";
+import { GUEST_CART_ID, GUEST_CART_TOKEN, IS_GUEST, GUEST_COOKIE_OPTION } from "@/utils/constants";
+import { CreateCartTokenData } from "@/types/cart/type";
+
+
+export async function deleteGuestCookie() {
+    const authSession = (await getServerSession(authOptions)) as BagistoSession | null;
+    const accessToken = authSession?.user?.accessToken;
+    const isGuest = accessToken ? false : true;
+    const cookieStore = await cookies();
+    if(isGuest) {
+        cookieStore.delete(GUEST_CART_TOKEN);
+        cookieStore.delete(GUEST_CART_ID);
+        cookieStore.set(IS_GUEST, "true");
+    } else {
+        cookieStore.set(IS_GUEST, "false");
+    }
+
+}
+
+
+export async function setGuestCookie({
+    guestToken,
+    guestCartId,
+    isGuest
+}: {
+    guestToken: string;
+    guestCartId: string;
+    isGuest: boolean;
+}) {
+    const cookieOptions = {
+        httpOnly: GUEST_COOKIE_OPTION.httpOnly,
+        maxAge: GUEST_COOKIE_OPTION.maxAge,
+        path: GUEST_COOKIE_OPTION.path,
+        secure: GUEST_COOKIE_OPTION.secure,
+        sameSite: GUEST_COOKIE_OPTION.sameSite,
+    };
+
+    const cookieStore = await cookies();
+    cookieStore.set(GUEST_CART_TOKEN,guestToken,cookieOptions);
+    cookieStore.set(GUEST_CART_ID,guestCartId,cookieOptions);
+    cookieStore.set(IS_GUEST,isGuest ? "true" : "false");
+}
+
+export async function createGuestCartToken() {
+
+    const cookieStore = await cookies();
+
+    const existingToken = cookieStore.get(GUEST_CART_TOKEN)?.value;
+    const existingCartId = cookieStore.get(GUEST_CART_ID)?.value;
+
+    if (existingToken && existingCartId) {
+        return {
+            success: true,
+            cartToken: existingToken,
+            cartId: existingCartId,
+            msg: ''
+        };
+    }
+
+    try{
+         
+        const response = await serverGraphqlFetch<CreateCartTokenData>({
+            query: CREATE_CART_TOKEN,
+            cache: "no-store",
+            takeAuthorization: false
+        });
+        const success = response.error ? false : true;
+        let guestToken = null;
+        let guestCartId = null;
+        const msg = response.error ? response.error.message : '';
+        if(success && response.data.createCartToken) {
+            guestToken = response.data.createCartToken.cartToken.sessionToken;
+            guestCartId = response.data.createCartToken.cartToken.id;
+
+            await setGuestCookie({
+                guestToken,
+                guestCartId,
+                isGuest: true
+            });
+        }
+
+        
+        return {
+            success: success,
+            cartToken: guestToken,
+            cartId: guestCartId,
+            msg: msg
+        };
+    } catch(e: any) {
+        return {
+            success:false,
+            cartToken: null,
+            cartId: null,
+            msg: e.message || 'Create guest cart token failed!'
+        };
+    }
+}

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

@@ -60,6 +60,22 @@ 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;
+  shippingInsuranceName: string;
+  shippingInsuranceType: string;
+  shippingInsuranceValue: number;
+  shippingInsuranceAmount: number;
+  baseShippingInsuranceAmount: number;
+  formattedShippingInsuranceAmount: string;
 
   formattedSubtotal: string;
   formattedSubTotalInclTax: string;
@@ -117,6 +133,7 @@ export interface AddToCartVariables {
   cartId?: number | null;
   productId: number;
   quantity: number;
+  variantId?: number;
 }
 
 

+ 7 - 5
src/types/checkout/type.ts

@@ -229,11 +229,13 @@ export interface SaveCheckoutCartData {
   createSaveCheckoutCart: SaveCheckoutCartPayload;
 }
 export interface SaveCheckoutCartVariables {
-  shippingMethod: string;
-  paymentMethod: string;
-  couponCode: string;
-  giftCardNumber: string;
-  memberDiscount: string;
+  shippingMethod?: string;
+  paymentMethod?: string;
+  couponCode?: string;
+  giftCardNumber?: string;
+  memberDiscount?: string;
+  useShippingInsurance?: string;
+  rewardPoints?: number; // -1 跳过,0 取消,大于0 使用
 }
 // Checkout Place Order
 

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

@@ -110,9 +110,19 @@ export interface OrderDetails {
     vipDiscountAmount: string;
     baseVipPlusAmount: string;
     baseVipDiscountAmount: string;
+    shippingInsuranceAmount: string;
+    baseShippingInsuranceAmount: string;
+    shippingInsuranceInvoiced: string;
+    baseShippingInsuranceInvoiced: string;
     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;

+ 8 - 1
src/types/types.ts

@@ -1,7 +1,14 @@
 import { SVGProps } from "react";
 import { Session } from "next-auth";
 
-
+export interface LogoutData { 
+  createLogout: { 
+    logout: { 
+      success: boolean; 
+      message: string 
+    } 
+  } 
+}
 export interface BagistoSession extends Session {
   user: {
     id: string;

+ 0 - 4
src/utils/actions.ts

@@ -3,7 +3,6 @@
 import { redirect } from "next/navigation";
 import {
   createUserToLogin,
-  logoutUser,
   recoverUserLogin,
   subscribeUser,
 } from '@/utils/bagisto';
@@ -139,6 +138,3 @@ export async function userSubscribe(
 }
 
 
-export async function logoutAction() {
-  return await logoutUser();
-}

+ 19 - 51
src/utils/bagisto/index.ts

@@ -13,7 +13,6 @@ import {
 } from "../constants";
 import { getServerSession } from "next-auth";
 import {
-  CUSTOMER_LOGOUT,
   CUSTOMER_REGISTRATION,
   FORGET_PASSWORD,
 } from "@/graphql/customer/mutations";
@@ -211,6 +210,7 @@ export async function serverGraphqlFetch<
   tags,
   variables,
   revalidate = 0,
+  takeAuthorization = true, // 是否携带token,默认携带
 }: {
   cache?: RequestCache;
   headers?: HeadersInit | Record<string, string>;
@@ -218,17 +218,20 @@ export async function serverGraphqlFetch<
   tags?: string[];
   variables?: TVariables;
   revalidate?: number;
+  takeAuthorization?: boolean;
 }): Promise<FetchGraphqlResult<TData>> {
   try {
     const queryString = typeof query === "string" ? query : print(query);
 
-    const tokenRes = await getAuthorizationToken();
+    
     const headerRes = await getBaseHeader();
     const baseHeaders: Record<string, string> = {...headerRes};
-
-
-    if (tokenRes.token) {
-      baseHeaders['Authorization'] = `Bearer ${tokenRes.token}`;
+    
+    if(takeAuthorization) {
+      const tokenRes = await getAuthorizationToken();
+      if (tokenRes.token) {
+        baseHeaders['Authorization'] = `Bearer ${tokenRes.token}`;
+      }
     }
 
     if (headers) {
@@ -276,6 +279,7 @@ export async function bagistoFetch<T>({
   variables,
   // isCookies = true,
   // guestToken,
+  takeAuthorization = true, // 是否携带token,默认携带
   revalidate = 60,
 }: {
   cache?: RequestCache;
@@ -285,13 +289,14 @@ export async function bagistoFetch<T>({
   variables?: ExtractVariables<T>;
   // isCookies?: boolean;
   // guestToken?: string;
+  takeAuthorization?: boolean;
   revalidate?: number;
 }): Promise<{ status: number; body: ExtractGraphqlData<T> } | never> {
   try {
     const queryString =
       typeof query === "string" ? query : print(query);
 
-    const tokenRes = await getAuthorizationToken();
+    
     const headerRes = await getBaseHeader();
     const baseHeaders: Record<string, string> = {...headerRes};
     /*
@@ -311,9 +316,14 @@ export async function bagistoFetch<T>({
       baseHeaders.Authorization = `Bearer ${guestToken}`;
     }
     */
-    if (tokenRes.token) {
-      baseHeaders.Authorization = `Bearer ${tokenRes.token}`;
+    if(takeAuthorization) {
+      const tokenRes = await getAuthorizationToken();
+      if (tokenRes.token) {
+        baseHeaders.Authorization = `Bearer ${tokenRes.token}`;
+      }
+
     }
+    
 
     
 
@@ -432,48 +442,6 @@ export async function createUserToLogin(
   }
 }
 
-export async function logoutUser() {
-  try {
-    const session = await getServerSession(authOptions);
-    const token = session?.user?.accessToken;
-
-    if (!token) {
-      return {
-        success: false,
-        message: "User token missing",
-      };
-    }
-    /**
-     * @todo CUSTOMER_LOGOUT 接口会报错,待修复(确定是php后端接口报错)
-     */
-    const res = await bagistoFetch<{
-      data: { createLogout: { logout: { success: boolean; message: string } } };
-      variables: { input: { token: string } };
-    }>({
-      query: CUSTOMER_LOGOUT,
-      // isCookies: true,
-      revalidate: 3600,
-    });
-
-    const success = res?.body?.data?.createLogout?.logout?.success ?? false;
-
-    const message =
-      res?.body?.data?.createLogout?.logout?.message ?? "Logout executed";
-
-
-
-
-    return {
-      success,
-      message,
-    };
-  } catch (error: unknown) {
-    return {
-      success: false,
-      message: error instanceof Error ? error.message : "Something went wrong",
-    };
-  }
-}
 
 export async function recoverUserLogin(
   input: Record<string, unknown>,

+ 17 - 0
src/utils/cartDetailTools.ts

@@ -8,6 +8,23 @@ export function formatCartDetail(cart:CartDetail): CartDetail {
         isGuest: cart.isGuest,
         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,
+        shippingInsuranceType: cart.shippingInsuranceType,
+        shippingInsuranceValue: cart.shippingInsuranceValue,
+        shippingInsuranceAmount: cart.shippingInsuranceAmount,
+        baseShippingInsuranceAmount: cart.baseShippingInsuranceAmount,
+        formattedShippingInsuranceAmount: cart.formattedShippingInsuranceAmount,
+
         isVip: cart.isVip,
         vipDiscountAmount: cart.vipDiscountAmount,
         formattedVipDiscountAmount: cart.formattedVipDiscountAmount,

+ 4 - 2
src/utils/constants.ts

@@ -107,13 +107,15 @@ export const SortByFields: SortOrderTypes[] = [
 ];
 
 export const DEFAULT_EXPIRES_DAYS = 7; // cookie里的 GUEST_CART_TOKEN GUEST_CART_ID IS_GUEST 的过期时间
+const sameSite: boolean | "lax" | "none" | "strict" | undefined = "lax";
 export const GUEST_COOKIE_OPTION = {
-    days: DEFAULT_EXPIRES_DAYS,
+    httpOnly: true,
+    maxAge: DEFAULT_EXPIRES_DAYS * 24 * 3600,
     encode: true,          // 默认编码,更加安全
     path: '/',
     // domain:,
     secure: process.env.NEXT_PUBLIC_APP_ENV !== 'development',
-    sameSite: 'lax',
+    sameSite: sameSite,
 };
 
 export const GUEST_CART_TOKEN = "guest_cart_token";

+ 4 - 23
src/utils/cookie-tools.ts

@@ -34,12 +34,12 @@ export const deleteCookie = (name: string) => {
   document.cookie = `${name}=; Max-Age=0; path=/`;
 };
 */
-import {GUEST_COOKIE_OPTION,GUEST_CART_TOKEN,IS_GUEST,GUEST_CART_ID} from "@/utils/constants";
+import {GUEST_COOKIE_OPTION} from "@/utils/constants";
 // cookie.ts
 
 export interface CookieOptions {
   /** 过期天数(从当前时间起算),默认 7 天 */
-  days?: number;
+  maxAge?: number;
   /** 是否需要对值进行编码(encodeURIComponent),默认 false */
   encode?: boolean;
   /** Cookie 路径,默认 '/' */
@@ -84,7 +84,7 @@ export function setCookie(
   if (typeof document === 'undefined') return;
   const setOption = Object.assign({},GUEST_COOKIE_OPTION,options);
   const {
-    days,
+    maxAge,
     encode,          // 默认编码,更加安全
     path,
     domain,
@@ -93,7 +93,7 @@ export function setCookie(
   } = setOption;
 
   // 处理过期时间
-  const expires = new Date(Date.now() + days * 864e5).toUTCString();
+  const expires = new Date(Date.now() + maxAge * 1000).toUTCString();
 
   // 处理值:编码或直接转换
   const val = encode
@@ -135,22 +135,3 @@ export function deleteCookie(
   document.cookie = cookie;
 }
 
-export function getGuestCartId(): string | null {
-  const res = getCookie(GUEST_CART_ID);
-  return res;
-}
-export function getIsGuest(): boolean {
-  const res = getCookie(IS_GUEST);
-  return res === 'true' || res === null;
-}
-export const getGuestCartToken = (): string | null => {
-  const raw = getCookie(GUEST_CART_TOKEN);
-  if (!raw) return null;
-
-  return raw;
-};
-
-export function deleteGuestCookie() {
-    deleteCookie(GUEST_CART_ID);
-    deleteCookie(GUEST_CART_TOKEN);
-}

+ 0 - 55
src/utils/fetch-handler.ts

@@ -1,55 +0,0 @@
-type Method = "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
-
-interface FetchHandlerOptions<TBody = unknown> {
-  url: string; // API route, e.g., "addToCart"
-  method?: Method;
-  body?: TBody;
-  headers?: Record<string, string>;
-  contentType?: boolean;
-}
-
-export async function fetchHandler({
-  url,
-  method = "GET",
-  body,
-  headers = {},
-  contentType = true,
- 
-}: FetchHandlerOptions): Promise<any> {
-  try {
-    const defaultHeaders: Record<string, string> = {
-      ...(contentType ? { "Content-Type": "application/json" } : {}),
-      ...headers,
-    };
-
-    const response = await fetch(`/api/${url}`, {
-      method,
-      headers: defaultHeaders,
-      body: body ? JSON.stringify(body) : undefined,
-    });
-   
-     
-    const result = await response.json();
-    if (!response.ok) {
-      return {
-        data: null,
-        error: {
-          status: response?.status,
-          message: result?.error || "Something went wrong",
-        },
-      };
-    }
-    return {
-      ...result,
-    };
-  } catch (err) {
-    const error = err instanceof Error ? err.message : "Unknown error";
-
-    return {
-      data: null,
-      error: {
-        message: error,
-      },
-    };
-  }
-}

+ 46 - 158
src/utils/hooks/useAddToCart.ts

@@ -1,128 +1,59 @@
 "use client";
 
-import { useCallback } from "react";
+import { useCallback, useState } from "react";
 import { useCustomToast } from "./useToast";
 import { useAppDispatch } from "@/store/hooks";
 import { addItem, clearCart } from "@/store/slices/cart-slice";
 import { isObject } from "@utils/type-guards";
-import { 
-  setCookie, 
-  getIsGuest,
-  getGuestCartToken,
-  deleteGuestCookie
-} from "@utils/cookie-tools";
-import { useGuestCartToken } from "./useGuestCartToken";
-import { IS_GUEST,GUEST_CART_TOKEN,GUEST_CART_ID } from "@/utils/constants";
 import { useMutation, useApolloClient } from "@apollo/client/react";
 import {
-  CREATE_ADD_PRODUCT_IN_CART,
-  REMOVE_CART_ITEM,
   UPDATE_CART_ITEM,
 } from "@/graphql";
 import { formatCartDetail } from "@/utils/cartDetailTools";
 import {handleApolloBusinessError} from "@/lib/ApolloErrorHandler";
-
+import {addProductToCartAction, deleteCartProductAction} from '@/actions';
+import { AddToCartVariables, AddProductInCart } from "@/types/cart/type";
 
 export const useAddProduct = () => {
   const dispatch = useAppDispatch();
-  const { createGuestToken } = useGuestCartToken();
   const { showToast } = useCustomToast();
   const apolloClient = useApolloClient();
-
-  const [mutateAsync, { loading: isCartLoading }] = useMutation(
-    CREATE_ADD_PRODUCT_IN_CART,
-    {
-      onCompleted: (res) => {
-        console.log('useAddToCart onCompleted run ----- 0');
-        const responseData = res?.createAddProductInCart?.addProductInCart;
-
-        if (!responseData?.success) {
-          showToast(responseData?.message || "Error adding to cart", "danger");
-          return;
-        }
-        if (responseData) {
-          if (responseData.success) {
-            /** 兜底代码 start*/
-            // 游客加购,然后清空购物车,然后刷新页面,然后再加购,然后再刷新页面,购物车会失效,所以添加兜底代码
-            const isGuest = getIsGuest();// true 是游客
-            // 如果之前的cartToken有效,responseCartId和responseCartToken的值是一样的都是cart id; 否则两者不相等
-            const responseCartId = responseData.id;
-            const responseCartToken = responseData.cartToken;
-            if(isGuest && responseCartId !== responseCartToken) {
-                setCookie(GUEST_CART_TOKEN, responseCartToken,{encode: false});
-                setCookie(GUEST_CART_ID, responseCartId);
-                setCookie(IS_GUEST, String(isGuest));
+  const [isRemoveLoading, setIsRemoveLoading] = useState(false);
+
+  const appendProductToCart = async (param: AddToCartVariables) => {
+      const res = await addProductToCartAction(param);
+      const result: {
+        data: AddProductInCart | null;
+        error: boolean;
+      } = {
+        data: null,
+        error: true
+      };
+      if(res.success) {
+          const responseData = res.cartData?.createAddProductInCart?.addProductInCart;
+          if (responseData) {
+            if (responseData.success) {
+                const cartDetail = formatCartDetail(responseData);
+                dispatch(addItem(cartDetail));
+                showToast("Product added to cart successfully", "success");
+                result.data = responseData;
+                result.error = false;
             }
-            /** 兜底代码 end*/
-
-            const cartDetail = formatCartDetail(responseData);
-            dispatch(addItem(cartDetail));
-            showToast("Product added to cart successfully", "success");
           }
-        }
-      },
-
-      onError: (err) => {
-        handleApolloBusinessError(err,(error) => {
-          showToast(error.message, "danger");
-        });
-      },
-    },
-  );
-
-  const onAddToCart = async ({
-    productId,
-    quantity,
-    variantId
-  }: {
-    productId: string;
-    quantity: number;
-    variantId?: number;
-    token?: string;
-    cartId?: number | string;
-  }) => {
-    
-    // Ensure token exists - create if needed
-    let token = getGuestCartToken(); // 从cookie获取token
-    const isGuest = getIsGuest();
-
-    if (!token && isGuest) {
-      // 没有token,则创建一个并写入cookie
-      token = await createGuestToken();
+          
 
-      if (!token) {
-        showToast("Failed to create cart session", "danger");
-        return;
+      } else {
+          showToast(res.msg, "danger");
       }
-    }
-    
-    const param : { productId: number; quantity:number; variantId?: number; } = {
-      productId: parseInt(productId),
-      quantity,
-    };
-    if(variantId) {
-      param.variantId = variantId;
-    }
-
-    const result = await mutateAsync({
-      variables: param,
-    });
-
-    return {
-      data: result.data,
-      error: result.error,
-    };
+      return result;
   };
 
   // 删除购物车中的产品
-  const deleteProductFromCart = useCallback((cartItemId: number) => {
-      return apolloClient.mutate({
-          mutation: REMOVE_CART_ITEM,
-          variables: {
-            cartItemId: cartItemId
-          },
-      }).then((res) => {
-          const resCatData =  res.data?.createRemoveCartItem?.removeCartItem ?? null;
+  const deleteProductFromCart = useCallback(async (cartItemId: number) => {
+      setIsRemoveLoading(true);
+      const deleteRes = await deleteCartProductAction(cartItemId);
+      if(deleteRes.success) {
+          const resCatData =  deleteRes.cartData?.createRemoveCartItem?.removeCartItem ?? null;
           if(resCatData && resCatData.itemsQty) {
             const cartDetail = formatCartDetail(resCatData);
             dispatch(addItem(cartDetail));
@@ -130,27 +61,25 @@ export const useAddProduct = () => {
           } 
           
           if(!resCatData || !resCatData?.itemsQty) {
-              dispatch(clearCart());
-              // @todo 可以优化为通过接口或者server action 删除cookie
-              const isGuest = getIsGuest();
-              if (isGuest) {
-                deleteGuestCookie();
-              }
+             dispatch(clearCart());
           }
+          setIsRemoveLoading(false);
           return {
               data: resCatData?.itemsQty ? formatCartDetail(resCatData) : null,
               error: false,
               msg: ""
           };
+
+      } else {
+        setIsRemoveLoading(false);
+        return {
+            data: null,
+            error: true,
+            msg: deleteRes.msg,
+        }
+      }
       
-      }).catch((err) => {
-          return {
-              data: null,
-              error: true,
-              msg: err.message,
-          }
-      });
-  },[apolloClient]);
+  },[]);
   
   // 修改购物车中产品的数量
   const editProductQtyFromCart = useCallback((cartItemId: number,quantity:number) => {
@@ -189,45 +118,6 @@ export const useAddProduct = () => {
       });
   },[apolloClient]);
 
-  //--------Remove Cart Product Quantity--------//
-  const [removeFromCart, { loading: isRemoveLoading }] = useMutation(
-    REMOVE_CART_ITEM,
-    {
-      onCompleted: async (response) => {
-        const responseData = response?.createRemoveCartItem?.removeCartItem;
-        if (isObject(responseData)) {
-          const message = "Cart item removed successfully";
-          const cartDetail = formatCartDetail(responseData);
-          dispatch(addItem(cartDetail));
-          showToast(message as string, "warning");
-
-          if (!responseData?.itemsQty) {
-            dispatch(clearCart());
-
-            const isGuest = getIsGuest();
-            if (isGuest) {
-              deleteGuestCookie();
-            }
-          }
-        } else {
-          showToast("Something went wrong", "warning");
-        }
-      },
-      onError: (error) => {
-        handleApolloBusinessError(error,(err) => {
-          showToast(err.message, "danger");
-        });
-      },
-    },
-  );
-
-  const onAddToRemove = async (productId: string) => {
-    await removeFromCart({
-      variables: {
-        cartItemId: parseInt(productId),
-      },
-    });
-  };
 
   //---------Update Cart Product Quantity--------//
   const [updateCartItem, { loading: isUpdateLoading }] = useMutation(
@@ -273,13 +163,11 @@ export const useAddProduct = () => {
   };
 
   return {
-    isCartLoading,
-    onAddToCart,
     isRemoveLoading,
-    onAddToRemove,
     onUpdateCart,
     isUpdateLoading,
     deleteProductFromCart,
-    editProductQtyFromCart
+    editProductQtyFromCart,
+    appendProductToCart
   };
 };

+ 0 - 110
src/utils/hooks/useGuestCartToken.ts

@@ -1,110 +0,0 @@
-"use client";
-
-import { useState, useRef } from "react";
-import { fetchHandler } from "../fetch-handler";
-import { GUEST_CART_ID, GUEST_CART_TOKEN, IS_GUEST } from "@/utils/constants";
-import { 
-  setCookie, 
-  getGuestCartToken,
-  getGuestCartId,
-  deleteGuestCookie
-} from "@/utils/cookie-tools";
-import { CREATE_CART_TOKEN } from "@/graphql";
-
-// ---------------------------
-// Main Hook
-// ---------------------------
-export const useGuestCartToken = () => {
-
-  const [token, setToken] = useState(() => {
-      const guestCartToken = getGuestCartToken();
-      return guestCartToken;
-  });
-  const [cartId, setCartId] = useState(() => {
-    const guestCartId = getGuestCartId();
-    
-    return guestCartId ? Number(guestCartId) : null;
-  });
-  // const [isReady, setIsReady] = useState(true);
-
-  const isResettingRef = useRef(false);
-  const tokenCreatedRef = useRef(false);
-  const tokenPromiseRef = useRef<Promise<string | null> | null>(null);
-
-  const createGuestToken = async (): Promise<string | null> => {
-    if (tokenPromiseRef.current) return tokenPromiseRef.current;
-
-    tokenPromiseRef.current = (async () => {
-      if (tokenCreatedRef.current) {
-        // Return existing raw token from cookie
-        const cookieVal = getGuestCartToken();
-        if (cookieVal) {
-          return cookieVal;
-        }
-        return null;
-      }
-      tokenCreatedRef.current = true;
-
-      try {
-        const query = CREATE_CART_TOKEN;
-        const queryString = typeof query === "string" ? query : (query.loc?.source?.body ?? "");
-        const res = await fetchHandler({
-          url: "graphql",
-          method: "POST",
-          body: { operationName: "CreateCart",query: queryString },
-          contentType: true,
-        });
-
-        const cart = res?.data?.createCartToken?.cartToken;
-        if (!cart) {
-          tokenCreatedRef.current = false;
-          return null;
-        }
-
-        const newCartId = Number(cart.id);
-
-        setCookie(GUEST_CART_TOKEN, cart.sessionToken,{encode: false});
-        setCookie(GUEST_CART_ID, String(newCartId));
-        setCookie(IS_GUEST, String(cart?.isGuest));
-
-        // State and return should be the RAW token
-        setToken(cart.sessionToken);
-        setCartId(newCartId);
-        return cart.sessionToken;
-      } catch (e) {
-        console.error("Error creating guest token:", e);
-        tokenCreatedRef.current = false;
-        return null;
-      } finally {
-        tokenPromiseRef.current = null;
-      }
-    })();
-
-    return tokenPromiseRef.current;
-
-
-  };
-
-  const resetGuestToken = async () => {
-    if (isResettingRef.current) return;
-    isResettingRef.current = true;
-
-    tokenCreatedRef.current = false;
-
-    // delete old
-    deleteGuestCookie();
-
-    await createGuestToken();
-
-    isResettingRef.current = false;
-  };
-
-
-  return {
-    token,
-    cartId,
-    // isReady,
-    createGuestToken,
-    resetGuestToken,
-  };
-};

+ 0 - 37
src/utils/hooks/useMergeCart.ts

@@ -1,37 +0,0 @@
-"use client";
-
-import { useMutation } from "@apollo/client/react";
-import { useAppDispatch } from "@/store/hooks";
-import { addItem } from "@/store/slices/cart-slice";
-import { CREATE_MERGE_CART } from "@/graphql";
-import { GUEST_CART_ID } from "@/utils/constants";
-import { setCookie } from "@utils/cookie-tools";
-import { formatCartDetail } from "@/utils/cartDetailTools";
-
-export function useMergeCart() {
-  const dispatch = useAppDispatch();
-
-  const [mergeCart, { loading: isLoading }] = useMutation(CREATE_MERGE_CART, {
-    onCompleted: (response) => {
-      const responseData = response?.createMergeCart?.mergeCart;
-      if (!responseData) {
-        return;
-      }
-       const cartId = responseData?.id ?? null;
-
-      if (cartId !== null && typeof cartId !== "undefined") {
-        setCookie(GUEST_CART_ID, String(cartId));
-      }
-      const cartDetail = formatCartDetail(responseData);
-
-      dispatch(addItem(cartDetail));
-    },
-    onError: (_error) => {
-    },
-  });
-
-  return {
-    mergeCart,
-    isLoading,
-  };
-}

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

@@ -14,10 +14,20 @@ interface SaveCheckoutCartResult {
 
 export const useSaveCheckoutCart = () => {
     const client = useApolloClient();
+    const defaultVariables = {
+        shippingMethod: '-1',
+        paymentMethod: '-1',
+        couponCode: '-1',
+        giftCardNumber: '-1',
+        memberDiscount: '-1',
+        useShippingInsurance: '-1',
+        rewardPoints: -1
+    };
     const saveCheckoutCart = useCallback((params: SaveCheckoutCartVariables) => {
+        const variables = Object.assign({},defaultVariables,params);
         return client.mutate({
             mutation: CREATE_SAVE_CHECKOUT_CART,
-            variables: params
+            variables: variables //params
         }).then((res) => {
             const resData = res.data?.createSaveCheckoutCart.saveCheckoutCart ?? null; 
             const result: SaveCheckoutCartResult = { 

+ 0 - 36
src/utils/signInAuth.ts

@@ -1,36 +0,0 @@
-
-
-import { bagistoFetch, /*restApiFetch*/} from "@/utils/bagisto";
-import { CUSTOMER_LOGIN } from "@/graphql/customer/mutations";
-
-
-type LoginFormInputs = {
-  username: string;
-  password: string;
-};
-export default async function signInAuth(loginData:LoginFormInputs) {
-    const input = {
-        email: loginData.username,
-        password: loginData.password,
-    };
-    const res = await bagistoFetch<any>({
-        query: CUSTOMER_LOGIN,
-        variables: { input },
-        cache: "no-store",
-    });
-    const data = res?.body?.data?.createCustomerLogin?.customerLogin;
-        
-
-    if (!data || !data.success || !data.token) {
-        throw new Error(data?.message || "Invalid credentials.");
-    }
-
-    return {
-        id: data.id, // required by NextAuth
-        email: loginData.username,
-        name: loginData.username, // Using email as name since firstName/lastName are missing in response
-        apiToken: data.apiToken,
-        accessToken: data.token, // Sanctum token
-        role: "customer",
-    };
-}