瀏覽代碼

根layout.tsx优化:购物车数据初始化;ConfigProvider优化;货币码优化;赠品展示;

fogwind 1 天之前
父節點
當前提交
0e265fc56e
共有 43 個文件被更改,包括 857 次插入 和 922 次删除
  1. 3 3
      src/actions/switchCurrencyAction.ts
  2. 2 2
      src/app/(checkout)/checkout/_components/CheckoutAddress/BillingAddressCheckout.tsx
  3. 2 2
      src/app/(checkout)/checkout/_components/CheckoutAddress/ShippingAddressCheckout.tsx
  4. 2 2
      src/app/(checkout)/checkout/_components/CheckoutPlaceOrder.tsx
  5. 66 19
      src/app/(checkout)/checkout/_components/CheckoutProducts/CheckoutProducts.tsx
  6. 63 19
      src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx
  7. 5 6
      src/app/(checkout)/checkout/_components/ContinueToPay/PaymentMethodContinueTpPay.tsx
  8. 12 6
      src/app/(checkout)/checkout/_components/ContinueToPay/ProductsModalPendingOrder.tsx
  9. 1 4
      src/app/(checkout)/checkout/_components/LostInsurance/LostInsurance.tsx
  10. 4 4
      src/app/(checkout)/checkout/_components/PaymentButton/PaypalApplepayButton.tsx
  11. 2 2
      src/app/(checkout)/checkout/_components/PaymentButton/PaypalButton.tsx
  12. 5 4
      src/app/(checkout)/checkout/_components/PaymentMethodCheckout.tsx
  13. 74 44
      src/app/(checkout)/checkout/cart/_components/CartWrapper.tsx
  14. 77 19
      src/app/(checkout)/checkout/cart/_components/UnpaidOrderSection.tsx
  15. 84 31
      src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx
  16. 5 5
      src/app/(public)/product/_components/ProductInformation.tsx
  17. 21 34
      src/app/layout.tsx
  18. 21 0
      src/components/cart/CartBootstrap.tsx
  19. 6 6
      src/components/common/AddToCartModal/AddToCartModal.tsx
  20. 49 49
      src/components/common/CurrencySwitch/CurrencySwitch.tsx
  21. 12 6
      src/components/layout/navbar/NavbarActions.tsx
  22. 5 5
      src/components/theme/filters/FilterPrice.tsx
  23. 10 7
      src/components/theme/ui/Price.tsx
  24. 93 0
      src/graphql/cart/fragments/CartDetails.ts
  25. 2 70
      src/graphql/cart/mutations/AddProductToCart.ts
  26. 6 78
      src/graphql/cart/mutations/CreateApplyCoupon.ts
  27. 2 70
      src/graphql/cart/mutations/CreateMergeCart.ts
  28. 2 70
      src/graphql/cart/mutations/CreateRemoveCoupon.ts
  29. 2 70
      src/graphql/cart/mutations/GetCartItem.ts
  30. 2 70
      src/graphql/cart/mutations/RemoveCartItem.ts
  31. 2 70
      src/graphql/cart/mutations/UpdateCartItems.ts
  32. 3 0
      src/graphql/cart/mutations/readme.md
  33. 2 70
      src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts
  34. 44 12
      src/providers/ConfigProvider.tsx
  35. 15 23
      src/providers/ReduxProvider.tsx
  36. 37 4
      src/store/slices/cart-slice.ts
  37. 2 13
      src/store/store.ts
  38. 10 1
      src/types/cart/type.ts
  39. 31 0
      src/types/freegift/type.ts
  40. 3 0
      src/utils/cartDetailTools.ts
  41. 31 7
      src/utils/constants.ts
  42. 5 0
      src/utils/helper.ts
  43. 32 15
      src/utils/hooks/useConfig.ts

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

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

文件差異過大導致無法顯示
+ 49 - 49
src/components/common/CurrencySwitch/CurrencySwitch.tsx


文件差異過大導致無法顯示
+ 12 - 6
src/components/layout/navbar/NavbarActions.tsx


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

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