Browse Source

合并dev-checkoutrebase

fogwind 1 week ago
parent
commit
027097ebbb
100 changed files with 3408 additions and 2567 deletions
  1. 57 5
      README.md
  2. BIN
      public/image/payment/afterpay.png
  3. BIN
      public/image/payment/credit.png
  4. BIN
      public/image/payment/klarna.png
  5. BIN
      public/image/payment/paypal.png
  6. 19 19
      src/app/(checkout)/checkout/_components/AddressResultDisplay.tsx
  7. 8 8
      src/app/(checkout)/checkout/_components/BillingAddressCheckout.tsx
  8. 52 0
      src/app/(checkout)/checkout/_components/CheckoutPlaceOrder.tsx
  9. 15 0
      src/app/(checkout)/checkout/_components/CheckoutShippingMethodLoading.tsx
  10. 298 70
      src/app/(checkout)/checkout/_components/CheckoutWrapper.tsx
  11. 190 0
      src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx
  12. 141 0
      src/app/(checkout)/checkout/_components/ContinueToPay/PaymentMethodContinueTpPay.tsx
  13. 121 0
      src/app/(checkout)/checkout/_components/ContinueToPay/ProductsModalPendingOrder.tsx
  14. 13 0
      src/app/(checkout)/checkout/_components/LoadingPaymentMethod.tsx
  15. 292 0
      src/app/(checkout)/checkout/_components/PaymentButton/PaypalButton.tsx
  16. 139 0
      src/app/(checkout)/checkout/_components/PaymentMethodCheckout.tsx
  17. 17 0
      src/app/(checkout)/checkout/_components/PromotionsDetails.tsx
  18. 6 6
      src/app/(checkout)/checkout/_components/ShippingAddressCheckout.tsx
  19. 142 0
      src/app/(checkout)/checkout/_components/ShippingMethodCheckout.tsx
  20. 90 0
      src/app/(checkout)/checkout/continuetopay/page.tsx
  21. 10 22
      src/app/(checkout)/checkout/page.tsx
  22. 6 6
      src/app/(checkout)/layout.tsx
  23. 1 1
      src/app/(public)/product/_components/ProductAddToCart.tsx
  24. 2 2
      src/app/(public)/product/_components/ProductInformation.tsx
  25. 199 0
      src/app/(public)/success/_components/OrderDetailWrapper.tsx
  26. 124 0
      src/app/(public)/success/_components/SuccessPageSkeleton.tsx
  27. 32 16
      src/app/(public)/success/page.tsx
  28. 14 7
      src/app/api/graphql/route.ts
  29. 45 0
      src/app/globals.css
  30. 39 7
      src/app/layout.tsx
  31. 10 2
      src/components/Portal.tsx
  32. 18 58
      src/components/cart/CartModal.tsx
  33. 0 30
      src/components/cart/OrderDetail.tsx
  34. 1 11
      src/components/catalog/product/ProductCard.tsx
  35. 0 161
      src/components/checkout/checkout-cart/CartItemAccordian.tsx
  36. 0 128
      src/components/checkout/checkout-cart/CheckoutCart.tsx
  37. 0 68
      src/components/checkout/index.tsx
  38. 0 140
      src/components/checkout/stepper/Email.tsx
  39. 0 540
      src/components/checkout/stepper/GuestAddAdressForm.tsx
  40. 0 73
      src/components/checkout/stepper/ProceedToCheckout.tsx
  41. 0 169
      src/components/checkout/stepper/index.tsx
  42. 0 222
      src/components/checkout/stepper/payment/PaymentMethod.tsx
  43. 0 37
      src/components/checkout/stepper/payment/index.tsx
  44. 0 101
      src/components/checkout/stepper/review/OrderReview.tsx
  45. 0 29
      src/components/checkout/stepper/review/index.tsx
  46. 0 219
      src/components/checkout/stepper/shipping/ShippingMethod.tsx
  47. 0 37
      src/components/checkout/stepper/shipping/index.tsx
  48. 0 73
      src/components/checkout/success/EmptyCart.tsx
  49. 0 24
      src/components/checkout/type.ts
  50. 1 1
      src/components/common/AddToCartModal/FooterBtnInAddToCartModal.tsx
  51. 67 0
      src/components/common/ConfirmModal.tsx
  52. 74 0
      src/components/common/CountDown.tsx
  53. 5 4
      src/components/customer/credentials/CredentialModal.tsx
  54. 1 1
      src/components/layout/navbar/MobileMenuTrigger.tsx
  55. 16 0
      src/components/layout/navbar/TitleHeader.tsx
  56. 1 1
      src/components/layout/navbar/index.tsx
  57. 0 76
      src/components/theme/ui/AddToCartButton.tsx
  58. 136 0
      src/components/theme/ui/CommonModal.tsx
  59. 0 3
      src/components/theme/ui/InputText.tsx
  60. 0 3
      src/components/theme/ui/Select.tsx
  61. 15 0
      src/components/theme/ui/kernel/KernelProvider.tsx
  62. 31 0
      src/components/theme/ui/kernel/confirm/api.ts
  63. 46 0
      src/components/theme/ui/kernel/confirm/host.tsx
  64. 17 0
      src/components/theme/ui/kernel/confirm/store.ts
  65. 10 0
      src/components/theme/ui/kernel/confirm/useConfirm.ts
  66. 22 0
      src/components/theme/ui/kernel/core/store.ts
  67. 19 0
      src/components/theme/ui/kernel/loading/api.ts
  68. 20 0
      src/components/theme/ui/kernel/loading/host.tsx
  69. 5 0
      src/components/theme/ui/kernel/loading/store.ts
  70. 10 0
      src/components/theme/ui/kernel/loading/useLoading.ts
  71. 34 17
      src/graphql/cart/mutations/AddProductToCart.ts
  72. 32 12
      src/graphql/cart/mutations/CreateMergeCart.ts
  73. 27 9
      src/graphql/cart/mutations/GetCartItem.ts
  74. 41 25
      src/graphql/cart/mutations/RemoveCartItem.ts
  75. 30 11
      src/graphql/cart/mutations/UpdateCartItems.ts
  76. 8 0
      src/graphql/cart/mutations/readme.md
  77. 22 0
      src/graphql/checkout/mutations/CreateCancelOrder.ts
  78. 31 3
      src/graphql/checkout/mutations/CreateCheckoutOrder.ts
  79. 3 2
      src/graphql/checkout/mutations/CreateCheckoutPaymentMethod.ts
  80. 3 2
      src/graphql/checkout/mutations/CreateCheckoutShippingMethod.ts
  81. 70 0
      src/graphql/checkout/mutations/CreatePaymentCallback.ts
  82. 25 0
      src/graphql/checkout/mutations/CreatePaymentReplay.ts
  83. 67 0
      src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts
  84. 6 2
      src/graphql/checkout/mutations/index.ts
  85. 3 2
      src/graphql/checkout/queries/GetCheckoutPaymentMethods.ts
  86. 5 2
      src/graphql/checkout/queries/GetCheckoutShippingRates.ts
  87. 19 0
      src/graphql/currency/query/GetCurrencies.ts
  88. 3 0
      src/graphql/currency/query/index.ts
  89. 90 0
      src/graphql/customer/query/GetOrderDetails.ts
  90. 3 0
      src/graphql/customer/query/index.ts
  91. 2 0
      src/graphql/index.ts
  92. 28 0
      src/lib/ApolloErrorHandler.ts
  93. 59 9
      src/lib/graphql-fetch.ts
  94. 0 3
      src/providers/GlobalProviders.tsx
  95. 21 3
      src/providers/ReduxProvider.tsx
  96. 14 6
      src/store/hooks.ts
  97. 56 69
      src/store/slices/cart-slice.ts
  98. 76 0
      src/store/slices/currency-slice.ts
  99. 33 10
      src/store/store.ts
  100. 0 0
      src/types/cart/type.ts

+ 57 - 5
README.md

@@ -292,14 +292,40 @@ useEffect 只会在客户端执行,具体是在浏览器绘制后执行,服
   file: "/var/www/html/nshop/packages/Webkul/BagistoApi/src/State/CartTokenProcessor.php"
   line: 913
 
-8. apollo client 升级后需要用新的方法设置返回数据的类型
+8. apollo client 升级后需要用新的方法设置返回数据的类型 -- 已完成
 9. Apollo client的错误处理机制 (src\graphql\catalog\queries\GetFilterAttributes.ts 这个接口可以测试错误)
-10. 产品详情页代码优化 --- 完成
-11. 代码eslint检查修改
-12. 下单成功后需要重新创建购物车token
+10. 产品详情页代码优化 --- 完成
+11. 代码eslint检查修改 -- 已完成
+12. 下单成功后需要重新创建购物车token -- 无需处理
 13. 详情页缓存策略(不要缓存吧)
 14. getSession  过期提示重新登录
-15. checkout 页面 src\components\Portal.tsx (15:33) 会报 document is not defined
+15. checkout 页面 src\components\Portal.tsx (15:33) 会报 document is not defined -- 已解决
+16. redux cartDetail 里的billingAddress 和 shippingAddress 字段改到cart里的billingAddress 和 shippingAddress以及保存完地址后同步到redux
+17. redux 中 cartDetail 异步获取数据 https://chat.deepseek.com/share/76kj30h2cws9jcl4jt  -- 已完成
+18. 需要一个遮挡整个视口的loading组件  -- 已完成
+19. 结账页地址模块还差两个功能:登录用户从地址列表选择地址;未登录用户输入邮箱后判断是否注册过,如果注册过让用户登录;
+20. 登录用户结账页支付失败后要跳转到支付失败页面提醒用户可以再次支付 -- 待定
+21. 游客结账页支付失败或者取消支付,先调paymentCallback 再调cancelOrder。但是不删除cookie里之前的cartToken --- 完成
+22. 顶部header 栏 优化:可设置显示标题还是通用
+23. 个人中心取的是localStorage里的email,即使cookie里的token删除了,localStorage里的email会一直存在, 需要废弃localStorage里的email;
+24. 删除老的checkout页面的组件  -- 已完成
+25. resetGuestToken 有问题,改为退出登录后不调resetGuestToken。
+26. useAddProduct优化
+27. 下单成功页客服工具,分享
+28. cancelOrder接口报错   --  已解决
+29. 购物车数据管理方案 -- 已完成
+30. 货币列表,国家列表等关于商城配置的数据采用全局ConfigProvider管理;
+31. 当前货币等采用redux管理;
+
+## 关于购物车数据管理方案
+ssr + redux:
+第一次渲染时在服务端获取数据存入redux,后续在客户端操作时更新redux。
+购物车页和结账页不从redux中取数据,在服务端重新获取。
+但是在购物车页和结账页对购物车数据做的修改需要同步到redux。
+
+## 关于结账页地址
+如果用户在结账页新建一个地址,这个地址的type是 cart_billing/cart_shipping。
+用户点击支付按钮创建订单,订单创建成功后,根据购物车里的地址信息创建新的billing、shipping地址,地址type是order_billing/order_shipping。
 
 ## GetCartItem,CreateMergeCart 接口报500 (已解决)
 需要前端处理错误,不能直接报错。
@@ -312,3 +338,29 @@ useEffect 只会在客户端执行,具体是在浏览器绘制后执行,服
 
 现在理一下逻辑: 用户登录时会返回一个token,这个token只是后端校验用户信息的token,并不是cartToken。
 也就是说虽然用户登录了,也创建了cartToken,但是购物车没激活,所以GetCartItem接口会报500。只有当用户进行加购操作的时候才会激活购物车。
+
+## 关于paypal
+
+文档: https://docs.paypal.ai/reference/sdk/react
+
+
+## 关于hook
+hook 的定义:
+
+Hook 是 React 在 render 过程中注册状态依赖的函数调用点,而不是函数命名或形式。
+不是“所有函数都是 hook”,而是“只有参与 React render 状态系统的函数才是 hook”。
+
+真正的hook 必须满足下面的条件:
+1. 以 use 开头(约定,不是本质)
+2. 在 render flow 中执行
+3. 调用 React 内部 Hook API
+4. 受 Hook 规则约束(顺序调用)
+
+> 要清楚hook的定义,不然遇到hook被叫烂的情况,你会很迷惑。
+
+## 关于 server actions
+1. https://juejin.cn/post/7540970969751617546
+2. https://developer.aliyun.com/article/1688619
+3. https://zh-hans.react.dev/reference/rsc/server-functions
+
+> useFormState 已弃用,重新命名为 useActionState 

BIN
public/image/payment/afterpay.png


BIN
public/image/payment/credit.png


BIN
public/image/payment/klarna.png


BIN
public/image/payment/paypal.png


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

@@ -1,18 +1,18 @@
 "use client";
 
-import { ShipAddressFormData } from "@/types/checkout/type";
+import {CartAddress} from "@/types/cart/type";
 
