Prechádzať zdrojové kódy

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

fogwind 1 týždeň pred
rodič
commit
bfb90e91d7
45 zmenil súbory, kde vykonal 923 pridanie a 924 odobranie
  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. 63 18
      src/app/(checkout)/checkout/_components/CheckoutProducts/CheckoutProducts.tsx
  6. 62 17
      src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx
  7. 4 6
      src/app/(checkout)/checkout/_components/ContinueToPay/PaymentMethodContinueTpPay.tsx
  8. 7 6
      src/app/(checkout)/checkout/_components/ContinueToPay/ProductsInPendingOrder.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. 4 4
      src/app/(checkout)/checkout/_components/PaymentMethodCheckout.tsx
  13. 71 37
      src/app/(checkout)/checkout/cart/_components/CartWrapper.tsx
  14. 79 20
      src/app/(checkout)/checkout/cart/_components/UnpaidOrderSection.tsx
  15. 75 22
      src/app/(public)/paymentresult/_components/CancelOrFailure.tsx
  16. 75 23
      src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx
  17. 6 6
      src/app/(public)/product/_components/ProductInformation.tsx
  18. 9 0
      src/app/globals.css
  19. 18 28
      src/app/layout.tsx
  20. 21 0
      src/components/cart/CartBootstrap.tsx
  21. 7 7
      src/components/common/AddToCartModal/AddToCartModal.tsx
  22. 48 48
      src/components/common/CurrencySwitch/CurrencySwitch.tsx
  23. 14 6
      src/components/layout/navbar/NavbarActions.tsx
  24. 5 5
      src/components/theme/filters/FilterPrice.tsx
  25. 10 7
      src/components/theme/ui/Price.tsx
  26. 93 0
      src/graphql/cart/fragments/CartDetails.ts
  27. 2 70
      src/graphql/cart/mutations/AddProductToCart.ts
  28. 6 78
      src/graphql/cart/mutations/CreateApplyCoupon.ts
  29. 2 70
      src/graphql/cart/mutations/CreateMergeCart.ts
  30. 4 72
      src/graphql/cart/mutations/CreateRemoveCoupon.ts
  31. 2 70
      src/graphql/cart/mutations/GetCartItem.ts
  32. 2 70
      src/graphql/cart/mutations/RemoveCartItem.ts
  33. 2 70
      src/graphql/cart/mutations/UpdateCartItems.ts
  34. 3 0
      src/graphql/cart/mutations/readme.md
  35. 2 70
      src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts
  36. 44 12
      src/providers/ConfigProvider.tsx
  37. 15 23
      src/providers/ReduxProvider.tsx
  38. 37 4
      src/store/slices/cart-slice.ts
  39. 2 13
      src/store/store.ts
  40. 10 1
      src/types/cart/type.ts
  41. 31 0
      src/types/freegift/type.ts
  42. 3 0
      src/utils/cartDetailTools.ts
  43. 31 7
      src/utils/constants.ts
  44. 6 0
      src/utils/helper.ts
  45. 32 15
      src/utils/hooks/useConfig.ts

+ 3 - 3
src/actions/switchCurrencyAction.ts

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

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

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

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

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

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

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

+ 63 - 18
src/app/(checkout)/checkout/_components/CheckoutProducts/CheckoutProducts.tsx

@@ -5,11 +5,11 @@ import Image from "next/image";
 import type {CartDetail, CartItem} from "@/types/cart/type";
 import { useAddProduct } from "@utils/hooks/useAddToCart";
 import { useCustomToast } from "@/utils/hooks/useToast";
-import {useConfig} from "@/utils/hooks/useConfig";
+import {useStore} from "@/utils/hooks/useConfig";
 import { formatCartDetail, formatCartProductOptions } from "@/utils/cartDetailTools";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
-
+import { Price } from "@components/theme/ui/Price";
 
 
 // 修改数量接口 数量不能为0
@@ -29,12 +29,17 @@ export default function CheckoutProducts({
     const { showToast } = useCustomToast();
     const router = useRouter();
 
-    const {getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
-    const currencySymbol = currentCurrency.symbol;
+    const {store} = useStore();
+    const currentCurrencyCode = store.currentCurrency;
 
-    const 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);
+        }
     });
 
 
