fogwind 1 deň pred
rodič
commit
e756f8f030
50 zmenil súbory, kde vykonal 960 pridanie a 969 odobranie
  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. 结账页默认设置运输方式问题(导致结算联动风暴)
 36. 学习set-state-in-effect,然后检查代码,看是否可以优化(特别是CommonModal组件,看能不能不使用useEffect实现)
 37. paypal provider 支付参数根据环境变量配置 --- 已完成
-38. CartAndUserActions 组件在支付结果页多次挂载问题
+38. CartAndUserActions 组件在支付结果页多次挂载问题 -- 组件已删除
 39. 服务端去掉apollo client。apollo client 只在客户端使用; apollo 错误处理(第9条);
 40. Authorization 写入header优化;-- 已完成
     ---- https://chatgpt.com/share/6a6acb5d-e184-83ea-acfc-839fe0d34caf
@@ -358,8 +358,15 @@ useEffect 只会在客户端执行,具体是在浏览器绘制后执行,服
 48. 游客token的创建优化(想在服务端创建写入token)--- 已完成
 49. 结账页地址模块未登录用户输入邮箱后判断是否注册过,如果注册过让用户登录;--- 已完成
 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

+ 6 - 3
src/actions/addProductToCartAction.ts

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

+ 3 - 3
src/actions/switchCurrencyAction.ts

@@ -1,7 +1,7 @@
 "use server";
 
 import { cookies } from "next/headers";
