Browse Source

合并dev-freegift

fogwind 1 day ago
parent
commit
e756f8f030
50 changed files with 960 additions and 969 deletions
  1. 10 3
      README.md
  2. 6 3
      src/actions/addProductToCartAction.ts
  3. 3 3
      src/actions/switchCurrencyAction.ts
  4. 2 2
      src/app/(checkout)/checkout/_components/CheckoutAddress/BillingAddressCheckout.tsx
  5. 2 2
      src/app/(checkout)/checkout/_components/CheckoutAddress/ShippingAddressCheckout.tsx
  6. 2 2
      src/app/(checkout)/checkout/_components/CheckoutPlaceOrder.tsx
  7. 66 19
      src/app/(checkout)/checkout/_components/CheckoutProducts/CheckoutProducts.tsx
  8. 63 19
      src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx
  9. 5 6
      src/app/(checkout)/checkout/_components/ContinueToPay/PaymentMethodContinueTpPay.tsx
  10. 12 6
      src/app/(checkout)/checkout/_components/ContinueToPay/ProductsModalPendingOrder.tsx
  11. 1 4
      src/app/(checkout)/checkout/_components/LostInsurance/LostInsurance.tsx
  12. 4 4
      src/app/(checkout)/checkout/_components/PaymentButton/PaypalApplepayButton.tsx
  13. 2 2
      src/app/(checkout)/checkout/_components/PaymentButton/PaypalButton.tsx
  14. 5 4
      src/app/(checkout)/checkout/_components/PaymentMethodCheckout.tsx
  15. 74 44
      src/app/(checkout)/checkout/cart/_components/CartWrapper.tsx
  16. 77 19
      src/app/(checkout)/checkout/cart/_components/UnpaidOrderSection.tsx
  17. 84 31
      src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx
  18. 5 5
      src/app/(public)/product/_components/ProductInformation.tsx
  19. 2 2
      src/app/api/graphql/route.ts
  20. 21 34
      src/app/layout.tsx
  21. 21 0
      src/components/cart/CartBootstrap.tsx
  22. 6 6
      src/components/common/AddToCartModal/AddToCartModal.tsx
  23. 49 49
      src/components/common/CurrencySwitch/CurrencySwitch.tsx
  24. 12 6
      src/components/layout/navbar/NavbarActions.tsx
  25. 9 9
      src/components/layout/navbar/TopSwiperBanner.tsx
  26. 18 15
      src/components/layout/navbar/index.tsx
  27. 5 5
      src/components/theme/filters/FilterPrice.tsx
  28. 10 7
      src/components/theme/ui/Price.tsx
  29. 93 0
      src/graphql/cart/fragments/CartDetails.ts
  30. 2 70
      src/graphql/cart/mutations/AddProductToCart.ts
  31. 6 78
      src/graphql/cart/mutations/CreateApplyCoupon.ts
  32. 2 70
      src/graphql/cart/mutations/CreateMergeCart.ts
  33. 2 70
      src/graphql/cart/mutations/CreateRemoveCoupon.ts
  34. 2 70
      src/graphql/cart/mutations/GetCartItem.ts
  35. 2 70
      src/graphql/cart/mutations/RemoveCartItem.ts
  36. 2 70
      src/graphql/cart/mutations/UpdateCartItems.ts
  37. 3 0
      src/graphql/cart/mutations/readme.md
  38. 2 70
      src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts
  39. 44 12
      src/providers/ConfigProvider.tsx
  40. 15 23
      src/providers/ReduxProvider.tsx
  41. 37 4
      src/store/slices/cart-slice.ts
  42. 2 13
      src/store/store.ts
  43. 10 1
      src/types/cart/type.ts
  44. 31 0
      src/types/freegift/type.ts
  45. 25 0
      src/types/theme/theme-customization.ts
  46. 33 15
      src/utils/bagisto/index.ts
  47. 3 0
      src/utils/cartDetailTools.ts
  48. 31 7
      src/utils/constants.ts
  49. 5 0
      src/utils/helper.ts
  50. 32 15
      src/utils/hooks/useConfig.ts

+ 10 - 3
README.md

@@ -339,7 +339,7 @@ useEffect 只会在客户端执行,具体是在浏览器绘制后执行,服
 35. 结账页默认设置运输方式问题(导致结算联动风暴)
 35. 结账页默认设置运输方式问题(导致结算联动风暴)
 36. 学习set-state-in-effect,然后检查代码,看是否可以优化(特别是CommonModal组件,看能不能不使用useEffect实现)
 36. 学习set-state-in-effect,然后检查代码,看是否可以优化(特别是CommonModal组件,看能不能不使用useEffect实现)
 37. paypal provider 支付参数根据环境变量配置 --- 已完成
 37. paypal provider 支付参数根据环境变量配置 --- 已完成
-38. CartAndUserActions 组件在支付结果页多次挂载问题
+38. CartAndUserActions 组件在支付结果页多次挂载问题 -- 组件已删除
 39. 服务端去掉apollo client。apollo client 只在客户端使用; apollo 错误处理(第9条);
 39. 服务端去掉apollo client。apollo client 只在客户端使用; apollo 错误处理(第9条);
 40. Authorization 写入header优化;-- 已完成
 40. Authorization 写入header优化;-- 已完成
     ---- https://chatgpt.com/share/6a6acb5d-e184-83ea-acfc-839fe0d34caf
     ---- https://chatgpt.com/share/6a6acb5d-e184-83ea-acfc-839fe0d34caf
@@ -358,8 +358,15 @@ useEffect 只会在客户端执行,具体是在浏览器绘制后执行,服
 48. 游客token的创建优化(想在服务端创建写入token)--- 已完成
 48. 游客token的创建优化(想在服务端创建写入token)--- 已完成
 49. 结账页地址模块未登录用户输入邮箱后判断是否注册过,如果注册过让用户登录;--- 已完成
 49. 结账页地址模块未登录用户输入邮箱后判断是否注册过,如果注册过让用户登录;--- 已完成
 50. 注册接口,提交的phone是 +1 345345345 格式,但是保存到数据库后成了+1345345345  -- 后端已处理
 50. 注册接口,提交的phone是 +1 345345345 格式,但是保存到数据库后成了+1345345345  -- 后端已处理
-51. serverGraphqlFetch 方法中关于token和x-currency, x-channel, x-local 是从cookie中获取的,但是next.js build时检测到调用cookie,会将页面强制转换成动态渲染。
-    也就是说没有缓存了。这个问题需要验证和研究,暂时先不管。
+51. 关于分享组件:https://chatgpt.com/share/6aa351f3-5ed0-83ea-8320-43d34d66f10a
+    -- 可以参考https://github.com/nygardk/react-share
+    -- 接着SocialShare写
+    -- https://developer.mozilla.org/zh-CN/docs/Web/API/Clipboard/writeText
+52. serverGraphqlFetch 方法中关于token和x-currency, x-channel, x-local 是从cookie中获取的,但是next.js build时检测到调用cookie,会将页面强制转换成动态渲染。也就是说没有缓存了。这个问题需要验证和研究,暂时先不管。
+53. 加购之后直接跳转结账页没有触发 结账页loading.tsx;在购物车页跳转其他页面很慢,不能立即触发目标页的loading.tsx;
+54. 如果要启用缓存,要区分渠道,货币,语言;
+55. next-auth 中设置的cookie过期时间存在漂移问题:比如8月1号用户登录,设置的30天过期(8月31号过期)。
+    但是如果用户8月3号又访问网站,那么过期时间会以8月3号往后延30天。不知道为什么会是这种机制。
 
 
 
 
 > 39,40 参考 https://chatgpt.com/share/6a4f0637-ad28-83ea-ad44-423740795054
 > 39,40 参考 https://chatgpt.com/share/6a4f0637-ad28-83ea-ad44-423740795054

+ 6 - 3
src/actions/addProductToCartAction.ts

