zgl 2 settimane fa
parent
commit
be6eee7629
100 ha cambiato i file con 1834 aggiunte e 1154 eliminazioni
  1. 16 2
      README.md
  2. 73 0
      src/actions/addProductToCartAction.ts
  3. 13 0
      src/actions/createGuestCartAction.ts
  4. 48 0
      src/actions/deleteCartProductAction.ts
  5. 12 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. 1 1
      src/app/(checkout)/checkout/_components/CheckoutAddress/BillingAddressCheckout.tsx
  10. 10 4
      src/app/(checkout)/checkout/_components/CheckoutAddress/CheckoutAssress.tsx
  11. 1 1
      src/app/(checkout)/checkout/_components/CheckoutAddress/ShippingAddressCheckout.tsx
  12. 4 4
      src/app/(checkout)/checkout/_components/CheckoutCoupon/CheckoutCoupon.tsx
  13. 33 12
      src/app/(checkout)/checkout/_components/CheckoutWrapper.tsx
  14. 18 0
      src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx
  15. 4 4
      src/app/(checkout)/checkout/_components/GiftCard/CheckoutGiftCardGate.tsx
  16. 88 0
      src/app/(checkout)/checkout/_components/LostInsurance/LostInsurance.tsx
  17. 22 1
      src/app/(checkout)/checkout/_components/PromotionsDetails.tsx
  18. 149 0
      src/app/(checkout)/checkout/_components/RewardPointsRedeemMoney/RewardPointsRedeemMoney.tsx
  19. 5 5
      src/app/(checkout)/checkout/_components/VipPlus/CheckoutVipPlus.tsx
  20. 2 3
      src/app/(checkout)/checkout/continuetopay/page.tsx
  21. 2 4
      src/app/(checkout)/checkout/page.tsx
  22. 47 5
      src/app/(checkout)/error.tsx
  23. 3 3
      src/app/(public)/customer/account/_components/AccountVipPopup.tsx
  24. 4 4
      src/app/(public)/customer/account/mypoints/_components/GetPoints.tsx
  25. 1 1
      src/app/(public)/customer/account/pointlist/page.tsx
  26. 2 2
      src/app/(public)/customer/address/_components/DefaultAddress.tsx
  27. 2 2
      src/app/(public)/customer/address/eidt/[id]/page.tsx
  28. 1 1
      src/app/(public)/customer/address/new/_components/NewAddressDefault.tsx
  29. 3 3
      src/app/(public)/customer/login/page.tsx
  30. 1 1
      src/app/(public)/customer/order/track/order_id/[id]/_components/TrackDetail.tsx
  31. 1 0
      src/app/(public)/customer/order/track/order_id/[id]/page.tsx
  32. 1 0
      src/app/(public)/customer/order/view/order_id/[id]/page.tsx
  33. 47 5
      src/app/(public)/error.tsx
  34. 4 5
      src/app/(public)/page.tsx
  35. 23 2
      src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx
  36. 5 5
      src/app/(public)/paymentresult/result/page.tsx
  37. 15 13
      src/app/(public)/product/[...urlProduct]/page.tsx
  38. 9 11
      src/app/(public)/product/_components/ProductAddToCart.tsx
  39. 95 0
      src/app/(public)/product/_components/ProductDescription.tsx
  40. 46 33
      src/app/(public)/product/_components/ProductInformation.tsx
  41. 3 0
      src/app/(public)/product/_components/ProductReviewSection.tsx
  42. 5 1
      src/app/(public)/product/_components/ProductText.tsx
  43. 59 5
      src/app/(public)/product/_components/SwitchButton.tsx
  44. 5 3
      src/app/(public)/product/_components/popup/PriceModal.tsx
  45. 184 101
      src/app/(public)/product/_components/review/ReviewDetail.tsx
  46. 79 36
      src/app/(public)/product/_components/review/ReviewImageGalleryModal.tsx
  47. 142 33
      src/app/(public)/product/_components/review/ReviewModal.tsx
  48. 0 20
      src/app/api/clearCartCookie/route.ts
  49. 11 19
      src/app/api/customer/growth-value/route.ts
  50. 9 17
      src/app/api/customer/orders/[id]/route.ts
  51. 7 16
      src/app/api/customer/orders/route.ts
  52. 7 16
      src/app/api/customer/orders/tracking/route.ts
  53. 7 15
      src/app/api/customer/reward-points/browse-product/route.ts
  54. 8 17
      src/app/api/customer/reward-points/follow-status/route.ts
  55. 6 15
      src/app/api/customer/reward-points/follow/route.ts
  56. 5 15
      src/app/api/customer/reward-points/history/route.ts
  57. 21 54
      src/app/api/customer/token/address/route.ts
  58. 7 15
      src/app/api/home/route.ts
  59. 0 40
      src/app/api/shop/customer-address-gets/route.ts
  60. 0 41
      src/app/api/shop/customer-addresses/route.ts
  61. 8 15
      src/app/api/shop/products/[productId]/reviews/route.ts
  62. 42 0
      src/app/api/shop/reviews/[id]/like/route.ts
  63. 3 3
      src/app/api/shop/reviews/route.ts
  64. 2 0
      src/app/layout.tsx
  65. 0 197
      src/components/cart/AddToCart.tsx
  66. 0 137
      src/components/catalog/product/ProductDescription.tsx
  67. 0 31
      src/components/catalog/product/ProductInfo.tsx
  68. 1 0
      src/components/catalog/type.ts
  69. 22 12
      src/components/common/AddToCartModal/AddToCartModal.tsx
  70. 3 5
      src/components/common/AddToCartModal/FooterBtnInAddToCartModal.tsx
  71. 1 1
      src/components/common/CurrencySwitch/CurrencySwitch.tsx
  72. 1 1
      src/components/common/LoginModal/LoginModal.tsx
  73. 6 4
      src/components/common/button/ReviewButton.tsx
  74. 2 2
      src/components/common/icons/cart/DeleteItemButton.tsx
  75. 109 0
      src/components/customer/AuthSessionGuard.tsx
  76. 14 51
      src/components/customer/LoginForm.tsx
  77. 4 8
      src/components/customer/credentials/CredentialModal.tsx
  78. 1 1
      src/components/error/ErrorBoundary.tsx
  79. 3 3
      src/components/layout/navbar/CartAndUserActions.tsx
  80. 16 2
      src/graphql/cart/mutations/AddProductToCart.ts
  81. 14 0
      src/graphql/cart/mutations/CreateApplyCoupon.ts
  82. 3 2
      src/graphql/cart/mutations/CreateCartToken.ts
  83. 14 0
      src/graphql/cart/mutations/CreateMergeCart.ts
  84. 14 0
      src/graphql/cart/mutations/CreateRemoveCoupon.ts
  85. 14 0
      src/graphql/cart/mutations/GetCartItem.ts
  86. 14 0
      src/graphql/cart/mutations/RemoveCartItem.ts
  87. 14 0
      src/graphql/cart/mutations/UpdateCartItems.ts
  88. 1 0
      src/graphql/catalog/fragments/ProductDetailed.ts
  89. 20 8
      src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts
  90. 5 7
      src/graphql/customer/mutations/VerifyCustomer.ts
  91. 10 0
      src/graphql/customer/query/GetOrderDetails.ts
  92. 20 2
      src/lib/ApolloErrorHandler.ts
  93. 4 1
      src/lib/restApiClient.ts
  94. 3 0
      src/providers/GlobalProviders.tsx
  95. 0 11
      src/providers/NextAuthProvider.tsx
  96. 0 12
      src/providers/SessionManager.tsx
  97. 0 8
      src/providers/SessionProvider.tsx
  98. 0 3
      src/providers/index.ts
  99. 9 0
      src/proxy/auth.ts
  100. 0 0
      src/server-service/guestCartTokenService.ts

+ 16 - 2
README.md

@@ -310,7 +310,7 @@ useEffect 只会在客户端执行,具体是在浏览器绘制后执行,服
 11. 代码eslint检查修改 -- 已完成
 12. 下单成功后需要重新创建购物车token -- 无需处理
 13. 详情页缓存策略(不要缓存吧)
-14. getSession  过期提示重新登录;token校验
+14. getSession  过期提示重新登录;token校验(见第47条)
 15. checkout 页面 src\components\Portal.tsx (15:33) 会报 document is not defined -- 已解决
 16. redux cartDetail 里的billingAddress 和 shippingAddress 字段改到cart里的billingAddress 和 shippingAddress以及保存完地址后同步到redux
 17. redux 中 cartDetail 异步获取数据 https://chat.deepseek.com/share/76kj30h2cws9jcl4jt  -- 已完成
@@ -350,13 +350,27 @@ useEffect 只会在客户端执行,具体是在浏览器绘制后执行,服
 45. nextjs缓存与bagisto后台管理打通(后台修改配置通知nextjs清除缓存)
     -- 参考https://chat.deepseek.com/share/6h1huduahkqln69gu1
 46. 购物车详情里的subtotal和grandtotal金额不对,(一个sku产品加购多个,钱只算了一个)--- 后端已处理
-47. 请求接口时需要区分哪些接口需要token,哪些不需要(不需要的不在请求头中添加token,所以要扩展请求方法,加一个是否携带token的参数)。因为登录用户的token,需要校验有效性(设想是在nextAuth中校验)。
+47. 请求接口时需要区分哪些接口需要token,哪些不需要(不需要的不在请求头中添加token,所以要扩展请求方法,加一个是否携带token的参数)。
+    因为登录用户的token,需要校验有效性(设想是在nextAuth中校验)。
     对于需要token的接口:登录用户校验token; 游客只需要检查有没有token.