-import { revalidatePath } from "next/cache";
+// import { revalidatePath } from "next/cache";
 import {CURRENT_CURRENCY} from "@/utils/constants";
 import {currencyCookieOptions} from "@/utils/constants/server";
 export async function switchCurrencyAction(currency: string) {
@@ -9,6 +9,6 @@ export async function switchCurrencyAction(currency: string) {
     const cookieStore = await cookies()
 
     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_FULL_PHONE 
 } from "@utils/constants";
-import { useConfig } from "@utils/hooks/useConfig";
+import { useCountries } from "@utils/hooks/useConfig";
 import type { CustomerAddressItem } from "@/types/customer/type";
 import InputText from "@/components/theme/ui/InputText";
 import Select from "@/components/theme/ui/Select";
@@ -40,7 +40,7 @@ export default function BillingAddressCheckout ({
     onShowFormFieldChange: (e: boolean) => void;
     customerAddressList: CustomerAddressItem[];
 }) {
-    const {countries} = useConfig();
+    const countries = useCountries();
     const { 
         getValues,
         setValue,

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

@@ -15,7 +15,7 @@ import {
 import { GET_COUNTRY_STATES } from "@/graphql";
 import { EMAIL_REGEX, IS_VALID_INPUT, IS_VALID_FULL_PHONE, IS_VALID_PHONECODE } from "@utils/constants";
 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 Select from "@/components/theme/ui/Select";
 import PhoneNumberInput from "@/components/theme/ui/PhoneNumberInput/PhoneNumberInput";
@@ -40,7 +40,7 @@ export default function ShippingAddressCheckout({
     onEmailChange: (value: string) => void;
 }) {
 
-    const {countries} = useConfig();
+    const countries = useCountries();
     const { 
         getValues,
         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 { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
 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";
 export default function CheckoutPlaceOrder({
     paymentMethod,
@@ -21,7 +21,7 @@ export default function CheckoutPlaceOrder({
     validateCheckout: () => Promise<boolean>;
 }) {
     const router = useRouter();
-    const {store} = useConfig();
+    const {store} = useStore();
     const { showToast } = useCustomToast();
     const { createPaymentCallback } = usePlaceOrder();
     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 { useAddProduct } from "@utils/hooks/useAddToCart";
 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 { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import CommonModal from "@/components/theme/ui/CommonModal";
-
+import { Price } from "@components/theme/ui/Price";
 
 // 修改数量接口 数量不能为0
 // 删除接口
@@ -30,16 +30,22 @@ export default function CheckoutProducts({
     const { showToast } = useCustomToast();
     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 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 = () => {
         setShowProductsModal(false);
     };
@@ -118,7 +124,7 @@ export default function CheckoutProducts({
 
             <div className="relative mt-3 w-full">
                 <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) : {};
                         return (
                             <div className="relative w-22.5 h-30 flex-none" key={product.id}>
@@ -153,7 +159,7 @@ export default function CheckoutProducts({
             }
             body={
                 <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 productOptions = product.options.edges.map((edge) => edge.node);
                         return (
@@ -177,24 +183,65 @@ export default function CheckoutProducts({
                                     </div>
                                     <div className="flex items-center justify-between">
                                         <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> */}
                                         </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>
                             
                         )
                     })}
+                    
+
+                    {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>
             }

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

@@ -3,12 +3,12 @@
 import { useState } from "react";
 import Image from "next/image";
 import { useRouter } from 'next/navigation';
-import {useConfig} from "@/utils/hooks/useConfig";
 import type { OrderDetails } from "@/types/customer/type";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import ProductsModalPendingOrder from "./ProductsModalPendingOrder";
 import dynamic from "next/dynamic";
 import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
+import { Price } from "@components/theme/ui/Price";
 
 const CountDown = dynamic(() => import("@/components/common/CountDown"), {
   ssr: false,
@@ -20,13 +20,7 @@ export default function ContinueToPayOrderInfo({
 }) { 
     
     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);
 
@@ -166,7 +160,12 @@ export default function ContinueToPayOrderInfo({
                 <h3 className="text-ly-24 font-medium">Promotion Details</h3>
                 <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">{orderSymbol}{orderDetail.subTotal}</p>
+                    <p className="text-ly-12 leading-ly-20">
+                        <Price
+                            amount={orderDetail.subTotal}
+                            currencyCode={orderDetail.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
                 {orderDetail.rewardPointsEarned > 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
@@ -177,49 +176,89 @@ export default function ContinueToPayOrderInfo({
                 {orderDetail.shippingAmount !== 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Shipping</p>
-                        <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.shippingAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={orderDetail.shippingAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {Number(orderDetail?.shippingInsuranceAmount) > 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Shipping</p>
-                        <p className="text-ly-12 leading-ly-20">{orderSymbol}{Number(orderDetail.shippingInsuranceAmount)}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={orderDetail.shippingInsuranceAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {orderDetail.taxAmount !== 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Tax</p>
-                        <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>
                 }
                 {(orderDetail.vipPlusAmount !== '0.00' && orderDetail.vipPlusAmount !== null)  &&
                     <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">{orderSymbol}{orderDetail.vipPlusAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={orderDetail.vipPlusAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {orderDetail.discountAmount !== 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">-{orderSymbol}{orderDetail.discountAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={orderDetail.discountAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {(orderDetail.giftcardAmount !== '0.00' && orderDetail.giftcardAmount !== null) &&
                     <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">-{orderSymbol}{orderDetail.giftcardAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={orderDetail.giftcardAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {(orderDetail.vipDiscountAmount !== '0.00' && orderDetail.vipDiscountAmount !== null) &&
                     <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">-{orderSymbol}{orderDetail.vipDiscountAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={orderDetail.vipDiscountAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {orderDetail.rewardPointsAmount > 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">You Redeem ({orderDetail.rewardPointsUsed} Reward Points)</p>
-                        <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.rewardPointsAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={orderDetail.rewardPointsAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
 
@@ -229,7 +268,12 @@ export default function ContinueToPayOrderInfo({
                 </div> */}
                 <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 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>
 
@@ -238,7 +282,7 @@ export default function ContinueToPayOrderInfo({
             <ProductsModalPendingOrder 
                 isOpen={productsModalOpen}
                 products={productItems}
-                currencySymbol={orderSymbol}
+                currencyCode={orderDetail.orderCurrencyCode}
                 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 {FetchGraphqlResult} from "@/types/graphqlFetch/type";
 import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
+import {currencyCountryMap} from "@/utils/constants";
 import PaypalButton from "../PaymentButton/PaypalButton";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import PaypalApplepayButton from "../PaymentButton/PaypalApplepayButton";
 import KlarnaButton from "../PaymentButton/KlarnaButton";
 import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
-import { useConfig } from '@utils/hooks/useConfig';
 import {usePaymentSDKContext} from "@/providers/PaymentSDKProvider";
 
+
 /**@todo 优化目标:不使用 react-hook-form */
 export function PaymentMethodContinueTpPay({
     paymentMethodsDataPromise,
@@ -45,11 +46,9 @@ export function PaymentMethodContinueTpPay({
     const { paymentRepay, createPaymentCallback } = usePlaceOrder();
 
     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();
@@ -427,7 +426,7 @@ export function PaymentMethodContinueTpPay({
         return getPaymentDescription({
             payment: payment,
             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 CommonModal from "@/components/theme/ui/CommonModal";
 import type {ProductsInOrderDetails, ProductItemAdditional} from "@/types/customer/type";
+import { Price } from "@components/theme/ui/Price";
 
 
 // 根据products 计算总价和总个数
@@ -50,12 +51,12 @@ function getProductAdditionalInfo(productItem: ProductItemAdditional) {
 export default function ProductsModalPendingOrder({
     isOpen,
     products,
-    currencySymbol,
+    currencyCode,
     onClose,
 }: {
     isOpen: boolean;
     products: ProductsInOrderDetails[],
-    currencySymbol: string;
+    currencyCode: string;
     onClose: () => void;
 }) {
 
@@ -80,7 +81,10 @@ export default function ProductsModalPendingOrder({
             
             <div className="mt-6 flex justify-between items-center">
                 <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>
       }
@@ -106,9 +110,11 @@ export default function ProductsModalPendingOrder({
                                 </p>
                             </div>
                             <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>
                             </div>
                         </div>

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

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

+ 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 { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 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 {getPaypalApplePayConfig, type PaypalApplePaySession} from "@/lib/Applepay/applepay";
 
@@ -36,10 +36,10 @@ export default function PaypalApplepayButton({
 }) { 
     const router = useRouter();
     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 gatewayOrderIdRef = useRef("");

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

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

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

@@ -3,8 +3,9 @@
 import Image from "next/image";
 import { type Ref, useEffect, useImperativeHandle } from "react";
 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 {currencyCountryMap} from "@/utils/constants";
 import type { CheckoutPaymentMethod } from "@/types/checkout/type";
 import AirwallexCardInput from "./PaymentMethodAdditional/AirwallexCardInput";
 import {useAirwallexCardState} from "@/lib/Airwallex/useAirwallexCard";
@@ -32,9 +33,9 @@ export function PaymentMethodCheckout({
     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();
   

+ 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 { useAddProduct } from "@utils/hooks/useAddToCart";
 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 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 { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 // import {BonusBuy} from "./BonusBuy";
@@ -21,6 +21,7 @@ import CheckoutVipPlus from "../../_components/VipPlus/CheckoutVipPlus";
 import CheckoutCoupon from "../../_components/CheckoutCoupon/CheckoutCoupon";
 import CommonModal from "@/components/theme/ui/CommonModal";
 import Portal from "@/components/common/portal/Portal";
+import { Price } from "@components/theme/ui/Price";
 
 function computeGrandTotal(cartData: CartDetail) {
 
@@ -53,9 +54,8 @@ export function CartWrapper({
     const {deleteProductFromCart, editProductQtyFromCart} = useAddProduct();
 
     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 bottomRef = useRef<HTMLDivElement>(null);
@@ -64,8 +64,15 @@ export function CartWrapper({
 
     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 不包含运费和丢件险和积分抵扣金额
@@ -211,7 +218,7 @@ export function CartWrapper({
     return (<>
         <section className="w-full box-border pb-38">
             {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">
                         Don't lose your cart! Sync it to Your Email. 
                         <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 productOptions = product.options.edges.map((edge) => edge.node);
                     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">
                                 <Image className="object-cover" fill
                                     src={baseImage.medium_image_url || ''} 
@@ -244,48 +252,66 @@ export function CartWrapper({
                                         </button>
                                     </div>
                                     <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 className="flex items-center justify-between">
                                     <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> */}
                                     </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 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>
+            {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">
@@ -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>
                         </span>
                         <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>
+                        <Price className="ml-2 text-ly-24 font-bold"
+                            amount={grandTotal}
+                            currencyCode={currentCurrencyCode}
+                        />
                     </button>
                 </div>
                 <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 { useRouter } from 'next/navigation';
 import { useCustomToast } from "@/utils/hooks/useToast";
-import {useConfig} from "@/utils/hooks/useConfig";
 import {getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import type { OrderDetails, ProductsInOrderDetails } from "@/types/customer/type";
 import CommonModal from "@/components/theme/ui/CommonModal";
+import { Price } from "@components/theme/ui/Price";
 const CountDown = dynamic(() => import("@/components/common/CountDown"), {
   ssr: false,
 });
@@ -23,11 +23,6 @@ export function UnpaidOrderSection({
     const { cancelOrder } = usePlaceOrder();
     const router = useRouter();
     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 [showProductsModal, setShowProductsModal] = useState(false);
@@ -114,7 +109,14 @@ export function UnpaidOrderSection({
             </div>
 
             <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"
                     onClick={toContinuePay}
                 >
@@ -145,7 +147,10 @@ export function UnpaidOrderSection({
                     <div className="flex w-full justify-between items-center">
                         <span className="text-ly-14 font-medium">Total:</span>
                         <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>
                         </button>
                     </div>
@@ -182,7 +187,10 @@ export function UnpaidOrderSection({
                                     </div>
                                     <div className="flex items-center justify-between">
                                         <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> */}
                                         </div>
 
@@ -217,7 +225,12 @@ export function UnpaidOrderSection({
 
                         <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">{orderSymbol}{orderDetail.subTotal}</p>
+                            <p className="text-ly-12 leading-ly-20">
+                                <Price
+                                    amount={String(orderDetail.subTotal)}
+                                    currencyCode={orderDetail.orderCurrencyCode}
+                                />
+                            </p>
                         </div>
                         {orderDetail.rewardPointsEarned > 0 &&
                             <div className="flex justify-between mt-3 first:mt-0">
@@ -228,55 +241,100 @@ export function UnpaidOrderSection({
                         {orderDetail.shippingAmount !== 0 &&
                             <div className="flex justify-between mt-3 first:mt-0">
                                 <p className="text-ly-12 leading-ly-20">Shipping</p>
-                                <p className="text-ly-12 leading-ly-20">{orderSymbol}{orderDetail.shippingAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    <Price
+                                        amount={String(orderDetail.shippingAmount)}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                         }
                         {Number(orderDetail?.shippingInsuranceAmount) > 0 &&
                             <div className="flex justify-between mt-3 first:mt-0">
                                 <p className="text-ly-12 leading-ly-20">Shipping</p>
-                                <p className="text-ly-12 leading-ly-20">{orderSymbol}{Number(orderDetail.shippingInsuranceAmount)}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    <Price
+                                        amount={orderDetail.shippingInsuranceAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                         }
                         {orderDetail.taxAmount !== 0 &&
                             <div className="flex justify-between mt-3 first:mt-0">
                                 <p className="text-ly-12 leading-ly-20">Tax</p>
-                                <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>
                         }
                         {(orderDetail.vipPlusAmount !== '0.00' && orderDetail.vipPlusAmount !== null)  &&
                             <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">{orderSymbol}{orderDetail.vipPlusAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    <Price
+                                        amount={orderDetail.vipPlusAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                         }
                         {orderDetail.discountAmount !== 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">-{orderSymbol}{orderDetail.discountAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    -<Price
+                                        amount={orderDetail.discountAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                         }
                         {(orderDetail.giftcardAmount !== '0.00' && orderDetail.giftcardAmount !== null) &&
                             <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">-{orderSymbol}{orderDetail.giftcardAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    -<Price
+                                        amount={orderDetail.giftcardAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                         }
                         {(orderDetail.vipDiscountAmount !== '0.00' && orderDetail.vipDiscountAmount !== null) &&
                             <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">-{orderSymbol}{orderDetail.vipDiscountAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    -<Price
+                                        amount={orderDetail.vipDiscountAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                         }
                         {orderDetail.rewardPointsAmount > 0 &&
                             <div className="flex justify-between mt-3 first:mt-0">
                                 <p className="text-ly-12 leading-ly-20">You Redeem ({orderDetail.rewardPointsUsed} Reward Points)</p>
-                                <p className="text-ly-12 leading-ly-20">-{orderSymbol}{orderDetail.rewardPointsAmount}</p>
+                                <p className="text-ly-12 leading-ly-20">
+                                    -<Price
+                                        amount={orderDetail.rewardPointsAmount}
+                                        currencyCode={orderDetail.orderCurrencyCode}
+                                    />
+                                </p>
                             </div>
                         }
 
                         <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 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>
 

+ 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 { OrderDetailsData } from "@/types/customer/type";
 import {deleteGuestCookieAction} from "@/actions";
-import { useConfig } from "@utils/hooks/useConfig";
 import {getAddressFromOrderDetailAddressList,getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import Faqs from "./Faqs";
+import { Price } from "@components/theme/ui/Price";
 
 /**
  * 订单详情里的金额的货币符号不会根据用户选择的货币改变 ,而是固定为下单时使用的货币
@@ -23,22 +23,22 @@ export default function OrderDetailWrapper({
     isGuest: boolean;
 }) { 
 
-    const {currencies} = useConfig();
+
     const promiseRes = use(orderDetailPromise);
     const orderDetailRes = promiseRes.data?.customerOrder ?? null;
     if(!orderDetailRes) {
         redirect('/', RedirectType.replace);
     }
-    const shippingAddress = getAddressFromOrderDetailAddressList(orderDetailRes?.addresses,"order_shipping");
+    const shippingAddress = getAddressFromOrderDetailAddressList(orderDetailRes.addresses,"order_shipping");
     let orderCreatedAt = 'Can\'t get order date';
-    if(orderDetailRes?.createdAt) {
+    if(orderDetailRes.createdAt) {
         const localDate = new Date(orderDetailRes.createdAt);
         orderCreatedAt = localDate.toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });
     }
     const productItems = orderDetailRes?.items?.edges.map(item => {
         return item.node;
     }) || [];
-    const currency = currencies.find(item => item.code === orderDetailRes?.orderCurrencyCode) || null;
+    
 
     useEffect(() => {
         // 如果是游客且支付成功,删除cookie里的GUEST_CART_TOKEN GUEST_CART_ID
@@ -61,10 +61,10 @@ export default function OrderDetailWrapper({
                 </div>
                 <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">
-                    <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 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/>
                     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.
@@ -96,13 +96,13 @@ export default function OrderDetailWrapper({
                 <div className="mt-6 flex justify-between">
                     <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">
-                        {orderDetailRes?._id}
+                        {orderDetailRes._id}
                     </div>
                 </div>
                 <div className="mt-6 flex justify-between">
                     <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">
-                        {orderDetailRes?.status}
+                        {orderDetailRes.status}
                     </div>
                 </div>
                 <div className="mt-6 flex justify-between">
@@ -114,13 +114,13 @@ export default function OrderDetailWrapper({
                 <div className="mt-6 flex justify-between">
                     <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">
-                        {orderDetailRes?.shippingTitle}
+                        {orderDetailRes.shippingTitle}
                     </div>
                 </div>
                 <div className="mt-6 flex justify-between">
                     <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">
-                        {orderDetailRes?.payment?.methodTitle}
+                        {orderDetailRes.payment?.methodTitle}
                     </div>
                 </div>
 
@@ -152,7 +152,10 @@ export default function OrderDetailWrapper({
                                 </div>
                                 <div className="flex justify-between">
                                     <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 className="text-ly-12 leading-ly-20">x {product.qtyOrdered}</p>
                                 </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="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">{currency && currency.symbol}{orderDetailRes?.subTotal}</p>
+                    <p className="text-ly-12 leading-ly-20">
+                        <Price
+                            amount={String(orderDetailRes.subTotal)}
+                            currencyCode={orderDetailRes.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
-                {orderDetailRes?.rewardPointsEarned > 0 &&
+                {orderDetailRes.rewardPointsEarned > 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">You Earn</p>
-                        <p className="text-ly-12 leading-ly-20">{orderDetailRes?.rewardPointsEarned} Reward Points</p>
+                        <p className="text-ly-12 leading-ly-20">{orderDetailRes.rewardPointsEarned} Reward Points</p>
                     </div>
                 }
-                {!!orderDetailRes?.shippingAmount &&
+                {!!orderDetailRes.shippingAmount &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Shipping</p>
-                        <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{orderDetailRes?.shippingAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={String(orderDetailRes.shippingAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {Number(orderDetailRes?.shippingInsuranceAmount) > 0 &&
+                {Number(orderDetailRes.shippingInsuranceAmount) > 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">Insurance of Lost</p>
-                        <p className="text-ly-12 leading-ly-20">{currency && currency.symbol}{Number(orderDetailRes?.shippingInsuranceAmount)}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={String(orderDetailRes.shippingInsuranceAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {!!orderDetailRes?.taxAmount &&
+                {!!orderDetailRes.taxAmount &&
                     <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">{currency && currency.symbol}{orderDetailRes?.taxAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            <Price
+                                amount={String(orderDetailRes.taxAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </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">
                         <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>
                 }
-                {orderDetailRes?.rewardPointsAmount > 0 &&
+                {orderDetailRes.rewardPointsAmount > 0 &&
                     <div className="flex justify-between mt-3 first:mt-0">
                         <p className="text-ly-12 leading-ly-20">You Redeem ({orderDetailRes.rewardPointsUsed} Reward Points)</p>
-                        <p className="text-ly-12 leading-ly-20">-{currency && currency.symbol}{orderDetailRes?.rewardPointsAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={String(orderDetailRes.rewardPointsAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {!!orderDetailRes?.discountAmount &&
+                {!!orderDetailRes.discountAmount &&
                     <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">-{currency && currency.symbol}{orderDetailRes?.discountAmount}</p>
+                        <p className="text-ly-12 leading-ly-20">
+                            -<Price
+                                amount={String(orderDetailRes.discountAmount)}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </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">
                         <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>
                 }
                 {(orderDetailRes?.vipDiscountAmount !== '0.00' && orderDetailRes?.vipDiscountAmount !== null) &&
                     <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">-{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 className="flex justify-between mt-3 first:mt-0">
@@ -228,7 +276,12 @@ export default function OrderDetailWrapper({
                 </div> */}
                 <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 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>
         </>

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

@@ -14,7 +14,8 @@ import ProductText from "./ProductText";
 import NewUserBanner from "./NewUserBanner";
 import PromotionDiscountCardProps from "./PromotionDiscountCardProps ";
 import Image from "next/image";
-import {useConfig} from "@/utils/hooks/useConfig";
+import { useStore } from "@utils/hooks/useConfig";
+import {currencyCountryMap} from "@/utils/constants";
 import {
   isValueAvailable,
   isOptionValueAvailable,
@@ -242,10 +243,9 @@ export function ProductInformation({
       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 = "$";
 

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

@@ -101,12 +101,12 @@ export async function POST(req: NextRequest) {
 
         
         const response = await bagistoFetch<any>(fetchOption);
-        console.log('bagistoFetch response ------ ', response);
+
         return NextResponse.json(response.body,{
             status: response.status
         });
     } catch (error) {
-        console.log('bagistoFetch ERROR ------ ', error);
+
         return NextResponse.json(
             {
                 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 clsx from "clsx";
 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 type { GetCartItemData } from "@/types/cart/type";
 import type { Country } from "@/types/checkout/type";
 import type { CurrenciesData } from "@/types/currency/type";
 import {CURRENT_CURRENCY,CURRENT_LOCAL,CURRENT_CHANNEL} from "@/utils/constants";
@@ -73,17 +72,11 @@ export default async function RootLayout({
   if (env.APP_ENV !== "production") {
     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 
   const defaultChannel = env.DEFAULT_CHANNEL;
   const defaultCurrency = env.DEFAULT_CURRENCY;
   const defaultLocale = env.DEFAULT_LOCAL;
   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();
   ////////eruda调试/////////
@@ -95,23 +88,27 @@ export default async function RootLayout({
 
 
   // 获取country list 和currency list
-  const {data: countryResData} = await serverGraphqlFetch<{
-      countries: Country[] 
+
+  const countriesPromise = serverGraphqlFetch<{
+        countries: Country[];
   }>({
     query: GET_COUNTRIES,
-    cache: 'force-cache',
+    takeAuthorization: false,
+    cache: "force-cache",
     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,
-    cache: 'force-cache',
+    takeAuthorization: false,
+    cache: "force-cache",
     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 = {
     // 系统默认值
     defaultChannel: defaultChannel,
@@ -124,11 +121,9 @@ export default async function RootLayout({
     currentChannel: currentChannel
   };
 
-  const {data: cartDetailsRes} = await serverGraphqlFetch<GetCartItemData>({
-    query: GET_CART_ITEM
-  });
+
   
-  const cartDetails = cartDetailsRes?.createReadCart?.readCart ?? null;
+
   return (
     <html lang="en" suppressHydrationWarning>
       <head>
@@ -143,21 +138,13 @@ export default async function RootLayout({
             <PayPalWrapper clientId={paypalClientId}>
               <ConfigProvider
                   config={{
-                    currencies: currencyList,
-                    countries: countries,
+                    countriesPromise,
+                    currenciesPromise,
                     store: storeConfig
                   }}
               >
               {/* Redux Provider 的初始化 state 只在首次挂载时生效,不会因为 Server Component 重新渲染而重新初始化。 */}
-              <ReduxProvider 
-                preloadedState={{
-                  cartDetail:{
-                    cart: cartDetails,
-                    loading: false,
-                    error: null
-                  }
-                }}
-              >
+              <ReduxProvider>
                 <GlobalProviders>
                   {children}
                   <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 
 } from '@/store/slices/addToCartDialogSlice';
 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 type { 
@@ -34,8 +34,8 @@ import {
 export default function AddToCartModal() {
     const router = useRouter();
     const dispatch = useAppDispatch();
-    const {getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
+    const {store} = useStore();
+    const currentCurrencyCode = store.currentCurrency;
     const {isOpen, product} = useAppSelector((state) => state.addToCartDialog);
 
     const { appendProductToCart } = useAddProduct();
@@ -285,18 +285,18 @@ export default function AddToCartModal() {
                         <Price 
                             className="text-ly-14 text-ly-gray line-through"
                             amount={String(currentVariantInfo.totalLinePrice)} 
-                            currencyCode={currentCurrency.code}
+                            currencyCode={currentCurrencyCode}
                         />
                         <div className="flex items-center">
                             <Price 
                                 className="text-ly-14 bg-ly-gold py-0.5 px-1"
                                 amount={String(currentVariantInfo.save)} 
-                                currencyCode={currentCurrency.code}
+                                currencyCode={currentCurrencyCode}
                             />
                             <Price 
                                 className="text-ly-20 font-bold ml-1.5"
                                 amount={String(currentVariantInfo.totalNowPrice)} 
-                                currencyCode={currentCurrency.code}
+                                currencyCode={currentCurrencyCode}
                             />
                         </div>
                     </div>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 49 - 49
src/components/common/CurrencySwitch/CurrencySwitch.tsx


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 12 - 6
src/components/layout/navbar/NavbarActions.tsx


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

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

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

@@ -1,9 +1,9 @@
 import Link from "next/link";
+import {Suspense} from "react";
 import LogoIcon from "@components/common/icons/LogoIcon";
 import MobileMenuTrigger from "./MobileMenuTrigger";
 import { NavbarActions } from "./NavbarActions";
 import { NavbarErrorBoundary } from "@/components/error/ErrorBoundary";
-
 import { 
   GET_TREEFRONT_MENUS } from "@/graphql";
 import { serverGraphqlFetch } from "@utils/bagisto/index";
@@ -13,29 +13,29 @@ import { auth } from "@/utils/auth/auth-helper";
 import TopSwiperBanner from "./TopSwiperBanner"
 import { restApiFetch } from "@utils/bagisto/index";
 import type { AnnouncementItem } from "./TopSwiperBanner";
+import type { RestfulGetThemeCustomizations } from '@/types/theme/theme-customization';
 export default async function Navbar() {
   // 2. 服务端 fetch 请求我们自己的 Next.js API
-  const res = await restApiFetch({
+  const topBarPromise:Promise<AnnouncementItem[]> = restApiFetch<{data:RestfulGetThemeCustomizations}>({
     api: `/shop/theme-customizations?name=Top_Bar`,
     method: "GET",
     isRoute: false,
     takeAuthorization: false,
-  
-  });
-  let topBarData: AnnouncementItem[] = [];
-  try {
-    // 安全取值链
-    const themeItem = res?.body?.[0];
+  }).then((result) => {
+    const themeItem = result.body[0];
     const optionsStr = themeItem?.translation?.options;
     if (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 isGuest = session === null;
@@ -44,6 +44,7 @@ export default async function Navbar() {
       GetTreefrontMenusResult
     >({
       query: GET_TREEFRONT_MENUS,
+      takeAuthorization: false,
     });
     console.log('GetTreefrontMenusResult --- ',menudata)
     const menudatas =menudata? menudata.treefrontMenus:[];
@@ -51,7 +52,9 @@ export default async function Navbar() {
   return (
     <NavbarErrorBoundary>
       <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">
           <div className="flex w-full items-center justify-between gap-0 sm:gap-4">
             {/* 1. THE STATIC SHELL (Visible Instantly) */}

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

@@ -1,7 +1,8 @@
 "use client";
 import { useState, useEffect, useRef } from "react";
 import { Slider } from "@heroui/slider";
-import { useConfig } from "@/utils/hooks/useConfig";
+import { useStore } from "@utils/hooks/useConfig";
+import {currencyCountryMap} from "@/utils/constants";
 
 type PriceFilterNode = {
   _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 (
     <div className="py-5">

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

@@ -4,15 +4,18 @@ export const Price = ({
   currencyCode = "USD",
   ...rest
 }: {
-  amount: string;
+  amount: string | number;
   className?: 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, {
       style: "currency",
       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 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`
   mutation createAddProductInCart(
@@ -17,78 +18,9 @@ export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData,AddToCa
       }
     ) {
       addProductInCart {
-        id
+        ${CART_DETAILES_FRAGMENT}
         cartToken
         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
         message
       }

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

@@ -1,85 +1,13 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 import type { CreateApplyCouponData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 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 type { CreateMergeCartData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 export const CREATE_MERGE_CART: TypedDocumentNode<CreateMergeCartData> = gql`
   mutation createMergeCart(
@@ -11,76 +12,7 @@ export const CREATE_MERGE_CART: TypedDocumentNode<CreateMergeCartData> = gql`
       }
     ) {
       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 type { CreateRemoveCouponData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 export const CREATE_REMOVE_COUPON: TypedDocumentNode<CreateRemoveCouponData> = gql`
     mutation createRemoveCoupon {
         createRemoveCoupon(input:{}) {
                 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 type { GetCartItemData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 export const GET_CART_ITEM: TypedDocumentNode<GetCartItemData> = gql`
   mutation GetCartItem {
     createReadCart(input: {}) {
       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 type { RemoveCartItemData } from "@/types/cart/type";
+import { CART_DETAILES_FRAGMENT } from "../fragments/CartDetails";
 
 export const REMOVE_CART_ITEM: TypedDocumentNode<RemoveCartItemData,{cartItemId: number}> = gql`
   mutation RemoveCartItem( $cartItemId: Int! ) {
     createRemoveCartItem(input: { cartItemId: $cartItemId }) {
       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 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
   `
@@ -14,76 +15,7 @@ export const UPDATE_CART_ITEM: TypedDocumentNode<UpdateCartItemData,{cartItemId:
       }
     ) {
       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
 RemoveCartItem
 UpdateCartItems
+CreateApplyCoupon
+CreateRemoveCoupon
+CreateSaveCheckoutCart
 
 以上这几个接口返回数据的字段要一样
 因为调完这几个接口后都需要更新redux中的购物车数据

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

@@ -1,5 +1,6 @@
 import { gql, type TypedDocumentNode } from "@apollo/client";
 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`
     mutation CreateSaveCheckoutCart(
@@ -23,76 +24,7 @@ export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData,S
         }
         ) {
             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";
 
 
-import {
-    createContext,
-} from "react";
-
+import { createContext, useState } from "react";
 import type {CurrencyItem} from "@/types/currency/type";
 import type { Country } from "@/types/checkout/type";
 
@@ -31,18 +28,21 @@ export interface StoreConfig {
 
 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({
     config,
@@ -51,9 +51,41 @@ export function ConfigProvider({
     config:AppConfig;
     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 (
         <ConfigContext.Provider
-            value={config}
+            value={value}
         >
             {children}
         </ConfigContext.Provider>

+ 15 - 23
src/providers/ReduxProvider.tsx

@@ -1,31 +1,23 @@
 "use client";
 
 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 = ({ 
-  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 {CartDetail} from "@/types/cart/type";
 import { GET_CART_ITEM } from "@/graphql";
-
 import { formatCartDetail } from "@/utils/cartDetailTools";
+
+/*
+idle 尚未初始化
+loading 正在请求购物车
+succeeded 请求成功,即使购物车为空也算成功
+failed 请求失败
+*/
+export type CartStatus =
+  | "idle"
+  | "loading"
+  | "succeeded"
+  | "failed";
 export interface CartState {
   cart: CartDetail | null;
   loading: boolean;
   error: string | null;
+  status: CartStatus;
 }
 
 // 定义异步 action
-export const fetchCartDetail = createAsyncThunk(
+export const fetchCartDetail = createAsyncThunk<
+  ReturnType<typeof formatCartDetail> | null,
+  void,
+  {
+    state: { cartDetail: CartState };
+    rejectValue: string;
+  }
+>(
   'cartDetail/fetchCartDetail',
   async (_, { rejectWithValue }) => { //_ 占位,表示不需要参数
     try {
@@ -31,13 +50,24 @@ export const fetchCartDetail = createAsyncThunk(
       }
       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 = {
   cart: null,
   loading: false,
+  status: "idle",
   error: null,
 };
 
@@ -63,16 +93,19 @@ const cartSlice = createSlice({
   extraReducers: (builder) => {
     builder
       .addCase(fetchCartDetail.pending, (state) => {
+        state.status = "loading";
         state.loading = true;
         state.error = null;
       })
       .addCase(fetchCartDetail.fulfilled, (state, action) => {
+        state.status = "succeeded";
         state.loading = false;
         state.cart = action.payload;
       })
       .addCase(fetchCartDetail.rejected, (state, action) => {
+        state.status = "failed";
         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 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 { 
   cartDetail: CartState; 
 }
-export const makeStore = (preloadedState: PreloadedStateType) => {
+export const makeStore = (preloadedState?: PreloadedStateType) => {
     return configureStore({
       reducer: {
         cartDetail: cartSlice,
         addToCartDialog: addToCartDialogReducer,
       },
-      preloadedState
+      ...(preloadedState ? { preloadedState } : {}),
     });
 }
 // Infer the type of makeStore

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

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

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

@@ -1,6 +1,6 @@
 import { cookies } from "next/headers";
 import { auth } from "@/utils/auth/auth-helper";
-import { print, type DocumentNode } from "graphql";
+import { print, parse, getOperationAST, type DocumentNode } from "graphql";
 import { 
   GRAPHQL_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
 export async function restApiFetch<T>({
   api,
   method,
-  cache = "force-cache",
+  cache = "no-store",
   headers,
   tags,
   variables,
   isRoute = true,
-  revalidate = 60,
+  revalidate = 0,
   takeAuthorization = true, // 是否携带token,默认携带
 }: {
   api: string;
@@ -119,7 +130,10 @@ export async function restApiFetch<T>({
         Object.assign(baseHeaders, headers);
       }
     }
-    console.log('restApiFetch --- baseHeaders:', baseHeaders)
+    console.log('restApiFetch --- baseHeaders:', {
+      ...baseHeaders, 
+      apiUrl
+    })
     const param: RequestInit = {
       method: method,
       headers: baseHeaders,
@@ -144,7 +158,6 @@ export async function restApiFetch<T>({
     const result = await fetch(url, param);
 
     const body = await result.json();
-    console.log('restApiFetch --- body:', body)
     if(!isRoute) {
       if(result.status === 401) {
         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 {
     const queryString = typeof query === "string" ? query : print(query);
 
+    const operationName = getGraphqlOperationName(query);
+
     if (headers) {
       if (headers instanceof Headers) {
         headers.forEach((value, key) => (baseHeaders[key] = value));
@@ -198,7 +213,10 @@ export async function serverGraphqlFetch<
         Object.assign(baseHeaders, headers);
       }
     }
-    console.log('serverGraphqlFetch --- baseHeaders:', baseHeaders);
+    console.log('serverGraphqlFetch --- baseHeaders:', {
+      ...baseHeaders,
+      operationName
+    });
     const result = await fetch(GRAPHQL_URL, {
       method: "POST",
       headers: baseHeaders,
@@ -266,8 +284,9 @@ export async function bagistoFetch<T>({
 
     }
   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 instanceof Headers) {
@@ -276,12 +295,12 @@ export async function bagistoFetch<T>({
         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, {
       method: "POST",
       headers: baseHeaders,
@@ -297,7 +316,6 @@ export async function bagistoFetch<T>({
     });
 
     const body = await result.json();
-    console.log('bagistoFetch --- body:',body);
     return { status: result.status, body };
   } catch (e) {
     throw e;

+ 3 - 0
src/utils/cartDetailTools.ts

@@ -8,6 +8,9 @@ export function formatCartDetail(cart:CartDetail): CartDetail {
         isGuest: cart.isGuest,
         itemsCount: cart.itemsCount,
         items: cart.items,
+        hasFreeGift: cart.hasFreeGift,
+        freeGiftAvailable: cart.freeGiftAvailable,
+        freeGiftAvailableQty: cart.freeGiftAvailableQty,
 
         earnableRewardPoints: cart.earnableRewardPoints,
         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 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> = {

+ 5 - 0
src/utils/helper.ts

@@ -1,6 +1,7 @@
 import { ReadonlyURLSearchParams } from "next/navigation";
 import { type Metadata } from "next";
 import type { FilterDataTypes } from "@/types/types";
+import type {CurrencyItem} from "@/types/currency/type";
 import { isArray } from "./type-guards";
 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];
 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 = (
   pathname: string,
   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";
-export function useConfig(){
+
+export function useStore(){
     const context = useContext(ConfigContext);
 
     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 {
-        countries,
-        currencies,
         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);
 }