Browse Source

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

fogwind 1 day ago
parent
commit
0e265fc56e
43 changed files with 857 additions and 922 deletions
  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";
 "use server";
 
 
 import { cookies } from "next/headers";
 import { cookies } from "next/headers";
-import { revalidatePath } from "next/cache";
+// import { revalidatePath } from "next/cache";
 import {CURRENT_CURRENCY} from "@/utils/constants";
 import {CURRENT_CURRENCY} from "@/utils/constants";
 import {currencyCookieOptions} from "@/utils/constants/server";
 import {currencyCookieOptions} from "@/utils/constants/server";
 export async function switchCurrencyAction(currency: string) {
 export async function switchCurrencyAction(currency: string) {
@@ -9,6 +9,6 @@ export async function switchCurrencyAction(currency: string) {
     const cookieStore = await cookies()
     const cookieStore = await cookies()
 
 
     cookieStore.set(CURRENT_CURRENCY,currency,currencyCookieOptions);
     cookieStore.set(CURRENT_CURRENCY,currency,currencyCookieOptions);
-
-    revalidatePath('/', 'layout'); // 根 layout.tsx下的所有页面,缓存都失效
+    // 还没有启用缓存,不需要调revalidatePath
+    // revalidatePath('/', 'layout'); // 根 layout.tsx下的所有页面,缓存都失效
 }
 }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 21 - 34
src/app/layout.tsx

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

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

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

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

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

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


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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

+ 44 - 12
src/providers/ConfigProvider.tsx

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

+ 15 - 23
src/providers/ReduxProvider.tsx

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

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

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

+ 2 - 13
src/store/store.ts

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

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

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

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

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

+ 3 - 0
src/utils/cartDetailTools.ts

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

+ 31 - 7
src/utils/constants.ts

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

+ 5 - 0
src/utils/helper.ts

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

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

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