+    ---- 47 条 请求方法已经扩展是否携带token的参数,但是不再添加对于token的有效性校验,以后端接口返回的错误为依据判断有效性
+48. 游客token的创建优化(想在服务端创建写入token)--- 已完成
 
 
 
 > 39,40 参考 https://chatgpt.com/share/6a4f0637-ad28-83ea-ad44-423740795054
 
+## 关于token过期
+
+第一种,服务端或者客户端请求接口时,接口返回token过期或者需要需要登录的状态码(401),根据状态码提示用户登录。
+> 客户端通过弹窗提示;服务端通过抛出错误,让error.tsx提示
+
+第二种,通过后端提供的校验token接口结合NextAuth.js主动校验token是否可用
+
+
+需要注意的是,不管哪一种,都要在token失效后,执行nextAuth.js的signOut函数。
+
+
 ## 关于请求接口和错误处理
 接口请求有四种情况:服务端组件里的graphql请求和restful请求;客户端组件里的graphql请求和restful请求。
 ### 服务端组件里的graphql请求

+ 73 - 0
src/actions/addProductToCartAction.ts

@@ -0,0 +1,73 @@
+"use server";
+
+import {cookies} from "next/headers";
+import { GUEST_CART_TOKEN } from "@/utils/constants";
+import { CREATE_ADD_PRODUCT_IN_CART } from "@/graphql";
+import { auth } from "@/utils/auth/auth-helper";
+import { serverGraphqlFetch } from "@utils/bagisto";
+import {createGuestCartToken,setGuestCookie} from "@/server-service/guestCartTokenService";
+import { AddToCartData,AddToCartVariables } from "@/types/cart/type";
+
+
+export async function addProductToCartAction(param:AddToCartVariables) {
+
+    const authSession = await auth();
+    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
+        }
+    }
+};

+ 12 - 0
src/actions/deleteGuestCookieAction.ts