@@ -105,7 +110,7 @@ export default function CheckoutProducts({
      
         <div className="w-full max-h-100 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-500/30">
             <div className="relative box-border w-full h-auto">
-                {productList.map((product) => {
+                {normalProducts.map((product) => {
                     const baseImage = product.baseImage ? JSON.parse(product.baseImage) : {};
                     const productOptions = product.options.edges.map((edge) => edge.node);
                     return (
@@ -129,18 +134,25 @@ export default function CheckoutProducts({
                                 </div>
                                 <div className="flex items-center justify-between">
                                     <div className="flex items-center gap-1.5">
-                                        <span className="text-ly-14 font-bold">{currencySymbol}{product.price}</span>
+                                        <Price className="text-ly-14 font-bold"
+                                            amount={product.price}
+                                            currencyCode={currentCurrencyCode}
+                                        />
                                         {/* <span className="text-ly-14 text-[#d3d3d3] line-through">$200.12</span> */}
                                     </div>
-                                    <div className=" flex-none flex border-1 rounded-lg px-2.5 py-1.5 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-16">{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 px-2.5 py-1.5 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-16">{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-16">X{product.quantity}</span>
+                                    }
                                 </div>
                             </div>
                         </div>
@@ -149,6 +161,39 @@ export default function CheckoutProducts({
                 })}
                         
             </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>
 
     </>);

+ 62 - 17
src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx

@@ -2,12 +2,12 @@
 
 
 import { useRouter } from 'next/navigation';
-import {useConfig} from "@/utils/hooks/useConfig";
 import type { OrderDetails } from "@/types/customer/type";
 import { confirmDialog } from "@/components/theme/ui/kernel/confirm/api";
 import ProductsInPendingOrder from "./ProductsInPendingOrder";
 import dynamic from "next/dynamic";
 import {getAddressFromOrderDetailAddressList} from "@/utils/orderDetailTools";
+import { Price } from "@components/theme/ui/Price";
 
 const CountDown = dynamic(() => import("@/components/common/CountDown"), {
   ssr: false,
@@ -19,11 +19,6 @@ export default function ContinueToPayOrderInfo({
 }) { 
     
     const router = useRouter();
-    const {currencies,getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
-    const currencySymbol = currentCurrency.symbol;
-    const orderCurrency = currencies.find(item => item.code === orderDetail.orderCurrencyCode) || null;
-    const orderSymbol = orderCurrency?.symbol || currencySymbol;
     
 
     const countDownEnd = (new Date(orderDetail.createdAt)).getTime() + 24*3600*1000;
@@ -123,7 +118,7 @@ export default function ContinueToPayOrderInfo({
                 <div className="w-full box-border max-h-90 overflow-y-auto scrollbar-thin scrollbar-thumb-gray-200">
                     <ProductsInPendingOrder 
                         products={productItems}
-                        currencySymbol={orderSymbol}
+                        currencyCode={orderDetail.orderCurrencyCode}
                     />
 
                 </div> 
@@ -134,7 +129,12 @@ export default function ContinueToPayOrderInfo({
                 <h3 className="text-ly-20 font-medium">Promotion Details</h3>
                 <div className="flex justify-between mt-4 first:mt-0">
                     <p className="text-ly-14">Subtotal</p>
-                    <p className="text-ly-14">{orderSymbol}{orderDetail.subTotal}</p>
+                    <p className="text-ly-14">
+                        <Price 
+                            amount={orderDetail.subTotal}
+                            currencyCode={orderDetail.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
                 {orderDetail.rewardPointsEarned > 0 &&
                     <div className="flex justify-between mt-4 first:mt-0">
@@ -145,49 +145,89 @@ export default function ContinueToPayOrderInfo({
                 {orderDetail.shippingAmount !== 0 &&
                     <div className="flex justify-between mt-4 first:mt-0">
                         <p className="text-ly-14">Shipping</p>
-                        <p className="text-ly-14">{orderSymbol}{orderDetail.shippingAmount}</p>
+                        <p className="text-ly-14">
+                            <Price 
+                                amount={orderDetail.shippingAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {Number(orderDetail?.shippingInsuranceAmount) > 0 &&
                     <div className="flex justify-between mt-4 first:mt-0">
                         <p className="text-ly-14">Shipping</p>
-                        <p className="text-ly-14">{orderSymbol}{Number(orderDetail.shippingInsuranceAmount)}</p>
+                        <p className="text-ly-14">
+                            <Price 
+                                amount={orderDetail.shippingInsuranceAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {orderDetail.taxAmount !== 0 &&
                     <div className="flex justify-between mt-4 first:mt-0">
                         <p className="text-ly-14">Tax</p>
-                        <p className="text-ly-14">{orderSymbol}{orderDetail.taxAmount}</p>
+                        <p className="text-ly-14">
+                            <Price 
+                                amount={orderDetail.taxAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {(orderDetail.vipPlusAmount !== '0.00' && orderDetail.vipPlusAmount !== null)  &&
                     <div className="flex justify-between mt-4 first:mt-0">
                         <p className="text-ly-14">Plus Fee</p>
-                        <p className="text-ly-14">{orderSymbol}{orderDetail.vipPlusAmount}</p>
+                        <p className="text-ly-14">
+                            <Price 
+                                amount={orderDetail.vipPlusAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {orderDetail.discountAmount !== 0 &&
                     <div className="flex justify-between mt-4 first:mt-0">
                         <p className="text-ly-14">Discount</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{orderSymbol}{orderDetail.discountAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price 
+                                amount={orderDetail.discountAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {(orderDetail.giftcardAmount !== '0.00' && orderDetail.giftcardAmount !== null) &&
                     <div className="flex justify-between mt-4 first:mt-0">
                         <p className="text-ly-14">Gift Card</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{orderSymbol}{orderDetail.giftcardAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price 
+                                amount={orderDetail.giftcardAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {(orderDetail.vipDiscountAmount !== '0.00' && orderDetail.vipDiscountAmount !== null) &&
                     <div className="flex justify-between mt-4 first:mt-0">
                         <p className="text-ly-14">Plus Save</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{orderSymbol}{orderDetail.vipDiscountAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price 
+                                amount={orderDetail.vipDiscountAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {orderDetail.rewardPointsAmount > 0 &&
                     <div className="flex justify-between mt-4 first:mt-0">
                         <p className="text-ly-14">You Redeem ({orderDetail.rewardPointsUsed} Reward Points)</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{orderSymbol}{orderDetail.rewardPointsAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price 
+                                amount={orderDetail.rewardPointsAmount}
+                                currencyCode={orderDetail.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
 
@@ -197,7 +237,12 @@ export default function ContinueToPayOrderInfo({
                 </div> */}
                 <div className="flex justify-between mt-4 first:mt-0">
                     <p className="text-ly-16">Total</p>
-                    <p className="text-ly-16 font-bold">{orderSymbol}{orderDetail.grandTotal}</p>
+                    <p className="text-ly-16 font-bold">
+                        <Price 
+                            amount={orderDetail.grandTotal}
+                            currencyCode={orderDetail.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
             </div>
         </>

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

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

+ 7 - 6
src/app/(checkout)/checkout/_components/ContinueToPay/ProductsInPendingOrder.tsx

@@ -3,14 +3,14 @@
 import Image from "next/image";
 import type {ProductsInOrderDetails} from "@/types/customer/type";
 import {getProductAdditionalInfo} from "@/utils/orderDetailTools";
-
+import { Price } from "@components/theme/ui/Price";
 
 export default function ProductsInPendingOrder({
     products,
-    currencySymbol,
+    currencyCode,
 }: {
     products: ProductsInOrderDetails[],
-    currencySymbol: string;
+    currencyCode: string;
 }) {
 
     return (
@@ -37,9 +37,10 @@ export default function ProductsInPendingOrder({
                                 </p>
                             </div>
                             <div className="flex justify-between items-center">
-                                <span className="text-ly-14 font-bold">
-                                    {currencySymbol + product.price}
-                                </span>
+                                <Price className="text-ly-14 font-bold"
+                                    amount={product.price}
+                                    currencyCode={currencyCode}
+                                />
                                 <span className="text-ly-14">X {product.qtyOrdered}</span>
                             </div>
                         </div>

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

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

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

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

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

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

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

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

+ 71 - 37
src/app/(checkout)/checkout/cart/_components/CartWrapper.tsx

@@ -9,7 +9,7 @@ import { useCustomToast } from "@/utils/hooks/useToast";
 import { useAppDispatch } from "@/store/hooks";
 import { useAddProduct } from "@utils/hooks/useAddToCart";
 import { updateCart, clearCart } from "@/store/slices/cart-slice";
-import {useConfig} from "@/utils/hooks/useConfig";
+import {useStore} from "@/utils/hooks/useConfig";
 import {useLoginModal} from "@/providers/LoginModalProvider";
 import type { CartDetail, CartItem } from "@/types/cart/type";
 import type { OrderDetails } from "@/types/customer/type";
@@ -21,6 +21,7 @@ import CheckoutGiftCardGate from "../../_components/GiftCard/CheckoutGiftCardGat
 import CheckoutVipPlus from "../../_components/VipPlus/CheckoutVipPlus";
 import CheckoutCoupon from "../../_components/CheckoutCoupon/CheckoutCoupon";
 import { UnpaidOrderSection } from "./UnpaidOrderSection";
+import { Price } from "@components/theme/ui/Price";
 
 function computeGrandTotal(cartData: CartDetail) {
 
@@ -57,16 +58,23 @@ export function CartWrapper({
     const {deleteProductFromCart, editProductQtyFromCart} = useAddProduct();
 
     const { showToast } = useCustomToast();
-    const {getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
-    const currencySymbol = currentCurrency.symbol;
+    const {store} = useStore();
+    const currentCurrencyCode = store.currentCurrency;
+
 
     const [cartData, setCartData] = useState<CartDetail>(cartDetailData);
     const [amountDetailOpen, setAmountDetailOpen] = useState(false);
 
 
-    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 不包含运费和丢件险和积分抵扣金额
@@ -222,7 +230,7 @@ export function CartWrapper({
                 </div>
 
                 <div className="mt-4 box-border px-4">
-                    {productList.map((product) => {
+                    {normalProducts.map((product) => {
                         const baseImage = product.baseImage ? JSON.parse(product.baseImage) : {};
                         const productOptions = product.options.edges.map((edge) => edge.node);
                         return (
@@ -250,42 +258,67 @@ export function CartWrapper({
                                     <div className="flex items-center justify-end">
                                         <div className="flex items-center gap-2 mr-3">
                                             {/* <span className="text-ly-14 text-[#d3d3d3] line-through">$200.12</span> */}
-                                            <span className="text-ly-20 font-bold">{currencySymbol}{product.price}</span>
+                                            <Price className="text-ly-20 font-bold"
+                                                amount={product.price}
+                                                currencyCode={currentCurrencyCode}
+                                            />
                                         </div>
-                                        <div className=" flex-none flex border-1 rounded-lg px-2.5 py-1.25 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>
+                                        {product.canChangeQty ?
+                                            <div className=" flex-none flex border-1 rounded-lg px-2.5 py-1.25 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-16">{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-16">{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>
+                                        }
+                                        
                                     </div>
                                 </div>
                             </div>
                             
                         )
                     })}
-
-                    {/* <div className="flex justify-between gap-2 w-full bg-ly-lightgray box-border p-3">
-                        <div className="relative flex-none w-30 h-30">
-                            <Image className="object-cover" fill
-                                src="https://cdn.asteriahair.com/media/catalog/product/1/_/1_1_5.png"
-                                alt="test" 
-                            />
-                        </div>
-                        <div className="w-full flex flex-col justify-between">
-                            <p className="text-ly-12 leading-5">
-                                Asteria Exclusive Bonus Free Random Gifts (1 Order 1 PC)
-                            </p>
-                            <p className="flex justify-between items-center">
-                                <span className="text-ly-14 font-bold">$0.00</span>
-                                <span className="text-ly-12">1</span>
-                            </p>
-                        </div>
-                    </div> */}
                 </div>
+    
+                {freeGiftProducts.length > 0 && 
+                    <div className="w-full box-border mt-6 px-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-6 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">
@@ -308,9 +341,10 @@ export function CartWrapper({
                             Total({cartData.itemsQty}{cartData.itemsQty > 1 ? ' items' : ' item'})
                             <svg className={clsx("w-6 h-6 ml-1", amountDetailOpen && "-rotate-180")} 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>
                         </div>
-                        <span className="text-ly-24 font-bold">
-                            {currencySymbol}{grandTotal}
-                        </span>
+                        <Price className="text-ly-24 font-bold"
+                            amount={grandTotal}
+                            currencyCode={currentCurrencyCode}
+                        />
                     </div>
 
                     <ul className={clsx("w-full mt-6",!amountDetailOpen && "hidden")}>

+ 79 - 20
src/app/(checkout)/checkout/cart/_components/UnpaidOrderSection.tsx

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

+ 75 - 22
src/app/(public)/paymentresult/_components/CancelOrFailure.tsx

@@ -7,11 +7,11 @@ import Link from "next/link";
 import type {FetchGraphqlResult} from "@/types/graphqlFetch/type";
 import type { OrderDetailsData } from "@/types/customer/type";
 import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
-import { useConfig } from "@utils/hooks/useConfig";
 import {getAddressFromOrderDetailAddressList,getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import { useAppDispatch } from "@/store/hooks";
 import { fetchCartDetail } from "@/store/slices/cart-slice";
 import Faqs from "./Faqs";
+import { Price } from "@components/theme/ui/Price";
 
 export default function CancelOrFailure({
     orderDetailPromise,
@@ -23,7 +23,7 @@ export default function CancelOrFailure({
     isGuest: boolean
 }) { 
     const dispatch = useAppDispatch();
-    const {currencies} = useConfig();
+
     const promiseRes = use(orderDetailPromise);
     const orderDetailRes = promiseRes.data?.customerOrder ?? null;
     if(!orderDetailRes) {
@@ -34,7 +34,7 @@ export default function CancelOrFailure({
     const orderStatus = orderDetailRes?.status;
     const cancelOrderRef = useRef(false);
     const shippingAddress = getAddressFromOrderDetailAddressList(orderDetailRes.addresses,"order_shipping");
-    const currency = currencies.find(item => item.code === orderDetailRes.orderCurrencyCode) || null;
+
     let orderCreatedAt = 'Can\'t get order date';
     if(orderDetailRes?.createdAt) {
         const localDate = new Date(orderDetailRes.createdAt);
@@ -192,7 +192,10 @@ export default function CancelOrFailure({
                                 </div>
                                 <div className="flex justify-between">
                                     <p className="text-ly-14 leading-ly-20 font-bold">
-                                        {currency && currency.symbol}{product.price}
+                                        <Price
+                                            amount={product.price}
+                                            currencyCode={orderDetailRes.orderCurrencyCode}
+                                        />
                                     </p>
                                     <p className="text-ly-14 leading-ly-20">x {product.qtyOrdered}</p>
                                 </div>
@@ -206,7 +209,12 @@ export default function CancelOrFailure({
             <div className="box-border w-full mt-8">
                 <div className="flex justify-between items-center mt-4 first:mt-0">
                     <p className="text-ly-14">Subtotal</p>
-                    <p className="text-ly-14">{currency && currency.symbol}{orderDetailRes?.subTotal}</p>
+                    <p className="text-ly-14">
+                        <Price
+                            amount={orderDetailRes.subTotal}
+                            currencyCode={orderDetailRes.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
                 {orderDetailRes?.rewardPointsEarned > 0 &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
@@ -214,52 +222,92 @@ export default function CancelOrFailure({
                         <p className="text-ly-14">{orderDetailRes?.rewardPointsEarned} Reward Points</p>
                     </div>
                 }
-                {!!orderDetailRes?.shippingAmount &&
+                {!!orderDetailRes.shippingAmount &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Shipping</p>
-                        <p className="text-ly-14">{currency && currency.symbol}{orderDetailRes?.shippingAmount}</p>
+                        <p className="text-ly-14">
+                            <Price
+                                amount={orderDetailRes.shippingAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {Number(orderDetailRes?.shippingInsuranceAmount) > 0 &&
+                {Number(orderDetailRes.shippingInsuranceAmount) > 0 &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Insurance of Lost</p>
-                        <p className="text-ly-14">{currency && currency.symbol}{Number(orderDetailRes?.shippingInsuranceAmount)}</p>
+                        <p className="text-ly-14">
+                            <Price
+                                amount={orderDetailRes.shippingInsuranceAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {!!orderDetailRes?.taxAmount &&
+                {!!orderDetailRes.taxAmount &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Tax</p>
-                        <p className="text-ly-14">{currency && currency.symbol}{orderDetailRes?.taxAmount}</p>
+                        <p className="text-ly-14">
+                            <Price
+                                amount={orderDetailRes.taxAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {(orderDetailRes?.vipPlusAmount !== '0.00' && orderDetailRes?.vipPlusAmount !== null) &&
+                {(orderDetailRes.vipPlusAmount !== '0.00' && orderDetailRes.vipPlusAmount !== null) &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Plus Fee</p>
-                        <p className="text-ly-14">{currency && currency.symbol}{orderDetailRes?.vipPlusAmount}</p>
+                        <p className="text-ly-14">
+                            <Price
+                                amount={orderDetailRes.vipPlusAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {orderDetailRes?.rewardPointsAmount > 0 &&
+                {orderDetailRes.rewardPointsAmount > 0 &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">You Redeem ({orderDetailRes.rewardPointsUsed} Reward Points)</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{currency && currency.symbol}{orderDetailRes?.rewardPointsAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price
+                                amount={orderDetailRes.rewardPointsAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {!!orderDetailRes?.discountAmount &&
+                {!!orderDetailRes.discountAmount &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Discount</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{currency && currency.symbol}{orderDetailRes?.discountAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price
+                                amount={orderDetailRes.discountAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {(orderDetailRes?.giftcardAmount !== '0.00' && orderDetailRes?.giftcardAmount !== null) &&
+                {(orderDetailRes.giftcardAmount !== '0.00' && orderDetailRes.giftcardAmount !== null) &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Gift Card</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{currency && currency.symbol}{orderDetailRes?.giftcardAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price
+                                amount={orderDetailRes.giftcardAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {(orderDetailRes?.vipDiscountAmount !== '0.00' && orderDetailRes?.vipDiscountAmount !== null) &&
+                {(orderDetailRes.vipDiscountAmount !== '0.00' && orderDetailRes.vipDiscountAmount !== null) &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Plus Save</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{currency && currency.symbol}{orderDetailRes?.vipDiscountAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price
+                                amount={orderDetailRes.vipDiscountAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {/* <div className="flex justify-between mt-3 first:mt-0">
@@ -268,7 +316,12 @@ export default function CancelOrFailure({
                 </div> */}
                 <div className="flex justify-between mt-4 first:mt-0 pt-6 border-t-1 border-[#f0f0f0]">
                     <p className="text-ly-14">Total</p>
-                    <p className="text-ly-24 font-bold">{currency && currency.symbol}{orderDetailRes?.grandTotal}</p>
+                    <p className="text-ly-24 font-bold">
+                        <Price
+                            amount={orderDetailRes.grandTotal}
+                            currencyCode={orderDetailRes.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
             </div>
         </div>

+ 75 - 23
src/app/(public)/paymentresult/_components/OrderDetailWrapper.tsx

@@ -7,9 +7,9 @@ import Image from "next/image";
 import type {FetchGraphqlResult} from "@/types/graphqlFetch/type";
 import type { OrderDetailsData } from "@/types/customer/type";
 import {deleteGuestCookieAction} from "@/actions";
-import { useConfig } from "@utils/hooks/useConfig";
 import {getAddressFromOrderDetailAddressList, getProductAdditionalInfo} from "@/utils/orderDetailTools";
 import Faqs from "./Faqs";
+import { Price } from "@components/theme/ui/Price";
 
 /**
  * 订单详情里的金额的货币符号不会根据用户选择的货币改变 ,而是固定为下单时使用的货币
@@ -23,7 +23,6 @@ export default function OrderDetailWrapper({
     isGuest: boolean;
 }) { 
 
-    const {currencies} = useConfig();
     const promiseRes = use(orderDetailPromise);
     const orderDetailRes = promiseRes.data?.customerOrder ?? null;
     if(!orderDetailRes) {
@@ -38,7 +37,7 @@ export default function OrderDetailWrapper({
     const productItems = orderDetailRes?.items?.edges.map(item => {
         return item.node;
     }) || [];
-    const currency = currencies.find(item => item.code === orderDetailRes?.orderCurrencyCode) || null;
+  
 
     useEffect(() => {
         // 如果是游客且支付成功,删除cookie里的GUEST_CART_TOKEN GUEST_CART_ID
@@ -155,7 +154,10 @@ export default function OrderDetailWrapper({
                                 </div>
                                 <div className="flex justify-between">
                                     <p className="text-ly-14 leading-ly-20 font-bold">
-                                        {currency && currency.symbol}{product.price}
+                                        <Price
+                                            amount={product.price}
+                                            currencyCode={orderDetailRes.orderCurrencyCode}
+                                        />
                                     </p>
                                     <p className="text-ly-14 leading-ly-20">x {product.qtyOrdered}</p>
                                 </div>
@@ -169,60 +171,105 @@ export default function OrderDetailWrapper({
             <div className="box-border w-full mt-8">
                 <div className="flex justify-between items-center mt-4 first:mt-0">
                     <p className="text-ly-14">Subtotal</p>
-                    <p className="text-ly-14">{currency && currency.symbol}{orderDetailRes?.subTotal}</p>
+                    <p className="text-ly-14">
+                        <Price
+                            amount={orderDetailRes.subTotal}
+                            currencyCode={orderDetailRes.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
-                {orderDetailRes?.rewardPointsEarned > 0 &&
+                {orderDetailRes.rewardPointsEarned > 0 &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">You Earn</p>
                         <p className="text-ly-14">{orderDetailRes?.rewardPointsEarned} Reward Points</p>
                     </div>
                 }
-                {!!orderDetailRes?.shippingAmount &&
+                {!!orderDetailRes.shippingAmount &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Shipping</p>
-                        <p className="text-ly-14">{currency && currency.symbol}{orderDetailRes?.shippingAmount}</p>
+                        <p className="text-ly-14">
+                            <Price
+                                amount={orderDetailRes.shippingAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {Number(orderDetailRes?.shippingInsuranceAmount) > 0 &&
+                {Number(orderDetailRes.shippingInsuranceAmount) > 0 &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Insurance of Lost</p>
-                        <p className="text-ly-14">{currency && currency.symbol}{Number(orderDetailRes?.shippingInsuranceAmount)}</p>
+                        <p className="text-ly-14">
+                            <Price
+                                amount={orderDetailRes.shippingInsuranceAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {!!orderDetailRes?.taxAmount &&
+                {!!orderDetailRes.taxAmount &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Tax</p>
-                        <p className="text-ly-14">{currency && currency.symbol}{orderDetailRes?.taxAmount}</p>
+                        <p className="text-ly-14">
+                            <Price
+                                amount={orderDetailRes.taxAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {(orderDetailRes?.vipPlusAmount !== '0.00' && orderDetailRes?.vipPlusAmount !== null) &&
+                {(orderDetailRes.vipPlusAmount !== '0.00' && orderDetailRes.vipPlusAmount !== null) &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Plus Fee</p>
-                        <p className="text-ly-14">{currency && currency.symbol}{orderDetailRes?.vipPlusAmount}</p>
+                        <p className="text-ly-14">
+                            <Price
+                                amount={orderDetailRes.vipPlusAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {orderDetailRes?.rewardPointsAmount > 0 &&
+                {orderDetailRes.rewardPointsAmount > 0 &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">You Redeem ({orderDetailRes.rewardPointsUsed} Reward Points)</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{currency && currency.symbol}{orderDetailRes?.rewardPointsAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price
+                                amount={orderDetailRes.rewardPointsAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {!!orderDetailRes?.discountAmount &&
+                {!!orderDetailRes.discountAmount &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Discount</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{currency && currency.symbol}{orderDetailRes?.discountAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price
+                                amount={orderDetailRes.discountAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {(orderDetailRes?.giftcardAmount !== '0.00' && orderDetailRes?.giftcardAmount !== null) &&
+                {(orderDetailRes.giftcardAmount !== '0.00' && orderDetailRes.giftcardAmount !== null) &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Gift Card</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{currency && currency.symbol}{orderDetailRes?.giftcardAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price
+                                amount={orderDetailRes.giftcardAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
-                {(orderDetailRes?.vipDiscountAmount !== '0.00' && orderDetailRes?.vipDiscountAmount !== null) &&
+                {(orderDetailRes.vipDiscountAmount !== '0.00' && orderDetailRes.vipDiscountAmount !== null) &&
                     <div className="flex justify-between items-center mt-4 first:mt-0">
                         <p className="text-ly-14">Plus Save</p>
-                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">-{currency && currency.symbol}{orderDetailRes?.vipDiscountAmount}</p>
+                        <p className="flex items-center h-6 px-2 bg-ly-gold text-ly-14">
+                            -<Price
+                                amount={orderDetailRes.vipDiscountAmount}
+                                currencyCode={orderDetailRes.orderCurrencyCode}
+                            />
+                        </p>
                     </div>
                 }
                 {/* <div className="flex justify-between mt-3 first:mt-0">
@@ -231,7 +278,12 @@ export default function OrderDetailWrapper({
                 </div> */}
                 <div className="flex justify-between mt-4 first:mt-0 pt-6 border-t-1 border-[#f0f0f0]">
                     <p className="text-ly-14">Total</p>
-                    <p className="text-ly-24 font-bold">{currency && currency.symbol}{orderDetailRes?.grandTotal}</p>
+                    <p className="text-ly-24 font-bold">
+                        <Price
+                            amount={orderDetailRes.grandTotal}
+                            currencyCode={orderDetailRes.orderCurrencyCode}
+                        />
+                    </p>
                 </div>
             </div>
         </div>

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

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

+ 9 - 0
src/app/globals.css

@@ -144,6 +144,8 @@
   --color-selected-color: var(--selected-color);
   --color-selected-color-dark: var(--selected-color-dark);
 
+  --color-skeletonbg: var(--skeleton-loading-background-color);
+
 }
 
 
@@ -173,6 +175,8 @@
     --selected-color: #e3ecff;
     --selected-color-dark: #555b69;
 
+    --skeleton-loading-background-color: #f7f7f7;
+
   }
 
 
@@ -488,4 +492,9 @@
       bottom: -16px ;
       top: unset;
   }
+  .product-media-swiper .product-media-swiper-pagination{
+      width: auto;
+      left: auto;
+      bottom:auto;
+  }
 }

+ 18 - 28
src/app/layout.tsx

@@ -12,9 +12,8 @@ import { ErrorBoundary } from "@/components/error/ErrorBoundary";
 import { AddToCartModalWrapper } from "@components/common/AddToCartModal/AddToCartModalWrapper";
 import clsx from "clsx";
 import {KernelProvider} from "@components/theme/ui/kernel/KernelProvider";
-import { GET_CART_ITEM,GET_COUNTRIES,GET_CURRENCIES } from "@/graphql";
+import { GET_COUNTRIES,GET_CURRENCIES } from "@/graphql";
 import {serverGraphqlFetch} from "@utils/bagisto/index";
-import type { GetCartItemData } from "@/types/cart/type";
 import type { Country } from "@/types/checkout/type";
 import type { CurrenciesData } from "@/types/currency/type";
 import {CURRENT_CURRENCY,CURRENT_LOCAL,CURRENT_CHANNEL} from "@/utils/constants";
@@ -92,23 +91,26 @@ export default async function RootLayout({
 
 
   // 获取country list 和currency list
-  const {data: countryResData} = await serverGraphqlFetch<{
-      countries: Country[] 
+  const countriesPromise = serverGraphqlFetch<{
+        countries: Country[];
   }>({
     query: GET_COUNTRIES,
-    cache: 'force-cache',
+    takeAuthorization: false,
+    cache: "force-cache",
     revalidate: 3600,
-    tags: ["config-country-list"]
-  });
-  const {data: currencyResData} = await serverGraphqlFetch<CurrenciesData>({
+    tags: ["store-countries"],
+  }).then(({ data }) => data?.countries ?? []);
+
+  const currenciesPromise = serverGraphqlFetch<CurrenciesData>({
     query: GET_CURRENCIES,
-    cache: 'force-cache',
+    takeAuthorization: false,
+    cache: "force-cache",
     revalidate: 3600,
-    tags: ["config-currency-list"]
+    tags: ["store-currencies"],
+  }).then(({ data }) => {
+      return (data?.currencies?.edges.map((item) => item.node) ?? [])
   });
- 
-  const countries = countryResData?.countries ?? [];
-  const currencyList = currencyResData?.currencies?.edges.map((item) => item.node) ?? [];
+
   const storeConfig = {
     // 系统默认值
     defaultChannel: defaultChannel,
@@ -121,11 +123,7 @@ export default async function RootLayout({
     currentChannel: currentChannel
   };
 
-  const {data: cartDetailsRes} = await serverGraphqlFetch<GetCartItemData>({
-    query: GET_CART_ITEM
-  });
   
-  const cartDetails = cartDetailsRes?.createReadCart?.readCart ?? null;
   return (
     <html lang="en" suppressHydrationWarning>
       <head>
@@ -140,21 +138,13 @@ export default async function RootLayout({
             <PayPalWrapper clientId={paypalClientId}>
               <ConfigProvider
                   config={{
-                    currencies: currencyList,
-                    countries: countries,
+                    countriesPromise,
+                    currenciesPromise,
                     store: storeConfig
                   }}
               >
               {/* Redux Provider 的初始化 state 只在首次挂载时生效,不会因为 Server Component 重新渲染而重新初始化。 */}
-              <ReduxProvider 
-                preloadedState={{
-                  cartDetail:{
-                    cart: cartDetails,
-                    loading: false,
-                    error: null
-                  }
-                }}
-              >
+              <ReduxProvider>
                 <GlobalProviders>
                   {children}
                   <AddToCartModalWrapper />

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

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

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

@@ -13,7 +13,7 @@ import {
     clearAddToCartProduct 
 } from '@/store/slices/addToCartDialogSlice';
 import { useAddProduct } from "@utils/hooks/useAddToCart";
-import {useConfig} from "@/utils/hooks/useConfig";
+import {useStore} from "@/utils/hooks/useConfig";
 import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
 import type { 
     ProductOption, 
@@ -36,8 +36,8 @@ export default function AddToCartModal() {
     const router = useRouter();
     const dispatch = useAppDispatch();
 
-    const {getCurrentCurrencyItem} = useConfig();
-    const currentCurrency = getCurrentCurrencyItem();
+    const {store} = useStore();
+    const currentCurrencyCode = store.currentCurrency;
 
     const {isOpen, product} = useAppSelector((state) => state.addToCartDialog);
 
@@ -326,13 +326,13 @@ export default function AddToCartModal() {
                                     /> */}
                                     <Price 
                                         className="text-ly-24 font-bold"
-                                        amount={String(currentVariantInfo.totalNowPrice)} 
-                                        currencyCode={currentCurrency.code}
+                                        amount={currentVariantInfo.totalNowPrice} 
+                                        currencyCode={currentCurrencyCode}
                                     />
                                     <Price 
                                         className="text-ly-14 text-ly-gray line-through ml-2.5"
-                                        amount={String(currentVariantInfo.totalLinePrice)} 
-                                        currencyCode={currentCurrency.code}
+                                        amount={currentVariantInfo.totalLinePrice} 
+                                        currencyCode={currentCurrencyCode}
                                     />
                                     
                                 </div>

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


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


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

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

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

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

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

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

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

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

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

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

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

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

+ 4 - 72
src/graphql/cart/mutations/CreateRemoveCoupon.ts

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

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

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

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

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

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

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

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

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

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

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

+ 44 - 12
src/providers/ConfigProvider.tsx

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

+ 15 - 23
src/providers/ReduxProvider.tsx

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

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

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

+ 2 - 13
src/store/store.ts

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

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

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

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

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

+ 3 - 0
src/utils/cartDetailTools.ts

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

+ 31 - 7
src/utils/constants.ts

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

+ 6 - 0
src/utils/helper.ts

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

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

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