@@ -39,12 +39,14 @@ export async function addProductToCartAction(param:AddToCartVariables) {
             const responseData = response.data?.createAddProductInCart?.addProductInCart;
             const responseData = response.data?.createAddProductInCart?.addProductInCart;
             /** 兜底代码 start*/
             /** 兜底代码 start*/
             // 游客加购,然后清空购物车,然后刷新页面,然后再加购,然后再刷新页面,购物车会失效,所以添加兜底代码
             // 游客加购,然后清空购物车,然后刷新页面,然后再加购,然后再刷新页面,购物车会失效,所以添加兜底代码
-            // 如果之前的cartToken有效,responseCartId和responseCartToken的值是一样的都是cart id; 否则两者不相等
+            // 如果之前的cartToken有效,responseCartId和responseCartToken的值是一样的都是cart id; 否则两者不相等(最开始是这样,现在不是了,详情见下面注释)
             if(responseData) {
             if(responseData) {
+                // 登录状态正常情况下: responseData.id 是字符串(比如:"123"),responseData.cartToken 是null
+                // 游客状态正常情况下:responseData.id 是字符串(比如:"123"),responseData.cartToken 是游客token字符串(比如:203b97c9-3417-476a-95df-293b1478131c)
                 const responseCartId = responseData.id;
                 const responseCartId = responseData.id;
                 const responseCartToken = responseData.cartToken;
                 const responseCartToken = responseData.cartToken;
                 // 游客状态下
                 // 游客状态下
-                if(!accessToken && responseCartId !== responseCartToken) {
+                if(!accessToken && responseCartToken !== null) {
                     await setGuestCookie({
                     await setGuestCookie({
                         guestCartId: responseCartId,
                         guestCartId: responseCartId,
                         guestToken: responseCartToken,
                         guestToken: responseCartToken,
@@ -52,7 +54,8 @@ export async function addProductToCartAction(param:AddToCartVariables) {
                     });
                     });
                 }
                 }
                 // 登录状态,但是后端发现用户的accessToken不可用返回一个游客token时,需要走退出登录流程,提示用户过期了需要重新登录
                 // 登录状态,但是后端发现用户的accessToken不可用返回一个游客token时,需要走退出登录流程,提示用户过期了需要重新登录
-                if(accessToken && responseCartId !== responseCartToken) {
+                // 此时 responseData.id 是字符串(比如:"123"),responseData.cartToken 是游客token字符串(比如:203b97c9-3417-476a-95df-293b1478131c)
+                if(accessToken && responseCartToken !== null) {
                     const e = new Error("Authorization Bearer token has expired!");
                     const e = new Error("Authorization Bearer token has expired!");
                     e.name = "UnauthorizedError";
                     e.name = "UnauthorizedError";
                     return {
                     return {

+ 3 - 3
src/actions/switchCurrencyAction.ts

@@ -1,7 +1,7 @@
 "use server";
 "use server";
 
 
 import { cookies } from "next/headers";
 import { cookies } from "next/headers";
-import { revalidatePath } from "next/cache";
+// import { revalidatePath } from "next/cache";
 import {CURRENT_CURRENCY} from "@/utils/constants";
 import {CURRENT_CURRENCY} from "@/utils/constants";
 import {currencyCookieOptions} from "@/utils/constants/server";
 import {currencyCookieOptions} from "@/utils/constants/server";
 export async function switchCurrencyAction(currency: string) {
 export async function switchCurrencyAction(currency: string) {
@@ -9,6 +9,6 @@ export async function switchCurrencyAction(currency: string) {
     const cookieStore = await cookies()
     const cookieStore = await cookies()
 
 
     cookieStore.set(CURRENT_CURRENCY,currency,currencyCookieOptions);
     cookieStore.set(CURRENT_CURRENCY,currency,currencyCookieOptions);
-
-    revalidatePath('/', 'layout'); // 根 layout.tsx下的所有页面,缓存都失效
+    // 还没有启用缓存,不需要调revalidatePath
+    // revalidatePath('/', 'layout'); // 根 layout.tsx下的所有页面,缓存都失效
 }
 }

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

@@ -19,7 +19,7 @@ import {
     IS_VALID_PHONECODE,
     IS_VALID_PHONECODE,
     IS_VALID_FULL_PHONE 
     IS_VALID_FULL_PHONE 
 } from "@utils/constants";
 } from "@utils/constants";
-import { useConfig } from "@utils/hooks/useConfig";
+import { useCountries } from "@utils/hooks/useConfig";
 import type { CustomerAddressItem } from "@/types/customer/type";
 import type { CustomerAddressItem } from "@/types/customer/type";
 import InputText from "@/components/theme/ui/InputText";
 import InputText from "@/components/theme/ui/InputText";
 import Select from "@/components/theme/ui/Select";
 import Select from "@/components/theme/ui/Select";
@@ -40,7 +40,7 @@ export default function BillingAddressCheckout ({
     onShowFormFieldChange: (e: boolean) => void;
     onShowFormFieldChange: (e: boolean) => void;
     customerAddressList: CustomerAddressItem[];
     customerAddressList: CustomerAddressItem[];
 }) {
 }) {
-    const {countries} = useConfig();
+    const countries = useCountries();
     const { 
     const { 
         getValues,
         getValues,
         setValue,
         setValue,

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

@@ -15,7 +15,7 @@ import {
 import { GET_COUNTRY_STATES } from "@/graphql";
 import { GET_COUNTRY_STATES } from "@/graphql";
 import { EMAIL_REGEX, IS_VALID_INPUT, IS_VALID_FULL_PHONE, IS_VALID_PHONECODE } from "@utils/constants";
 import { EMAIL_REGEX, IS_VALID_INPUT, IS_VALID_FULL_PHONE, IS_VALID_PHONECODE } from "@utils/constants";
 import type { CustomerAddressItem } from "@/types/customer/type";
 import type { CustomerAddressItem } from "@/types/customer/type";
-import { useConfig } from "@utils/hooks/useConfig";
+import { useCountries } from "@utils/hooks/useConfig";
 import InputText from "@/components/theme/ui/InputText";
 import InputText from "@/components/theme/ui/InputText";
 import Select from "@/components/theme/ui/Select";
 import Select from "@/components/theme/ui/Select";
 import PhoneNumberInput from "@/components/theme/ui/PhoneNumberInput/PhoneNumberInput";
 import PhoneNumberInput from "@/components/theme/ui/PhoneNumberInput/PhoneNumberInput";
@@ -40,7 +40,7 @@ export default function ShippingAddressCheckout({
     onEmailChange: (value: string) => void;
     onEmailChange: (value: string) => void;
 }) {
 }) {
 
 
-    const {countries} = useConfig();
+    const countries = useCountries();
     const { 
     const { 
         getValues,
         getValues,
         setValue,
         setValue,

+ 2 - 2
src/app/(checkout)/checkout/_components/CheckoutPlaceOrder.tsx

@@ -9,7 +9,7 @@ import type { PlaceOrderFunction,KlarnaOtherData } from "@/types/checkout/type";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
 import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
-import { useConfig } from '@utils/hooks/useConfig';
+import { useStore } from '@utils/hooks/useConfig';
 import KlarnaButton from "./PaymentButton/KlarnaButton";
 import KlarnaButton from "./PaymentButton/KlarnaButton";
 export default function CheckoutPlaceOrder({
 export default function CheckoutPlaceOrder({
     paymentMethod,
     paymentMethod,
@@ -21,7 +21,7 @@ export default function CheckoutPlaceOrder({
     validateCheckout: () => Promise<boolean>;
     validateCheckout: () => Promise<boolean>;
 }) {
 }) {
     const router = useRouter();
     const router = useRouter();
-    const {store} = useConfig();
+    const {store} = useStore();
     const { showToast } = useCustomToast();
     const { showToast } = useCustomToast();
     const { createPaymentCallback } = usePlaceOrder();
     const { createPaymentCallback } = usePlaceOrder();
     const currentCurrencyCode  = store.currentCurrency;
     const currentCurrencyCode  = store.currentCurrency;

+ 66 - 19
src/app/(checkout)/checkout/_components/CheckoutProducts/CheckoutProducts.tsx

@@ -6,12 +6,12 @@ import Image from "next/image";
 import type {CartDetail, CartItem} from "@/types/cart/type";
 import type {CartDetail, CartItem} from "@/types/cart/type";
 import { useAddProduct } from "@utils/hooks/useAddToCart";
 import { useAddProduct } from "@utils/hooks/useAddToCart";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import { useCustomToast } from "@/utils/hooks/useToast";
-import {useConfig} from "@/utils/hooks/useConfig";
+import { useStore } from "@/utils/hooks/useConfig";
 import { formatCartDetail, formatCartProductOptions } from "@/utils/cartDetailTools";
 import { formatCartDetail, formatCartProductOptions } from "@/utils/cartDetailTools";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import CommonModal from "@/components/theme/ui/CommonModal";
 import CommonModal from "@/components/theme/ui/CommonModal";
-
+import { Price } from "@components/theme/ui/Price";
 
 
 // 修改数量接口 数量不能为0
 // 修改数量接口 数量不能为0
 // 删除接口
 // 删除接口
@@ -30,16 +30,22 @@ export default function CheckoutProducts({
     const { showToast } = useCustomToast();
     const { showToast } = useCustomToast();
     const router = useRouter();
     const router = useRouter();
 
 
-    const {getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
-    const currencySymbol = currentCurrency.symbol;
+    const {store} = useStore();
+    const currentCurrencyCode = store.currentCurrency;
 
 
     const [showProductsModal, setShowProductsModal] = useState(false);
     const [showProductsModal, setShowProductsModal] = useState(false);
 
 
-    const productList = cartData.items.edges.map((item) => {
-        return item.node;
+    const freeGiftProducts: CartItem[] = [];
+    const normalProducts: CartItem[] = [];
+    cartData.items.edges.forEach((item) => {
+        if(item.node.isFreeGift) {
+            freeGiftProducts.push(item.node);
+        } else {
+            normalProducts.push(item.node);
+        }
     });
     });
 
 
+
     const closeProductsModal = () => {
     const closeProductsModal = () => {
         setShowProductsModal(false);
         setShowProductsModal(false);
     };
     };
@@ -118,7 +124,7 @@ export default function CheckoutProducts({
 
 
             <div className="relative mt-3 w-full">
             <div className="relative mt-3 w-full">
                 <div className="flex w-full gap-2 overflow-auto pr-6">
                 <div className="flex w-full gap-2 overflow-auto pr-6">
-                    {productList.map((product) => {
+                    {normalProducts.map((product) => {
                         const baseImage = product.baseImage ? JSON.parse(product.baseImage) : {};
                         const baseImage = product.baseImage ? JSON.parse(product.baseImage) : {};
                         return (
                         return (
                             <div className="relative w-22.5 h-30 flex-none" key={product.id}>
                             <div className="relative w-22.5 h-30 flex-none" key={product.id}>
@@ -153,7 +159,7 @@ export default function CheckoutProducts({
             }
             }
             body={
             body={
                 <div className="relative box-border w-full h-auto px-4 pt-2 pb-8">
                 <div className="relative box-border w-full h-auto px-4 pt-2 pb-8">
-                    {productList.map((product) => {
+                    {normalProducts.map((product) => {
                         const baseImage = product.baseImage ? JSON.parse(product.baseImage) : {};
                         const baseImage = product.baseImage ? JSON.parse(product.baseImage) : {};
                         const productOptions = product.options.edges.map((edge) => edge.node);
                         const productOptions = product.options.edges.map((edge) => edge.node);
                         return (
                         return (
@@ -177,24 +183,65 @@ export default function CheckoutProducts({
                                     </div>
                                     </div>
                                     <div className="flex items-center justify-between">
                                     <div className="flex items-center justify-between">
                                         <div className="flex items-center gap-1.5">
                                         <div className="flex items-center gap-1.5">
-                                            <span className="text-ly-14 font-bold">{currencySymbol}{product.price}</span>
+                                            <Price className="text-ly-14 font-bold"
+                                                amount={String(product.price)}
+                                                currencyCode={currentCurrencyCode}
+                                            />
                                             {/* <span className="text-ly-12 text-[#d3d3d3] line-through">$200.12</span> */}
                                             {/* <span className="text-ly-12 text-[#d3d3d3] line-through">$200.12</span> */}
                                         </div>
                                         </div>
-                                        <div className=" flex-none flex border-1 rounded-lg p-2 gap-4">
-                                            <button className="flex-none" onClick={() => changeProductQty(product,'minus')}>
-                                                <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"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M3.33301 8L12.6663 8"></path></svg>
-                                            </button>
-                                            <span className="text-ly-14">{product.quantity}</span>
-                                            <button className="flex-none" onClick={() => changeProductQty(product,'plus')}>
-                                                <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"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M8.02001 3.3335L8.00781 12.6668"></path><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M3.33301 8L12.6663 8"></path></svg>
-                                            </button>
-                                        </div>
+                                        {product.canChangeQty ?
+                                            <div className=" flex-none flex border-1 rounded-lg p-2 gap-4">
+                                                <button className="flex-none" onClick={() => changeProductQty(product,'minus')}>
+                                                    <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"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M3.33301 8L12.6663 8"></path></svg>
+                                                </button>
+                                                <span className="text-ly-14">{product.quantity}</span>
+                                                <button className="flex-none" onClick={() => changeProductQty(product,'plus')}>
+                                                    <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"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M8.02001 3.3335L8.00781 12.6668"></path><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M3.33301 8L12.6663 8"></path></svg>
+                                                </button>
+                                            </div>
+                                        : 
+                                            <span className="text-ly-14">X{product.quantity}</span>
+                                        }
                                     </div>
                                     </div>
                                 </div>
                                 </div>
                             </div>
                             </div>
                             
                             
                         )
                         )
                     })}
                     })}
+                    
+
+                    {freeGiftProducts.length > 0 && 
+                        <div className="w-full box-border mt-4">
+                            {freeGiftProducts.map((gift) => {
+                                const baseImage = gift.baseImage ? JSON.parse(gift.baseImage) : {};
+                                const productOptions = gift.options.edges.map((edge) => edge.node);
+                                return (
+                                    <div key={gift.id} className="flex justify-between gap-2 w-full bg-ly-lightgray box-border p-3 mt-4 first:mt-0">
+                                        <div className="relative flex-none w-30 h-30">
+                                            <Image className="object-cover" fill
+                                                src={baseImage.medium_image_url || ''} 
+                                                alt={gift.name}
+                                            />
+                                        </div>
+                                        <div className="w-full flex flex-col justify-between">
+                                            <div>
+                                                <p className="text-ly-12 leading-5 line-clamp-2">
+                                                    {gift.name}
+                                                </p>
+                                                <div className="w-full mt-2 text-ly-12 text-ly-placeholder">
+                                                    {formatCartProductOptions(productOptions)}
+                                                </div>
+                                            </div>
+                                            <p className="flex justify-between items-center">
+                                                <span className="text-ly-14 font-bold">{gift.formattedPrice}</span>
+                                                <span className="text-ly-12">{gift.quantity}</span>
+                                            </p>
+                                        </div>
+                                    </div>
+                                )
+                            })}
+                        </div>
+                    }
                            
                            
                 </div>
                 </div>
             }
             }

+ 63 - 19
src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx

@@ -3,12 +3,12 @@
 import { useState } from "react";
 import { useState } from "react";
 import Image from "next/image";
 import Image from "next/image";
 import { useRouter } from 'next/navigation';
 import { useRouter } from 'next/navigation';
-import {useConfig} from "@/utils/hooks/useConfig";
 import type { OrderDetails } from "@/types/customer/type";
 import type { OrderDetails } from "@/types/customer/type";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import ProductsModalPendingOrder from "./ProductsModalPendingOrder";
 import ProductsModalPendingOrder from "./ProductsModalPendingOrder";
 import dynamic from "next/dynamic";
 import dynamic from "next/dynamic";
 import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
 import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
+import { Price } from "@components/theme/ui/Price";
 
 
 const CountDown = dynamic(() => import("@/components/common/CountDown"), {
 const CountDown = dynamic(() => import("@/components/common/CountDown"), {
   ssr: false,
   ssr: false,
@@ -20,13 +20,7 @@ export default function ContinueToPayOrderInfo({
 }) { 
 }) { 
     
     
     const router = useRouter();
     const router = useRouter();
-    const {currencies,getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
-    const currencySymbol = currentCurrency.symbol;
-    const orderCurrency = currencies.find(item => item.code === orderDetail.orderCurrencyCode) || null;
-    const orderSymbol = orderCurrency?.symbol || currencySymbol;
-    
-    
+
     
     
     const [productsModalOpen, setProductsModalOpen] = useState(false);
     const [productsModalOpen, setProductsModalOpen] = useState(false);
 
 
@@ -166,7 +160,12 @@ export default function ContinueToPayOrderInfo({
                 <h3 className="text-ly-24 font-medium">Promotion Details</h3>
                 <h3 className="text-ly-24 font-medium">Promotion Details</h3>
                 <div className="flex justify-between mt-3 first:mt-0">
                 <div className="flex justify-between mt-3 first:mt-0">
                     <p className="text-ly-12 leading-ly-20">Subtotal</p>
                     <p className="text-ly-12 leading-ly-20">Subtotal</p>
-                    <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.subTotal}</p>
+                    <p className="text-ly-12 leading-ly-20">
+                        <Price
+                            amount={orderDetail.subTotal}
+                            currencyCode={orderDetail.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
                 </div>
                 {orderDetail.rewardPointsEarned > 0 &&
                 {orderDetail.rewardPointsEarned > 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
@@ -177,49 +176,89 @@ export default function ContinueToPayOrderInfo({
                 {orderDetail.shippingAmount !== 0 &&
                 {orderDetail.shippingAmount !== 0 &&
                     <div className="flex justify-between mt-3 first:mt-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">Shipping</p>
-                        <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.shippingAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={orderDetail.shippingAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
                 {Number(orderDetail?.shippingInsuranceAmount) > 0 &&
                 {Number(orderDetail?.shippingInsuranceAmount) > 0 &&
                     <div className="flex justify-between mt-3 first:mt-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">Shipping</p>
-                        <p className="text-ly-12 leading-ly-20">{orderSymbol}{Number(orderDetail.shippingInsuranceAmount)}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={orderDetail.shippingInsuranceAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
                 {orderDetail.taxAmount !== 0 &&
                 {orderDetail.taxAmount !== 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Tax</p>
                         <p className="text-ly-12 leading-ly-20">Tax</p>
-                        <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.taxAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={orderDetail.taxAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
                 {(orderDetail.vipPlusAmount !== '0.00' && orderDetail.vipPlusAmount !== null)  &&
                 {(orderDetail.vipPlusAmount !== '0.00' && orderDetail.vipPlusAmount !== null)  &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Plus Fee</p>
                         <p className="text-ly-12 leading-ly-20">Plus Fee</p>
-                        <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.vipPlusAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={orderDetail.vipPlusAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
                 {orderDetail.discountAmount !== 0 &&
                 {orderDetail.discountAmount !== 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Discount</p>
                         <p className="text-ly-12 leading-ly-20">Discount</p>
-                        <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.discountAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={orderDetail.discountAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
                 {(orderDetail.giftcardAmount !== '0.00' && orderDetail.giftcardAmount !== null) &&
                 {(orderDetail.giftcardAmount !== '0.00' && orderDetail.giftcardAmount !== null) &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Gift Card</p>
                         <p className="text-ly-12 leading-ly-20">Gift Card</p>
-                        <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.giftcardAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={orderDetail.giftcardAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
                 {(orderDetail.vipDiscountAmount !== '0.00' && orderDetail.vipDiscountAmount !== null) &&
                 {(orderDetail.vipDiscountAmount !== '0.00' && orderDetail.vipDiscountAmount !== null) &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Plus Save</p>
                         <p className="text-ly-12 leading-ly-20">Plus Save</p>
-                        <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.vipDiscountAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={orderDetail.vipDiscountAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
                 {orderDetail.rewardPointsAmount > 0 &&
                 {orderDetail.rewardPointsAmount > 0 &&
                     <div className="flex justify-between mt-3 first:mt-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">You Redeem ({orderDetail.rewardPointsUsed} Reward Points)</p>
-                        <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.rewardPointsAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={orderDetail.rewardPointsAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
 
 
@@ -229,7 +268,12 @@ export default function ContinueToPayOrderInfo({
                 </div> */}
                 </div> */}
                 <div className="flex justify-between mt-3 first:mt-0">
                 <div className="flex justify-between mt-3 first:mt-0">
                     <p className="text-ly-16 leading-ly-24">Total</p>
                     <p className="text-ly-16 leading-ly-24">Total</p>
-                    <p className="text-ly-16 leading-ly-24 font-bold">{orderSymbol}{orderDetail.grandTotal}</p>
+                    <p className="text-ly-16 leading-ly-24 font-bold">
+                        <Price
+                            amount={orderDetail.grandTotal}
+                            currencyCode={orderDetail.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
                 </div>
             </div>
             </div>
 
 
@@ -238,7 +282,7 @@ export default function ContinueToPayOrderInfo({
             <ProductsModalPendingOrder 
             <ProductsModalPendingOrder 
                 isOpen={productsModalOpen}
                 isOpen={productsModalOpen}
                 products={productItems}
                 products={productItems}
-                currencySymbol={orderSymbol}
+                currencyCode={orderDetail.orderCurrencyCode}
                 onClose={() => closeProductsModal()}
                 onClose={() => closeProductsModal()}
             />
             />
         </>
         </>

+ 5 - 6
src/app/(checkout)/checkout/_components/ContinueToPay/PaymentMethodContinueTpPay.tsx

@@ -16,15 +16,16 @@ import type {RepayOrderPaymentMethodsData} from "@/types/checkout/type";
 import type { OrderDetails } from "@/types/customer/type";
 import type { OrderDetails } from "@/types/customer/type";
 import type {FetchGraphqlResult} from "@/types/graphqlFetch/type";
 import type {FetchGraphqlResult} from "@/types/graphqlFetch/type";
 import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
 import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
+import {currencyCountryMap} from "@/utils/constants";
 import PaypalButton from "../PaymentButton/PaypalButton";
 import PaypalButton from "../PaymentButton/PaypalButton";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import PaypalApplepayButton from "../PaymentButton/PaypalApplepayButton";
 import PaypalApplepayButton from "../PaymentButton/PaypalApplepayButton";
 import KlarnaButton from "../PaymentButton/KlarnaButton";
 import KlarnaButton from "../PaymentButton/KlarnaButton";
 import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
 import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
-import { useConfig } from '@utils/hooks/useConfig';
 import {usePaymentSDKContext} from "@/providers/PaymentSDKProvider";
 import {usePaymentSDKContext} from "@/providers/PaymentSDKProvider";
 
 
+
 /**@todo 优化目标:不使用 react-hook-form */
 /**@todo 优化目标:不使用 react-hook-form */
 export function PaymentMethodContinueTpPay({
 export function PaymentMethodContinueTpPay({
     paymentMethodsDataPromise,
     paymentMethodsDataPromise,
@@ -45,11 +46,9 @@ export function PaymentMethodContinueTpPay({
     const { paymentRepay, createPaymentCallback } = usePlaceOrder();
     const { paymentRepay, createPaymentCallback } = usePlaceOrder();
 
 
     const {loadSdk} = usePaymentSDKContext();
     const {loadSdk} = usePaymentSDKContext();
-    const {currencies, getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
 
 
-    const orderCurrency = currencies.find(item => item.code === orderDetail.orderCurrencyCode) || null;
-    const orderCurrencyCode = orderCurrency?.code || currentCurrency.code;
+    const orderCurrencyCode = orderDetail.orderCurrencyCode;
+    const orderCurrencySymbol = currencyCountryMap[orderCurrencyCode].symbol;
 
 
 
 
     const airwallexCardState = useAirwallexCardState();
     const airwallexCardState = useAirwallexCardState();
@@ -427,7 +426,7 @@ export function PaymentMethodContinueTpPay({
         return getPaymentDescription({
         return getPaymentDescription({
             payment: payment,
             payment: payment,
             grandTotal: orderDetail.grandTotal,
             grandTotal: orderDetail.grandTotal,
-            symble: orderCurrency?.symbol ?? currentCurrency.symbol
+            symble: orderCurrencySymbol
         })
         })
     };
     };
 
 

+ 12 - 6
src/app/(checkout)/checkout/_components/ContinueToPay/ProductsModalPendingOrder.tsx

@@ -3,6 +3,7 @@
 import Image from "next/image";
 import Image from "next/image";
 import CommonModal from "@/components/theme/ui/CommonModal";
 import CommonModal from "@/components/theme/ui/CommonModal";
 import type {ProductsInOrderDetails, ProductItemAdditional} from "@/types/customer/type";
 import type {ProductsInOrderDetails, ProductItemAdditional} from "@/types/customer/type";
+import { Price } from "@components/theme/ui/Price";
 
 
 
 
 // 根据products 计算总价和总个数
 // 根据products 计算总价和总个数
@@ -50,12 +51,12 @@ function getProductAdditionalInfo(productItem: ProductItemAdditional) {
 export default function ProductsModalPendingOrder({
 export default function ProductsModalPendingOrder({
     isOpen,
     isOpen,
     products,
     products,
-    currencySymbol,
+    currencyCode,
     onClose,
     onClose,
 }: {
 }: {
     isOpen: boolean;
     isOpen: boolean;
     products: ProductsInOrderDetails[],
     products: ProductsInOrderDetails[],
-    currencySymbol: string;
+    currencyCode: string;
     onClose: () => void;
     onClose: () => void;
 }) {
 }) {
 
 
@@ -80,7 +81,10 @@ export default function ProductsModalPendingOrder({
             
             
             <div className="mt-6 flex justify-between items-center">
             <div className="mt-6 flex justify-between items-center">
                 <span className="text-ly-12">{totalItems}</span>
                 <span className="text-ly-12">{totalItems}</span>
-                <span className="text-ly-14 font-bold">{currencySymbol + subTotal}</span>
+                <Price className="text-ly-14 font-bold"
+                    amount={subTotal}
+                    currencyCode={currencyCode}
+                />
             </div>
             </div>
         </div>
         </div>
       }
       }
@@ -106,9 +110,11 @@ export default function ProductsModalPendingOrder({
                                 </p>
                                 </p>
                             </div>
                             </div>
                             <div className="flex justify-between items-center">
                             <div className="flex justify-between items-center">
-                                <span className="text-ly-14 font-bold">
-                                    {currencySymbol + product.price}
-                                </span>
+                                <Price className="text-ly-14 font-bold"
+                                    amount={product.price}
+                                    currencyCode={currencyCode}
+                                />
+                                
                                 <span className="text-ly-12">X {product.qtyOrdered}</span>
                                 <span className="text-ly-12">X {product.qtyOrdered}</span>
                             </div>
                             </div>
                         </div>
                         </div>

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

@@ -1,7 +1,6 @@
 "use client";
 "use client";
 
 
 
 
-// import {useConfig} from "@/utils/hooks/useConfig";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import {useSaveCheckoutCart} from "@/utils/hooks/useSaveCheckoutCart";
 import {useSaveCheckoutCart} from "@/utils/hooks/useSaveCheckoutCart";
 import { formatCartDetail } from "@/utils/cartDetailTools";
 import { formatCartDetail } from "@/utils/cartDetailTools";
@@ -21,9 +20,7 @@ export default function LostInsurance({
     onSwitchInsurance: (payload:CartDetail) => void;
     onSwitchInsurance: (payload:CartDetail) => void;
 }) {
 }) {
 
 
-    // const {getCurrentCurrencyItem} = useConfig();
-    // const currentCurrency = getCurrentCurrencyItem();
-    // const currencySymbol = currentCurrency.symbol;
+    
     const { showToast } = useCustomToast();
     const { showToast } = useCustomToast();
     const {saveCheckoutCart} = useSaveCheckoutCart();
     const {saveCheckoutCart} = useSaveCheckoutCart();
     const clickSwitchButton = async (bool: boolean) => {
     const clickSwitchButton = async (bool: boolean) => {

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

@@ -15,7 +15,7 @@ import {
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import type { CartAddress } from "@/types/cart/type";
 import type { CartAddress } from "@/types/cart/type";
-import { useConfig } from "@/utils/hooks/useConfig";
+import { useStore, useCountries } from "@/utils/hooks/useConfig";
 import type {ApplePayAddressContact} from "@/lib/Applepay/type";
 import type {ApplePayAddressContact} from "@/lib/Applepay/type";
 // import {getPaypalApplePayConfig, type PaypalApplePaySession} from "@/lib/Applepay/applepay";
 // import {getPaypalApplePayConfig, type PaypalApplePaySession} from "@/lib/Applepay/applepay";
 
 
@@ -36,10 +36,10 @@ export default function PaypalApplepayButton({
 }) { 
 }) { 
     const router = useRouter();
     const router = useRouter();
     const {createPaymentCallback} = usePlaceOrder();
     const {createPaymentCallback} = usePlaceOrder();
-    const {countries,store,getCurrentCurrencyItem} = useConfig();
+    const countries = useCountries();
+    const {store} = useStore();
 
 
-    const currentCurrency = getCurrentCurrencyItem();
-    const currencyCode = currentCurrency.code;
+    const currencyCode = store.currentCurrency;
     
     
     const storeName = store.storeName;
     const storeName = store.storeName;
     const gatewayOrderIdRef = useRef("");
     const gatewayOrderIdRef = useRef("");

+ 2 - 2
src/app/(checkout)/checkout/_components/PaymentButton/PaypalButton.tsx

@@ -18,7 +18,7 @@ import {
 } from "@paypal/react-paypal-js/sdk-v6";
 } from "@paypal/react-paypal-js/sdk-v6";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
-import { useConfig } from "@utils/hooks/useConfig";
+import { useStore } from "@utils/hooks/useConfig";
 
 
 export default function PaypalButton({
 export default function PaypalButton({
     createOrder,
     createOrder,
@@ -28,7 +28,7 @@ export default function PaypalButton({
   isRePay?: boolean;
   isRePay?: boolean;
 }) { 
 }) { 
     const router = useRouter();
     const router = useRouter();
-    const {store} = useConfig();
+    const {store} = useStore();
 
 
     const {createPaymentCallback} = usePlaceOrder();
     const {createPaymentCallback} = usePlaceOrder();
 
 

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

@@ -3,8 +3,9 @@
 import Image from "next/image";
 import Image from "next/image";
 import { type Ref, useEffect, useImperativeHandle } from "react";
 import { type Ref, useEffect, useImperativeHandle } from "react";
 import { useForm, get } from "react-hook-form";
 import { useForm, get } from "react-hook-form";
-import {useConfig} from "@/utils/hooks/useConfig";
+import {useStore} from "@/utils/hooks/useConfig";
 import {getPaymentMethodLogo,getPaymentDescription} from "@/utils/paymentTools";
 import {getPaymentMethodLogo,getPaymentDescription} from "@/utils/paymentTools";
+import {currencyCountryMap} from "@/utils/constants";
 import type { CheckoutPaymentMethod } from "@/types/checkout/type";
 import type { CheckoutPaymentMethod } from "@/types/checkout/type";
 import AirwallexCardInput from "./PaymentMethodAdditional/AirwallexCardInput";
 import AirwallexCardInput from "./PaymentMethodAdditional/AirwallexCardInput";
 import {useAirwallexCardState} from "@/lib/Airwallex/useAirwallexCard";
 import {useAirwallexCardState} from "@/lib/Airwallex/useAirwallexCard";
@@ -32,9 +33,9 @@ export function PaymentMethodCheckout({
     onPaymentMethodChange: (value: string) => Promise<boolean>;
     onPaymentMethodChange: (value: string) => Promise<boolean>;
 }) {
 }) {
 
 
-    const {getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
-    const currencySymbol = currentCurrency.symbol;
+    const {store} = useStore();
+    const currentCurrencyCode = store.currentCurrency;
+    const currencySymbol = currencyCountryMap[currentCurrencyCode].symbol
 
 
     const airwallexCardState = useAirwallexCardState();
     const airwallexCardState = useAirwallexCardState();
   
   

+ 74 - 44
src/app/(checkout)/checkout/cart/_components/CartWrapper.tsx

@@ -9,10 +9,10 @@ import { useCustomToast } from "@/utils/hooks/useToast";
 import { useAppDispatch } from "@/store/hooks";
 import { useAppDispatch } from "@/store/hooks";
 import { useAddProduct } from "@utils/hooks/useAddToCart";
 import { useAddProduct } from "@utils/hooks/useAddToCart";
 import { updateCart, clearCart } from "@/store/slices/cart-slice";
 import { updateCart, clearCart } from "@/store/slices/cart-slice";
-import {useConfig} from "@/utils/hooks/useConfig";
+import {useStore} from "@/utils/hooks/useConfig";
 import {useLoginModal} from "@/providers/LoginModalProvider";
 import {useLoginModal} from "@/providers/LoginModalProvider";
 import type { CartDetail, CartItem } from "@/types/cart/type";
 import type { CartDetail, CartItem } from "@/types/cart/type";
-import { formatCartDetail } from "@/utils/cartDetailTools";
+import { formatCartDetail, formatCartProductOptions } from "@/utils/cartDetailTools";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 // import {BonusBuy} from "./BonusBuy";
 // import {BonusBuy} from "./BonusBuy";
@@ -21,6 +21,7 @@ import CheckoutVipPlus from "../../_components/VipPlus/CheckoutVipPlus";
 import CheckoutCoupon from "../../_components/CheckoutCoupon/CheckoutCoupon";
 import CheckoutCoupon from "../../_components/CheckoutCoupon/CheckoutCoupon";
 import CommonModal from "@/components/theme/ui/CommonModal";
 import CommonModal from "@/components/theme/ui/CommonModal";
 import Portal from "@/components/common/portal/Portal";
 import Portal from "@/components/common/portal/Portal";
+import { Price } from "@components/theme/ui/Price";
 
 
 function computeGrandTotal(cartData: CartDetail) {
 function computeGrandTotal(cartData: CartDetail) {
 
 
@@ -53,9 +54,8 @@ export function CartWrapper({
     const {deleteProductFromCart, editProductQtyFromCart} = useAddProduct();
     const {deleteProductFromCart, editProductQtyFromCart} = useAddProduct();
 
 
     const { showToast } = useCustomToast();
     const { showToast } = useCustomToast();
-    const {getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
-    const currencySymbol = currentCurrency.symbol;
+    const {store} = useStore();
+    const currentCurrencyCode = store.currentCurrency;
 
 
     const [cartData, setCartData] = useState<CartDetail>(cartDetailData);
     const [cartData, setCartData] = useState<CartDetail>(cartDetailData);
     const bottomRef = useRef<HTMLDivElement>(null);
     const bottomRef = useRef<HTMLDivElement>(null);
@@ -64,8 +64,15 @@ export function CartWrapper({
 
 
     const [bottomZindex, setBottomZindex] = useState('z-49');
     const [bottomZindex, setBottomZindex] = useState('z-49');
 
 
-    const productList = cartData.items.edges.map((item) => {
-        return item.node;
+    const freeGiftProducts: CartItem[] = [];
+    const normalProducts: CartItem[] = [];
+    cartData.items.edges.forEach((item) => {
+        // return item.node;
+        if(item.node.isFreeGift) {
+            freeGiftProducts.push(item.node);
+        } else {
+            normalProducts.push(item.node);
+        }
     });
     });
 
 
     // 这里的grand total 不包含运费和丢件险和积分抵扣金额
     // 这里的grand total 不包含运费和丢件险和积分抵扣金额
@@ -211,7 +218,7 @@ export function CartWrapper({
     return (<>
     return (<>
         <section className="w-full box-border pb-38">
         <section className="w-full box-border pb-38">
             {cartData.isGuest &&
             {cartData.isGuest &&
-                <div className="mt-6 box-border px-4">
+                <div className="mt-4 box-border px-4">
                     <div className="flex items-center justify-center box-border w-full mt-5.5 text-center text-ly-12 bg-ly-lightgray h-7 rounded-sm">
                     <div className="flex items-center justify-center box-border w-full mt-5.5 text-center text-ly-12 bg-ly-lightgray h-7 rounded-sm">
                         Don't lose your cart! Sync it to Your Email. 
                         Don't lose your cart! Sync it to Your Email. 
                         <button type="button" onClick={guestUserLogin}>
                         <button type="button" onClick={guestUserLogin}>
@@ -222,11 +229,12 @@ export function CartWrapper({
             }
             }
             
             
 
 
-            <div className="mt-6 box-border px-4 pb-8">
-                {productList.map((product) => {
+            <div className="mt-4 box-border px-4">
+                {normalProducts.map((product) => {
                     const baseImage = product.baseImage ? JSON.parse(product.baseImage) : {};
                     const baseImage = product.baseImage ? JSON.parse(product.baseImage) : {};
+                    const productOptions = product.options.edges.map((edge) => edge.node);
                     return (
                     return (
-                        <div key={product.id} className="w-full flex gap-2 mt-4 first:mt-0 pb-4 border-b-1 border-b-[#f0f0f0] last:border-none">
+                        <div key={product.id} className="w-full flex gap-2 mt-4 first:mt-0 pb-4 border-b-1 border-b-[#f0f0f0] last:border-none last:pb-0">
                             <div className="relative w-33 h-44 flex-none">
                             <div className="relative w-33 h-44 flex-none">
                                 <Image className="object-cover" fill
                                 <Image className="object-cover" fill
                                     src={baseImage.medium_image_url || ''} 
                                     src={baseImage.medium_image_url || ''} 
@@ -244,48 +252,66 @@ export function CartWrapper({
                                         </button>
                                         </button>
                                     </div>
                                     </div>
                                     <div className="w-full mt-2 text-ly-12 border-1 rounded-sm border-[#d9d9d9] px-2.5 py-1.5 leading-5">
                                     <div className="w-full mt-2 text-ly-12 border-1 rounded-sm border-[#d9d9d9] px-2.5 py-1.5 leading-5">
-                                        {product.sku}
+                                        {formatCartProductOptions(productOptions)}
                                     </div>
                                     </div>
                                 </div>
                                 </div>
                                 <div className="flex items-center justify-between">
                                 <div className="flex items-center justify-between">
                                     <div className="flex items-center gap-1.5">
                                     <div className="flex items-center gap-1.5">
-                                        <span className="text-ly-14 font-bold">{currencySymbol}{product.price}</span>
+                                        <span className="text-ly-14 font-bold">{product.formattedPrice}</span>
                                         {/* <span className="text-ly-12 text-[#d3d3d3] line-through">$200.12</span> */}
                                         {/* <span className="text-ly-12 text-[#d3d3d3] line-through">$200.12</span> */}
                                     </div>
                                     </div>
-                                    <div className=" flex-none flex border-1 rounded-lg p-2 gap-4">
-                                        <button className="flex-none" onClick={() => changeProductQty(product,'minus')} 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"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M3.33301 8L12.6663 8"></path></svg>
-                                        </button>
-                                        <span className="text-ly-14">{product.quantity}</span>
-                                        <button className="flex-none" onClick={() => changeProductQty(product,'plus')} 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"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M8.02001 3.3335L8.00781 12.6668"></path><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M3.33301 8L12.6663 8"></path></svg>
-                                        </button>
-                                    </div>
+                                    {product.canChangeQty ?
+                                        <div className=" flex-none flex border-1 rounded-lg p-2 gap-4">
+                                            <button className="flex-none" onClick={() => changeProductQty(product,'minus')} 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"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M3.33301 8L12.6663 8"></path></svg>
+                                            </button>
+                                            <span className="text-ly-14">{product.quantity}</span>
+                                            <button className="flex-none" onClick={() => changeProductQty(product,'plus')} 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"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M8.02001 3.3335L8.00781 12.6668"></path><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M3.33301 8L12.6663 8"></path></svg>
+                                            </button>
+                                        </div>
+                                    : 
+                                        <span className="text-ly-14">X{product.quantity}</span>
+                                    }
                                 </div>
                                 </div>
                             </div>
                             </div>
                         </div>
                         </div>
                         
                         
                     )
                     )
                 })}
                 })}
-
-                {/* <div className="flex justify-between gap-2 w-full bg-ly-lightgray box-border p-3">
-                    <div className="relative flex-none w-30 h-30">
-                        <Image className="object-cover" fill
-                            src="https://cdn.asteriahair.com/media/catalog/product/1/_/1_1_5.png"
-                            alt="test" 
-                        />
-                    </div>
-                    <div className="w-full flex flex-col justify-between">
-                        <p className="text-ly-12 leading-5">
-                            Asteria Exclusive Bonus Free Random Gifts (1 Order 1 PC)
-                        </p>
-                        <p className="flex justify-between items-center">
-                            <span className="text-ly-14 font-bold">$0.00</span>
-                            <span className="text-ly-12">1</span>
-                        </p>
-                    </div>
-                </div> */}
             </div>
             </div>
+            {freeGiftProducts.length > 0 && 
+                <div className="w-full box-border px-4 mt-4">
+                    {freeGiftProducts.map((gift) => {
+                        const baseImage = gift.baseImage ? JSON.parse(gift.baseImage) : {};
+                        const productOptions = gift.options.edges.map((edge) => edge.node);
+                        return (
+                            <div key={gift.id} className="flex justify-between gap-2 w-full bg-ly-lightgray box-border p-3 mt-4 first:mt-0">
+                                <div className="relative flex-none w-30 h-30">
+                                    <Image className="object-cover" fill
+                                        src={baseImage.medium_image_url || ''} 
+                                        alt={gift.name}
+                                    />
+                                </div>
+                                <div className="w-full flex flex-col justify-between">
+                                    <div>
+                                        <p className="text-ly-12 leading-5 line-clamp-2">
+                                            {gift.name}
+                                        </p>
+                                        <div className="w-full mt-2 text-ly-12 text-ly-placeholder">
+                                            {formatCartProductOptions(productOptions)}
+                                        </div>
+                                    </div>
+                                    <p className="flex justify-between items-center">
+                                        <span className="text-ly-14 font-bold">{gift.formattedPrice}</span>
+                                        <span className="text-ly-12">{gift.quantity}</span>
+                                    </p>
+                                </div>
+                            </div>
+                        )
+                    })}
+                </div>
+            }
 
 
  
  
             <div className="mt-6 box-border px-4">
             <div className="mt-6 box-border px-4">
@@ -335,11 +361,15 @@ export function CartWrapper({
                             <svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M18 9L12 15L6 9"></path></svg>
                             <svg xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M18 9L12 15L6 9"></path></svg>
                         </span>
                         </span>
                         <span className="flex-none flex items-center text-ly-14 bg-ly-gold h-6 px-2 ml-2">
                         <span className="flex-none flex items-center text-ly-14 bg-ly-gold h-6 px-2 ml-2">
-                            -{currencySymbol}{saveAmount}
-                        </span>
-                        <span className="ml-2 text-ly-24 font-bold">
-                            {currencySymbol}{grandTotal}
+                            -<Price className="inline"
+                                amount={saveAmount}
+                                currencyCode={currentCurrencyCode}
+                            />
                         </span>
                         </span>
+                        <Price className="ml-2 text-ly-24 font-bold"
+                            amount={grandTotal}
+                            currencyCode={currentCurrencyCode}
+                        />
                     </button>
                     </button>
                 </div>
                 </div>
                 <div className="flex justify-between mt-4">
                 <div className="flex justify-between mt-4">

+ 77 - 19
src/app/(checkout)/checkout/cart/_components/UnpaidOrderSection.tsx

@@ -6,12 +6,12 @@ import Image from "next/image";
 import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
 import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
 import { useRouter } from 'next/navigation';
 import { useRouter } from 'next/navigation';
 import { useCustomToast } from "@/utils/hooks/useToast";
 import { useCustomToast } from "@/utils/hooks/useToast";
-import {useConfig} from "@/utils/hooks/useConfig";
 import {getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import {getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import type { OrderDetails, ProductsInOrderDetails } from "@/types/customer/type";
 import type { OrderDetails, ProductsInOrderDetails } from "@/types/customer/type";
 import CommonModal from "@/components/theme/ui/CommonModal";
 import CommonModal from "@/components/theme/ui/CommonModal";
+import { Price } from "@components/theme/ui/Price";
 const CountDown = dynamic(() => import("@/components/common/CountDown"), {
 const CountDown = dynamic(() => import("@/components/common/CountDown"), {
   ssr: false,
   ssr: false,
 });
 });
@@ -23,11 +23,6 @@ export function UnpaidOrderSection({
     const { cancelOrder } = usePlaceOrder();
     const { cancelOrder } = usePlaceOrder();
     const router = useRouter();
     const router = useRouter();
     const { showToast } = useCustomToast();
     const { showToast } = useCustomToast();
-    const {currencies,getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
-    const currencySymbol = currentCurrency.symbol;
-    const orderCurrency = currencies.find(item => item.code === orderDetail.orderCurrencyCode) || null;
-    const orderSymbol = orderCurrency?.symbol || currencySymbol;
 
 
     const [isShowUpaidOrder, setIsShowUpaidOrder] = useState(true);
     const [isShowUpaidOrder, setIsShowUpaidOrder] = useState(true);
     const [showProductsModal, setShowProductsModal] = useState(false);
     const [showProductsModal, setShowProductsModal] = useState(false);
@@ -114,7 +109,14 @@ export function UnpaidOrderSection({
             </div>
             </div>
 
 
             <div className=" flex items-center justify-between w-full mt-3">
             <div className=" flex items-center justify-between w-full mt-3">
-                <span className="text-ly-14 font-medium">Total: {orderSymbol}{orderDetail.grandTotal}</span>
+                <span className="text-ly-14 font-medium">
+                    Total: 
+                    <Price className="inline"
+                        amount={String(orderDetail.grandTotal)}
+                        currencyCode={orderDetail.orderCurrencyCode}
+                    />
+                </span>
+                
                 <button className="flex-none flex justify-center items-center px-2 h-8 text-ly-14 text-white bg-ly-middlegreen rounded-md"
                 <button className="flex-none flex justify-center items-center px-2 h-8 text-ly-14 text-white bg-ly-middlegreen rounded-md"
                     onClick={toContinuePay}
                     onClick={toContinuePay}
                 >
                 >
@@ -145,7 +147,10 @@ export function UnpaidOrderSection({
                     <div className="flex w-full justify-between items-center">
                     <div className="flex w-full justify-between items-center">
                         <span className="text-ly-14 font-medium">Total:</span>
                         <span className="text-ly-14 font-medium">Total:</span>
                         <button onClick={openDetails} className="flex items-center text-ly-14 font-medium">
                         <button onClick={openDetails} className="flex items-center text-ly-14 font-medium">
-                            {orderSymbol}{orderDetail.grandTotal}
+                            <Price
+                                amount={orderDetail.grandTotal}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
                             <svg className="flex-none w-6 h-6" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M18 9L12 15L6 9"></path></svg>
                             <svg className="flex-none w-6 h-6" xmlns="http://www.w3.org/2000/svg" xmlnsXlink="http://www.w3.org/1999/xlink" width="24" height="24" viewBox="0 0 24 24" fill="none"><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M18 9L12 15L6 9"></path></svg>
                         </button>
                         </button>
                     </div>
                     </div>
@@ -182,7 +187,10 @@ export function UnpaidOrderSection({
                                     </div>
                                     </div>
                                     <div className="flex items-center justify-between">
                                     <div className="flex items-center justify-between">
                                         <div className="flex items-center gap-1.5">
                                         <div className="flex items-center gap-1.5">
-                                            <span className="text-ly-14 font-bold">{orderSymbol}{product.price}</span>
+                                            <Price className="text-ly-14 font-bold"
+                                                amount={String(product.price)}
+                                                currencyCode={orderDetail.orderCurrencyCode}
+                                            />
                                             {/* <span className="text-ly-12 text-[#d3d3d3] line-through">$200.12</span> */}
                                             {/* <span className="text-ly-12 text-[#d3d3d3] line-through">$200.12</span> */}
                                         </div>
                                         </div>
 
 
@@ -217,7 +225,12 @@ export function UnpaidOrderSection({
 
 
                         <div className="flex justify-between mt-3 first:mt-0">
                         <div className="flex justify-between mt-3 first:mt-0">
                             <p className="text-ly-12 leading-ly-20">Subtotal</p>
                             <p className="text-ly-12 leading-ly-20">Subtotal</p>
-                            <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.subTotal}</p>
+                            <p className="text-ly-12 leading-ly-20">
+                                <Price
+                                    amount={String(orderDetail.subTotal)}
+                                    currencyCode={orderDetail.orderCurrencyCode}
+                                />
+                            </p>
                         </div>
                         </div>
                         {orderDetail.rewardPointsEarned > 0 &&
                         {orderDetail.rewardPointsEarned > 0 &&
                             <div className="flex justify-between mt-3 first:mt-0">
                             <div className="flex justify-between mt-3 first:mt-0">
@@ -228,55 +241,100 @@ export function UnpaidOrderSection({
                         {orderDetail.shippingAmount !== 0 &&
                         {orderDetail.shippingAmount !== 0 &&
                             <div className="flex justify-between mt-3 first:mt-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">Shipping</p>
-                                <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.shippingAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    <Price
+                                        amount={String(orderDetail.shippingAmount)}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                             </div>
                         }
                         }
                         {Number(orderDetail?.shippingInsuranceAmount) > 0 &&
                         {Number(orderDetail?.shippingInsuranceAmount) > 0 &&
                             <div className="flex justify-between mt-3 first:mt-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">Shipping</p>
-                                <p className="text-ly-12 leading-ly-20">{orderSymbol}{Number(orderDetail.shippingInsuranceAmount)}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    <Price
+                                        amount={orderDetail.shippingInsuranceAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                             </div>
                         }
                         }
                         {orderDetail.taxAmount !== 0 &&
                         {orderDetail.taxAmount !== 0 &&
                             <div className="flex justify-between mt-3 first:mt-0">
                             <div className="flex justify-between mt-3 first:mt-0">
                                 <p className="text-ly-12 leading-ly-20">Tax</p>
                                 <p className="text-ly-12 leading-ly-20">Tax</p>
-                                <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.taxAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    <Price
+                                        amount={orderDetail.taxAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                             </div>
                         }
                         }
                         {(orderDetail.vipPlusAmount !== '0.00' && orderDetail.vipPlusAmount !== null)  &&
                         {(orderDetail.vipPlusAmount !== '0.00' && orderDetail.vipPlusAmount !== null)  &&
                             <div className="flex justify-between mt-3 first:mt-0">
                             <div className="flex justify-between mt-3 first:mt-0">
                                 <p className="text-ly-12 leading-ly-20">Plus Fee</p>
                                 <p className="text-ly-12 leading-ly-20">Plus Fee</p>
-                                <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.vipPlusAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    <Price
+                                        amount={orderDetail.vipPlusAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                             </div>
                         }
                         }
                         {orderDetail.discountAmount !== 0 &&
                         {orderDetail.discountAmount !== 0 &&
                             <div className="flex justify-between mt-3 first:mt-0">
                             <div className="flex justify-between mt-3 first:mt-0">
                                 <p className="text-ly-12 leading-ly-20">Discount</p>
                                 <p className="text-ly-12 leading-ly-20">Discount</p>
-                                <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.discountAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    -<Price
+                                        amount={orderDetail.discountAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                             </div>
                         }
                         }
                         {(orderDetail.giftcardAmount !== '0.00' && orderDetail.giftcardAmount !== null) &&
                         {(orderDetail.giftcardAmount !== '0.00' && orderDetail.giftcardAmount !== null) &&
                             <div className="flex justify-between mt-3 first:mt-0">
                             <div className="flex justify-between mt-3 first:mt-0">
                                 <p className="text-ly-12 leading-ly-20">Gift Card</p>
                                 <p className="text-ly-12 leading-ly-20">Gift Card</p>
-                                <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.giftcardAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    -<Price
+                                        amount={orderDetail.giftcardAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                             </div>
                         }
                         }
                         {(orderDetail.vipDiscountAmount !== '0.00' && orderDetail.vipDiscountAmount !== null) &&
                         {(orderDetail.vipDiscountAmount !== '0.00' && orderDetail.vipDiscountAmount !== null) &&
                             <div className="flex justify-between mt-3 first:mt-0">
                             <div className="flex justify-between mt-3 first:mt-0">
                                 <p className="text-ly-12 leading-ly-20">Plus Save</p>
                                 <p className="text-ly-12 leading-ly-20">Plus Save</p>
-                                <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.vipDiscountAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    -<Price
+                                        amount={orderDetail.vipDiscountAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                             </div>
                         }
                         }
                         {orderDetail.rewardPointsAmount > 0 &&
                         {orderDetail.rewardPointsAmount > 0 &&
                             <div className="flex justify-between mt-3 first:mt-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">You Redeem ({orderDetail.rewardPointsUsed} Reward Points)</p>
-                                <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.rewardPointsAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    -<Price
+                                        amount={orderDetail.rewardPointsAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                             </div>
                         }
                         }
 
 
                         <div className="flex justify-between mt-3 first:mt-0">
                         <div className="flex justify-between mt-3 first:mt-0">
                             <p className="text-ly-16 leading-ly-24">Grand Total</p>
                             <p className="text-ly-16 leading-ly-24">Grand Total</p>
-                            <p className="text-ly-16 leading-ly-24 font-bold">{orderSymbol}{orderDetail.grandTotal}</p>
+                            <p className="text-ly-16 leading-ly-24 font-bold">
+                                <Price
+                                    amount={orderDetail.grandTotal}
+                                    currencyCode={orderDetail.orderCurrencyCode}
+                                />
+                            </p>
                         </div>
                         </div>
                     </div>
                     </div>
 
 

+ 84 - 31
src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx

@@ -7,9 +7,9 @@ import Image from "next/image";
 import type {FetchGraphqlResult} from "@/types/graphqlFetch/type";
 import type {FetchGraphqlResult} from "@/types/graphqlFetch/type";
 import type { OrderDetailsData } from "@/types/customer/type";
 import type { OrderDetailsData } from "@/types/customer/type";
 import {deleteGuestCookieAction} from "@/actions";
 import {deleteGuestCookieAction} from "@/actions";
-import { useConfig } from "@utils/hooks/useConfig";
 import {getAddressFromOrderDetailAddressList,getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import {getAddressFromOrderDetailAddressList,getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import Faqs from "./Faqs";
 import Faqs from "./Faqs";
+import { Price } from "@components/theme/ui/Price";
 
 
 /**
 /**
  * 订单详情里的金额的货币符号不会根据用户选择的货币改变 ,而是固定为下单时使用的货币
  * 订单详情里的金额的货币符号不会根据用户选择的货币改变 ,而是固定为下单时使用的货币
@@ -23,22 +23,22 @@ export default function OrderDetailWrapper({
     isGuest: boolean;
     isGuest: boolean;
 }) { 
 }) { 
 
 
-    const {currencies} = useConfig();
+
     const promiseRes = use(orderDetailPromise);
     const promiseRes = use(orderDetailPromise);
     const orderDetailRes = promiseRes.data?.customerOrder ?? null;
     const orderDetailRes = promiseRes.data?.customerOrder ?? null;
     if(!orderDetailRes) {
     if(!orderDetailRes) {
         redirect('/', RedirectType.replace);
         redirect('/', RedirectType.replace);
     }
     }
-    const shippingAddress = getAddressFromOrderDetailAddressList(orderDetailRes?.addresses,"order_shipping");
+    const shippingAddress = getAddressFromOrderDetailAddressList(orderDetailRes.addresses,"order_shipping");
     let orderCreatedAt = 'Can\'t get order date';
     let orderCreatedAt = 'Can\'t get order date';
-    if(orderDetailRes?.createdAt) {
+    if(orderDetailRes.createdAt) {
         const localDate = new Date(orderDetailRes.createdAt);
         const localDate = new Date(orderDetailRes.createdAt);
         orderCreatedAt = localDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
         orderCreatedAt = localDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
     }
     }
     const productItems = orderDetailRes?.items?.edges.map(item => {
     const productItems = orderDetailRes?.items?.edges.map(item => {
         return item.node;
         return item.node;
     }) || [];
     }) || [];
-    const currency = currencies.find(item => item.code === orderDetailRes?.orderCurrencyCode) || null;
+    
 
 
     useEffect(() => {
     useEffect(() => {
         // 如果是游客且支付成功,删除cookie里的GUEST_CART_TOKEN GUEST_CART_ID
         // 如果是游客且支付成功,删除cookie里的GUEST_CART_TOKEN GUEST_CART_ID
@@ -61,10 +61,10 @@ export default function OrderDetailWrapper({
                 </div>
                 </div>
                 <p className="text-ly-16 text-center mt-6">Your Order Has Been Received</p>
                 <p className="text-ly-16 text-center mt-6">Your Order Has Been Received</p>
                 <p className="text-ly-16 mt-4 flex justify-center">
                 <p className="text-ly-16 mt-4 flex justify-center">
-                    <span className="px-3.75 leading-ly-28 bg-[#f0fffa]">Order #{orderDetailRes?._id}</span>
+                    <span className="px-3.75 leading-ly-28 bg-[#f0fffa]">Order #{orderDetailRes._id}</span>
                 </p>
                 </p>
                 <p className="text-ly-12 text-center mt-4 leading-ly-22">
                 <p className="text-ly-12 text-center mt-4 leading-ly-22">
-                    Hello, {orderDetailRes?.customerFirstName} <br/>
+                    Hello, {orderDetailRes.customerFirstName} <br/>
                     Thank you so much for your order !<br/>
                     Thank you so much for your order !<br/>
                     We’ll get started on your order very soon!<br/>
                     We’ll get started on your order very soon!<br/>
                     You’ll receive an order confirmation email with details of your order in an hour, please be sure to check your email and confirm your order, that is very important for us to process your order.
                     You’ll receive an order confirmation email with details of your order in an hour, please be sure to check your email and confirm your order, that is very important for us to process your order.
@@ -96,13 +96,13 @@ export default function OrderDetailWrapper({
                 <div className="mt-6 flex justify-between">
                 <div className="mt-6 flex justify-between">
                     <div className="text-ly-12 flex-none">Order number:</div>
                     <div className="text-ly-12 flex-none">Order number:</div>
                     <div className="w-40.5 text-ly-12 leading-ly-20 text-[#666666] flex-none">
                     <div className="w-40.5 text-ly-12 leading-ly-20 text-[#666666] flex-none">
-                        {orderDetailRes?._id}
+                        {orderDetailRes._id}
                     </div>
                     </div>
                 </div>
                 </div>
                 <div className="mt-6 flex justify-between">
                 <div className="mt-6 flex justify-between">
                     <div className="text-ly-12 flex-none">Status:</div>
                     <div className="text-ly-12 flex-none">Status:</div>
                     <div className="w-40.5 text-ly-12 leading-ly-20 text-[#666666] flex-none capitalize">
                     <div className="w-40.5 text-ly-12 leading-ly-20 text-[#666666] flex-none capitalize">
-                        {orderDetailRes?.status}
+                        {orderDetailRes.status}
                     </div>
                     </div>
                 </div>
                 </div>
                 <div className="mt-6 flex justify-between">
                 <div className="mt-6 flex justify-between">
@@ -114,13 +114,13 @@ export default function OrderDetailWrapper({
                 <div className="mt-6 flex justify-between">
                 <div className="mt-6 flex justify-between">
                     <div className="text-ly-12 flex-none">Shipping method:</div>
                     <div className="text-ly-12 flex-none">Shipping method:</div>
                     <div className="w-40.5 text-ly-12 leading-ly-20 text-[#666666] flex-none">
                     <div className="w-40.5 text-ly-12 leading-ly-20 text-[#666666] flex-none">
-                        {orderDetailRes?.shippingTitle}
+                        {orderDetailRes.shippingTitle}
                     </div>
                     </div>
                 </div>
                 </div>
                 <div className="mt-6 flex justify-between">
                 <div className="mt-6 flex justify-between">
                     <div className="text-ly-12 flex-none">Payment method:</div>
                     <div className="text-ly-12 flex-none">Payment method:</div>
                     <div className="w-40.5 text-ly-12 leading-ly-20 text-[#666666] flex-none">
                     <div className="w-40.5 text-ly-12 leading-ly-20 text-[#666666] flex-none">
-                        {orderDetailRes?.payment?.methodTitle}
+                        {orderDetailRes.payment?.methodTitle}
                     </div>
                     </div>
                 </div>
                 </div>
 
 
@@ -152,7 +152,10 @@ export default function OrderDetailWrapper({
                                 </div>
                                 </div>
                                 <div className="flex justify-between">
                                 <div className="flex justify-between">
                                     <p className="text-ly-14 leading-ly-20 font-bold">
                                     <p className="text-ly-14 leading-ly-20 font-bold">
-                                        {currency && currency.symbol}{product.price}
+                                        <Price
+                                            amount={String(product.price)}
+                                            currencyCode={orderDetailRes.orderCurrencyCode}
+                                        />
                                     </p>
                                     </p>
                                     <p className="text-ly-12 leading-ly-20">x {product.qtyOrdered}</p>
                                     <p className="text-ly-12 leading-ly-20">x {product.qtyOrdered}</p>
                                 </div>
                                 </div>
@@ -166,60 +169,105 @@ export default function OrderDetailWrapper({
             <div className="box-border px-4 w-full mt-6 pt-6 border-t-1 border-black">
             <div className="box-border px-4 w-full mt-6 pt-6 border-t-1 border-black">
                 <div className="flex justify-between mt-3 first:mt-0">
                 <div className="flex justify-between mt-3 first:mt-0">
                     <p className="text-ly-12 leading-ly-20">Subtotal</p>
                     <p className="text-ly-12 leading-ly-20">Subtotal</p>
-                    <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{orderDetailRes?.subTotal}</p>
+                    <p className="text-ly-12 leading-ly-20">
+                        <Price
+                            amount={String(orderDetailRes.subTotal)}
+                            currencyCode={orderDetailRes.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
                 </div>
-                {orderDetailRes?.rewardPointsEarned > 0 &&
+                {orderDetailRes.rewardPointsEarned > 0 &&
                     <div className="flex justify-between mt-3 first:mt-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">You Earn</p>
-                        <p className="text-ly-12 leading-ly-20">{orderDetailRes?.rewardPointsEarned} Reward Points</p>
+                        <p className="text-ly-12 leading-ly-20">{orderDetailRes.rewardPointsEarned} Reward Points</p>
                     </div>
                     </div>
                 }
                 }
-                {!!orderDetailRes?.shippingAmount &&
+                {!!orderDetailRes.shippingAmount &&
                     <div className="flex justify-between mt-3 first:mt-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">Shipping</p>
-                        <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{orderDetailRes?.shippingAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={String(orderDetailRes.shippingAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
-                {Number(orderDetailRes?.shippingInsuranceAmount) > 0 &&
+                {Number(orderDetailRes.shippingInsuranceAmount) > 0 &&
                     <div className="flex justify-between mt-3 first:mt-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">Insurance of Lost</p>
-                        <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{Number(orderDetailRes?.shippingInsuranceAmount)}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={String(orderDetailRes.shippingInsuranceAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
-                {!!orderDetailRes?.taxAmount &&
+                {!!orderDetailRes.taxAmount &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Tax</p>
                         <p className="text-ly-12 leading-ly-20">Tax</p>
-                        <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{orderDetailRes?.taxAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={String(orderDetailRes.taxAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
-                {(orderDetailRes?.vipPlusAmount !== '0.00' && orderDetailRes?.vipPlusAmount !== null) &&
+                {(orderDetailRes.vipPlusAmount !== '0.00' && orderDetailRes.vipPlusAmount !== null) &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Plus Fee</p>
                         <p className="text-ly-12 leading-ly-20">Plus Fee</p>
-                        <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{orderDetailRes?.vipPlusAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={String(orderDetailRes.vipPlusAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
-                {orderDetailRes?.rewardPointsAmount > 0 &&
+                {orderDetailRes.rewardPointsAmount > 0 &&
                     <div className="flex justify-between mt-3 first:mt-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">You Redeem ({orderDetailRes.rewardPointsUsed} Reward Points)</p>
-                        <p className="text-ly-12 leading-ly-20">-{currency && currency.symbol}{orderDetailRes?.rewardPointsAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={String(orderDetailRes.rewardPointsAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
-                {!!orderDetailRes?.discountAmount &&
+                {!!orderDetailRes.discountAmount &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Discount</p>
                         <p className="text-ly-12 leading-ly-20">Discount</p>
-                        <p className="text-ly-12 leading-ly-20">-{currency && currency.symbol}{orderDetailRes?.discountAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={String(orderDetailRes.discountAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
-                {(orderDetailRes?.giftcardAmount !== '0.00' && orderDetailRes?.giftcardAmount !== null) &&
+                {(orderDetailRes.giftcardAmount !== '0.00' && orderDetailRes.giftcardAmount !== null) &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Gift Card</p>
                         <p className="text-ly-12 leading-ly-20">Gift Card</p>
-                        <p className="text-ly-12 leading-ly-20">-{currency && currency.symbol}{orderDetailRes?.giftcardAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={String(orderDetailRes.giftcardAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
                 {(orderDetailRes?.vipDiscountAmount !== '0.00' && orderDetailRes?.vipDiscountAmount !== null) &&
                 {(orderDetailRes?.vipDiscountAmount !== '0.00' && orderDetailRes?.vipDiscountAmount !== null) &&
                     <div className="flex justify-between mt-3 first:mt-0">
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Plus Save</p>
                         <p className="text-ly-12 leading-ly-20">Plus Save</p>
-                        <p className="text-ly-12 leading-ly-20">-{currency && currency.symbol}{orderDetailRes?.vipDiscountAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={String(orderDetailRes.vipDiscountAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                     </div>
                 }
                 }
                 {/* <div className="flex justify-between mt-3 first:mt-0">
                 {/* <div className="flex justify-between mt-3 first:mt-0">
@@ -228,7 +276,12 @@ export default function OrderDetailWrapper({
                 </div> */}
                 </div> */}
                 <div className="flex justify-between mt-3 first:mt-0">
                 <div className="flex justify-between mt-3 first:mt-0">
                     <p className="text-ly-16 leading-ly-24">Total</p>
                     <p className="text-ly-16 leading-ly-24">Total</p>
-                    <p className="text-ly-16 leading-ly-24 font-bold">{currency && currency.symbol}{orderDetailRes?.grandTotal}</p>
+                    <p className="text-ly-16 leading-ly-24 font-bold">
+                        <Price
+                            amount={String(orderDetailRes.grandTotal)}
+                            currencyCode={orderDetailRes.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
                 </div>
             </div>
             </div>
         </>
         </>

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

@@ -14,7 +14,8 @@ import ProductText from "./ProductText";
 import NewUserBanner from "./NewUserBanner";
 import NewUserBanner from "./NewUserBanner";
 import PromotionDiscountCardProps from "./PromotionDiscountCardProps ";
 import PromotionDiscountCardProps from "./PromotionDiscountCardProps ";
 import Image from "next/image";
 import Image from "next/image";
-import {useConfig} from "@/utils/hooks/useConfig";
+import { useStore } from "@utils/hooks/useConfig";
+import {currencyCountryMap} from "@/utils/constants";
 import {
 import {
   isValueAvailable,
   isValueAvailable,
   isOptionValueAvailable,
   isOptionValueAvailable,
@@ -242,10 +243,9 @@ export function ProductInformation({
       addProductToCart("buynow");
       addProductToCart("buynow");
     }
     }
   };
   };
-  const {getCurrentCurrencyItem} = useConfig();
-  const currentCurrency = getCurrentCurrencyItem();
-  const currencySymbol = currentCurrency.symbol;
-  const currencyCode = currentCurrency.code;
+  const {store} = useStore();
+  const currencyCode = store.currentCurrency;
+  const currencySymbol = currencyCountryMap[currencyCode].symbol;
   
   
   // const currencySymbol = "$";
   // const currencySymbol = "$";
 
 

+ 2 - 2
src/app/api/graphql/route.ts

@@ -101,12 +101,12 @@ export async function POST(req: NextRequest) {
 
 
         
         
         const response = await bagistoFetch<any>(fetchOption);
         const response = await bagistoFetch<any>(fetchOption);
-        console.log('bagistoFetch response ------ ', response);
+
         return NextResponse.json(response.body,{
         return NextResponse.json(response.body,{
             status: response.status
             status: response.status
         });
         });
     } catch (error) {
     } catch (error) {
-        console.log('bagistoFetch ERROR ------ ', error);
+
         return NextResponse.json(
         return NextResponse.json(
             {
             {
                 errors:[
                 errors:[

+ 21 - 34
src/app/layout.tsx

@@ -12,9 +12,8 @@ import { ErrorBoundary } from "@/components/error/ErrorBoundary";
 import { AddToCartModalWrapper } from "@components/common/AddToCartModal/AddToCartModalWrapper";
 import { AddToCartModalWrapper } from "@components/common/AddToCartModal/AddToCartModalWrapper";
 import clsx from "clsx";
 import clsx from "clsx";
 import {KernelProvider} from "@components/theme/ui/kernel/KernelProvider";
 import {KernelProvider} from "@components/theme/ui/kernel/KernelProvider";
-import { GET_CART_ITEM,GET_COUNTRIES,GET_CURRENCIES } from "@/graphql";
+import { GET_COUNTRIES,GET_CURRENCIES } from "@/graphql";
 import {serverGraphqlFetch} from "@utils/bagisto/index";
 import {serverGraphqlFetch} from "@utils/bagisto/index";
-import type { GetCartItemData } from "@/types/cart/type";
 import type { Country } from "@/types/checkout/type";
 import type { Country } from "@/types/checkout/type";
 import type { CurrenciesData } from "@/types/currency/type";
 import type { CurrenciesData } from "@/types/currency/type";
 import {CURRENT_CURRENCY,CURRENT_LOCAL,CURRENT_CHANNEL} from "@/utils/constants";
 import {CURRENT_CURRENCY,CURRENT_LOCAL,CURRENT_CHANNEL} from "@/utils/constants";
@@ -73,17 +72,11 @@ export default async function RootLayout({
   if (env.APP_ENV !== "production") {
   if (env.APP_ENV !== "production") {
     paypalClientId = env.PAYPAL_SANDBOX_CLIENT_ID;
     paypalClientId = env.PAYPAL_SANDBOX_CLIENT_ID;
   }
   }
-  // if(paypalClientId === undefined) {
-  //   throw new Error('Paypal Client Id is not defined');
-  // }
   // DEFAULT_CHANNEL DEFAULT_CURRENCY DEFAULT_LOCAL 同时也会写入cookie 
   // DEFAULT_CHANNEL DEFAULT_CURRENCY DEFAULT_LOCAL 同时也会写入cookie 
   const defaultChannel = env.DEFAULT_CHANNEL;
   const defaultChannel = env.DEFAULT_CHANNEL;
   const defaultCurrency = env.DEFAULT_CURRENCY;
   const defaultCurrency = env.DEFAULT_CURRENCY;
   const defaultLocale = env.DEFAULT_LOCAL;
   const defaultLocale = env.DEFAULT_LOCAL;
   const storeName = env.STORE_NAME;
   const storeName = env.STORE_NAME;
-  // if(!defaultChannel || !defaultCurrency || !defaultLocale ||!storeName) {
-  //   throw new Error('Default Channel or Default Currency or Default Locale or store name is not defined');
-  // }
 
 
   const cookieStore = await cookies();
   const cookieStore = await cookies();
   ////////eruda调试/////////
   ////////eruda调试/////////
@@ -95,23 +88,27 @@ export default async function RootLayout({
 
 
 
 
   // 获取country list 和currency list
   // 获取country list 和currency list
-  const {data: countryResData} = await serverGraphqlFetch<{
-      countries: Country[] 
+
+  const countriesPromise = serverGraphqlFetch<{
+        countries: Country[];
   }>({
   }>({
     query: GET_COUNTRIES,
     query: GET_COUNTRIES,
-    cache: 'force-cache',
+    takeAuthorization: false,
+    cache: "force-cache",
     revalidate: 3600,
     revalidate: 3600,
-    tags: ["config-country-list"]
-  });
-  const {data: currencyResData} = await serverGraphqlFetch<CurrenciesData>({
+    tags: ["store-countries"],
+  }).then(({ data }) => data?.countries ?? []);
+
+  const currenciesPromise = serverGraphqlFetch<CurrenciesData>({
     query: GET_CURRENCIES,
     query: GET_CURRENCIES,
-    cache: 'force-cache',
+    takeAuthorization: false,
+    cache: "force-cache",
     revalidate: 3600,
     revalidate: 3600,
-    tags: ["config-currency-list"]
+    tags: ["store-currencies"],
+  }).then(({ data }) => {
+      return (data?.currencies?.edges.map((item) => item.node) ?? [])
   });
   });
- 
-  const countries = countryResData?.countries ?? [];
-  const currencyList = currencyResData?.currencies?.edges.map((item) => item.node) ?? [];
+
   const storeConfig = {
   const storeConfig = {
     // 系统默认值
     // 系统默认值
     defaultChannel: defaultChannel,
     defaultChannel: defaultChannel,
@@ -124,11 +121,9 @@ export default async function RootLayout({
     currentChannel: currentChannel
     currentChannel: currentChannel
   };
   };
 
 
-  const {data: cartDetailsRes} = await serverGraphqlFetch<GetCartItemData>({
-    query: GET_CART_ITEM
-  });
+
   
   
-  const cartDetails = cartDetailsRes?.createReadCart?.readCart ?? null;
+
   return (
   return (
     <html lang="en" suppressHydrationWarning>
     <html lang="en" suppressHydrationWarning>
       <head>
       <head>
@@ -143,21 +138,13 @@ export default async function RootLayout({
             <PayPalWrapper clientId={paypalClientId}>
             <PayPalWrapper clientId={paypalClientId}>
               <ConfigProvider
               <ConfigProvider
                   config={{
                   config={{
-                    currencies: currencyList,
-                    countries: countries,
+                    countriesPromise,
+                    currenciesPromise,
                     store: storeConfig
                     store: storeConfig
                   }}
                   }}
               >
               >
               {/* Redux Provider 的初始化 state 只在首次挂载时生效,不会因为 Server Component 重新渲染而重新初始化。 */}
               {/* Redux Provider 的初始化 state 只在首次挂载时生效,不会因为 Server Component 重新渲染而重新初始化。 */}
-              <ReduxProvider 
-                preloadedState={{
-                  cartDetail:{
-                    cart: cartDetails,
-                    loading: false,
-                    error: null
-                  }
-                }}
-              >
+              <ReduxProvider>
                 <GlobalProviders>
                 <GlobalProviders>
                   {children}
                   {children}
                   <AddToCartModalWrapper />
                   <AddToCartModalWrapper />

+ 21 - 0
src/components/cart/CartBootstrap.tsx

@@ -0,0 +1,21 @@
+"use client";
+
+import { useEffect } from "react";
+import { useAppDispatch, useAppSelector } from "@/store/hooks";
+import { fetchCartDetail } from "@/store/slices/cart-slice";
+
+export default function CartBootstrap() {
+  const dispatch = useAppDispatch();
+
+  const status = useAppSelector(
+    (state) => state.cartDetail.status
+  );
+
+  useEffect(() => {
+    if (status === "idle") {
+      void dispatch(fetchCartDetail());
+    }
+  }, [dispatch, status]);
+
+  return null;
+}

+ 6 - 6
src/components/common/AddToCartModal/AddToCartModal.tsx

@@ -11,7 +11,7 @@ import {
     clearAddToCartProduct 
     clearAddToCartProduct 
 } from '@/store/slices/addToCartDialogSlice';
 } from '@/store/slices/addToCartDialogSlice';
 import { useAddProduct } from "@utils/hooks/useAddToCart";
 import { useAddProduct } from "@utils/hooks/useAddToCart";
-import {useConfig} from "@/utils/hooks/useConfig";
+import {useStore} from "@/utils/hooks/useConfig";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 
 
 import type { 
 import type { 
@@ -34,8 +34,8 @@ import {
 export default function AddToCartModal() {
 export default function AddToCartModal() {
     const router = useRouter();
     const router = useRouter();
     const dispatch = useAppDispatch();
     const dispatch = useAppDispatch();
-    const {getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
+    const {store} = useStore();
+    const currentCurrencyCode = store.currentCurrency;
     const {isOpen, product} = useAppSelector((state) => state.addToCartDialog);
     const {isOpen, product} = useAppSelector((state) => state.addToCartDialog);
 
 
     const { appendProductToCart } = useAddProduct();
     const { appendProductToCart } = useAddProduct();
@@ -285,18 +285,18 @@ export default function AddToCartModal() {
                         <Price 
                         <Price 
                             className="text-ly-14 text-ly-gray line-through"
                             className="text-ly-14 text-ly-gray line-through"
                             amount={String(currentVariantInfo.totalLinePrice)} 
                             amount={String(currentVariantInfo.totalLinePrice)} 
-                            currencyCode={currentCurrency.code}
+                            currencyCode={currentCurrencyCode}
                         />
                         />
                         <div className="flex items-center">
                         <div className="flex items-center">
                             <Price 
                             <Price 
                                 className="text-ly-14 bg-ly-gold py-0.5 px-1"
                                 className="text-ly-14 bg-ly-gold py-0.5 px-1"
                                 amount={String(currentVariantInfo.save)} 
                                 amount={String(currentVariantInfo.save)} 
-                                currencyCode={currentCurrency.code}
+                                currencyCode={currentCurrencyCode}
                             />
                             />
                             <Price 
                             <Price 
                                 className="text-ly-20 font-bold ml-1.5"
                                 className="text-ly-20 font-bold ml-1.5"
                                 amount={String(currentVariantInfo.totalNowPrice)} 
                                 amount={String(currentVariantInfo.totalNowPrice)} 
-                                currencyCode={currentCurrency.code}
+                                currencyCode={currentCurrencyCode}
                             />
                             />
                         </div>
                         </div>
                     </div>
                     </div>

File diff suppressed because it is too large
+ 49 - 49
src/components/common/CurrencySwitch/CurrencySwitch.tsx


File diff suppressed because it is too large
+ 12 - 6
src/components/layout/navbar/NavbarActions.tsx


+ 9 - 9
src/components/layout/navbar/TopSwiperBanner.tsx

@@ -1,6 +1,6 @@
 "use client";
 "use client";
 
 
-import { useEffect, useState } from "react";
+import { useEffect, useState, use } from "react";
 import Link from "next/link";
 import Link from "next/link";
 
 
 const ITEM_HEIGHT = 40;
 const ITEM_HEIGHT = 40;
@@ -14,19 +14,19 @@ export interface AnnouncementItem {
 }
 }
 
 
 interface AnnouncementBarProps {
 interface AnnouncementBarProps {
-  items?: AnnouncementItem[];
+  topBarPromise: Promise<AnnouncementItem[]>;
 }
 }
 
 
-export default function TopSwiperBanner({ items = [] }: AnnouncementBarProps) {
+export default function TopSwiperBanner({ topBarPromise }: AnnouncementBarProps) {
   const [index, setIndex] = useState(0);
   const [index, setIndex] = useState(0);
 
 
   const [enableTransition, setEnableTransition] = useState(true);
   const [enableTransition, setEnableTransition] = useState(true);
 
 
-  const loopItems = [...items, ...(items.length ? [items[0]] : [])];
+  const loopItems = use(topBarPromise);
 
 
   // 自动滚动
   // 自动滚动
   useEffect(() => {
   useEffect(() => {
-    if (items.length <= 1) {
+    if (loopItems.length <= 1) {
       return;
       return;
     }
     }
 
 
@@ -37,11 +37,11 @@ export default function TopSwiperBanner({ items = [] }: AnnouncementBarProps) {
     return () => {
     return () => {
       window.clearInterval(timer);
       window.clearInterval(timer);
     };
     };
-  }, [items.length]);
+  }, [loopItems.length]);
 
 
   // 无缝循环重置
   // 无缝循环重置
   useEffect(() => {
   useEffect(() => {
-    if (index !== items.length) {
+    if (index !== loopItems.length) {
       return;
       return;
     }
     }
 
 
@@ -60,8 +60,8 @@ export default function TopSwiperBanner({ items = [] }: AnnouncementBarProps) {
     return () => {
     return () => {
       window.clearTimeout(timer);
       window.clearTimeout(timer);
     };
     };
-  }, [index, items.length]);
-  if (!items.length) {
+  }, [index, loopItems.length]);
+  if (!loopItems.length) {
     return null;
     return null;
   }
   }
 
 

+ 18 - 15
src/components/layout/navbar/index.tsx

@@ -1,9 +1,9 @@
 import Link from "next/link";
 import Link from "next/link";
+import {Suspense} from "react";
 import LogoIcon from "@components/common/icons/LogoIcon";
 import LogoIcon from "@components/common/icons/LogoIcon";
 import MobileMenuTrigger from "./MobileMenuTrigger";
 import MobileMenuTrigger from "./MobileMenuTrigger";
 import { NavbarActions } from "./NavbarActions";
 import { NavbarActions } from "./NavbarActions";
 import { NavbarErrorBoundary } from "@/components/error/ErrorBoundary";
 import { NavbarErrorBoundary } from "@/components/error/ErrorBoundary";
-
 import { 
 import { 
   GET_TREEFRONT_MENUS } from "@/graphql";
   GET_TREEFRONT_MENUS } from "@/graphql";
 import { serverGraphqlFetch } from "@utils/bagisto/index";
 import { serverGraphqlFetch } from "@utils/bagisto/index";
@@ -13,29 +13,29 @@ import { auth } from "@/utils/auth/auth-helper";
 import TopSwiperBanner from "./TopSwiperBanner"
 import TopSwiperBanner from "./TopSwiperBanner"
 import { restApiFetch } from "@utils/bagisto/index";
 import { restApiFetch } from "@utils/bagisto/index";
 import type { AnnouncementItem } from "./TopSwiperBanner";
 import type { AnnouncementItem } from "./TopSwiperBanner";
+import type { RestfulGetThemeCustomizations } from '@/types/theme/theme-customization';
 export default async function Navbar() {
 export default async function Navbar() {
   // 2. 服务端 fetch 请求我们自己的 Next.js API
   // 2. 服务端 fetch 请求我们自己的 Next.js API
-  const res = await restApiFetch({
+  const topBarPromise:Promise<AnnouncementItem[]> = restApiFetch<{data:RestfulGetThemeCustomizations}>({
     api: `/shop/theme-customizations?name=Top_Bar`,
     api: `/shop/theme-customizations?name=Top_Bar`,
     method: "GET",
     method: "GET",
     isRoute: false,
     isRoute: false,
     takeAuthorization: false,
     takeAuthorization: false,
-  
-  });
-  let topBarData: AnnouncementItem[] = [];
-  try {
-    // 安全取值链
-    const themeItem = res?.body?.[0];
+  }).then((result) => {
+    const themeItem = result.body[0];
     const optionsStr = themeItem?.translation?.options;
     const optionsStr = themeItem?.translation?.options;
     if (optionsStr) {
     if (optionsStr) {
       const parsed = JSON.parse(optionsStr);
       const parsed = JSON.parse(optionsStr);
-      topBarData = parsed?.images ?? [];
+      return parsed?.images ?? [];
+    } else {
+      return [];
     }
     }
-  } catch (err) {
-    console.error("解析 translation.options 失败", err);
-    topBarData = [];
-  }
-  console.log('topbar-res----------------------------:',res);
+  }).catch((err) => {
+      // 获取top_bar失败不应该阻止用户访问网站
+      console.error('get top_bar error: ', err);
+      return [];
+  });
+
   
   
     const session = await auth();// 游客是null
     const session = await auth();// 游客是null
     const isGuest = session === null;
     const isGuest = session === null;
@@ -44,6 +44,7 @@ export default async function Navbar() {
       GetTreefrontMenusResult
       GetTreefrontMenusResult
     >({
     >({
       query: GET_TREEFRONT_MENUS,
       query: GET_TREEFRONT_MENUS,
+      takeAuthorization: false,
     });
     });
     console.log('GetTreefrontMenusResult --- ',menudata)
     console.log('GetTreefrontMenusResult --- ',menudata)
     const menudatas =menudata? menudata.treefrontMenus:[];
     const menudatas =menudata? menudata.treefrontMenus:[];
@@ -51,7 +52,9 @@ export default async function Navbar() {
   return (
   return (
     <NavbarErrorBoundary>
     <NavbarErrorBoundary>
       <header className="sticky top-0 z-10">
       <header className="sticky top-0 z-10">
-         <TopSwiperBanner  items={topBarData} /> 
+        <Suspense fallback={<div className="flex items-center justify-center h-10 w-full overflow-hidden bg-ly-middlegreen text-ly-14 text-white">Loading top bar...</div>}>
+          <TopSwiperBanner  topBarPromise={topBarPromise} /> 
+        </Suspense>
         <nav className="relative flex flex-col items-center justify-between gap-4 bg-neutral-50 p-4 pt-3 pb-3 dark:bg-neutral-900 md:flex-row lg:px-6 lg:py-4">
         <nav className="relative flex flex-col items-center justify-between gap-4 bg-neutral-50 p-4 pt-3 pb-3 dark:bg-neutral-900 md:flex-row lg:px-6 lg:py-4">
           <div className="flex w-full items-center justify-between gap-0 sm:gap-4">
           <div className="flex w-full items-center justify-between gap-0 sm:gap-4">
             {/* 1. THE STATIC SHELL (Visible Instantly) */}
             {/* 1. THE STATIC SHELL (Visible Instantly) */}

+ 5 - 5
src/components/theme/filters/FilterPrice.tsx

@@ -1,7 +1,8 @@
 "use client";
 "use client";
 import { useState, useEffect, useRef } from "react";
 import { useState, useEffect, useRef } from "react";
 import { Slider } from "@heroui/slider";
 import { Slider } from "@heroui/slider";
-import { useConfig } from "@/utils/hooks/useConfig";
+import { useStore } from "@utils/hooks/useConfig";
+import {currencyCountryMap} from "@/utils/constants";
 
 
 type PriceFilterNode = {
 type PriceFilterNode = {
   _id: number;
   _id: number;
@@ -96,10 +97,9 @@ export default function PriceRangeSlider({
   };
   };
 
 
   // 货币符号
   // 货币符号
-  const { getCurrentCurrencyItem } = useConfig();
-  const currentCurrency = getCurrentCurrencyItem();
-  const currencySymbol = currentCurrency.symbol;
-  const currencyCode = currentCurrency.code;
+  const {store} = useStore();
+  const currencyCode = store.currentCurrency;
+  const currencySymbol = currencyCountryMap[currencyCode].symbol;
 
 
   return (
   return (
     <div className="py-5">
     <div className="py-5">

+ 10 - 7
src/components/theme/ui/Price.tsx

@@ -4,15 +4,18 @@ export const Price = ({
   currencyCode = "USD",
   currencyCode = "USD",
   ...rest
   ...rest
 }: {
 }: {
-  amount: string;
+  amount: string | number;
   className?: string;
   className?: string;
   currencyCode: string;
   currencyCode: string;
-} & React.ComponentProps<"p">) => (
-  <p className={className} suppressHydrationWarning={true} {...rest}>
+} & React.ComponentProps<"span">) => {
+  // currencyDisplay使用narrowSymbol的话,加元,澳元的货币符号都会用简写形式显示成$
+  // 使用symbol,美元符号又会显示成US$
+  const currencyDisplay = currencyCode === 'USD' ? 'narrowSymbol' : 'symbol';
+  return <span className={className} suppressHydrationWarning={true} {...rest}>
     {`${new Intl.NumberFormat(undefined, {
     {`${new Intl.NumberFormat(undefined, {
       style: "currency",
       style: "currency",
       currency: currencyCode,
       currency: currencyCode,
-      // currencyDisplay: "narrowSymbol",
-    }).format(parseFloat(amount))}`}
-  </p>
-);
+      currencyDisplay: currencyDisplay,
+    }).format(Number(amount))}`}
+  </span>
+};

+ 93 - 0
src/graphql/cart/fragments/CartDetails.ts

@@ -0,0 +1,93 @@
+/**
+ * AddProductToCart
+ * CreateMergeCart
+ * GetCartItem
+ * RemoveCartItem
+ * UpdateCartItems
+ * CreateApplyCoupon
+ * CreateRemoveCoupon
+ * CreateSaveCheckoutCart
+ * 因为后端schema里上面各个接口定义的类型不同,导致没办法使用graphql的fragment实现语句共用
+ * 只能使用模板字符串拼接方式实现共用(有风险,跳过了graphql的类型检查)
+ */
+
+export const CART_DETAILES_FRAGMENT = `
+    id
+    appliedTaxes
+    itemsQty
+    isGuest
+    itemsCount
+    hasFreeGift
+    freeGiftAvailable
+    freeGiftAvailableQty
+    items {
+        edges {
+            node {
+                id
+                cartId
+                productId
+                name
+                price
+                baseImage
+                sku
+                quantity
+                type
+                productUrlKey
+                canChangeQty
+                options
+                total
+                isFreeGift
+                discountAmount
+                formattedPrice
+                formattedTotal
+            }
+        }
+    }
+    earnableRewardPoints
+    rewardPoints
+    rewardPointsUsed
+    rewardPointsAmount
+    baseRewardPointsAmount
+    formattedRewardPointsAmount
+    useShippingInsurance
+    shippingInsuranceEnabled
+    shippingInsuranceName
+    shippingInsuranceType
+    shippingInsuranceValue
+    shippingInsuranceAmount
+    baseShippingInsuranceAmount
+    formattedShippingInsuranceAmount
+    isVip
+    vipDiscountAmount
+    formattedVipDiscountAmount
+    giftcardNumber
+    subtotal
+    subTotalInclTax
+    discountAmount
+    giftcardAmount
+    vipPlusAmount
+    vipExpireDate
+    taxAmount
+    taxTotal
+    shippingAmount
+    shippingAmountInclTax
+    grandTotal
+    formattedSubtotal
+    formattedSubTotalInclTax
+    formattedGiftcardAmount
+    formattedDiscountAmount
+    formattedVipPlusAmount
+    formattedTaxAmount
+    formattedTaxTotal
+    formattedShippingAmount
+    formattedShippingAmountInclTax
+    formattedGrandTotal
+    couponCode
+    selectedShippingRate
+    selectedShippingRateTitle
+    paymentMethod
+    paymentMethodTitle
+    haveStockableItems
+    billingAddress
+    shippingAddress
+`;

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

@@ -1,5 +1,6 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import type { AddToCartData,AddToCartVariables } from "@/types/cart/type";
 import type { AddToCartData,AddToCartVariables } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 
 export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData,AddToCartVariables> = gql`
 export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData,AddToCartVariables> = gql`
   mutation createAddProductInCart(
   mutation createAddProductInCart(
@@ -17,78 +18,9 @@ export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData,AddToCa
       }
       }
     ) {
     ) {
       addProductInCart {
       addProductInCart {
-        id
+        ${CART_DETAILES_FRAGMENT}
         cartToken
         cartToken
         sessionToken
         sessionToken
-        appliedTaxes
-        itemsQty
-        isGuest
-        itemsCount
-        items {
-          edges {
-            node {
-              id
-              cartId
-              productId
-              name
-              price
-              baseImage
-              sku
-              quantity
-              type
-              productUrlKey
-              canChangeQty
-              options
-            }
-          }
-        }
-        earnableRewardPoints
-        rewardPoints
-        rewardPointsUsed
-        rewardPointsAmount
-        baseRewardPointsAmount
-        formattedRewardPointsAmount
-        useShippingInsurance
-        shippingInsuranceEnabled
-        shippingInsuranceName
-        shippingInsuranceType
-        shippingInsuranceValue
-        shippingInsuranceAmount
-        baseShippingInsuranceAmount
-        formattedShippingInsuranceAmount
-        isVip
-        vipDiscountAmount
-        formattedVipDiscountAmount
-        giftcardNumber
-        subtotal
-        subTotalInclTax
-        discountAmount
-        giftcardAmount
-        vipPlusAmount
-        vipExpireDate
-        taxAmount
-        taxTotal
-        shippingAmount
-        shippingAmountInclTax
-        grandTotal
-        formattedSubtotal
-        formattedSubTotalInclTax
-        formattedGiftcardAmount
-        formattedDiscountAmount
-        formattedVipPlusAmount
-        formattedTaxAmount
-        formattedTaxTotal
-        formattedShippingAmount
-        formattedShippingAmountInclTax
-        formattedGrandTotal
-        couponCode
-        selectedShippingRate
-        selectedShippingRateTitle
-        paymentMethod
-        paymentMethodTitle
-        haveStockableItems
-        billingAddress
-        shippingAddress
         success
         success
         message
         message
       }
       }

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

@@ -1,85 +1,13 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import type { CreateApplyCouponData } from "@/types/cart/type";
 import type { CreateApplyCouponData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 
 export const CREATE_APPLY_COUPON: TypedDocumentNode<CreateApplyCouponData,{couponCode: string;}> = gql`
 export const CREATE_APPLY_COUPON: TypedDocumentNode<CreateApplyCouponData,{couponCode: string;}> = gql`
-    mutation createApplyCoupon (
-        $couponCode: String!
-    ) {
-        createApplyCoupon(input: {
-            couponCode: $couponCode
-        }) {
-                applyCoupon {
-                    id
-                    appliedTaxes
-                    itemsQty
-                    isGuest
-                    itemsCount
-                    items {
-                    edges {
-                        node {
-                        id
-                        cartId
-                        productId
-                        name
-                        price
-                        baseImage
-                        sku
-                        quantity
-                        type
-                        productUrlKey
-                        canChangeQty
-                        options
-                        }
-                    }
-                    }
-                    earnableRewardPoints
-                    rewardPoints
-                    rewardPointsUsed
-                    rewardPointsAmount
-                    baseRewardPointsAmount
-                    formattedRewardPointsAmount
-                    useShippingInsurance
-                    shippingInsuranceEnabled
-                    shippingInsuranceName
-                    shippingInsuranceType
-                    shippingInsuranceValue
-                    shippingInsuranceAmount
-                    baseShippingInsuranceAmount
-                    formattedShippingInsuranceAmount
-                    isVip
-                    vipDiscountAmount
-                    formattedVipDiscountAmount
-                    giftcardNumber
-                    subtotal
-                    subTotalInclTax
-                    discountAmount
-                    giftcardAmount
-                    vipPlusAmount
-                    vipExpireDate
-                    taxAmount
-                    taxTotal
-                    shippingAmount
-                    shippingAmountInclTax
-                    grandTotal
-                    formattedSubtotal
-                    formattedSubTotalInclTax
-                    formattedGiftcardAmount
-                    formattedDiscountAmount
-                    formattedVipPlusAmount
-                    formattedTaxAmount
-                    formattedTaxTotal
-                    formattedShippingAmount
-                    formattedShippingAmountInclTax
-                    formattedGrandTotal
-                    couponCode
-                    selectedShippingRate
-                    selectedShippingRateTitle
-                    paymentMethod
-                    paymentMethodTitle
-                    haveStockableItems
-                    billingAddress
-                    shippingAddress
-                }
+    mutation createApplyCoupon ($couponCode: String!) {
+        createApplyCoupon(input: {couponCode: $couponCode}) {
+            applyCoupon {
+                ${CART_DETAILES_FRAGMENT}
             }
             }
+        }
     }
     }
 `;
 `;

+ 2 - 70
src/graphql/cart/mutations/CreateMergeCart.ts

@@ -1,5 +1,6 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import type { CreateMergeCartData } from "@/types/cart/type";
 import type { CreateMergeCartData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 
 export const CREATE_MERGE_CART: TypedDocumentNode<CreateMergeCartData> = gql`
 export const CREATE_MERGE_CART: TypedDocumentNode<CreateMergeCartData> = gql`
   mutation createMergeCart(
   mutation createMergeCart(
@@ -11,76 +12,7 @@ export const CREATE_MERGE_CART: TypedDocumentNode<CreateMergeCartData> = gql`
       }
       }
     ) {
     ) {
       mergeCart {
       mergeCart {
-        id
-        appliedTaxes
-        itemsQty
-        isGuest
-        itemsCount
-        items {
-          edges {
-            node {
-              id
-              cartId
-              productId
-              name
-              price
-              baseImage
-              sku
-              quantity
-              type
-              productUrlKey
-              canChangeQty
-              options
-            }
-          }
-        }
-        earnableRewardPoints
-        rewardPoints
-        rewardPointsUsed
-        rewardPointsAmount
-        baseRewardPointsAmount
-        formattedRewardPointsAmount
-        useShippingInsurance
-        shippingInsuranceEnabled
-        shippingInsuranceName
-        shippingInsuranceType
-        shippingInsuranceValue
-        shippingInsuranceAmount
-        baseShippingInsuranceAmount
-        formattedShippingInsuranceAmount
-        isVip
-        vipDiscountAmount
-        formattedVipDiscountAmount
-        giftcardNumber
-        subtotal
-        subTotalInclTax
-        discountAmount
-        giftcardAmount
-        vipPlusAmount
-        vipExpireDate
-        taxAmount
-        taxTotal
-        shippingAmount
-        shippingAmountInclTax
-        grandTotal
-        formattedSubtotal
-        formattedSubTotalInclTax
-        formattedGiftcardAmount
-        formattedDiscountAmount
-        formattedVipPlusAmount
-        formattedTaxAmount
-        formattedTaxTotal
-        formattedShippingAmount
-        formattedShippingAmountInclTax
-        formattedGrandTotal
-        couponCode
-        selectedShippingRate
-        selectedShippingRateTitle
-        paymentMethod
-        paymentMethodTitle
-        haveStockableItems
-        billingAddress
-        shippingAddress
+        ${CART_DETAILES_FRAGMENT}
       }
       }
     }
     }
   }
   }

+ 2 - 70
src/graphql/cart/mutations/CreateRemoveCoupon.ts

@@ -1,80 +1,12 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import type { CreateRemoveCouponData } from "@/types/cart/type";
 import type { CreateRemoveCouponData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 
 export const CREATE_REMOVE_COUPON: TypedDocumentNode<CreateRemoveCouponData> = gql`
 export const CREATE_REMOVE_COUPON: TypedDocumentNode<CreateRemoveCouponData> = gql`
     mutation createRemoveCoupon {
     mutation createRemoveCoupon {
         createRemoveCoupon(input:{}) {
         createRemoveCoupon(input:{}) {
                 removeCoupon {
                 removeCoupon {
-                    id
-                    appliedTaxes
-                    itemsQty
-                    isGuest
-                    itemsCount
-                    items {
-                    edges {
-                        node {
-                        id
-                        cartId
-                        productId
-                        name
-                        price
-                        baseImage
-                        sku
-                        quantity
-                        type
-                        productUrlKey
-                        canChangeQty
-                        options
-                        }
-                    }
-                    }
-                    earnableRewardPoints
-                    rewardPoints
-                    rewardPointsUsed
-                    rewardPointsAmount
-                    baseRewardPointsAmount
-                    formattedRewardPointsAmount
-                    useShippingInsurance
-                    shippingInsuranceEnabled
-                    shippingInsuranceName
-                    shippingInsuranceType
-                    shippingInsuranceValue
-                    shippingInsuranceAmount
-                    baseShippingInsuranceAmount
-                    formattedShippingInsuranceAmount
-                    isVip
-                    vipDiscountAmount
-                    formattedVipDiscountAmount
-                    giftcardNumber
-                    subtotal
-                    subTotalInclTax
-                    discountAmount
-                    giftcardAmount
-                    vipPlusAmount
-                    vipExpireDate
-                    taxAmount
-                    taxTotal
-                    shippingAmount
-                    shippingAmountInclTax
-                    grandTotal
-                    formattedSubtotal
-                    formattedSubTotalInclTax
-                    formattedGiftcardAmount
-                    formattedDiscountAmount
-                    formattedVipPlusAmount
-                    formattedTaxAmount
-                    formattedTaxTotal
-                    formattedShippingAmount
-                    formattedShippingAmountInclTax
-                    formattedGrandTotal
-                    couponCode
-                    selectedShippingRate
-                    selectedShippingRateTitle
-                    paymentMethod
-                    paymentMethodTitle
-                    haveStockableItems
-                    billingAddress
-                    shippingAddress
+                    ${CART_DETAILES_FRAGMENT}
                 }
                 }
             }
             }
     }
     }

+ 2 - 70
src/graphql/cart/mutations/GetCartItem.ts

@@ -1,80 +1,12 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import type { GetCartItemData } from "@/types/cart/type";
 import type { GetCartItemData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 
 export const GET_CART_ITEM: TypedDocumentNode<GetCartItemData> = gql`
 export const GET_CART_ITEM: TypedDocumentNode<GetCartItemData> = gql`
   mutation GetCartItem {
   mutation GetCartItem {
     createReadCart(input: {}) {
     createReadCart(input: {}) {
       readCart {
       readCart {
-        id
-        appliedTaxes
-        itemsQty
-        isGuest
-        itemsCount
-        items {
-          edges {
-            node {
-              id
-              cartId
-              productId
-              name
-              price
-              baseImage
-              sku
-              quantity
-              type
-              productUrlKey
-              canChangeQty
-              options
-            }
-          }
-        }
-        earnableRewardPoints
-        rewardPoints
-        rewardPointsUsed
-        rewardPointsAmount
-        baseRewardPointsAmount
-        formattedRewardPointsAmount
-        useShippingInsurance
-        shippingInsuranceEnabled
-        shippingInsuranceName
-        shippingInsuranceType
-        shippingInsuranceValue
-        shippingInsuranceAmount
-        baseShippingInsuranceAmount
-        formattedShippingInsuranceAmount
-        isVip
-        vipDiscountAmount
-        formattedVipDiscountAmount
-        giftcardNumber
-        subtotal
-        subTotalInclTax
-        discountAmount
-        giftcardAmount
-        vipPlusAmount
-        vipExpireDate
-        taxAmount
-        taxTotal
-        shippingAmount
-        shippingAmountInclTax
-        grandTotal
-        formattedSubtotal
-        formattedSubTotalInclTax
-        formattedGiftcardAmount
-        formattedDiscountAmount
-        formattedVipPlusAmount
-        formattedTaxAmount
-        formattedTaxTotal
-        formattedShippingAmount
-        formattedShippingAmountInclTax
-        formattedGrandTotal
-        couponCode
-        selectedShippingRate
-        selectedShippingRateTitle
-        paymentMethod
-        paymentMethodTitle
-        haveStockableItems
-        billingAddress
-        shippingAddress
+        ${CART_DETAILES_FRAGMENT}
       }
       }
     }
     }
   }
   }

+ 2 - 70
src/graphql/cart/mutations/RemoveCartItem.ts

@@ -1,80 +1,12 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import type { RemoveCartItemData } from "@/types/cart/type";
 import type { RemoveCartItemData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 
 export const REMOVE_CART_ITEM: TypedDocumentNode<RemoveCartItemData,{cartItemId: number}> = gql`
 export const REMOVE_CART_ITEM: TypedDocumentNode<RemoveCartItemData,{cartItemId: number}> = gql`
   mutation RemoveCartItem( $cartItemId: Int! ) {
   mutation RemoveCartItem( $cartItemId: Int! ) {
     createRemoveCartItem(input: { cartItemId: $cartItemId }) {
     createRemoveCartItem(input: { cartItemId: $cartItemId }) {
       removeCartItem {
       removeCartItem {
-        id
-        appliedTaxes
-        itemsQty
-        isGuest
-        itemsCount
-        items {
-          edges {
-            node {
-              id
-              cartId
-              productId
-              name
-              price
-              baseImage
-              sku
-              quantity
-              type
-              productUrlKey
-              canChangeQty
-              options
-            }
-          }
-        }
-        earnableRewardPoints
-        rewardPoints
-        rewardPointsUsed
-        rewardPointsAmount
-        baseRewardPointsAmount
-        formattedRewardPointsAmount
-        useShippingInsurance
-        shippingInsuranceEnabled
-        shippingInsuranceName
-        shippingInsuranceType
-        shippingInsuranceValue
-        shippingInsuranceAmount
-        baseShippingInsuranceAmount
-        formattedShippingInsuranceAmount
-        isVip
-        vipDiscountAmount
-        formattedVipDiscountAmount
-        giftcardNumber
-        subtotal
-        subTotalInclTax
-        discountAmount
-        giftcardAmount
-        vipPlusAmount
-        vipExpireDate
-        taxAmount
-        taxTotal
-        shippingAmount
-        shippingAmountInclTax
-        grandTotal
-        formattedSubtotal
-        formattedSubTotalInclTax
-        formattedGiftcardAmount
-        formattedDiscountAmount
-        formattedVipPlusAmount
-        formattedTaxAmount
-        formattedTaxTotal
-        formattedShippingAmount
-        formattedShippingAmountInclTax
-        formattedGrandTotal
-        couponCode
-        selectedShippingRate
-        selectedShippingRateTitle
-        paymentMethod
-        paymentMethodTitle
-        haveStockableItems
-        billingAddress
-        shippingAddress
+        ${CART_DETAILES_FRAGMENT}
       }
       }
     }
     }
   }
   }

+ 2 - 70
src/graphql/cart/mutations/UpdateCartItems.ts

@@ -1,5 +1,6 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import type { UpdateCartItemData } from "@/types/cart/type";
 import type { UpdateCartItemData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 
 export const UPDATE_CART_ITEM: TypedDocumentNode<UpdateCartItemData,{cartItemId: number;quantity: number;}> = gql
 export const UPDATE_CART_ITEM: TypedDocumentNode<UpdateCartItemData,{cartItemId: number;quantity: number;}> = gql
   `
   `
@@ -14,76 +15,7 @@ export const UPDATE_CART_ITEM: TypedDocumentNode<UpdateCartItemData,{cartItemId:
       }
       }
     ) {
     ) {
       updateCartItem {
       updateCartItem {
-        id
-        appliedTaxes
-        itemsQty
-        isGuest
-        itemsCount
-        items {
-          edges {
-            node {
-              id
-              cartId
-              productId
-              name
-              price
-              baseImage
-              sku
-              quantity
-              type
-              productUrlKey
-              canChangeQty
-              options
-            }
-          }
-        }
-        earnableRewardPoints
-        rewardPoints
-        rewardPointsUsed
-        rewardPointsAmount
-        baseRewardPointsAmount
-        formattedRewardPointsAmount
-        useShippingInsurance
-        shippingInsuranceEnabled
-        shippingInsuranceName
-        shippingInsuranceType
-        shippingInsuranceValue
-        shippingInsuranceAmount
-        baseShippingInsuranceAmount
-        formattedShippingInsuranceAmount
-        isVip
-        vipDiscountAmount
-        formattedVipDiscountAmount
-        giftcardNumber
-        subtotal
-        subTotalInclTax
-        discountAmount
-        giftcardAmount
-        vipPlusAmount
-        vipExpireDate
-        taxAmount
-        taxTotal
-        shippingAmount
-        shippingAmountInclTax
-        grandTotal
-        formattedSubtotal
-        formattedSubTotalInclTax
-        formattedGiftcardAmount
-        formattedDiscountAmount
-        formattedVipPlusAmount
-        formattedTaxAmount
-        formattedTaxTotal
-        formattedShippingAmount
-        formattedShippingAmountInclTax
-        formattedGrandTotal
-        couponCode
-        selectedShippingRate
-        selectedShippingRateTitle
-        paymentMethod
-        paymentMethodTitle
-        haveStockableItems
-        billingAddress
-        shippingAddress
+        ${CART_DETAILES_FRAGMENT}
       }
       }
     }
     }
   }
   }

+ 3 - 0
src/graphql/cart/mutations/readme.md

@@ -3,6 +3,9 @@ CreateMergeCart
 GetCartItem
 GetCartItem
 RemoveCartItem
 RemoveCartItem
 UpdateCartItems
 UpdateCartItems
+CreateApplyCoupon
+CreateRemoveCoupon
+CreateSaveCheckoutCart
 
 
 以上这几个接口返回数据的字段要一样
 以上这几个接口返回数据的字段要一样
 因为调完这几个接口后都需要更新redux中的购物车数据
 因为调完这几个接口后都需要更新redux中的购物车数据

+ 2 - 70
src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts

@@ -1,5 +1,6 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import type { SaveCheckoutCartData,SaveCheckoutCartVariables } from "@/types/checkout/type";
 import type { SaveCheckoutCartData,SaveCheckoutCartVariables } from "@/types/checkout/type";
+import { CART_DETAILES_FRAGMENT } from "@/graphql/cart/fragments/CartDetails";
 
 
 export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,SaveCheckoutCartVariables> = gql`
 export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,SaveCheckoutCartVariables> = gql`
     mutation CreateSaveCheckoutCart(
     mutation CreateSaveCheckoutCart(
@@ -23,76 +24,7 @@ export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,S
         }
         }
         ) {
         ) {
             saveCheckoutCart {
             saveCheckoutCart {
-                id
-                appliedTaxes
-                itemsQty
-                isGuest
-                itemsCount
-                items {
-                    edges {
-                        node {
-                            id
-                            cartId
-                            productId
-                            name
-                            price
-                            baseImage
-                            sku
-                            quantity
-                            type
-                            productUrlKey
-                            canChangeQty
-                            options
-                        }
-                    }
-                }
-                earnableRewardPoints
-                rewardPoints
-                rewardPointsUsed
-                rewardPointsAmount
-                baseRewardPointsAmount
-                formattedRewardPointsAmount
-                useShippingInsurance
-                shippingInsuranceEnabled
-                shippingInsuranceName
-                shippingInsuranceType
-                shippingInsuranceValue
-                shippingInsuranceAmount
-                baseShippingInsuranceAmount
-                formattedShippingInsuranceAmount
-                isVip
-                vipDiscountAmount
-                formattedVipDiscountAmount
-                giftcardNumber
-                subtotal
-                subTotalInclTax
-                discountAmount
-                giftcardAmount
-                vipPlusAmount
-                vipExpireDate
-                taxAmount
-                taxTotal
-                shippingAmount
-                shippingAmountInclTax
-                grandTotal
-                formattedSubtotal
-                formattedSubTotalInclTax
-                formattedGiftcardAmount
-                formattedDiscountAmount
-                formattedVipPlusAmount
-                formattedTaxAmount
-                formattedTaxTotal
-                formattedShippingAmount
-                formattedShippingAmountInclTax
-                formattedGrandTotal
-                couponCode
-                selectedShippingRate
-                selectedShippingRateTitle
-                paymentMethod
-                paymentMethodTitle
-                haveStockableItems
-                billingAddress
-                shippingAddress
+                ${CART_DETAILES_FRAGMENT}
             }
             }
         }
         }
     }
     }

+ 44 - 12
src/providers/ConfigProvider.tsx

@@ -1,10 +1,7 @@
 "use client";
 "use client";
 
 
 
 
-import {
-    createContext,
-} from "react";
-
+import { createContext, useState } from "react";
 import type {CurrencyItem} from "@/types/currency/type";
 import type {CurrencyItem} from "@/types/currency/type";
 import type { Country } from "@/types/checkout/type";
 import type { Country } from "@/types/checkout/type";
 
 
@@ -31,18 +28,21 @@ export interface StoreConfig {
 
 
 export interface AppConfig {
 export interface AppConfig {
 
 
+    countriesPromise: Promise<Country[]>;
+    currenciesPromise: Promise<CurrencyItem[]>;
+    store: StoreConfig;
 
 
-    currencies: CurrencyItem[];
-
-
-    countries: Country[];
-
+}
 
 
-    store: StoreConfig;
+export interface ConfigContextValue {
+  config: AppConfig;
 
 
+  setCurrentCurrency: (currency: string) => void;
+  setCurrentLocale: (locale: string) => void;
+  setCurrentChannel: (channel: string) => void;
 }
 }
 
 
-export const ConfigContext = createContext<AppConfig|null>(null);
+export const ConfigContext = createContext<ConfigContextValue|null>(null);
 
 
 export function ConfigProvider({
 export function ConfigProvider({
     config,
     config,
@@ -51,9 +51,41 @@ export function ConfigProvider({
     config:AppConfig;
     config:AppConfig;
     children:React.ReactNode;
     children:React.ReactNode;
 }){
 }){
+
+    const [store, setStore] = useState(config.store);
+
+    const value = {
+        config: {
+            ...config,
+            store,
+        },
+
+        setCurrentCurrency: (currency: string) => {
+            setStore((prev) => ({
+                ...prev,
+                currentCurrency: currency,
+            }));
+        },
+
+        setCurrentLocale: (locale: string) => {
+            setStore((prev) => ({
+                ...prev,
+                currentLocale: locale,
+            }));
+        },
+
+        setCurrentChannel: (channel: string) => {
+            setStore((prev) => ({
+                ...prev,
+                currentChannel: channel,
+            }));
+        },
+    };
+
+
     return (
     return (
         <ConfigContext.Provider
         <ConfigContext.Provider
-            value={config}
+            value={value}
         >
         >
             {children}
             {children}
         </ConfigContext.Provider>
         </ConfigContext.Provider>

+ 15 - 23
src/providers/ReduxProvider.tsx

@@ -1,31 +1,23 @@
 "use client";
 "use client";
 
 
 import { Provider } from "react-redux";
 import { Provider } from "react-redux";
-
-// import { store } from "@/store/store";
-
-import { useState } from 'react'
-
-import { makeStore, type PreloadedStateType } from '@/store/store'
+import { useState } from 'react';
+import { makeStore, type PreloadedStateType } from '@/store/store';
+import CartBootstrap from "@/components/cart/CartBootstrap";
 
 
 export const ReduxProvider = ({ 
 export const ReduxProvider = ({ 
-  children,
-  preloadedState
+    children,
+    preloadedState
 }: { 
 }: { 
-  children: React.ReactNode,
-  preloadedState: PreloadedStateType
+    children: React.ReactNode,
+    preloadedState?: PreloadedStateType
 }) => {
 }) => {
-  // return <Provider store={store}>{children}</Provider>;
-  
-  // const storeRef = useRef<AppStore>(undefined)
-  // if (!storeRef.current) {
-  //   // Create the store instance the first time this renders
-  //   storeRef.current = makeStore(preloadedState)
-  // }
-
-  // return <Provider store={storeRef.current}>{children}</Provider>
-
-  const [store] = useState(() => makeStore(preloadedState));
-
-  return <Provider store={store}>{children}</Provider>
+    const [store] = useState(() => makeStore(preloadedState));
+
+    return ( 
+        <Provider store={store}>
+            <CartBootstrap />
+            {children}
+        </Provider>
+    );
 }
 }

+ 37 - 4
src/store/slices/cart-slice.ts

@@ -2,16 +2,35 @@ import makeClient from "@/lib/ApolloClientBrowser";
 import { type PayloadAction, createSlice, createAsyncThunk } from "@reduxjs/toolkit";
 import { type PayloadAction, createSlice, createAsyncThunk } from "@reduxjs/toolkit";
 import type {CartDetail} from "@/types/cart/type";
 import type {CartDetail} from "@/types/cart/type";
 import { GET_CART_ITEM } from "@/graphql";
 import { GET_CART_ITEM } from "@/graphql";
-
 import { formatCartDetail } from "@/utils/cartDetailTools";
 import { formatCartDetail } from "@/utils/cartDetailTools";
+
+/*
+idle 尚未初始化
+loading 正在请求购物车
+succeeded 请求成功,即使购物车为空也算成功
+failed 请求失败
+*/
+export type CartStatus =
+  | "idle"
+  | "loading"
+  | "succeeded"
+  | "failed";
 export interface CartState {
 export interface CartState {
   cart: CartDetail | null;
   cart: CartDetail | null;
   loading: boolean;
   loading: boolean;
   error: string | null;
   error: string | null;
+  status: CartStatus;
 }
 }
 
 
 // 定义异步 action
 // 定义异步 action
-export const fetchCartDetail = createAsyncThunk(
+export const fetchCartDetail = createAsyncThunk<
+  ReturnType<typeof formatCartDetail> | null,
+  void,
+  {
+    state: { cartDetail: CartState };
+    rejectValue: string;
+  }
+>(
   'cartDetail/fetchCartDetail',
   'cartDetail/fetchCartDetail',
   async (_, { rejectWithValue }) => { //_ 占位,表示不需要参数
   async (_, { rejectWithValue }) => { //_ 占位,表示不需要参数
     try {
     try {
@@ -31,13 +50,24 @@ export const fetchCartDetail = createAsyncThunk(
       }
       }
       return rejectWithValue(errorMessage);
       return rejectWithValue(errorMessage);
     }
     }
-  }
+  },
+  {
+    condition: (_, { getState }) => {
+      const state = getState();
+      const fetchStatus = state.cartDetail.status;
+      if (fetchStatus === 'succeeded' || fetchStatus === 'loading') {
+        // Already fetched or in progress, don't need to re-fetch
+        return false
+      }
+    },
+  },
 );
 );
 
 
 
 
 const initialState: CartState = {
 const initialState: CartState = {
   cart: null,
   cart: null,
   loading: false,
   loading: false,
+  status: "idle",
   error: null,
   error: null,
 };
 };
 
 
@@ -63,16 +93,19 @@ const cartSlice = createSlice({
   extraReducers: (builder) => {
   extraReducers: (builder) => {
     builder
     builder
       .addCase(fetchCartDetail.pending, (state) => {
       .addCase(fetchCartDetail.pending, (state) => {
+        state.status = "loading";
         state.loading = true;
         state.loading = true;
         state.error = null;
         state.error = null;
       })
       })
       .addCase(fetchCartDetail.fulfilled, (state, action) => {
       .addCase(fetchCartDetail.fulfilled, (state, action) => {
+        state.status = "succeeded";
         state.loading = false;
         state.loading = false;
         state.cart = action.payload;
         state.cart = action.payload;
       })
       })
       .addCase(fetchCartDetail.rejected, (state, action) => {
       .addCase(fetchCartDetail.rejected, (state, action) => {
+        state.status = "failed";
         state.loading = false;
         state.loading = false;
-        state.error = action.payload as string;
+        state.error = (action.payload as string | undefined) ?? action.error.message ?? "Get cart detail failed!";
       });
       });
   },
   },
 });
 });

+ 2 - 13
src/store/store.ts

@@ -3,27 +3,16 @@ import { configureStore } from "@reduxjs/toolkit";
 import cartSlice, {type CartState} from "./slices/cart-slice";
 import cartSlice, {type CartState} from "./slices/cart-slice";
 import addToCartDialogReducer from "./slices/addToCartDialogSlice";
 import addToCartDialogReducer from "./slices/addToCartDialogSlice";
 
 
-// export const store = configureStore({
-//   reducer: {
-//     cartDetail: cartSlice,
-//     user: userSlice,
-//     addToCartDialog: addToCartDialogReducer,
-//     currencyList: currencySlice
-//   },
-// });
-
-// export type RootState = ReturnType<typeof store.getState>;
-// export type AppDispatch = typeof store.dispatch;
 export interface PreloadedStateType { 
 export interface PreloadedStateType { 
   cartDetail: CartState; 
   cartDetail: CartState; 
 }
 }
-export const makeStore = (preloadedState: PreloadedStateType) => {
+export const makeStore = (preloadedState?: PreloadedStateType) => {
     return configureStore({
     return configureStore({
       reducer: {
       reducer: {
         cartDetail: cartSlice,
         cartDetail: cartSlice,
         addToCartDialog: addToCartDialogReducer,
         addToCartDialog: addToCartDialogReducer,
       },
       },
-      preloadedState
+      ...(preloadedState ? { preloadedState } : {}),
     });
     });
 }
 }
 // Infer the type of makeStore
 // Infer the type of makeStore

+ 10 - 1
src/types/cart/type.ts

@@ -22,7 +22,12 @@ export interface CartItem {
   canChangeQty: boolean;
   canChangeQty: boolean;
   options: {
   options: {
     edges: Array<{node:CartItemOptions}>
     edges: Array<{node:CartItemOptions}>
-  }
+  };
+  total: number;
+  isFreeGift: boolean;
+  discountAmount: number;
+  formattedPrice: string;
+  formattedTotal: string;
 }
 }
 
 
 // GraphQL Edge wrapper
 // GraphQL Edge wrapper
@@ -54,6 +59,10 @@ export interface CartDetail {
   itemsQty: number; //Total quantity of all items
   itemsQty: number; //Total quantity of all items
   isGuest: boolean;
   isGuest: boolean;
   itemsCount: number;
   itemsCount: number;
+  // 赠品相关
+  hasFreeGift: boolean; // 购物车里现在有赠品行(行内 items[].isFreeGift 也是 true) 
+  freeGiftAvailable: number; // 根据这个字段判断是否需要获取赠品列表,让用户选赠品
+  freeGiftAvailableQty: number; // 剩余可选赠品的件数
   items: CartItemsConnection;
   items: CartItemsConnection;
   isVip: boolean;
   isVip: boolean;
   vipDiscountAmount: number;
   vipDiscountAmount: number;

+ 31 - 0
src/types/freegift/type.ts

@@ -0,0 +1,31 @@
+
+export interface FreeGiftItem {
+    sku: string;
+    product_id: number;
+    name: string;
+    url: string;
+    image: string;
+    price: string;
+    allowed_qty: number;
+    reserved_qty: number;
+    remaining_qty: number;
+    cart_item_id: any;
+}
+
+export interface FreeGiftGroupItem {
+    rule_id: number;
+    rule_name: string;
+    type: number;
+    auto_add: boolean;
+    allowed_qty: number;
+    reserved_qty: number;
+    remaining_qty: number;
+    gifts: FreeGiftItem[];
+}
+
+export interface FreeGiftCandidatesResponse{
+    data: {
+        groups: FreeGiftGroupItem[];
+        banners: Array<any>;
+    }
+}

+ 25 - 0
src/types/theme/theme-customization.ts

@@ -18,6 +18,31 @@ export interface GetThemeCustomization {
   }
   }
 }
 }
 
 
+// restful /api/shop/theme-customizations
+export type RestfulGetThemeCustomizations = Array<{
+  id: number;
+  themeCode: string;
+  type: string;
+  name: string;
+  sortOrder: number;
+  status: number;
+  showstyle: string;
+  createdAt: string;
+  updatedAt: string;
+  translation: {
+      id: number;
+      themeCustomizationId: number;
+      locale: string;
+      options: string;
+  },
+  translations: Array<{
+      id: number;
+      themeCustomizationId: number;
+      locale: string;
+      options: string;
+  }>;
+    
+}>;
 
 
 // Theme Customization Options Types
 // Theme Customization Options Types
 
 

+ 33 - 15
src/utils/bagisto/index.ts

@@ -1,6 +1,6 @@
 import { cookies } from "next/headers";
 import { cookies } from "next/headers";
 import { auth } from "@/utils/auth/auth-helper";
 import { auth } from "@/utils/auth/auth-helper";
-import { print, type DocumentNode } from "graphql";
+import { print, parse, getOperationAST, type DocumentNode } from "graphql";
 import { 
 import { 
   GRAPHQL_URL, 
   GRAPHQL_URL, 
   REST_API_URL,
   REST_API_URL,
@@ -76,16 +76,27 @@ export async function getAuthorizationToken(): Promise<{token: string | null; is
   };
   };
 }
 }
 
 
+function getGraphqlOperationName(query: string | DocumentNode) {
+    const documentAST = typeof query === "string" ? parse(query) : query;
+    // 不传入 operationName 时,如果文档中只有一个操作,会自动返回该操作
+    const operation = getOperationAST(documentAST);
+    const operationName = operation && operation.name
+      ? operation.name.value
+      : undefined;
+    
+    return operationName;
+}
+
 // rest api fetch
 // rest api fetch
 export async function restApiFetch<T>({
 export async function restApiFetch<T>({
   api,
   api,
   method,
   method,
-  cache = "force-cache",
+  cache = "no-store",
   headers,
   headers,
   tags,
   tags,
   variables,
   variables,
   isRoute = true,
   isRoute = true,
-  revalidate = 60,
+  revalidate = 0,
   takeAuthorization = true, // 是否携带token,默认携带
   takeAuthorization = true, // 是否携带token,默认携带
 }: {
 }: {
   api: string;
   api: string;
@@ -119,7 +130,10 @@ export async function restApiFetch<T>({
         Object.assign(baseHeaders, headers);
         Object.assign(baseHeaders, headers);
       }
       }
     }
     }
-    console.log('restApiFetch --- baseHeaders:', baseHeaders)
+    console.log('restApiFetch --- baseHeaders:', {
+      ...baseHeaders, 
+      apiUrl
+    })
     const param: RequestInit = {
     const param: RequestInit = {
       method: method,
       method: method,
       headers: baseHeaders,
       headers: baseHeaders,
@@ -144,7 +158,6 @@ export async function restApiFetch<T>({
     const result = await fetch(url, param);
     const result = await fetch(url, param);
 
 
     const body = await result.json();
     const body = await result.json();
-    console.log('restApiFetch --- body:', body)
     if(!isRoute) {
     if(!isRoute) {
       if(result.status === 401) {
       if(result.status === 401) {
         const err = new Error('Authorization Bearer token is required. Please Login.');//new Error(body.message || 'Authorization Bearer token is required');
         const err = new Error('Authorization Bearer token is required. Please Login.');//new Error(body.message || 'Authorization Bearer token is required');
@@ -191,6 +204,8 @@ export async function serverGraphqlFetch<
   try {
   try {
     const queryString = typeof query === "string" ? query : print(query);
     const queryString = typeof query === "string" ? query : print(query);
 
 
+    const operationName = getGraphqlOperationName(query);
+
     if (headers) {
     if (headers) {
       if (headers instanceof Headers) {
       if (headers instanceof Headers) {
         headers.forEach((value, key) => (baseHeaders[key] = value));
         headers.forEach((value, key) => (baseHeaders[key] = value));
@@ -198,7 +213,10 @@ export async function serverGraphqlFetch<
         Object.assign(baseHeaders, headers);
         Object.assign(baseHeaders, headers);
       }
       }
     }
     }
-    console.log('serverGraphqlFetch --- baseHeaders:', baseHeaders);
+    console.log('serverGraphqlFetch --- baseHeaders:', {
+      ...baseHeaders,
+      operationName
+    });
     const result = await fetch(GRAPHQL_URL, {
     const result = await fetch(GRAPHQL_URL, {
       method: "POST",
       method: "POST",
       headers: baseHeaders,
       headers: baseHeaders,
@@ -266,8 +284,9 @@ export async function bagistoFetch<T>({
 
 
     }
     }
   try {
   try {
-    const queryString =
-      typeof query === "string" ? query : print(query);
+    const queryString = typeof query === "string" ? query : print(query);
+
+    const operationName = getGraphqlOperationName(query);
 
 
     if (headers) {
     if (headers) {
       if (headers instanceof Headers) {
       if (headers instanceof Headers) {
@@ -276,12 +295,12 @@ export async function bagistoFetch<T>({
         Object.assign(baseHeaders, headers);
         Object.assign(baseHeaders, headers);
       }
       }
     }
     }
-    // let cc = await cookies();
-    // console.log('bagistoFetch --- url:', GRAPHQL_URL,isCookies);
-    // console.log('bagistoFetch --- queryString:', queryString);
-    console.log('bagistoFetch --- baseHeaders:', baseHeaders);
-    // console.log('bagistoFetch --- cookies:', cc.getAll());
-    // console.log('bagistoFetch --- variables:', variables);
+
+    console.log('bagistoFetch --- baseHeaders:', {
+      ...baseHeaders,
+      operationName
+    });
+
     const result = await fetch(GRAPHQL_URL, {
     const result = await fetch(GRAPHQL_URL, {
       method: "POST",
       method: "POST",
       headers: baseHeaders,
       headers: baseHeaders,
@@ -297,7 +316,6 @@ export async function bagistoFetch<T>({
     });
     });
 
 
     const body = await result.json();
     const body = await result.json();
-    console.log('bagistoFetch --- body:',body);
     return { status: result.status, body };
     return { status: result.status, body };
   } catch (e) {
   } catch (e) {
     throw e;
     throw e;

+ 3 - 0
src/utils/cartDetailTools.ts

@@ -8,6 +8,9 @@ export function formatCartDetail(cart:CartDetail): CartDetail {
         isGuest: cart.isGuest,
         isGuest: cart.isGuest,
         itemsCount: cart.itemsCount,
         itemsCount: cart.itemsCount,
         items: cart.items,
         items: cart.items,
+        hasFreeGift: cart.hasFreeGift,
+        freeGiftAvailable: cart.freeGiftAvailable,
+        freeGiftAvailableQty: cart.freeGiftAvailableQty,
 
 
         earnableRewardPoints: cart.earnableRewardPoints,
         earnableRewardPoints: cart.earnableRewardPoints,
         rewardPoints: cart.rewardPoints,
         rewardPoints: cart.rewardPoints,

+ 31 - 7
src/utils/constants.ts

@@ -179,13 +179,37 @@ export const IS_VALID_PHONECODE = /^(\+\d+\s|\+\d+\-\d+\s|\+\d+\-\d+and\d+\-\d+\
 export const IS_VALID_FULL_PHONE = /^(\+\d+\s|\+\d+\-\d+\s|\+\d+\-\d+and\d+\-\d+\s)\d{9,12}$/;
 export const IS_VALID_FULL_PHONE = /^(\+\d+\s|\+\d+\-\d+\s|\+\d+\-\d+and\d+\-\d+\s)\d{9,12}$/;
 
 
 
 
-export const currencyCountryMap: Record<string, string> = {
-    "USD": "US",
-    "GBP": "GB",
-    "CAD": "CA",
-    "EUR": "",
-    "AUD": "AU",
-    "ZAR": "ZA"
+export const currencyCountryMap: Record<string, {icon: string;symbol: string; countryCode: string;}> = {
+    "USD": {
+      icon: "/image/flags/1x1/us.svg",
+      symbol: '$',
+      countryCode: 'US'
+    },
+    "GBP": {
+      icon: "/image/flags/1x1/gb.svg",
+      symbol: '£',
+      countryCode: 'GB'
+    },
+    "CAD": {
+      icon: "/image/flags/1x1/ca.svg",
+      symbol: 'CA$',
+      countryCode: 'CA'
+    },
+    "EUR": {
+      icon: "/image/flags/1x1/eu.svg",
+      symbol: '€',
+      countryCode: 'EU'
+    },
+    "AUD": {
+      icon: "/image/flags/1x1/au.svg",
+      symbol: 'A$',
+      countryCode: 'AU'
+    },
+    "ZAR": {
+      icon: "/image/flags/1x1/za.svg",
+      symbol: 'R',
+      countryCode: 'ZA'
+    }
 };
 };
 
 
 export const paymentMethodImageMap:Record<string, string> = {
 export const paymentMethodImageMap:Record<string, string> = {

+ 5 - 0
src/utils/helper.ts

@@ -1,6 +1,7 @@
 import { ReadonlyURLSearchParams } from "next/navigation";
 import { ReadonlyURLSearchParams } from "next/navigation";
 import { type Metadata } from "next";
 import { type Metadata } from "next";
 import type { FilterDataTypes } from "@/types/types";
 import type { FilterDataTypes } from "@/types/types";
+import type {CurrencyItem} from "@/types/currency/type";
 import { isArray } from "./type-guards";
 import { isArray } from "./type-guards";
 import { env } from '@/env';
 import { env } from '@/env';
 
 
@@ -10,6 +11,10 @@ import { env } from '@/env';
 const __br = [100,115,118,45,50,48,50,53,46,48,52,46,49,57,45,55,101,50,57];
 const __br = [100,115,118,45,50,48,50,53,46,48,52,46,49,57,45,55,101,50,57];
 export const __buildRevision = String.fromCharCode.apply(null, __br);
 export const __buildRevision = String.fromCharCode.apply(null, __br);
 
 
+export const getCurrencyItem = (code: string, currencies: CurrencyItem[]) => {
+    const currencyItem = currencies.find(currency => currency.code === code);
+    return currencyItem;
+}
 export const createUrl = (
 export const createUrl = (
   pathname: string,
   pathname: string,
   params: URLSearchParams | ReadonlyURLSearchParams,
   params: URLSearchParams | ReadonlyURLSearchParams,

+ 32 - 15
src/utils/hooks/useConfig.ts

@@ -1,6 +1,7 @@
-import { useContext } from "react";
+import { useContext, use } from "react";
 import { ConfigContext } from "@/providers/ConfigProvider";
 import { ConfigContext } from "@/providers/ConfigProvider";
-export function useConfig(){
+
+export function useStore(){
     const context = useContext(ConfigContext);
     const context = useContext(ConfigContext);
 
 
     if(!context){
     if(!context){
@@ -10,21 +11,37 @@ export function useConfig(){
         );
         );
 
 
     }
     }
-    const {countries,currencies,store} = context;
-    const getCurrentCurrencyItem = () => {
-        const currentCurrencyItem = currencies.find(currency => currency.code === store.currentCurrency);
-        if(!currentCurrencyItem) {
-            throw new Error(
-                "Can't find current currency item"
-            );
-        }
-        return currentCurrencyItem;
-    }
+    const {store} = context.config;
+   
     
     
     return {
     return {
-        countries,
-        currencies,
         store,
         store,
-        getCurrentCurrencyItem
+        setCurrentCurrency: context.setCurrentCurrency
     };
     };
+    
+}
+
+
+export function useCountries() {
+  const context = useContext(ConfigContext);
+
+  if (!context) {
+    throw new Error(
+      "useCountries must be used within ConfigProvider",
+    );
+  }
+
+  return use(context.config.countriesPromise);
+}
+
+export function useCurrencyList() {
+  const context = useContext(ConfigContext);
+
+  if (!context) {
+    throw new Error(
+      "useCurrencyList must be used within ConfigProvider",
+    );
+  }
+
+  return use(context.config.currenciesPromise);
 }
 }