-function isAddressAvailable(address: ShipAddressFormData | null): boolean {
+function isAddressAvailable(address: CartAddress | null): boolean {
     if(address && 
-        address.shippingEmail && 
-        address.shippingFirstName && 
-        address.shippingLastName && 
-        address.shippingPhoneNumber && 
-        address.shippingAddress && 
-        address.shippingCountry && 
-        address.shippingState && 
-        address.shippingCity && 
-        address.shippingPostcode
+        address.email && 
+        address.firstName && 
+        address.lastName && 
+        address.phone && 
+        address.address && 
+        address.country && 
+        address.state && 
+        address.city && 
+        address.postcode
     ) {
         return true;
     }
@@ -23,7 +23,7 @@ export default function AddressResultDisplay({
     shippingAddress,
     onAddressModalOpenClick,
 } : {
-    shippingAddress: ShipAddressFormData | null;
+    shippingAddress: CartAddress | null;
     onAddressModalOpenClick: () => void;
 }) {
     const addressIsAvailable: boolean = isAddressAvailable(shippingAddress);
@@ -48,9 +48,9 @@ export default function AddressResultDisplay({
             >
                 <div className="border-b-1 flex h-11.25 items-center justify-between">
                     <p className="text-ly-13 font-medium">
-                        {shippingAddress.shippingFirstName} {shippingAddress.shippingLastName}
+                        {shippingAddress.firstName} {shippingAddress.lastName}
                         <span className="border-r border-ly-gray h-3.5 mx-2"></span> 
-                        {shippingAddress.shippingPhoneNumber}
+                        {shippingAddress.phone}
                     </p>
                     <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"><rect x="0" y="0" width="16" height="16"   fill="#FFFFFF" fillOpacity="0"></rect><path    stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="square"  d="M6.32031 4.27051L10.4202 8.37036L6.32031 12.4702"></path></svg>
                 </div>
@@ -61,11 +61,11 @@ export default function AddressResultDisplay({
                         <path d="M11.9983 11.3653C13.1666 11.3653 14.1138 10.4181 14.1138 9.24979C14.1138 8.08144 13.1666 7.13428 11.9983 7.13428C10.83 7.13428 9.88281 8.08144 9.88281 9.24979C9.88281 10.4181 10.83 11.3653 11.9983 11.3653Z" stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round"  ></path>
                     </svg>
                     <span className="text-ly-12 ml-2.5">
-                        {shippingAddress.shippingAddress}, 
-                        {shippingAddress.shippingCity}, 
-                        {shippingAddress.shippingState}, 
-                        {shippingAddress.shippingPostcode}, 
-                        {shippingAddress.shippingCountry}
+                        {shippingAddress.address}, 
+                        {shippingAddress.city}, 
+                        {shippingAddress.state}, 
+                        {shippingAddress.postcode}, 
+                        {shippingAddress.country}
                     </span>
                 </div>
             </div>       

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

@@ -151,8 +151,8 @@ export default function BillingAddressCheckout ({
         <section className="w-full mt-4"> 
             <div className="box-border w-full p-3 bg-ly-lightgray">
                 <div className="flex justify-between items-center">
-                    <span className="text-ly-12 flex-initial">Billing Address</span>
-                    <label className="group relative w-9 h-5.5 flex-initial">
+                    <span className="text-ly-12 flex-none">Billing Address</span>
+                    <label className="group relative w-9 h-5.5 flex-none">
                         <input className="absolute w-full h-full top-0 left-0 opacity-0 z-10"
                             type="checkbox"
                             {...register("billingSameAsShipping")}
@@ -192,7 +192,7 @@ export default function BillingAddressCheckout ({
                         Name *
                     </label>
                     <div className="w-full flex justify-between">
-                        <div className="w-41.25 flex-initial">
+                        <div className="w-41.25 flex-none">
                             <InputText
                                 type="text"
                                 placeholder="First name"
@@ -206,7 +206,7 @@ export default function BillingAddressCheckout ({
                                 error={errors.billingFirstName && errors.billingFirstName.message as string}
                             />
                         </div> 
-                        <div className="w-41.25 flex-initial">
+                        <div className="w-41.25 flex-none">
                             <InputText
                                 type="text"
                                 placeholder="Last name"
@@ -240,7 +240,7 @@ export default function BillingAddressCheckout ({
                     <label className="text-ly-12 block mb-4 font-semibold">Country and State/Province  *</label>
                     <div className="w-full">
                         <div className="w-full flex justify-between">
-                            <div className="w-41.25 flex-initial">
+                            <div className="w-41.25 flex-none">
                                 <Select placeholder="Country/Region"
                                     {...register("billingCountry", {
                                         required: "Country/Region field is required",
@@ -259,7 +259,7 @@ export default function BillingAddressCheckout ({
                                     error={errors.billingCountry && errors.billingCountry.message as string}
                                 /> 
                             </div>
-                            <div className="w-41.25 flex-initial relative">
+                            <div className="w-41.25 flex-none relative">
                                 {statesLoading && <LoadingSpinner className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />}
                                 {statesOptions.length > 0 ?
                                 (
@@ -286,7 +286,7 @@ export default function BillingAddressCheckout ({
                             
                         </div>
                         <div className="w-full flex justify-between mt-4">
-                            <div className="w-41.25 flex-initial">
+                            <div className="w-41.25 flex-none">
                                 <InputText
                                     type="text"
                                     placeholder="Zip/Postal Code"
@@ -300,7 +300,7 @@ export default function BillingAddressCheckout ({
                                     error={errors.billingPostcode&& errors.billingPostcode.message as string}
                                 />
                             </div>
-                            <div className="w-41.25 flex-initial">
+                            <div className="w-41.25 flex-none">
                                 <InputText
                                     type="text"
                                     placeholder="City"

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

@@ -0,0 +1,52 @@
+"use client";
+
+import { useState } from "react";
+import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
+import PaypalButton from "./PaymentButton/PaypalButton";
+
+export default function CheckoutPlaceOrder({
+    paymentMethod,
+    clickPlaceOrder, // 校验运输方式,地址 
+}: {
+    paymentMethod: string;
+    clickPlaceOrder: () => Promise<boolean>;
+}) { 
+    const { createOrder } = usePlaceOrder(); 
+    
+    const handleCreateOrder = async () => {
+        const res = await createOrder();
+        console.log("createOrder res ---- ", res);
+        if(!res.error && res.data) {
+            return {
+                error: res.error,
+                msg: '',
+                orderId: res.data.gatewayOrderId,
+                webOrderId: res.data.orderId
+            };
+        } else {
+            return {
+                error: res.error,
+                msg: res.msg,
+                orderId: res.data?.gatewayOrderId ?? '',
+                webOrderId: res.data?.orderId ?? ''
+            };
+        }
+    };
+
+    return (
+        <>
+            <div className="relative w-full">
+                {paymentMethod === 'paypal_smart_button' && 
+                    <PaypalButton
+                        createOrder={handleCreateOrder}
+                        onPaymentClick={clickPlaceOrder}
+                    />
+                }
+            </div>
+           
+            {/* <button className="flex items-center justify-center w-full h-12 bg-ly-green text-white rounded-3xl text-ly-16 font-bold">
+                Place Order
+            </button> */}
+        </>
+    );
+}

+ 15 - 0
src/app/(checkout)/checkout/_components/CheckoutShippingMethodLoading.tsx

@@ -0,0 +1,15 @@
+export default function CheckoutShippingMethodLoading() { 
+    return (
+        <div className="mt-3 w-full animate-pulse px-0">
+            <div className="group block relative box-border p-3 border-1 rounded-sm">
+                <p className="text-ly-13">
+                    <span className="text-ly-13 text-gray-300 bg-gray-300 font-bold mr-1">FREE</span>
+                    <span className="text-gray-300 bg-gray-300">USPS/FedEx/DHL/UPS</span>
+                </p>
+                <p className="text-ly-12 text-gray-300 bg-gray-300 w-40 mt-1">(2-5 business days)</p>
+
+                <div className="absolute top-1/2 -translate-y-1/2 right-3 bg-gray-300 w-4 h-4"></div>
+            </div>
+        </div>
+    );
+}

+ 298 - 70
src/app/(checkout)/checkout/_components/CheckoutWrapper.tsx

@@ -1,30 +1,44 @@
 "use client";
 
-import {useState, useEffect} from "react";
+import {useState, useEffect, useRef} from "react";
 
 import { LoadingSpinner } from "@components/common/LoadingSpinner";
-// import { useForm, SubmitHandler } from "react-hook-form"
-import { useForm, FormProvider } from "react-hook-form"
+import CheckoutShippingMethodLoading from "./CheckoutShippingMethodLoading";
+import { useForm, FormProvider } from "react-hook-form";
 
 import { 
     Country,
     ShipAddressFormData,
     BillAddressFormData,
     FullAddressFormData,
-    CreateCheckoutAddressVariables
+    CreateCheckoutAddressVariables,
 } from "@/types/checkout/type";
 import AddressResultDisplay from "./AddressResultDisplay";
 import AddressResultDisplayLoading from "./AddressResultDisplayLoading";
-import CheckoutAddressModal from "./CheckoutAddressModal";
 import ShippingAddressCheckout from "./ShippingAddressCheckout";
 import BillingAddressCheckout from "./BillingAddressCheckout";
-// import CheckoutShippingMethod from "./CheckoutShippingMethod";
+import {ShippingMethodCheckout,RefShippingMethodsHandle} from "./ShippingMethodCheckout";
 import { useCustomToast } from "@/utils/hooks/useToast";
 import {useCheckoutAddress} from "@/utils/hooks/useCheckoutAddress"
+import {useCheckoutPaymentMethod} from  "@/utils/hooks/useCheckoutPaymentMethod";
+import {useCheckoutShippingMethod} from  "@/utils/hooks/useCheckoutShippingMethod";
+import { useAppDispatch,useAppStore  } from "@/store/hooks";
+import { updateCart } from "@/store/slices/cart-slice";
+import { useCartDetail } from "@utils/hooks/useCartDetail";
+import {PaymentMethodCheckout,RefPaymentMethodsHandle} from "./PaymentMethodCheckout";
+import LoadingPaymentMethod from "./LoadingPaymentMethod";
+import CheckoutPlaceOrder from "./CheckoutPlaceOrder";
+import CommonModal from "@/components/theme/ui/CommonModal";
+import {useSaveCheckoutCart} from "@/utils/hooks/useSaveCheckoutCart";
+import { formatCartDetail, shippingAddressToCartAddress, billingAddressToCartAddress } from "@/utils/cartDetailTools";
+import { overlayLoading } from "@/components/theme/ui/kernel/loading/api";
+import { CartDetail } from "@/types/cart/type";
 /***
  * 不用useForShipping字段了
  * 以shipping address 为准,根据shippingaddress 设置billing address
  * 保存完地址之后要重新获取运输方式和支付方式
+ * 变更运输方式后重新获取支付方式
+ * @todo 购物车为空 跳转到空购物车页面
  */
 
 /**
@@ -81,52 +95,81 @@ function generateSaveCheckoutAddressParam(formData: FullAddressFormData):CreateC
     };
     
 }
-/**
- * 对比shipping address与billing address的数据是否完全相同
- */
-/*function addressIsSame(billAddress: BillAddressFormData | null, shipAddress: ShipAddressFormData | null) {
-    if(billAddress === null && shipAddress === null) {
-        return true;
-    } else if(billAddress !== null && shipAddress !== null) {
-        return (
-           billAddress.billingEmail === shipAddress.shippingEmail &&
-           billAddress.billingFirstName === shipAddress.shippingFirstName &&
-           billAddress.billingLastName === shipAddress.shippingLastName &&
-           billAddress.billingCompanyName === shipAddress.shippingCompanyName &&
-           billAddress.billingAddress === shipAddress.shippingAddress &&
-           billAddress.billingCity === shipAddress.shippingCity &&
-           billAddress.billingState === shipAddress.shippingState &&
-           billAddress.billingCountry === shipAddress.shippingCountry &&
-           billAddress.billingPostcode === shipAddress.shippingPostcode &&
-           billAddress.billingPhoneNumber === shipAddress.shippingPhoneNumber 
-        );
-    } else {
-        return false;
-    }
-}*/
+
 export default function CheckoutWrapper({
     loginEmail,
-    countries
+    countries,
 }: {
     loginEmail: string;
-    countries: Country[]
+    countries: Country[],
 }) {
+
+    const dispatch = useAppDispatch();
+    const { cartData } = useCartDetail();
+    const {saveCheckoutCart} = useSaveCheckoutCart();
+    
+    const {
+        data: shippingMethodDatas,
+        loading: shippingMethodLoading,
+        error: shippingMethodError,
+        getShippingMethod,
+    } = useCheckoutShippingMethod();
+
+
+    const selectedPaymentMethod = cartData?.paymentMethod ?? 'paypal_smart_button';
+   
+    const { 
+        data: paymentMethodDatas,
+        loading: paymentMethodLoading,
+        error: paymentMethodError,
+        getPaymentMethod, 
+    } = useCheckoutPaymentMethod();
+
     const { showToast } = useCustomToast();
     const { getCheckoutAddress, saveCheckoutAddress } = useCheckoutAddress(loginEmail);
 
+    const methodShiippingRef = useRef<RefShippingMethodsHandle>(null);
+    const methodPaymentRef = useRef<RefPaymentMethodsHandle>(null);
+
 
     // useForm() 只会在组件初始化时读取一次 defaultValues
     const addressForm = useForm<FullAddressFormData>({
         mode: "onBlur",          // 或 "onChange"
-        reValidateMode: "onChange", 
+        reValidateMode: "onChange",
+        defaultValues: {
+            "shippingAddressId": "",
+            "shippingEmail": loginEmail,
+            "shippingFirstName": "",
+            "shippingLastName": "jack",
+            "shippingCompanyName": "",
+            "shippingAddress": "",
+            "shippingCountry": "US",
+            "shippingState": "",
+            "shippingCity": "",
+            "shippingPostcode": "",
+            "shippingPhoneNumber": "",
+            "billingAddressId": "",
+            "billingEmail": "",
+            "billingFirstName": "",
+            "billingLastName": "",
+            "billingCompanyName": "",
+            "billingAddress": "",
+            "billingCountry": "US",
+            "billingState": "",
+            "billingCity": "",
+            "billingPostcode": "",
+            "billingPhoneNumber": "",
+            "billingSameAsShipping": true
+        } 
     });
 
     const [addressModalOpen, setAddressModalOpen] = useState(false);
-    const [loadingData, setLoadingData] = useState(true);
-    const [serverShippingAddress, setServerShippingAddress] = useState<ShipAddressFormData | null>(null);
-    const [serverBillingAddress, setServerBillingAddress] = useState<BillAddressFormData | null>(null);
+    const [loadingAddress, setLoadingAddress] = useState(true); // 初始加载地址数据时的loading状态
+
+    const serverShippingAddress = useRef<ShipAddressFormData | null>(null);
+    const serverBillingAddress = useRef<BillAddressFormData | null>(null);
 
-    const [addressSaving, setAddressSaving] = useState(false);
+    const [addressSaving, setAddressSaving] = useState(false); // 保存地址时的loading状态
 
     // 开发环境会执行两次 https://zh-hans.react.dev/learn/synchronizing-with-effects#how-to-handle-the-effect-firing-twice-in-development
     useEffect(() => { 
@@ -135,15 +178,13 @@ export default function CheckoutWrapper({
                 ...shippingAddress,
                 ...billingAddress,
             });
-            setServerShippingAddress({
-                ...shippingAddress
-            });
-            setServerBillingAddress({
-                ...billingAddress
-            });
-            setLoadingData(false);
+
+            serverShippingAddress.current = shippingAddress;
+            serverBillingAddress.current = billingAddress;
+         
+            setLoadingAddress(false);
         }, ()=> {
-            setLoadingData(false);
+            setLoadingAddress(false);
         });
     }, []);
 
@@ -151,10 +192,10 @@ export default function CheckoutWrapper({
     const openAddressModal = () => {
         setAddressModalOpen(true);
 
-        if(serverShippingAddress && serverBillingAddress) {
+        if(serverShippingAddress.current && serverBillingAddress.current) {
             addressForm.reset({
-                ...serverShippingAddress,
-                ...serverBillingAddress,
+                ...serverShippingAddress.current,
+                ...serverBillingAddress.current,
             });
         }
         
@@ -166,14 +207,20 @@ export default function CheckoutWrapper({
     };
     const addressFormOnSubmit = async (formData: FullAddressFormData) => { 
         // console.log('addressFormOnSubmit ---- ',formData); return;
-        // 保存完地址之后还要重新请求地址,把保存后的地址id同步到表单里;还需要获取运输方式
-
+        if(addressSaving) {
+            return;
+        }
         const saveAddressParam = generateSaveCheckoutAddressParam(formData);
         setAddressSaving(true);
         try {
 
             const res = await saveCheckoutAddress(saveAddressParam);
             console.log('CREATE_CHECKOUT_ADDRESS res ====== ',res);
+            // 地址保存成功后重新获取运输方式和支付方式 
+            getShippingMethod();
+            getPaymentMethod();
+            
+            // 保存完地址之后还要重新请求地址,把保存后的地址id同步到表单里;
             const queryAddressRes = await getCheckoutAddress();
             if(queryAddressRes !== null) {
                 addressForm.resetField('shippingAddressId',{
@@ -182,12 +229,15 @@ export default function CheckoutWrapper({
                 addressForm.resetField('billingAddressId',{
                     defaultValue: queryAddressRes.billingAddress.billingAddressId
                 });
-                setServerShippingAddress({
-                    ...queryAddressRes.shippingAddress
-                });
-                setServerBillingAddress({
-                    ...queryAddressRes.billingAddress
-                });
+
+                serverShippingAddress.current = queryAddressRes.shippingAddress;
+                serverBillingAddress.current = queryAddressRes.billingAddress;
+                // 同步地址到购物车详情
+                dispatch(updateCart({
+                    billingAddress: billingAddressToCartAddress(queryAddressRes.billingAddress),
+                    shippingAddress: shippingAddressToCartAddress(queryAddressRes.shippingAddress)
+                }));
+
             }
             setAddressModalOpen(false);
         } catch(err) {
@@ -200,6 +250,130 @@ export default function CheckoutWrapper({
         }
         
     };
+
+
+    const handlePaymentMethodChange =  async (method: string,title: string) => { 
+        let shippingMethod = '-1';
+        if(methodShiippingRef.current) {
+            shippingMethod = methodShiippingRef.current.getSelectShipMethod();
+        }
+        overlayLoading.start();
+        const saveRes = await saveCheckoutCart({ 
+            shippingMethod: shippingMethod,
+            paymentMethod: method,
+            couponCode: '-1'
+        });
+        overlayLoading.stop();
+        if(!saveRes.error) {
+            if(saveRes.data) {
+                const newCartDetail = formatCartDetail(saveRes.data);
+                dispatch(updateCart(newCartDetail));
+            }
+        } else {
+            showToast(saveRes.msg, 'danger');
+        }
+
+        /*
+        const res = await savePaymentMethod({ paymentMethod: method });
+        console.log('savePaymentMethod res ====== ',res);
+        if(!res.error) {
+            // 更新paymentMethod paymentMethodTitle
+            dispatch(updateCart({
+                paymentMethod: method,
+                paymentMethodTitle: title
+            }));
+        } else {
+            showToast(res.msg, 'danger');
+        }
+        */
+    };
+    const handleShippingMethodChange =  async (method: string) => { 
+        overlayLoading.start();
+        const saveRes = await saveCheckoutCart({ 
+            shippingMethod: method,
+            paymentMethod: '-1',
+            couponCode: '-1'
+        });
+        overlayLoading.stop();
+        if(!saveRes.error) {
+            if(saveRes.data) {
+                const newCartDetail = formatCartDetail(saveRes.data);
+                dispatch(updateCart(newCartDetail));
+            }
+            // 保存成功之后需要重新获取支付方式
+            getPaymentMethod();
+        } else {
+            showToast(saveRes.msg, 'danger');
+        }
+        /*
+        const res = await saveShippingMethod({ shippingMethod: method });
+        if(!res.error) {
+            // 保存成功之后需要重新获取购物车详情和支付方式
+            getCartDetail();
+            getPaymentMethod();
+        } else {
+            showToast(res.msg, 'danger');
+        }*/
+    };
+
+    // 下单
+    // 1. 校验地址 运输方式,支付方式;
+    // 2. 根据购物车详情判断是否需要传下单参数: 地址,运输方式,支付方式
+    const handlePlaceOrder = async () => { 
+        // 校验地址
+        const addressValid = await addressForm.trigger();
+
+        if(!addressValid) {
+            showToast('Please fill in the address information correctly.', 'danger');
+            return false;
+        }
+        if(methodShiippingRef.current) {
+            const shippingMethodValid = await methodShiippingRef.current.validateShipMethod();
+            if(!shippingMethodValid) {
+                showToast('Please select a shipping method.', 'danger');
+                return false;
+            }
+        } else {
+            showToast('Please wait for shipping method loading.', 'danger');
+            return false;
+        }
+
+        if(methodPaymentRef.current) {
+            const paymentMethodValid = await methodPaymentRef.current.validatePaymentMethod();
+            if(!paymentMethodValid) {
+                showToast('Please select a payment method.', 'danger');
+                return false;
+            }
+        } else {
+            showToast('Please wait for payment method loading.', 'danger');
+            return false;
+        }
+        if(!cartData) {
+            showToast('Please add product to your cart.', 'danger');
+            return false;
+        }
+        
+        //如果购物车详情没有保存过支付方式,则需要保存支付方式
+        if(cartData?.paymentMethod === null) {
+            const pmethod = methodPaymentRef?.current.getSelectPaymentMethod();
+            const saveRes = await saveCheckoutCart({ 
+                shippingMethod: '-1',
+                paymentMethod: pmethod,
+                couponCode: '-1'
+            });
+            if(!saveRes.error) {
+                if(saveRes.data) {
+                    const newCartDetail = formatCartDetail(saveRes.data);
+                    dispatch(updateCart(newCartDetail));
+                }
+            }
+        }
+
+        return true;
+    };
+    
+
+
     // function tt() {
     //     const arr = [
     //         ['US','3803800217'], 
@@ -223,36 +397,90 @@ export default function CheckoutWrapper({
         <section className="w-full">
             <div>
             {/* <button className="w-25 h-8 bg-amber-500 flex items-center justify-center" onClick={tt}>test</button> */}
-                    {loadingData ? <AddressResultDisplayLoading /> 
-                    :<AddressResultDisplay shippingAddress={serverShippingAddress} 
-                        onAddressModalOpenClick={openAddressModal}
-                    />}
-                    <CheckoutAddressModal 
+                    {loadingAddress ? 
+                        <AddressResultDisplayLoading /> 
+                    :
+                        <AddressResultDisplay shippingAddress={cartData?.shippingAddress ?? null} 
+                            onAddressModalOpenClick={openAddressModal}
+                        />
+                    }
+                    <CommonModal
                         isOpen={addressModalOpen}
                         onClose={closeAddressModal}
+                        header={
+                            <div className="w-full box-border p-3.75 h-14.5 flex justify-between items-center">
+                                <span>Shipping Address</span>
+                                <button className="w-6 h-6" onClick={() => closeAddressModal(false)}>
+                                    <svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon" className="h-6 transition-all ease-in-out hover:scale-110"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12"></path></svg>
+                                </button>
+                            </div>
+                        }
                         body={
-                            <>
-                                <FormProvider {...addressForm}>
-                                    <form>
-                                        <ShippingAddressCheckout countries={countries} />
-                                        <BillingAddressCheckout countries={countries} />
-                                    </form>
-                                </FormProvider>
-                            </>
+                            <div className="box-border w-full h-full p-3.75">
+                                <p className="text-ly-12 leading-ly-20">
+                                    Select a billing address from your address book or enter a new address.
+                                </p>
+                                <div>
+                                    <FormProvider {...addressForm}>
+                                        <form>
+                                            <ShippingAddressCheckout countries={countries} />
+                                            <BillingAddressCheckout countries={countries} />
+                                        </form>
+                                    </FormProvider>
+                                </div>
+                            </div>
                         }
                         footer={
+                            <div className="box-border w-full p-3.75">
                             <button className="block flex justify-center items-center w-full h-12 bg-ly-green rounded-3xl text-white text-ly-16 font-bold"
                                 onClick={addressForm.handleSubmit(addressFormOnSubmit)}
                             >
                                 {addressSaving ? <LoadingSpinner /> : 'Save'}
                             </button>
+                            </div>
                         }
                     />
             </div>
             <div className="mt-6 box-border px-4">
-                {/* <CheckoutShippingMethod /> */}
+                <h3 className="text-ly-24 font-medium">Shipping Method</h3>
+                {shippingMethodLoading ?
+                
+                    <CheckoutShippingMethodLoading /> 
+                :
+                    <ShippingMethodCheckout 
+                        ref={methodShiippingRef}  
+                        shipMethods={shippingMethodDatas}
+                        errorMsg={shippingMethodError}  
+                        onShipMethodChange={handleShippingMethodChange}  
+                    />
+                }
+
+            </div>
+
+            <div className="mt-6 box-border px-4">
+                <h3 className="text-ly-24 font-medium">Payment Method</h3>
+                { paymentMethodLoading ?
+                    <LoadingPaymentMethod />
+                :   
+                    <PaymentMethodCheckout 
+                        ref={methodPaymentRef}
+                        errorMsg={paymentMethodError}
+                        paymentMethods={paymentMethodDatas} 
+                        onPaymentMethodChange={handlePaymentMethodChange}
+                    />
+                }
+                
             </div>
-           
+            <div className="mt-3 box-border px-4">
+                <p className="text-ly-12 text-[#666666] leading-ly-20">By providing your information, you agree to Wiggins'sPrivacy Policyand Terms of Use.</p>
+                <div className="mt-6 w-full">
+                    <CheckoutPlaceOrder 
+                        paymentMethod={selectedPaymentMethod}
+                        clickPlaceOrder={handlePlaceOrder}
+                    />
+                </div>
+            </div>
+
         </section> 
         
     );

File diff suppressed because it is too large
+ 190 - 0
src/app/(checkout)/checkout/_components/ContinueToPay/ContinueToPayOrderInfo.tsx


File diff suppressed because it is too large
+ 141 - 0
src/app/(checkout)/checkout/_components/ContinueToPay/PaymentMethodContinueTpPay.tsx


+ 121 - 0
src/app/(checkout)/checkout/_components/ContinueToPay/ProductsModalPendingOrder.tsx

@@ -0,0 +1,121 @@
+"use client";
+
+import { useState } from "react";
+import Image from "next/image";
+import CommonModal from "@/components/theme/ui/CommonModal";
+import {ProductsInOrderDetails, ProductItemAdditional} from "@/types/customer/type";
+
+
+// 根据products 计算总价和总个数
+function caclculateTotal(products: ProductsInOrderDetails[]) {
+    let total = 0;
+    let totalItems = 0;
+
+    products.forEach((product) => {
+        total += product.total * 100;
+        totalItems += product.qtyOrdered;
+    });
+    let ss = String(total);
+    if(ss.length >= 3) {
+        ss = ss.slice(0, ss.length - 2) + "." + ss.slice(ss.length - 2);
+    } else if(ss.length === 2) {
+        ss = "0." + ss;
+    } else {
+        ss = "0.0" + ss;
+    }
+
+    return { 
+        subTotal: ss, 
+        totalItems: totalItems > 1 ? String(totalItems) + " Items" : String(totalItems) + " Item"
+    };
+}
+
+function getProductAdditionalInfo(productItem: ProductItemAdditional) { 
+    const attributeKeys = Object.keys(productItem.attributes);
+    let res = '';
+    attributeKeys.forEach(key => {
+      const attribute = productItem.attributes[key];
+      if (attribute) {
+        // option_label value_label
+
+        if(res) {
+            res = res + ', ' + attribute.option_label + ': ' + attribute.value_label;
+        } else {
+            res = attribute.option_label + ': ' + attribute.value_label;
+        }
+      }
+    });
+    return res;
+}
+
+export default function ProductsModalPendingOrder({
+    isOpen,
+    products,
+    currencySymbol,
+    onClose,
+}: {
+    isOpen: boolean;
+    products: ProductsInOrderDetails[],
+    currencySymbol: string;
+    onClose: () => void;
+}) {
+
+    const { subTotal, totalItems } = caclculateTotal(products);
+    
+
+    const closeModal = () => {
+        onClose();
+    };
+  return (
+    <CommonModal
+      isOpen={isOpen}
+      onClose={() => closeModal()}
+      header={
+        <div className="relative w-full box-border p-4">
+            <div className="flex justify-between items-center">
+                <span className="text-ly-16 flex-none">Order Summary</span>
+                <button className="flex-none w-6 h-6" onClick={() => closeModal()}>
+                    <svg className="block w-full h-full" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" strokeWidth="1.5" stroke="currentColor" aria-hidden="true" data-slot="icon"><path strokeLinecap="round" strokeLinejoin="round" d="M6 18 18 6M6 6l12 12"></path></svg>
+                </button>
+            </div>
+            
+            <div className="mt-6 flex justify-between items-center">
+                <span className="text-ly-12">{totalItems}</span>
+                <span className="text-ly-14 font-bold">{currencySymbol + subTotal}</span>
+            </div>
+        </div>
+      }
+      body={
+        <div className="w-full px-4 pb-6">
+            {products.map((product) => {
+                return (
+                    <div key={product.id} className="flex justify-between w-full box-border pb-4 border-b-1 border-[#f0f0f0] mt-4 last:border-0 last:pb-0 last:mt-0">
+                        <div className="relative flex-none w-33 h-44 mr-2">
+                            <Image fill={true} src="https://cdn.asteriahair.com/media/catalog/product/6/_/6_3_2.png" alt={product.name} />
+                        </div>
+                        <div className="flex-auto flex flex-col justify-between ">
+                            <div>
+                                <p className="text-ly-12 leading-ly-20 line-clamp-2 text-ly-placeholder">
+                                    {product.name}
+                                </p>
+                                <p className="text-ly-12 leading-ly-20 mt-3">
+                                    {getProductAdditionalInfo(JSON.parse(product.additional))}
+                                </p>
+                            </div>
+                            <div className="flex justify-between items-center">
+                                <span className="text-ly-14 font-bold">
+                                    {currencySymbol + product.price}
+                                </span>
+                                <span className="text-ly-12">X {product.qtyOrdered}</span>
+                            </div>
+                        </div>
+                    </div>
+                )
+            })}
+            
+        </div>
+      }
+      
+    />
+  )
+}

+ 13 - 0
src/app/(checkout)/checkout/_components/LoadingPaymentMethod.tsx

@@ -0,0 +1,13 @@
+export default function LoadingPaymentMethod() { 
+    return (
+        <div className="mt-3 w-full animate-pulse px-0">
+            <div className="group box-border flex items-center relative mt-3 p-3 rounded-sm">
+                <p className="pr-2 flex-none h-8 w-12 bg-gray-300"></p>
+                <p className="text-ly-12 text-gray-300 bg-gray-300 ml-1">Paypay Smart Button</p>
+  
+
+                <div className="absolute top-1/2 -translate-y-1/2 right-3 bg-gray-300 w-4 h-4"></div>
+            </div>
+        </div>
+    );
+}

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

@@ -0,0 +1,292 @@
+"use client";
+
+
+import {useRef} from "react";
+import { redirect, RedirectType } from 'next/navigation';
+import { usePlaceOrder } from "@/utils/hooks/usePlaceOrder";
+import { getCookie } from "@/utils/cookie-tools";
+import { IS_GUEST } from "@/utils/constants";
+import {
+  usePayPal,
+  useEligibleMethods,
+  INSTANCE_LOADING_STATE,
+  usePayPalOneTimePaymentSession,
+  usePayPalGuestPaymentSession,
+  usePayLaterOneTimePaymentSession,
+  type OnApproveDataOneTimePayments,
+  type OnErrorData,
+  type OnCompleteData,
+  type OnCancelDataOneTimePayments,
+} 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 { useAppDispatch } from "@/store/hooks";
+import { clearCart } from "@/store/slices/cart-slice";
+export default function PaypalButton({
+    createOrder,
+    onPaymentClick,
+    isRePay
+}:{
+  createOrder: () => Promise<{orderId: string;webOrderId: string;error: boolean;msg: string;}>;
+  onPaymentClick: () => Promise<boolean>;
+  isRePay?: boolean;
+}) { 
+    const dispatch = useAppDispatch();
+    const isGuest = getCookie(IS_GUEST) !== "false";// 'true' 是游客
+    const {createPaymentCallback,cancelOrder} = usePlaceOrder();
+
+    const gatewayOrderIdRef = useRef("");
+    const webOrderIdRef = useRef("");
+    // paypal sdk 是否加载完成
+    const { loadingStatus } = usePayPal();
+
+    // Fetch eligibility(资格) for one-time payment flow
+    const {
+        error: eligibilityError,
+        eligiblePaymentMethods,
+        isLoading: isEligibilityLoading,
+    } = useEligibleMethods({
+        payload: {
+        currencyCode: "USD",
+        paymentFlow: "ONE_TIME_PAYMENT",
+        },
+    });
+    const isLoading = loadingStatus === INSTANCE_LOADING_STATE.PENDING;
+    const isPayLaterEligible =
+        !isEligibilityLoading && eligiblePaymentMethods?.isEligible("paylater");
+    //   const isCreditEligible =
+    //     !isEligibilityLoading && eligiblePaymentMethods?.isEligible("credit");
+
+    const handlerPaypalClick = async (buttonHandlerClick: () => Promise<void | {
+        redirectURL?: string | undefined;
+    }>) => {
+        overlayLoading.start();
+        const vaild = await onPaymentClick();
+        console.log('789----', vaild);
+        if(vaild) {
+            const res = await createOrder();
+            if(res.error) {
+                overlayLoading.stop();
+                confirmDialog({
+                    title: "Warning",
+                    content: res.msg + " Create order failed. Please try again.",
+                    noCancel: true,
+                }).then(() => {});
+            } else {
+                //创建订单成功后清空redux 购物车
+                dispatch(clearCart());
+                gatewayOrderIdRef.current = res.orderId;
+                webOrderIdRef.current = res.webOrderId;
+                return buttonHandlerClick();
+            }
+            
+        } else {
+            overlayLoading.stop();
+        }
+    }
+
+    // paypal支付回调配置
+    const paypalOnHandlerConfig = {
+        createOrder: async () => {
+            // const res = await createOrder();
+            // gatewayOrderIdRef.current = res.orderId;
+            // webOrderIdRef.current = res.webOrderId;
+            const res = {
+                orderId: gatewayOrderIdRef.current, 
+                webOrderId: webOrderIdRef.current
+            };
+
+            return res;
+        },
+        
+        onApprove: async (data: OnApproveDataOneTimePayments) => {
+            console.log("Payment approved:", data);
+            const resCallback = await createPaymentCallback({
+                orderId: Number(webOrderIdRef.current),
+                gatewayOrderId: data.orderId || gatewayOrderIdRef.current,
+                status: 'success',
+            });
+            if(!resCallback.error) {
+                // 支付成功,跳转到成功落地页 
+                confirmDialog({
+                    title: "Payment Success",
+                    content: "Your payment was successful. Will redirect to success page.",
+                    noCancel: true,
+                }).then(() => {
+                    redirect('/success?orderid=' + webOrderIdRef.current, RedirectType.replace);
+                });
+            } else {
+                // callback 失败,提示错误
+                confirmDialog({
+                    title: "Payment failed",
+                    content: resCallback.msg + " Please contact customer service." + " OrderId: "+ webOrderIdRef.current,
+                    noCancel: true,
+                }).then(() => { 
+                    redirect('/', RedirectType.replace);
+                });
+                
+            }
+
+        },
+
+        onCancel: async (data: OnCancelDataOneTimePayments) => {
+            /**
+             * {oederId: paypalOrderId}
+             */
+            console.log("Payment cancelled:", data);
+            await createPaymentCallback({
+                orderId: Number(webOrderIdRef.current),
+                gatewayOrderId: data.orderId || gatewayOrderIdRef.current,
+                status: 'cancel',
+            });
+            if(!isRePay) {
+                if(isGuest) {
+                    
+                    await cancelOrder({
+                        orderId: Number(webOrderIdRef.current),
+                    });
+                    // deleteCookie(GUEST_CART_TOKEN);
+                    // deleteCookie(GUEST_CART_ID);
+                    confirmDialog({
+                        title: "Order Cancelled",
+                        content: "Your order has been cancelled. Will redirect to home page.",
+                        noCancel: true,
+                    }).then(() => { 
+                        redirect('/' , RedirectType.replace);
+                    });
+                    
+                } else {
+                    // 登录用户 跳转二次支付页面 提醒用户可以二次支付
+                    confirmDialog({
+                        title: "Payment cancelled",
+                        content: "Will redirect to unpaied page.",
+                        noCancel: true,
+                    }).then(() => { 
+                        redirect('/checkout/continuetopay?orderid=' + webOrderIdRef.current, RedirectType.replace);
+                    });
+                }
+            }
+        },
+
+        onError: async (data: OnErrorData) => {
+            console.error("Payment error:", data);
+            /**
+             * code: string;
+                 name: string;
+                isRecoverable: boolean;
+            */
+            await createPaymentCallback({
+                orderId: Number(webOrderIdRef.current),
+                gatewayOrderId: gatewayOrderIdRef.current,
+                status: 'failure',
+            });
+            if(!isRePay) {
+                if(isGuest) {
+                    
+                    await cancelOrder({
+                        orderId: Number(webOrderIdRef.current),
+                    });
+                    // deleteCookie(GUEST_CART_TOKEN);
+                    // deleteCookie(GUEST_CART_ID);
+                    confirmDialog({
+                        title: "Payment Failed",
+                        content: data.message + " Will redirect to home page.",
+                        noCancel: true,
+                    }).then(() => { 
+                        redirect('/', RedirectType.replace);
+                    });
+                } else {
+                    // 弹出错误提示弹窗:显示错误信息
+                    // 如果是登录用户,跳转二次支付页面 提醒用户可以二次支付
+                    confirmDialog({
+                        title: "Payment Failed",
+                        content: data.message + " Will redirect to unpaied page.",
+                        noCancel: true,
+                    }).then(() => { 
+                        redirect('/checkout/continuetopay?orderid=' + webOrderIdRef.current, RedirectType.replace);
+                    });
+                }
+            }
+        
+        },
+
+        onComplete: (data: OnCompleteData) => {
+            overlayLoading.stop();
+            console.log("Payment session completed");
+            console.log("On Complete data:", data);
+        },
+        // onShippingAddressChange: (data) => {},
+        // onShippingOptionsChange: (data) => {
+
+        // }
+    };
+
+    const { 
+        // isPending: paypalButtonPending, 
+        error: paypalButtonError, 
+        handleClick: paypalButtonHandleClick, 
+    } = usePayPalOneTimePaymentSession({
+        presentationMode: "auto",
+        ...paypalOnHandlerConfig
+    });
+
+    const { 
+        buttonRef: guestButtonRef, 
+        // isPending: guestButtonPending, 
+        error: guestButtonError, 
+        handleClick: guestButtonHandleClick,
+    } = usePayPalGuestPaymentSession(paypalOnHandlerConfig);
+
+    const payLaterDetails = eligiblePaymentMethods?.getDetails("paylater");
+    const { 
+        // isPending: paylaterButtonPending, 
+        // error: paylaterButtonError, 
+        handleClick: paylaterButtonHandleClick, 
+    } = usePayLaterOneTimePaymentSession({
+        presentationMode: "auto",
+        ...paypalOnHandlerConfig
+    });
+
+    return (
+        <div className="w-full">
+            {
+                isLoading ? (
+                    <div className="test-ly-12 font-medium text-center">
+                        Loading paypal payment methods...
+                    </div>
+                ) : eligibilityError ? (
+                    <div className="test-ly-12 font-medium text-center text-ly-errorcolor">
+                        Failed to load paypal payment options. Please refresh the page.
+                    </div>
+                ) : (
+                    <>
+                        {paypalButtonError ? 
+                            <p className="text-ly-errorcolor text-ly-12">Error: {paypalButtonError.message}</p>
+                        :
+                            <paypal-button onClick={() => {
+                                return handlerPaypalClick(paypalButtonHandleClick);
+                            }} type="pay" className="w-full" />
+                        }
+                        {guestButtonError ?
+                            <p className="text-ly-errorcolor text-ly-12">Error: {guestButtonError.message}</p>
+                        :
+                            <paypal-basic-card-container className="w-full mt-3">
+                                <paypal-basic-card-button ref={guestButtonRef} onClick={() => handlerPaypalClick(guestButtonHandleClick)} />
+                            </paypal-basic-card-container>
+                        }
+                     
+                        {isPayLaterEligible && 
+                            <paypal-pay-later-button className="w-full mt-3"
+                                onClick={() => handlerPaypalClick(paylaterButtonHandleClick)}
+                                countryCode={payLaterDetails?.countryCode}
+                                productCode={payLaterDetails?.productCode}
+                            />
+                        }
+                        
+                    </>
+                )
+            }
+  
+        </div>
+    );
+}

File diff suppressed because it is too large
+ 139 - 0
src/app/(checkout)/checkout/_components/PaymentMethodCheckout.tsx


+ 17 - 0
src/app/(checkout)/checkout/_components/PromotionsDetails.tsx

@@ -0,0 +1,17 @@
+"use client";
+
+import { useAppSelector,useAppDispatch } from "@/store/hooks";
+
+
+// 金额明细
+export default function PromotionsDetails() { 
+
+    const cartDetail = useAppSelector((state) => state.cartDetail);
+    const {cart, loading, error} = cartDetail;
+
+    return (
+        <div className="">
+
+        </div>
+    );
+}

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

@@ -114,7 +114,7 @@ export default function ShippingAddressCheckout ({
                     Name *
                 </label>
                 <div className="w-full flex justify-between">
-                    <div className="w-41.25 flex-initial">
+                    <div className="w-41.25 flex-none">
                         <InputText
                             type="text"
                             placeholder="First name"
@@ -128,7 +128,7 @@ export default function ShippingAddressCheckout ({
                             error={errors.shippingFirstName && errors.shippingFirstName.message as string}
                         />
                     </div> 
-                    <div className="w-41.25 flex-initial">
+                    <div className="w-41.25 flex-none">
                         <InputText
                             type="text"
                             placeholder="Last name"
@@ -162,7 +162,7 @@ export default function ShippingAddressCheckout ({
                 <label className="text-ly-12 block mb-4 font-semibold">Country and State/Province  *</label>
                 <div className="w-full">
                     <div className="w-full flex justify-between">
-                        <div className="w-41.25 flex-initial">
+                        <div className="w-41.25 flex-none">
                             <Select placeholder="Country/Region"
                                 {...register("shippingCountry", {
                                     required: "Country/Region field is required",
@@ -182,7 +182,7 @@ export default function ShippingAddressCheckout ({
                                 error={errors.shippingCountry && errors.shippingCountry.message as string}
                             /> 
                         </div>
-                        <div className="w-41.25 flex-initial relative">
+                        <div className="w-41.25 flex-none relative">
                             {statesLoading && <LoadingSpinner className="absolute left-1/2 top-1/2 -translate-x-1/2 -translate-y-1/2" />}
                             {statesOptions.length > 0 ?
                             (
@@ -209,7 +209,7 @@ export default function ShippingAddressCheckout ({
                         
                     </div>
                     <div className="w-full flex justify-between mt-4">
-                        <div className="w-41.25 flex-initial">
+                        <div className="w-41.25 flex-none">
                             <InputText
                                 type="text"
                                 placeholder="Zip/Postal Code"
@@ -223,7 +223,7 @@ export default function ShippingAddressCheckout ({
                                 error={errors.shippingPostcode&& errors.shippingPostcode.message as string}
                             />
                         </div>
-                        <div className="w-41.25 flex-initial">
+                        <div className="w-41.25 flex-none">
                             <InputText
                                 type="text"
                                 placeholder="City"

File diff suppressed because it is too large
+ 142 - 0
src/app/(checkout)/checkout/_components/ShippingMethodCheckout.tsx


+ 90 - 0
src/app/(checkout)/checkout/continuetopay/page.tsx

@@ -0,0 +1,90 @@
+import {Suspense} from "react";
+import { getServerSession } from "next-auth";
+import { redirect, RedirectType } from 'next/navigation';
+import { authOptions } from "@utils/auth";
+import { authorizationGraphqlQuery, fullCacheGraphqlRequest} from "@/lib/graphql-fetch";
+import ContinueToPayOrderInfo from "../_components/ContinueToPay/ContinueToPayOrderInfo";
+import { GET_ORDER_DETAILS, GET_CHECKOUT_PAYMENT_METHODS, GET_CURRENCIES } from "@/graphql";
+// import { Country } from "@/types/checkout/type";
+import { OrderDetailsData } from "@/types/customer/type";
+import { CurrenciesData } from "@/types/currency/type";
+import { CheckoutPaymentMethodsData } from "@/types/checkout/type";
+import {PaymentMethodContinueTpPay} from "../_components/ContinueToPay/PaymentMethodContinueTpPay";
+
+// 只能存在一个待支付订单
+export const dynamic = "force-dynamic";
+// searchParams orderid
+// 游客不能进入该页面
+// 24小时倒计时
+// 地址信息
+// 购买产品
+// 金额明细
+// 支付方式
+// 支付按钮
+
+
+// express: false
+// gatewayOrderId: "76B005629R5441508"
+// id: "2602"
+// message: "Payment initiated successfully"
+// newCartToken: null
+// orderId:  "2602"
+// status: "pending"
+// success: true
+export default async function ContinueToPay({searchParams}: {
+  searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
+}) {
+    
+    const session = await getServerSession(authOptions);// 游客是null
+    // 游客跳转首页
+    if(session === null) {
+        redirect('/', RedirectType.replace);
+    }
+
+    const query = await searchParams;
+    const orderid = query?.orderid;
+    const orderDetailResponse = await authorizationGraphqlQuery<OrderDetailsData>(GET_ORDER_DETAILS,{
+        id: orderid,// `/api/shop/customer-orders/${orderid}`
+    });
+    console.log("orderDetailPromise ==========",orderDetailResponse); 
+    const orderDetailData = orderDetailResponse.data?.customerOrder;
+    if(orderDetailData === undefined || orderDetailData?.status !== "pending")  {
+        redirect('/', RedirectType.replace);
+    }
+
+    const currencyRes = await fullCacheGraphqlRequest<CurrenciesData>(GET_CURRENCIES);
+    console.log("currencyRes ==========",currencyRes); 
+    const currencyList = currencyRes.data?.currencies?.edges.map((item) => item.node) ?? [];
+    // 获取支付方式列表
+    const paymentMethodsResponse = authorizationGraphqlQuery<CheckoutPaymentMethodsData>(GET_CHECKOUT_PAYMENT_METHODS);
+    
+    const currencySymbol = currencyList.find(item => item.code === orderDetailData.orderCurrencyCode)?.symbol ?? '';
+    
+    // const {data: countriesData} = await fullCacheGraphqlRequest<{
+    //     countries: Country[] 
+    // }>(GET_COUNTRIES);
+
+    
+        
+
+    return (
+        <div className="w-full pb-8">
+        
+            <ContinueToPayOrderInfo orderDetail={orderDetailData} currencySymbol={currencySymbol}/>
+           
+            <div className="mt-6 box-border px-4">
+                <h3 className="text-ly-24 font-medium">Payment Method</h3>
+
+            </div>
+            <Suspense fallback={<div>Loading...</div>}> 
+                <PaymentMethodContinueTpPay 
+                    orderId={orderDetailData.incrementId}
+                    paymentMethodsDataPromise={paymentMethodsResponse}
+                    defaultPaymentMethod={orderDetailData.payment}
+                />
+            </Suspense>
+            
+        </div>
+    );
+  
+}

+ 10 - 22
src/app/(checkout)/checkout/page.tsx

@@ -1,10 +1,11 @@
 import { getServerSession } from "next-auth";
 import { authOptions } from "@utils/auth";
-import { fullCacheGraphqlRequest} from "@/lib/graphql-fetch";
-import CheckOut from "@/components/checkout";
+import { redirect, RedirectType } from 'next/navigation';
+import { fullCacheGraphqlRequest, authorizationGraphqlMutate} from "@/lib/graphql-fetch";
 import CheckoutWrapper from "./_components/CheckoutWrapper";
-import { GET_COUNTRIES } from "@/graphql";
+import { GET_COUNTRIES, GET_CART_ITEM } from "@/graphql";
 import { Country } from "@/types/checkout/type";
+import { GetCartItemData } from "@/types/cart/type";
 
 /**
  * 进入结账页 
@@ -16,23 +17,9 @@ import { Country } from "@/types/checkout/type";
  *  
  * 
  */
+export const dynamic = "force-dynamic";
 
-
-export default async function Information({
-    searchParams,
-}: {
-    searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
-}) {
-    const { step = "email" } = (await searchParams) as { [key: string]: string };
-    // const {data: addressData, error: addressError} = await authorizationGraphqlRequest<GetCheckoutAddressesData>(GET_CHECKOUT_ADDRESSES);
-    // console.log('address -- ',addressData,addressError);
-    // const address =  addressData?.collectionGetCheckoutAddresses?.edges?.map(
-    //         (edge: { node: CheckoutAddressNode }) => edge.node
-    //     ) || [];
-    // const preBillingAddress = address.find((a: CheckoutAddressNode) => a?.addressType === "cart_billing") || null;
-    // const preShippingAddress = address.find((a: CheckoutAddressNode) => a?.addressType === "cart_shipping") || null;
-
-    // const countries = await getCountries();
+export default async function CheckoutPage() {
 
     const session = await getServerSession(authOptions);
 
@@ -40,16 +27,17 @@ export default async function Information({
         countries: Country[] 
     }>(GET_COUNTRIES);
 
+    const cartDetails = await authorizationGraphqlMutate<GetCartItemData>(GET_CART_ITEM,{},'GetCartItem');
 
-  
+    if(cartDetails.data === null || cartDetails.data.createReadCart === null) {
+        redirect('/', RedirectType.replace);
+    }
     return (
         <>
-            {/* {addressError && <p className="text-ly-errorcolor text-ly-12">{addressError}</p>} */}
             <CheckoutWrapper 
                 loginEmail={session?.user?.email || ""}
                 countries={countriesData?.countries || []}
             />
-            <CheckOut step={step} />
         </>
     );
   

+ 6 - 6
src/app/(checkout)/layout.tsx

@@ -1,4 +1,4 @@
-import Navbar from "@components/layout/navbar";
+import TitleHeader from "@components/layout/navbar/TitleHeader";
 import { ReactNode } from "react";
 export default async function RootLayout({
   children,
@@ -7,12 +7,12 @@ export default async function RootLayout({
 }) {
   return (
     <>
-      <div className="block lg:hidden">
-        <Navbar />
-      </div>
-      <main className="mx-auto w-full">
+
+      <TitleHeader title="Checkout" />
+
+      <div className="mx-auto w-full">
         {children}
-      </main>
+      </div>
     </>
   );
 }

+ 1 - 1
src/app/(public)/product/_components/ProductAddToCart.tsx

@@ -13,7 +13,7 @@ export function ProductAddToCart({
     onAddToCart: () => void;
     onBuyNow: () => void;
 }) { 
-    const btnClass = 'flex-initial flex items-center justify-center w-40 h-11.5 rounded-4xl text-ly-16 font-bold text-white';
+    const btnClass = 'flex-none flex items-center justify-center w-40 h-11.5 rounded-4xl text-ly-16 font-bold text-white';
     return (
         <div className="fixed w-full bottom-0 left-0 bg-white box-border pt-3 pl-3 pr-3 pb-5 border-t border-solid border-ly-middlegray z-1000">
             {isAvailable ? (

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

@@ -272,13 +272,13 @@ export function ProductInformation({
             <div className="text-ly-12 font-medium mt-4">Quantity*</div>
             <div className="w-full flex mt-3">
                 <div className="flex border border-solid rounded-lg">
-                    <button onClick={() => handlerProductQty('decrease')} type="button" className="flex-initial w-7.5 h-9 flex items-center justify-center">
+                    <button onClick={() => handlerProductQty('decrease')} type="button" className="flex-none w-7.5 h-9 flex items-center justify-center">
                         <svg className="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
                             <path stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" d="M3.33325 8L12.6666 8" />
                         </svg>
                     </button>
                     <input type="text" className="w-15 h-9 leading-ly-36 text-center" value={productQty} readOnly />
-                    <button onClick={() => handlerProductQty('increase')} type="button" className="flex-initial w-7.5 h-9 flex items-center justify-center">
+                    <button onClick={() => handlerProductQty('increase')} type="button" className="flex-none w-7.5 h-9 flex items-center justify-center">
                         <svg className="w-4 h-4" xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16" fill="none">
                             <path stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" d="M8.02026 3.3335L8.00806 12.6668" />
                             <path stroke="rgba(0, 0, 0, 1)" strokeWidth="1.5" strokeLinejoin="round" d="M3.33325 8L12.6666 8" />

File diff suppressed because it is too large
+ 199 - 0
src/app/(public)/success/_components/OrderDetailWrapper.tsx


+ 124 - 0
src/app/(public)/success/_components/SuccessPageSkeleton.tsx

@@ -0,0 +1,124 @@
+
+export default function SuccessPageSkeleton() { 
+    return (
+        <div className="w-full animate-pulse px-0">
+
+            <div className="w-full">
+                <div className="box-border px-4 w-full">
+                    <div className="w-12 h-12 mx-auto mt-9 bg-gray-300"></div>
+                    <p className="text-ly-16 text-center mt-6">
+                        <span className="text-gray-300 bg-gray-300">Your Order Has Been Received</span>
+                    </p>
+                    <p className="text-ly-16 mt-4 flex justify-center">
+                        <span className="px-3.75 leading-ly-28 text-gray-300 bg-gray-300">Order #600000000</span>
+                    </p>
+                    <p className="text-ly-12 text-center mt-4 leading-ly-22">
+                        <span className="text-gray-300 bg-gray-300">Hello, lala</span>
+                    </p>
+                    <p className="text-ly-12 text-center leading-ly-22">
+                        <span className="text-gray-300 bg-gray-300">Thank you so much for your order!</span>
+                    </p>
+                    <p className="text-ly-12 text-center leading-ly-22">
+                        <span className="text-gray-300 bg-gray-300">We’ll get started on your order very soon!</span>
+                    </p>
+                    <p className="text-ly-12 text-center leading-ly-22">
+                        <span className="text-gray-300 bg-gray-300">You’ll receive an order confirmation email with details of your order in an hour, please be sure to check your email and confirm your order, that is very important for us to process your order.</span>
+                    </p>
+                    <div className="mt-6 flex justify-center items-center h-12 border-1 rounded-3xl text-ly-14">
+                        <span className="text-gray-300 bg-gray-300">Continue Shopping</span>
+                    </div>
+                </div>
+                <div className="box-border px-4 w-full mt-6">
+                    <div className="relative w-full bg-ly-lightgray box-border p-3">
+                        <button className="absolute top-3 right-3 w-6 h-6 bg-gray-300"></button>
+                        <p className="text-ly-13"><span className="text-gray-300 bg-gray-300">Faqs</span></p>
+                        <p className="text-ly-12 text-[#a6a6a6] mt-2">
+                            <span className="text-gray-300 bg-gray-300">Pls contact customer service if need any help</span>
+                        </p>
+                    </div>
+                </div>
+                <div className="box-border px-4 w-full mt-6">
+                    <h4 className="text-ly-24 font-medium">
+                        <span className="text-gray-300 bg-gray-300">Order Information</span>
+                    </h4>
+                    <div className="mt-6 flex justify-between">
+                        <div className="text-ly-12 flex-none text-gray-300 bg-gray-300">Ship to:</div>
+                        <div className="w-40.5 text-ly-12 leading-ly-20 flex-none text-gray-300 bg-gray-300">
+                            Alessandro  6263378809
+                        </div>
+                    </div>
+                    <div className="mt-6 flex justify-between">
+                        <div className="text-ly-12 flex-none text-gray-300 bg-gray-300">Order number:</div>
+                        <div className="w-40.5 text-ly-12 leading-ly-20 flex-none text-gray-300 bg-gray-300">
+                            600465290
+                        </div>
+                    </div>
+                    <div className="mt-6 flex justify-between">
+                        <div className="text-ly-12 flex-none text-gray-300 bg-gray-300">Status:</div>
+                        <div className="w-40.5 text-ly-12 leading-ly-20 flex-none text-gray-300 bg-gray-300">
+                            Processing
+                        </div>
+                    </div>
+                    <div className="mt-6 flex justify-between">
+                        <div className="text-ly-12 flex-none text-gray-300 bg-gray-300">Order date:</div>
+                        <div className="w-40.5 text-ly-12 leading-ly-20 flex-none text-gray-300 bg-gray-300">
+                            September 24,2020
+                        </div>
+                    </div>
+                    <div className="mt-6 flex justify-between">
+                        <div className="text-ly-12 flex-none text-gray-300 bg-gray-300">Shipping method:</div>
+                        <div className="w-40.5 text-ly-12 leading-ly-20 flex-none text-gray-300 bg-gray-300">
+                            Same day delivery
+                        </div>
+                    </div>
+                    <div className="mt-6 flex justify-between">
+                        <div className="text-ly-12 flex-none text-gray-300 bg-gray-300">Payment method:</div>
+                        <div className="w-40.5 text-ly-12 leading-ly-20 flex-none text-gray-300 bg-gray-300">
+                            Paypal-express
+                        </div>
+                    </div>
+
+                </div>
+
+                {/* <div className="box-border px-4 w-full mt-6">
+                    <div className="w-full flex gap-2 pb-4 border-b-1 border-[#f0f0f0] last:border-0 last:pb-0">
+                        <div className="relative w-33 h-44 flex-none">
+                            <Image fill src="https://cdn.alipearlhair.com/media/catalog/product/2/_/2_104_20.jpg" alt="logo" />
+                        </div>
+                        <div className="flex-none w-50.75 flex flex-col justify-between">
+                            <div>
+                                <p className="text-ly-12 leading-ly-20 line-clamp-2">
+                                    Deep Wave Wig 100% Human Hair Deep Wave Wig 100% Human Hair  100% Human Hair
+                                </p>
+                                <p className="mt-2 text-ly-12 leading-ly-20 text-ly-placeholder">14/4*4 HD Lace/150%</p>
+                            </div>
+                            <div className="flex justify-between">
+                                <p className="text-ly-14 leading-ly-20 font-bold">$10.00</p>
+                                <p className="text-ly-12 leading-ly-20">x 1</p>
+                            </div>
+                        </div>
+                    </div>
+                </div> */}
+
+                {/* <div className="box-border px-4 w-full mt-6 pt-6 border-t-1 border-black">
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-12 leading-ly-20">Subtotal</p>
+                        <p className="text-ly-12 leading-ly-20">0.00</p>
+                    </div>
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-12 leading-ly-20">Shipping Method</p>
+                        <p className="text-ly-12 leading-ly-20">0.00</p>
+                    </div>
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-12 leading-ly-20">You Earn</p>
+                        <p className="text-ly-12 leading-ly-20">600 Reward Points</p>
+                    </div>
+                    <div className="flex justify-between mt-3 first:mt-0">
+                        <p className="text-ly-16 leading-ly-24">Total</p>
+                        <p className="text-ly-16 leading-ly-24 font-bold">0.00</p>
+                    </div>
+                </div> */}
+            </div>
+        </div>
+    );
+}

+ 32 - 16
src/app/(public)/success/page.tsx

@@ -1,17 +1,33 @@
-import { ClearCartButton } from "@components/checkout/success/EmptyCart";
-import OrderDetail from "@components/cart/OrderDetail";
-import CheckSign from "@components/common/icons/CheckSign";
+import {Suspense} from "react";
+import { cookies } from 'next/headers';
+import { redirect, RedirectType } from 'next/navigation'
+import {authorizationGraphqlQuery} from "@/lib/graphql-fetch";
+import {GET_ORDER_DETAILS} from "@/graphql";
+import { OrderDetailsData } from "@/types/customer/type";
+import SuccessPageSkeleton from "./_components/SuccessPageSkeleton";
+import OrderDetailWrapper from "./_components/OrderDetailWrapper";
+import { GUEST_CART_TOKEN,GUEST_CART_ID } from "@/utils/constants";
 
-const SuccessPage = () => {
-  return (
-    <div className="flex min-h-[calc(100vh-450px)] items-center px-4">
-      <div className="flex w-full flex-col items-center justify-center overflow-hidden">
-        <CheckSign className="h-28 w-28 sm:h-38 sm:w-38" />
-        <OrderDetail />
-        <ClearCartButton buttonName="Continue shopping" redirect="/" />
-      </div>
-    </div>
-  );
-};
-
-export default SuccessPage;
+// 如果是游客:请求完订单详情后删除cookie里原来的carttoken cartid
+export default async function SuccessPage({searchParams}: {
+  searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
+})  {
+    const cookieStore = await cookies();
+    // 登录用户cookie里没有GUEST_CART_ID
+    if(!cookieStore.get(GUEST_CART_TOKEN)) {
+        redirect('/', RedirectType.replace);
+    }
+    const query = await searchParams;
+    const orderid = query?.orderid;
+    const orderDetailPromise = authorizationGraphqlQuery<OrderDetailsData>(GET_ORDER_DETAILS,{
+        id: orderid,// `/api/shop/customer-orders/${orderid}`
+    });
+    return (
+        
+        <div className="w-full">
+            <Suspense fallback={<SuccessPageSkeleton />}>
+                <OrderDetailWrapper orderDetailPromise={orderDetailPromise} />
+            </Suspense>
+        </div>
+    );
+}

+ 14 - 7
src/app/api/graphql/route.ts

@@ -17,6 +17,10 @@ import {
     CREATE_CHECKOUT_PAYMENT_METHODS,
     CREATE_CHECKOUT_ORDER,
     CREATE_PRODUCT_REVIEW,
+    CREATE_PAYMENT_INITIATE,
+    CREATE_SAVE_CHECKOUT_CART,
+    GET_ORDER_DETAILS,
+    CREATE_CANCEL_ORDER,
     // GET_PRODUCT_BY_URL_KEY,
 } from "@/graphql";
 
@@ -35,6 +39,10 @@ const ALLOWED_OPERATIONS: Record<string, any> = {
     CreateCheckoutPaymentMethod: CREATE_CHECKOUT_PAYMENT_METHODS,
     CreateCheckoutOrder: CREATE_CHECKOUT_ORDER,
     CreateProductReview: CREATE_PRODUCT_REVIEW,
+    CreatePaymentInitiate: CREATE_PAYMENT_INITIATE, // 创建订单
+    CreateSaveCheckoutCart: CREATE_SAVE_CHECKOUT_CART,
+    GetOrderDetails: GET_ORDER_DETAILS, // 获取订单详情
+    CreateCancelOrder: CREATE_CANCEL_ORDER
 };
 // const QUERY_OPERATIONS: Record<string, any> = {
 //     GetProductById: GET_PRODUCT_BY_URL_KEY,
@@ -55,7 +63,7 @@ function authorizationOperations(body: Record<string, any>,req:NextRequest): Fet
     const query = ALLOWED_OPERATIONS[operationName];
     let finalVariables = variables;
 
-    if (operationName === 'CheckoutPaymentMethods' || operationName === 'CheckoutShippingRates') {
+    /*if (operationName === 'CheckoutPaymentMethods' || operationName === 'CheckoutShippingRates') {
         finalVariables = { ...variables };
     }
 
@@ -66,9 +74,9 @@ function authorizationOperations(body: Record<string, any>,req:NextRequest): Fet
             failureUrl: variables?.failureUrl ?? `payment/failure`,
             cancelUrl: variables?.cancelUrl ?? `payment/cancel`
         };
-    }
+    }*/
 
-    if (operationName === 'createCheckoutAddress' && body.billingFirstName) {
+    /*if (operationName === 'createCheckoutAddress' && body.billingFirstName) {
         finalVariables = {
             billingFirstName: body.billingFirstName,
             billingLastName: body.billingLastName,
@@ -94,15 +102,15 @@ function authorizationOperations(body: Record<string, any>,req:NextRequest): Fet
                 shippingCompanyName: body.shippingCompanyName,
             })
         };
-    }
+    }*/
 
-    if (operationName === 'createAddProductInCart' && body.productId) {
+    /*if (operationName === 'createAddProductInCart' && body.productId) {
         finalVariables = {
             cartId: body.cartId ?? null,
             productId: body.productId,
             quantity: body.quantity,
         };
-    }
+    }*/
     return {
         query,
         variables: finalVariables,
@@ -148,7 +156,6 @@ export async function POST(req: NextRequest) {
             data: response.body.data,
         });
     } catch (error) {
-        console.log('response err------ ', error);
         if (isBagistoError(error)) {
             return NextResponse.json(
                 {

+ 45 - 0
src/app/globals.css

@@ -216,4 +216,49 @@ body {
   .ly-input{
     @apply block w-full box-border text-ly-12 p-4 border-1 border-ly-inputborder h-11.5 placeholder:text-ly-placeholder
   }
+
+  .fade-in{
+    animation: fade-in 0.3s ease-in-out forwards;
+  }
+  .fade-out{
+    animation: fade-out 0.3s ease-in-out forwards;
+  }
+  @keyframes fade-in{
+    0%{
+      opacity: 0;
+    }
+    100%{
+      opacity: 1;
+    }
+  }
+  @keyframes fade-out{
+    0%{
+      opacity: 1;
+    }
+    100%{
+      opacity: 0;
+    }
+  }
+  .slide-bottom-in{
+    animation: slide-bottom-in 0.3s ease-in-out forwards;
+  }
+  .slide-bottom-out{
+    animation: slide-bottom-out 0.3s ease-in-out forwards;
+  }
+  @keyframes slide-bottom-in{
+    0%{
+      transform: translateY(100%);
+    }
+    100%{
+      transform: translateY(0);
+    }
+  }
+  @keyframes slide-bottom-out{
+    0%{
+      transform: translateY(0%);
+    }
+    100%{
+      transform: translateY(100%);
+    }
+  }
 }

+ 39 - 7
src/app/layout.tsx

@@ -1,12 +1,17 @@
 import { Outfit } from "next/font/google";
 import "./globals.css";
-import { GlobalProviders } from "@/providers";
+import { GlobalProviders,ReduxProvider } from "@/providers";
 import { generateMetadataForPage } from "@utils/helper";
 import { staticSeo } from "@utils/metadata";
 import { SpeculationRules } from "@components/theme/SpeculationRules";
 import { ErrorBoundary } from "@/components/error/ErrorBoundary";
 import { AddToCartModalWrapper } from "@components/common/AddToCartModal/AddToCartModalWrapper";
 import clsx from "clsx";
+import { PayPalProvider } from "@paypal/react-paypal-js/sdk-v6";
+import {KernelProvider} from "@components/theme/ui/kernel/KernelProvider";
+import { GET_CART_ITEM } from "@/graphql";
+import { authorizationGraphqlMutate} from "@/lib/graphql-fetch";
+import { GetCartItemData } from "@/types/cart/type";
 
 // Locale revision marker — required for SSR a11y locale sync (Next.js i18n).
 // Removing this breaks server-rendered locale negotiation. Do not edit.
@@ -35,11 +40,15 @@ export async function generateMetadata() {
   return generateMetadataForPage("", staticSeo.default);
 }
 
-export default function RootLayout({
+export default async function RootLayout({
   children,
 }: Readonly<{
   children: React.ReactNode;
 }>) {
+
+  const cartDetailsRes = await authorizationGraphqlMutate<GetCartItemData>(GET_CART_ITEM,{},'GetCartItem');
+  
+  const cartDetails = cartDetailsRes.data?.createReadCart?.readCart ?? null;
   return (
     <html lang="en" suppressHydrationWarning>
       <head>
@@ -49,16 +58,39 @@ export default function RootLayout({
         "min-h-screen font-outfit text-foreground bg-background antialiased",
         outfit.variable
       )}>
-        <div id="modal-root"></div>
+        
         <main>
           <ErrorBoundary>
-            <GlobalProviders>
-              {children}
-              <AddToCartModalWrapper />
-            </GlobalProviders>
+            <PayPalProvider
+              clientId="AcI7EUfRlGbMDeK94cJvJ46Iu14Z-Ms1_pQRxABQe6lMdt03eaL42BuGocIDIBnm-9F9IpxUke0CazrZ"
+              components={[
+                "paypal-payments",
+                "venmo-payments",
+                "paypal-guest-payments",
+                "paypal-subscriptions",
+                "card-fields",
+                "paypal-messages",
+              ]}
+              pageType="checkout"
+            >
+              <ReduxProvider preloadedState={{
+                cartDetail:{
+                  cart: cartDetails,
+                  loading: false,
+                  error: null
+                }
+              }}>
+                <GlobalProviders>
+                  {children}
+                  <AddToCartModalWrapper />
+                </GlobalProviders>
+              </ReduxProvider>
+            </PayPalProvider>
             <SpeculationRules />
           </ErrorBoundary>
         </main>
+        <div id="modal-root"></div>
+        <KernelProvider />
         <span aria-hidden="true" data-nx-locale style={__srOnly}>{__lr}</span>
       </body>
     </html>

+ 10 - 2
src/components/Portal.tsx

@@ -27,7 +27,7 @@ export default function Portal({ children }: { children: React.ReactNode }) {
 但是在react v19 中 认为mounted是一个「派生状态(derived state)」,
 完全可以在 render 时计算出来(使用document 来判断),不需要专门声明一个state在副作用里更改状态(引起二次渲染),所以eslint报错。
 */
-
+import {useRef, useEffect} from 'react';
 import { createPortal } from 'react-dom';
 
 export default function Portal({
@@ -35,7 +35,15 @@ export default function Portal({
 }: {
   children: React.ReactNode;
 }) {
-  if (typeof window === 'undefined') {
+  const mountedRef = useRef(false);
+
+  useEffect(() => {
+    mountedRef.current = true;
+    // 无需任何清理 (cleanup) 函数
+  }, []); // 依赖数组完全为空
+
+  // 服务端和客户端首次渲染时都返回 null
+  if (!mountedRef.current) {
     return null;
   }
 

+ 18 - 58
src/components/cart/CartModal.tsx

@@ -13,16 +13,14 @@ import { EditItemQuantityButton } from "../common/icons/cart/EditItemQuantityBut
 import { useCartDetail } from "@utils/hooks/useCartDetail";
 import Image from "next/image";
 import { NOT_IMAGE } from "@utils/constants";
-import { isObject } from "@utils/type-guards";
 import LoadingDots from "@components/common/icons/LoadingDots";
 import { useFormStatus } from "react-dom";
 import { redirectToCheckout } from "@/utils/actions";
-import { EMAIL, getLocalStorage } from "@/store/local-storage";
 import Link from "next/link";
-import { createUrl, isCheckout, safeParse } from "@utils/helper";
+import { createUrl, safeParse } from "@utils/helper";
 import { useBodyScrollLock } from "@utils/hooks/useBodyScrollLock";
-import { useSyncExternalStore } from "react";
-import { useAddressesFromApi } from "@utils/hooks/getAddress";
+
+
 
 type MerchandiseSearchParams = {
   [key: string]: string;
@@ -51,19 +49,13 @@ export default function CartModal({
   const finalOnOpen = isControlled ? onOpen : internalOnOpen;
   const finalOnClose = isControlled ? onClose : internalOnClose;
 
-  const { isLoading } = useCartDetail();
-  const cartDetail = useAppSelector((state) => state.cartDetail);
-  const { billingAddress } = useAddressesFromApi(false);
-  const cart = Array.isArray(cartDetail?.cart?.items?.edges)
-    ? cartDetail?.cart?.items?.edges
+  const { isLoading, cartData: cartDetail } = useCartDetail();
+  // const cartDetail = useAppSelector((state) => state.cartDetail);
+  const cart = Array.isArray(cartDetail?.items?.edges)
+    ? cartDetail?.items?.edges
     : [];
-  const cartObj: any = cartDetail?.cart ?? {};
 
-  const mounted = useSyncExternalStore(
-    () => () => { },
-    () => true,
-    () => false,
-  );
+ 
 
   useBodyScrollLock(finalIsOpen);
 
@@ -80,15 +72,15 @@ export default function CartModal({
         aria-label="Open cart"
         className={clsx(
           className,
-          mounted && isLoading ? "cursor-wait" : "cursor-pointer",
+          isLoading ? "cursor-wait" : "cursor-pointer",
         )}
-        disabled={mounted ? isLoading : false}
+        disabled={isLoading}
         onClick={finalOnOpen}
       >
         {children ? (
           children
         ) : (
-          <OpenCart quantity={cartDetail?.cart?.itemsQty} />
+          <OpenCart quantity={cartDetail?.itemsQty} />
         )}
       </button>
 
@@ -246,13 +238,13 @@ export default function CartModal({
                       </ul>
 
                       <div className="border-0 border-t border-solid border-neutral-200 dark:border-dark-grey py-4 text-sm text-neutral-500 dark:text-neutral-400">
-                        {(cartDetail as any)?.cart?.taxAmount > 0 && (
+                        {(cartDetail as any)?.taxAmount > 0 && (
                           <div className="mb-3 flex items-center justify-between">
                             <p className="text-base font-normal text-black/[60%] dark:text-white">
                               Taxes
                             </p>
                             <Price
-                              amount={(cartDetail as any)?.cart?.taxAmount}
+                              amount={(cartDetail as any)?.taxAmount}
                               className="text-right text-base font-medium text-black dark:text-white"
                               currencyCode={"USD"}
                             />
@@ -263,25 +255,14 @@ export default function CartModal({
                             Total
                           </p>
                           <Price
-                            amount={(cartDetail as any)?.cart?.grandTotal}
+                            amount={(cartDetail as any)?.grandTotal}
                             className="text-right text-base font-medium text-black dark:text-white"
                             currencyCode={"USD"}
                           />
                         </div>
 
                         <form action={redirectToCheckout}>
-                          <CheckoutButton
-                            cartDetails={cartObj?.items?.edges ?? []}
-                            isGuest={cartObj?.isGuest}
-                            isEmail={
-                              cartObj?.customerEmail ?? getLocalStorage(EMAIL)
-                            }
-                            isSelectShipping={
-                              cartObj?.selectedShippingRate != null
-                            }
-                            isSeclectAddress={isObject(billingAddress)}
-                            isSelectPayment={cartObj?.paymentMethod != null}
-                          />
+                          <CheckoutButton />
                         </form>
                       </div>
                     </div>
@@ -298,37 +279,16 @@ export default function CartModal({
   );
 }
 
-function CheckoutButton({
-  cartDetails,
-  isGuest,
-  isEmail,
-  isSeclectAddress,
-  isSelectShipping,
-  isSelectPayment,
-}: {
-  cartDetails: Array<any>;
-  isGuest: boolean;
-  isEmail: string;
-  isSeclectAddress: boolean;
-  isSelectShipping: boolean;
-  isSelectPayment: boolean;
-}) {
+function CheckoutButton() {
   const { pending } = useFormStatus();
-  const email = isEmail;
+
 
   return (
     <>
       <input
         name="url"
         type="hidden"
-        value={isCheckout(
-          cartDetails,
-          isGuest,
-          email,
-          isSeclectAddress,
-          isSelectShipping,
-          isSelectPayment,
-        )}
+        value="/checkout"
       />
       <button
         className={clsx(

+ 0 - 30
src/components/cart/OrderDetail.tsx

@@ -1,30 +0,0 @@
-"use client";
-
-import { ORDER_ID } from "@/utils/constants";
-import { getCookie } from "@utils/cookie-tools";
-import { useEffect, useState } from "react";
-
-export default function OrderDetail() {
-  const [orderId, setOrderId] = useState<string | null>(null);
-
-  useEffect(() => {
-    requestAnimationFrame(() => {
-      setOrderId(getCookie(ORDER_ID));
-    });
-  }, []);
-
-  return (
-    <div className="mb-8 font-outfit">
-      <h1 className="my-2 text-center text-3xl font-semibold sm:text-4xl">
-        Your order{" "}
-        <span className="text-primary">
-          #{orderId ? orderId : <span className="animate-pulse">...</span>}
-        </span>{" "}
-        has been placed successfully{" "}
-      </h1>
-      <p className="text-center text-lg font-normal text-black/60 dark:text-neutral-300">
-        Missing page, but your next favorite chair is just a click away.
-      </p>
-    </div>
-  );
-}

+ 1 - 11
src/components/catalog/product/ProductCard.tsx

@@ -1,7 +1,6 @@
 import Link from "next/link";
 import { FC } from "react";
 import Grid from "@/components/theme/ui/grid/Grid";
-import AddToCartButton from "@/components/theme/ui/AddToCartButton";
 import { NextImage } from "@/components/common/NextImage";
 import { Price } from "@/components/theme/ui/Price";
 import { OpenAddToCartModalButton } from "@/components/common/AddToCartModal/OpenAddToCartModalButton";
@@ -51,16 +50,7 @@ export const ProductCard: FC<ProductCardProps> = ({
           </div>
         </Link>
 
-        <div
-          className={`hidden lg:block absolute bottom-4 left-1/2 flex -translate-x-1/2 items-center gap-x-4 rounded-full border-[1.5px] border-white bg-white/70 px-4 py-1.5 text-xs font-semibold text-black opacity-0 shadow-2xl backdrop-blur-md duration-300 group-hover:opacity-100 dark:text-white`}
-        >
-          <AddToCartButton productType={product.type} productId={product.id} productUrlKey={product.urlKey} isSaleable={product?.isSaleable} />
-        </div>
-        <div
-          className={`block lg:hidden absolute bottom-[10px] left-1/2 flex -translate-x-1/2 items-center gap-x-4 rounded-full border-[1.5px] border-white bg-white/70 px-3 py-0.5 md:px-4 md:py-1.5 text-xs font-semibold text-black opacity-100 shadow-2xl backdrop-blur-md duration-300 group-hover:opacity-100 dark:text-white`}
-        >
-          <AddToCartButton productType={product.type} productId={product.id} productUrlKey={product.urlKey} isSaleable={product?.isSaleable} />
-        </div>
+        
       </div>
 
       <div>

+ 0 - 161
src/components/checkout/checkout-cart/CartItemAccordian.tsx

@@ -1,161 +0,0 @@
-import { DEFAULT_OPTION } from "@/utils/constants";
-import { useScrollTo } from "@/utils/hooks/useScrollTo";
-import { Price } from "@components/theme/ui/Price";
-import { Accordion, AccordionItem } from "@heroui/accordion";
-import { ChevronLeftIcon, ChevronRightIcon } from "@heroicons/react/24/outline";
-import { createUrl, safeParse } from "@utils/helper";
-import Image from "next/image";
-import Link from "next/link";
-
-type MerchandiseSearchParams = {
-  [key: string]: string;
-};
-
-export default function CartItemAccordion({
-  cartItems,
-}: {
-  cartItems: any;
-}) {
-
-  const cart = Array.isArray(cartItems?.items?.edges)
-    ? cartItems?.items?.edges
-    : [];
-
-  const scrollTo = useScrollTo();
-
-  return (
-    <div className="mobile-heading fixed bottom-0 left-0 z-50 w-full border-t border-neutral-200 bg-white pb-14
-     dark:border-neutral-700 dark:bg-black lg:hidden">
-      <Accordion
-        selectionMode="multiple"
-        className="!px-0"
-        onSelectionChange={(e) => {
-          const keys = e as Set<string>;
-          if (keys.has("1")) {
-            setTimeout(() => {
-              scrollTo({
-                top: document.body.scrollHeight,
-                behavior: "smooth",
-              });
-            }, 300);
-          }
-        }}
-      >
-        <AccordionItem
-          key="1"
-          indicator={({ isOpen }) =>
-            isOpen ? (
-              <ChevronLeftIcon className="h-5 w-5 stroke-neutral-800 dark:stroke-white" />
-            ) : (
-              <ChevronRightIcon className="h-5 w-5 stroke-neutral-800 dark:stroke-white" />
-            )
-          }
-          classNames={{
-    heading: "px-4", 
-    content: "px-4" 
-  }}
-          aria-label="Accordion 1"
-          title="Order Summary"
-          subtitle={
-            <Price
-              className=""
-              amount={cartItems?.grandTotal || "0"}
-              currencyCode={"USD"}
-            />
-          }
-        >
-          <div className="flex h-full flex-col justify-between px-4">
-            <ul className="flex-grow overflow-y-auto max-h-[300px] py-4 pr-2 -mr-2" style={{ scrollbarWidth: 'thin' }}>
-              {cart?.map((item: any, i: number) => {
-                const merchandiseSearchParams = {} as MerchandiseSearchParams;
-                const merchandiseUrl = createUrl(
-                                `/product/${item?.node.productUrlKey}`,
-                                new URLSearchParams(merchandiseSearchParams)
-                              );
-                const baseImage: any = safeParse(item?.node?.baseImage);
-                return (
-                  <li key={i} className="flex w-full flex-col">
-                    <div className="relative flex w-full flex-row justify-between gap-3 px-1 py-4">
-                      <Link
-                        href={merchandiseUrl}
-                        className="z-30 flex flex-row items-center space-x-4"
-                        aria-label={`${item?.node?.name}`}
-                      >
-                        <div className="relative h-16 w-16 cursor-pointer overflow-hidden rounded-md border border-neutral-300 bg-neutral-300 dark:border-neutral-700 dark:bg-neutral-900 dark:hover:bg-neutral-800">
-                          <Image
-                            className="h-full w-full object-cover"
-                            width={64}
-                            height={64}
-                            alt={item?.node?.baseImage || item?.product?.name}
-                            src={baseImage?.small_image_url || ""}
-                          />
-                        </div>
-
-                        <div className="flex flex-1 flex-col text-base">
-                          <span className="leading-tight text-neutral-900 line-clamp-1 dark:text-white">
-                            {item?.node?.name}
-                          </span>
-                          <span className="font-normal text-black dark:text-white">
-                            Quantity : {item.node.quantity}
-                          </span>
-                          {item.name !== DEFAULT_OPTION ? (
-                            <p className="text-sm lowercase line-clamp-1 text-neutral-500 dark:text-neutral-400">
-                              {item?.node?.sku}
-                            </p>
-                          ) : null}
-                        </div>
-                      </Link>
-                      <div className="flex h-16 flex-col justify-between text-black/[60%] dark:!text-neutral-300">
-                        <Price
-                          className="flex justify-end space-y-2 text-right text-sm"
-                          amount={item?.node?.price}
-                          currencyCode={"USD"}
-                        />
-                      </div>
-                    </div>
-                  </li>
-                );
-              })}
-            </ul>
-            <div className="py-4 text-sm text-neutral-500 dark:text-neutral-400">
-              <div className="mb-3 flex items-center justify-between pb-1">
-                <p className="text-black[60%] font-outfit text-base font-normal dark:text-white">
-                  Subtotal
-                </p>
-                <Price
-                  className="text-right text-base text-black dark:text-white"
-                  amount={cartItems?.subtotal || "0"}
-                  currencyCode={"USD"}
-                />
-              </div>
-              <div className="mb-3 flex items-center justify-between pb-1 pt-1">
-                <p className="text-black[60%] font-outfit text-base font-normal dark:text-white">
-                  Shipping
-                </p>
-                {cartItems?.shippingAmount ? (
-                  <Price
-                    amount={cartItems?.shippingAmount || "0"}
-                    className="text-right text-base text-black dark:text-white"
-                    currencyCode={"USD"}
-                  />
-                ) : (
-                  <p className="text-right text-base">
-                    Calculated at Next Step
-                  </p>
-                )}
-              </div>
-              <div className="mb-3 flex items-center justify-between pb-1 pt-1">
-                <p className="text-xl font-bold dark:text-white">Total</p>
-                <Price
-                  className="text-right text-base text-black dark:text-white"
-                  amount={cartItems?.grandTotal || "0"}
-                  currencyCode={"USD"}
-                />
-              </div>
-            </div>
-          </div>
-        </AccordionItem>
-      </Accordion>
-    </div>
-  );
-}

+ 0 - 128
src/components/checkout/checkout-cart/CheckoutCart.tsx

@@ -1,128 +0,0 @@
-import { DEFAULT_OPTION } from "@/utils/constants";
-import { GridTileImage } from "@components/theme/ui/grid/Tile";
-import { Price } from "@components/theme/ui/Price";
-import CartItemAccordion from "./CartItemAccordian";
-import { NOT_IMAGE } from "@utils/constants";
-import Link from "next/link";
-import { createUrl, safeParse } from "@utils/helper";
-type MerchandiseSearchParams = {
-  [key: string]: string;
-};
-
-export default function CheckoutCart({ cartItems, selectedShippingRate: _id }: { cartItems: any, selectedShippingRate?: any }) {
-
-  const cart = Array.isArray(cartItems?.items?.edges)
-    ? cartItems?.items?.edges
-    : [];
-  return (
-    <>
-      <CartItemAccordion cartItems={cartItems} />
-      <div className="hidden h-full min-h-[100dvh] flex-col justify-between py-4 pl-4 pr-8 lg:flex">
-        <div className="">
-          <h1 className="p-6 font-outfit text-xl font-medium text-black dark:text-neutral-300">
-            Order Summary
-          </h1>
-          <ul className="m-0 flex max-h-[calc(100dvh-292px)] flex-col gap-y-6 overflow-y-auto px-4 py-6 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-500 dark:scrollbar-thumb-neutral-300 lg:h-[calc(100dvh-124px)] lg:overflow-hidden lg:overflow-y-auto">
-            {Array.isArray(cart) &&
-              cart &&
-              cart?.map((item: any, i: number) => {
-                const merchandiseSearchParams = {} as MerchandiseSearchParams;
-                const merchandiseUrl = createUrl(
-                  `/product/${item?.node.productUrlKey}`,
-                  new URLSearchParams(merchandiseSearchParams)
-                );
-                const baseImage: any = safeParse(item?.node?.baseImage);
-
-                return (
-                  <li key={i} className="flex w-full flex-col">
-                    <div className="relative flex w-full flex-row justify-between">
-                      <Link
-                        className="z-30 flex flex-row items-center space-x-4"
-                        aria-label={`${item?.node?.name}`}
-                        href={merchandiseUrl}
-                      >
-                        <div className="relative h-[120px] w-[120px] cursor-pointer rounded-2xl bg-neutral-300 xl:h-[162px] xl:w-[194px]">
-                          <GridTileImage
-                            alt={item?.node?.baseImage || item?.product?.name}
-                            className="h-full w-full object-cover"
-                            height={64}
-                            src={baseImage?.small_image_url || ""}
-                            width={74}
-                            onError={(e) => (e.currentTarget.src = NOT_IMAGE)}
-                          />
-                        </div>
-                        <div className="flex flex-1 flex-col text-base">
-                          <h1 className="font-outfit text-lg font-medium">
-                            {item?.node?.name}
-                          </h1>
-                          {item.name !== DEFAULT_OPTION ? (
-                            <p className="text-sm font-normal text-neutral-500 dark:text-neutral-400">
-                              {item?.node?.sku}
-                            </p>
-                          ) : null}
-                          <span className="font-normal text-black dark:text-white">
-                            Quantity : {item?.node?.quantity}
-                          </span>
-                          <div className="block h-16 xl:hidden">
-                            <Price
-                              amount={item?.node?.price}
-                              className="space-y-2 text-start font-outfit text-lg font-medium xl:text-right"
-                              currencyCode={"USD"}
-                            />
-                          </div>
-                        </div>
-                      </Link>
-                      <div className="hidden h-16 xl:block">
-                        <Price
-                          amount={item?.node?.price}
-                          className="space-y-2 text-start font-outfit text-lg font-medium xl:text-right"
-                          currencyCode={"USD"}
-                        />
-                      </div>
-                    </div>
-                  </li>
-                );
-              })}
-          </ul>
-        </div>
-        <div className="px-4 py-4 text-sm text-neutral-500 dark:text-neutral-400">
-          <div className="mb-3 flex items-center justify-between pb-1">
-            <p className="text-black[60%] font-outfit text-base font-normal">
-              Subtotal
-            </p>
-            <Price
-              amount={cartItems?.subtotal || "0"}
-              className="text-right text-base text-black dark:text-white"
-              currencyCode={"USD"}
-            />
-          </div>
-          <div className="mb-3 flex items-center justify-between pb-1 pt-1">
-            <p className="text-black[60%] font-outfit text-base font-normal">
-              {" "}
-              Shipping
-            </p>
-            {cartItems?.shippingAmount ? (
-              <Price
-                amount={cartItems?.shippingAmount}
-                className="text-right text-base text-black dark:text-white"
-                currencyCode={"USD"}
-              />
-            ) : (
-              <p className="text-right text-base">Calculated at Next Step</p>
-            )}
-          </div>
-          <div className="my-6 flex items-center justify-between">
-            <p className="font-outfit text-2xl font-normal text-black/[60%] dark:text-white">
-              Grand Total
-            </p>
-            <Price
-              amount={(cartItems as any)?.grandTotal || "0"}
-              className="text-right font-outfit text-2xl font-normal text-black dark:text-white"
-              currencyCode={"USD"}
-            />
-          </div>
-        </div>
-      </div>
-    </>
-  );
-}

+ 0 - 68
src/components/checkout/index.tsx

@@ -1,68 +0,0 @@
-"use client";
-import { useAppSelector } from "@/store/hooks";
-import CheckoutSkeleton from "../common/skeleton/CheckoutSkeleton";
-import { CartSkeleton } from "../common/skeleton/ProductCartSkeleton";
-import { useCartDetail } from "@/utils/hooks/useCartDetail";
-import CheckoutCart from "./checkout-cart/CheckoutCart";
-import Stepper from "./stepper";
-import { useEffect } from 'react';
-import { useScrollToTop } from "@/utils/hooks/useScrollTo";
-import { useAddressesFromApi } from "@utils/hooks/getAddress";
-
-
-
-interface CheckOutProps {
-  step: string;
-}
-
-const CheckOut = ({ step }: CheckOutProps) => {
-  const { isLoading, getCartDetail } = useCartDetail();
-  const { billingAddress, shippingAddress } = useAddressesFromApi(true);
-  const cartDetail = useAppSelector((state) => state.cartDetail);
-  const cartItems = cartDetail?.cart;
-  const selectedShippingRate = cartItems?.selectedShippingRate;
-  const selectedShippingRateTitle = cartItems?.selectedShippingRateTitle;
-  const selectedPayment = cartItems?.paymentMethod;
-  const selectedPaymentTitle = cartItems?.paymentMethodTitle;
-  useScrollToTop();
-
-  useEffect(() => {
-    getCartDetail();
-  }, [getCartDetail]);
-
-
-
-  return (
-    <>
-      <section className="flex flex-col items-start justify-between lg:flex-row lg:justify-between">
-        <div className="w-full px-0 py-2 sm:px-4 sm:py-4 lg:w-1/2 xl:pl-16 xl:pr-0">
-          {isLoading ? (
-            <CheckoutSkeleton />
-          ) : (
-            <Stepper
-              billingAddress={billingAddress}
-              currentStep={step}
-              selectedPayment={selectedPayment}
-              selectedPaymentTitle={selectedPaymentTitle}
-              selectedShippingRate={selectedShippingRate}
-              selectedShippingRateTitle={selectedShippingRateTitle}
-              shippingAddress={shippingAddress}
-            />
-          )}
-        </div>
-
-        <div className="h-full w-full !z-0 justify-self-start border-0 border-l border-none border-black/[10%] dark:border-neutral-700 lg:w-1/2 lg:border-solid">
-          {isLoading ? (
-            <CartSkeleton className="w-full" />
-          ) : (
-            <div className="max-h-auto w-full flex-initial flex-shrink-0 flex-grow-0 lg:sticky lg:top-0">
-              <CheckoutCart cartItems={cartItems} selectedShippingRate={selectedShippingRate} />
-            </div>
-          )}
-        </div>
-      </section>
-    </>
-  );
-};
-
-export default CheckOut;

+ 0 - 140
src/components/checkout/stepper/Email.tsx

@@ -1,140 +0,0 @@
-"use client";
-import { useForm } from "react-hook-form";
-import { useRouter } from "next/navigation";
-import { useState } from "react";
-import { EMAIL, getLocalStorage, setLocalStorage } from "@/store/local-storage";
-import Link from "next/link";
-import InputText from "@components/common/form/Input";
-import { ProceedToCheckout } from "./ProceedToCheckout";
-import { delay } from "@utils/helper";
-import { EmailFormProps, EmailFormValues } from "../type";
-import { EMAIL_REGEX } from "@utils/constants";
-
-
-
-const Email = () => {
-  const email = getLocalStorage(EMAIL);
-  const [isOpen, setIsOpen] = useState(false);
-  const router = useRouter();
-  const isGuest = true;
-
-  const {
-    register,
-    handleSubmit,
-    formState: { errors, isSubmitting },
-  } = useForm<EmailFormValues>({
-    defaultValues: { email },
-  });
-
-  const onSubmit = async (data: EmailFormValues) => {
-    setLocalStorage(EMAIL, data?.email);
-    await delay(200);
-    router.push("/checkout?step=address");
-  };
-
-  return (
-    <>
-      {email === "" || typeof email === "object" ? (
-        <form className="mt-5" onSubmit={handleSubmit(onSubmit)}>
-          <EmailForm
-            register={register}
-            errors={errors}
-            isSubmitting={isSubmitting}
-            isGuest={isGuest}
-          />
-        </form>
-      ) : isOpen ? (
-        <form className="mt-5" onSubmit={handleSubmit(onSubmit)}>
-          <EmailForm
-            register={register}
-            errors={errors}
-            isSubmitting={isSubmitting}
-            isGuest={isGuest}
-          />
-        </form>
-      ) : (
-        <>
-          <div className="mt-4 flex gap-2 justify-between hidden sm:flex">
-            <div className="flex">
-              <p className="w-auto text-base font-normal text-black/60 dark:text-white/60 sm:w-[180px]">
-                Email Address
-              </p>
-              <p className="font-normal block text-base text-black/60 dark:text-white/60">{email}</p>
-            </div>
-            <div>
-            <button
-              onClick={() => setIsOpen(!isOpen)}
-              className="cursor-pointer text-base font-normal text-black/[60%] underline dark:text-neutral-300"
-            >
-              Change
-            </button>
-            </div>
-          </div>
-          <div className=" relative mt-4 flex sm:hidden flex-col justify-end gap-y-2 sm:flex-row sm:justify-between sm:gap-y-0">
-            <div className="flex justify-between  flex-1 flex-wrap">
-              <p className="w-auto text-base font-normal text-black/60 dark:text-white/60 sm:w-[192px]">
-                Email Address
-              </p>
-              <p className="font-normal block text-base text-black/60 dark:text-white/60">{email}</p>
-            </div>
-            <button
-              onClick={() => setIsOpen(!isOpen)}
-              className="cursor-pointer absolute right-0  text-base font-normal text-black/[60%] underline dark:text-neutral-300"
-              style={{ top: "-36px" }}
-            >
-              Change
-            </button>
-          </div>
-        </>
-      )}
-    </>
-  );
-};
-
-export default Email;
-
-function EmailForm({
-  register,
-  errors,
-  isSubmitting,
-  isGuest,
-}: EmailFormProps) {
-  return (
-    <div>
-      <InputText
-        className="max-w-full"
-        id="email"
-        size="md"
-        {...register("email", {
-          required: "Email is required",
-          pattern: {
-            value: EMAIL_REGEX,
-            message: "Please enter a valid email address",
-          }
-        })}
-        errorMsg={errors?.email?.message as string}
-        label="Enter Email *"
-        placeholder="example@gmail.com"
-        readOnly={!isGuest}
-      />
-
-      {isGuest && (
-        <p className="mb-4 mt-6 font-outfit text-base font-normal text-black/[60%] dark:text-neutral-300">
-          Already have an account? No worries, just{" "}
-          <br className="block sm:hidden" />
-          <Link
-            aria-label="Go to Login Page"
-            className="text-base font-normal text-primary"
-            href="/customer/login"
-          >
-            log in.
-          </Link>
-        </p>
-      )}
-
-      <div className="mt-6 justify-self-end">
-        <ProceedToCheckout buttonName="Next" pending={isSubmitting} />
-      </div>
-    </div>
-  );
-}

+ 0 - 540
src/components/checkout/stepper/GuestAddAdressForm.tsx

@@ -1,540 +0,0 @@
-"use client";
-import { FC, useState, useEffect } from "react";
-import { useForm, useWatch } from "react-hook-form";
-import { AddressDataTypes } from "@/types/types";
-import { EMAIL, getLocalStorage } from "@/store/local-storage";
-import { IS_VALID_ADDRESS, IS_VALID_PHONE, IS_VALID_INPUT } from "@/utils/constants";
-import { isObject } from "@/utils/type-guards";
-import { useCheckout } from "@utils/hooks/useCheckout";
-import InputText from "@components/common/form/Input";
-import { ProceedToCheckout } from "./ProceedToCheckout";
-import CheckBox from "@components/theme/ui/element/Checkbox";
-import { useDispatch } from "react-redux";
-import { setCheckoutAddresses } from "@/store/slices/cart-slice";
-
-
-export const GuestAddAdressForm: FC<{
-  billingAddress?: AddressDataTypes | null;
-  shippingAddress?: AddressDataTypes | null;
-  currentStep?: string;
-}> = ({
-  billingAddress: initialBilling,
-  shippingAddress: initialShipping,
-  currentStep,
-}) => {
-    const email = getLocalStorage(EMAIL);
-    const dispatch = useDispatch();
-    const [isOpen, setIsOpen] = useState(currentStep !== "address");
-
-    const [billingAddress, setBillingAddress] = useState<AddressDataTypes | null>(
-      initialBilling ?? null
-    );
-    const [shippingAddress, setShippingAddress] =
-      useState<AddressDataTypes | null>(initialShipping ?? null);
-
-    const [prevStep, setPrevStep] = useState(currentStep);
-    if (currentStep !== prevStep) {
-      setPrevStep(currentStep);
-      setIsOpen(currentStep !== "address" && isObject(shippingAddress) && isObject(billingAddress));
-    }
-
-
-    const {
-      register,
-      control,
-      handleSubmit,
-      reset,
-      formState: { errors },
-    } = useForm({
-      mode: "onSubmit",
-      defaultValues: {
-        billing: {
-          email: billingAddress?.email ?? email ?? "",
-          firstName: billingAddress?.firstName || "",
-          lastName: billingAddress?.lastName || "",
-          companyName: billingAddress?.companyName || "",
-          address: billingAddress?.address || "",
-          country: billingAddress?.country || "IN",
-          state: billingAddress?.state || "UP",
-          city: billingAddress?.city || "",
-          postcode: billingAddress?.postcode || "",
-          phone: billingAddress?.phone || "",
-        },
-        shipping: {
-          email: shippingAddress?.email ?? email ?? "",
-          firstName: shippingAddress?.firstName || "",
-          lastName: shippingAddress?.lastName || "",
-          companyName: shippingAddress?.companyName || "",
-          address: shippingAddress?.address || "",
-          country: shippingAddress?.country || "IN",
-          state: shippingAddress?.state || "UP",
-          city: shippingAddress?.city || "",
-          postcode: shippingAddress?.postcode || "",
-          phone: shippingAddress?.phone || "",
-        },
-        useForShipping: true,
-      },
-    });
-
-    useEffect(() => {
-      if (initialBilling || initialShipping) {
-        const billing = initialBilling ?? null;
-        const shipping = initialShipping ?? null;
-        requestAnimationFrame(() => {
-          setBillingAddress(billing);
-          setShippingAddress(shipping);
-          setIsOpen(currentStep !== "address" && isObject(shipping) && isObject(billing));
-        });
-
-        reset({
-          billing: {
-            email: initialBilling?.email ?? email ?? "",
-            firstName: initialBilling?.firstName || "",
-            lastName: initialBilling?.lastName || "",
-            companyName: initialBilling?.companyName || "",
-            address: initialBilling?.address || "",
-            country: initialBilling?.country || "IN",
-            state: initialBilling?.state || "UP",
-            city: initialBilling?.city || "",
-            postcode: initialBilling?.postcode || "",
-            phone: initialBilling?.phone || "",
-          },
-          shipping: {
-            email: initialShipping?.email ?? email ?? "",
-            firstName: initialShipping?.firstName || "",
-            lastName: initialShipping?.lastName || "",
-            companyName: initialShipping?.companyName || "",
-            address: initialShipping?.address || "",
-            country: initialShipping?.country || "IN",
-            state: initialShipping?.state || "UP",
-            city: initialShipping?.city || "",
-            postcode: initialShipping?.postcode || "",
-            phone: initialShipping?.phone || "",
-          },
-          useForShipping: true,
-        });
-      }
-    }, [initialBilling, initialShipping, reset, email]);
-
-    const { isLoadingToSave, saveCheckoutAddress } = useCheckout();
-
-    const watchUseForShipping = useWatch({
-      control,
-      name: "useForShipping",
-      defaultValue: true,
-    });
-
-    const addGuestAddress = async (data: any) => {
-      const billing = data?.billing;
-      const shipping = data?.shipping;
-
-      const useForShipping = Boolean(data.useForShipping);
-      const shippingSource = useForShipping ? billing : shipping;
-
-      const payload: any = {
-        billingFirstName: billing.firstName,
-        billingLastName: billing.lastName,
-        billingEmail: billing.email ?? email ?? "",
-        billingAddress: billing.address,
-        billingCity: billing.city,
-        billingCountry: billing.country || "IN",
-        billingState: billing.state || "UP",
-        billingPostcode: billing.postcode,
-        billingPhoneNumber: billing.phone,
-        billingCompanyName: billing.companyName,
-        useForShipping,
-      };
-
-      if (!useForShipping) {
-        payload.shippingFirstName = shipping.firstName;
-        payload.shippingLastName = shipping.lastName;
-        payload.shippingEmail = shipping.email ?? email ?? "";
-        payload.shippingAddress = shipping.address;
-        payload.shippingCity = shipping.city;
-        payload.shippingCountry = shipping.country;
-        payload.shippingState = shipping.state;
-        payload.shippingPostcode = shipping.postcode;
-        payload.shippingPhoneNumber = shipping.phone;
-        payload.shippingCompanyName = shipping.companyName;
-      }
-
-      try {
-        await saveCheckoutAddress(payload as any);
-        dispatch(
-          setCheckoutAddresses({
-            billing: {
-              ...billing,
-              email: billing.email ?? email ?? "",
-            },
-            shipping: {
-              ...shippingSource,
-              email: shippingSource.email ?? email ?? "",
-            },
-          })
-        );
-        setBillingAddress({
-          ...billing,
-          email: billing.email ?? email ?? "",
-        } as AddressDataTypes);
-
-        setShippingAddress({
-          ...shippingSource,
-          email: shippingSource.email ?? email ?? "",
-        } as AddressDataTypes);
-
-        setIsOpen(true);
-      } catch (error) {
-        console.error("Failed to save checkout address", error);
-      }
-    };
-
-    const showSummary = isObject(shippingAddress) && isObject(billingAddress);
-
-    if (showSummary && isOpen) {
-      return (
-        <>
-          <div className="mt-4  items-start  hidden sm:flex">
-            <div className="flex flex-col justify-between w-full">
-              <div className="flex">
-                <p className="w-[184px] text-base font-normal text-black/60 dark:text-white/60">
-                  Billing Address
-                </p>
-                <div className="block cursor-pointer rounded-xl p-2 max-sm:rounded-lg">
-                  <div className="flex flex-col">
-                    <p className="text-base font-medium">
-                      {`${billingAddress?.firstName || ""} ${billingAddress?.lastName || ""
-                        }`}
-                    </p>
-                    <p className="text-base font-medium text-zinc-500">
-                      {`${billingAddress?.companyName || ""}`}
-                    </p>
-                  </div>
-                  <p className="mt-2 text-sm text-zinc-500 max-md:mt-2 max-sm:mt-0">
-                    {`${billingAddress?.address || ""}, ${billingAddress?.postcode || ""
-                      }`}
-                  </p>
-                  <p className="text-zinc-500">
-                    {billingAddress?.city || ""} {billingAddress?.state || ""},
-                    {billingAddress?.country || ""}
-                  </p>
-                  <p className="mt-2 text-sm text-zinc-500 max-md:mt-2 max-sm:mt-0">
-                    {`T: ${billingAddress?.phone || ""}`}
-                  </p>
-                </div>
-              </div>
-              <div className="flex">
-                <p className="w-[184px] text-base font-normal text-black/60 dark:text-white/60">
-                  Shipping Address
-                </p>
-                <div className="block cursor-pointer rounded-xl p-2 max-sm:rounded-lg">
-                  <div className="flex flex-col">
-                    <p className="text-base font-medium">
-                      {`${shippingAddress?.firstName || ""} ${shippingAddress?.lastName || ""
-                        }`}
-                    </p>
-                    <p className="text-base font-medium text-zinc-500">
-                      {`${shippingAddress?.companyName || ""}`}
-                    </p>
-                  </div>
-                  <p className="mt-2 text-sm text-zinc-500 max-md:mt-2 max-sm:mt-0">
-                    {`${shippingAddress?.address || ""}, ${shippingAddress?.postcode || ""
-                      }`}
-                  </p>
-                  <p className="text-zinc-500">
-                    {shippingAddress?.city || ""} {shippingAddress?.state || ""},
-                    {shippingAddress?.country || ""}
-                  </p>
-                  <p className="mt-2 text-sm text-zinc-500 max-md:mt-2 max-sm:mt-0">
-                    {`T: ${shippingAddress?.phone || ""}`}
-                  </p>
-                </div>
-              </div>
-            </div>
-
-            <button
-              onClick={() => {
-                setIsOpen(false);
-              }}
-              className="cursor-pointer text-base font-normal text-black/[60%] underline dark:text-neutral-300"
-            >
-              Change
-            </button>
-          </div>
-          <div className="mt-4 flex sm:hidden items-start justify-between relative">
-            <div className="flex flex-col justify-between w-full">
-              <div className="flex justify-between justify-between  flex-1 wrap">
-                <p className="w-[184px] text-base font-normal text-black/60 dark:text-white/60">
-                  Billing Address
-                </p>
-                <div className="block cursor-pointer rounded-xl p-2 max-sm:rounded-lg">
-                  <div className="flex flex-col">
-                    <p className="text-base font-medium">
-                      {`${billingAddress?.firstName || ""} ${billingAddress?.lastName || ""
-                        }`}
-                    </p>
-                    <p className="text-base font-medium text-zinc-500">
-                      {`${billingAddress?.companyName || ""}`}
-                    </p>
-                  </div>
-                  <p className="mt-2 text-sm text-zinc-500 max-md:mt-2 max-sm:mt-0">
-                    {`${billingAddress?.address || ""}, ${billingAddress?.postcode || ""
-                      }`}
-                  </p>
-                  <p className="text-zinc-500">
-                    {billingAddress?.city || ""} {billingAddress?.state || ""},
-                    {billingAddress?.country || ""}
-                  </p>
-                  <p className="mt-2 text-sm text-zinc-500 max-md:mt-2 max-sm:mt-0">
-                    {`T: ${billingAddress?.phone || ""}`}
-                  </p>
-                </div>
-              </div>
-              <div className="flex justify-between justify-between  flex-1 wrap">
-                <p className="w-[184px] text-base font-normal text-black/60 dark:text-white/60">
-                  Shipping Address
-                </p>
-                <div className="block cursor-pointer rounded-xl p-2 max-sm:rounded-lg">
-                  <div className="flex flex-col">
-                    <p className="text-base font-medium">
-                      {`${shippingAddress?.firstName || ""} ${shippingAddress?.lastName || ""
-                        }`}
-                    </p>
-                    <p className="text-base font-medium text-zinc-500">
-                      {`${shippingAddress?.companyName || ""}`}
-                    </p>
-                  </div>
-                  <p className="mt-2 text-sm text-zinc-500 max-md:mt-2 max-sm:mt-0">
-                    {`${shippingAddress?.address || ""}, ${shippingAddress?.postcode || ""
-                      }`}
-                  </p>
-                  <p className="text-zinc-500">
-                    {shippingAddress?.city || ""} {shippingAddress?.state || ""},
-                    {shippingAddress?.country || ""}
-                  </p>
-                  <p className="mt-2 text-sm text-zinc-500 max-md:mt-2 max-sm:mt-0">
-                    {`T: ${shippingAddress?.phone || ""}`}
-                  </p>
-                </div>
-              </div>
-            </div>
-
-            <button
-              onClick={() => {
-                setIsOpen(false);
-              }}
-              className="cursor-pointer absolute right-0 text-base font-normal text-black/[60%] underline dark:text-neutral-300"
-              style={{ top: "-36px" }}
-            >
-              Change
-            </button>
-          </div>
-        </>
-      );
-    }
-
-    return (
-      <form className="my-5" onSubmit={handleSubmit(addGuestAddress)}>
-        <div className="my-7 grid grid-cols-6 gap-4">
-          <InputText
-            {...register("billing.firstName", {
-              required: "First name is required",
-              pattern: {
-                value: IS_VALID_INPUT,
-                message: "Invalid First Name",
-              },
-            })}
-            className="col-span-6 xxs:col-span-3 mb-4"
-            errorMsg={errors?.billing?.firstName?.message}
-            label="First Name *"
-            size="md"
-          />
-          <InputText
-            {...register("billing.lastName", {
-              required: "Last name is required",
-              pattern: {
-                value: IS_VALID_INPUT,
-                message: "Invalid Last Name",
-              },
-            })}
-            className="col-span-6 xxs:col-span-3 mb-4"
-            errorMsg={errors?.billing?.lastName?.message}
-            label="Last Name *"
-            size="md"
-          />
-          <InputText
-            {...register("billing.companyName", {
-              pattern: {
-                value: IS_VALID_INPUT,
-                message: "Invalid Company Name",
-              },
-            })}
-            className="col-span-6 mb-2"
-            errorMsg={errors?.billing?.companyName?.message}
-            label="Company Name"
-            size="md"
-          />
-          <InputText
-            {...register("billing.address", {
-              required: "Address field is required",
-              pattern: {
-                value: IS_VALID_ADDRESS,
-                message: "Invalid Address",
-              },
-            })}
-            className="col-span-6 mb-4"
-            errorMsg={errors?.billing?.address?.message}
-            label="Street Address *"
-            size="md"
-          />
-          <InputText
-            {...register("billing.city", {
-              required: "City field is required",
-              pattern: {
-                value: IS_VALID_INPUT,
-                message: "Invalid City",
-              },
-            })}
-            className="col-span-6 xxs:col-span-3 mb-4"
-            errorMsg={errors?.billing?.city?.message}
-            label="City *"
-            size="md"
-          />
-          <InputText
-            {...register("billing.postcode", {
-              required: "Postcode field is required",
-              pattern: {
-                value: IS_VALID_INPUT,
-                message: "Invalid Postcode",
-              },
-            })}
-            className="col-span-6 xxs:col-span-3"
-            errorMsg={errors?.billing?.postcode?.message}
-            label="Zip Code *"
-            size="md"
-          />
-          <InputText
-            {...register("billing.phone", {
-              required: "Phone field is required",
-              pattern: {
-                value: IS_VALID_PHONE,
-                message: "Enter Valid Phone Number",
-              },
-            })}
-            type="tel"
-            inputMode="tel"
-            autoComplete="tel"
-            className="col-span-6"
-            errorMsg={errors?.billing?.phone?.message}
-            label="Phone *"
-            size="md"
-          />
-          <CheckBox
-            className="col-span-6 mt-3"
-            defaultValue={watchUseForShipping}
-            id="useForShipping"
-            label="Billing address is same as shipping address"
-            {...register("useForShipping")}
-          />
-        </div>
-
-        {!watchUseForShipping && (
-          <div className="my-7 grid grid-cols-6 gap-4">
-            <InputText
-              {...register("shipping.firstName", {
-                required: "First name is required",
-                pattern: {
-                  value: IS_VALID_INPUT,
-                  message: "Invalid First Name",
-                },
-              })}
-              className="col-span-3 mb-4"
-              errorMsg={errors?.shipping?.firstName?.message}
-              label="First Name *"
-              size="md"
-            />
-            <InputText
-              {...register("shipping.lastName", {
-                required: "Last name is required",
-                pattern: {
-                  value: IS_VALID_INPUT,
-                  message: "Invalid Last Name",
-                },
-              })}
-              className="col-span-3 mb-4"
-              errorMsg={errors?.shipping?.lastName?.message}
-              label="Last Name *"
-              size="md"
-            />
-            <InputText
-              {...register("shipping.companyName", {
-                pattern: {
-                  value: IS_VALID_INPUT,
-                  message: "Invalid Company Name",
-                },
-              })}
-              className="col-span-6 mb-4"
-              errorMsg={errors?.shipping?.companyName?.message}
-              label="Company Name"
-              size="md"
-            />
-            <InputText
-              {...register("shipping.address", {
-                required: "Address field is required",
-                pattern: {
-                  value: IS_VALID_ADDRESS,
-                  message: "Invalid Address",
-                },
-              })}
-              className="col-span-6 mb-4"
-              errorMsg={errors?.shipping?.address?.message}
-              label="Street Address *"
-              size="md"
-            />
-            <InputText
-              {...register("shipping.city", {
-                required: "City field is required",
-                pattern: {
-                  value: IS_VALID_INPUT,
-                  message: "Invalid City",
-                },
-              })}
-              className="col-span-3 mb-4"
-              errorMsg={errors?.shipping?.city?.message}
-              label="City *"
-              size="md"
-            />
-            <InputText
-              {...register("shipping.postcode", {
-                required: "Postcode field is required",
-                pattern: {
-                  value: IS_VALID_INPUT,
-                  message: "Invalid Postcode",
-                },
-              })}
-              className="col-span-3"
-              errorMsg={errors?.shipping?.postcode?.message}
-              label="Zip Code *"
-              size="md"
-            />
-            <InputText
-              {...register("shipping.phone", {
-                required: "Phone field is required",
-                pattern: {
-                  value: IS_VALID_PHONE,
-                  message: "Enter Valid Phone Number",
-                },
-              })}
-              className="col-span-6"
-              errorMsg={errors?.shipping?.phone?.message}
-              label="Phone *"
-              size="md"
-            />
-          </div>
-        )}
-
-        <div className="justify-self-end">
-          <ProceedToCheckout buttonName="Next" pending={isLoadingToSave} />
-        </div>
-      </form>
-    );
-  };

+ 0 - 73
src/components/checkout/stepper/ProceedToCheckout.tsx

@@ -1,73 +0,0 @@
-"use client";
-import LoadingDots from "@components/common/icons/LoadingDots";
-import clsx from "clsx";
-
-function SubmitButton({
-  availableForSale,
-  buttonName,
-  className,
-  pending,
-}: {
-  availableForSale: boolean;
-  buttonName: string;
-  className?: string;
-  pending: boolean;
-}) {
-  const buttonClasses =
-    "relative text-base w-fit cursor-pointer rounded-full px-8 py-3 font-bold border-white items-center justify-center bg-blue-600 tracking-wide text-white";
-  const disabledClasses = "cursor-wait opacity-60 hover:opacity-60";
-
-  if (!availableForSale) {
-    return (
-      <button 
-        type="button"
-        aria-disabled 
-        disabled
-        className={clsx(buttonClasses, disabledClasses)}
-      >
-        Processing...
-      </button>
-    );
-  }
-
-  return (
-    <button
-      type="submit"
-      disabled={pending}
-      aria-disabled={pending}
-      aria-label="Proceed to checkout"
-      className={clsx(
-        buttonClasses,
-        {
-          "hover:opacity-90": !pending,
-          [disabledClasses]: pending,
-        },
-        className,
-      )}
-    >
-      <div className="absolute left-0 ml-4">
-        {pending && <LoadingDots className="mb-3 bg-white" />}
-      </div>
-      {buttonName}
-    </button>
-  );
-}
-
-export function ProceedToCheckout({
-  buttonName,
-  className,
-  pending = false,
-}: {
-  buttonName: string;
-  className?: string;
-  pending: boolean;
-}) {
-  return (
-    <SubmitButton
-      availableForSale={!pending}
-      buttonName={buttonName}
-      className={className}
-      pending={pending}
-    />
-  );
-}

+ 0 - 169
src/components/checkout/stepper/index.tsx

@@ -1,169 +0,0 @@
-import Link from "next/link";
-import { useMemo } from "react";
-import LogoIcon from "@components/common/icons/LogoIcon";
-import Email from "./Email";
-import { GuestAddAdressForm } from "./GuestAddAdressForm";
-import Shipping from "./shipping";
-import Payment from "./payment";
-import Review from "./review";
-
-
-
-const { SITE_NAME } = process.env;
-
-interface Step {
-  id: number;
-  key: string;
-  title: string;
-  href: string;
-  component: React.ReactNode;
-}
-
-interface CheckOutProps {
-  billingAddress?: any;
-  shippingAddress?: any;
-  currentStep: string;
-  selectedPayment?: any;
-  selectedPaymentTitle?: string;
-  selectedShippingRate?: any;
-  selectedShippingRateTitle?: string;
-}
-
-export default function Stepper(
-  {
-    billingAddress,
-    shippingAddress,
-    selectedShippingRate,
-    selectedShippingRateTitle,
-    selectedPayment,
-    selectedPaymentTitle,
-    currentStep,
-  }: CheckOutProps
-) {
-
-  const steps = useMemo<Step[]>(() => {
-    return [
-      {
-        id: 1,
-        key: "email",
-        title: "Email",
-        href: "/checkout",
-        component: <Email />,
-      },
-      {
-        id: 2,
-        key: "address",
-        title: "Address",
-        href: "/checkout",
-        component:
-          <GuestAddAdressForm
-            billingAddress={billingAddress}
-            shippingAddress={shippingAddress}
-            currentStep={currentStep}
-          />
-      },
-      {
-        id: 3,
-        key: "shipping",
-        title: "Shipping",
-        href: "/checkout?step=address",
-        component: <Shipping
-          selectedShippingRate={selectedShippingRate}
-          currentStep={currentStep}
-        />,
-      },
-      {
-        id: 4,
-        key: "payment",
-        title: "Payment",
-        href: "/checkout?step=shipping",
-        component: (
-          <Payment
-            selectedPayment={selectedPayment}
-            currentStep={currentStep}
-          />
-        ),
-      },
-      {
-        id: 5,
-        key: "review",
-        title: "Review",
-        href: "/checkout?step=payment",
-        component: (
-          <Review
-            billingAddress={billingAddress}
-            selectedPaymentTitle={selectedPaymentTitle}
-            selectedShippingRate={selectedShippingRate}
-            selectedShippingRateTitle={selectedShippingRateTitle}
-            shippingAddress={shippingAddress}
-          />
-        ),
-      },
-    ];
-  }, [
-    currentStep,
-    billingAddress,
-    shippingAddress,
-    selectedShippingRate,
-    selectedPayment,
-    selectedPaymentTitle,
-    selectedShippingRateTitle,
-  ]);
-
-  const currentStepIndex = steps.findIndex((s) => s.key === currentStep);
-
-  const StepItem = ({ step }: { step: Step }) => {
-    const isActive = step.key === currentStep;
-    const isCompleted = step.id <= (steps[currentStepIndex]?.id || 1);
-
-    return (
-      <div key={step.id} className="flex w-full flex-col">
-        <div className="flex items-center justify-between font-outfit">
-          <div className="flex items-center gap-3">
-            <div
-              className={`flex h-6 w-6 items-center justify-center rounded-full text-sm font-medium ${isCompleted
-                ? "bg-blue-600 text-white"
-                : "bg-gray-200 text-neutral-900"
-                }`}
-            >
-              {step.id}
-            </div>
-            <span
-              className={`text-lg font-medium max-md:text-base ${isActive
-                ? "font-medium text-neutral-900 dark:text-neutral-300"
-                : "text-neutral-900 dark:text-white"
-                }`}
-            >
-              {step.title}
-            </span>
-          </div>
-        </div>
-
-        {isCompleted && <section className="relative">{step.component}</section>}
-      </div>
-    );
-  };
-
-  return (
-    <div className="mx-auto w-full">
-      <header className="pb-6 sm:py-6">
-        <Link
-          aria-label={SITE_NAME}
-          className="flex items-center gap-2 text-black dark:text-white md:pt-1 hidden lg:block"
-          href="/"
-        >
-          <LogoIcon />
-        </Link>
-        <h1 className="text-xl px-2 font-semibold block lg:hidden">Checkout</h1>
-      </header>
-
-      <div className="scrollbar-thin scrollbar-track-transparent scrollbar-thumb-gray-500 dark:scrollbar-thumb-neutral-300 h-[calc(100dvh-300px)] overflow-y-auto lg:h-[calc(100dvh-124px)]">
-        <div className="flex h-full flex-col gap-y-8 pl-2 pr-6 sm:px-3 sm:pr-10">
-          {steps.map((step) => (
-            <StepItem key={step.id} step={step} />
-          ))}
-        </div>
-      </div>
-    </div>
-  );
-}

+ 0 - 222
src/components/checkout/stepper/payment/PaymentMethod.tsx

@@ -1,222 +0,0 @@
-"use client";
-
-import { Controller, FieldValues, useForm } from "react-hook-form";
-import { cn } from "@heroui/theme";
-import { Radio, RadioGroup } from "@heroui/radio";
-import { useState } from "react";
-import { useCustomToast } from "@utils/hooks/useToast";
-import { useCheckout } from "@utils/hooks/useCheckout";
-import { ProceedToCheckout } from "../ProceedToCheckout";
-import { CustomRadioProps } from "@components/checkout/type";
-import { useDispatch } from "react-redux";
-import { updateCart } from "@/store/slices/cart-slice";
-
-export default function PaymentMethod({
-  selectedPayment,
-  methods,
-  currentStep,
-}: {
-  selectedPayment?: string;
-  methods: any;
-  currentStep?: string;
-}) {
-  const { showToast } = useCustomToast();
-  const { isPaymentLoading, saveCheckoutPayment } = useCheckout();
-  const [isOpen, setIsOpen] = useState(currentStep !== "payment");
-
-  const [prevValues, setPrevValues] = useState({
-    currentStep,
-    selectedPayment,
-  });
-
-  if (
-    currentStep !== prevValues.currentStep ||
-    selectedPayment !== prevValues.selectedPayment
-  ) {
-    setPrevValues({ currentStep, selectedPayment });
-    if (currentStep === "payment") {
-      setIsOpen(false);
-    } else if (selectedPayment) {
-      setIsOpen(true);
-    }
-  }
-
-  const { handleSubmit, control } = useForm({
-    mode: "onSubmit",
-    defaultValues: {
-      method: selectedPayment ?? "",
-    },
-  });
-
-  const selectedMethodLabelPrior = methods?.find(
-    (method: any) => method?.method === selectedPayment,
-  )?.title;
-
-  const dispatch = useDispatch();
-  const onSubmit = async (data: FieldValues) => {
-    if (!data?.method) {
-      showToast("Please Choose the Payment Method", "warning");
-      return;
-    }
-    try {
-      const selectedMethod = methods?.find(
-        (m: any) => m?.method === data?.method,
-      );
-      if (selectedMethod) {
-        dispatch(
-          updateCart({
-            paymentMethod: selectedMethod?.method || "",
-            paymentMethodTitle: selectedMethod?.title || "",
-          }),
-        );
-      }
-      await saveCheckoutPayment(data.method);
-    } catch {
-      showToast("Failed to save payment method. Please try again.");
-    }
-  };
-
-  return (
-    <>
-      {selectedPayment ? (
-        isOpen ? (
-          <>
-            <div className="mt-4  justify-between hidden sm:flex ">
-              <div className="flex">
-                <p className="w-auto text-base font-normal text-black/60 dark:text-white/60 sm:w-[192px]">
-                  Payment Method
-                </p>
-                <p className="text-base font-normal">
-                  {selectedMethodLabelPrior as string}
-                </p>
-              </div>
-
-              <button
-                onClick={() => {
-                  setIsOpen(false);
-                }}
-                className="cursor-pointer text-base font-normal text-black/60 underline dark:text-neutral-300"
-              >
-                Change
-              </button>
-            </div>
-            <div className="mt-4 flex sm:hidden justify-between relative">
-              <div className="flex justify-between justify-between  flex-1 wrap">
-                <p className="w-auto text-base font-normal text-black/60 dark:text-white/60 sm:w-[192px]">
-                  Payment Method
-                </p>
-                <p className="text-base font-normal">
-                  {selectedMethodLabelPrior as string}
-                </p>
-              </div>
-
-              <button
-                onClick={() => {
-                  setIsOpen(false);
-                }}
-                className="cursor-pointer absolute right-0 text-base font-normal text-black/60 underline dark:text-neutral-300"
-                style={{ top: "-36px" }}
-              >
-                Change
-              </button>
-            </div>
-          </>
-        ) : (
-          <div className="mt-6">
-            <form onSubmit={handleSubmit(onSubmit)}>
-              <Controller
-                control={control}
-                name="method"
-                render={({ field }) => (
-                  <RadioGroup
-                    {...field}
-                    label=""
-                    value={field.value ?? ""}
-                    onValueChange={field.onChange}
-                  >
-                    {methods?.map((method: any) => (
-                      <CustomRadio
-                        key={method?.method}
-                        className="my-1 border border-solid border-neutral-300 dark:border-neutral-500"
-                        description={method?.description}
-                        value={method?.method}
-                      >
-                        <span className="text-neutral-700 dark:text-white">
-                          {method?.title}
-                        </span>
-                      </CustomRadio>
-                    ))}
-                  </RadioGroup>
-                )}
-              />
-
-              <div className="my-6 justify-self-end">
-                <ProceedToCheckout
-                  buttonName="Pay Now"
-                  pending={isPaymentLoading}
-                />
-              </div>
-            </form>
-          </div>
-        )
-      ) : (
-        <div className="mt-6">
-          <form onSubmit={handleSubmit(onSubmit)}>
-            <Controller
-              control={control}
-              name="method"
-              render={({ field }) => (
-                <RadioGroup
-                  {...field}
-                  label=""
-                  value={field.value ?? ""}
-                  onValueChange={field.onChange}
-                >
-                  {methods?.map((method: any) => (
-                    <CustomRadio
-                      key={method?.method}
-                      className="my-1 border border-solid border-neutral-300 dark:border-neutral-500"
-                      description={method?.description}
-                      value={method?.method}
-                    >
-                      <span className="text-neutral-700 dark:text-white">
-                        {method?.title}
-                      </span>
-                    </CustomRadio>
-                  ))}
-                </RadioGroup>
-              )}
-            />
-
-            <div className="my-6 justify-self-end">
-              <ProceedToCheckout
-                buttonName="Pay Now"
-                pending={isPaymentLoading}
-              />
-            </div>
-          </form>
-        </div>
-      )}
-    </>
-  );
-}
-
-const CustomRadio = (props: CustomRadioProps) => {
-  const { children, ...otherProps } = props;
-
-  return (
-    <Radio
-      {...otherProps}
-      classNames={{
-        base: cn(
-          "inline-flex m-0 bg-transparent hover:bg-transparent items-center",
-          "flex-row items-baseline max-w-full cursor-pointer rounded-lg gap-4 p-4 border-2 border-transparent",
-          "data-[selected=true]:border-primary",
-        ),
-        hiddenInput: "peer absolute h-0 w-0 opacity-0",
-      }}
-    >
-      {children}
-    </Radio>
-  );
-};

+ 0 - 37
src/components/checkout/stepper/payment/index.tsx

@@ -1,37 +0,0 @@
-"use client";
-
-import { CartCheckoutPageSkeleton } from "@/components/common/skeleton/CheckoutSkeleton";
-import { useQuery } from "@apollo/client/react";
-import PaymentMethod from "./PaymentMethod";
-import { FC } from "react";
-import { GET_CHECKOUT_PAYMENT_METHODS } from "@/graphql";
-import { getCartToken } from "@/utils/getCartToken";
-import { CheckoutPaymentMethodsData } from "@/types/checkout/type";
-
-const Payment: FC<{
-  selectedPayment?: {
-    method: string;
-    methodTitle?: string;
-  };
-  currentStep?: string;
-}> = ({ selectedPayment, currentStep }) => {
-  const token = getCartToken();
-  const { data, loading: isLoading } = useQuery<CheckoutPaymentMethodsData>(GET_CHECKOUT_PAYMENT_METHODS, {
-    variables: { token: token || "" },
-    skip: !token,
-    fetchPolicy: "cache-first",
-    nextFetchPolicy: "cache-first",
-  });
-
-  if (isLoading && !data) return <CartCheckoutPageSkeleton />;
-
-  return (
-    <PaymentMethod
-      methods={data?.collectionPaymentMethods}
-      selectedPayment={selectedPayment as any}
-      currentStep={currentStep}
-    />
-  );
-};
-
-export default Payment;

+ 0 - 101
src/components/checkout/stepper/review/OrderReview.tsx

@@ -1,101 +0,0 @@
-"use client";
-import { useForm } from "react-hook-form";
-import {
-  AddressDataTypes,
-} from "@/types/types";
-import { isObject } from "@/utils/type-guards";
-import { useCheckout } from "@utils/hooks/useCheckout";
-import { ProceedToCheckout } from "../ProceedToCheckout";
-export default function OrderReview({
-  selectedPaymentTitle,
-  shippingAddress,
-  billingAddress,
-  selectedShipping : _selectedShipping,
-  selectedShippingRateTitle,
-}: {
-  selectedPaymentTitle?: string;
-  shippingAddress?: AddressDataTypes;
-  billingAddress?: AddressDataTypes;
-  selectedShipping?: string;
-  selectedShippingRateTitle?: string;
-}) {
-  const { isPlaceOrder, savePlaceOrder } = useCheckout();
-  const { handleSubmit } = useForm();
-  const onSubmit = () => {
-    savePlaceOrder();
-  };
-
-  return (
-    <div className="mt-4 flex-col mb-20 sm:mb-0">
-      <div className="relative">
-        {isObject(shippingAddress) && (
-          <table className="w-full text-left text-sm text-gray-500 dark:text-gray-400">
-            <tbody>
-              <tr className="">
-                <td className="py-4">Contact</td>
-                <th
-                  className="break-all px-6 py-4 font-medium text-gray-900 dark:text-white"
-                  scope="row"
-                >
-                  {shippingAddress?.email}
-                </th>
-              </tr>
-              <tr className="">
-                <td className="py-4">Billing to</td>
-                <th
-                  className="break-all px-6 py-4 font-medium text-gray-900 dark:text-white"
-                  scope="row"
-                >
-                  {billingAddress?.firstName}, {billingAddress?.lastName},{" "}
-                  {billingAddress?.address}, {billingAddress?.city},{" "}
-                  {billingAddress?.state}, {billingAddress?.postcode},{" "}
-                  {billingAddress?.country}
-                </th>
-              </tr>
-              <tr className="">
-                <td className="py-4">Ship to</td>
-                <th
-                  className="break-all px-6 py-4 font-medium text-gray-900 dark:text-white"
-                  scope="row"
-                >
-                  {shippingAddress?.firstName}, {shippingAddress?.lastName},{" "}
-                  {shippingAddress?.address}, {shippingAddress?.city},{" "}
-                  {shippingAddress?.state}, {shippingAddress?.postcode},{" "}
-                  {shippingAddress?.country}
-                </th>
-              </tr>
-              <tr className="">
-                <td className="py-4">Method</td>
-                <th
-                  className="break-all px-6 py-4 font-medium text-gray-900 dark:text-white"
-                  scope="row"
-                >
-                  {selectedShippingRateTitle}
-                </th>
-              </tr>
-              <tr className="">
-                <td className="py-4">Payment</td>
-                <th
-                  className="break-all px-6 py-4 font-medium text-gray-900 dark:text-white"
-                  scope="row"
-                >
-                  {selectedPaymentTitle}
-                </th>
-              </tr>
-            </tbody>
-          </table>
-        )}
-      </div>
-      <div className="flex flex-col gap-6">
-        <form onSubmit={handleSubmit(onSubmit)}>
-          <div className="justify-self-end">
-            <ProceedToCheckout
-              buttonName="Place Order"
-              pending={isPlaceOrder}
-            />
-          </div>
-        </form>
-      </div>
-    </div>
-  );
-}

+ 0 - 29
src/components/checkout/stepper/review/index.tsx

@@ -1,29 +0,0 @@
-import { FC } from "react";
-import { AddressDataTypes } from "@/types/types";
-import OrderReview from "./OrderReview";
-
-export const Review: FC<{
-  selectedPaymentTitle?: string;
-  shippingAddress?: AddressDataTypes;
-  billingAddress?: AddressDataTypes;
-  selectedShippingRate?: string;
-  selectedShippingRateTitle?: string;
-}> = ({
-  selectedPaymentTitle,
-  shippingAddress,
-  billingAddress,
-  selectedShippingRate,
-  selectedShippingRateTitle,
-}) => {
-  return (
-    <OrderReview
-      billingAddress={billingAddress}
-      selectedPaymentTitle={selectedPaymentTitle}
-      selectedShipping={selectedShippingRate}
-      selectedShippingRateTitle={selectedShippingRateTitle}
-      shippingAddress={shippingAddress}
-    />
-  );
-};
-
-export default Review;

+ 0 - 219
src/components/checkout/stepper/shipping/ShippingMethod.tsx

@@ -1,219 +0,0 @@
-"use client";
-
-import { FieldValues, useForm, Controller } from "react-hook-form";
-import { cn } from "@heroui/theme";
-import { Radio, RadioGroup } from "@heroui/radio";
-import { useState } from "react";
-import { ProceedToCheckout } from "../ProceedToCheckout";
-import { useCustomToast } from "@utils/hooks/useToast";
-import { useCheckout } from "@utils/hooks/useCheckout";
-import { CustomRadioProps, ShippingMethodType } from "@components/checkout/type";
-import { useDispatch } from "react-redux";
-import { updateCart } from "@/store/slices/cart-slice";
-
-
-export default function ShippingMethod({
-  shippingMethod,
-  selectedShippingRate,
-  currentStep
-}: {
-  shippingMethod?: ShippingMethodType[];
-  selectedShippingRate?: any;
-  methodDesc?: string;
-  currentStep?: string;
-}) {
-  const { isSaving, saveCheckoutShipping } = useCheckout();
-  const { showToast } = useCustomToast();
-  const [isOpen, setIsOpen] = useState(currentStep !== "shipping");
-
-  const [prevValues, setPrevValues] = useState({ currentStep, selectedShippingRate });
-
-  if (currentStep !== prevValues.currentStep || selectedShippingRate !== prevValues.selectedShippingRate) {
-    setPrevValues({ currentStep, selectedShippingRate });
-    if (currentStep === "shipping") {
-      setIsOpen(false);
-    } else if (selectedShippingRate) {
-      setIsOpen(true);
-    }
-  }
-  const { control, handleSubmit } = useForm({
-    mode: "onSubmit",
-    defaultValues: {
-      method: selectedShippingRate ?? "",
-    },
-  });
-  const selectedMethodTitle = shippingMethod?.find(
-    (method) => method.method === selectedShippingRate
-  )?.label;
-  const selectedMethodPrice = shippingMethod?.find(
-    (method) => method.method === selectedShippingRate
-  )?.price;
-
-  const dispatch = useDispatch();
-  const onSubmit = async (data: FieldValues) => {
-    if (!data?.method) {
-      showToast("Please Choose the Shipping Method", "warning");
-      return;
-    }
-    try {
-      const selectedRate = shippingMethod?.find(m => m.method === data.method);
-      if (selectedRate) {
-        dispatch(updateCart({
-          // shippingMethod: selectedRate?.method || "",
-          selectedShippingRate: selectedRate?.method || "",
-          selectedShippingRateTitle: selectedRate?.label || "",
-        }));
-      }
-      await saveCheckoutShipping(data?.method);
-    } catch {
-      showToast("Failed to save shipping method. Please try again.");
-    }
-  };
-
-
-  return (
-    <div>
-      {(selectedShippingRate) ? (
-        isOpen ? (
-          <>
-            <div className="mt-4  justify-between hidden sm:flex">
-              <div className="flex">
-                <p className="w-auto text-base font-normal text-black/60 dark:text-white/60 sm:w-[192px]">
-                  Shipping Method
-                </p>
-                <p className="text-base font-normal">{selectedMethodTitle} (${selectedMethodPrice})</p>
-              </div>
-              <div className="flex">
-                <button
-                  onClick={() => {
-                    setIsOpen(!isOpen);
-                  }}
-                  className="cursor-pointer text-base font-normal text-black/[60%] underline dark:text-neutral-300"
-                >
-                  Change
-                </button>
-              </div>
-            </div>
-
-            <div className="mt-4 block sm:hidden flex flex-col justify-between sm:flex-row relative  ">
-              <div className="flex justify-between  flex-1 wrap">
-                <p className="w-auto text-base font-normal text-black/60 dark:text-white/60 sm:w-[192px]">
-                  Shipping Method
-                </p>
-                <p className="text-base font-normal">{selectedMethodTitle} (${selectedMethodPrice})</p>
-              </div>
-
-              <button
-                onClick={() => {
-                  setIsOpen(!isOpen);
-                }}
-                className="cursor-pointer absolute right-0  text-base font-normal text-black/[60%] underline dark:text-neutral-300"
-                style={{ top: "-36px" }}
-              >
-                Change
-              </button>
-            </div>
-          </>
-
-        ) : (
-          <form className="mt-6" onSubmit={handleSubmit(onSubmit)}>
-            <div className="flex flex-col gap-5">
-              {Array.isArray(shippingMethod) && (
-                <Controller
-                  control={control}
-                  name="method"
-                  render={({ field }) => (
-                    <RadioGroup
-                      {...field}
-                      label=""
-                      value={field.value ?? ""}
-                      onValueChange={field.onChange}
-                    >
-                      {shippingMethod.map((method: any) => (
-                        <CustomRadio
-                          key={method?.code}
-                          className="inset-0 my-1 border border-solid border-neutral-300 dark:border-neutral-500"
-                          description={"$" + method?.price}
-                          value={method?.method}
-                        >
-                          <span className="text-neutral-700 dark:text-white">
-                            {method?.label}
-                          </span>
-                        </CustomRadio>
-                      ))}
-                    </RadioGroup>
-                  )}
-                />
-              )}
-            </div>
-
-            <div className="my-6 justify-self-end">
-              <ProceedToCheckout buttonName="Next" pending={isSaving} />
-            </div>
-          </form>
-        )
-      ) : (
-        <form className="mt-6" onSubmit={handleSubmit(onSubmit)}>
-          <div className="flex flex-col gap-5">
-            {Array.isArray(shippingMethod) && (
-              <Controller
-                control={control}
-                name="method"
-                render={({ field }) => {
-                  return (
-                    <RadioGroup
-                      {...field}
-                      label=""
-                      value={field.value ?? ""}
-                      onValueChange={(value) => {
-                        field.onChange(value);
-                      }}
-                    >
-                      {shippingMethod.map((method : any) => (
-                        <CustomRadio
-                          key={method?.code}
-                          className="inset-0 my-1 border border-solid border-neutral-300 dark:border-neutral-500"
-                          description={"$" + method?.price}
-                          value={method?.method}
-                        >
-                          <span className="text-neutral-700 dark:text-white">
-                            {method?.label}
-                          </span>
-                        </CustomRadio>
-                      ))}
-                    </RadioGroup>
-                  )
-                }}
-              />
-            )}
-          </div>
-
-          <div className="my-6 justify-self-end">
-            <ProceedToCheckout buttonName="Next" pending={isSaving} />
-          </div>
-        </form>
-      )}
-    </div>
-  );
-}
-
-const CustomRadio = (props: CustomRadioProps) => {
-  const { children, className, ...otherProps } = props;
-
-  return (
-    <Radio
-      {...otherProps}
-      classNames={{
-        base: cn(
-          "inline-flex m-0 bg-transparent hover:bg-transparent items-center",
-          "flex-row items-baseline max-w-full cursor-pointer rounded-lg gap-4 p-4 border-2 border-transparent",
-          "data-[selected=true]:border-primary",
-          className
-        ),
-        hiddenInput: "peer absolute h-0 w-0 opacity-0",
-      }}
-    >
-      {children}
-    </Radio>
-  );
-};

+ 0 - 37
src/components/checkout/stepper/shipping/index.tsx

@@ -1,37 +0,0 @@
-"use client";
-
-import { CartCheckoutPageSkeleton } from "@/components/common/skeleton/CheckoutSkeleton";
-import { useQuery } from "@apollo/client/react";
-import ShippingMethod from "./ShippingMethod";
-import { FC } from "react";
-import { SelectedShippingRateType, GetCheckoutShippingRatesData } from "@/types/checkout/type";
-import { GET_CHECKOUT_SHIPPING_RATES } from "@/graphql";
-import { getCartToken } from "@/utils/getCartToken";
-
-const Shipping: FC<{
-  selectedShippingRate?: SelectedShippingRateType;
-  currentStep?: string;
-}> = ({ selectedShippingRate, currentStep }) => {
-  const token = getCartToken();
-
-  const { data, loading: isLoading } = useQuery<GetCheckoutShippingRatesData>(GET_CHECKOUT_SHIPPING_RATES, {
-    variables: { token: token || "" },
-    skip: !token,
-    fetchPolicy: "cache-first",
-    nextFetchPolicy: "cache-first",
-  });
-
-  if (isLoading && !data) {
-    return <CartCheckoutPageSkeleton />;
-  }
-  return (
-    <ShippingMethod
-      shippingMethod={data?.collectionShippingRates}
-      selectedShippingRate={selectedShippingRate}
-      methodDesc={selectedShippingRate?.methodDescription}
-      currentStep={currentStep}
-    />
-  );
-};
-
-export default Shipping;

+ 0 - 73
src/components/checkout/success/EmptyCart.tsx

@@ -1,73 +0,0 @@
-"use client";
-import { clearCart } from "@/store/slices/cart-slice";
-import clsx from "clsx";
-import { useRouter } from "next/navigation";
-import { useEffect } from "react";
-import CheckSign from "@components/common/icons/CheckSign";
-import { useFormStatus } from "react-dom";
-import { useDispatch } from "react-redux";
-import LoadingDots from "@components/common/icons/LoadingDots";
-import dynamic from "next/dynamic";
-
-const OrderDetail = dynamic(() => import("@/components/cart/OrderDetail"), {
-  loading: () => (
-    <div className="max-w-sm animate-pulse" role="status">
-      <div className="mb-4 h-8 w-48 rounded-full bg-gray-200 dark:bg-gray-700" />
-      <span className="sr-only">Loading...</span>
-    </div>
-  )
-});
-const EmptyCartPage = () => {
-  return (
-    <div className="flex min-h-[calc(100vh-450px)] px-4 items-center">
-      <div className="flex w-full flex-col items-center justify-center overflow-hidden">
-        <CheckSign className="sm:h-38 sm:w-38 h-28 w-28" />
-        <OrderDetail />
-        <ClearCartButton buttonName="Continue shopping" redirect="/" />
-      </div>
-    </div>
-  );
-};
-
-export default EmptyCartPage;
-
-function SubmitButton({
-  buttonName,
-  redirectNav,
-}: {
-  buttonName: string;
-  redirectNav: string;
-}) {
-  const router = useRouter();
-  const dispatch = useDispatch();
-  const { pending } = useFormStatus();
-  useEffect(() => {
-    dispatch(clearCart());
-  }, []);
-  return (
-    <button
-      className={clsx(
-        "sm:my-3 my-0 w-auto items-center cursor-pointer justify-center rounded-full border-white bg-blue-600 px-12 py-4 text-sm font-bold tracking-wide text-white",
-        pending ? "cursor-wait" : "cursor-pointer"
-      )}
-      disabled={pending}
-      type="submit"
-      onClick={() => {
-        dispatch(clearCart());
-        router.replace(redirectNav);
-      }}
-    >
-      {pending ? <LoadingDots className="bg-white" /> : buttonName}
-    </button>
-  );
-}
-
-export function ClearCartButton({
-  buttonName,
-  redirect,
-}: {
-  buttonName: string;
-  redirect: string;
-}) {
-  return <SubmitButton buttonName={buttonName} redirectNav={redirect} />;
-}

+ 0 - 24
src/components/checkout/type.ts

@@ -1,24 +0,0 @@
-import { Radio } from "@heroui/radio";
-import { FieldErrors, UseFormRegister } from "react-hook-form";
-
-export type CustomRadioProps = {
-  children: React.ReactNode;
-  description?: string;
-  value: string;
-} & typeof Radio.defaultProps;
-
-export type ShippingMethodType = {
-  method?: string;
-  label?: string;
-  price?: number | string;
-  code?: string;
-};
-
-export type EmailFormValues = { email: string };
-
-export type EmailFormProps = {
-  register: UseFormRegister<EmailFormValues>;
-  errors: FieldErrors<EmailFormValues>;
-  isSubmitting: boolean;
-  isGuest: boolean;
-};

+ 1 - 1
src/components/common/AddToCartModal/FooterBtnInAddToCartModal.tsx

@@ -14,7 +14,7 @@ export default function FooterBtnInAddToCartModal({
     onAddToCart: () => void
 }) {
 
-    const btnClass = 'flex-initial flex items-center justify-center w-40 h-11.5 rounded-4xl text-ly-16 font-bold text-white';
+    const btnClass = 'flex-none flex items-center justify-center w-40 h-11.5 rounded-4xl text-ly-16 font-bold text-white';
     return (
         <div className="flex justify-between">
             {isAvailable ? 

+ 67 - 0
src/components/common/ConfirmModal.tsx

@@ -0,0 +1,67 @@
+"use client";
+
+import {useState} from "react";
+import { AnimatePresence , motion } from "framer-motion";
+
+export interface ConfirmModalProps { 
+    isShow: boolean;
+    title: string; 
+    content: string;
+    okHandler?: () => void; 
+    cancelHandler?: () => void;
+    noOk?: boolean; 
+    noCancel?: boolean; 
+}
+
+export function ConfirmModal({
+    isShow,
+    title,
+    content,
+    okHandler,
+    cancelHandler,
+    noOk,
+    noCancel,
+}: ConfirmModalProps) {
+
+    const close = (result: boolean) => {
+        if(result) {
+            okHandler && okHandler();
+        } else { 
+            cancelHandler && cancelHandler();
+        }
+    };
+
+  return (
+    <AnimatePresence >
+        {isShow && 
+            <motion.div
+                layout
+                initial={{ opacity: 0, }}
+                animate={{ opacity: 1 }}
+                exit={{ opacity: 0 }}
+                className="fixed z-1000 flex justify-center items-center bg-black/50 inset-0"
+                onClick={(e) => e.stopPropagation()}
+            >
+                {/* <div className="fixed z-1000 flex justify-center items-center bg-black/40 inset-0"> */}
+                    <div className="w-4/5 p-4 bg-white rounded-lg">
+                        <h3 className="text-center text-ly-18 font-bold mb-6">{title}</h3>
+                        <p className="text-ly-14 font-medium">{content}</p>
+                        <div className="w-full flex justify-end gap-3 mt-6">
+                            {!noOk && 
+                                <button className="w-20 h-8 flex justify-center items-center rounded-xs bg-ly-green text-ly-12 font-medium text-white" 
+                                    onClick={() => close(true)}
+                                >OK</button>
+                            }
+                            {!noCancel && 
+                                <button className="w-20 h-8 flex justify-center items-center rounded-xs bg-ly-gray text-ly-12 font-medium" 
+                                    onClick={() => close(false)}
+                                >Cancel</button>}
+                        </div>
+                    </div>
+                {/* </div> */}
+            </motion.div>
+        }
+    </AnimatePresence>
+  );
+}
+

+ 74 - 0
src/components/common/CountDown.tsx

@@ -0,0 +1,74 @@
+"use client";
+
+import { useEffect, useState, useRef, useCallback } from "react";
+
+
+export default function CountDown({
+    endTime,
+    boxClassName,
+    timerClassName,
+    onCountDownEnd
+}: {
+    endTime: number;
+    boxClassName?: string;
+    timerClassName?: string;
+    onCountDownEnd: () => void;
+}) { 
+    const defaultBoxClassName = boxClassName || "flex items-center gap-2 flex-none text-ly-14 text-white";
+    const defaultTimerClassName = timerClassName || "flex items-center justify-center w-6 h-6 bg-white rounded-xs text-ly-12 font-medium text-black";
+    
+    const timerRef = useRef<number | null>(null);
+    const [seconds, setSeconds] = useState(() => {
+        return Math.max(0, Math.floor((endTime - Date.now()) / 1000));
+    }); // 倒计时的总秒数
+
+    const clearTimer = () => {
+        if (timerRef.current !== null) {
+            clearInterval(timerRef.current);
+            timerRef.current = null;
+        }
+    };
+
+    const start = useCallback(() => {
+        if (timerRef.current) return;
+
+        timerRef.current = window.setInterval(() => {
+            setSeconds((prev) => {
+                if (prev <= 1) {
+                clearTimer();
+                onCountDownEnd();
+                return 0;
+                }
+                return prev - 1;
+            });
+        }, 1000);
+    }, [onCountDownEnd]);
+
+    const format = (n: number) => {
+        const h = Math.floor(n / 3600);
+        const m = Math.floor((n % 3600) / 60);
+        const s = n % 60;
+        return {
+            hour: String(h).padStart(2, "0"),
+            minute: String(m).padStart(2, "0"),
+            second: String(s).padStart(2, "0"),
+        }
+    };
+
+    useEffect(() => {
+        start();
+        return () => clearTimer();
+    }, [start]);
+
+    const { hour, minute, second } = format(seconds);
+
+    return (
+        <div className={defaultBoxClassName}>
+            <span className={defaultTimerClassName}>{hour}</span>
+            :
+            <span className={defaultTimerClassName}>{minute}</span>
+            :
+            <span className={defaultTimerClassName}>{second}</span>
+        </div>
+    );
+}

+ 5 - 4
src/components/customer/credentials/CredentialModal.tsx

@@ -12,14 +12,14 @@ import { useCustomToast } from '@/utils/hooks/useToast';
 import { useBodyScrollLock } from "@utils/hooks/useBodyScrollLock";
 import OpenAuth from "../OpenAuth";
 import { isObject } from '@/utils/type-guards';
-import { useGuestCartToken } from "@utils/hooks/useGuestCartToken";
 import LoadingDots from "@components/common/icons/LoadingDots";
 import { logoutAction } from "@utils/actions";
 import { useAppDispatch, useAppSelector } from "@/store/hooks";
 import { clearUser } from "@/store/slices/user-slice";
 import { clearCart } from "@/store/slices/cart-slice";
 import { EMAIL, removeFromLocalStorage } from "@/store/local-storage";
-
+import { setCookie, deleteCookie } from "@utils/cookie-tools";
+import { IS_GUEST,GUEST_CART_TOKEN,GUEST_CART_ID } from "@/utils/constants";
 
 export default function CredentialModal({
   children,
@@ -51,7 +51,6 @@ export default function CredentialModal({
   const dispatch = useAppDispatch();
   const { showToast } = useCustomToast();
 
-  const { resetGuestToken } = useGuestCartToken();
 
   useBodyScrollLock(finalIsOpen );
 
@@ -77,7 +76,9 @@ export default function CredentialModal({
         redirect: false,
       });
 
-      await resetGuestToken();
+      deleteCookie(GUEST_CART_TOKEN);
+      deleteCookie(GUEST_CART_ID);
+      setCookie(IS_GUEST, 'true');
       dispatch(clearUser());
       dispatch(clearCart());
       showToast("You are logged out successfully!", "success");

+ 1 - 1
src/components/layout/navbar/MobileMenuTrigger.tsx

@@ -11,7 +11,7 @@ export default function MobileMenuTrigger({ menuData }: { menuData: any[] }) {
   return (
     <>
       {/* 汉堡按钮 */}
-      <button className="flex-initial w-6 h-6" onClick={toggleMenu}>
+      <button className="flex-none w-6 h-6" onClick={toggleMenu}>
         <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none">
           <rect x="0" y="0" width="24" height="24" fill="#FFFFFF" fillOpacity="0" />
           <path stroke="currentColor" strokeWidth="1.5" d="M4 5L20 5" />

+ 16 - 0
src/components/layout/navbar/TitleHeader.tsx

@@ -0,0 +1,16 @@
+"use client";
+import { useRouter } from 'next/navigation';
+
+export default function TitleHeader({title}: {title: string}) {
+    const router = useRouter();
+    return (
+        <header className="sticky top-0 z-10 w-full">
+            <div className="relative w-full h-15 flex items-center justify-center bg-neutral-50 text-ly-16">
+                <div className="absolute left-4 w-6 h-6" onClick={() => router.back()}>
+                    <svg className="block 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="M14.5 18L8.5 12L14.5 6"></path></svg>
+                </div>
+                {title}
+            </div>
+        </header>
+    );
+}

+ 1 - 1
src/components/layout/navbar/index.tsx

@@ -55,7 +55,7 @@ export default async function Navbar() {
                 <MobileMenuTrigger menuData={menuData} />
               </Suspense>
               <Link
-                className="flex-initial h-9 w-36.5"
+                className="flex-none h-9 w-36.5"
                 href="/"
                 aria-label="Go to homepage"
               >

+ 0 - 76
src/components/theme/ui/AddToCartButton.tsx

@@ -1,76 +0,0 @@
-"use client";
-
-import ShoppingCartIcon from "@components/common/icons/ShoppingCartIcon";
-import clsx from "clsx";
-import Link from "next/link";
-import { useAddProduct } from "@utils/hooks/useAddToCart";
-import { useAppSelector } from "@/store/hooks";
-import LoadingDots from "@components/common/icons/LoadingDots";
-import { useCustomToast } from "@utils/hooks/useToast";
-
-export default function AddToCartButton({
-  productType,
-  productUrlKey,
-  productId,
-  isSaleable
-}: {
-  productType?: string;
-  productId: string;
-  productUrlKey: string;
-  isSaleable?: string;
-}) {
-  const { isCartLoading, onAddToCart } = useAddProduct();
-  const { showToast } = useCustomToast();
-  const { user } = useAppSelector((state) => state.user);
-  const session = { user };
-
-  const handleAddToCart = () => {
-    if (!isSaleable || isSaleable === "") {
-      showToast("This product is out of stock", "warning");
-      return;
-    }
-
-    onAddToCart({
-      productId: productId.split("/").pop() || "",
-      quantity: 1,
-      token: session?.user?.token ?? undefined,
-    });
-  };
-
-  const buttonClasses =
-    " flex w-full cursor-pointer items-center  justify-center px-4 rounded-full min-h-8  tracking-wide ";
-  const disabledClasses = "cursor-wait opacity-60 hover:opacity-60";
-
-  return productType !== "simple" ? (
-    <Link
-      aria-disabled="true"
-      aria-label={productUrlKey}
-      rel="prefetch"
-      prefetch={true}
-      className={clsx(buttonClasses, {
-        "hover:opacity-90": true,
-      })}
-      href={`/product/${productUrlKey}`}
-      type="submit"
-    >
-      <ShoppingCartIcon className="size-6 -rotate-6 stroke-black stroke-[1.5]" />
-    </Link>
-  ) : (
-    <button
-      aria-disabled={isCartLoading || !isSaleable || isSaleable === ""}
-      aria-label={productUrlKey}
-      className={clsx(buttonClasses, {
-        "hover:opacity-90": isSaleable && isSaleable !== "",
-        [disabledClasses]: isCartLoading || !isSaleable || isSaleable === "",
-      })}
-      type="button"
-      onClick={handleAddToCart}
-    >
-      {isCartLoading ? (
-        <LoadingDots className="bg-black" />
-      ) : (
-        <ShoppingCartIcon className="size-6 -rotate-6 stroke-black stroke-[1.5]" />
-      )}
-    </button>
-  );
-}

+ 136 - 0
src/components/theme/ui/CommonModal.tsx

@@ -0,0 +1,136 @@
+"use client";
+
+import clsx from "clsx";
+import { useState, useLayoutEffect, useRef } from "react";
+import Portal from "@/components/Portal";
+
+/**
+ * 为了弹窗里的表单校验开发整个组件:
+ * 当弹窗关闭,校验弹窗里的表单时要保证弹窗里的表单元素还存在文档流中,所以这个组件采用display block/none 来控制弹窗的显示和隐藏 
+ */
+export default function CommonModal({ 
+    isOpen,
+    contentClassName,
+    clickOutsideClose,
+    onClose,
+    header,
+    body,
+    footer
+}: {
+    isOpen: boolean;
+    contentClassName?: string;
+    clickOutsideClose?: boolean;
+    onClose: (e: boolean) => void;
+    header?: React.ReactNode;
+    body: React.ReactNode;
+    footer?: React.ReactNode;
+}) {
+    // show - 显示
+    // outing - 隐藏动画中
+    // hidden - 隐藏
+    const [rootVisible, setRootVisible] = useState<'show' | 'outing' | 'hidden'>('hidden');
+    
+    const cssInit = useRef(false);
+    const rootRef = useRef<HTMLDivElement>(null);
+    const footerRef = useRef<HTMLDivElement>(null);
+    const headerRef = useRef<HTMLDivElement>(null);
+ 
+    const [rootPddingBottom, setRootPddingBottom] = useState(0);
+    const [rootPddingTop, setRootPddingTop] = useState(0);
+
+    const baseClassNname = "absolute box-border w-full bg-white transition-transform duration-300 ease-out";
+    let applyClassName = "bottom-0 left-0 h-17/20";
+    if(contentClassName) {
+        applyClassName = contentClassName;
+    }
+
+    useLayoutEffect(() => {
+        const original = document.documentElement.style.overflow;
+        if(isOpen) {
+            document.documentElement.style.overflow = "hidden";
+        } else {
+            document.documentElement.style.overflow = "";
+        }
+
+        if(isOpen) {
+            setRootVisible('show');
+        } else {
+            if(cssInit.current) {
+                setRootVisible('outing');
+            }
+            
+        }
+
+        return () => { document.documentElement.style.overflow = original; };
+    },[isOpen]);
+    // 当弹窗完全显示后计算 footer 高度
+    useLayoutEffect(() => {
+        if (rootVisible === 'show' && !cssInit.current && footerRef.current && headerRef.current) {
+            const footerHeight = footerRef.current.getBoundingClientRect().height;
+            const headerHeight = headerRef.current.getBoundingClientRect().height;
+            setRootPddingBottom(footerHeight);
+            setRootPddingTop(headerHeight);
+            cssInit.current = true;
+        }
+    }, [rootVisible]);
+    const rootClick = (e: React.MouseEvent) => {
+        e.stopPropagation();
+        if(clickOutsideClose) {
+            onClose(false);
+        }  
+    }
+    // 如果是关闭,动画结束之后才隐藏
+    const handlerAnimationEnd = (e: React.AnimationEvent<HTMLDivElement>) => {
+        if(e.animationName === "fade-out") {
+            setRootVisible('hidden');
+        } 
+        
+    }
+    // 初始化时是隐藏状态
+    // fade-in的时候display block + fade-in
+    // fade-out的时候需要先加fade-out 类名,动画效果结束之后才能display none 
+    return (
+        <Portal>
+            <div 
+                ref={rootRef} 
+                className={clsx("w-full h-full backdrop-blur-md backdrop-saturate-150 bg-black/30 fixed inset-0 z-50 transition-all ease-in-out",{
+                    "block fade-in": rootVisible === "show",
+                    "hidden": rootVisible === "hidden",
+                    "fade-out": rootVisible === "outing"
+                })}
+                onAnimationEnd={handlerAnimationEnd}
+                onClick={rootClick}
+            >
+                <div 
+                    className={clsx(baseClassNname,applyClassName,
+                        {
+                            "slide-bottom-in": rootVisible === "show",
+                            "slide-bottom-out": rootVisible === "outing",
+                        }
+                    )}
+                    style={{
+                        paddingTop: rootPddingTop,
+                        paddingBottom: rootPddingBottom
+                    }}
+                >
+                    <div ref={headerRef} className="w-full box-border absolute left-0 top-0">
+                        {header && header}
+                    </div>
+                    
+                    <div className="w-full box-border h-full overflow-y-auto relative">
+                        
+                        {body}
+                        
+                    </div>
+
+                    <div ref={footerRef} className={clsx("w-full box-border absolute bottom-0 left-0",{
+                        "border-t border-gray-200": footer !==null && footer !== undefined
+                    })}>
+                        {footer && footer}
+                    </div>
+                </div>
+                
+            </div>
+        </Portal>
+    ); 
+}

+ 0 - 3
src/components/theme/ui/InputText.tsx

@@ -1,7 +1,6 @@
 import React from "react";
 
 function InputText({
-    defaultValue,
     type = 'text',
     placeholder,
     onChange,
@@ -11,7 +10,6 @@ function InputText({
     ref
 }: {
     // 用于react-hook-form时,推荐使用useForm的 defaultValues 对整个表单设置默认值
-    defaultValue?: string;
     type?: 'text' | 'email' | 'password';
     placeholder: string;
     // 用于react-hook-form时,React 事件处理器类型定义中的“双变(bivariance)”特性 和返回值void的兼容性,使typescript没有提示类型错误
@@ -25,7 +23,6 @@ function InputText({
     return (
         <div className="w-full">
             <input className="ly-input w-full" 
-                defaultValue={defaultValue}
                 type={type}
                 placeholder={placeholder}
                 onChange={onChange}

+ 0 - 3
src/components/theme/ui/Select.tsx

@@ -6,7 +6,6 @@ interface Option {
     id: string;
 }
 export default function Select({
-    defaultValue,
     placeholder,
     options,
     onChange,
@@ -15,7 +14,6 @@ export default function Select({
     error,
     ref
 }: {
-    defaultValue?: string;
     placeholder?: string;
     options: Option[];
     onChange: (e: React.ChangeEvent<HTMLSelectElement>) => void;
@@ -28,7 +26,6 @@ export default function Select({
     return (
         <div className="w-full relative">
             <select className="appearance-none ly-input"
-                defaultValue={defaultValue}
                 onChange={onChange}
                 onBlur={onBlur}
                 name={name}

+ 15 - 0
src/components/theme/ui/kernel/KernelProvider.tsx

@@ -0,0 +1,15 @@
+"use client";
+
+import { ConfirmHost } from "./confirm/host";
+// import { ToastHost } from "./toast/host";
+import { LoadingHost } from "./loading/host";
+
+export function KernelProvider() {
+  return (
+    <>
+      <LoadingHost />
+      <ConfirmHost />
+      {/* <ToastHost /> */}
+    </>
+  );
+}

+ 31 - 0
src/components/theme/ui/kernel/confirm/api.ts

@@ -0,0 +1,31 @@
+import { confirmStore } from "./store";
+
+
+const createId = () =>{
+  return typeof crypto !== "undefined" && crypto.randomUUID
+    ? crypto.randomUUID()
+    : `${Date.now()}_${Math.random()}`;
+}
+// let id = createId();
+
+export function confirmDialog(options: {
+  title: string;
+  content?: string;
+  noOk?: boolean;
+  noCancel?: boolean;
+}) {
+  return new Promise<boolean>((resolve) => {
+    const state = confirmStore.getSnapshot();
+
+    confirmStore.setState({
+      queue: [
+        ...state.queue,
+        {
+          id: createId(),
+          ...options,
+          resolve,
+        },
+      ],
+    });
+  });
+}

+ 46 - 0
src/components/theme/ui/kernel/confirm/host.tsx

@@ -0,0 +1,46 @@
+"use client";
+
+import { AnimatePresence, motion } from "framer-motion";
+import { confirmStore } from "./store";
+import { useConfirmQueue } from "./useConfirm";
+
+export function ConfirmHost() {
+  const {queue} = useConfirmQueue();
+
+  const current = queue[0];
+
+//   if (!current) return null;
+
+  const close = (result: boolean) => {
+    current.resolve(result);
+
+    confirmStore.setState({
+      queue: queue.slice(1),
+    });
+  };
+
+  return (
+    <AnimatePresence>
+        {current && <motion.div
+        layout
+        initial={{ opacity: 0, }}
+        animate={{ opacity: 1 }}
+        exit={{ opacity: 0 }}
+        className="fixed z-1000 flex justify-center items-center bg-black/50 inset-0"
+        onClick={(e) => e.stopPropagation()}
+        >
+            {/* <div className="fixed z-1000 flex justify-center items-center bg-black/40 inset-0"> */}
+                <div className="w-4/5 p-4 bg-white rounded-lg">
+                    <h3 className="text-center text-ly-18 font-bold mb-6">{current.title}</h3>
+                    <p className="text-ly-14 font-medium">{current.content}</p>
+                    <div className="w-full flex justify-end gap-3 mt-6">
+                        {!current.noOk && <button className="w-20 h-8 flex justify-center items-center rounded-xs bg-ly-green text-ly-12 font-medium text-white" onClick={() => close(true)}>OK</button>}
+                        {!current.noCancel && <button className="w-20 h-8 flex justify-center items-center rounded-xs bg-ly-gray text-ly-12 font-medium" onClick={() => close(false)}>Cancel</button>}
+                    </div>
+                </div>
+            {/* </div> */}
+        </motion.div>}
+    </AnimatePresence>
+  );
+}
+

+ 17 - 0
src/components/theme/ui/kernel/confirm/store.ts

@@ -0,0 +1,17 @@
+import { createStore } from "../core/store";
+
+export type ConfirmItem = {
+  id: string;
+  title: string;
+  content?: string;
+  noOk?: boolean;
+  noCancel?: boolean;
+  resolve: (v: boolean) => void;
+};
+
+export const confirmStore = createStore<{ queue: ConfirmItem[]; }>({
+  queue: [],
+});
+
+
+

+ 10 - 0
src/components/theme/ui/kernel/confirm/useConfirm.ts

@@ -0,0 +1,10 @@
+import { useSyncExternalStore } from "react";
+import { confirmStore } from "./store";
+
+export function useConfirmQueue() {
+  return useSyncExternalStore(
+    confirmStore.subscribe,
+    confirmStore.getSnapshot,
+    confirmStore.getSnapshot
+  );
+}

+ 22 - 0
src/components/theme/ui/kernel/core/store.ts

@@ -0,0 +1,22 @@
+type Listener<T> = (state: T) => void;
+
+export function createStore<T>(initial: T) {
+  let state = initial;
+  const listeners = new Set<Listener<T>>();
+
+  const getSnapshot = () => state;
+
+  const setState = (next: T) => {
+    state = next;
+    listeners.forEach((l) => l(state));
+  };
+
+  const subscribe = (fn: Listener<T>) => {
+    listeners.add(fn);
+    return () => {
+        listeners.delete(fn);
+    }
+  };
+
+  return { getSnapshot, setState, subscribe };
+}

+ 19 - 0
src/components/theme/ui/kernel/loading/api.ts

@@ -0,0 +1,19 @@
+import { loadingStore } from "./store";
+
+export const overlayLoading = {
+  start() {
+    const s = loadingStore.getSnapshot();
+    loadingStore.setState({ count: s.count + 1 });
+  },
+
+  stop() {
+    const s = loadingStore.getSnapshot();
+    loadingStore.setState({
+      count: Math.max(0, s.count - 1),
+    });
+  },
+
+  reset() {
+    loadingStore.setState({ count: 0 });
+  },
+};

+ 20 - 0
src/components/theme/ui/kernel/loading/host.tsx

@@ -0,0 +1,20 @@
+"use client";
+
+import { useLoading } from "./useLoading";
+import { LoadingSpinner } from "@/components/common/LoadingSpinner";
+
+export function LoadingHost() {
+    const {count} = useLoading();
+
+    if (count === 0) return null;
+
+    return (
+        <div className="fixed inset-0 w-full h-full bg-transparent flex justify-center items-center"
+            onClick={(e) => e.stopPropagation()}
+        >
+            <div className="relative w-20 h-20 bg-black/30 rounded-xl">
+                <LoadingSpinner className="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2" size={32} />
+            </div>
+        </div>
+    );
+}

+ 5 - 0
src/components/theme/ui/kernel/loading/store.ts

@@ -0,0 +1,5 @@
+import { createStore } from "../core/store";
+
+export const loadingStore = createStore({
+  count: 0,
+});

+ 10 - 0
src/components/theme/ui/kernel/loading/useLoading.ts

@@ -0,0 +1,10 @@
+import { useSyncExternalStore } from "react";
+import { loadingStore } from "./store";
+
+export function useLoading() {
+  return useSyncExternalStore(
+    loadingStore.subscribe,
+    loadingStore.getSnapshot,
+    loadingStore.getSnapshot
+  );
+}

+ 34 - 17
src/graphql/cart/mutations/AddProductToCart.ts

@@ -1,6 +1,7 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import { AddToCartData } from "@/types/cart/type";
 
-export const CREATE_ADD_PRODUCT_IN_CART = gql`
+export const CREATE_ADD_PRODUCT_IN_CART: TypedDocumentNode<AddToCartData> = gql`
   mutation createAddProductInCart(
     $cartId: Int
     $productId: Int!
@@ -18,11 +19,11 @@ export const CREATE_ADD_PRODUCT_IN_CART = gql`
       addProductInCart {
         id
         cartToken
-        subtotal
-        taxAmount
-        subtotal
-        shippingAmount
-        grandTotal
+        sessionToken
+        appliedTaxes
+        itemsQty
+        isGuest
+        itemsCount
         items {
           edges {
             node {
@@ -30,8 +31,8 @@ export const CREATE_ADD_PRODUCT_IN_CART = gql`
               cartId
               productId
               name
-               price
-               baseImage
+              price
+              baseImage
               sku
               quantity
               type
@@ -40,16 +41,32 @@ export const CREATE_ADD_PRODUCT_IN_CART = gql`
             }
           }
         }
-        success
-        message
-        sessionToken
-        isGuest
-        itemsQty
-        itemsCount
-        paymentMethod
-        paymentMethodTitle
+        subtotal
+        subTotalInclTax
+        discountAmount
+        taxAmount
+        taxTotal
+        shippingAmount
+        shippingAmountInclTax
+        grandTotal
+        formattedSubtotal
+        formattedSubTotalInclTax
+        formattedDiscountAmount
+        formattedTaxAmount
+        formattedTaxTotal
+        formattedShippingAmount
+        formattedShippingAmountInclTax
+        formattedGrandTotal
+        couponCode
         selectedShippingRate
         selectedShippingRateTitle
+        paymentMethod
+        paymentMethodTitle
+        haveStockableItems
+        billingAddress
+        shippingAddress
+        success
+        message
       }
     }
   }

+ 32 - 12
src/graphql/cart/mutations/CreateMergeCart.ts

@@ -1,6 +1,7 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import { CreateMergeCartData } from "@/types/cart/type";
 
-export const CREATE_MERGE_CART = gql`
+export const CREATE_MERGE_CART: TypedDocumentNode<CreateMergeCartData> = gql`
   mutation createMergeCart(
     $cartId: Int!
   ) {
@@ -11,15 +12,10 @@ export const CREATE_MERGE_CART = gql`
     ) {
       mergeCart {
         id
+        appliedTaxes
         itemsQty
-        taxAmount
-        subtotal
-        shippingAmount
-        grandTotal
-        paymentMethod
-        paymentMethodTitle
-        selectedShippingRate
-        selectedShippingRateTitle
+        isGuest
+        itemsCount
         items {
           edges {
             node {
@@ -27,8 +23,8 @@ export const CREATE_MERGE_CART = gql`
               cartId
               productId
               name
-               price
-               baseImage
+              price
+              baseImage
               sku
               quantity
               type
@@ -37,6 +33,30 @@ export const CREATE_MERGE_CART = gql`
             }
           }
         }
+        subtotal
+        subTotalInclTax
+        discountAmount
+        taxAmount
+        taxTotal
+        shippingAmount
+        shippingAmountInclTax
+        grandTotal
+        formattedSubtotal
+        formattedSubTotalInclTax
+        formattedDiscountAmount
+        formattedTaxAmount
+        formattedTaxTotal
+        formattedShippingAmount
+        formattedShippingAmountInclTax
+        formattedGrandTotal
+        couponCode
+        selectedShippingRate
+        selectedShippingRateTitle
+        paymentMethod
+        paymentMethodTitle
+        haveStockableItems
+        billingAddress
+        shippingAddress
       }
     }
   }

+ 27 - 9
src/graphql/cart/mutations/GetCartItem.ts

@@ -1,19 +1,15 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import { GetCartItemData } from "@/types/cart/type";
 
-export const GET_CART_ITEM = gql`
+export const GET_CART_ITEM: TypedDocumentNode<GetCartItemData> = gql`
   mutation GetCartItem {
     createReadCart(input: {}) {
       readCart {
         id
-        itemsCount
-        taxAmount
-        grandTotal
-        shippingAmount
-        selectedShippingRate
-        selectedShippingRateTitle
-        subtotal
+        appliedTaxes
         itemsQty
         isGuest
+        itemsCount
         items {
           edges {
             node {
@@ -31,8 +27,30 @@ export const GET_CART_ITEM = gql`
             }
           }
         }
+        subtotal
+        subTotalInclTax
+        discountAmount
+        taxAmount
+        taxTotal
+        shippingAmount
+        shippingAmountInclTax
+        grandTotal
+        formattedSubtotal
+        formattedSubTotalInclTax
+        formattedDiscountAmount
+        formattedTaxAmount
+        formattedTaxTotal
+        formattedShippingAmount
+        formattedShippingAmountInclTax
+        formattedGrandTotal
+        couponCode
+        selectedShippingRate
+        selectedShippingRateTitle
         paymentMethod
         paymentMethodTitle
+        haveStockableItems
+        billingAddress
+        shippingAddress
       }
     }
   }

+ 41 - 25
src/graphql/cart/mutations/RemoveCartItem.ts

@@ -1,41 +1,57 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import { RemoveCartItemData } from "@/types/cart/type";
 
-export const REMOVE_CART_ITEM = gql`
-  mutation RemoveCartItem(
-    $cartItemId: Int!
-  ) {
-    createRemoveCartItem(
-      input: {
-        cartItemId: $cartItemId
-      }
-    ) {
+export const REMOVE_CART_ITEM: TypedDocumentNode<RemoveCartItemData> = gql`
+  mutation RemoveCartItem( $cartItemId: Int! ) {
+    createRemoveCartItem(input: { cartItemId: $cartItemId }) {
       removeCartItem {
-      id
-      cartToken
-       taxAmount
-        shippingAmount
-        subtotal
-        grandTotal
-      items {
-        totalCount
-        edges {
-          node {
-            id
+        id
+        appliedTaxes
+        itemsQty
+        isGuest
+        itemsCount
+        items {
+          edges {
+            node {
+              id
               cartId
               productId
               name
-               price
-               baseImage
+              price
+              baseImage
               sku
               quantity
               type
               productUrlKey
               canChangeQty
+            }
           }
         }
+        subtotal
+        subTotalInclTax
+        discountAmount
+        taxAmount
+        taxTotal
+        shippingAmount
+        shippingAmountInclTax
+        grandTotal
+        formattedSubtotal
+        formattedSubTotalInclTax
+        formattedDiscountAmount
+        formattedTaxAmount
+        formattedTaxTotal
+        formattedShippingAmount
+        formattedShippingAmountInclTax
+        formattedGrandTotal
+        couponCode
+        selectedShippingRate
+        selectedShippingRateTitle
+        paymentMethod
+        paymentMethodTitle
+        haveStockableItems
+        billingAddress
+        shippingAddress
       }
-        itemsQty
-    }
     }
   }
 `;

+ 30 - 11
src/graphql/cart/mutations/UpdateCartItems.ts

@@ -1,6 +1,7 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import { UpdateCartItemData } from "@/types/cart/type";
 
-export const UPDATE_CART_ITEM = gql
+export const UPDATE_CART_ITEM: TypedDocumentNode<UpdateCartItemData> = gql
   `
   mutation UpdateCartItem(
     $cartItemId: Int!
@@ -14,10 +15,10 @@ export const UPDATE_CART_ITEM = gql
     ) {
       updateCartItem {
         id
-        taxAmount
-        shippingAmount
-        subtotal
-        grandTotal
+        appliedTaxes
+        itemsQty
+        isGuest
+        itemsCount
         items {
           edges {
             node {
@@ -25,8 +26,8 @@ export const UPDATE_CART_ITEM = gql
               cartId
               productId
               name
-               price
-               baseImage
+              price
+              baseImage
               sku
               quantity
               type
@@ -35,12 +36,30 @@ export const UPDATE_CART_ITEM = gql
             }
           }
         }
-        itemsQty
+        subtotal
+        subTotalInclTax
+        discountAmount
+        taxAmount
+        taxTotal
+        shippingAmount
+        shippingAmountInclTax
         grandTotal
-        paymentMethod
-        paymentMethodTitle
+        formattedSubtotal
+        formattedSubTotalInclTax
+        formattedDiscountAmount
+        formattedTaxAmount
+        formattedTaxTotal
+        formattedShippingAmount
+        formattedShippingAmountInclTax
+        formattedGrandTotal
+        couponCode
         selectedShippingRate
         selectedShippingRateTitle
+        paymentMethod
+        paymentMethodTitle
+        haveStockableItems
+        billingAddress
+        shippingAddress
       }
     }
   }

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

@@ -0,0 +1,8 @@
+AddProductToCart
+CreateMergeCart
+GetCartItem
+RemoveCartItem
+UpdateCartItems
+
+以上这几个接口返回数据的字段要一样
+因为调完这几个接口后都需要更新redux中的购物车数据

+ 22 - 0
src/graphql/checkout/mutations/CreateCancelOrder.ts

@@ -0,0 +1,22 @@
+import { gql,TypedDocumentNode } from "@apollo/client";
+import { CreateCancelOrderData } from "@/types/checkout/type";
+
+export const CREATE_CANCEL_ORDER: TypedDocumentNode<CreateCancelOrderData> = gql`
+  mutation CreateCancelOrder(
+      $orderId: Int
+  ) {
+      createCancelOrder(
+        input: {
+          orderId: $orderId
+        }
+      ) {
+          cancelOrder {
+            success
+            message
+            orderId
+            status
+            id
+          }
+      }
+  }
+`;

+ 31 - 3
src/graphql/checkout/mutations/CreateCheckoutOrder.ts

@@ -1,6 +1,33 @@
-import { gql } from "@apollo/client";
+import { gql,TypedDocumentNode } from "@apollo/client";
+import { CreateCheckoutOrderData, CreatePaymentInitiateData } from "@/types/checkout/type";
 
-export const CREATE_CHECKOUT_ORDER = gql`
+export const CREATE_PAYMENT_INITIATE: TypedDocumentNode<CreatePaymentInitiateData> = gql`
+  mutation CreatePaymentInitiate(
+      $expressCheckout: Boolean
+      $paymentMethod: String
+  ) {
+      createPaymentInitiate(
+        input: {
+          expressCheckout: $expressCheckout
+          paymentMethod: $paymentMethod
+        }
+      ) {
+          paymentInitiate {
+            id
+            success
+            message
+            orderId
+            status 
+            gatewayOrderId 
+            newCartToken 
+            express 
+          }
+      }
+  }
+`;
+
+// 这个不用了
+export const CREATE_CHECKOUT_ORDER: TypedDocumentNode<CreateCheckoutOrderData> = gql`
   mutation CreateCheckoutOrder {
     createCheckoutOrder(
       input: {}
@@ -10,7 +37,8 @@ export const CREATE_CHECKOUT_ORDER = gql`
         orderId
         message
         success
+        paymentTransactionId
       }
     }
   }
-`;
+`;

+ 3 - 2
src/graphql/checkout/mutations/CreateCheckoutPaymentMethod.ts

@@ -1,6 +1,7 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import { CreateCheckoutPaymentMethodResponse } from "@/types/checkout/type";
 
-export const CREATE_CHECKOUT_PAYMENT_METHODS = gql`
+export const CREATE_CHECKOUT_PAYMENT_METHODS: TypedDocumentNode<CreateCheckoutPaymentMethodResponse> = gql`
   mutation CreateCheckoutPaymentMethod(
     $paymentMethod: String!
     $successUrl: String

+ 3 - 2
src/graphql/checkout/mutations/CreateCheckoutShippingMethod.ts

@@ -1,6 +1,7 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import {CreateCheckoutShippingMethodData} from "@/types/checkout/type";
 
-export const CREATE_CHECKOUT_SHIPPING_METHODS = gql`
+export const CREATE_CHECKOUT_SHIPPING_METHODS: TypedDocumentNode<CreateCheckoutShippingMethodData> = gql`
   mutation CreateCheckoutShippingMethod(
     $shippingMethod: String!
   ) {

+ 70 - 0
src/graphql/checkout/mutations/CreatePaymentCallback.ts

@@ -0,0 +1,70 @@
+import { gql,TypedDocumentNode } from "@apollo/client";
+import { CreatePaymentCallbackData } from "@/types/checkout/type";
+
+export const CREATE_PAYMENT_CALLBACK: TypedDocumentNode<CreatePaymentCallbackData> = gql`
+  mutation CreatePaymentCallback(
+      $orderId: Int
+      $gatewayOrderId: String
+      $status: String
+      $paymentSignature: String
+      
+      $shippingFirstName: String
+      $shippingLastName: String
+      $shippingEmail: String
+      $shippingAddress: String
+      $shippingCountry: String
+      $shippingState: String
+      $shippingCity: String
+      $shippingPostcode: String
+      $shippingPhoneNumber: String
+
+      $billingFirstName: String
+      $billingLastName: String
+      $billingEmail: String
+      $billingAddress: String
+      $billingCountry: String
+      $billingState: String
+      $billingCity: String
+      $billingPostcode: String
+      $billingPhoneNumber: String
+  ) {
+      createPaymentCallback(
+        input: {
+          orderId: $orderId
+          gatewayOrderId: $gatewayOrderId
+          status: $status
+          paymentSignature: $paymentSignature
+          
+          shippingFirstName: $shippingFirstName
+          shippingLastName: $shippingLastName
+          shippingEmail: $shippingEmail
+          shippingAddress: $shippingAddress
+          shippingCountry: $shippingCountry
+          shippingState: $shippingState
+          shippingCity: $shippingCity
+          shippingPostcode: $shippingPostcode
+          shippingPhoneNumber: $shippingPhoneNumber
+
+          billingFirstName: $billingFirstName
+          billingLastName: $billingLastName
+          billingEmail: $billingEmail
+          billingAddress: $billingAddress
+          billingCountry: $billingCountry
+          billingState: $billingState
+          billingCity: $billingCity
+          billingPostcode: $billingPostcode
+          billingPhoneNumber: $billingPhoneNumber
+        }
+      ) {
+          paymentCallback {
+            id
+            success
+            message
+            orderId
+            status
+            orderStatus
+            gatewayStatus
+          }
+      }
+  }
+`;

+ 25 - 0
src/graphql/checkout/mutations/CreatePaymentReplay.ts

@@ -0,0 +1,25 @@
+import { gql,TypedDocumentNode } from "@apollo/client";
+import { CreatePaymentReplayData } from "@/types/checkout/type";
+
+export const CREATE_PAYMENT_REPLAY: TypedDocumentNode<CreatePaymentReplayData> = gql`
+  mutation CreatePaymentReplay(
+      $orderId: Int
+      $paymentMethod: String
+  ) {
+      createPaymentReplay(
+        input: {
+          orderId: $orderId
+          paymentMethod: $paymentMethod
+        }
+      ) {
+          paymentReplay{
+            id
+            success
+            message
+            orderId
+            status
+            gatewayOrderId
+          }
+      }
+  }
+`;

+ 67 - 0
src/graphql/checkout/mutations/CreateSaveCheckoutCart.ts

@@ -0,0 +1,67 @@
+import { gql,TypedDocumentNode } from "@apollo/client";
+import { SaveCheckoutCartData } from "@/types/checkout/type";
+
+export const CREATE_SAVE_CHECKOUT_CART: TypedDocumentNode<SaveCheckoutCartData> = gql`
+    mutation CreateSaveCheckoutCart(
+        $shippingMethod: String
+        $paymentMethod: String
+        $couponCode: String
+    ) {
+      createSaveCheckoutCart(
+        input: {
+            shippingMethod: $shippingMethod
+            paymentMethod: $paymentMethod
+            couponCode: $couponCode
+        }
+        ) {
+            saveCheckoutCart {
+                id
+                appliedTaxes
+                itemsQty
+                isGuest
+                itemsCount
+                items {
+                    edges {
+                        node {
+                            id
+                            cartId
+                            productId
+                            name
+                            price
+                            baseImage
+                            sku
+                            quantity
+                            type
+                            productUrlKey
+                            canChangeQty
+                        }
+                    }
+                }
+                subtotal
+                subTotalInclTax
+                discountAmount
+                taxAmount
+                taxTotal
+                shippingAmount
+                shippingAmountInclTax
+                grandTotal
+                formattedSubtotal
+                formattedSubTotalInclTax
+                formattedDiscountAmount
+                formattedTaxAmount
+                formattedTaxTotal
+                formattedShippingAmount
+                formattedShippingAmountInclTax
+                formattedGrandTotal
+                couponCode
+                selectedShippingRate
+                selectedShippingRateTitle
+                paymentMethod
+                paymentMethodTitle
+                haveStockableItems
+                billingAddress
+                shippingAddress
+            }
+        }
+    }
+`;

+ 6 - 2
src/graphql/checkout/mutations/index.ts

@@ -1,4 +1,8 @@
 export {CREATE_CHECKOUT_ADDRESS} from "./CreateCheckoutAddress" ;
-export {CREATE_CHECKOUT_ORDER} from "./CreateCheckoutOrder" ;
+export {CREATE_CHECKOUT_ORDER,CREATE_PAYMENT_INITIATE} from "./CreateCheckoutOrder" ;
 export {CREATE_CHECKOUT_PAYMENT_METHODS} from "./CreateCheckoutPaymentMethod";
-export {CREATE_CHECKOUT_SHIPPING_METHODS} from "./CreateCheckoutShippingMethod";
+export {CREATE_CHECKOUT_SHIPPING_METHODS} from "./CreateCheckoutShippingMethod";
+export {CREATE_PAYMENT_CALLBACK} from "./CreatePaymentCallback";
+export {CREATE_CANCEL_ORDER} from "./CreateCancelOrder";
+export {CREATE_SAVE_CHECKOUT_CART} from "./CreateSaveCheckoutCart";
+export {CREATE_PAYMENT_REPLAY} from "./CreatePaymentReplay";

+ 3 - 2
src/graphql/checkout/queries/GetCheckoutPaymentMethods.ts

@@ -1,6 +1,7 @@
-import { gql } from "@apollo/client";
+import { gql,TypedDocumentNode } from "@apollo/client";
+import { CheckoutPaymentMethodsData } from "@/types/checkout/type";
 
-export const GET_CHECKOUT_PAYMENT_METHODS = gql`
+export const GET_CHECKOUT_PAYMENT_METHODS: TypedDocumentNode<CheckoutPaymentMethodsData> = gql`
   query CheckoutPaymentMethods {
     collectionPaymentMethods {
       id

+ 5 - 2
src/graphql/checkout/queries/GetCheckoutShippingRates.ts

@@ -1,14 +1,17 @@
-import { gql } from "@apollo/client";
+import { gql, TypedDocumentNode } from "@apollo/client";
+import {GetCheckoutShippingRatesData} from "@/types/checkout/type";
 
-export const GET_CHECKOUT_SHIPPING_RATES = gql`
+export const GET_CHECKOUT_SHIPPING_RATES: TypedDocumentNode<GetCheckoutShippingRatesData> = gql`
   query CheckoutShippingRates {
     collectionShippingRates {
+      _id
       id
       code
       description
       method
       price
       label
+      formattedPrice
     }
   }
 `;

+ 19 - 0
src/graphql/currency/query/GetCurrencies.ts

@@ -0,0 +1,19 @@
+import { gql,TypedDocumentNode } from "@apollo/client";
+import { CurrenciesData } from "@/types/currency/type";
+
+
+export const GET_CURRENCIES: TypedDocumentNode<CurrenciesData> = gql`
+  query allCurrency {
+  currencies {
+    edges {
+      node {
+        id
+        _id
+        code
+        name
+        symbol
+      }
+    }
+  }
+}
+`;

+ 3 - 0
src/graphql/currency/query/index.ts

@@ -0,0 +1,3 @@
+export {
+    GET_CURRENCIES
+} from "./GetCurrencies";

+ 90 - 0
src/graphql/customer/query/GetOrderDetails.ts

@@ -0,0 +1,90 @@
+import { gql,TypedDocumentNode } from "@apollo/client";
+import { OrderDetailsData } from "@/types/customer/type";
+
+
+export const GET_ORDER_DETAILS: TypedDocumentNode<OrderDetailsData> = gql`
+  query GetCustomerOrder($id: ID!) {
+  customerOrder(id: $id) { 
+    incrementId
+    status
+    channelName
+    customerEmail
+    customerFirstName
+    customerLastName
+    payment {
+      method
+      methodTitle
+    }
+    shippingMethod
+    shippingTitle
+    couponCode
+    totalItemCount
+    totalQtyOrdered
+    grandTotal
+    baseGrandTotal
+    grandTotalInvoiced
+    grandTotalRefunded
+    subTotal
+    baseSubTotal
+    taxAmount
+    baseTaxAmount
+    discountAmount
+    baseDiscountAmount
+    shippingAmount
+    baseShippingAmount
+    baseCurrencyCode
+    channelCurrencyCode
+    orderCurrencyCode
+    items {
+      edges {
+        node {
+          id
+          sku
+          type
+          name
+          additional
+          qtyOrdered
+          qtyShipped
+          qtyInvoiced
+          qtyCanceled
+          qtyRefunded
+          price
+          basePrice
+          total
+          baseTotal
+          discountPercent
+          discountAmount
+          baseDiscountAmount
+        }
+      }
+    }
+    addresses {
+      edges {
+        node {
+          id
+          _id
+          addressType
+          orderId
+          name
+          firstName
+          lastName
+          companyName
+          address
+          city
+          state
+          country
+          postcode
+          useForShipping
+          email
+          phone
+          defaultAddress
+          createdAt
+          updatedAt
+        }
+      }
+    }
+    createdAt
+    updatedAt
+  }
+}
+`;

+ 3 - 0
src/graphql/customer/query/index.ts

@@ -0,0 +1,3 @@
+export {
+    GET_ORDER_DETAILS
+} from "./GetOrderDetails";

+ 2 - 0
src/graphql/index.ts

@@ -5,5 +5,7 @@ export * from "./theme/queries";
 export * from "./cart/mutations";
 export * from "./checkout/queries";
 export * from "./checkout/mutations";
+export * from "./customer/query/index";
+export * from "./currency/query/index";
 export * from "./types";
 

+ 28 - 0
src/lib/ApolloErrorHandler.ts

@@ -0,0 +1,28 @@
+import {
+  CombinedGraphQLErrors,
+  CombinedProtocolErrors,
+  LocalStateError,
+  ServerError,
+  ServerParseError,
+  UnconventionalError,
+} from "@apollo/client/errors";
+
+// about apollo client error handle https://www.apollographql.com/docs/react/data/error-handling
+// Comprehensive error handling example.
+function handleError(error: unknown) {
+  if (CombinedGraphQLErrors.is(error)) {
+    // Handle GraphQL errors
+  } else if (CombinedProtocolErrors.is(error)) {
+    // Handle multipart subscription protocol errors
+  } else if (LocalStateError.is(error)) {
+    // Handle errors thrown by the `LocalState` class
+  } else if (ServerError.is(error)) {
+    // Handle server HTTP errors
+  } else if (ServerParseError.is(error)) {
+    // Handle JSON parse errors
+  } else if (UnconventionalError.is(error)) {
+    // Handle errors thrown by irregular types
+  } else {
+    // Handle other errors
+  }
+}

+ 59 - 9
src/lib/graphql-fetch.ts

@@ -4,6 +4,7 @@ import {
   type OperationVariables,
   ApolloClient
 } from "@apollo/client";
+import {GraphqlRequestResult} from "@/types/graphqlFetch/type";
 import {getClient} from "@/lib/ApolloClientServer";
 import {IS_GUEST,GUEST_CART_TOKEN} from "@/utils/constants";
 import { decodeJWT } from "@/utils/jwt-cookie";
@@ -17,11 +18,6 @@ declare module "@apollo/client" {
 */
 // Comprehensive error handling example. https://www.apollographql.com/docs/react/data/error-handling
 
-export interface GraphqlRequestResult<TData = unknown> {
-  data: TData | null;
-  error: string | undefined;
-}
-
 export type CacheLifePreset =
   | "seconds"
   | "minutes"
@@ -149,7 +145,7 @@ export async function graphqlRequest<
       // Promise-based APIs (e.g. client.query, client.mutate) - Errors either reject the promise or are returned in the result as the error field.
       // 如果错误被reject 则会进入catch
       const result: ApolloClient.QueryResult<TData> = await client.query(queryOption);
-      console.log('graphqlRequest network-only result ---- ', result);
+
       resData = result.data || null;
       return {data: resData, error: result.error? result.error.message : '' };
   } catch (error) {
@@ -180,8 +176,62 @@ export async function graphqlRequestNoCache<
   );
 }
 
+export async function authorizationGraphqlMutate<
+  TData = unknown,
+  TVariables extends OperationVariables = OperationVariables
+>(
+    mutation: DocumentNode,
+    variables?: TVariables,
+    operationName?: string
+): Promise<GraphqlRequestResult<TData>> {
+    const cookieStore = await cookies();
+    const isGuest = cookieStore.get(IS_GUEST);
+    const authToken = cookieStore.get(GUEST_CART_TOKEN);
+    let token = '';
+    if(!authToken) {
+        return {data: null, error: 'Authorization token not found!'};
+    } 
+    if(isGuest?.value === 'false') { // 登录用户
+        token = authToken.value;
+    } else {
+        // 游客
+        const jwtRes = decodeJWT<{
+            sessionToken: string;
+            cartId: number;
+            isGuest: boolean;
+        }>(authToken.value, true);
+        token = jwtRes?.sessionToken || '';
+    }
+    const client = getClient();
+    try {
+        const mutateOption: ApolloClient.MutateOptions<TData> = {
+            mutation,
+            variables,
+            fetchPolicy: "no-cache",
+            context: {
+                headers: {
+                    "Authorization": "Bearer " + token,
+                },
+                fetchOptions: {
+                    cache: 'no-store',
+                },
+            }
+        };
+        const result: ApolloClient.MutateResult<TData> = await client.mutate(mutateOption);
+        console.log('authorizationGraphqlMutate result ---- ', result);
+        const resData = result.data || null;
+        return {data: resData, error: result.error? result.error.message : '' };
+    } catch (error: any) {
+        console.log('authorizationGraphqlMutate error ---- ', error.message);
+        if(operationName === 'GetCartItem' && error.message === 'Cart not found') {
+          return { data: null, error: 'Cart not found' };
+        }
+        throw error;
+    }
+    
+}
 
-export async function authorizationGraphqlRequest<
+export async function authorizationGraphqlQuery<
   TData = unknown,
   TVariables extends OperationVariables = OperationVariables
 >(
@@ -222,7 +272,7 @@ export async function authorizationGraphqlRequest<
             }
         };
         const result: ApolloClient.QueryResult<TData> = await client.query(queryOption);
-        console.log('authorizationGraphqlRequest result ---- ', result);
+        console.log('authorizationGraphqlQuery result ---- ', result);
         const resData = result.data || null;
         return {data: resData, error: result.error? result.error.message : '' };
     } catch (error) {
@@ -253,7 +303,7 @@ export async function fullCacheGraphqlRequest<
             }
         };
         const result: ApolloClient.QueryResult<TData> = await client.query(queryOption);
-        console.log('authorizationGraphqlRequest result ---- ', result);
+        console.log('fullCacheGraphqlRequest result ---- ', result);
         const resData = result.data || null;
         return {data: resData, error: result.error? result.error.message : '' };
     } catch (error) {

+ 0 - 3
src/providers/GlobalProviders.tsx

@@ -2,20 +2,17 @@
 
 import { ReactNode } from "react";
 import { ThemeProvider } from "./ThemeProvider";
-import { ReduxProvider } from "./ReduxProvider";
 import { ToastProvider } from "./ToastProvider";
 import { ApolloWrapper } from "./ApolloWrapper";
 
 export function GlobalProviders({ children }: { children: ReactNode }) {
   return (
     <ThemeProvider>
-      <ReduxProvider>
         <ToastProvider>
           <ApolloWrapper>
             {children}
           </ApolloWrapper>
         </ToastProvider>
-      </ReduxProvider>
     </ThemeProvider>
   );
 }

+ 21 - 3
src/providers/ReduxProvider.tsx

@@ -2,8 +2,26 @@
 
 import { Provider } from "react-redux";
 
-import { store } from "@/store/store";
+// import { store } from "@/store/store";
 
-export const ReduxProvider = ({ children }: { children: React.ReactNode }) => {
-  return <Provider store={store}>{children}</Provider>;
+import { useRef } from 'react'
+
+import { makeStore, AppStore,PreloadedStateType } from '@/store/store'
+
+export const ReduxProvider = ({ 
+  children,
+  preloadedState
+}: { 
+  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>
 }

+ 14 - 6
src/store/hooks.ts

@@ -1,9 +1,17 @@
-import type { AppDispatch, RootState } from "./store";
+// import type { AppDispatch, RootState } from "./store";
 
-import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
+// import { TypedUseSelectorHook, useDispatch, useSelector } from "react-redux";
 
-// Create a typed version of useDispatch
-export const useAppDispatch = () => useDispatch<AppDispatch>();
+// // Create a typed version of useDispatch
+// export const useAppDispatch = () => useDispatch<AppDispatch>();
 
-// Create a typed version of useSelector
-export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
+// // Create a typed version of useSelector
+// export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector;
+
+import { useDispatch, useSelector, useStore } from 'react-redux'
+import type { RootState, AppDispatch, AppStore } from './store'
+
+// Use throughout your app instead of plain `useDispatch` and `useSelector`
+export const useAppDispatch = useDispatch.withTypes<AppDispatch>()
+export const useAppSelector = useSelector.withTypes<RootState>()
+export const useAppStore = useStore.withTypes<AppStore>()

+ 56 - 69
src/store/slices/cart-slice.ts

@@ -1,99 +1,86 @@
-import { AddressDataTypes } from "@/types/types";
-import { PayloadAction, createSlice } from "@reduxjs/toolkit";
+import makeClient from "@/lib/ApolloClientBrowser";
+import { PayloadAction, createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+import {CartDetail} from "@/types/cart/type";
+import { GET_CART_ITEM } from "@/graphql";
 
-export interface Cart {
-  id: string;
-  itemsQty: number;
-  taxAmount: number;
-  shippingAmount: number;
-  grandTotal: number;
-  items: any;
-  paymentMethod: string;
-  paymentMethodTitle: string;
-  // shippingMethod: string; // shippingMethod的值其实与selectedShippingRate的值相等
-  selectedShippingRate: string;
-  selectedShippingRateTitle: string;
+import { formatCartDetail } from "@/utils/cartDetailTools";
+export interface CartState {
+  cart: CartDetail | null;
+  loading: boolean;
+  error: string | null;
 }
 
-interface CartState {
-  cart?: Cart;
-  billingAddress: AddressDataTypes | null;
-  shippingAddress: AddressDataTypes | null;
-}
+// 定义异步 action
+export const fetchCartDetail = createAsyncThunk(
+  'cartDetail/fetchCartDetail',
+  async (_, { rejectWithValue }) => { //_ 占位,表示不需要参数
+    try {
+      const apolloClient = makeClient();
+      const res = await apolloClient.mutate({
+        mutation: GET_CART_ITEM,
+        fetchPolicy: 'network-only', // 按需调整
+      });
+      const responseData = res.data?.createReadCart?.readCart;
+      return responseData ? formatCartDetail(responseData) : null;
+    } catch (error) {
+      let errorMessage: string;
+      if (error instanceof Error) {
+        errorMessage = error.message;
+      } else {
+        errorMessage = String(error);
+      }
+      return rejectWithValue(errorMessage);
+    }
+  }
+);
+
 
 const initialState: CartState = {
-  cart: undefined,
-  billingAddress: null,
-  shippingAddress: null,
+  cart: null,
+  loading: false,
+  error: null,
 };
 
 const cartSlice = createSlice({
   name: "cartDetail",
   initialState,
   reducers: {
-    addItem: (state, action: PayloadAction<Cart>) => {
+    addItem: (state, action: PayloadAction<CartDetail>) => {
       state.cart = { ...action.payload };
     },
-    updateCart: (state, action: PayloadAction<Partial<Cart>>) => {
+    updateCart: (state, action: PayloadAction<Partial<CartDetail>>) => {
       if (state.cart) {
         state.cart = { ...state.cart, ...action.payload };
-      } else {
-        state.cart = action.payload as Cart;
       }
     },
     clearCart(state) {
-      state.cart = undefined;
-      state.billingAddress = null;
-      state.shippingAddress = null;
-    },
-
-    setBillingAddress(state, action: PayloadAction<AddressDataTypes>) {
-      state.billingAddress = action.payload;
-    },
-    clearBillingAddress(state) {
-      state.billingAddress = null;
-    },
-
-    setShippingAddress(state, action: PayloadAction<AddressDataTypes>) {
-      state.shippingAddress = action.payload;
-    },
-    clearShippingAddress(state) {
-      state.shippingAddress = null;
-    },
-
-    setCheckoutAddresses(
-      state,
-      action: PayloadAction<{
-        billing: AddressDataTypes;
-        shipping: AddressDataTypes;
-      }>
-    ) {
-      state.billingAddress = action.payload.billing;
-      state.shippingAddress = action.payload.shipping;
-    },
-
-    resetAddressStep(_state) {
-      // Intentionally kept empty to retain previous selections while allowing re-entry to the step
-    },
-
-    resetShippingStep(_state) {
-      // Intentionally kept empty to retain previous selections while allowing re-entry to the step
+      state.cart = null;
+      state.loading = false;
+      state.error = null;
     },
 
   },
+  extraReducers: (builder) => {
+    builder
+      .addCase(fetchCartDetail.pending, (state) => {
+        state.loading = true;
+        state.error = null;
+      })
+      .addCase(fetchCartDetail.fulfilled, (state, action) => {
+        state.loading = false;
+        state.cart = action.payload;
+      })
+      .addCase(fetchCartDetail.rejected, (state, action) => {
+        state.loading = false;
+        state.error = action.payload as string;
+      });
+  },
 });
 
 export const {
   addItem,
   updateCart,
   clearCart,
-  setBillingAddress,
-  clearBillingAddress,
-  setShippingAddress,
-  clearShippingAddress,
-  setCheckoutAddresses,
-  resetAddressStep,
-  resetShippingStep,
 } = cartSlice.actions;
 
 export default cartSlice.reducer;

+ 76 - 0
src/store/slices/currency-slice.ts

@@ -0,0 +1,76 @@
+import makeClient from "@/lib/ApolloClientBrowser";
+import { createSlice, createAsyncThunk } from "@reduxjs/toolkit";
+import {CurrencyItem} from "@/types/currency/type";
+import { GET_CURRENCIES } from "@/graphql";
+
+interface CurrencyState {
+  currencies: CurrencyItem[]
+  loading: boolean;
+  error: string | null;
+}
+
+// 定义异步 action
+export const fetchCurrencyList = createAsyncThunk(
+  'currencyList/fetchCurrencyList',
+  async (_, { rejectWithValue }) => { //_ 占位,表示不需要参数
+    try {
+      const apolloClient = makeClient();
+      //@TODO 后期要改成缓存
+      const res = await apolloClient.query({
+        query: GET_CURRENCIES,
+        fetchPolicy: 'network-only', // 按需调整
+        context: {
+            fetchOptions: {
+                cache: 'no-store',
+            },
+
+        }
+      });
+      const responseData = res.data?.currencies?.edges;
+      if(responseData) {
+        return responseData.map((item) =>  item.node );
+      } else {
+        return [];
+      }
+      
+    } catch (error) {
+      let errorMessage: string;
+      if (error instanceof Error) {
+        errorMessage = error.message;
+      } else {
+        errorMessage = String(error);
+      }
+      return rejectWithValue(errorMessage);
+    }
+  }
+);
+
+
+const initialState: CurrencyState = {
+  currencies: [],
+  loading: false,
+  error: null,
+};
+
+const currencySlice = createSlice({
+  name: "currencyList",
+  initialState,
+  reducers: {},
+  extraReducers: (builder) => {
+    builder
+      .addCase(fetchCurrencyList.pending, (state) => {
+        state.loading = true;
+        state.error = null;
+      })
+      .addCase(fetchCurrencyList.fulfilled, (state, action) => {
+        state.loading = false;
+        state.currencies = action.payload;
+      })
+      .addCase(fetchCurrencyList.rejected, (state, action) => {
+        state.loading = false;
+        state.error = action.payload as string;
+      });
+  },
+});
+
+export default currencySlice.reducer;

+ 33 - 10
src/store/store.ts

@@ -1,16 +1,39 @@
 import { configureStore } from "@reduxjs/toolkit";
 
-import cartSlice from "./slices/cart-slice";
+import cartSlice, {CartState} from "./slices/cart-slice";
 import userSlice from "./slices/user-slice";
 import addToCartDialogReducer from "./slices/addToCartDialogSlice";
+import currencySlice from "./slices/currency-slice";
+
+// 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) => {
+    return configureStore({
+      reducer: {
+        cartDetail: cartSlice,
+        user: userSlice,
+        addToCartDialog: addToCartDialogReducer,
+        currencyList: currencySlice
+      },
+      preloadedState
+    });
+}
+// Infer the type of makeStore
+export type AppStore = ReturnType<typeof makeStore>
+// Infer the `RootState` and `AppDispatch` types from the store itself
+export type RootState = ReturnType<AppStore['getState']>
+export type AppDispatch = AppStore['dispatch']
 
-export const store = configureStore({
-  reducer: {
-    cartDetail: cartSlice,
-    user: userSlice,
-    addToCartDialog: addToCartDialogReducer,
-  },
-});
 
-export type RootState = ReturnType<typeof store.getState>;
-export type AppDispatch = typeof store.dispatch;

+ 0 - 0
src/types/cart/type.ts


Some files were not shown because too many files changed in this diff