@@ -0,0 +1,12 @@
+"use server";
+
+import {deleteGuestCookie} from "@/server-service/guestCartTokenService";
+
+export async function deleteGuestCookieAction(isGuest: boolean = true) {
+    await deleteGuestCookie(isGuest);
+  
+    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 {

+ 1 - 1
src/app/(checkout)/checkout/_components/CheckoutAddress/BillingAddressCheckout.tsx

@@ -226,7 +226,7 @@ export default function BillingAddressCheckout ({
                     >
                         <span className="text-ly-12">+New Shipping Address</span>
                         {!isGuest && 
-                            <button className="flex-none" title="change address">
+                            <button className="flex-none" title="change address" type="button">
                                 <svg className="w-4 h-4" 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="16" y="0" width="16" height="16" transform="rotate(90 16 0)"   fill="#FFFFFF" fillOpacity="0"></rect><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M11.7295 6.32028L7.62964 10.4201L3.52978 6.32028"></path></svg>
                             </button>
                         }

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

+ 1 - 1
src/app/(checkout)/checkout/_components/CheckoutAddress/ShippingAddressCheckout.tsx

@@ -175,7 +175,7 @@ export default function ShippingAddressCheckout({
                 >
                     <span className="text-ly-12">+New Shipping Address</span>
                     {!isGuest && 
-                        <button className="flex-none" title="change address">
+                        <button className="flex-none" title="change address" type="button">
                             <svg className="w-4 h-4" 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="16" y="0" width="16" height="16" transform="rotate(90 16 0)"   fill="#FFFFFF" fillOpacity="0"></rect><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M11.7295 6.32028L7.62964 10.4201L3.52978 6.32028"></path></svg>
                         </button>
                     }

+ 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();

+ 2 - 3
src/app/(checkout)/checkout/continuetopay/page.tsx

@@ -1,8 +1,7 @@
 import {Suspense} from "react";
-import { getServerSession } from "next-auth";
 import Link from "next/link";
 import { redirect, RedirectType } from 'next/navigation';
-import { authOptions } from "@utils/auth";
+import { auth } from "@/utils/auth/auth-helper";
 import {serverGraphqlFetch} from "@utils/bagisto/index";
 import ContinueToPayOrderInfo from "../_components/ContinueToPay/ContinueToPayOrderInfo";
 import { GET_ORDER_DETAILS, GET_REPAYORDER_PAYMENT_METHODS } from "@/graphql";
@@ -36,7 +35,7 @@ export default async function ContinueToPay({searchParams}: {
   searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
 }) {
     
-    const session = await getServerSession(authOptions);// 游客是null
+    const session = await auth();// 游客是null
     // 游客跳转首页
     if(session === null) {
         redirect('/', RedirectType.replace);

+ 2 - 4
src/app/(checkout)/checkout/page.tsx

@@ -1,10 +1,8 @@
-import { getServerSession } from "next-auth";
-import { authOptions } from "@utils/auth";
 import { redirect, RedirectType } from 'next/navigation';
 import {serverGraphqlFetch} from "@utils/bagisto/index";
 import CheckoutWrapper from "./_components/CheckoutWrapper";
 import { GET_CART_ITEM } from "@/graphql";
-
+import { auth } from "@/utils/auth/auth-helper";
 import { GetCartItemData } from "@/types/cart/type";
 
 /**
@@ -21,7 +19,7 @@ export const dynamic = "force-dynamic";
 
 export default async function CheckoutPage() {
 
-    const session = await getServerSession(authOptions); // 游客是null
+    const session = await auth(); // 游客是null
 
 
     const {data: cartDetailsRes} = await serverGraphqlFetch<GetCartItemData>({

+ 47 - 5
src/app/(checkout)/error.tsx

@@ -1,18 +1,60 @@
 "use client";
+import { useEffect } from 'react'
+import { useRouter } from 'next/navigation';
+import { signOut } from "next-auth/react";
+import { useAppDispatch } from "@/store/hooks";
+import { clearCart } from "@/store/slices/cart-slice";
+//https://nextjs.org/docs/app/api-reference/file-conventions/error
+export default function Error({
+  error, 
+  reset
+}: {
+  reset: () => void;
+  error: Error & { digest?: string };
+}) {
+  const router = useRouter();
+  const dispatch = useAppDispatch();
+  let msg = "There was an issue with our storefront. This could be a temporary issue, please try your action again.";
+  if(error.name === 'UnauthorizedError') {
+    msg = "Authorization Bearer token is required. Will redirect to Login page.";
+  }
+  const clickHandler = () => {
+      if(error.name === 'UnauthorizedError') {
+          router.push('/customer/login')
+      } else {
+        reset();
+      }
+  };
+  useEffect(() => {
+      if (error.name === "UnauthorizedError") {
+            signOut({
+              callbackUrl:"/customer/login",
+              redirect: false,
+            }).then(() => {
+                dispatch(clearCart());
+     
+                setTimeout(() => {
+                  router.push("/customer/login");
+                  router.refresh();
+                }, 100);
+            });
+
+      }
+    // Log the error to an error reporting service
+    console.error(error)
+  }, [error])
 
-export default function Error({ reset }: { reset: () => void }) {
   return (
     <div className="mx-auto my-4 flex max-w-xl flex-col rounded-lg border border-neutral-200 bg-white p-8 md:p-12 dark:border-neutral-800 dark:bg-black">
       <h2 className="text-xl font-bold">Oh no!</h2>
       <p className="my-2">
-        There was an issue with our storefront. This could be a temporary issue,
-        please try your action again.
+        {msg}
       </p>
       <button
         className="mx-auto mt-4 flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90"
-        onClick={() => reset()}
+        onClick={clickHandler}
       >
-        Try Again
+        {error.name === 'UnauthorizedError' ? "To Login" : "Try Again"}
       </button>
     </div>
   );

+ 3 - 3
src/app/(public)/customer/account/_components/AccountVipPopup.tsx

@@ -133,9 +133,9 @@ export default function AccountVipPopup({
       try {
         setLoading(true);
         const res = await clientFetch("/api/customer/growth-value");
-        if (res.data.success) {
-          setVipGrothData(res.data.data);
-          console.log("vip成长值数据:", res.data.data);
+        if (res.success) {
+          setVipGrothData(res.data);
+          console.log("vip成长值数据:", res.data);
         }
       } catch (error) {
         console.error("获取vip成长值失败:", error);

+ 4 - 4
src/app/(public)/customer/account/mypoints/_components/GetPoints.tsx

@@ -12,8 +12,8 @@ export default function GetPoints() {
         setLoading(true);
         const res = await clientFetch("/api/customer/reward-points/follow-status");
       
-        setTaskData(res.data.data);
-        console.log("积分任务数据:", res.data.data);
+        setTaskData(res.data);
+        console.log("积分任务数据:", res.data);
       } catch (error) {
         console.error("获取任务状态失败:", error);
       } finally {
@@ -62,12 +62,12 @@ export default function GetPoints() {
         method: "POST",
         body: JSON.stringify({ categorys: item.type }),
       });
-      if(getpointsres.data.success){
+      if(getpointsres.success){
         alert("Points claimed successfully!");
       }
       
       const res = await clientFetch("/api/customer/reward-points/follow-status");
-      setTaskData(res.data.data);
+      setTaskData(res.data);
     } catch (err) {
       console.error("领取失败", err);
       alert("Failed to claim points");

+ 1 - 1
src/app/(public)/customer/account/pointlist/page.tsx

@@ -42,7 +42,7 @@ const PointsDetails = () => {
       console.log("接口返回:", resp);
 
       if (resp.status === 200) {
-        const formData = resp.data?.data || resp.data || { history: [] };
+        const formData = resp?.data || resp.data || { history: [] };
         const newList = formData.history || [];
         // 测试数据
         //  const testList = [...newList, ...newList, ...newList, ...newList];

+ 2 - 2
src/app/(public)/customer/address/_components/DefaultAddress.tsx

@@ -12,8 +12,8 @@ export default function DefaultAddress() {
         `/api/customer/token/address`, ///api/shop/customer-addresses?page=${page}
       );
       console.log("完整返回:", resp.data);
-      resp.data.data= Array.isArray(resp.data.data) ? resp.data.data : [];
-      const sortedList = [...resp.data.data].sort((a, b) => {
+      resp.data= Array.isArray(resp.data) ? resp.data : [];
+      const sortedList = [...resp.data].sort((a, b) => {
         // true(1) 排在 false(0) 前面
         if (a.default_address !== b.default_address) {
           return b.default_address ? 1 : -1;

+ 2 - 2
src/app/(public)/customer/address/eidt/[id]/page.tsx

@@ -51,7 +51,7 @@ const CustomerAddressEditPage = () => {
     });
     const json = await res;
     console.log("提交成功,返回结果:", json);
-    if (json?.data?.success) {
+    if (json?.success) {
       window.location.reload();
     } else {
       alert("error");
@@ -112,7 +112,7 @@ const CustomerAddressEditPage = () => {
       console.log("【完整接口响应】", resp);
 
       // 自动兼容后端 99% 的返回格式
-      const formData = resp.data?.data || resp.data || {};
+      const formData = resp?.data || resp.data || {};
       console.log("✅ 回填数据:", formData);
 
       reset(formData);

+ 1 - 1
src/app/(public)/customer/address/new/_components/NewAddressDefault.tsx

@@ -40,7 +40,7 @@ export default function NewAddressDefault({ countries }: { countries: any[] }) {
     });
     const json = await res;
     console.log("提交成功,返回结果:", json);
-    if(json?.status== 201){
+    if(json?.success){
         router.push("/customer/address/");
     }else{
       alert("error");

+ 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>
+
   );
 }

+ 1 - 1
src/app/(public)/customer/order/track/order_id/[id]/_components/TrackDetail.tsx

@@ -67,7 +67,7 @@ export default function TrackAddress({ address, id }: TrackAddressProps) {
         const res = await clientFetch(
           `/api/customer/orders/tracking?track_number=${id}`,
         );
-        let resData = res?.data;
+        let resData = res;
 
         // ========== 开发mock,上线务必删除这段赋值 ==========
         resData = {

+ 1 - 0
src/app/(public)/customer/order/track/order_id/[id]/page.tsx

@@ -56,6 +56,7 @@ export default async function TrackOrderDetail({ params }: { params: Params }) {
   const res = await restApiFetch<OrderApiResponse>({
     api: `/customer/orders/${orderId}`,
     method: "GET",
+    isRoute: false
   });
   const orderData: OrderData = res.body.data;
   const shippingAddr: AddressData = formatOrderAddress(orderData.shipping_address);

+ 1 - 0
src/app/(public)/customer/order/view/order_id/[id]/page.tsx

@@ -18,6 +18,7 @@ export default async function OrderDetail({ params }: { params: Params }) {
   const res = await restApiFetch<OrderApiResponse>({
     api:`/customer/orders/${orderId}`,
     method:"GET",
+    isRoute: false
   }
   );
 

+ 47 - 5
src/app/(public)/error.tsx

@@ -1,18 +1,60 @@
 "use client";
+import { useEffect } from 'react'
+import { useRouter } from 'next/navigation';
+import { signOut } from "next-auth/react";
+import { useAppDispatch } from "@/store/hooks";
+import { clearCart } from "@/store/slices/cart-slice";
+//https://nextjs.org/docs/app/api-reference/file-conventions/error
+export default function Error({
+  error, 
+  reset
+}: {
+  reset: () => void;
+  error: Error & { digest?: string };
+}) {
+  const router = useRouter();
+  const dispatch = useAppDispatch();
+  let msg = "There was an issue with our storefront. This could be a temporary issue, please try your action again.";
+  if(error.name === 'UnauthorizedError') {
+    msg = "Authorization Bearer token is required. Will redirect to Login page.";
+  }
+  const clickHandler = () => {
+      if(error.name === 'UnauthorizedError') {
+          router.push('/customer/login')
+      } else {
+        reset();
+      }
+  };
+  useEffect(() => {
+      if (error.name === "UnauthorizedError") {
+            signOut({
+              callbackUrl:"/customer/login",
+              redirect: false,
+            }).then(() => {
+                dispatch(clearCart());
+     
+                setTimeout(() => {
+                  router.push("/customer/login");
+                  router.refresh();
+                }, 100);
+            });
+
+      }
+    // Log the error to an error reporting service
+    console.error(error)
+  }, [error])
 
-export default function Error({ reset }: { reset: () => void }) {
   return (
     <div className="mx-auto my-4 flex max-w-xl flex-col rounded-lg border border-neutral-200 bg-white p-8 md:p-12 dark:border-neutral-800 dark:bg-black">
       <h2 className="text-xl font-bold">Oh no!</h2>
       <p className="my-2">
-        There was an issue with our storefront. This could be a temporary issue,
-        please try your action again.
+        {msg}
       </p>
       <button
         className="mx-auto mt-4 flex w-full items-center justify-center rounded-full bg-blue-600 p-4 tracking-wide text-white hover:opacity-90"
-        onClick={() => reset()}
+        onClick={clickHandler}
       >
-        Try Again
+        {error.name === 'UnauthorizedError' ? "To Login" : "Try Again"}
       </button>
     </div>
   );

+ 4 - 5
src/app/(public)/page.tsx

@@ -21,12 +21,11 @@ export default async function Home() {
 const res = await restApiFetch({
   api: `/home`,
   method: "GET",
-  isCookies: true, // 关键开关
-  headers: {
-    "X-Channel": "wap",
-  },
+  isRoute: false,
+  takeAuthorization: false,
+
 });
-console.log("res----------------------",res);
+
 const homeData: HomeApiData = res.body.data;
 
   return (

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

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

@@ -1,8 +1,7 @@
 import {Suspense} from "react";
-import { getServerSession } from "next-auth";
 import { cookies } from 'next/headers';
 import { redirect, RedirectType } from 'next/navigation'
-import { authOptions } from "@utils/auth";
+import { auth } from "@/utils/auth/auth-helper";
 import {serverGraphqlFetch} from "@utils/bagisto/index";
 import {GET_ORDER_DETAILS} from "@/graphql";
 import { OrderDetailsData } from "@/types/customer/type";
@@ -16,13 +15,14 @@ import { GUEST_CART_TOKEN } from "@/utils/constants";
 export default async function SuccessPage({searchParams}: {
   searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
 })  {
+    const session = await auth();// 游客是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;

+ 15 - 13
src/app/(public)/product/[...urlProduct]/page.tsx

@@ -3,7 +3,7 @@ import { Suspense } from "react";
 // import clsx from "clsx";
 // import type { ProductReviewList } from "@/types/products/productDetail";
 // import { getProductReviews } from "@utils/hooks/getProductReviews";
-import { restApiFetch } from "@utils/bagisto/index";
+// import { restApiFetch } from "@utils/bagisto/index";
 import {
   ProductDetailSkeleton,
   RelatedProductSkeleton,
@@ -20,8 +20,9 @@ import { RelatedProductsSection } from "@components/catalog/product/RelatedProdu
 import { HeroCarouselShimmer } from "@components/common/slider";
 import { ProductMedia } from "@/app/(public)/product/_components/ProductMedia";
 import { ProductInformation } from "@/app/(public)/product/_components/ProductInformation";
-import { ProductShortDescription } from "@/app/(public)/product/_components/ProductShortDescription";
+// import { ProductShortDescription } from "@/app/(public)/product/_components/ProductShortDescription";
 import { ProductDetail } from "@/app/(public)/product/_components/ProductDetail";
+import ProductDescription from "../_components/ProductDescription";
 import SwitchButton from "../_components/SwitchButton";
 import ShopServicePanel from "../_components/ShopServicePanel";
 import Recommend from "../_components/youmaylike/Recommend"
@@ -71,12 +72,12 @@ export default async function ProductPage({
   //   String(product._id),
   // );
   // 3792
-  const res = await restApiFetch({
-  api: `/shop/products/${3792}/reviews?page=1&per_page=10&has_images=0&sort=all`,
-  method: "GET",
-});
-const allReviews = res?.body?.data;
-console.log("res----------------------",allReviews);
+  // const res = await restApiFetch({
+  // api: `/shop/products/${3792}/reviews?page=1&per_page=10&has_images=0&sort=all`,
+  // method: "GET",
+// });
+// const allReviews = res?.body?.data;
+console.log("productCustomerFeatures----------------------",product);
   const questionTotal = 73;
   // const imageUrl = getImageUrl(product?.baseImageUrl, baseUrl, NOT_IMAGE);
 
@@ -133,16 +134,17 @@ console.log("res----------------------",allReviews);
             productOptions={productOptions}
             flexibleVariants={flexibleVariants}
             isSaleable={product.isSaleable}
+            shortDescription={product.shortDescription || ""}
           />
           <ShopServicePanel />
-          <ProductShortDescription
+          {/* <ProductShortDescription
             shortDescription={product.shortDescription || ""}
-          />
-
+          /> */}
+         <ProductDescription   htmlString={product?.feature || ""} />
           <SwitchButton
             productId={"3792"} //|| String(product._id)
-            reviewList={allReviews}
-            reviewTotal={allReviews.length}
+            // reviewList={allReviews}
+            // reviewTotal={allReviews.length}
             questionTotal={questionTotal}
             questionList={["aaa"]}
           />

+ 9 - 11
src/app/(public)/product/_components/ProductAddToCart.tsx

@@ -70,20 +70,21 @@ type VariantPriceInfo = {
   totalNowPrice: number; // 现价 $119
   totalLinePrice: number; // 划线原价 $170
   save: number; // 优惠金额 $51
+ 
 };
 
 export function ProductAddToCart({
   isAvailable,
-  isLoading,
   onAddToCart,
   onBuyNow,
   priceInfo, // 父组件传入价格数据
+  currencyCode
 }: {
   isAvailable: boolean;
-  isLoading: boolean;
   onAddToCart: () => void;
   onBuyNow: () => void;
   priceInfo: VariantPriceInfo; // 价格数据源
+  currencyCode:string;
 }) {
   const btnClass =
     "flex items-center justify-center rounded-full text-ly-16 font-bold text-white h-[46px]";
@@ -120,7 +121,7 @@ export function ProductAddToCart({
           <Price
             className="text-ly-gray  text-base line-through "
             amount={String(totalLinePrice)}
-            currencyCode="USD"
+            currencyCode={currencyCode}
           />
         )}
 
@@ -130,14 +131,14 @@ export function ProductAddToCart({
             <Price
               className="bg-ly-gold px-2  leading-ly-22  text-sm mr-2  font-bold"
               amount={`-${save}`}
-              currencyCode="USD"
+              currencyCode={currencyCode}
             />
           )}
           {/* 现价 */}
           <Price
             className="  text-base font-bold mr-1"
             amount={String(totalNowPrice)}
-            currencyCode="USD"
+            currencyCode={currencyCode}
           />
           {/* 右侧下拉箭头SVG */}
           <span onClick={() => setCouponModalOpen(true)}>
@@ -155,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>
@@ -165,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>
@@ -181,6 +178,7 @@ export function ProductAddToCart({
         visible={couponModalOpen}
         onClose={() => setCouponModalOpen(false)}
         priceInfo={priceInfo}
+        currencyCode={currencyCode}
       />
     </div>
   );

+ 95 - 0
src/app/(public)/product/_components/ProductDescription.tsx

@@ -0,0 +1,95 @@
+"use client";
+import { useState, useMemo } from "react";
+
+interface SpecItem {
+  label: string;
+  value: string;
+}
+interface ProductSpecsProps {
+  htmlString: string;
+}
+
+// html表格字符串解析工具函数
+function parseTableHtml(html: string): SpecItem[] {
+  const items: SpecItem[] = [];
+  // 正则匹配每一行 <tr> 里面 th.label 和 td.data
+  const reg = /<tr[\s\S]*?<th class="label">([\s\S]*?)<\/th>[\s\S]*?<td class="data last">([\s\S]*?)<\/td>/g;
+  let match;
+  while ((match = reg.exec(html)) !== null) {
+    const label = match[1].replace(/<[^>]*>/g, "").trim();
+    const value = match[2].replace(/<[^>]*>/g, "").trim();
+    if (label && value) {
+      items.push({ label, value });
+    }
+  }
+  return items;
+}
+
+export default function ProductDescription({ htmlString }: ProductSpecsProps) {
+  // 整体收展(标题旁边箭头)
+  const [panelCollapsed, setPanelCollapsed] = useState(false);
+  // 是否点击了View More,展示全部属性
+  const [showAll, setShowAll] = useState(false);
+
+  const specList = useMemo(() => {
+    return parseTableHtml(htmlString);
+  }, [htmlString]);
+
+  // 默认4条
+  const displayList = showAll ? specList : specList.slice(0, 4);
+  // 是否需要显示View More按钮
+  const needViewMore = specList.length > 4 && !showAll;
+
+  return (
+    <div className="w-full px-4">
+      {/* 头部标题 + 下拉箭头,可整体收展 */}
+      <div
+        className="flex justify-between items-center cursor-pointer pb-2"
+        onClick={() => setPanelCollapsed(!panelCollapsed)}
+      >
+        <h2 className="text-[clamp(1.5rem,3vw,2.25rem)] font-bold">Description</h2>
+        {/* 箭头svg 旋转180代表展开 */}
+        <svg
+          className={`w-7 h-7 transition-transform duration-200 ${panelCollapsed ? "rotate-180" : ""}`}
+          viewBox="0 0 24 24"
+          fill="none"
+          stroke="currentColor"
+          strokeWidth="2"
+        >
+          <path d="M6 9l6 6 6-6" />
+        </svg>
+      </div>
+
+      {/* 主体属性区域,整体收展控制 */}
+      {!panelCollapsed && (
+        <>
+          <div className="pl-4">
+            {displayList.map((item, index) => (
+              <div
+                key={index}
+                className="flex justify-start py-2 items-center"
+              >
+                <span className="text-black text-ly-13 leading-ly-22  font-normal w-35.5 shrink-0">{item.label}</span>
+                <span className="text-[#A6A6A6FF] text-ly-13 leading-ly-22  font-normal text-start ">
+                  {item.value}
+                </span>
+              </div>
+            ))}
+          </div>
+
+          {/* View More按钮 */}
+          {needViewMore && (
+            <div className="flex justify-center mt-5">
+              <button
+                onClick={() => setShowAll(true)}
+                className="px-8 py-2 border border-black rounded-full text-base hover:bg-gray-50 transition"
+              >
+                View More
+              </button>
+            </div>
+          )}
+        </>
+      )}
+    </div>
+  );
+}

+ 46 - 33
src/app/(public)/product/_components/ProductInformation.tsx

@@ -6,13 +6,14 @@ import { ProductOption, ResolvedVariant } from "@/components/catalog/type";
 import { ProductAddToCart } from "@/app/(public)/product/_components/ProductAddToCart";
 import { useCustomToast } from "@utils/hooks/useToast";
 import { useAddProduct } from "@utils/hooks/useAddToCart";
-import { redirect, RedirectType } from "next/navigation";
+import { useRouter } from "next/navigation";
 import InstallmentPopup from "./popup/InstallmentPopup";
 import Guide from "./popup/Guide";
 import ProductText from "./ProductText";
 import NewUserBanner from "./NewUserBanner";
 import PromotionDiscountCardProps from "./PromotionDiscountCardProps ";
 import Image from "next/image";
+import {useConfig} from "@/utils/hooks/useConfig";
 import {
   isValueAvailable,
   isOptionValueAvailable,
@@ -22,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,
@@ -29,15 +31,18 @@ export function ProductInformation({
   productOptions,
   flexibleVariants,
   isSaleable,
+  shortDescription,
 }: {
   name: string;
   productId: number;
   productOptions: ProductOption[];
   flexibleVariants: ResolvedVariant[];
   isSaleable: string | undefined;
+  shortDescription:string;
 }) {
-  const { isCartLoading, onAddToCart } = useAddProduct();
+  const { appendProductToCart } = useAddProduct();
   const { showToast } = useCustomToast();
+  const router = useRouter();
 
   // 记录当前哪个选项组刚刚被点击(用于实现“点击组内全部可点”)
   const [lastClickedOptionId, setLastClickedOptionId] = useState<number>(
@@ -196,45 +201,53 @@ 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) {
+                    router.push('/checkout?step=address');
+                }
+            }
         }
-      }
-    }
-  }
+  } 
 
   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");
     }
   };
-  const currencySymbol = "$";
+  const {getCurrentCurrencyItem} = useConfig();
+  const currentCurrency = getCurrentCurrencyItem();
+  const currencySymbol = currentCurrency.symbol;
+  const currencyCode = currentCurrency.code;
+  
+  // const currencySymbol = "$";
 
   return (
     <>
@@ -242,12 +255,12 @@ export function ProductInformation({
         <h3 className="text-ly-14 text-black mt-4 leading-ly-22">
           {name} {isSaleable}
         </h3>
-        <ProductText ProductText="" />
+        <ProductText ProductText={shortDescription} />
         <div className="flex items-center mt-2">
           <Price
             className="text-ly-16 leading-ly-24 font-bold"
             amount={String(currentVariantInfo.totalNowPrice)}
-            currencyCode="USD"
+            currencyCode={currencyCode}
           />
           {currentVariantInfo.totalLinePrice !==
             currentVariantInfo.totalNowPrice && (
@@ -255,17 +268,17 @@ export function ProductInformation({
               <Price
                 className="text-ly-12 leading-ly-20 text-ly-gray line-through ml-1"
                 amount={String(currentVariantInfo.totalLinePrice)}
-                currencyCode="USD"
+                currencyCode={currencyCode}
               />
               <Price
                 className="text-ly-12 leading-ly-20 text-ly-gray line-through ml-1"
                 amount={String(currentVariantInfo.totalLinePrice)}
-                currencyCode="USD"
+                currencyCode={currencyCode}
               />
               <Price
                 className="text-ly-12 leading-ly-16 bg-ly-gold pr-1.5 pl-1.5 ml-3"
                 amount={String(currentVariantInfo.save)}
-                currencyCode="USD"
+                currencyCode={currencyCode}
               />
             </>
           )}
@@ -420,9 +433,9 @@ export function ProductInformation({
       </div>
       <ProductAddToCart
         isAvailable={isCurrentSelectionAvailable}
-        isLoading={isCartLoading}
         onAddToCart={addToCartHandler}
         onBuyNow={buyNowHandler}
+        currencyCode={currencyCode}
         priceInfo={{
           totalNowPrice: currentVariantInfo.totalNowPrice,
           totalLinePrice: currentVariantInfo.totalLinePrice,

+ 3 - 0
src/app/(public)/product/_components/ProductReviewSection.tsx

@@ -12,6 +12,7 @@ interface ProductReviewSectionProps {
   onOpenBaseModal: () => void;
   activeTab: string;
   onChangeTab: (tabKey: string) => void;
+  uuid:string;
 }
 
 function ProductReviewSection({
@@ -22,6 +23,7 @@ function ProductReviewSection({
   activeTab,
   onOpenBaseModal,
   onChangeTab,
+  uuid,
 }: ProductReviewSectionProps) {
   // const getAllreviews = await getProductReviews(productId)
   console.log("productId-------------:", productId, reviews);
@@ -39,6 +41,7 @@ function ProductReviewSection({
               onClose={closeModal}
               activeTab={activeTab}
               onChangeTab={onChangeTab} // 关键:把切换tab的函数传给弹窗
+              uuid={uuid}
             />
           </>
         ) : (

+ 5 - 1
src/app/(public)/product/_components/ProductText.tsx

@@ -2,9 +2,13 @@
 import { useState } from "react";
 
 export default function ProductText({ ProductText }: { ProductText: string }) {
+  const stripHtml = (html: string) => {
+  if(!html) return ""
+  return html.replace(/<[^>]*>/g, "").trim()
+}
   // 模拟商品长标题
   const titleText =
-    ProductText ||
+     (ProductText ? stripHtml(ProductText) : null) ||
     "Wiggins Hair HD Lace Frontal Wigs straight A Grade Human Hair 13x4 Transparent Lace Front Wig For Women Natural Black 18-30 Inch";
   const [isExpand, setIsExpand] = useState(false);
 

+ 59 - 5
src/app/(public)/product/_components/SwitchButton.tsx

@@ -5,11 +5,12 @@ import Image from "next/image";
 import ReviewAdd from "@/app/(public)/product/_components/review/ReviewAdd";
 import FaqList, { FaqItem } from "./question/FaqList";
 import HairPhotoGallery from "./review/HairPhotoGallery";
+import { clientFetch } from "@/lib/restApiClient";
 interface ReviewQuestionTabProps {
   productId: string;
   questionList: any[];
-  reviewList: any[]; // 替换为你的 ProductReviewList 类型
-  reviewTotal: number; // 评论总数 (allReviews.length)
+  // reviewList: any[]; // 替换为你的 ProductReviewList 类型
+  // reviewTotal: number; // 评论总数 (allReviews.length)
   questionTotal: number; // 问答总数,后端接口获取
 }
 // 模拟截图内FAQ数据
@@ -45,14 +46,66 @@ const mockFaqData: FaqItem[] = [
 ];
 export default function SwitchButton({
   productId,
-  reviewList,
   questionList,
-  reviewTotal,
   questionTotal,
 }: ReviewQuestionTabProps) {
+    // const [uuid, setUuidId] = useState<string>('');
+  // 自己通过接口拿到,不再由父组件传入
+  const [reviewList, setReviewList] = useState<any[]>([]);
+  const [reviewTotal, setReviewTotal] = useState<number>(0);
+    // 获取/生成访客UUID
+  const getUuId = () => {
+    const key = "GUEST_UUID";
+    let id = localStorage.getItem(key);
+    if (!id) {
+      id = crypto.randomUUID();
+      localStorage.setItem(key, id);
+    }
+    return id;
+  };
+  // 组件顶层,只生成一次uuid
+const [clientUuid] = useState(() => getUuId());
+  useEffect(() => {
+  if (!productId) return;
+  // 1. 请求中断控制器,组件卸载时取消网络请求,防止卸载后执行setState
+  const abortCtrl = new AbortController();
+  const signal = abortCtrl.signal;
+
+  // uuid 只初始化生成一次,存ref,不需要触发渲染
+  
+  
+  const fetchReviews = async () => {
+    try {
+      const res = await clientFetch(
+        `/api/shop/products/${productId}/reviews?page=1&per_page=10&has_images=0&sort=all&client_id=${clientUuid}`,
+        { signal } // 绑定中断信号
+      );
+      console.log('res------------------------------ccc:',res);
+
+      if (res.success) {
+        setReviewList(res?.data ?? []);
+        setReviewTotal(res?.pagination?.total ?? 0);
+      }
+    } catch (err: any) {
+      // 手动取消的请求不需要打印报错
+      if (err.name !== "AbortError") {
+        console.error("获取评论失败", err);
+      }
+    }
+  };
+
+  fetchReviews();
+
+  // useEffect清理函数:组件卸载,终止请求
+  return () => {
+    abortCtrl.abort();
+  };
+}, [productId]);
+
+
   // 切换状态:默认激活评论
   const [activeTab, setActiveTab] = useState<"review" | "question">("review");
-  // 提前计算空状态,减少模板内嵌套
+  // 提前计算空状态,减少模板内嵌套 
   const isEmptyReview = useMemo(() => reviewList.length === 0, [reviewList]);
   const isEmptyQuestion = useMemo(
     () => questionList.length === 0,
@@ -147,6 +200,7 @@ export default function SwitchButton({
               onOpenBaseModal={openAllReviewModal}
               activeTab={activeModalTab}
               onChangeTab={onTabChange}
+              uuid={clientUuid}
             />
           )
         ) : isEmptyQuestion ? (

+ 5 - 3
src/app/(public)/product/_components/popup/PriceModal.tsx

@@ -13,12 +13,14 @@ interface PriceModalModalProps {
   visible: boolean;
   onClose: () => void;
   priceInfo: PriceInfo;
+  currencyCode:string;
 }
 
 export default function PriceModal({
   visible,
   onClose,
   priceInfo,
+  currencyCode,
 }: PriceModalModalProps) {
   if (!visible) return null;
 
@@ -59,7 +61,7 @@ export default function PriceModal({
               <Price
                 className="text-ly-20 leading-ly-25 font-normal block "
                 amount={String(totalLinePrice)}
-                currencyCode="USD"
+                currencyCode={currencyCode}
               />
               <p className="text-ly-12 leading-ly-20 font-normal">Retail price</p>
             </div>
@@ -72,7 +74,7 @@ export default function PriceModal({
               <Price
                 className="text-ly-20 leading-ly-25 font-normal "
                 amount={String(realDiscount)}
-                currencyCode="USD"
+                currencyCode={currencyCode}
               />
               <p className="text-ly-12 leading-ly-20 font-normal">Max Discount</p>
             </div>
@@ -85,7 +87,7 @@ export default function PriceModal({
               <Price
                 className="text-ly-20 leading-ly-25 font-bold"
                 amount={String(totalNowPrice)}
-                currencyCode="USD"
+                currencyCode={currencyCode}
               />
               <p className="text-ly-12 leading-ly-20 font-normal">Deal Price</p>
             </div>

File diff suppressed because it is too large
+ 184 - 101
src/app/(public)/product/_components/review/ReviewDetail.tsx


+ 79 - 36
src/app/(public)/product/_components/review/ReviewImageGalleryModal.tsx

@@ -12,6 +12,13 @@ interface GalleryModalProps {
   onClose: () => void;
   galleryList: GalleryItem[];
   initialSlide: number;
+  // 新增:父组件传进来点赞回调
+  onLike: (reviewItem: any) => Promise<void>;
+  // 新增:读取点赞缓存状态
+  getCurrentLike: (reviewItem: any) => {
+    liked: boolean;
+    like_count: number;
+  };
 }
 
 export default function ReviewImageGalleryModal({
@@ -19,6 +26,8 @@ export default function ReviewImageGalleryModal({
   onClose,
   galleryList,
   initialSlide,
+  onLike,
+  getCurrentLike,
 }: GalleryModalProps) {
   const swiperRef = useRef<any>(null);
   // 新增状态存储当前下标,初始值传入initialSlide
@@ -51,45 +60,79 @@ export default function ReviewImageGalleryModal({
           // 滑动切换时更新下标
           onSlideChange={(swiper) => setCurrentIndex(swiper.activeIndex)}
         >
-          {galleryList.map((item, idx) => (
-            <SwiperSlide
-              key={idx}
-              className="flex flex-col justify-between h-full py-4"
-            >
-              {/* 大图 */}
-              <div className="flex-1 flex items-center justify-center px-2">
-                <Image
-                  src={item.imageUrl}
-                  alt={`gallery-${idx}`}
-                  width={600}
-                  height={800}
-                  className="max-w-full max-h-[70vh] object-contain"
-                />
-              </div>
-              {/* 底部用户评论信息 */}
-              <div className="bg-black/80 text-white p-4 mt-4">
-                <div className="flex items-center gap-2 mb-2">
-                  <div className="w-10 h-10 rounded-full bg-gray-300"></div>
-                  <span className="font-medium">{item.review.name}</span>
-                  <span className="ml-auto">{item.review.likeCount} 👍</span>
+          {galleryList.map((item, idx) => {
+            const likeInfo = getCurrentLike(item.review);
+            return (
+              <SwiperSlide
+                key={idx}
+                className="flex flex-col justify-between h-full py-4"
+              >
+                {/* 大图 */}
+                <div className="flex-1 flex items-center justify-center px-2">
+                  <Image
+                    src={item.imageUrl}
+                    alt={`gallery-${idx}`}
+                    width={600}
+                    height={800}
+                    className="max-w-full max-h-[70vh] object-contain"
+                  />
                 </div>
-                <div className="flex gap-0.5 mb-2">
-                  {Array.from({ length: 5 }).map((_, i) => (
-                    <svg
-                      key={i}
-                      width="14"
-                      height="14"
-                      fill={i < item.review.rating ? "#fff" : "#666"}
-                      viewBox="0 0 24 24"
+                {/* 底部用户评论信息 */}
+                <div className="bg-black/80 text-white p-4 mt-4">
+                  <div className="flex items-center gap-2 mb-2">
+                    <div className="w-10 h-10 rounded-full bg-gray-300"></div>
+                    <span className="font-medium">{item.review.name}</span>
+                    <div
+                      className="ml-auto flex items-center gap-1 cursor-pointer"
+                      onClick={() => onLike(item.review)}
                     >
-                      <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
-                    </svg>
-                  ))}
+                      <span
+                        className={likeInfo.liked ? "text-[#036141FF]" : ""}
+                      >
+                        {likeInfo.like_count}
+                      </span>
+                      {likeInfo.liked ? (
+                        <svg
+                          xmlns="http://www.w3.org/2000/svg"
+                          width="16"
+                          height="16"
+                          viewBox="0 0 16 16"
+                          fill="#036141FF"
+                        >
+                          <path d="M8 1.314C12.438-3.248 23.534 4.735 8 15-7.534 4.736 3.562-3.248 8 1.314z" />
+                        </svg>
+                      ) : (
+                        <svg
+                          xmlns="http://www.w3.org/2000/svg"
+                          width="16"
+                          height="16"
+                          viewBox="0 0 16 16"
+                          fill="white"
+                        >
+                          <path d="M8 2.748l-.717-.737C5.6.281 2.514.878 1.4 3.053c-.523 1.023-.641 2.5.314 4.385.92 1.815 2.834 3.981 6.186 6.362 3.352-2.38 5.265-4.547 6.186-6.362.955-1.884.838-3.362.314-4.385C13.486.878 10.4.28 8.717 2.01L8 2.748zM8 15C-7.333 4.868 3.279-3.04 7.824 1.143c.06.055.119.112.176.171a3.12 3.12 0 0 1 .176-.17C12.72-3.042 23.333 4.867 8 15z" />
+                        </svg>
+                      )}
+                    </div>
+                    {/* <span className="ml-auto">{item.review.like_count} 👍</span> */}
+                  </div>
+                  <div className="flex gap-0.5 mb-2">
+                    {Array.from({ length: 5 }).map((_, i) => (
+                      <svg
+                        key={i}
+                        width="14"
+                        height="14"
+                        fill={i < item.review.rating ? "#fff" : "#666"}
+                        viewBox="0 0 24 24"
+                      >
+                        <path d="M12 2l3.09 6.26L22 9.27l-5 4.87 1.18 6.88L12 17.77l-6.18 3.25L7 14.14 2 9.27l6.91-1.01L12 2z" />
+                      </svg>
+                    ))}
+                  </div>
+                  <p className="text-sm text-gray-200">{item.review.comment}</p>
                 </div>
-                <p className="text-sm text-gray-200">{item.review.comment}</p>
-              </div>
-            </SwiperSlide>
-          ))}
+              </SwiperSlide>
+            );
+          })}
         </Swiper>
       </div>
     </div>

File diff suppressed because it is too large
+ 142 - 33
src/app/(public)/product/_components/review/ReviewModal.tsx


+ 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;
-}

+ 11 - 19
src/app/api/customer/growth-value/route.ts

@@ -1,33 +1,25 @@
-import { NextRequest, NextResponse } from "next/server";
+import { NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
 
-export async function GET(req: NextRequest) {
+
+
+export async function GET() {
   try {
-    const guestToken = getAuthToken(req);
+
     const response = await restApiFetch<any>({
       api: "/customer/growth-value",
       method: "GET",
       cache: "no-store",
-      guestToken,
-    });
 
-    return NextResponse.json({
-      status: response.status,
-      data: response.body,
     });
 
+    return NextResponse.json(
+      response.body,
+      {status: response.status}
+    );
+
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 }
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 9 - 17
src/app/api/customer/orders/[id]/route.ts

@@ -1,12 +1,12 @@
 import { NextRequest, NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
+
+
 type Params = Promise<{ id: string }>;
 export async function GET(req: NextRequest, { params }: { params: Params }) {
   try {
      const { id } = await params; // 解包拿到订单ID
-    const guestToken = getAuthToken(req);
+
 
    const apiUrl = `/customer/orders/${id}`;
 
@@ -16,24 +16,16 @@ export async function GET(req: NextRequest, { params }: { params: Params }) {
       api: apiUrl,
       method: "GET",
       cache: "no-store",
-      guestToken,
-    });
 
-    return NextResponse.json({
-      status: response.status,
-      data: response.body,
     });
 
+    return NextResponse.json(
+      response.body,
+      {status: response.status}
+    );
+
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 }
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 7 - 16
src/app/api/customer/orders/route.ts

@@ -1,11 +1,11 @@
 import { NextRequest, NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
+
+
 
 export async function GET(req: NextRequest) {
   try {
-    const guestToken = getAuthToken(req);
+
 
     // 👇 取出所有前端传过来的查询参数
     const searchParams = req.nextUrl.searchParams;
@@ -31,24 +31,15 @@ export async function GET(req: NextRequest) {
       api: apiUrl,
       method: "GET",
       cache: "no-store",
-      guestToken,
+
     });
 
-    return NextResponse.json({
-      status: response.status,
-      data: response.body,
+    return NextResponse.json(response.body,{
+      status: response.status
     });
 
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 }
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 7 - 16
src/app/api/customer/orders/tracking/route.ts

@@ -1,11 +1,11 @@
 import { NextRequest, NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
+
+
 
 export async function GET(req: NextRequest) {
   try {
-    const guestToken = getAuthToken(req);
+
 
     // 👇 取出所有前端传过来的查询参数
     const searchParams = req.nextUrl.searchParams;
@@ -29,24 +29,15 @@ export async function GET(req: NextRequest) {
       api: apiUrl,
       method: "GET",
       cache: "no-store",
-      guestToken,
+
     });
 
-    return NextResponse.json({
-      status: response.status,
-      data: response.body,
+    return NextResponse.json(response.body,{
+      status: response.status
     });
 
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 }
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 7 - 15
src/app/api/customer/reward-points/browse-product/route.ts

@@ -1,10 +1,10 @@
 import { NextRequest, NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
+
+
 export async function POST(req: NextRequest) {
   try {
-    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+    
     const params = await req.json();
     const response = await restApiFetch<{
       data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
@@ -16,28 +16,20 @@ export async function POST(req: NextRequest) {
       method: "POST",
       cache: "no-store",
       variables: params,
-      guestToken: authorizationToken,
+
     });
     // 打印后端原始返回结构
     console.log(
       "接口原始response.body =",
       JSON.stringify(response.body, null, 2),
     );
-    return NextResponse.json({
+    return NextResponse.json(response.body,{
       status: response.status,
-      data: response.body,
+      
     });
   } catch (error) {
     console.log("/customer/token/address---", error); // 调试用
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 },
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 8 - 17
src/app/api/customer/reward-points/follow-status/route.ts

@@ -1,33 +1,24 @@
-import { NextRequest, NextResponse } from "next/server";
+import { NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
 
-export async function GET(req: NextRequest) {
+
+
+export async function GET() {
   try {
-    const guestToken = getAuthToken(req);
+
     const response = await restApiFetch<any>({
       api: "/customer/reward-points/follow-status",
       method: "GET",
       cache: "no-store",
-      guestToken,
+
     });
 
-    return NextResponse.json({
+    return NextResponse.json(response.body,{
       status: response.status,
-      data: response.body,
     });
 
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 }
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 6 - 15
src/app/api/customer/reward-points/follow/route.ts

@@ -1,10 +1,10 @@
 import { NextRequest, NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
+
+
 export async function POST(req: NextRequest) {
   try {
-    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+
     const params = await req.json();
     const response = await restApiFetch<{
       data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
@@ -28,28 +28,19 @@ export async function POST(req: NextRequest) {
       method: "POST",
       cache: "no-store",
       variables: params,
-      guestToken: authorizationToken,
+
     });
     // 打印后端原始返回结构
     console.log(
       "接口原始response.body =",
       JSON.stringify(response.body, null, 2),
     );
-    return NextResponse.json({
+    return NextResponse.json(response.body,{
       status: response.status,
-      data: response.body,
     });
   } catch (error) {
     console.log("/customer/token/address---", error); // 调试用
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 },
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 5 - 15
src/app/api/customer/reward-points/history/route.ts

@@ -1,11 +1,10 @@
 import { NextRequest, NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
+
 
 export async function GET(req: NextRequest) {
   try {
-    const guestToken = getAuthToken(req);
+
 
     // 👇 取出所有前端传过来的查询参数
     const searchParams = req.nextUrl.searchParams;
@@ -31,24 +30,15 @@ export async function GET(req: NextRequest) {
       api: apiUrl,
       method: "GET",
       cache: "no-store",
-      guestToken,
+
     });
 
-    return NextResponse.json({
+    return NextResponse.json(response.body,{
       status: response.status,
-      data: response.body,
+      
     });
 
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 }
-      );
-    }
 
     return NextResponse.json(
       {

+ 21 - 54
src/app/api/customer/token/address/route.ts

@@ -1,11 +1,10 @@
 import { NextRequest, NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
+
 // import type { GiftListBody,FetchWrap  } from '@/types/api/gift/lists';
 export async function GET(req: NextRequest) {
   try {
-    const guestToken = getAuthToken(req);
+
     // 从url取查询参数 id
     const addressId = req.nextUrl.searchParams.get("id");
 
@@ -19,22 +18,14 @@ export async function GET(req: NextRequest) {
       api: apiUrl, //
       method: "GET",
       cache: "no-store",
-      guestToken,
+
     });
-    return NextResponse.json({
+    return NextResponse.json(response.body,{
       status: response.status,
-      data: response.body,
+
     });
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 },
-      );
-    }
+
 
     return NextResponse.json(
       {
@@ -47,7 +38,7 @@ export async function GET(req: NextRequest) {
 }
 export async function POST(req: NextRequest) {
   try {
-    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+
     const params = await req.json();
     const response = await restApiFetch<{
       data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
@@ -68,28 +59,20 @@ export async function POST(req: NextRequest) {
       method: "POST",
       cache: "no-store",
       variables: params,
-      guestToken: authorizationToken,
+
     });
     // 打印后端原始返回结构
     console.log(
       "接口原始response.body =",
       JSON.stringify(response.body, null, 2),
     );
-    return NextResponse.json({
+    return NextResponse.json(response.body,{
       status: response.status,
-      data: response.body,
+
     });
   } catch (error) {
     console.log("/customer/token/address---", error); // 调试用
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 },
-      );
-    }
+
 
     return NextResponse.json(
       {
@@ -102,7 +85,7 @@ export async function POST(req: NextRequest) {
 }
 export async function PUT(req: NextRequest) {
   try {
-    const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+
     const params = await req.json();
     // 从url取查询参数 id
     const addressId = req.nextUrl.searchParams.get("id");
@@ -125,28 +108,20 @@ export async function PUT(req: NextRequest) {
       method: "PUT",
       cache: "no-store",
       variables: params,
-      guestToken: authorizationToken,
+
     });
     // 打印后端原始返回结构
     console.log(
       "接口原始response.body =",
       JSON.stringify(response.body, null, 2),
     );
-    return NextResponse.json({
+    return NextResponse.json(response.body,{
       status: response.status,
-      data: response.body,
+
     });
   } catch (error) {
     console.log("/customer/token/address---", error); // 调试用
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 },
-      );
-    }
+
 
     return NextResponse.json(
       {
@@ -159,28 +134,20 @@ export async function PUT(req: NextRequest) {
 }
 export async function DELETE(req: NextRequest) {
   try {
-    const guestToken = getAuthToken(req);
+
     const addressId = req.nextUrl.searchParams.get("id");
     const response = await restApiFetch<any>({
       api: `/customer/token/address/${addressId}`, //
       method: "DELETE",
       cache: "no-store",
-      guestToken,
+
     });
-    return NextResponse.json({
+    return NextResponse.json(response.body,{
       status: response.status,
-      data: response.body,
+
     });
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 },
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 7 - 15
src/app/api/home/route.ts

@@ -1,11 +1,11 @@
-import { NextRequest, NextResponse } from "next/server";
+import { NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
-export async function GET(req: NextRequest) {
+
+
+export async function GET() {
   try {
     //  const { id } = await params; // 解包拿到订单ID
-    const guestToken = getAuthToken(req);
+  
 
    const apiUrl = `/home`;
 
@@ -15,7 +15,7 @@ export async function GET(req: NextRequest) {
       api: apiUrl,
       method: "GET",
       cache: "no-store",
-      guestToken,
+
     });
 
     return NextResponse.json({
@@ -24,15 +24,7 @@ export async function GET(req: NextRequest) {
     });
 
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 }
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 0 - 40
src/app/api/shop/customer-address-gets/route.ts

@@ -1,40 +0,0 @@
-import { NextRequest, NextResponse } from "next/server";
-import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
-// import type { GiftListBody,FetchWrap  } from '@/types/api/gift/lists';
-export async function GET(req: NextRequest) {
-    try {
-        const guestToken = getAuthToken(req);
-        // 从查询参数取 page,默认 1
-        const id = req.nextUrl.searchParams.get("id") || "1";
-        const response = await restApiFetch<any>({
-            api: `/shop/customer-address-gets?id=${id}`,
-            method:'GET',
-            cache:'no-store',
-            guestToken,
-        });
-        return NextResponse.json({
-            status: response.status,
-            data: response.body,
-        });
-    } catch (error) {
-        if (isBagistoError(error)) {
-            return NextResponse.json(
-                {
-                    data: null,
-                    error: error.cause ?? error,
-                },
-                { status: 200 }
-            );
-        }
-
-        return NextResponse.json(
-            {
-                message: "Network error",
-                error: error instanceof Error ? error.message : error,
-            },
-            { status: 500 }
-        );
-    }
-}

+ 0 - 41
src/app/api/shop/customer-addresses/route.ts

@@ -1,41 +0,0 @@
-import { NextResponse,NextRequest } from "next/server";
-import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
-// import { NextRequestHint } from "next/dist/server/web/adapter";
-// import type { GiftListBody,FetchWrap  } from '@/types/api/gift/lists';
-export async function GET(req: NextRequest) {
-    try {
-        const guestToken = getAuthToken(req);
-        // 从查询参数取 page,默认 1
-        const page = req.nextUrl.searchParams.get("page") || "1";
-        const response = await restApiFetch<any>({
-            api: `/shop/customer-addresses?page=${page}`,
-            method:'GET',
-            cache:'no-store',
-            guestToken,
-        });
-        return NextResponse.json({
-            status: response.status,
-            data: response.body,
-        });
-    } catch (error) {
-        if (isBagistoError(error)) {
-            return NextResponse.json(
-                {
-                    data: null,
-                    error: error.cause ?? error,
-                },
-                { status: 200 }
-            );
-        }
-
-        return NextResponse.json(
-            {
-                message: "Network error",
-                error: error instanceof Error ? error.message : error,
-            },
-            { status: 500 }
-        );
-    }
-}

+ 8 - 15
src/app/api/shop/products/[productId]/reviews/route.ts

@@ -1,14 +1,14 @@
 import { NextRequest, NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { isBagistoError } from "@/utils/type-guards";
-import { getAuthToken } from "@/utils/helper";
+
+
 type Params = Promise<{ productId: string }>;
 
 export async function GET(req: NextRequest, { params }: { params: Params }) {
   try {
     const { productId } = await params;
     const searchParams = req.nextUrl.searchParams;
-    const guestToken = getAuthToken(req);
+
     const response = await restApiFetch<any>({
       api: `/shop/products/${productId}/reviews`,
       method: "GET",
@@ -18,24 +18,17 @@ export async function GET(req: NextRequest, { params }: { params: Params }) {
         per_page: searchParams.get("per_page") ?? 10,
         has_images:searchParams.get("has_images") ?? 0, 
         sort:searchParams.get("sort") ?? 'all', 
+        client_id:searchParams.get("client_id") ?? '', 
       },
-       guestToken,
+
     });
 
-    return NextResponse.json({
+    return NextResponse.json(response.body,{
       status: response.status,
-      data: response.body,
+
     });
   } catch (error) {
-    if (isBagistoError(error)) {
-      return NextResponse.json(
-        {
-          data: null,
-          error: error.cause ?? error,
-        },
-        { status: 200 }
-      );
-    }
+
 
     return NextResponse.json(
       {

+ 42 - 0
src/app/api/shop/reviews/[id]/like/route.ts

@@ -0,0 +1,42 @@
+import { NextRequest, NextResponse } from "next/server";
+import { restApiFetch } from "@/utils/bagisto";
+
+type RouteParams = Promise<{ id: string }>;
+export async function POST(req: NextRequest, {params}: { params: RouteParams }) {
+    try {
+        const { id } = await params;
+
+         const bodyData = await req.json();
+         const api =   `/shop/reviews/${id}/like`;
+        console.log("bodyData,api :",bodyData,api);
+         
+        const response = await restApiFetch<{
+            data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
+            variables: {
+                id: number; // 到时候你自己改看是number还是string
+            }
+        }>({
+            api: `/shop/reviews/${id}/like`,
+            method:'POST',
+            cache:'no-store',
+            variables: bodyData,
+
+        });
+        // 打印后端原始返回结构
+        console.log('接口原始response.body =', JSON.stringify(response.body, null, 2));
+        return NextResponse.json(response.body,{
+            status: response.status,
+        });
+
+    } catch (error) {
+        console.log('/gift/add --- ', error); // 调试用
+        return NextResponse.json(
+            {
+                message: error instanceof Error ? error.message : "Network error",
+                success: false,
+                data: []
+            },
+            { status:500 }
+        );
+    }
+}

+ 3 - 3
src/app/api/shop/reviews/route.ts

@@ -1,10 +1,10 @@
 import { NextRequest, NextResponse } from "next/server";
 import { restApiFetch } from "@/utils/bagisto";
-import { getAuthToken } from "@/utils/helper";
+
 
 export async function POST(req: NextRequest) {
     try {
-        const authorizationToken = getAuthToken(req); // 获取headers中的Authorization的值
+
         const params = await req.json();
         const response = await restApiFetch<{
             data: any; // 这个是返回结果的数据类型,暂时写成any,具体看后端反的数据结构再改成确定的类型1
@@ -16,7 +16,7 @@ export async function POST(req: NextRequest) {
             method:'POST',
             cache:'no-store',
             variables: params,
-            guestToken: authorizationToken,
+
         });
         // 打印后端原始返回结构
         console.log('接口原始response.body =', JSON.stringify(response.body, null, 2));

+ 2 - 0
src/app/layout.tsx

@@ -16,6 +16,7 @@ import { GetCartItemData } from "@/types/cart/type";
 import { Country } from "@/types/checkout/type";
 import { CurrenciesData } from "@/types/currency/type";
 import {CURRENT_CURRENCY,CURRENT_LOCAL,CURRENT_CHANNEL} from "@/utils/constants";
+import AuthSessionGuard from "@/components/customer/AuthSessionGuard";
 
 // Locale revision marker — required for SSR a11y locale sync (Next.js i18n).
 // Removing this breaks server-rendered locale negotiation. Do not edit.
@@ -152,6 +153,7 @@ export default async function RootLayout({
                 <GlobalProviders>
                   {children}
                   <AddToCartModalWrapper />
+                  <AuthSessionGuard />
                 </GlobalProviders>
               </ReduxProvider>
               </ConfigProvider>

+ 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)}
-    />
-  );
-}

+ 1 - 0
src/components/catalog/type.ts

@@ -112,6 +112,7 @@ export interface ProductNode {
   name?: string;
   urlKey?: string;
   isSaleable?: string | undefined;
+  feature?:string;
   description?: string;
   shortDescription?: string;
   specialPrice?: string;

+ 22 - 12
src/components/common/AddToCartModal/AddToCartModal.tsx

@@ -1,7 +1,7 @@
 "use client";
 
 import { useState, useMemo } from "react";
-import { redirect, RedirectType } from 'next/navigation'
+import { useRouter } from "next/navigation";
 import { useAppDispatch, useAppSelector } from "@/store/hooks";
 import { useCustomToast } from "@utils/hooks/useToast";
 import { 
@@ -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,
@@ -35,10 +36,11 @@ import {
 } from "@/utils/variantTools";
 
 export default function AddToCartModal() {
+    const router = useRouter();
     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 +182,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 +208,7 @@ export default function AddToCartModal() {
             return;
         }
 
-        if(currentVariantInfo.variant && !isCartLoading) {
+        if(currentVariantInfo.variant) {
             await addProductToCart();
             callback();
         }
@@ -208,13 +219,13 @@ 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);
+                    router.push('/checkout');
                 }
             }
         }
@@ -295,7 +306,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 (

+ 109 - 0
src/components/customer/AuthSessionGuard.tsx

@@ -0,0 +1,109 @@
+"use client";
+
+import {
+    useCallback,
+    useEffect,
+    useRef,
+} from "react";
+import { signOut } from "next-auth/react";
+import { useApolloClient } from "@apollo/client/react";
+import {
+    AUTH_EXPIRED_EVENT,
+} from "@/utils/auth/auth-events";
+import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
+import { useRouter } from 'next/navigation';
+import { useAppDispatch } from "@/store/hooks";
+import { clearCart } from "@/store/slices/cart-slice";
+
+export default function AuthSessionGuard() {
+
+    const apolloClient = useApolloClient();
+    const router = useRouter();
+    const dispatch = useAppDispatch();
+
+    /**
+     * signOut 正在执行
+     */
+    const signingOutRef = useRef(false);
+
+    const handleAuthExpired = useCallback(
+        async () => {
+
+            /**
+             * 防止重复 signOut
+             */
+            if (signingOutRef.current) {
+                return;
+            }
+
+            signingOutRef.current = true;
+
+            try {
+
+                /**
+                 * 1. 清除 NextAuth session
+                 */
+                await signOut({
+                    redirect: false,
+                });
+                dispatch(clearCart());
+                /**
+                 * 2. 清除 Apollo Cache
+                 *
+                 * 防止旧用户的数据残留。
+                 */
+                await apolloClient.clearStore();
+                confirmDialog({
+                    title: "Unauthorized",
+                    content: 'Authorization Bearer token is required. Please login!',
+                    noCancel: true,
+                }).then(() => {
+                    router.replace('/customer/login')
+                });
+
+            } catch (error) {
+
+                console.error(
+                    "[AuthSessionGuard] Failed to handle auth expiration:",
+                    error
+                );
+
+            } finally {
+
+                /**
+                 * signOut 锁释放。
+                 *
+                 * 注意:
+                 * 这里不是“恢复登录状态”,
+                 * 只是释放并发锁。
+                 */
+                signingOutRef.current = false;
+            }
+        },
+        [apolloClient]
+    );
+
+    useEffect(() => {
+
+        const handleAuthExpiredEvent = () => {
+            void handleAuthExpired();
+        };
+
+        window.addEventListener(
+            AUTH_EXPIRED_EVENT,
+            handleAuthExpiredEvent
+        );
+
+        return () => {
+
+            window.removeEventListener(
+                AUTH_EXPIRED_EVENT,
+                handleAuthExpiredEvent
+            );
+
+        };
+
+    }, [handleAuthExpired]);
+
+    return null;
+}

+ 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(false);
+          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(() => {

+ 1 - 1
src/components/error/ErrorBoundary.tsx

@@ -12,7 +12,7 @@ interface State {
   hasError: boolean;
   error?: Error;
 }
-
+//https://zh-hans.react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary
 export class ErrorBoundary extends Component<Props, State> {
   public state: State = {
     hasError: false,

+ 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

+ 1 - 0
src/graphql/catalog/fragments/ProductDetailed.ts

@@ -10,6 +10,7 @@ export const PRODUCT_DETAILED_FRAGMENT = gql`
     urlKey
     description
     shortDescription
+    feature
     price
     baseImageUrl
     minimumPrice

+ 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

+ 5 - 7
src/graphql/customer/mutations/VerifyCustomer.ts

@@ -1,12 +1,10 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import {CreateVerifyTokenData} from "@/types/types";
 
-export const VERIFY_CUSTOMER = gql`
-  mutation verifyCustomer($token: String!, $clientMutationId: String) {
+export const VERIFY_CUSTOMER: TypedDocumentNode<CreateVerifyTokenData> = gql`
+  mutation verifyCustomer {
     createVerifyToken(
-      input: {
-        token: $token
-        clientMutationId: $clientMutationId
-      }
+      input: {}
     ) {
       verifyToken {
         id

+ 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

+ 20 - 2
src/lib/ApolloErrorHandler.ts

@@ -7,9 +7,21 @@ import {
   UnconventionalError,
 } from "@apollo/client/errors";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
+import { emitAuthExpired } from "@/utils/auth/auth-events";
 // about apollo client error handle https://www.apollographql.com/docs/react/data/error-handling
 // Comprehensive error handling example.
 
+function is401(error: CombinedGraphQLErrors) {
+    let res = false;
+    for(let i = 0; i < error.errors.length; i++) {
+        if(error.errors[i].extensions?.status === 401) {
+            res = true;
+            break;
+        }
+    }
+    return res;
+}
+
 export function handleApolloBusinessError(error: unknown, callback: (err:CombinedGraphQLErrors) => void) {
   if (CombinedGraphQLErrors.is(error)) {
     // Handle GraphQL errors
@@ -18,7 +30,8 @@ export function handleApolloBusinessError(error: unknown, callback: (err:Combine
     //     `[GraphQL error]: Message: ${message}, Location: ${locations}, Path: ${path}`
     //   )
     // );
-    callback(error);
+    if(!is401(error)) callback(error);
+    
     // console.log('CombinedGraphQLErrors--',error.message);
 
   }
@@ -27,7 +40,12 @@ export function handleApolloBusinessError(error: unknown, callback: (err:Combine
 // 已知有一种情况graphql的响应状态码是401,即请求接口的时候请求头缺少X-STOREFRONT-KEY
 export function handleErrorForErrorLink(error: unknown) {
   // CombinedGraphQLErrors 交给具体的业务自己捕获处理
-  if (CombinedGraphQLErrors.is(error)) return;
+  if (CombinedGraphQLErrors.is(error)) {
+    if(is401(error)) {
+       emitAuthExpired();
+    }
+    return
+  }
   if (CombinedProtocolErrors.is(error)) {
     // CombinedProtocolErrors 是GraphQL 传输协议层错误,该项目中遇不到
     // Handle multipart subscription protocol errors

+ 4 - 1
src/lib/restApiClient.ts

@@ -1,6 +1,6 @@
 'use client';
 /**前端客户端组件调rest api 接口 */
-
+import { emitAuthExpired } from "@/utils/auth/auth-events";
 export async function clientFetch<T = any>(apiUrl: string, options: RequestInit = {}): Promise<T> {
     // 请求的是nextjs的代理接口
 
@@ -18,6 +18,9 @@ export async function clientFetch<T = any>(apiUrl: string, options: RequestInit
     const response = await fetch(apiUrl, options);
     const result = await response.json();
     console.log('response -- ', response);
+    if(response.status === 401) { // Unauthorized 需要登录
+        emitAuthExpired();
+    }
     // if(!response.ok) { // 非2xx状态
     //     // 
     // }

+ 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";

+ 9 - 0
src/proxy/auth.ts

@@ -20,6 +20,15 @@ export async function handleAuth(
         return response;
     }
 
+    /**
+     * Server Action 请求不能按照页面访问进行 redirect。
+     */
+    const isServerAction = request.headers.has("Next-Action");
+
+    if (isServerAction) {
+        return response;
+    }
+
     const token = await getToken({
         req: request,
         secret: process.env.NEXTAUTH_SECRET

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


Some files were not shown because too many files changed in this diff